Nanite fallback mesh buffers VRAM residence

Hello,

We found that Nanite fallback meshes use around 100MB of VRAM in some of our scenes. We have the fallback meshes relatively low-poly, but there are just too many unique ones.

`FStaticMeshRenderData::InitResources` seems to initialize `LODResources` and `LODVertexFactories` for any mesh, even if Nanite data is valid.

I modified this function to skip the loop through `LODResources`

static const auto EnableNaniteCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite")); if (!HasValidNaniteData() || !DoesPlatformSupportNanite(GMaxRHIShaderPlatform) || !EnableNaniteCVar || EnableNaniteCVar->GetInt() == 0) { for (int32 LODIndex = 0; LODIndex < LODResources.Num(); ++LODIndex) ....And it worked well, until it crashed in `FSceneProxy::FFallbackLODInfo::FFallbackLODInfo` on `checkf(LocalVF->GetTangentsSRV()…` because Nanite StaticMeshes were used by some StaticMeshComponents with OverrideVertexColors. We’ve fixed that issue by removing the MeshPainting from those components.

However, we are now very concerned about the stability and general viability of this approach. Do you have any hidden issues or drawbacks in mind?

We only use the software Lumen, so the global `IsRayTracingAllowed()` returns false. In this case, Lumen should not use the fallback mesh at cooked-packaged runtime, correct? Is the SDF generated from the fallback mesh during the cooking process?

Where else does the engine use the fallback mesh?

Thank you.

Best,

Denis

Hello,

Thank you for reaching out.

I’ve been assigned this issue, and we will be looking into Nanite fallback uses and memory impacts for you.

Hello,

Instead of stripping the Nanite Fallback at runtime, you can strip them at cook-time.

These two CLs from //UE5/Main implement a method of stripping the Nanite Fallback during cook.

  • CL 40280437 - Primary Implementation
  • CL 40871224 - Fix for an edge case with ISMs

The description for CL 40280437 has information about the usage for Nanite Fallback meshes, and how the decision to strip them is made.

Please let us know if this helps.

Hello Stephen,

Thank you for recommending the proper approach and providing the CL numbers. I carefully cherry-picked it into our 5.4.3 engine.

This set of changes seems to be working and stable, but it now causes a seemingly unrelated issue — the texture streaming pool constantly exceeds the budget.

I tried a clean build of our game, and there is no issue.

The streaming pool issue is only present in the build (from the same internal CL, exact configuration, same script to build-cook-package) with those two cherry-picked changes and the corresponding change in the Config/DefaultEngine.ini

[/Script/WindowsTargetPlatform.WindowsTargetSettings] bGenerateNaniteFallbackMeshes=FalseI analyzed RHI resources dumped by rhi.DumpResourceMemory all -csvfile. Strangely enough, the build with stripping seems to have many fewer Mip0 textures streamed, and the overall memory usage of the resources is 400MB less compared to the build without the said feature integrated. It’s almost like something confuses the streaming system badly.

Attaching the output of the DumpTextureStreamingStats console command, same scene, right after the game starts

The “clean” build without cherry-picked stripping:

[2025.05.14-15.28.44:825][ 24]--------------------------------------------------------

[2025.05.14-15.28.44:826][ 24]Texture Streaming Stats:

[2025.05.14-15.28.44:827][ 24]Total Pool Size (aka RenderAssetPool) = 2000.00 MB

[2025.05.14-15.28.44:827][ 24]Non-Streaming Mips = 3296.72 MB

[2025.05.14-15.28.44:827][ 24]Remaining Streaming Pool Size = 2000.00 MB

[2025.05.14-15.28.44:827][ 24]Streaming Assets, Current/Pool = 1075.81 / 2000.00 MB (54%)

[2025.05.14-15.28.44:827][ 24]Streaming Assets, Target/Pool = 1075.81 / 2000.00 MB (54%)

[2025.05.14-15.28.44:827][ 24]--------------------------------------------------------

With cherry-picked stripping:

[2025.05.14-15.35.24:674][166]--------------------------------------------------------

[2025.05.14-15.35.24:674][166]Texture Streaming Stats:

[2025.05.14-15.35.24:675][166]Total Pool Size (aka RenderAssetPool) = 2000.00 MB

[2025.05.14-15.35.24:675][166]Non-Streaming Mips = 3316.09 MB

[2025.05.14-15.35.24:675][166]Remaining Streaming Pool Size = 2000.00 MB

[2025.05.14-15.35.24:675][166]Streaming Assets, Current/Pool = 1999.98 / 2000.00 MB (100%)

[2025.05.14-15.35.24:675][166]Streaming Assets, Target/Pool = 7705.27 / 2000.00 MB (385%)

[2025.05.14-15.35.24:675][166]--------------------------------------------------------

I know it doesn’t sound related, but I triple-checked everything. Please let me know what could have potentially caused this strange result.

Best,

Denis

Recooking with the feature present but simply disabled stripping removes the issue. Obviously, it returns the fallback meshes’ VRAM cost.

[/Script/WindowsTargetPlatform.WindowsTargetSettings] bGenerateNaniteFallbackMeshes=TrueI put a breakpoint in FRenderAssetInstanceAsyncView::UpdateBoundSizes_Async() and inspected FAsyncRenderAssetStreamingData::StaticInstancesViews.

The build with Nanite Fallbacks stripped has fewer StaticInstancesViews—I might be wrong, but I only found SK components or SM components with Nanite-disabled SMs, like translucent glass meshes.

No fallback meshes:

[Image Removed]With fallback meshes:

[Image Removed]

The streaming decision logic is quite complicated to debug efficiently, and I haven’t studied it before. Any information on where to look is much appreciated.

Found it by going through the streaming code with the debugger and comparing the code paths taken.

A fix from CL 40885333 was missing.

It was messing up the texture streaming, as UStaticMeshComponent::GetStreamingRenderAssetInfo() has an immediate early-out that did not check GetStaticMesh()->

HasValidNaniteData() prior to this CL.

Hello,

Thank you for sharing the CL you needed.

Can we now close your case? You can always re-open it if you need additional assistance with the same issue.

Hello Stephen,

Thank you for the help. The stripping feature seems to be working and stable, so we can close the case.

Best,

Denis