UE4中使用PSO缓存优化
1、简述
在首次进入项目时,第一次加载新场景,或打开RT缩略图的卡顿;在第二次进入游戏后不会,实则是编译新着色器时项目出现的卡顿耗时,可以通过PSO缓存方式优化加载时间:
UE 中的 PSO Caching 机制,用于预先记录和构建出运行时所使用的材质依赖的 Shader 信息,当项目首次使用这些 Shader 时,该列表可以加速 Shader 的加载 / 编译过程。PSO Caching 会把渲染状态、顶点声明、Primitive 类型、RenderTarget 像素格式等数据保存到文件中,提升 Shader 的加载效率。
相关官方介绍:PSO缓存 | 虚幻引擎文档 (unrealengine.com)
2、构建流程
- DefaultEngine.ini修改,在执行Cook时生成稳定的ShaderKey,作为记录Shader的凭据并打包
[DevOptions.Shaders]
NeedsShaderStableKeys=true
-
打包会生成2个scl.csv 对应项目、引擎,可以查看到有10w+cook项
-
配置里加入并打包
[ConsoleVariables]
r.ShaderPipelineCache.Enabled=1
r.ShaderPipelineCache.LogPSO=1
r.ShaderPipelineCache.SaveBoundPSOLog=1
启动游戏,运行会在手机生成.upipelinecache(运行时捕获的PSO数据)
此处PSO采集尽可能的多会有助于生成的数据全面
android下可以在内部存储-\Android\data\YourGame\files\UE4Game\YourGameName\YourGameName\Saved获取
ios下由于其沙盒机制,不可直接访问文件,可以连接mac后,打开xcode下载Container获取
4. 根据上述3个文件,放入C:/PSOCaching写.bat脚本,使用UE4Editor-Cmd.exe生成.stablepc.csv
android内容如下:
YourEnginepath\UnrealEngine\Engine\Binaries\Win64\UE4Editor-Cmd.exe YourProjectpath.uproject -run=ShaderPipelineCacheTools expand C:\PSOCaching\*.rec.upipelinecache C:\PSOCaching\*.scl.csv C:\PSOCaching\YourProjectName_GLSL_ES3_1_ANDROID.stablepc.csv
ios内容如下:
C:\Project\Z_Plan\Project_G_Engine\UnrealEngine\Engine\Binaries\Win64\UE4Editor-Cmd.exe C:\Project\Client_ZPlan\Project\YourProjectName.uproject -run=ShaderPipelineCacheTools expand C:\PSOCaching_IOS\*.rec.upipelinecache C:\PSOCaching_IOS\*.scl.csv C:\PSOCaching_IOS\YourProjectName_SF_METAL_IOS.stablepc.csv
- stablepc.csv 放到本地的Build/Android/PipelineCaches下再次进行打包,引擎在Cook时通过stavlepc.csv创建PipelineCache,如此一来,新的pak包就可以读取加载PSO缓存。
- 如何最小成本放到项目中去,即作为一个资源,可以将流程的5略过,进行如下调整,把本质上引擎内的commandlet拿出来,可参考引擎中的ShaderPipelineCacheToolsCommandlet.cpp,执行BuildPSOSC来生成.stable.upipelinecache追加一个bat脚本如下:
android内容如下:
YourEnginepath\UnrealEngine\Engine\Binaries\Win64\UE4Editor-Cmd.exe YourProjectpath.uproject -run=ShaderPipelineCacheTools build "YourProjectpatht\Build\Android\PipelineCaches\*YourProjectName_GLSL_ES3_1_ANDROID.stablepc.csv" "YourProjectpath\Saved\Cooked\Android_ETC2\YourProjectName\Metadata\PipelineCaches\ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv" "YourProjectpath\Saved\Cooked\Android_ETC2\YourProjectName\Metadata\PipelineCaches\ShaderStableInfo-YourProjectName-GLSL_ES3_1_ANDROID.scl.csv" "YourProjectpath\Saved\Cooked\Android_ETC2\YourProjectName\Content\PipelineCaches\Android\YourProjectName_GLSL_ES3_1_ANDROID.stable.upipelinecache"
ios内容如下:
C:\Project\Z_Plan\Project_G_Engine\UnrealEngine\Engine\Binaries\Win64\UE4Editor-Cmd.exe C:\Project\Client_ZPlan\Project\YourProjectName.uproject -run=ShaderPipelineCacheTools build "C:\Project\Client_ZPlan\Project\Build\IOS\*YourProjectName_SF_METAL_IOS.stablepc.csv" "C:\Project\Client_ZPlan\Project\Saved\Cooked\IOS\*.scl.csv" "C:\PSOCaching_IOS\YourProjectName_SF_METAL.stable.upipelinecache"
这样会生成一个.upipelinecache,放进项目里的Content\PipelineCaches\platform下面,这样就可以作为一个资源进行打包和热更了,最终实现效果与步骤5一致。
最终区分平台保存upipelinecache资源后的路径如下:
Content\PipelineCaches\Android\YourProjectName_GLSL_ES3_1_ANDROID.stable.upipelinecache
Content\PipelineCaches\IOS\YourProjectName_SF_MTEAL.stable.upipelinecache
3、测试结果
以下为安卓独立app,实测同一场景下多次测试下的平均值:(上下略有误差在100ms以内)数据可以作为参考,实际优化的多少视你项目场景的复杂性而定。
首次进入游戏
打开页面总耗时(ms) | 加载小地图0用时(ms) | 加载小地图1用时(ms) | 加载小地图2用时(ms) | 加载小地图3用时(ms) |
---|---|---|---|---|
2793.0 | 1192.0 | 238.0 | 821.0 | 536.0 |
清空缩略图本地缓存后
打开页面总耗时(ms) | 加载小地图0用时(ms) | 加载小地图1用时(ms) | 加载小地图2用时(ms) | 加载小地图3用时(ms) |
---|---|---|---|---|
1349.0 | 334.0 | 91.0 | 534.0 | 384.0 |
使用PSO Cache首次进入游戏,(与清空缓存后打开几乎没有差别)
打开页面总耗时(ms) | 加载小地图0用时(ms) | 加载小地图1用时(ms) | 加载小地图2用时(ms) | 加载小地图3用时(ms) |
---|---|---|---|---|
1267.0 | 340.0 | 78.0 | 504.0 | 341.0 |
本地有缩略图缓存下:
打开页面:114ms
模拟资源更新
以上为打包为apk测试结果,为模拟热更资源效果是否生效,即跳过步骤5,6进行打包,手机安装完成后,将upipelinecache资源手动复制到手机缓存路径下的Content\PipelineCaches\Android:
再次进行测试,结果与上述一致,说明该资源可经过热更达到优化效果
4.更改路径和热更:
为了符合不同项目的需求,可以考虑从几个方面进行修改:即PSO启动顺序、项目名称、项目路径的更改,来保证后续资源热更到自己想要的路径之下。
由流程可知,最终我们需要的upipelinecache放进了项目里的Content\PipelineCaches\Android下面,
引擎在启动时加载PSO Cache,会执行OpenPipelineFileCache函数,这里的路径在引擎写死,即读取路径为content/PipelineCaches/PlatformName,如下:
FString GamePathStable = FPaths::ProjectContentDir() / TEXT("PipelineCaches") / ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName()) / FString::Printf(TEXT("%s_%s.stable.upipelinecache"), *FileName, *PlatformName.ToString());
想要实现更改,需要做如下步骤:
1、配置文件中限制引擎启动时就会自动加载PSO Cache,并且添加你自己的项目名称ProjectName
[ConsoleVariables]
r.ShaderPipelineCache.Enabled=0
r.ShaderPipelineCache.LogPSO=0
r.ShaderPipelineCache.SaveBoundPSOLog=0
r.ShaderPipelineCache.ProjectName=YourGameName
2、在进入游戏后,再手动执行引擎内的Load函数:在脚本文件中加入:(当然这些原生自C++,可以用任意语言调用即可)
UE4.UFlibShaderPipelineCacheHelper.EnableSaveBoundPSOLog(true)
UE4.UFlibShaderPipelineCacheHelper.EnableShaderPipelineCaches(true)
UE4.UFlibShaderPipelineCacheHelper.LoadShaderPipelineCache("YourGameName")
3、在引擎侧修改,当项目名称为项目时的路径特殊处理:
//读取配置中的ProjectGName
FString ProjectName;
GConfig->GetString(TEXT("ConsoleVariables"),TEXT("r.ShaderPipelineCache.ProjectName"),ProjectName,*GEngineIni);
if (FileName == ProjectName)
{
GamePathStable = FPaths::ProjectContentDir() / TEXT("YourGamePath/PipelineCaches") / ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName()) / FString::Printf(TEXT("%s_%s.stable.upipelinecache"), *FileName, *PlatformName.ToString());
GamePath = FPaths::ProjectContentDir() / TEXT("YourGamePath/PipelineCaches") / ANSI_TO_TCHAR(FPlatformProperties::IniPlatformName()) / FString::Printf(TEXT("%s_%s.upipelinecache"), *FileName, *PlatformName.ToString());
}
经过测试,最终情况为:
在进入项目后(而非引擎启动时)加载PSO,并可以经过热更在content/YourGamePath/PipelineCaches/PlatformName下生效。
5.总结:
可以看出使用PSO 缓存优化可以解决首次进入游戏,减少材质需要编译新着色器时项目可能出现的卡顿问题。
代价:理论上由于需要打包额外的PipelineCaches,需要牺牲磁盘空间,测试下独立app下磁盘上升约100kb,缓存列表主要充当字典,故上升的磁盘空间不多,大小主要受到流程3中运行时捕获的PSO数据影响。