UE3 Cook解析

发表于2017-03-10
评论0 7.8k浏览
   Cook可以将内容转换为当前平台可使用的格式,考虑到有些开发者还不熟悉Cook流程,下面就给大家解析下Cook的使用,希望能帮到大家。

UE3 Cook介绍
Cook简介
  Cook是指如何转换内容为当前平台支持的格式,CookPC(PLATFORM_Windows)为Windows客户端使用,CookPCServer(PLATFORM_WindowsServer)为服务器使用,这将会产生更快的加载速度。

Cook工作
1、去掉一些不必要的数据,比如editor-only数据。
2、Byte-swappping数据使之可以被big-endian架构读取。
3、使一些资源可以以他们的nativeformat保存,可以直接读取不需要更多的处理。
4、创建SeekfreePackage,self-contained packages,获得最优的loading时间

Cook的内容


UE3 Cook解析

   Cooker使用Unreal commandlet(UCookPackagesCommandlet),通过加载包、处理它们然后输出最后数据到Cooked目录下。加载的包列表由命令行开关和.ini设置决定,Cook被保存到一个Cook目录中,它的格式是TGameCookedXX,如TGameCookedPC、TGameCookedPCServer,以下解析一些Ini设置:
1、[Engine.PackagesToAlwaysCook]
+SeekFreePackage=UTGameContent
+SeekFreeDirectory=AvatarAttachItem
+SeekFreePackage=FamilyAngelGirl_Torsor_001_F
+SeekFreePackage=FamilyAngelGirl_Arms_001_F
  其中+Package表示你确定需要cooked的package,比如UTFrontend包。
  +SeekFreePackage表示可以动态被任意level读取的standalone seekfree package。
如不Cook成seekfree包,对于外部包加载是阻塞加载会引发卡顿。
  现在我们把所有的角色和挂件都设置为SeekfreePackage。
2、[Engine.StartupPackages]
  +Package=FX_HitEffects
  表示被所有level使用的数据,会放到startuppackage,启动被读取后会一直放在内存中.
3、[Engine.PackagesToForceCookPerMap]
.Map=UTFrontEnd
.Package=UI_Skin_Derived
.Package=UI_Skins
表示只被一个level动态读取的数据,会强制cook到map数据中去.

Cook流程
  以下详细解释每个阶段。
1、Cook init
   Init 定义Cook方式,比较重要的定义:
  是否进行全量COOK(bForceFullRecook)
  TextureFileCache (逆战不使用忽略)
  Cook Platform 客户端使用PLATFORM_PC和服务器使用PLATFORM_WindowsServer
  a、ParseCommandLine定义本次Cook的行为方式。
  b、GlobalPersistentCookerData 保存Cook信息:增量Cook首先需要读取GlobalPersistentCookerData,然后检查资源包Version,如Version有变强制执行FullReCook,若GlobalPersistentCookerData包不存在也要指定Full ReCook。
  c、Force the current settingsto the highest when running the cooker and use those。
  d、NativeScript包处理:
  加载所有NativeScriptPackages ,且所有包内object AddToRoot。
  为所有Native包对象,Native包的LinkerLoad对象设置Flag(RF_MarkedByCooker),打上此Flag的对象不Cook进Seekfree包。
  创建得到GuidCache:保存每个对象唯一的GUID信息,服务器和客户端通过这个其保证双方资源一致。
  创建Container helperPersistentCookerData,用于跟踪Bulk data数据(如贴图的MipData数据)。
  初始化部分包的列表提供后续使用:
  从defaultEngine.ini 下PackagesToAlwaysCook节点得到AlwaysCookPackages列表,注意仅取Package(UTFrontEnd),不包括SeekFreePackage。
  Gfx相关包也存入AlwaysCookPackages。
  对AlwaysCookPackages做出分类,分别放入不同的列表TokenMaps(地图包),TokenScriptPackages(Script包),TokenContentPackages(资源包)。这些列表会决定后续包的Cook方式。

GeneratePackageList 生成需要Cook的文件列表
a、需要了解若干重要Package列表:
  EngineScriptPackage:函数appGetEngineScriptPackageNames内指定EngineScriptPackage

UE3 Cook解析

  GameNativeScriptPackage:函数appGetGameNativeScriptPackageNames内指定
  包括刚才的appGetEngineScriptPackageNames和appGetGameNativeScriptPackageNames,新增的Native包都需要在这里定义,如生化模式Native包TGBioGame。
  GameScriptPackage: 函数appGetGameScriptPackageNames内指定,其定义了所有游戏的Native包和内容Content包。
  AllStartupPackageNames:
  SeekFreeAlwaysCookMaps(StandaloneSeekfree): DefaultEngine节点内指定的SeekFreePackage
  SeekFreeAlwaysCookMaps(StandaloneSeekfree):DefaultEngine节点内指定的SeekFreeDirectory包含的Package
  GFx_AddExtraPackages: GfxUI相关包
  NativeScriptFile:EngineScriptFile+ GameNativeScriptFile,对于EngineScript包都属于Native包。
  Seekfree Package:MapFile+ NativeScriptFile + CombinedStartupPackage + StandaloneSeekfree
  还有在Init阶段构造的列表列表:TokenMaps(地图包),TokenScriptPackages(Script包),TokenContentPackages(资源包)。
  这些列表的关系:
  AllStartupPackageNames 
        = NativeScriptPackage + NonNativeStartupPackage + 相关LocalizedPackages
NativeScriptPackage
        = EngineScriptPackage + GameNativeScriptPackage + 相关LocalizedPackages
PackagesToAlwaysCook  = Package + SeekFreePackage + SeekFreeDirectory。
b、分析Package是否需要重新Cook,要经过2个Pass
  pass 0是针对于正常Package分析过程,正常包只需要经历此阶段即可。
  地图包需要进行Pass 1过程的分析,StartupPackage和NativePackage两个阶段都需要进行。
  针对StartupPackage Pass0是对针对其进行的特殊Out of Date检测,Pass 1直接把所有Startup包放入StartupFilenamePairs列表中后续做Cook。
  对于Map包是不需要走 Pass0定义的针对于StartUpPackage的Outof Data检测的,因为map不可能是StartUpPackage。
  对于NativePackage Pass0 在非bCoderMode模式把Native包被标记为bForceCookNativeScript,Pass1做Out Date检测。
c、分析流程
  我们把Cook源文件简称为SrcFile,生成的文件简称为DstFile方便下面说明,此流程非常重要决定那些包需要重新COOK:
  获取DstFile时间戳:Seekfree包直接从CookedPC目录对应文件中获取,非Seekfree包时间戳存在PersistentCookerData中。
  获取SrcFile时间戳:直接从源文件中获取。
  判断DstFile时间戳是否比SrcFile文件新:
  此处常常出问题,PM喜欢从主线拷贝资源文件到分支,由于DstFile比SrcFile新,资源文件不会重新Cooked,建议分支线上通过SVN更新资源。
  判断DstFile是否存在,并从PersistentCookerData得到其版本信息,看其是否是最新版本。
  在以上流程中,满足如下条件资源包需要Cook:
  SrcFile时间戳比DstFile新。
  DstFile不存在。
  如果是Seekfree Package(CombinedStartupPackage除外):依赖包检测AreSeekfreeDependenciesNewer结果为有变更。
  对于CombinedStartupPackage,要检测StartUpPackage,SrcStartupFile比DstStartupFile新。
  对于StandaloneSeekfreePackage(SeekFreeAlwaysCookMaps相关包),要检测CookedPC下的Seekfree包(后缀_SF),SrcFile比DstSeekfreeFile新。
  把变更的文件存入以下列表:
  NotRequiredFilenamePairs:非Seekfree普通资源包,StartUp包指定的资源包也会按照非Seekfree方式Cook一份。
  MapFilenamePairs :地图包。
  ScriptFilenamePairs:NonNativeScriptFile和NativeScriptFile。
  StandaloneSeekfreeFilenamePairs:SeekFreeAlwaysCookMaps内指定包。
  StartupFilenamePairs:StartUp包。
  注意:对于StandaloneSeekfreePackage会分别存入NotRequiredFilenamePairs和StandaloneSeekfreePackage,Cook非Seekfree包和带_Sf的StandaloneSeekfreePackage
  根据上述列表生成需要Cook包列表SortedFilenamePairs,并且是有序的,其顺序为:
  a、NotRequiredFilenamePairs列表:non-script,non-map content Pacages
  b、EngineScriptPackage
  c、GameNativeScriptPackage
  d、GameScriptPackage
  e、StartupPackage
  f、StandaloneSeekfreePackage
  g、MapPackage

3、CookPackage流程
a、首选需要了解若干包重要属性:
   bShouldBeSeekFree:
MapPackage,NativeScriptPackage, StandaloneSeekfreePackage,StartupPackage 都为SeekFree包
  bIsNativeScriptFile:
   包括NativeScriptPackage,StandaloneSeekfreePackage,StartupPackage
  bIsCombinedStartupPackage:
   StartupPackage
  bIsStandaloneSeekFreePackage:
   GaneratePackageList阶段生成的StandaloneSeekfreeFilenamePairs指定的包
  bShouldOnlyLoad:
   如果StartUpPackage未设置bForceCookStartupPackage,那么仅需加载使用不需要重新COOK.
  bIsNonNativeScriptFile:
  GaneratePackageList阶段生成的ScriptFilenamePairs NonNativeScriptFile部分
b、Cook之前先执行一次CollectGarbageAndVerify确保环境干净。
  执行包的Cook流程:

UE3 Cook解析

  以下详细解释这一流程
  a、LoadPackageForCooking,加载并反返回需要Cook的包,并设置PKG_Cooked标记(避免重复Cook).
  LoadPackage 加载ScrFile,若是Script包请保证其是Releasemode。
  GuidCache中PackageGuidMap以PackageName为Key存入Guid。
  b、遍历包内所有设置RF_ForceTagExp的对象和包内对象执行CookObject,核心代码:

UE3 Cook解析

RF_ForceTagExp对象:对于Seekfree包,LoadPackage的时候读取到的所有关联object也会设置此标记,核心代码:
CookObject都会执行StripData删除和平台相关的数据,并设置上RF_Cooked标记避免Re-cooking。
  针对不同的对象类型又有不同的Cook流程,以下详细解释:
  CookTexture
  删除PVRTC data。
  对于需要在加载过程中进行贴图类型转换的,不进行Streaming。
  对于TEXTUREGROUP_UI贴图只保留最高精度贴图。
  执行CalculateLODBias方法进行LOD Setting计算得到需要使用的贴图精度。
  加载所需使用贴图mipData,若是Seekfree包,Texture做以下处理:
  若Texture->IsIn(Package),则所有Mipdata数据保存在本包。
  若贴图不存在包内,MipData是存在SeparateFile包内,这里不需要保存所有MipData数据。
  CookStaticMesh, CookSkeletalMesh 只有PS3平台需要执行,忽略。
  CookAnimTree 删除所有的PreviewMeshList,PreviewSocketList,PreviewAnimSetList数据,此数据只有编辑器需要使用。
  CookSoundNodeWaveLocSoundNodeWave指定相应国际化信息,删除CompressedPS3Data,CompressedXbox360Data平台相关数据。
  CookMovieTexture 需要对Movie关联贴图进行数据转换
  CookParticleSystemPLATFORM_Stripped模式下的特殊处理,CookPC无需关注。
  CookFaceFXInterpTrack、CookSeqActPlayFaceFXAnim、和FaceFX相关数据处理,后续研究,暂时不关注。
  在CookObject中,Object->StripData(Platform)会根据不同的数据类型,去除bulkdata.
  AnimSequence会去除raw animation数据,StaticMesh会去除raw triangles,Textures会去除sourceart,但例如SkeletalMesh等其他类型的package不会去除原数据.
  c、遍历所有设置RF_ForceTagExp的对象和包内对象执行PrepareForSaving流程:
  贴图:针对Seekfree包需要区分那些精度保存_SF包中,并打上StoreInSeparateFile状态,剔除UnusedMipLevel。
  材质:确保材质都编译好,并加入到相应ShaderCache中。
  d、SaveCookedPackage
  以SrcFile文件时间戳作为DstFilename的时间戳存入PersistentCookerData中,G enratePackageList需要读取此时间戳。
  确保所有RF_ForceTagExp对象关联的Outer都设置上RF_ForceTagExp

UE3 Cook解析

  创建Cooked文件目录,所有的UShaderCache需要清除RF_ForceTagExp标记,防止Cook到包内.
  将DstFile的版本号存入PersistentCookerData中
  调用SavePackage把当前内存对象存入CookedPC对应的包中,关键步骤为:
  遍历内存中对象,对于在包内的RF_Standalone对象(所有所有包的导出对象默认为RF_Standalone),使用FArchiveSaveTagExports执行Serialize,     此过程中涉及的对象如果是【包内对象】或者【带RF_ForceTagExp标记】,设置标记RF_TagExp(后续讲作为导出表对象)。
  对于非Seekfree包:ScrFile内的导出表对象将作为DstFile的导出对象。
  RF_ForceTagExp标记:
  对于bShouldBeSeekFree(Seekfree包):
  a、内存中非RF_Transient的包外对象都会为设置RF_ForceTagExp。
  b、内存中非RF_Transient的包外class对象及其关联的所有SuperClass对象都需要设置RF_ForceTagExp标记。
  c、理论上所有当前Cook包关联的所有对象都会设置上RF_ForceTagExp标记,但针对于RF_MarkedByCooker例外。
  RF_MarkedByCooker标记:
  先看以下的例子:
Package P:
  - A (private)
  - B (public, references A)
  - C (public, references A)
Map M:
    - MObj (references B)
Startup Package S:
    - SObj (referneces C)
  当Startup包cooked的时候,他会把C 和 ACook到包内。
  当地图包M cook的时候,他只会把Bcook进来不包含A,因为A是包M的Importbject。凡是带有RF_MarkedByCooker标记的对象都属于A这样的Private对象。
  a、所有的CombinedStartupPackage包内的所有对象都设置为RF_MarkedByCooker。
  b、对于LocalizedResource也会设置为RF_MarkedByCooker,不会Cook到Seekfree包中去。
  c、Cooked流程多处使用RF_MarkedByCooker控制对象是否Cook到包中去,比如PLATFORM_Stripped(GCookingTarget)使用它删除了包内的资源数据,
  d、后续继续补充更多RF_MarkedByCooker情况。
  创建DstFile的LinkerSave
  遍历所有导出表对象,得到:
  a、导出表对象依赖的Class(Obj->GetClass())。
  b、导出表对象依赖Archetype(Obj->GetArchetype())。
  c、导出对象本身。
  d、Class,Archetype ,Object执行Serialize过程中得到关联的对象,若关联对象没有设置RF_TagExp标记,则为导入表对象(RF_TagImp)。
  使用这三部分对象构造ImportTagger。ImportTagger存入依赖表ObjectDependencies,后续作为导入表对象存入ImportMap中。
  构造Linkder Summary.
  得到Linker->NameMap
  保存Linker->NameMap,并构造NameIndices索引
  Build ImportMap 导入表,当前内存带RF_TagImpflag为导入表对象
  Build ExportMap导入表,当前内存带RF_TagExpFlag为导入表对象
  构造ImportToIndexMap (ImportObject, -Index)和ExportToIndexMap(ExportObject, Index),方便后续跟踪对象情况。
  构造ExportMap列表中对象依赖的信息,有可能依赖于ExportToIndexMap或ImportToIndexMap,最后存入DependsMap列表。
  建立ExportMap和importMap的索引ObjectIndices。
  保存importMap和ExportMap,Depends,ImportGuids(来源于Outer->ExportGuids)。
  最后结束Linker文件头的保存。
  逐个保存Export对象,分为两种情况:ExportMap中Export._Object->GetOuter() ==InOuter,Export._Object->GetOuter()->IsIn(InOuter),InOuter is this package。
  逐个保存Import对象。
  关于UE3中的Cook使用就介绍这么多了,希望能帮到大家正在做的游戏项目。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引