Preventing Nanite pop-in issues in 2D rendering use cases

This question was created in reference to: [Some Nanite Meshes take longer time to have high [Content removed]

As described in the ticket mentioned above we were seeing many pop-in issues on the first ever 2D image rendered on a cloud instance in our 2D rendering use cases. As the discussion shows, so far our best option was to increase minimum residency for all static meshes slightly. We were at 1024 KB so far to avoid bad geometry resolution is visible in these images. Due to a crash reported within [Content removed] we had to reduce the minimum residency down to 512 KB. Whereas this prevents our application from crashing now, we do see the pop-in issues again on some parts (see attached images for example)[Image Removed]

As you can see most parts show the correct resolution, but some particular one’s don’t. So where are having a target conflict between preventing the crash and Nanite pop-in issues here.

Our question is: Do you see any other option to avoid these pop-in issues? At the moment we are still on UE5.3 but will switch to 5.5 soon. However, a quick test in 5.5 did show the same issue again. Are there any other options available in UE5.5 compared to 5.3 that could help? Do you have any idea why only particular meshes show the issue and not all?

Thanks much!

Hi,

I have been assigned to investigate this issue. Can you please clarify a few things:

  • By “on the first ever 2D image rendered”, does this mean that the issue only occurs the first time you render and does the problem disappear on the second try?
  • Does the issue resolve itself after a few frames (streaming in the correct resolution during the same session) or are the low resolution parts “stuck” in their low poly state? The reason I ask is because Nanite should be solving any LOD pop-in issues. In this case, it might be possible that for some reason Nanite is showing the fallback mesh (similar to this issue). In that case a workaround might be to set the Fallback Target on each mesh to Percent Triangles and leave the Fallback Triangle Percent to 100, to keep the maximum detail in the parts showing the fallback mesh. This is not a proper fix however.

This information can help me further diagnose the problem.

Thanks,

Sam

Hi Sam,

thanks for your reply. I am happy to answer your questions below:

  • We are running an image on demand service on many cloud instances. The images shown above occur “randomly”. However, I am able to reproduce the issue locally in the editor as well. The first image rendered from a specific configuration shows the issue of too low resolution. In other first, the first time the related static meh is made visible there is a high chance it shows a lower resolution. All further renderings even with different camera perspectives used but with the same configuration are ok.
  • After a few frames (and I think it is even just one frame) the right resolution is shown. Where the 2d rendered image shows low resolution the PIE viewport shows the correct one already. However, I can definitely say that we are not seeing the fallback mesh in these case since the resolution of the mesh shown differs based on conditions (e.g. hardware it is rendered, environment used, etc.). In the past we did do tests with increasing the Nanite Fallback Mesh relative error. We decided to go for Minimum Residency instead because increasing the fallback mesh introduced a notable increase of memory footprint.

One additional note in case that has any impact on diagnosis: We have changed the following 2 cvars:

r.Nanite.MaxPixelsPerEdge=0.1

r.RayTracing.Nanite.Mode=1

Thanks much,

Jan

Hi,

thanks for the clarifications. Looking at the release notes for UE5.4 and UE5.5, I couldn’t see anything that would help solve the Nanite pop-in issue (besides significant performance and memory improvements). Referencing the thread you linked to on this issue, I believe adding a few warm-up frames is currently still the best workaround. Receiving a notification from Nanite when the mesh has completed loading would be better indeed, but as far as I can tell, this would be difficult to achieve as Nanite constantly streams clusters in and out. However I can escalate this case to Epic to have someone with subject matter expertise look at this.

Alternatively, as far as I understand setting the static mesh’s Minimum Residency to 1024 KB would be a viable solution (if doing that didn’t cause a crash)? I would like to be able to follow up on this, but unfortunately I’m unable to read the content at the [Content removed] you posted (possibly because it’s confidential or still pending moderation). Do you happen to have a question link (instead of the case link)?

Thank you,

Sam

Thanks for your thoughts on this Sam. These answers are already helpful for us.

Nevertheless, it would be great to get your opinion on that crash issue. Unfortunately I cannot get any other link than the one I have posted. Can you probably get me a hint where to get a question link from? Maybe you cannot access because this was reported in UDN as confidential question (before the switch to Epic Pro Support).

Hi Sam, I have copied the relevant parts of the UDN ticket regarding the crash below:

We have an Engine crash in theNaniteStreamingManagerwhen we load some large models of our vehicles.

Unfortunately the logs do not provide much information, but we can easily replicate the problem in the Editor

appError called: Assertion failed: AllocatedPagesSize <= ( 1u << 31 ) [File:D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Rendering\NaniteStreamingManager.cpp] [Line: 1442]

The answer in the ticket was this one:

The limit you are hitting here is that the Nanite page pool is limited to 2GB. This is the API limitation in D3D12 for buffer sizes, which is hard to get around.

The page pool contains all the Nanite geometry data. We don’t currently have support for splitting the data over multiple buffer.

In this pool there are two types of pages. Streaming pages that are loaded/unloaded based on what is visible in the scene and root pages that are always loaded. By default each unique mesh in the scene has one root page, which guarantees that no matter what the streaming state is we always have something to draw for every mesh.

Streaming pages are 128KB each and root pages are 32KB each. What is happening here is that the sum of the two get over this hard limit of 2GB.

The memory dedicated to streaming pages is fixed and controllable with r.Nanite.Streaming.StreamingPoolSize. It defaults to 512MB.

Root pages allocated based on how many unique meshes are in the scene. So if the streaming pool size is at the default 512MB. That probably means you have more than 1536MB / 32KB = 49152 unique meshes in the scene.

I think the only real mitigation here is trying to reduce the number of unique meshes somehow, or reducing r.Nanite.Streaming.StreamingPoolSize, especially if it is larger than the default.

Ah, and one more thing. On each mesh there is a Residency setting that defaults to minimal (32KB), but can be adjusted higher. If you are nowhere near these high object counts, then maybe some of your objects use a higher residency than what is required.

Thanks much,

Jan

Hi,

thank you for sharing this info, it makes more sense now. As mentioned, unfortunately DX12 has a hard buffer size limit of 2GB, which might be hit when there are a large number of unique meshes in the scene, especially when each mesh keeps a relatively high amount of its geometry resident at all times (the minimum residency for root geometry).

If the minimum residency is set to 1024 KB, that would mean a maximum of 1536 (= 1536 MB / 1024 KB) unique meshes can be loaded in the scene, provided that the streaming pool is set to its default value of 512 MB (the 1536 MB number comes from subtracting the streaming pool size of 512 MB from the hard limit of 2048 MB for DX12 buffers).

To avoid hitting the 2048 MB limit and crash, you can either:

  • reduce the number of unique meshes to less than 1536 by e.g. by merging two or more smaller meshes into a larger mesh. Maybe some unique meshes are identical or mirror images and can be made into instances instead. This might give good results with minimum residency 1024 if the triangle count of each merged mesh is still relatively low.
  • lower the streaming pool size (with r.Nanite.Streaming.StreamingPoolSize) to make more space available for root pages (or root geometry) and as such allow for loading more unique meshes. According to the documentation, using smaller pools will increase IO and decompression work when moving around the scene. If the pool is not large enough to fit all the data needed for a view, cache thrashing can occur where streaming never settles even for a static view. I’m not sure how much the visual fidelity will be impact from doing this
  • only set a minimum residency of 1024 KB on the meshes which require the most detail or which will be captured first, and a minimum residency 512 KB on the remaining meshes. This could be done programmatically by looping over all the meshes and adjusting their minimum residency based on their triangle count
  • some combination of the above

Hopefully the above is helpful. Please let me know if you have any further questions or need clarification.

Best regards,

Sam

Hi again,

I discussed this with one of my colleagues, who mentioned that the 2GB buffer limit for Nanite’s geometry pool size does not apply to some higher end AMD GPUs which allow for a 4GB pool size ([UDN case about that [Content removed]

If it’s possible to use cloud instances with AMD GPUs, perhaps this could be another solution to avoid the crash.

I will close this ticket for now, please open a new case if you have any further questions.

Thanks,

Sam

Hi,

thanks for your feedback and you’re welcome.

I realised that the question view for the crash issue will also be inaccessible, but I have asked internally for more info about that case and will provide an update once I know more.

Thank you,

Sam

Hi,

after some asking around, unfortunately it’s not possible for me to see confidential cases. Are there any details you are able to share here on the crash? If that’s not possible, I can re-assign this case to Epic for further investigation.

Thanks,

Sam

Thanks much, that is helpful.