Cannot allocate more root pages...

Hello,

In a project which involves a city environment in World Partition, we met this stack and message in a crash trying to optimize the whole level before launching a package.

... [2025.06.21-17.08.55:788][ 8]AssetCheck: /Game/CompiledLevels/Full_Munich_2025-06-21-15-31/Meshes/SM_DynConv_DynamicMeshComponent_29805 Validating asset [2025.06.21-17.08.55:788][ 8]AssetCheck: /Game/CompiledLevels/Full_Munich_2025-06-21-15-31/Meshes/SM_DynConv_DynamicMeshComponent_29806 Validating asset [2025.06.21-17.08.55:788][ 8]LogAudioMixer: Display: Audio Buffer Underrun (starvation) detected. InstanceID=1 [2025.06.21-17.08.55:788][ 8]LogWindows: Error: === Critical error: === [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [2025.06.21-17.08.55:788][ 8]LogWindows: Error: Fatal error: [File:D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Rendering\NaniteStreamingManager.cpp] [Line: 1453] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: Cannot allocate more root pages 32768/32768. Pool resource has grown to maximum size of 2048MB. [2025.06.21-17.08.55:788][ 8]LogWindows: Error: 1024MB is spent on streaming data, leaving 1024MB for 32768 root pages. [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffd8ee24471 UnrealEditor-Engine.dll!Nanite::FStreamingManager::Add() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Rendering\NaniteStreamingManager.cpp:1457] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffdca64c2f5 UnrealEditor-RenderCore.dll!FRenderCommandPipeRegistry::StopRecording’::2'::<lambda_1>::operator()() [D:\build\++UE5\Sync\Engine\Source\Runtime\RenderCore\Private\RenderingThread.cpp:1541] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffdca679fff UnrealEditor-RenderCore.dll!TGraphTask<TFunctionGraphTaskImpl<void __cdecl(void),1> >::ExecuteTask() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h:634] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffd91ab2722 UnrealEditor-Core.dll!UE::Tasks::Private::FTaskBase::TryExecuteTask() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Public\Tasks\TaskPrivate.h:504] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffd91aa7d4a UnrealEditor-Core.dll!FNamedTaskThread::ProcessTasksNamedThread() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:779] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffd91aa81ee UnrealEditor-Core.dll!FNamedTaskThread::ProcessTasksUntilQuit() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:668] [2025.06.21-17.08.55:788][ 8]LogWindows: Error: [Callstack] 0x00007ffdca6a894c UnrealEditor-RenderCore.dll!RenderingThreadMain() [D:\build\++UE5\Sync\Engine\Source\Runtime\RenderCore\Private\RenderingThread.cpp:317] ...

Basically, we load the data by regions with a given size (actually 8192x8192 meters), we iterate over the dynamic mesh components to convert these in static meshes and replace afterwards the dynamic mesh component in static mesh component. In the process, we apply the Nanite property to the new assets. We save the static mesh right after.

When we are done, we save the actors and unload the region to continue with another one. Before loading the next one, we force the garbage collection.

My question is about the stack above itself. I saw a few references on the web where people tried a few things like changing this setting, r.Nanite.Streaming.StreamingPoolSize (I tried the values 512 and 1024).

I tried to lower the size of my regions (4096x4096 meters) to limit the number of the instances loaded at the same time. Nothing changed.

How could I inspect this issue? Would you have any other settings I could play with? I tried to see if there was no huge Nanite instance (because of an error at the instanciation) or something like that but I saw nothing in particular.

Any help on this will be welcome. Thank you,

Regards,

Philippe

Hello there,

Since it’s the root page, I think the limiting factor here is the number of unique nanite meshes in the scene. Root pages are allocated by FStreamingManager::Add and deallocated by Remove.

There is a 2GiB (4GiB on AMD) buffer that is shared between root and streaming pages. Potentially, you could reduce the StreamingPoolSize even lower than 512.

I imagine this is unattended, so stat NaniteStreaming is probably not an option to debug. It’s also possible something is holding on to the mesh in a way that causes Remove to not be called.

Best regards,

Chris

Hello Chris,

Thank you for your answer! I tried to reduce the StreamingPoolSize at 256 but the result is still the same. I will monitor the resources added by FStreamingManager::Add and check wether they are freed or not.

Thank you again, have a nice day,

Philippe

Hello Chris,

This is just an update, to keep you informed. It seems that a great part, if not all, of the actors for which I applied an optimization (for the record, conversion from dynamic / procedural / … to static meshes with Nanite option if it makes sense), were kept loaded in the editor even if I unload the current region and load another one to treat another batch.

I saw indeed in FStreamingManager::Add that I was adding a tremendous number of resources (most of them, the result of the conversion), and they were never released. I discovered in the function UWorldPartition::FWorldPartitionExternalDirtyActorsTracker::Tick that my actors were “pinned” and I believe, it does not help when we deal with a huge level. To save my actors, I use the function UEditorLoadingAndSavingUtils::SavePackages.

To solve this situation, I did 2 things :

  • If I had multiple dynamic meshes in the actors with similar properties, I merged these before converting to static mesh component. That decreased the number of Nanite resources to add to the pool.
  • For the actors, before calling the function SavePackages, I added the line UWorldPartition::FDisableNonDirtyActorTrackingScope Scope(pWorld->GetWorldPartition(), true); which seems to have done the trick and I think that I really don’t need that tracking in my case.

I have remaining actors that won’t unload even with my modification above but it is for a different reason (the problem is also present in my main level, so before the optimizations) and I’ll do a new post for that.

Have a nice day,

Philippe

You are very welcome, and I hope that helps.

Best regards,

Chris

Thanks for the followup!

Good finds, and I’m glad that this is mostly working for you.

If you have any further questions around this issue, please free feel ask. Otherwise, would you mind if I close the case for now?

Best regards,

Chris

Yes, you can close the case. Thank you.

Best regards,

Philippe

You’re very welcome.

Best regards,

Chris