Description

UE 5.8, cooked build, SSAM enabled (s.StreamableAssets.UseSimpleStreamableAssetManager=1, required by FastGeo).

Symptom: characters spawn at a crude LOD and can never stream up for the lifetime of the proxy.

FSkeletalMeshSceneProxy copies ForceStreamedLOD from FSkinnedMeshSceneProxyDesc at construction and encodes it as a negative TexelFactor in GetStreamableRenderAssetInfo, which only runs when the proxy is created. USkinnedMeshComponent::SetForceStreamedLOD reports later changes through IStreamingManager::NotifyPrimitiveUpdated, but FRenderAssetStreamingManager::NotifyPrimitiveUpdated

early-returns unconditionally when SSAM is enabled. Nothing else triggers a re-gather, so the streamer stays pinned to whatever LOD happened to be in effect at proxy creation.

This looks like a consequence of CL 53550964 (Main: 53550977 ) moving the gather from IPrimitiveComponent, which was deprecated there, onto the proxy. The component path was re-queried on

NotifyPrimitiveUpdated; the proxy path is a one-time snapshot.

Impact for us: an instrumented run showed 33 of 276 skeletal mesh registrations permanently pinned worse than requested. It is most visible on unique meshes such as named-character heads, since

residency is per asset via Max(LODNum - ForceStreamedLOD) and shared meshes get rescued by whichever requester wants the best LOD.

Local workaround: call MarkRenderStateDirty() in SetForceStreamedLOD (Engine\Source\Runtime\Engine\Private\Components\SkinnedMeshComponent.cpp)when SSAM is enabled and SceneProxy is non-null, forcing a re-gather.

Question: full proxy recreation is heavier than necessary. Would you prefer a targeted proxy-side update plus SSAM re-registration, or should SSAM expose a refresh entry point for this case?

Also verified present in //UE5/Release-5.8, //UE5/Dev-Release-5.8, //UE5/Main and //UE6/Main.

Steps to Reproduce

Steps to Reproduce

Steps to reproduce (packaged build; skeletal mesh LOD streaming does not run in-editor):

 

1. Enable SSAM. It is ECVF_ReadOnly and must be set before FStreamingManagerCollection is constructed, so put it in DefaultEngine.ini rather than the console:

 

    [SystemSettings]

    s.StreamableAssets.UseSimpleStreamableAssetManager=1

 

2. Take a skeletal mesh with several streaming LODs (4+) and confirm it streams correctly with SSAM off.

 

3. Add an actor with a ULODSyncComponent, listing the skeletal mesh component in both SubComponents and DriveComponents.

 

4. Spawn the actor far enough from the camera that ULODSyncComponent::UpdateLOD resolves a crude HighestPriLOD and calls SetForceStreamedLOD(3) on the mesh. The proxy now snapshots ForceStreamedLOD = 3.

 

5. Move the camera close. UpdateLOD now calls SetForceStreamedLOD(0).

 

6. Observe the mesh stays at LOD3 and never streams up. ListStreamingRenderAssets shows the asset still pinned to the LOD3 forced count, and no further gather occurs for that primitive.

 

Confirmation that this is not a memory budget issue:

 - r.Streaming.FullyLoadMeshes 1 immediately brings the LODs in.

 - Setting s.StreamableAssets.UseSimpleStreamableAssetManager=0 makes the same case behave correctly, because FRenderAssetStreamingManager::NotifyPrimitiveUpdated is no longer skipped.

 

Minimal variant without ULODSyncComponent: call USkinnedMeshComponent::SetForceStreamedLOD(3) on spawn, wait for the proxy to be created, then call SetForceStreamedLOD(0). The second call is lost.

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-389184 in the post.

0
Login to Vote

Unresolved
CreatedJul 29, 2026
UpdatedAug 4, 2026
View Jira Issue