We are seeing some instances of pretty bad hitching (sometimes up to ~200ms) on the render thread due to mip streaming when spawning certain particle systems and I was hoping to get some advice on how to mitigate.
The most common case for me is something like a projectile hit effect / explosion up close where it needs a bunch of high res mips. It seems like all of these mips are asked to stream in on the same frame. Even though these are handled asynchronously in background workers, they all contest the same lock in `FD3D12FastAllocator::Allocate`. There is often enough things being streamed in that the allocator has to create a lot of new committed resources for new pages and this sometimes ends up taking a long time and the frame is blocked until everything completes. Ideally these would be able to stream in the background and not hitch if they did not complete this frame.
I have seen “r.Streaming.MaxNumTexturesToStreamPerFrame / r.Streaming.AmortizeCPUToGPUCopy” that would limit the number of copies per frame, but from testing I would have to drop it pretty low to get rid of the hitching and I am hesitant to do so in case it causes visual issues in other situations, and I see it is completely off by default so I wonder if that is something you recommend using?
Alternatively maybe there is some allocator settings to help with this that I may not be aware of, to avoid it having to allocate so many committed resources in these cases or to avoid the bottleneck on the allocator?
We do have complex particle systems here in general, so maybe the answer is simply that we need to reduce the complexity of these particle systems so less mips need to be streamed in at once?
thanks for reaching out. Do these hitches only occur the first time the particle effect spawns or does it happen every time? In the former case, the hitching might be alleviated by preloading the effect and keeping the highest mips resident in GPU memory so they are ready to render immediately when needed. You can experiment with increasing the LOD bias in the particle’s texture settings from 0 to 1 (this will drop the highest detail mips and reduce the texture size upload by 75%) or setting the “Never Stream” option to always keep the texture and all its mips in memory if there is room for that. Another option is to lower the spawn rate to spread the particle spawning over several frames to prevent large bursts.
Hello, hope it’s OK to re-opening this; I wanted to provide some additional information and check on how things are going.
First of all, in Fortnite we only use r.Streaming.MaxNumTexturesToStreamPerFrame and r.Streaming.AmortizeCPUToGPUCopy for lower end platforms like mobile as shown in the related post marked above and for PCs and Macs with low memory we use this:
+CVars_Smallest=r.Streaming.AmortizeCPUToGPUCopy=1 +CVars_Smallest=r.Streaming.MaxNumTexturesToStreamPerFrame=1We have various Niagara particle effect textures assigned to TEXTUREGROUP_Effects and TEXTUREGROUP_EffectsNotFiltered, but don’t have any special streaming options set like “NumStreamedMips”. If you are seeing these streaming issues on lower-end hardware then experimenting with those options makes sense, but on anything else, will likely cause quality issues. Regarding the complexity of particle systems, Fortnite has a lot of stylized effects, so it’s possible yours are relying on more textures and higher resolution.
Please let us know how profiling is going and feel free to open a private post if you’d like to share Insight traces or any particular particle FX setups for us to look at.
Hi Alex. Thanks for the reply! We are seeing this issue on all platforms, even modern high end PCs.
We don’t have any more info beyond what is in the initial ticket on the actual issue, but we can make a confidential ticket with the relevant files tomorrow.
We’re also currently working to verify if this still happens on 5.5.
Thanks for the response! As far as I can tell the hitches have a chance to happen whenever the particle effects spawn but the required mips are not resident, this could be the first time or could happen again if it has been a while since the effect was seen.
We have a lot of different potential particle effects that can happen depending on the situation, so having these never stream is going to require a lot of GPU memory that we don’t really have the budget for. On lower texture quality settings we do have texture LOD groups with a LOD bias set for these textures, and that does help with the hitching (though still doesn’t completely fix it with a bias of 1) but on higher quality settings we would ideally like to use the full resolution mips.
Do you have any advice on either the CVars I mentioned, or on any other configuration that might help us avoid the allocation bottlenecks / prevent this process from blocking the frame? The answer may be that we end up having to be more aggressive with quality settings or have to change up the effects as you suggest but I’d like to explore any other options too!
I looked into this a bit more and there may be some additional options if keeping particle textures resident is not possible:
Using r.Streaming.AmortizeCPUToGPUCopy and r.Streaming.MaxNumTexturesToStreamPerFrame should indeed be advantageous (there is a bit more info from Epic on which settings to use for different texture qualities at [this [Content removed]
Another option might be to enable r.Streaming.AllowParallelStreamingRenderAssets when multiple cores are available
below is a potential configuration for CVars related to streaming which is optimised to mitigate stutters. You may have to experiment with the MaxNumTexturesToStreamPerFrame and NumStaticComponentsProcessedPerFrame settings to see what works best for your project. This page may be helpful to explain some of the CVars (you may have found it already)
r.Streaming.Boost=1 ; Extra optimizations for streaming r.Streaming.MinMipForSplitRequest=0 ; Additional streaming tweaks for stutter fixes r.Streaming.HiddenPrimitiveScale=1 ; Additional streaming tweaks for stutter fixes r.Streaming.AmortizeCPUToGPUCopy=1 ; Additional streaming tweaks for stutter fixes r.Streaming.MaxNumTexturesToStreamPerFrame=100 ; Additional streaming tweaks for stutter fixes r.Streaming.NumStaticComponentsProcessedPerFrame=100 ; Additional streaming tweaks for stutter fixes r.Streaming.FramesForFullUpdate=1 ; Additional streaming tweaks for stutter fixes s.AsyncLoadingThreadEnabled=1 ; Additional streaming tweaks for stutter fixes s.AsyncLoadingTimeLimit=2 ; Additional streaming tweaks for stutter fixes s.LevelStreamingActorsUpdateTimeLimit=2 ; Additional streaming tweaks for stutter fixes s.UnregisterComponentsTimeLimit=2 ; Additional streaming tweaks for stutter fixes s.AsyncLoadingUseFullTimeLimit=0 ; Additional streaming tweaks for stutter fixes s.IoDispatcherCacheSizeMB=256 ; Additional streaming tweaks for stutter fixes s.LevelStreamingComponentsRegistrationGranularity=1 ; Additional streaming tweaks for stutter fixes s.LevelStreamingComponentsUnregistrationGranularity=1 ; Additional streaming tweaks for stutter fixes s.MaxIncomingRequestsToStall=1 ; Additional streaming tweaks for stutter fixes s.MaxReadyRequestsToStallMB=0 ; Additional streaming tweaks for stutter fixes s.MinBulkDataSizeForAsyncLoading=0 ; Additional streaming tweaks for stutter fixes s.PriorityAsyncLoadingExtraTime=1 ; Additional streaming tweaks for stutter fixes s.PriorityLevelStreamingActorsUpdateExtraTime=0 ; Additional streaming tweaks for stutter fixes
Hopefully this helps. Please let me know if it works.