PSO Caching in Unreal Engine
Pipeline State Object (PSO) caching in Unreal Engine 5 for Windows (DirectX 12 SM5/6) and Linux (Vulkan SM5). PSO caching enables developer control over shader precompilation and reduces stutter linked to just-in-time shader compilation.
Pipeline State Object (PSO) caching in both UE4 and UE5 relates to the pipeline objects in Vulkan and Direct3D that hold shader and state information. In Unreal, these objects can be preloaded either before the game launches or in menus to cut down on hitches during gameplay caused by a new object not having a ready shader.
These stutters can be quite detracting from the gameplay experience, and are relatively easy to cut down on. At least, once you enable the PSO caching system in Unreal.
Epic have some good guidelines on the periodicity of PSO capture in their FAQ on PSO caching.
I'm going to cover some of the basics of enabling PSO in Unreal Engine 5 below. You will need to jump back to the Epic documentation for one part of the setup as that part is still fully relevant, such as C++ docs for programmatic control.
Configuring PSO
I highly recommend reading the documentation on Unreal Engine's website, but I don't find those complete in the current state.
In short, we need the follow two settings in Project Settings > Project > Packaging. The Deterministic Shader Code Order
may be unnecessary, though.
Next, we want to enable the cache. This is done in Device Profiles. The menu has moved a bit in UE5, and is now located under Tools > Platforms > Device Profiles.
In here, we want to edit our target platforms. I've only done Windows, as that's my current build target, but you can edit a lot in here. To access platform specific targets, just click the three dots and a second editor window will pop up.
Let's cover the some settings below. Caveat Emptor, though. I don't use all of these settings.
r.ShaderPipelineCache.Open
to start the process. I'm not sure why, but you can do this via BP or C++.r.ShaderPipelineCache.Enabled
This one enables the pipeline cache. We want this one enabled; otherwise, nothing we do here will have any impact.
r.ShaderPipelineCache.PreOptimizeEnabled
Whether we should enable the pre-optimisation stage. This is done at the first launch of the game and attempts to compile some of the PSOs.
You can manually flip the cache into precompile mode in C++, but there's probably not much point. As far as I can tell, it's mostly for first-launch compilation.
r.ShaderPipelineCache.MaxPrecompileTime
The maximum wall time before the PSO compilation switches from "fast" to "background", and we enter the game. I set this to 60 seconds as an example. You could set it to zero (0) to disable it. Disabling this seems to immediately skip precompilation.
r.ShaderPipelineCache.AutoSaveTimeBoundPSO
Time before we save logged PSOs to disk.
r.ShaderPipelineCache.BatchSize & Family
The batch size is the batch size when programmatically doing compilation. Unreal supports programmer definition of when we want PSO to compile.
For example, I kick PSO into "fast" mode in loading screens, and into "background" mode in a menu. In game, I disable PSO compilation since we now have a more important use of our CPU. PSO compilation doesn't use excessive amounts of disk, so it can be done at load time without significant negative repercussions.
The precompiled batch size is the size of the batch during precompilation at start up, and background batch size is used in "background" mode.
Mode | Default Max per Frame |
---|---|
BatchSize | 50 |
BackgroundBatchSize | 1 |
PreCompileBatchSize | 50 |
r.ShaderPipelineCache.BatchTime & Family
The target time per frame in milliseconds dedicated to compiling PSOs.
Mode | Default Time |
---|---|
BatchTime | 16ms |
BackgroundBatchTime | 0.0 |
PreCompileBatchTime | 10ms |
Interestingly, background is set to 0 by default. This means you get 1 PSO per frame at most in background mode. While this is a reasonable default, you could increase it to enable a more adaptive compilation strategy.
Stable Keys
Now, we're almost configured to follow the documentation on capturing and building with PSO data, but there's one omission in the documentation. This might be a change from UE4 to UE5, but even with -logPSO in my launch settings, I couldn't get either the static PSO or the runtime PSO to generate.
A little hunting through the documentation and the code revealed that the Android example option below is not Android specific. The only difference is that it goes in Config/DefaultEngine.ini
rather than Config/Android/AndroidEngine.ini
.
[DevOptions.Shaders]
NeedsShaderStableKeys=true
With the above lines in the DefaultEngine.ini, restart Unreal, and now you should be properly configured to follow the rest of the documentation about capturing PSO as laid out on this UE4 page.
Combining PSO Data
UE5 has slightly different syntax
In <ProjectName>\Saved\Cooked\<Platform>\<ProjectName>\Metadata\PipelineCaches
, you should find a few files. These don't match quite what the Building the PSO Cache page says, though.
Firstly, the .scl.csv
files referenced in the docs have become .shk
files, and there's a .pipelinecache
in there.
Secondly, T5 is the name of the test project I've used for this post. Replace it with your project name.
When you run a project with -logPSO
on, the runtime PSO will be placed in <ProjectName>/Saved/CollectedPSOs
from the exe of the captured build. This may seem a bit counterintuitive as it actually ends up being <Project>/Saved/StagedBuilds/<Platform>/<Project>/Saved/CollectedPSOs
when run via the project launcher.
"C:\Program Files\Epic Games\UE_5.0\Engine\Binaries\
Win64\UnrealEditor-Cmd.exe" "Z:\T5\T5.uproject" -run=ShaderPipelineCacheTools Expand Z:/Pipelines/T5/*.upipelinecache Z:/Pipelines/T5/*.shk Z:/Pipelines/T5/T5.spc
move T5.spc "Z:\T5\Build\Windows\PipelineCaches\"
Note that Expand is capitalised for UE5.
There is a chance that the .spc
needs to be named depending on the project and 3D API in use. For example, instead of T5.spc
it will need to be T5_PCD3D_SM5.spc
for Direct3D using Shader Model 5.
As seen above, the Unreal Engine will invoke a build of the PSO data placed in PipelineCaches during the cooking process. The engine is using a wildcard in front of our *.spc
files, so we can version them by prepending some information.
As an aside, you can also place the following in DefaultGame.ini
or GameUserSettings.ini
to specify which cache to use. The PCD3D_SM5
or SF_VULKAN_SM5
will be added by the engine.
[ShaderPipelineCache.CacheFile]
LastOpened=<CacheName>
Caveat in Unreal 5 (2022-Apr-04)
In Unreal 5, and possibly UE4, PSO caching is disabled for raytracing shaders due to an issue.