Hello there,
The system you are contructing seems good. Just to understand things a bit better, how are you waiting for the next frame? Depending on how, a single frame wait may actually end up being the same frame, and then none of the streaming systems would have refreshed yet. It may be the initial nanite residency that getting rendering if it’s the same frame.
One the first issue, with tradition mesh streaming, a forced LOD would be ideal, but with Nanite, that’s more complicated. Nanite streaming, like many of the continuous streaming systems, doesn’t provide any callbacks for completion as completion is a somewhat nebulous concept to define in such a continual streaming system. Waiting a few frames isn’t a bad option -- what your doing, in essence, is waiting for Nanite to reach a steady state -- but it can impact user experience. Depending on your fallback settings, disabling nanite on the captured mesh component and forcing LOD0 might actually be the best option to immediately have the mesh at full LOD.
Textures are similar to Nanite. The Virtual Texture (VT) system is GPU-driven and continuous. Since various tiles on a given object can be at effectively any mip level and that could be steady state for the given view, completion is hard to define. These concessions do absolutely complicate tasks such as the one you are doing, especially when the capture isn’t live. Since the object is relatively small in the UI, VT being slightly behind may get masked somewhat more than Nanite, but VT does typically take a few frames to settle in.
One solution would be to use an inventory version of the material that doesn’t rely on VT. That comes with the duplication of textures, which could certainly be an issue.
Another, and this is perhaps a properly hacky solution, is to use a non-deferred capture call on the scene capture and call it multiple times in a single frame. I’m unsure if this would kick the streaming systems since the CPU doesn’t get a tick in between. This also has definite performance implications and may well hitch.
PSOs are their own beast and their state isn’t trivial to track. There are a few reasons and workarounds, but the biggest key issue is that the state of the driver cache is essentially unknown between runs as it may have invalidated, so any check for the PSO existing would be limited to per-run. I’m assuming precaching is in use here as you mention a fallback strategy. Getting PSO state from the same run may be possible. PSO requests are discarded after completion, but can be kept around with D3D12.PSOPrecache.KeepLowLevel. Getting that working may be a fair effort though. I don’t believe this code exists in a form that would be easy to reuse, but it’s technically possible. I’ve listed three other options:
Option 1 would be to bundle to the PSOs for inventory items and do the compilation ahead of time. Bundling and Precache will work well together, but this adds manual effort in bundle collection. Automating this manual collection should be possible, though. Ideally, in an environment that is similar to the one in use for the capture as different PSOs can be required under different conditions, and PSO bundling only records the PSOs that were actually used. These PSOs also do have to be compiled at some point, but they could be prioritised with a usage mask either in the early precompile stage or manually after getting to the game logic.
Option 2 is to load the object early. I’m not sure there is such an event that would allow you to do this in your flow, but this is generally a recommendation for all assets when using Precaching. PostLoad will cause the Precache system to emit the required PSOs, so that may be useful to do at some point prior to needing the PSO in rendering the object. I believe changing the static mesh also emits PSO precache requests, but that’s typically very close to the point of rendering the asset.
Additionally, looking at the code, UPrimitiveComponent has a public member called PSOPrecacheRequestPriority that sets the priority. Depending on how the assets are set up, you may be able to leverage that to have your UI items queue-jump a bit if the early load is only briefly before the capture needs to happen.
Option 3 is to block on either all active PSOs or all PSO Precache requests. If there is no bundle, these are the same. This can hitch, so this is likely the last option I would try. The functions are NumPrecompilesRemaining() and NumActivePrecacheRequests(), and the code for these functions is in ShaderPipelineCache.cpp:825 and PipelineStateCache:4139.
I hope that helps.
Best regards,
Chris