Niagara Systems stuck in PendingSpawnPaused state with Scalability Cull Reaction set to Pause

Hello Friends :blush:

Some Niagara Systems are getting stuck in the PendingSpawnPaused state with Scalability settings of Max Time Without Render and Cull Reaction set to Pause at the start of the level’s gameplay.

Even when the systems’ bounds become visible again and the system should be unpaused, there is no way to get them out of this state, and the system never resumes. Only by toggling the Effects Quality to reset the scalability manager will the systems become unpaused.

In our case the Scalability Manager is culling the systems at the beginning of gameplay of this particular level, before the first tick and before any particles are spawned. The state of the systems is PendingSpawnPaused. The fact that they are being culled at the beginning isn’t a problem; the effect actually isn’t visible at the start of the level. The problem is that they stay culled in this state even when they shouldn’t.

When the player moves to a location where there is direct line of sight to the Niagara System, they do not transition to a visible state. Also, even though there are fixed bounds set, no bounds are drawn in the debug views.

Turning off Max Time Without Render fixes the problem. The systems are still culled as expected due to frustum visibility, but they resume once they become visible. Their debug bounds are also drawn correctly.

For this deadlock to happen, perhaps PendingSpawnPaused instances never tick, so they can never render, therefore GetLastRenderTime() can never update and bCulled never clears. There doesn’t seem to be a path to break out of the PendingSpawnPaused state in these conditions.

[Attachment Removed]

Steps to Reproduce

  1. Create a Niagara Effect Type with CullReaction = PauseResume and VisibilityCulling.bCullWhenNotRendered = true, with a low MaxTimeWithoutRender.
  2. Assign it to a System with fixed bounds and activate an instance such that the scalability manager evaluates it before its first tick completes (e.g., many instances spawning simultaneously, or a low scalability update frequency relative to tick scheduling).
  3. Observe with the Niagara Debug HUD that the instance’s System InstanceState shows PendingSpawnPaused indefinitely. It never transitions to Spawning/Running, and the debugger’s bounds are absent/not drawn.
  4. Confirm it does not self-recover regardless of camera distance or view frustum. Setting Fixed Bounds (including bounds large enough to cover the entire level) does not resolve it. Disabling bCullWhenNotRendered on the Effect Type does resolve it. Forcing the scalability manager to reset will also wake the system out of the culled state.

[Attachment Removed]

Hello!

You likely need to integrate the following fixes:

CL#54500464 (87020c) - Initialize Niagara scalability state with current world time to avoid treating newly registered components as long-invisible

- Guard render-time culling against uninitialized last render times so freshly spawned components are not prematurely culled

Test the above fix first and then, if it addresses the problem, you likely also need this for shipping builds:

CL#54767790 (9df7f8) - Remove DEBUG_SCALABILITY_STATE

- This fixes an issue where bCulledByVisibility is used but culled in SHIPPING builds

Please let me know if that doesn’t fix the problem!

[Attachment Removed]

Thank you, Alex!

We integrated both CLs but we are still seeing systems stuck in the culled state.

System InstanceState(PendingSpawnPaused) VMActualState(Active) VMRequestedState(Active)
Scalability - NET_Fire
TickGroup - TG_EndPhysics
GpuTickStage - PreInitViews
LastRenderTime - 231.36
Memory - 0.00MB
 
Scalability Dump:
| Culled:-Visibility- | Sig: 0.0000

This happens whether we are using Paused or Asleep cull reaction, but the Paused is easier to diagnose because it stays verbose in the Niagara Debug HUD.

Note that the Actual and Requested states are both Active. Memory is 0.00MB (no particles). The debug bounding box is not drawn.

There still appears to be at least one path where the culled system can get stuck before it spawns any particles.

Our workaround:

Setting Fixed Bounds on the System level fixes the problem. These systems are no longer stuck, and update as normal.

Using only Fixed/Dynamic Bounds on the Emitters did not fix the problem; only System Fixed Bounds worked.

We think this is because without System Fixed Bounds, CurrLocalBounds in NiagaraComponent.cpp only becomes valid after the system has actually ticked once.

A system stuck in PendingSpawn/PendingSpawnPaused has never ticked, so CurrLocalBounds stays invalid forever.

Without valid bounds, the system may be evaluated as visibility-culled before it ever spawns, and being culled prevents it from spawning, so it never produces valid bounds, ad infinitum.

[Attachment Removed]

Thanks for trying those fixes, this does sound like a new issue. I’m passing this to my colleague who is more familiar with this system.

[Attachment Removed]

Hey Matt,

Apologies for the delay here.

Yes I think you’re correct on the bounds issue here.

Since the system never ticks, it’s bounds are always invalid and so never renders and culling based on the rendering path always fails.

It’s possible a fix here could be initializing Niagara component bounds to something small always so direct LOS to the system will always work.

Though for now I think your workaround of fixed bounds is solid and what we would do internally.

Thanks,

Simon

[Attachment Removed]