Tracking down PSO precache misses for a custom mesh processor

Hi,

I hope you can help me again. I implemented PSO precaching for our custom mesh processor related to lighting (which removed all of the untracked PSOs, hooray!), but now we have legitimate PSO precache misses related to the same mesh processor. I’m having trouble tracking them down.

I implemented PSO precaching by:

  • Overriding CollectPSOInitializers function for the mesh pass processor
  • Using a REGISTER_MESHPASSPROCESSOR_AND_PSOCOLLECTOR macro to register it

I’m looking at multiple pieces of information to track down why PSO Precache Misses still happen, but I’m not sure how to parse all of it.

From the logs, I get a lot of similar entries:

PSO PRECACHING MISS:

Type: ShadersOnly

PSOPrecachingState: Missed

Material: roof

VertexFactoryType: FLocalVertexFactory

MDCStatsCategory: StaticMeshComponent

MeshPassName: SmartVoxelize

Shader Hashes:

VertexShader:    EC467F81CA0695B253E15823E35C8C118D380A9E

PixelShader:    53BB51FD38CF6761EF48AA9E7207E18FA1DC26A7

Missed Info:

Precached with:    FNaniteVertexFactory (PSOPrecacheParamData: 130048\)

which shows me the custom mesh pass SmartVoxelize failed for the roof material on a static mesh component, but I’m not quite sure why.

I also tried looking at the Unreal Insights Trace. Here is an example unrelated to the roof material, where PSO was missed on a PostProcessing pass:

[Image Removed]

I get quite a lot of DOFs and Blooms that trigger a PSOPrecache Miss. I’ve attached an Unreal Insights Trace in case it’s helpful.

I’m pretty sure this is all related to our custom mesh processor, but I’m not sure how to use this information to give PSO precaching what it needs during initialization. If you have any advice on how to track that down, that would be very appreciated.

Thank you!

[Attachment Removed]

Hi there,

According to your log statement, you are missing some PSO precaching settings for your SmartVoxelize pass. Your custom MPP got picked up through the NaniteVertexFactory. However, for your SmartVoxelize pass, you might still be missing an implementation of FPSOCollectorCreateManager::RegisterPSOCollector(), or your collection pass is not covering non-Nanite meshes. I would check if that function is in place first, before continuing with further debugging. The same goes for some of your other custom pass SmartBrick.

As for the DOF shaders, you might need to enable precaching of global shaders at startup (r.PSOPrecache.GlobalShader=<1 or 2>), since they should already be covered. We have this option turned off by default because it can slow load times, but it can help pick up these global shader hitches.

I hope that helps you get started, but please let me know if you have any more questions.

[Attachment Removed]

So a mesh draw command (MDC) captures everything the RHI needs to draw a primitive in a given pass:

- PSO initializer (shaders + render state + RT formats)

- Bound shader parameters (textures, uniform buffers, samplers)

- Vertex streams, index buffer, draw arguments (or indirect args)

There are two flavors:

- Cached MDCs: built once when a static primitive is added to the scene (FCachedMeshDrawCommandInfo), stored per scene/pass, reused every frame. This is the fast path for static geometry.

- Dynamic MDCs: built per-frame for dynamic primitives or passes that can’t cache (volumetric, view-dependent things). FMeshDrawCommandPassSetupTask is the async version that does this from the GatherDynamicMeshElements output.

Each pass has an FMeshPassProcessor that converts visible FMeshBatches into MDCs. Then at render time, the pass walks its visible MDC list, sorts them, and submits them via FMeshDrawCommand::SubmitDrawBegin. So MDCs are the decoupling layer: visibility/state setup runs asynchronously on worker threads, building MDCs, and the render thread/RHI thread iterates over and submits them. Without MDCs, every primitive would do all its state translation on the render thread every frame.

So when do we actually hitch?

Hitching happens at submit time, not build time. The MDC build only stores a FGraphicsMinimalPipelineStateInitializer (a key/hash), and no PSO is checked or compiled at that time. The precache state lookup lives in RetrieveAndCachePSOPrecacheResult, which is called from SubmitDrawBegin right before SetGraphicsPipelineStateCheckApply. If we do not have a compiled version ready at that time, we need to compile the PSO synchronously, which introduces the hitch.

I hope that answers your questions, but please let me know if you need any further clarification.

[Attachment Removed]

Thank you! I will investigate the r.PSOPrecache.GlobalShader cvar

As for our custom pass, we noticed something interesting during our investigation. The PSO precache miss was coming from trying to build the mesh draw command, but then we weren’t rendering anything using our custom mesh pass processors (there was a specific condition in our code that turned it off)

I’m just trying to understand this, how is building mesh draw commands actually related to rendering the scene in Unreal? And if we get a PSO precache miss when we are building mesh draw commands, do we hitch then or do we hitch when we actually try to render the shader with the missing PSO?

[Attachment Removed]