stride3d/stride · error · InvalidOperationException

Resource ' ' has slot in but slot was expected (from SDSL…

Error message

Resource '{resourceName}' has slot {slotStart} in {stage} but slot {entry.SlotStart} was expected (from SDSL compiler). Cross-stage slot mismatch is not supported.

What it means

When merging resource binding slots across shader stages into a combined binding table, ShaderCompiler.Compile checks that each resource got the same slot the SDSL compiler assigned. If a resource occupies a different slot in one stage than the recorded SlotStart, the merge is aborted with InvalidOperationException because D3D12 requires consistent register slots for a resource across all stages that use it.

Solutions

  1. Make the resource's register/slot identical in every shader stage that declares it (use explicit register(bN/tN/uN/sN) consistently).
  2. Recompile all shader stages from the same SDSL source with the same Stride compiler version so slot assignment is uniform.
  3. Remove hand-written register() or layout qualifiers that override the SDSL compiler's slot assignment.
  4. If reflection slots come from an external toolchain, re-run it with options that preserve the declared register order (e.g. no auto-binding renumbering).
  5. Clear the shader/effect cache and rebuild so stale per-stage bindings are not mixed with fresh ones.

Example fix

// before: mismatch across stages
cbuffer PerFrame : register(b0) { float4x4 View; };   // vs
cbuffer PerFrame : register(b2) { float4x4 View; };   // ps -> throws

// after: same slot in all stages
cbuffer PerFrame : register(b0) { float4x4 View; };   // vs and ps
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate binding consistency across stages before compiling the effect
var slots = new Dictionary<string, int>();
foreach (var stage in stageBindings)
    foreach (var binding in stage.InputBindings)
    {
        if (slots.TryGetValue(binding.Name, out var existing) && existing != binding.SlotStart)
            throw new InvalidOperationException($"Resource '{binding.Name}' bound to slot {binding.SlotStart} in {stage.Stage} but slot {existing} elsewhere. Align register assignments.");
        slots[binding.Name] = binding.SlotStart;
    }

Try / catch

try
{
    var result = shaderCompiler.Compile(source, parameters);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Cross-stage slot mismatch"))
{
    log.Error($"Binding slot conflict: {ex.Message}. Make the resource's register identical in every stage.");
    throw;
}

Prevention

When it happens

Trigger: ShaderCompiler.Compile processes a stage's input binding whose resource name maps to an entry with a different SlotStart than the slot reported by that stage's reflection — e.g. the same named uniform bound to slot 2 in the pixel shader but slot 3 in the vertex shader, or reflection produced by a different toolchain than the SDSL compiler reorders resource registers.

Common situations: Hand-written HLSL/SPIRV mixed with SDSL shaders where explicit register() assignments disagree; shader stages generated or precompiled by different compiler versions; renaming or reordering resources so SDSL-assigned slots no longer match reflected slots; using precompiled shaders built with an older Stride compiler.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Compilers/Direct3D/ShaderCompiler.cs:794

            // in the process.
            // This is a convenience method to avoid unnecessary reference counting, as implicit conversions
            // in the ComPtr<T> class will call AddRef() automatically.
            //
            // NOTE: This is a mirror from the ComPtrHelpers type.
            //
            static ComPtr<T> ToComPtr<T>(T* comPtr) where T : unmanaged, IComVtbl<T>
            {
                return new ComPtr<T> { Handle = comPtr };
            }

            static void UpdateResourceGroupStageFlags(Dictionary<string, (EffectResourceGroupDescription Group, int Index)> index, string resourceName, ShaderStage stage, int slotStart)
            {
                if (!index.TryGetValue(resourceName, out var loc))
                    return;

                var entry = loc.Group.Entries[loc.Index];
                if (entry.SlotStart != slotStart)
                    throw new InvalidOperationException(
                        $"Resource '{resourceName}' has slot {slotStart} in {stage} " +
                        $"but slot {entry.SlotStart} was expected (from SDSL compiler). " +
                        $"Cross-stage slot mismatch is not supported.");
                entry.Stages |= stage.ToFlag();
                loc.Group.Entries[loc.Index] = entry;
            }
        }
    }
}

#endif

View on GitHub (pinned to 96fad776d2)