Concept: PSO Precaching & the Grace Period
Why the plugin measures shader compilation instead of trying to predict it, and how the two engine PSO systems differ.
Two independent systems
Unreal ships two unrelated PSO mechanisms. The plugin handles both, but for different reasons and with different counters.
| System | What it is | Plugin's use |
|---|---|---|
Bundled PSO file cache (FShaderPipelineCache) | A .upipelinecache recorded from playtests and shipped in Content/. One large, finite, countable batch at boot. | Holds the loading screen with a progress bar. |
Runtime PSO precaching (PipelineStateCache) | Automatic per-component/material precache issued as things register. No recording step. | Optionally drains outstanding requests before a level becomes visible. |
FShaderPipelineCache::NumPrecompilesRemaining() already folds the runtime precache count into its result. That is why the level-load drain reads NumActivePrecacheRequests() directly - using the combined counter mid-game would also make the level wait on the boot-time batch.The engine cannot tell you whether a boot will be slow
The obvious design is to detect a stale cache and show the screen only then. Unreal does not expose that, and it cannot:
- The GPU driver's own PSO cache (NVIDIA DXCache and friends) is completely opaque to Unreal. The engine cannot see whether the driver already has your pipelines.
- The engine does validate the cache file's game version, shader platform and format - but the outcome is "ignore this file", not "tell the game to show a screen".
IsPrecompilationSlowTask()is the closest native hint, and it is only set by Vulkan, Metal and OpenGL. D3D12 never sets it, so on Windows it is permanently false.- The driver-health signal (
bDriverCacheSuspectedUnhealthy) is reactive - it flips only after the player has already suffered around 100 hitches. Useful telemetry, useless as a boot-time predictor.
So the plugin measures instead
A warm driver cache returns compiles in milliseconds; a cold one does not. That difference is directly observable, so there is no need to predict it. The loading screen is already on screen at boot, so the gate simply declines to release it and watches the clock:
boot -> loading screen already visible|start precompile, poll|+------+------+| |done <2s still going| |release swap widget -> "Optimizing shaders 42%"(no UI) |OnComplete -> release
On a normal boot the state never leaves Warming and the player sees an ordinary loading screen. After a game or driver update the work is real, ShaderPrecompileGraceSecs elapses, and the progress UI reveals. Because the screen was already up, there is no flicker of a menu appearing and then being covered.
Progress is advisory; completion is authoritative
Two engine constraints shape the API:
- There is no exported total. The engine keeps it private, so the only supported source for a percentage is the count handed to the precompilation-begin delegate. If that fired before the plugin's module loaded, the plugin falls back to a high-water mark of the remaining count - hence the
StartupMode=0recommendation. - The remaining count is not monotonic. It is clamped to a minimum of 1 while precompiling and can increase as further cache tasks queue. The bar can legitimately stall below 100%.
MaxShaderPrecompileBlockSecs exists as a backstop for the case where completion never arrives.Where the checks sit
The two checks are deliberately placed at opposite ends of the condition list:
| Check | Position | Why |
|---|---|---|
| Shader precompile gate | Condition 2, near the top | So it can hold the screen over a main menu that has already finished loading. |
| Runtime precache drain | Condition 16, last | So it only ever delays the final hide and never gates anything above it. |
See also: How-To: Show Shader Precompile Progress for the setup steps, and Concept: Lifecycle & Show Conditions for the full condition list.