UE引擎源码阅读笔记(2):资源cook、多进程cook

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子进程任务:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值