Skeletal mesh LODs never stream up under SSAM: FSkeletalMeshSceneProxy caches ForceStreamedLOD and is never re-gathered

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 Sebastien Lussier) 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.

[Attachment Removed]

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.

[Attachment Removed]

Hi Dan, thanks for the detailed steps and analysis. I’m currently setting up the scenario, will follow up with a CL on UE5 Main once reviewed and submitted.

You can follow this JIRA link as well: https://issues.unrealengine.com/issue/UE\-389184 - if its not active for you don’t worry we’re fixing an issue with that portal ATM.

[Attachment Removed]

And to be clearer, I’ll work on adding/piping in a refresh entry point, because SSAM fights external re-registration as it is.

[Attachment Removed]

Hi, I have a fix under review to add record refreshes in the SSAM when a forced LOD call comes in. I’m heading for a 2-week break though, will submit after.

[Attachment Removed]

The fix is in: CL 57641321 on //UE6/Main

I will wait 2 weeks for internal reports on stability before pushing to //UE5 (if the branch owners accept it).

[Attachment Removed]

Thanks, please provide branch and CL and I will cherry pick it when you are back.

[Attachment Removed]

Can you provide the CL to cherry pick please? Thanks, Dan

[Attachment Removed]

Hi Dan, I had to make several additional modifications to the CL to fix tangential issues in SSAM. I expect code review to finish today.

//UE5/Main is also now on hardlock, and at minimum won’t be able to push a fix there until it stabilizes on our main branch, UE6. Do you have access to the UE6 branch to pick from? Otherwise I may have to give you a diff.

[Attachment Removed]

I do have access to UE6, I am also on Epic Slack: ue-sup-wbg-render-ext

[Attachment Removed]

I cherry picked them into our 5.9 engine and can confirm the issue is fixed.

Thank you,

Dan

[Attachment Removed]