stride3d/stride · error · InvalidOperationException
Cannot pop more than push
Error message
Cannot pop more than push
What it means
RenderDrawContext keeps a stack of render-target states: PushRenderTargets saves a state and PopRenderTargets restores one. Popping without a matching push (currentStateIndex < 0) leaves nothing to restore, so the engine throws InvalidOperationException to keep the push/pop pairs balanced.
Solutions
- Ensure every PopRenderTargets call is paired with exactly one prior PushRenderTargets call in the same scope
- Move push/pop into try/finally only around code that cannot throw before the push
- Audit custom render features/pass ordering so a feature never pops a push owned by another feature
Example fix
// before commandList.SetRenderTarget(...); context.PopRenderTargets(); // never pushed // after context.PushRenderTargets(); commandList.SetRenderTarget(...); context.PopRenderTargets(); // balanced pair
Defensive patterns
Strategy: validation
Validate before calling
if (context.currentStateIndex < 0)
throw new InvalidOperationException("PopRenderTargets called without matching PushRenderTargets"); Try / catch
try { context.PopRenderTargets(); }
catch (InvalidOperationException ex) when (ex.Message == "Cannot pop more than push") {
logger.Error("Unbalanced Push/PopRenderTargets in render feature");
} Prevention
- Always pair Push/Pop in the same method scope
- Use try/finally only around code after a successful Push
- Ensure each RenderFeature owns its own push/pop and doesn't pop another's state
When it happens
Trigger: Calling PopRenderTargets more times than PushRenderTargets was called during a render frame, or calling Pop before any Push in the current frame context.
Common situations: Unbalanced error/early-exit paths in custom render features or renderers that push but skip the pop (or pop in a finally when push failed); calling pop from outside a RenderFeature's Execute scope.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A texture with size [ ] already exist with the same output…
- Asked for " + i + " but no more than 10 default textures…
- Begin must be called before functionName
- BoundingVolumeBufferDownsampleLevel
- Can not use more than
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/1f62fef98170f01c.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Rendering/Rendering/RenderDrawContext.cs:98
allocatedStates.Add(newState);
}
else
{
newState = allocatedStates[currentStateIndex];
}
newState.Capture(CommandList);
return new RenderTargetRestore(this);
}
/// <summary>
/// Restores render targets and viewport state.
/// </summary>
public void PopRenderTargets()
{
if (currentStateIndex < 0)
{
throw new InvalidOperationException("Cannot pop more than push");
}
var oldState = allocatedStates[currentStateIndex--];
oldState.Restore(CommandList);
}
/// <summary>
/// Gets or creates a shared effect.
/// </summary>
/// <typeparam name="T">Type of the shared effect (mush have a constructor taking a <see cref="Rendering.RenderContext"/></typeparam>
/// <returns>A singleton instance of <typeparamref name="T"/></returns>
public T GetSharedEffect<T>() where T : DrawEffect, new()
{
// TODO: Add a way to support custom constructor
lock (sharedEffects)
{
DrawEffect effect;
if (!sharedEffects.TryGetValue(typeof(T), out effect))View on GitHub (pinned to 96fad776d2)