stride3d/stride · error · InvalidOperationException

Bound render targets

Error message

Bound render targets ({RenderTargetCount} color, depth: {depthStencilBuffer != null}) do not match the active pipeline's output ({output.RenderTargetCount} color, depth: {output.DepthStencilFormat != PixelFormat.None}). The render targets bound on the command list must match the pipeline's Output description.

What it means

In debug mode, Stride validates that the render targets bound on the command list match the active pipeline's Output description (number of color targets and presence of a depth-stencil). Mismatches are undefined behavior on strict Vulkan drivers, so Stride fails loudly with a detailed message.

Solutions

  1. Recreate the pipeline state with an Output description matching the currently bound render targets
  2. Adjust SetRenderTargets to bind exactly the number of color targets and depth buffer the pipeline's Output declares
  3. Verify EffectPass/PipelineState selection matches the current framebuffer layout

Example fix

// before
var pipeline = PipelineState.New(..., new RenderOutputDescription(2, PixelFormat.R8G8B8A8_UNorm));
SetRenderTargetCount(1) // mismatch
// after
var pipeline = PipelineState.New(..., new RenderOutputDescription(1, PixelFormat.R8G8B8A8_UNorm, PixelFormat.D32_Float));
Defensive patterns

Strategy: validation

Validate before calling

var out0 = pipelineState.Description.Output;
if (cmdList.RenderTargetCount != out0.RenderTargetCount) throw new InvalidOperationException("Bind render targets matching pipeline Output");

Try / catch

try { cmdList.Draw(...); } catch (InvalidOperationException ex) when (ex.Message.Contains("Bound render targets")) { /* rebind correct targets or select matching pipeline */ }

Prevention

When it happens

Trigger: Setting a pipeline compiled for N color targets (with/without depth) while SetRenderTargets bound a different count or depth buffer, then executing a draw inside command list execution on Vulkan.

Common situations: Reusing a pipeline (effect/pass) with a framebuffer configured differently; binding depth buffer when the pipeline output has DepthStencilFormat None or vice versa; MRT pipelines applied to single-target framebuffers.

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/5b0c179dad541479. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs:1682

            // attachments and the depth layout stay the same
            if (activeRendering && !renderingDirty && activeDepthReadOnly == depthReadOnly)
                return;

            // End old render pass instance
            CleanupRenderPass();

            var renderTarget = RenderTargetCount > 0 ? renderTargets[0] : depthStencilBuffer;
            if (renderTarget == null)
                return;

            // An attachment mismatch with the pipeline's declared formats is undefined behavior that
            // loses the device on strict drivers; fail loud in debug (only, to not break drivers that
            // tolerate it) instead.
            if (GraphicsDevice.IsDebugMode)
            {
                var output = activePipeline.Description.Output;
                if (RenderTargetCount != output.RenderTargetCount || (depthStencilBuffer != null) != (output.DepthStencilFormat != PixelFormat.None))
                    throw new InvalidOperationException(
                        $"Bound render targets ({RenderTargetCount} color, depth: {depthStencilBuffer != null}) do not match the active pipeline's output " +
                        $"({output.RenderTargetCount} color, depth: {output.DepthStencilFormat != PixelFormat.None}). The render targets bound on the command list must match the pipeline's Output description.");
            }

            // Clear attachments if needed
            for (int index = 0; index < RenderTargetCount; index++)
            {
                if (!renderTarget.IsInitialized)
                {
                    Clear(renderTargets[index], Color.Transparent);
                }
            }

            if (depthStencilBuffer != null && !depthStencilBuffer.IsInitialized)
            {
                Clear(depthStencilBuffer, DepthStencilClearOptions.DepthBuffer | DepthStencilClearOptions.Stencil);
            }

View on GitHub (pinned to 96fad776d2)