UE引擎源码阅读笔记(2):资源cook、多进程cook
https://zhuanlan.zhihu.com/p/715567279
1. UE擎资源编译COOK:
Cook 是 Unreal Engine 中的一个重要概念,指的是将项目中的各种资源(如纹理、模型、声音等)进行打包和优化,生成可供游戏运行时加载和使用的文件,通常是.uasset格式的文件。
比如原始贴图16M 压缩成astc格式之后大小不到2M,无论对手机内存还是IO来说都有极大的优化。
对引擎进行断点之后,后续代码分析就比较简单分析了,如cook流程、贴图mip处理,贴图压缩、多进程cook。
1.1 cook的模式:
UE对于资源的cook主要有以下几种:
CookOnTheFly:(快速调试模式)它允许开发者在不预先烹饪游戏资源的情况下快速测试游戏。在开发过程中,cook是将游戏资源(如纹理、模型、声音等)处理成能够在目标平台运行的格式。CookByTheBook意味着每次更改游戏内容时都需要重新烹饪整个项目,这可能是一个非常耗时的过程。通过使用CookOnTheFly模式,开发者可以在运行游戏时仅烹饪所需的资源。这就意味着无需频繁烹饪整个项目,从而加快开发和测试速度。在实践中,开发者需要启动一个专用的烹饪服务器,游戏实例将根据需要从该服务器请求资源。这使得开发者可以在项目开发阶段更快地进行迭代和调整。(平常编辑器测试,编辑器会启动一个cookserver,打包出来一个游戏client,两个通过TCP/platform方式连接)
例子可以参考如下动图:(用的较少)
2. CookByTheBook: 本地编译资源,PAK合并成大文件,平常打包发布。在CookByTheBook模式下,所有游戏所需资源都会在部署和运行游戏之前,经过预处理。当你发布最终版本的游戏时,这种烹饪方式相比CookOnTheFly,可以获得更好的性能、更小的压缩文件尺寸以及更快的加载速度。
3. CookWorker : 多进程模式下 子进程作为cook任务运行模式。
2. 如何进行资源cook & 断点分析(UE5.3版本举例)
选择指定平台进行cook
选择指定平台cook
执行cook的命令行
拿到命令行之后再VS中断点
3. 引擎COOK 关键类以及流程图:核心类 UCookOnTheFlyServer
3. 1. CookOnTheFly模式(项目中使用较少):
核心类图如下:
2. CookByTheBook模式(资源打包发布流程):
cookontheBook核心类关系
执行 CookByTheBook的核心类对象为:UCookOnTheFlyServer。本质是收集需要cook的对象列表然后从DDC里面拿到这些对象的cache,然后编译成对应平台的资源。
核心执行流程如下:
+-----------------------------+
| CookByTheBook执行流程 |
| +-----------------------+ |
| | UCookCommandlet.cpp |
| |-----------------------------| |
| | - Main(const& CmdLineParams)() |
| | - CookByTheBook(TPM.GetActiveTargetPlatforms()) |
| +-----------------------+ |
| | |
| v |
| +-----------------------+ |
| | UCookOnTheFlyServer | |
| |-----------------------| |
| | - void Initialize( ECookMode::Type DesiredCookMode, ECookInitializationFlags ,
| | const FString& OutputDirectoryOverride = FString() ); | |
| | - StartCookByTheBook(StartupOptions);| |
| | - while(IsInSession()) //轮询cook结果 {
| | //核心tick,检查request状态
| | - TickCookByTheBook(const float TimeSlice, ECookTickFlags TickFlags = ECookTickFlags::None);
| | - --TickMainCookLoop(StackData);
| | - } | |
| +-----------------------+ |C
对cook过程重点关注 FPackageDatas的四个数据队列:FPackageData的SendToState()函数
在 UCookOnTheFlyServer::StartCookByTheBook()
-BlockOnAssetRegistry();
-BeginCookPackageWriters(BeginContext);
-GenerateInitialRequests(BeginContext);
-- CollectFilesToCook(FilesInPath, FilesInPathInstigators, CookMaps, CookDirectories)。。
将需要cook的资源收集起来后,接下来就是资源执行cook操作,UE源码轮询检查队列状态。
uint32 UCookOnTheFlyServer::TickCookOnTheSide(const float TimeSlice, uint32 &CookedPackageCount, ECookTickFlags TickFlags)
{
UE::Cook::FTickStackData StackData(TimeSlice, IsRealtimeMode(), TickFlags);
//更新cook状态 &根据当前状态 然后对task做不同阶段处理,如 取一个新的task,保存task
TickCookStatus(StackData);
ECookAction CookAction = DecideNextCookAction(StackData);
switch (CookAction)
{
case ECookAction::Request:
//UE的单个资源cook 是放在引擎多线程队列中的,将task放到多线程队列中
PumpRequests(StackData);
bLoadBusy = false;
break;
case ECookAction::Load:
//加载下一个task
PumpLoads(StackData, 0);
bSaveBusy = false;
break;
case ECookAction::LoadLimited:
PumpLoads(StackData, DesiredLoadQueueLength);
bSaveBusy = false;
break;
case ECookAction::Save:
//将cook完成的task保存下来
PumpSaves(StackData, 0);
break;
case ECookAction::SaveLimited:
PumpSaves(StackData, DesiredSaveQueueLength);
break;
case ECookAction::Done:
bContinueTick = false;
bCookComplete = true;
break;
case ECookAction::YieldTick:
bContinueTick = false;
break;
case ECookAction::Cancel:
CancelCookByTheBook();
bContinueTick = false;
break;
default:
check(false);
break;
}
}
本地执行cook的核心逻辑图:
最后cook执行PumpSaves 函数之后会保存到指定的cooked文件夹。下面会执行多线程加入到引擎的多线程框架里面。
加入到引擎多线程架构中,开启task
3.3 多进程COOK打包模式 :(UE5.3新引入的多进程COOK 大幅加速打包时间)
整体代码阅读下来发现:
UE Cook本身执行的是 CommandLet 即单独的一个轻量的exe ,Unity需要打开并且调用静态方法,偏重度
UE的Cook多进程通过socket网络通信分发任务的
另外因为多进程是UE新加的,在不影响原始cook模式下,加入了大量的插装代码,逻辑也比较清晰。
子进程加入了大量的:IsCookWorkerMode() ,CookWorkerClient 判断作为子进程。
主进程 加入了 if (CookDirector)这样的字段来插装,
大量的容错机制,主进程同时作为分发任务和执行cook任务
对分发任务做了负载计算,平均分配和根据图做分配任务。对失败异常任务回主进程执行cook
Cook任务多进程之后能大幅降低原始多线程cook任务。后续仍有可能性做成分布式任务。
多进程COOK的核心主要类图:
如何开启多进程cook:
1. 配置 cookprocesscount > 1https://github.com/EpicGames/UnrealEngine/commit/9a0837f98c13e137714dae084de0f5a03b207a16
2. 命令行传参数:CommandLine传参数 : -CookProcessCout
3. 多进程Cook流程图:
4. 创建worker子进程任务: