stride3d/stride · error · InvalidOperationException

End must be called before functionName

Error message

End must be called before functionName

What it means

CheckEndHasBeenCalled throws when Begin() is invoked while isBeginCalled is still true, i.e. the previous Begin/End cycle was never completed with End(). BatchBase enforces one active Begin session per batch because Begin captures device state that End must restore.

Solutions

  1. Pair every Begin with exactly one End before calling Begin again.
  2. Audit render code for nested or duplicated Begin calls on the same batch instance.
  3. Use try/finally so End always runs after a successful Begin.
  4. Give each subsystem its own batch instance instead of sharing one.

Example fix

// before
spriteBatch.Begin(mode, blend);
spriteBatch.Begin(mode, blend); // throws
// after
spriteBatch.Begin(mode, blend);
spriteBatch.End();
spriteBatch.Begin(mode, blend);
Defensive patterns

Strategy: try-catch

Validate before calling

if (isBatchActive) spriteBatch.End(); // close previous session first

Type guard

bool CanBegin(BatchBase b) => b != null && !isBatchActive;

Try / catch

try { spriteBatch.Begin(mode, blend); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("End must be called"))
{
    spriteBatch.End();
    spriteBatch.Begin(mode, blend);
}

Prevention

When it happens

Trigger: Calling batch.Begin(...) twice in a row without an intervening End(); calling Begin from two systems in the same frame (e.g. UI and sprites sharing one SpriteBatch); a previous End() call throwing or being skipped by an early return.

Common situations: Two game subsystems both calling Begin on a shared SpriteBatch per frame; nested render callbacks that call Begin again inside an active batch; error paths that skip the End call leaving the flag set for the next frame.

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


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/1fff704c7c36394a. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Graphics/BatchBase.cs:279

            if (ResourceContext.VertexBuffer != null)
                GraphicsContext.CommandList.SetVertexBuffer(0, ResourceContext.VertexBuffer, 0, vertexStructSize);
            if (ResourceContext.IndexBuffer != null)
                GraphicsContext.CommandList.SetIndexBuffer(ResourceContext.IndexBuffer, 0, indexStructSize == sizeof(int));
        }

        protected void CheckBeginHasBeenCalled(string functionName)
        {
            if (!isBeginCalled)
            {
                throw new InvalidOperationException("Begin must be called before " + functionName);
            }
        }

        protected void CheckEndHasBeenCalled(string functionName)
        {
            if (isBeginCalled)
            {
                throw new InvalidOperationException("End must be called before " + functionName);
            }
        }

        /// <summary>
        /// Flushes the sprite batch and restores the device state to how it was before Begin was called.
        /// </summary>
        public void End()
        {
            CheckBeginHasBeenCalled("End");

            if (sortMode == SpriteSortMode.Immediate)
            {
                ResourceContext.IsInImmediateMode = false;
            }
            else if (drawsQueueCount > 0)
            {
                // Draw the queued sprites now.
                if (ResourceContext.IsInImmediateMode)

View on GitHub (pinned to 96fad776d2)