type
status
date
slug
summary
tags
category
icon
password
UE项目优化:PSO Caching
UE中具有PSO Caching机制,全称Pipeline State Object Caching,用于预先记录和构建出运行时所使用的材质依赖的Shader信息,当项目首次使用这些Shader时,该列表可以加速Shader的加载/编译过程。PSO Caching会把渲染状态、顶点声明、Primitive类型、RenderTarget像素格式等数据保存到文件中,提升Shader的加载效率。本篇文章主要介绍PSO Caching的启用及构建流程,并会分析PSO Cache在引擎中的加载流程以及实现热更PSO方式、错误处理等,PSO Caching的原理有时间再进行详细分析。 PSO Caching的官方文档: PSO Caching 。 PSO Cache构建流程概览: PSO Caching的部署和使用大致分为以下几个步骤: 为项目开启PSO Cahche和ShaderStableKeys,打包后可以从Metadata/PipelineCaches目录下获得ShaderStableInfo*.scl.csv 添加logPSO参数启动游戏,用于在运行时记录PSO数据(*.rec.upipelinecache) 通过ShaderStableInfo*.scl.csv和*.rec.upipelinecache生成*.stablepc.csv 再次执行Cook,通过*.stablepc.csv生成upipelinecache文件,打至包内; 启动游戏,引擎自动加载*.stable.upipelinecache,编译Shader时使用PSO Caching 本篇文章的内容顺序也遵循着几个步骤。 首先需要为项目开启 ShaderStableKeys ,在执行Cook时生成稳定的ShaderKey,作为记录Shader的凭据。 在 DefaultEngine.ini (或平台相关如AndroidEngine.ini)中添加以下值: 添加之后再执行打包(Cook),会创建以下目录: 并且会在该目录下生成两个文件(分别对应项目、引擎): Cook过程中会有以下log,表明生成了这两个文件: 可以使用它们通过-run=ShaderPipelineCacheTools这个Commandlet来生成*.stablepc.csv 。 *.scl.csv 文件的内容: 启动游戏时加入-logPSO参数或者在 DefaultEngine.ini 中加入以下配置: 也可以在 Devices Profile中设置: 这两个参数在以下代码中使用: 运行游戏时会有log: 并会在 Saved/CoolectedPSOs 中创建以下文件: 使用以下commandlet: 以上命令会在引擎的Binaries/Win64下生成 Client_GLSL_ES3_1_ANDROID.stablepc.csv文件,注意一定要匹配{PROJECTNAME}_{SHADER_FORMART_NAME}.stablepc.csv 这个命名规则。 Android的命名为:Client_GLSL_ES3_1_ANDROID.stablepc.csvIOS的命名为:Client_SF_METAL.stablepc.csv 生成时具有以下Log: 最终PSO所需要的所有文件: 我把测试工程生成的文件备份了一份: PSOCache.7z ,可以查看每个文件中的内容。 把生成的*stablepc.csv放到 Build/Android/PipelineCaches目录下,注意 Build/PLATFORM这个Platform是编译平台,不是Cook的资源平台,Android的包就是 Android而不是 Android_ASTC等。之后重新打包即可。 引擎在Cook时通过 stavlepc.csv 创建PipelineCache的代码: 实际使用 stablepc.csv的地方就是用它来执行 ShaderPipelineCacheTools 这个commandlet生成upipelinecache文件并打至包内。 ShaderPipelineCacheToolsCommandlet的执行命令为: 生成*.stable.upipelinecache文件的包内路径为 Content\PipelineCaches\Android : 因为它是位于Content下并会打包进pak的文件,我们也可以对其进行热更。 当安装了包含 upipelinecache 的包,在运行时就会有以下log: 与 ShaderCode类似,引擎在启动时也是会自动加载PSO Cache的,在 FEngineLoop中通过调用 ...
UE项目优化:PSO Caching
 

载入

载入是在FEngineLoop::PreInitPostStartupScreen中发生OpenPipelineFileCache
逻辑是先从 \Content\PipelineCaches\Windows载入\Content\PipelineCaches\Windows\XXX_PCD3D_SM5.stable.upipelinecache
接着,检测是否需要ShouldLoadUserCache() ,这一步说的UserCache就是\Saved\xxx_PCD3D_SM5.upipelinecache 。判断条件是,是否能够开启r.ShaderPipelineCache.LogPSO ,以及r.ShaderPipelineCache.SaveUserCache 这两个flag。
notion image
notion image
SaveUserCache这个flag默认在mac的shipping版本是开的。对于这两个Flag,在Windows的Shipping版本也有强制开启。
WindowsRHI.cpp
但我个人觉得还是PSOFileCacheSaveUserCacheCVar.Set(1); 这种方式比较容易不产生怀疑吧。。
经过检测,这一调用的源头RHIInit()发生在FEngineLoop::PreInitPreStartupScreen()第2592行,而PreInitPostStartupScreen() 发生在FEngineLoop::PreInit3648行,在PreInitPreStartupScreen() 之后。说明变量赋值在变量调用之前。
所以在shipping版本中,它100%是_PCD3D_SM5.stable.upipelinecache +\Saved\xxx_PCD3D_SM5.upipelinecache 组合的结果。

退出及保存

notion image
保存在\Saved\xxx_PCD3D_SM5.upipelinecache
可以看到调用FShaderPipelineCache::Close()时会保存:
notion image
在析构时保存:
notion image
除此之外,运行时也会自动保存:
notion image
同样需要开启r.ShaderPipelineCache.SaveUserCache 这个flag,当然它默认在shipping中是打开的。

结论

结论是,载入时,一定会载入\Content\PipelineCaches\Windows\XXX_PCD3D_SM5.stable.upipelinecache 如果存在的话。
如果开了r.ShaderPipelineCache.LogPSOr.ShaderPipelineCache.SaveUserCache,则还会载入本地缓存的\Saved\xxx_PCD3D_SM5.upipelinecache
关于保存,如果开启了r.ShaderPipelineCache.SaveUserCache ,会尝试去保存,是否能保存还取决于 r.ShaderPipelineCache.Enabled r.ShaderPipelineCache.LogPSO ,结果会保存在\Saved\XXX_PCD3D_SM5.upipelinecache 。触发保存的时机分别在FShaderPipelineCache::Tick()FShaderPipelineCache::Close() ,FShaderPipelineCache::~FShaderPipelineCache() 以及PipelineStateCacheOnAppDeactivate()
 
总结而言,默认是一定会载入XXX_PCD3D_SM5.stable.upipelinecache ,运行时产生的新PSO会保存在\Saved\XXX_PCD3D_SM5.upipelinecache ,但条件是开启了 r.ShaderPipelineCache.Enabledr.ShaderPipelineCache.LogPSO 以及r.ShaderPipelineCache.SaveUserCache
我认为UE预期shipping版本开启这项保存PSO的功能,同时开发者应该尽量收集更多到PSO到stable.upipelinecache 中,玩家再帮忙收集一部分做到本地缓存更新\Saved\XXX_PCD3D_SM5.upipelinecache,就会越来越不卡顿。除此之外,也可以把XXX_PCD3D_SM5.upipelinecache发给开发者进行合并。
 
那么目前这些功能在Shipping版本可以看到代码里是开的。

验证

Test Build

Test Build中默认r.ShaderPipelineCache.LogPSOr.ShaderPipelineCache.SaveUserCache 没开,导致在运行时无法存储/生成\Saved\XXX_PCD3D_SM5.upipelinecache
所以的content里的文件都被加密打包成.pak 文件,XXX_PCD3D_SM5.stable.upipelinecache 同样在其中,能被成功读入(可从本地log验证)。
单纯增加-logpso并不会触发保存。
一个可行的方案是增加如下命令行参数:
可以在登陆界面通过console检查到对应变量的开启。
然而这一命令却无法触发打开游戏时本地cache的加载,并没有预期之中的LogRHI: Opened FPipelineCacheFile: ../../../xxx/Saved/XXX_PCD3D_SM5.upipelinecache
这是因为,由于UE_SHIPPING=0导致两个flag被赋值为0(>FEngineLoop::PreInitPreStartupScreen()),紧接着发生了OpenPipelineCache()(PreInitPostStartupScreen()),此时自然无法读取,推测后面才发生了命令行的flag赋值。
结论是,Test Build在代码不做改动的情况下,可以通过增加命令行参数收集PSO,但无法正常载入本地PSO缓存
 

Shipping Build

Jenkins上的太慢,本地先build了一个只有登录界面的shipping.
第一次打开客户端,经检查,默认开启两个flag.
notion image
检查log,没有发现类似LogRHI: Opened FPipelineCacheFile: ../../../xxx/Saved/XXX_PCD3D_SM5.upipelinecache 的信息。
符合预期,因为全新版本并没有这个本地缓存文件,会生成。
同时在Saved 文件夹中可发现有新创建的xxx_PCD3D_SM5.upipelinecache
第二次打开客户端,发现读取本地缓存的Log:
notion image
符合预期。
虽然有console可以用很诡异,但我很确定编译时选的是shipping.
 
接下来直接看看服务器做出的版本吧。。
第一次打开客户端
试图打开本地缓存,间接说明了LogPSO和SaveUserCache是开启的。
试图打开本地缓存,间接说明了LogPSO和SaveUserCache是开启的。
也新创建了本地缓存
notion image
在Haven里逛该之后,准备退出了,看一下目前PSO的情况:
notion image
第二次打开客户端,发现读取本地缓存的Log:
notion image
在登录界面看PSO的情况:
notion image
数目对的上,说明读取成功,没有新增的PSO。
接下来去haven逛该看看涨不涨
notion image
只有很微小的两个,主要是因为登陆时碰到了7日礼包界面,真的服咯,再开一遍。
notion image
OK,没有新增,证明Shipping版本的PSO载入和保存机制如上所言。注意编版本时一定要选shipping,虽然出来的也有命令行,但Test的PSO是没法读入本地缓存的。
 
UE4中通过代码创建贴图Texture Streaming中的LOD计算路径
Dongfangliu
Dongfangliu
一个想做游戏制作人的引擎民工🍚
公告
type
status
date
slug
summary
tags
category
icon
password
🎉NotionNext 4.0即将到来🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏