Mesh and texture loading optimization

Hello!

We’re working on optimizing asset loading in our Unreal Engine (5.6.1) scenes for an animated feature film.

We’re using persistent levels (not world partition) per sequence, with environments added as sublevels.

By default, we hide all sublevels in the Persistent Level (PER) and show only the ones needed from shots sequencer.

However, we’ve observed (using the Statistics tool and “rhi.dumpresourcememory all” command) that even hidden sublevels load many assets when opening the PER in the editor — notably static meshes and textures (though textures are streamed at low resolution, 64x64, so they’re not too heavy when not needed).

We initially suspected hard references in our Blueprints (static meshes directly assigned to components) and switched to soft references with dynamic loading/assignment in construction scripts.

But the assets still load.

Because the sublevels are in “Blueprint” mode, they won’t be loaded if not explicitly told so when in MRQ. But this does not apply to the editor it seems.

Further investigation of the ASCII version of a .umap file revealed that the level asset stores references to all actors and their components’ current assigned values — even for soft references set dynamically.

[Image Removed]

In our workflow, this pre-loading isn’t helpful, as we want assets loaded only when explicitly needed.

This behavior can also load assets that won’t be used, even when the sublevel is shown, because the level stores the current value at time T, but maybe the asset will be updated later with other data and the level won’t be re-saved and will keep a reference to an old data.

For example, if a Blueprint chooses a mesh based on another asset’s data (like our example BP_DA), the saved level might reference an old mesh (e.g., StaticMeshA), but runtime logic assigns a new one (e.g., StaticMeshB). This results in StaticMeshA being loaded unnecessarily.

Attached is a sample project reproducing this behavior, see “Steps to Reproduce” section.

Questions:

  • Why are meshes and textures loaded before the actors using them are even constructed?
  • What’s the reason behind this editor-specific behavior?
  • Do you have recommendations for limiting pre-loaded data in the editor?
  • Can we maybe disable this pre-loading or remove asset references from the level file?

Thanks a lot!

Maxime

[Attachment Removed]

Steps to Reproduce
Open the provided repro project.

The PER1 has the Sublevel1 as sublevel streaming, hidden by default. Same for PER2/Subevel2.

Both sublevels contains the same 3 actors, with no override from the level.

The actor’s assets are:

  • BP_SoftRef_NoUse__Cube: A blueprint with a variable of type “Static Mesh Soft Reference”, which do NOT load and assign the mesh to a component.
  • BP_SoftRef_Use__Cone: A blueprint with a variable of type “Static Mesh Soft Reference”, which DO load and assign the mesh to a component.
  • BP_DA_SoftRef_Use__Torus: A blueprint with a variable of type “Data Asset” which search in this data asset for a soft reference to a mesh to load and assign to a component.

Open either PER1 or PER2 (preferably each in a new Unreal, so that nothing is loaded before opening the PERs.)

Observe the Statistics Tool window and the result of the command “rhi.dumpresourcememory all”.

Opening PER1 will load 2 meshes (Cone and Torus), while PER2 will load 3.

PER2 behavior was created by allowing the BP_SoftRef_NoUse__Cube to actually load the cube, then the Sublevel2 was saved and the BP revert.

The Sublevel2 has “memorized” (stored) that the actor BP_SoftRef_NoUse__Cube had the cube assigned to its StaticMeshComponent, but the reality is now it won’t be assigned if we actually show the Sublevel2.

PER1 “rhi.dumpresourcememory all”

[Image Removed]

PER1 Statistics

[Image Removed]

PER2 “rhi.dumpresourcememory all”

[Image Removed]

PER2 Statistics

[Image Removed]

[Attachment Removed]

Hey guys :waving_hand:

Thanks for all the information and details on this topic. Level Streaming management through the Editor is indeed quite limited, and from what I can tell, this is expected behavior. I’m still investigating with the team, but after running a few tests on my side, I’m seeing the same results: in-editor level management still loads the assets, it’s essentially a visibility toggle.

That said, if you’re looking for more precise control using built-in engine features, have you recently tried World Partition? With its load and unload functionality, you can fully unload assets from a map, rather than just toggling their visibility.

I’m curious to know whether you’ve already explored workflows around World Partition, as it might offer solutions for more advanced level management needs.

Attached a quick demo where you can see the Sphere Static Mesh fully unloaded when using World Parition ‘Load/Unload’ functionality.

[Attachment Removed]

Hi Maxime,

  • About sub-levels, without World Partition, the sub-levels are loaded in the editor. I think that decision is made in ULevelStreaming::DetermineTargetState, which will return ELevelStreamingTargetState::LoadedNotVisible if the world is not a GameWorld.
  • Because the sublevel is loaded, it loads all its actors, including their components. In your case, the components have a hard reference to a StaticMesh. So that StaticMesh will also be loaded. This mesh is the mesh that was referenced by the component when the actor (Level) was last saved.
  • When you make the sublevel visible, it will add it to the scene. This will trigger AActor::RerunConstructionScripts which will re-run the CS. Then the logic may change the static mesh of the StaticMeshComponent of your actor(s).

If the sublevel’s static meshes of your actors were not set in the data and only modified in Blueprint at runtime (like during BeginPlay), then loading that sub-level shouldn’t load any meshes. But, I don’t know if this applies to your scenario.

Another idea - I didn’t try it (not sure it would work for you) - would be to add sub-levels dynamically via blueprint. Again not sure if this applies to your scenario. At runtime you can use “Load Level Instance”, and in editor I see in EditorLevelUtils the “Add Level to World” BP function. You would also need to manually unload the levels if needed.

As mentionned by Stéphane, you could convert your project to use World Partition. You could have a simple setup where streaming is disabled and each sub-level becomes a Data Layer.

Richard

[Attachment Removed]

Hey Stephane !

We know about world partition but we didn’t explore it yet, and we won’t be able to use it for our current project unfortunately.

I understand that it might be a little legacy and that the management in-editor might be specific, but I’m still curious about why this preloading is needed (as it might be wrong in some cases) and how in game development one would work with this is if all of the game was in the same PER ?

And could we somehow do a hotfix on our side to limit this behavior ?

Thanks!

Maxime

[Attachment Removed]