Cache

Cache Rules

Now let’s talk about a new topic: the mesh command cache. Think about this question first:

Do we need to do these steps in every frame?
image

If our mesh is a static cube and we are not changing its material or transform, then there is no need to rebuild the mesh batch or the mesh draw commands.

I recommend reading the official document about the mesh draw command caching system at this link. However, if you are busy, here is a helpful image from that page:

image
  • The VF means vertex factory. We talked in
  • The orange arrow indicates that updates occur every frame, while the blue arrow means that the results are cached.
  • Because our cube is a simple static mesh component, it follows the lowest path, resulting in one mesh batch and the caching of all mesh draw commands until you modify it and invalidate the cache.

Cache timing

When should cache data be built?

The main cache timing is UpdateAllPrimitiveSceneInfos of FScene . If a static mesh is added the first time, or if the cache is invalidated, this function will called to rebuild the cache.

Call Stack

Cache data holder

Where is the cached data stored?

In a previous discussion about RenderProxyFMeshBatch, we use this article From Render Proxy to Mesh Batches to explain the process. The final step we mentioned is:

  • It will call FStaticMeshSceneProxy::DrawStaticElements.
    • This function will call FStaticMeshSceneProxy::GetMeshElement , and return a FMeshBatch.

We stopped at this chapter earlier, but let's dive in a little deeper now.

The resulting mesh batch is stored in a FPrimitiveSceneInfo object which is used to cache data. This object includes:

  • TArray<class FStaticMeshBatch> StaticMeshes: for caching static mesh batches
  • TArray<class FCachedMeshDrawCommandInfo> StaticMeshCommandInfos : for caching static mesh command id
The real mesh draw command cache is inside the FScene , called CachedDrawLists and CachedMeshDrawCommandStateBuckets

This is the first time we're encountering FPrimitiveSceneInfo, so let's discuss it a bit more.

FPrimitiveSceneInfo

The official comment about this structure is

The renderer's internal state for a single UPrimitiveComponent. This has a one to one mapping with FPrimitiveSceneProxy, which is in the engine module.

This is a simple image to show the data relationships:

  • FPrimitiveSceneInfo directly caches the static mesh batches. But indirectly caches the mesh draw command with a struct called FStaticMeshCommandInfo which holds an index to the real cache CachedDrawLists in the FScene
  • FCachedPassMeshDrawList CachedDrawLists[EMeshPass::Num] can be treat as a dictionary with a key of EMeshPass . The element is a TSparseArray<FMeshDrawCommand> MeshDrawCommands

Cache invalidation

Rule

Any data that a Mesh Pass Processor reads in AddMeshBatch is a dependency of the cached mesh draw commands. When that dependency changes, the cached commands must be invalidated.
A single primitive's cached commands can be invalidated with FPrimitiveSceneInfo::BeginDeferredUpdateStaticMeshes. The entire scene's cached commands can be invalidated by setting Scene->bScenesPrimitivesNeedStaticMeshElementUpdate to true. This is a heavyweight operation and should be avoided during gameplay as it will cause a hitch in larger scenes.

Invalidation Process

As an example, you drag the cube a little

image

This is what happens:

  • Handle your mouse move event ( drag event), or the property changes, or you set rendering related flags, or
    1. image
    2. MarkRenderStateDirty()
  • End of frame
    1. image
    2. UWorld::SendAllEndOfFrameUpdates
    3. UActorComponent::DoDeferredRenderUpdates_Concurrent
    4. Because the bRenderStateDirty is true, this component tries to recreate the render state with RecreateRenderState_Concurrent
    5. In UPrimitiveComponent::CreateRenderState_Concurrent , it calls GetWorld()->Scene->AddPrimitive to add itself to the FScene
    6. Enqueue the task of add PrimitiveSceneInfo to the render thread
  • The render thread picks the task:
    1. image
    2. Add the PrimitiveSceneInfo passed from the game thread to AddedPrimitiveSceneInfos
  • Render thread update:
    1. image
    2. FScene::UpdateAllPrimitiveSceneInfos
      • Copy AddedPrimitiveSceneInfos to AddedLocalPrimitiveSceneInfos
      • Copy AddedLocalPrimitiveSceneInfos to SceneInfosWithStaticDrawListUpdate
      • For each SceneInfosWithStaticDrawListUpdate:
        • Call FPrimitiveSceneInfo::CacheMeshDrawCommands

A minimap of everything

image
  • We show two ticks in this image
  • You can see the timing of FRenderProxyFMeshBatchFMeshDrawCommands and get cached.
  • In my environment, the real mesh command caching timing is deferred.

Configure cache strategy

Mesh Processor

In , we gave an example:

REGISTER_MESHPASSPROCESSOR_AND_PSOCOLLECTOR(DepthPass, CreateDepthPassProcessor, EShadingPath::Deferred, EMeshPass::DepthPass, EMeshPassFlags::CachedMeshCommands | EMeshPassFlags::MainView);

The final parameter EMeshPassFlags::CachedMeshCommands is the key to report this FMeshCommandProcessor should support caching.

Mesh Render Proxy

In GetViewRelevance , you can configure if this render proxy is bStaticRelevance or bDynamicRelevance

An example is FPrimitiveViewRelevance FStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const. Please check the source.

Mesh Draw Command

Check SupportsCachingMeshDrawCommands in the PrimitiveSceneProxy.cpp

  • MeshBatch only has one element: MeshBatch.Elements.Num() == 1
  • VertexFactory supports caching mesh draw commands: MeshBatch.VertexFactory->GetType()->SupportsCachingMeshDrawCommands()
  • Material doesn’t have external texture parameters.
  • image