Posted onInUnrealEngine
,
PSOCacheViews: Symbols count in article: 12kReading time ≈30 mins.
In UE, there is a PSO Cache mechanism, which stands for Pipeline State Object Caching. It is used to pre-record and construct the shader information dependent on the materials used at runtime. When the project first utilizes these shaders, this list can speed up the shader loading/compilation process. The PSO Cache saves rendering states, vertex declarations, primitive types, render target pixel formats, and other data to files, enhancing shader loading efficiency. This article primarily introduces the enabling and construction process of PSO Cache, and will analyze the loading process of PSO Cache in the engine, along with implementing hot-update PSO methods, error handling, etc. The principle of PSO Cache will be analyzed in detail later.
The official documentation for PSO Cache: PSO Cache.
Overview of the PSO Cache construction process:
The deployment and use of PSO Cache can be roughly divided into the following steps:
Enable PSO Cache and Shader Stable Keys for the project, and after packaging, you can obtain ShaderStableInfo*.scl.csv from the Metadata/PipelineCaches directory.
Add the logPSO parameter to start the game, which is used to record PSO data at runtime (*.rec.upipelinecache).
Generate *.stablepc.csv using ShaderStableInfo*.scl.csv and *.rec.upipelinecache.
Execute Cook again to generate the upipelinecache file using *.stablepc.csv, which will be included in the package.
Start the game, and the engine will automatically load *.stable.upipelinecache, using the PSO Cache when compiling shaders.
The content order of this article also follows these steps.
Enable Shader Stable Keys
First, you need to enable ShaderStableKeys for the project to generate stable Shader Keys during Cook, serving as a record for shaders.
Add the following value in DefaultEngine.ini (or platform-specific files like AndroidEngine.ini):
1 2
[DevOptions.Shaders] NeedsShaderStableKeys=true
After adding this, execute packaging (Cook) again, which will create the following directory:
And a following file will be created in Saved/CoolectedPSOs:
Note that by default, only the current MaterialQualityLevel will be collected. If you wish to collect for other qualities, you need to switch and rerun.
The above command will generate the Client_GLSL_ES3_1_ANDROID.stablepc.csv file under the engine’s Binaries/Win64. Note that it must match the naming convention {PROJECTNAME}_{SHADER_FORMART_NAME}.stablepc.csv.
For Android, the naming will be: Client_GLSL_ES3_1_ANDROID.stablepc.csv For iOS, the naming will be: Client_SF_METAL.stablepc.csv
During generation, the following logs will be produced:
D:\PSOCache>"C:\Program Files\Epic Games\UE_4.25\Engine\Binaries\Win64\UE4Editor-Cmd.exe" "D:\PSOExample\PSOExample.uproject" -run=ShaderPipelineCacheTools expand D:/PSOCache/*.rec.upipelinecache D:/PSOCache/*.scl.csv D:/PSOCache/PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv [2021.04.22-08.57.39:623][ 0]LogTargetPlatformManager: Display: Building Assets For Windows [2021.04.22-08.57.39:648][ 0]LogAudioDebug: Display: Lib vorbis DLL was dynamically loaded. [2021.04.22-08.57.39:841][ 0]LogShaderCompilers: Display: Using Local Shader Compiler. [2021.04.22-08.57.40:492][ 0]LogDerivedDataCache: Display: Max Cache Size: 512 MB [2021.04.22-08.57.40:523][ 0]LogDerivedDataCache: Display: Loaded Boot cache: C:/Users/lipengzha/AppData/Local/UnrealEngine/4.25/DerivedDataCache/Boot.ddc [2021.04.22-08.57.40:533][ 0]LogDerivedDataCache: Display: Pak cache opened for reading ../../../Engine/DerivedDataCache/Compressed.ddp. [2021.04.22-08.57.44:616][ 0]LogAudioCaptureCore: Display: No Audio Capture implementations found. Audio input will be silent. [2021.04.22-08.57.44:616][ 0]LogAudioCaptureCore: Display: No Audio Capture implementations found. Audio input will be silent. [2021.04.22-08.57.45:274][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv... [2021.04.22-08.57.45:275][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv... [2021.04.22-08.57.45:280][ 0]LogShaderPipelineCacheTools: Display: Loaded 548 shader info lines from D:/PSOCache/ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv. [2021.04.22-08.57.45:287][ 0]LogShaderPipelineCacheTools: Display: Loaded 5707 shader info lines from D:/PSOCache/ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv. [2021.04.22-08.57.45:287][ 0]LogShaderPipelineCacheTools: Display: Loaded 6255 unique shader info lines total. [2021.04.22-08.57.45:289][ 0]LogShaderPipelineCacheTools: Display: Loading D:/PSOCache/++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_000843BC08D905AF09E24614C6A6086F.rec.upipelinecache.... [2021.04.22-08.57.45:293][ 0]LogShaderPipelineCacheTools: Display: Loaded 105 PSOs [2021.04.22-08.57.45:297][ 0]LogShaderPipelineCacheTools: Display: Loaded 105 PSOs total [Usage Mask Merged = 0]. [2021.04.22-08.57.45:325][ 0]LogShaderPipelineCacheTools: Display: Generated 478 stable PSOs total [2021.04.22-08.57.45:344][ 0]LogShaderPipelineCacheTools: Display: Wrote stable PSOs, 479 lines (541.8 KB) to D:/PSOCache/PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv [2021.04.22-08.57.45:346][ 0]LogInit: Display: [2021.04.22-08.57.45:349][ 0]LogInit: Display: Success - 0 error(s), 0 warning(s) [2021.04.22-08.57.45:353][ 0]LogInit: Display: Execution of commandlet took: 0.08 seconds [2021.04.22-08.57.45:408][ 0]LogShaderCompilers: Display: Shaders left to compile 0 [2021.04.22-08.57.45:621][ 0]LogContentStreaming: Display: There are 1 unreleased StreamingManagers
All files needed for the final PSO:
1 2 3 4 5 6 7 8
D:\PSOCache>tree /a /f Volume in drive D is Data Volume serial number is 004B-E876 D:. ++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_000843BC08D905AF09E24614C6A6086F.rec.upipelinecache PSOExample_GLSL_ES3_1_ANDROID.stablepc.csv ShaderStableInfo-Global-GLSL_ES3_1_ANDROID.scl.csv ShaderStableInfo-PSOExample-GLSL_ES3_1_ANDROID.scl.csv
I backed up the files generated by the test project: PSOCache.7z, which allows you to view the contents of each file.
Generate *.stable.upipelinecache
Place the generated *stablepc.csv into the Build/Android/PipelineCaches directory, noting that the Build/PLATFORM this platform is the compilation platform, not the resource platform during Cook; for Android, the package should be Android, not Android_ASTC, etc. Then repackage.
The engine creates the PipelineCache using stablepc.csv during Cook:
The path for the generated *.stable.upipelinecache file in the package is Content\PipelineCaches\Android:
1 2 3 4 5 6
D:\PSOExample\Saved\Cooked\Android_ASTC\PSOExample\Content\PipelineCaches>tree /a /f Volume in drive C is Windows Volume serial number is 0C49-9EA3 C:. \---Android PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache
Since it is located under Content and will be packed into pak, it can also be updated hot.
When a package containing the upipelinecache is installed, the following log will appear at runtime:
1 2 3 4 5 6 7 8 9 10 11
LogShaderLibrary: Display: Using ../../../PSOExample/Content/ShaderArchive-PSOExample-GLSL_ES3_1_ANDROID.ushaderbytecode for material shader code. Total 3053 unique shaders. LogShaderLibrary: Display: Cooked Context: Using Shared Shader Library PSOExample LogRHI: Display: Opened pipeline cache after state change and enqueued 0 of 0 tasks for precompile. LogRHI: Base name for record PSOs is ../../../PSOExample/Saved/CollectedPSOs/++UE4+Release-4.25-CL-13942748-PSOExample_GLSL_ES3_1_ANDROID_00087B4B08D905BBC5A827F40CA03A0C.rec.upipelinecache LogRHI: FPipelineCacheFile Header Game Version: 13942748 LogRHI: FPipelineCacheFile Header Engine Data Version: 17 LogRHI: FPipelineCacheFile Header TOC Offset: 38155 LogRHI: FPipelineCacheFile File Size: 51011 Bytes LogRHI: Opened FPipelineCacheFile: ../../../PSOExample/Content/PipelineCaches/Android/PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache (GUID: 00000000000000000000000000000000) with 102 entries. LogRHI: Scanning Binary program cache, using Shader Pipeline Cache version 6988202F47BA858F3F0DE483D7DB0606 LogRHI: AndroidEGL:SwapBuffers eglGetCompositorTimingANDROID EGL_COMPOSITE_DEADLINE_ANDROID=2718926192606265, EGL_COMPOSITE_INTERVAL_ANDROID=16559027, EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID=14559027
Loading and Hot Updating PSO Cache
Similar to ShaderCode, the engine automatically loads the PSO Cache upon startup by calling FPipelineCacheFile::OpenPipelineFileCache to read *.stable.upipelinecache within FEngineLoop.
The code for loading PSO in PreInitPreStartupScreen is as follows:
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine) { // ... { bool bUseCodeLibrary = FPlatformProperties::RequiresCookedData() || GAllowCookedDataInEditorBuilds; if (bUseCodeLibrary) { { SCOPED_BOOT_TIMING("FShaderCodeLibrary::InitForRuntime"); // Will open material shader code storage if project was packaged with it // This only opens the Global shader library, which is always in the content dir. FShaderCodeLibrary::InitForRuntime(GMaxRHIShaderPlatform); }
#if !UE_EDITOR // Cooked data only - but also requires the code library - game only if (FPlatformProperties::RequiresCookedData()) { SCOPED_BOOT_TIMING("FShaderPipelineCache::Initialize"); // Initialize the pipeline cache system. Opening is deferred until the manual call to // OpenPipelineFileCache below, after content pak's ShaderCodeLibraries are loaded. FShaderPipelineCache::Initialize(GMaxRHIShaderPlatform); } #endif//!UE_EDITOR } } // ... //Handle opening shader library after our EarlyLoadScreen { LLM_SCOPE(ELLMTag::Shaders); SCOPED_BOOT_TIMING("FShaderCodeLibrary::OpenLibrary");
// Open the game library which contains the material shaders. FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir()); for (const FString& RootDir : FPlatformMisc::GetAdditionalRootDirectories()) { FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::Combine(RootDir, FApp::GetProjectName(), TEXT("Content"))); }
// Now our shader code main library is opened, kick off the precompile, if already initialized FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform); } // ... }
FShaderPipelineCache::OpenPipelineFileCache has two overloaded versions:
Upon engine startup, it defaults to reading OpenPipelineFileCache(FApp::GetProjectName(), Platform), which is PSOExample_GLSL_ES3_1_ANDROID.stable.upipelinecache. The Platform parameter can be obtained by passing the global object GMaxRHIShaderPlatform.
UE also provides a console command to specify loading stable.upipelinecache: r.ShaderPipelineCache.Open, along with several other console commands that control PSO:
static FAutoConsoleCommand LoadPipelineCacheCmd( TEXT("r.ShaderPipelineCache.Open"), TEXT("Takes the desired filename to open and then loads the pipeline file cache."), FConsoleCommandWithArgsDelegate::CreateStatic(ConsoleCommandLoadPipelineFileCache) );
static FAutoConsoleCommand SavePipelineCacheCmd( TEXT("r.ShaderPipelineCache.Save"), TEXT("Save the current pipeline file cache."), FConsoleCommandDelegate::CreateStatic(ConsoleCommandSavePipelineFileCache) );
static FAutoConsoleCommand ClosePipelineCacheCmd( TEXT("r.ShaderPipelineCache.Close"), TEXT("Close the current pipeline file cache."), FConsoleCommandDelegate::CreateStatic(ConsoleCommandClosePipelineFileCache) );
static FAutoConsoleCommand SwitchModePipelineCacheCmd( TEXT("r.ShaderPipelineCache.SetBatchMode"), TEXT("Sets the compilation batch mode, which should be one of:\n\tPause: Suspend precompilation.\n\tBackground: Low priority precompilation.\n\tFast: High priority precompilation."), FConsoleCommandWithArgsDelegate::CreateStatic(ConsoleCommandSwitchModePipelineCacheCmd) );
This means that you only need to include the latest *.stable.upipelinecache in the hot update package, and then call OpenPipelineFileCache to load the latest PSO Cache, which can maintain consistency with the hot update process of ShaderCode.
Generating a new PSO Cache requires two critical types of data:
Runtime-captured PSO data (upipelinecache)
ShaderStableInfo (located in the Metadata directory)
Since ShaderCode can be hot updated, and ShaderStableInfo can be obtained by cooking the latest project, the PSO Cache can similarly be iteratively updated by hot updating shaders and continuously capturing the latest PSO data. I plan to add the PSO Cache hot update functionality to HotPatcher when I have time, thus integrating the deployment and packaging of PSO Cache into an automated hot update process, marking this down for future reference.
When r.ShaderPipelineCache.Enabled=1 is set, the engine will automatically load the project’s PSO Cache upon startup, and the engine imposes a restriction that it can only be loaded once; subsequent calls to OpenPipelineFileCache will not be loaded:
boolFPipelineFileCache::OpenPipelineFileCache(FString const& Name, EShaderPlatform Platform, FGuid& OutGameFileGuid) { bool bOk = false; OutGameFileGuid = FGuid(); if(IsPipelineFileCacheEnabled()) { FRWScopeLock Lock(FileCacheLock, SLT_Write); if(FileCache == nullptr) { FileCache = newFPipelineCacheFile(); bOk = FileCache->OpenPipelineFileCache(Name, Platform, OutGameFileGuid); // File Cache now exists - these caches should be empty for this file otherwise will have false positives from any previous file caching - if not something has been caching when it should not be check(NewPSOs.Num() == 0); check(NewPSOHashes.Num() == 0); check(RunTimeToPSOUsage.Num() == 0); } } return bOk; }
After the engine loads by default, FileCache will no longer be nullptr, so subsequent loading calls will directly return false. The solution is to prevent the engine from automatically loading the PSO Cache at startup and wait until runtime hot updates to load it manually. I checked the code and found that this can be detected from the IsPipelineFileCacheEnabled check:
Its return value depends on two values: FileCacheEnabled and CVarPSOFileCacheEnabled.
FileCacheEnabled is assigned in FPipelineFileCache::Initialize, and is always true for platforms other than iOS. For iOS, it depends on the result of FPipelineFileCache::ShouldEnableFileCache.
CVarPSOFileCacheEnabled is a console variable that controls the value of r.ShaderPipelineCache.Enabled:
This will enable delayed loading of the PSO Cache, which can be manually loaded after a hot update.### Delayed Collection and Storage
Since collecting and storing PSO Cache has additional performance overhead, you can disable the collection and storage of PSO data and enable it at runtime as needed. In DefaultEngine.ini, disable LogPSO and SaveBoundPSOLog, so that automatic collection and storage do not occur when packaging the basic build:
UENUM(BlueprintType) enum classEPSOSaveMode : uint8 { Incremental = 0, // Fast(er) approach which saves new entries incrementally at the end of the file, replacing the table-of-contents, but leaves everything else alone. BoundPSOsOnly = 1, // Slower approach which consolidates and saves all PSOs used in this run of the program, removing any entry that wasn't seen, and sorted by the desired sort-mode. SortedBoundPSOs = 2// Slow save consolidates all PSOs used on this device that were never part of a cache file delivered in game-content, sorts entries into the desired order and will thus read-back from disk. };
Enabling SaveBoundPSOLog will automatically store the collected PSO data. You can choose not to enable automatic storage and manually store it during runtime by calling FShaderPipelineCache::SavePipelineFileCache.
Error Handling
Cook Did Not Generate .scl.csv
Make sure to enable ShaderStableKeys for the project, or the .scl.csv file will not be generated.
Confirm that r.ShaderPipelineCache.Enabled is enabled (in DefaultEngine.ini or DeviceProfile).
Add the -logPSO parameter in ue4commandline.txt.
Bad PSO
If using the official engine, the above process is complete, but sometimes the project requires modifications to the engine to support specific rendering features, such as adding Multi-subpasshint support:
This change requires simultaneous modifications to both the GraphicsDescriptor::StateToString() and GraphicsDescriptor::StateFromString() functions to include serialization support for multi-subpasshint.
However, after modifying this, when using -run=ShaderPipelineCacheTools to generate *.stablepc.csv, the following log error occurs:
The key part is in the line DupItem.GraphicsDesc.FromString(StringRep); where the data for GraphicsDesc failed to restore successfully.
After debugging, it was found that the engine records the number of parseable elements in the string for Pipeline Cache Graphics Descriptor, which is the number of entries in the second column of the generated *.stablepc.csv. The official engine defaults to 63, recorded by FPipelineCacheGraphicsDescPartsNum:
Runtime\RHI\Private\PipelineFileCache.cpp
1
const int32 FPipelineCacheGraphicsDescPartsNum = 63; // parser will expect this number of parts in a description string
The status and data of each item in the generated *.stablepc.csv are as follows:
It consists of exactly 63 entries. Please note that in GraphicsDescriptor::StateFromString, there is a check for the expected number of parts, and the data passed to FromString must match the value of FPipelineCacheGraphicsDescPartsNum:
// check if we have expected number of parts if (Parts.Num() != PartCount) { // instead of crashing let caller handle this case returnfalse; } // ... }
Because we added multi-subpasshint support, we modified SubpassHint from uint8 to uint8[8], adding 7 more data points, so the corresponding FPipelineCacheGraphicsDescPartsNum must also be increased by 7 to become 70, otherwise the StateFromString verification will fail.
After modifying this, regenerating *.stablepc.csv using -run=ShaderPipelineCacheTools no longer results in the Bad PSO error.
You can control the construction of PSO data at runtime through the functions of FShaderPipelineCache:
Runtime\RenderCore\Public\ShaderPipelineCache.h
1 2 3 4 5 6 7 8 9 10
/** Pauses precompilation. */ staticvoidPauseBatching(); /** Resumes precompilation batching. */ staticvoidResumeBatching(); /** Returns the number of pipelines waiting for precompilation. */ static uint32 NumPrecompilesRemaining(); /** Returns the number of pipelines actively being precompiled this frame. */ static uint32 NumPrecompilesActive(); /** Sets the precompilation batching mode. */ staticvoidSetBatchMode(BatchMode Mode);
The official recommendation is to wait for the PSO to finish building before hiding the LoadingScreen during the loading screen:
1 2 3 4 5 6 7 8
if(FShaderPipelineCache::NumPrecompilesRemaining() > 0) { if (OutDebugReason != nullptr) { *OutDebugReason = FString(TEXT("PC: PSO cache still compiling")); } returntrue; }
You can also build during the opening of the UI, cutscenes, or pause menus by handling it through the following three function combinations:
1 2 3 4 5 6 7 8 9 10 11 12
// Pause PSO cache compilation FShaderPipelineCache::PauseBatching(); // Set the processing mode for PSO // enum class BatchMode // { // Background, // The maximum batch size is defined by r.ShaderPipelineCache.BackgroundBatchSize // Fast, // The maximum batch size is defined by r.ShaderPipelineCache.BatchSize // Precompile // The maximum batch size is defined by r.ShaderPipelineCache.PrecompileBatchSize // }; FShaderPipelineCache::SetBatchMode(FShaderPipelineCache::BatchMode::Background); // Resume compiling PSO staticvoidResumeBatching();
There are multiple CVars in the engine to control PSO capturing, loading, etc. For detailed information, refer to: UE Console Variables and Command, search for r.shaderpipelinecache to see all supported CVars.
You can also use configurations to automatically build when the game starts by modifying the [ConsoleVariables] configuration in DefaultEngine.ini. They are defined in Runtime\RenderCore\Private\ShaderPipelineCache.cpp within ConsoleVariable, which can be adjusted at runtime or in configuration files as needed:
[ConsoleVariables] ;Sets the startup mode for the PSO cache, determining what the cache does after initialisation: ;0: Precompilation is paused and nothing will compile until a call to ResumeBatching(). ;1: Precompilation is enabled in the 'Fast' mode. ;2: Precompilation is enabled in the 'Background' mode. ;Default is 1. r.ShaderPipelineCache.StartupMode=1
;Set the number of PipelineStateObjects to compile in a single batch operation when compiling takes priority. Defaults to a maximum of 50 per frame, due to async. file IO it is less in practice. r.ShaderPipelineCache.BackgroundBatchSize=1
;Set the number of PipelineStateObjects to compile in a single batch operation when pre-optimizing the cache. Defaults to a maximum of 50 per frame, due to async. file IO it is less in practice. r.ShaderPipelineCache.PrecompileBatchSize=50
;The target time (in ms) to spend precompiling each frame when in the background or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 0.0 (off). r.ShaderPipelineCache.BackgroundBatchTime=0.0
;The target time (in ms) to spend precompiling each frame when compiling takes priority or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 16.0 (max. ms per-frame of precompilation). r.ShaderPipelineCache.BatchTime=16.0
;The target time (in ms) to spend precompiling each frame when pre-optimizing or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 10.0 (off). r.ShaderPipelineCache.PrecompileBatchTime=0.0
;Set the number of PipelineStateObjects to log before automatically saving. 0 will disable automatic saving. Shipping defaults to 0, otherwise default is 100. r.ShaderPipelineCache.SaveAfterPSOsLogged=100
;Set the time where any logged PSO's will be saved if the number is < r.ShaderPipelineCache.SaveAfterPSOsLogged. Disabled when r.ShaderPipelineCache.SaveAfterPSOsLogged is 0
;Set the time where any logged PSO's will be saved if the number is < r.ShaderPipelineCache.SaveAfterPSOsLogged. Disabled when r.ShaderPipelineCache.SaveAfterPSOsLogged is 0 r.ShaderPipelineCache.AutoSaveTime=30
;Mask used to precompile the cache. Defaults to all PSOs (-1) r.ShaderPipelineCache.PreCompileMask=-1
;Set the time where any logged PSO's will be saved when -logpso is on the command line. r.ShaderPipelineCache.AutoSaveTimeBoundPSO=10
;If > 0 then a log of all bound PSOs for this run of the program will be saved to a writable user cache file. Defaults to 0 but is forced on with -logpso. r.ShaderPipelineCache.SaveBoundPSOLog=0
;Set non zero to use GameFileMask during PSO precompile - recording should always save out the usage masks to make that data availble when needed. r.ShaderPipelineCache.GameFileMaskEnabled=0
;Set non zero to PreOptimize PSOs - this allows some PSOs to be compiled in the foreground before going in to game r.ShaderPipelineCache.PreOptimizeEnabled=0
;The minimum bind count to allow a PSO to be precompiled. Changes to this value will not affect PSOs that have already been removed from consideration. r.ShaderPipelineCache.MinBindCount=0
;The maximum time to allow a PSO to be precompiled. if greather than 0, the amount of wall time we will allow pre-compile of PSOs and then switch to background processing. r.ShaderPipelineCache.MaxPrecompileTime=0.0
[ConsoleVariables] ;Sets the startup mode for the PSO cache, determining what the cache does after initialisation: ;0: Precompilation is paused and nothing will compile until a call to ResumeBatching(). ;1: Precompilation is enabled in the 'Fast' mode. ;2: Precompilation is enabled in the 'Background' mode. ;Default is 1. r.ShaderPipelineCache.StartupMode=1
;Set the number of PipelineStateObjects to compile in a single batch operation when compiling takes priority. Defaults to a maximum of 50 per frame, due to async. file IO it is less in practice. r.ShaderPipelineCache.BatchSize=50
;The target time (in ms) to spend precompiling each frame when compiling takes priority or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 16.0 (max. ms per-frame of precompilation). r.ShaderPipelineCache.BatchTime=16.0
;Set the number of PipelineStateObjects to compile in a single batch operation when compiling takes priority. Defaults to a maximum of 50 per frame, due to async. file IO it is less in practice. r.ShaderPipelineCache.BackgroundBatchSize=1
;Set the number of PipelineStateObjects to compile in a single batch operation when pre-optimizing the cache. Defaults to a maximum of 50 per frame, due to async. file IO it is less in practice. r.ShaderPipelineCache.PrecompileBatchSize=50
;The target time (in ms) to spend precompiling each frame when in the background or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 0.0 (off). r.ShaderPipelineCache.BackgroundBatchTime=11.0
;The target time (in ms) to spend precompiling each frame when cpre-optimizing or 0.0 to disable. When precompiling is faster the batch size will grow and when slower will shrink to attempt to occupy the full amount. Defaults to 10.0 (off). r.ShaderPipelineCache.PrecompileBatchTime=10.0
;Set the number of PipelineStateObjects to log before automatically saving. 0 will disable automatic saving. Shipping defaults to 0, otherwise default is 100. r.ShaderPipelineCache.SaveAfterPSOsLogged=100
;Set the time where any logged PSO's will be saved if the number is < r.ShaderPipelineCache.SaveAfterPSOsLogged. Disabled when r.ShaderPipelineCache.SaveAfterPSOsLogged is 0
;Set the time where any logged PSO's will be saved if the number is < r.ShaderPipelineCache.SaveAfterPSOsLogged. Disabled when r.ShaderPipelineCache.SaveAfterPSOsLogged is 0 r.ShaderPipelineCache.AutoSaveTime=30
;Mask used to precompile the cache. Defaults to all PSOs (-1) r.ShaderPipelineCache.PreCompileMask=-1
;Set the time where any logged PSO's will be saved when -logpso is on the command line. r.ShaderPipelineCache.AutoSaveTimeBoundPSO=10
;Set non zero to use GameFileMask during PSO precompile - recording should always save out the usage masks to make that data availble when needed. r.ShaderPipelineCache.GameFileMaskEnabled=0
;Set non zero to PreOptimize PSOs - this allows some PSOs to be compiled in the foreground before going in to game r.ShaderPipelineCache.PreOptimizeEnabled=1
;The minimum bind count to allow a PSO to be precompiled. Changes to this value will not affect PSOs that have already been removed from consideration. r.ShaderPipelineCache.MinBindCount=30
;The maximum time to allow a PSO to be precompiled. if greather than 0, the amount of wall time we will allow pre-compile of PSOs and then switch to background processing. r.ShaderPipelineCache.MaxPrecompileTime=33
Enabling automatic PSO data construction at startup will generate the following log:
1 2 3 4 5 6 7
LogRHI: Base name for record PSOs is ../../../FGame/Saved/CollectedPSOs/++UE4+Release-4.25-CL-0-FGame_SF_METAL_8F3222B7964FE2A89C849E90E0000736.rec.upipelinecache LogRHI: FPipelineCacheFile Header Game Version: 0 LogRHI: FPipelineCacheFile Header Engine Data Version: 17 LogRHI: FPipelineCacheFile Header TOC Offset: 293853 LogRHI: FPipelineCacheFile File Size: 380497 Bytes LogRHI: Opened FPipelineCacheFile: ../../../FGame/Content/PipelineCaches/IOS/FGame_SF_METAL.stable.upipelinecache (GUID: 00000000000000000000000000000000) with 690 entries. LogRHI: Display: Opened pipeline cache and enqueued 441 of 441 tasks for precompile with BatchSize 50 and BatchTime 10.000000.
You can also set the PSO’s SortOrder in DefaultGameUserSettings.ini:
[ShaderPipelineCache.CacheFile]
;default is 0
;Default = 0, // Whatever order they are already in.
;FirstToLatestUsed = 1, // Start with the PSOs with the lowest first-frame used and work toward those with the highest.
;MostToLeastUsed = 2 // Start with the most often used PSOs working toward the least.
SortOrder=1
```### Profiling
You can use Unreal Insights to view the time taken for shader linking:
![](https://img.imzlp.com/imgs/zlp/picgo/2023/20230524160612.png)
And by using `stat pipelinestatecache`, you can check the loading and generation status of PSOs.
### Notes
#### ShaderLibrary Requirements
Using PSOs requires changing the shader serialization method to share shader library; if it is in inline shader form, Pre-Compiled PSOs cannot be used. Runtime collection is possible, but it cannot be utilized in the next package build.
#### scl.csv to shk
> In 4.27, StableExtension has been changed from `scl.csv` to `shk`. [ShaderCodeLibrary.cpp#L65](https://github.com/EpicGames/UnrealEngine/blob/4.27/Engine/Source/Runtime/RenderCore/Private/ShaderCodeLibrary.cpp#L65)
![](https://img.imzlp.com/imgs/zlp/picgo/2023/20230525192513.png)
### References
- [PSO Cache](https://docs.unrealengine.com/en-US/SharingAndReleasing/PSOCaching/index.html).
- [Unreal Engine 4: PSO Cache (Pipeline State Object) to Reduce Load Times/Hitches](https://www.youtube.com/watch?v=0tzrsEm6V9Q&ab_channel=StephenMaloney)
- [Introduction to UE4 Rendering Engine Modules (2)](https://zhuanlan.zhihu.com/p/72768828)
The article is finished. If you have any questions, please comment and communicate.