PSO precache loading screen

I’ve been trying to get a loading screen working for the start of a game to compile the shaders and then skip this menu when the player launches the game again.

After running the game with -clearPSODriverCache it loads as expected taking longer to compile the shaders. On subsequent loads of the game without -clearPSODriverCache it still seems to be compiling or loading shaders, though certainly quicker than the first time.

I’ve logged the number from FShaderPipelineCache::NumPrecompilesRemaining() and you can see in the second and third log files that it has similar numbers but gets through them much quicker.

Whilst researching this I noticed some mention from people about using PipelineStateCache::NumActivePrecacheRequests() so I output it’s value as well and noticed it’s 0 after the first time I launch the game and get through the initial shader loading screen.

I’m unsure how best to detect if the shaders are ready to go so I can skip this loading screen entirely after the first time the player runs the game. Currently I check if FShaderPipelineCache::NumPrecompilesRemaining() returns 0 and if not, show the loading screen but this is never 0.

Hi there,

Thanks for reaching out. The PSO Precaching guide: https://dev.epicgames.com/documentation/en\-us/unreal\-engine/pso\-precaching\-for\-unreal\-engine\#loading\-screen has a section on how to handle shader PSO compilation with a loading screen. Calling FShaderPipelineCache::NumPrecompilesRemaining() and showing a loading screen until the count reaches 0 is our recommended approach, so you are on the right track. Looking at your logs, I see that the count also reaches zero on consecutive runs, which is to be expected. I also need to add that PSO compilation is faster after the first launch because the compiled PSOs are stored in a driver cache, which helps to accelerate the loading of your game. I hope that clears up your worries, but if anything is unclear, please get in touch with me.

Hi again,

Yes, that is the expected behavior. If you look at FShaderPipelineCache::NumPrecompilesRemaining(), it takes into account not only the number of PSOs that were passed through the precaching mechanism but also through other sources. So, it could be that there are still some PSOs that need compilation that were not picked up through the precaching mechanism. Something that you can try out is logging your game with r.PSOPrecache.Validation=2 set and checking what types of PSOs are being missed and queued for compilation.

Hi Justin,

I appreciate your follow-up question. I’ll try to answer it as best I can. PSO compilation can sometimes cause slower startup times, but I suggest you profile your game with Unreal Insights to confirm this. Just because you see many log statements on missed PSOs does not mean this will trigger a compilation immediately. I also need to mention that it is doubtful that FShaderPipelineCache::NumPrecompilesRemaining() will return zero immediately, even after a few runs. This is because we cannot tell if the PSO has been evicted from the driver cache or if the user made a driver update, which would cause a recompilation in both cases. However, you should notice that the load time decreases on consecutive runs, which is a sign that the PSO precaching mechanism is working. Please let me know if that is not the case.

Hi Justin,

I have been bundling it with a PSO cache that I compiled after running through the game multiple times and on different scalability settings (unless I’m confusing that with something else).

To ensure you know, we call the mechanism you describe the Bundled PSO Cache, which you use correctly. PSO Precaching is merely the system that compiles and caches PSOs at runtime. I must admit, the names of these two features have led to lots of confusion with licensees in the past, so hopefully we have not been talking past each other this entire time :slight_smile:

For WinGDK I had read that in another UDN post but it felt a little unclear to me as I would have thought like a Win64 build, the hardware would vary from player to player unlike say XSX or PS5 and therefore benefit from PSO precaching as well.

I don’t know what you mean here. Could you clarify your question, please?

Hi Tim

I have a PSO_GameProject_PCD3D_SM6.spc file that is packaged with builds that’s compiled from .rec.upipeline files generated while running through the game and the cooked ShaderStableInfo-Global-PCD3D_SM6.shk and ShaderStableInfo-GameProject-PCD3D_SM6.shk files.

As for WinGDK, I mean because the player’s PCs all vary in hardware. Wouldn’t the WinGDK builds also need to run PSO Precaching too just like any other Win64 build?

Hi Justin,

So, for our own projects, we have switched entirely to PSO precaching and let the compilation complete every time a session is launched. This is an acceptable trade-off, but you would also need to evaluate this on your own game.

As far as I know, the Windows Store is a frontend similar to Steam that distributes Windows applications. I think that the applications themselves are still built to target Win64 platforms, and the store packages them for easy distribution. That would mean that these applications could use PSO precaching. I hope that helps answer your question.

You are welcome. If you encounter new findings, feel free to share them here, and we can help clarify any problem spots.

Thanks for getting back to me Tim.

I’ve seen with other games they seem to only show this sort of loading screen on first launch or a patch and then subsequent launches it’s skipped. I’m hoping to replicate that behaviour but not sure on the best way to do it as it sounds like what I’m seeing at the moment from the logs and FShaderPipelineCache::NumPrecompilesRemaining() is the expected behaviour.

Thanks again Tim

I did another pass of building the cache and making sure to run through in scalability 0 through to 3 and it got bigger. I was noticing it taking longer as it grew (to be expected) I’ve attached an example of the log after setting r.PSOPrecache.Validation=2

This is from a level at the start of the game. I played through it slowly with -logPSO but still get stacks of these PSO PRECACHE misses. Could they be causing the compile to be slow at the start of the game?

My ultimate goal is to have this loading screen to be skipped after the first time the player launches the game but currently for that to work FShaderPipelineCache::NumPrecompilesRemaining() would need to start at zero which if I’m understanding correctly that would never be the case. Is there a suggested way to do this?

[2025.08.06-05.50.08:716][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: mi_car_Tyres

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: SkeletalMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: C2B9CBF991D99BA953B8F7DE459EBA5955224A81

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_car_Panels

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: SkeletalMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: A773C526657224AAC389600D2D6E8E728D4F2047

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_car_Lights

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: SkeletalMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: 26DF362B1AB2D14D1262D2C90F72ED7846FC1CC2

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_car_Interior

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: SkeletalMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: 68C47C86AD59A4D29E441CB8C9DC5830C1241F49

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: mi_char_NPC_Skin

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: bp_ProxyMeshComponent_C

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: 656AC26F74303DFA1A77873E9C8DFC1396E4C856

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: mi_char_NPC_Clothes

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: bp_ProxyMeshComponent_C

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: 69FC82E51941B0981E2F5F551BD60B8D47E7C2C3

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_char_NPC_Accessories

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: StaticMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: 400CE23DDBF6361B78113D9F4AD2504896ADC490

PixelShader: A537580241428C63B4EDD7A637CD8F95A0B57085

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 9460480)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: mi_char_NPC_Hair

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: bp_ProxyMeshComponent_C

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: DF4E8D29DE28D25D3AB2949EE2E9A2D1E270CDFE

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_char_Eyes_Toon

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: bp_ProxyMeshComponent_C

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: 65FEB0D2B92DB745823CE2B8D9703A965024BA00

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: BasicShapeMaterial

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: StaticMeshComponent

PassName: BasePass

Shader Hashes:

VertexShader: DEF745D4E25909E4FA8DE5CCCB29C5C67B385276

PixelShader: A9BD9F7D62C576F9E8A0934252243AF225D3F786

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 24320)

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 32512)

[2025.08.06-05.50.08:720][841]LogEngine:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: m_char_Mouth_Toon

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: bp_ProxyMeshComponent_C

PassName: BasePass

Shader Hashes:

VertexShader: 17C2C2CB48FA37052E591A1E483CC2230E9D4A1C

PixelShader: A492E537908AB3DDC963C60DE89B8E9492FB1935

Missed Info:

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: FLocalVertexFactory (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 9460480)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Precached with: TGPUSkinVertexFactoryDefault (PSOPrecacheParamData: 1071872)

Thanks Tim.

Curious if there’s an average of how many runs this might take before that number starts at zero?

Is there any advice on a best practice for this loading screen? Currently I have a loading bar but I feel like that won’t feel good for the player if it’s coming up for several runs of the game.

This does remind me, would a WinGDK build a separate PSO Cache than a Win64 build since it is technically a different platform?

Hi Justin,

It’s hard to say how many runs it would take, since it depends on a game-to-game basis. If you are having difficulty dealing with long loading screens, you could also create a bundled PSO cache that ships with your game. You could record several runs of your game and use them to build the bundled PSO cache, with the precaching mechanism picking up any missed PSOs. That should help in cutting some of the loading time as well.

For WinGDK, we do not use PSO precaching as the hardware on those platforms is fixed, and we can predictably build out the necessary PSOs when packaging your game.

Hi Tim

I currently have been bundling it with a PSO cache that I had compiled after running through the game multiple times and on different scalability settings (Unless I’m confusing that with something else).

For WinGDK I had read that in another UDN post but it felt a little unclear to me as I would have thought like a Win64 build, the hardware would vary from player to player unlike say XSX or PS5 and therefore benefit from PSO precaching as well.

Right. You are using both the bundled PSO cache and PSO Precaching, so you are on the right track.

WinGDK is the development kit for any Xbox platform, which does not require PSO precaching because those platforms have set hardware and driver configurations. On a PC platform, the GPU, the driver, and scalability settings can dictate the GPU pipeline’s state, making it very difficult to predict which PSOs to compile ahead of time. Simply compiling and shipping with all possible combinations is not tractable; you might have noticed that the bundled PSO caches can get quite large, so we introduced PSO precaching on PC to help alleviate this issue. Unfortunately, neither system is a perfect solution for combating the number of PSOs that must be compiled for a game, but if you use both in tandem, they should get you very close to solving it.

I hope this clears up your questions, but please let me know if you need further clarification.

Hi Tim

Thanks for confirming. Internally do you know what Epic does to help with longer loading screens at the start of their games that run precaching? Or do they just let it complete every time before starting the game.

So I guess to clarify, I’m referring to Windows Store builds that use WinGDK since it is a build that runs on PC. So I assumed that it would also need a bundled PSO cache and run the precaching system for the reasons you listed above. Am I misunderstanding that particular platform if there’s an extra layer of magic happening that means it doesn’t need this?

Thanks for the info, I’ll have to keep digging as to why it has such a high amount it needs to compile every time.