stride3d/stride · error · InvalidOperationException

cbuffer have a mix of stage and non-stage members

Error message

cbuffer {Name} have a mix of stage and non-stage members

What it means

CBuffer.ProcessSymbol walks the cbuffer's members and records whether the first member 'IsStaged'; every subsequent member must match. If some members are stage-bound (declared inside a stage block) and others are not, the cbuffer layout is ambiguous for SPIR-V emission, so an InvalidOperationException is thrown naming the cbuffer.

Solutions

  1. Split the cbuffer into two cbuffers: one with only staged members and one with only non-staged members.
  2. Ensure members added via composition/inheritance go into a differently named cbuffer than the base's stage-bound cbuffer.
  3. Make all members of the offending cbuffer consistently staged (or consistently non-staged) in the shader source.

Example fix

// before
cbuffer Mixed { float a; stage float b; };
// after
cbuffer NonStaged { float a; };
stage cbuffer Staged { float b; };
Defensive patterns

Strategy: validation

Validate before calling

var staged = members.Select(m => m.IsStaged).Distinct().ToList();
if (staged.Count > 1) throw new InvalidOperationException($"cbuffer {name} mixes staged and non-staged members");

Try / catch

try { cbuffer.ProcessSymbol(table, context); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("cbuffer")) { log(ex.Message); splitCBuffer(); }

Prevention

When it happens

Trigger: Declaring a cbuffer where some members come from stage-specific declarations and others from regular declarations, e.g. mixing members declared under different stages or merging cbuffers via shader composition/inheritance so staged and non-staged members land in the same cbuffer.

Common situations: Shader composition where a base class declares a cbuffer and a mixin/stage adds members to it; large codebases where a cbuffer name is reused across stage-scoped and global scopes.

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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/ShaderElements.cs:344

        foreach (var cbMember in Members)
        {
            cbMember.TypeName.ProcessSymbol(table);
            cbMember.Type = cbMember.TypeName.Type;
        }

        var pointerType = new PointerType(Type!, Specification.StorageClass.Uniform);

        isStaged = null;
        for (var index = 0; index < Members.Count; index++)
        {
            var member = Members[index];

            // Use first member as reference
            if (isStaged == null)
                isStaged = member.IsStaged;
            // Make sure IsStaged for all members match the first member (they're all the same)
            if (isStaged != member.IsStaged)
                throw new InvalidOperationException($"cbuffer {Name} have a mix of stage and non-stage members");
        }

        var constantBufferType = (ConstantBufferSymbol)Type!;

        // We try to avoid clash in case multiple cbuffer TYPE with same name
        // The variable itself is handled by adding a .0 .1 etc. in Shader.RenameCBufferVariables()
        int tryCount = 0;
        var typeName = constantBufferType.Name;
        while (!table.DeclaredTypes.TryAdd(constantBufferType.ToId(), Type!))
        {
            typeName = $"{typeName}_{++tryCount}";
            Type = constantBufferType = constantBufferType with { Name = typeName };
        }

        context.DeclareCBuffer(constantBufferType, context.Bound++);

        var sid = new SymbolID(Name, SymbolKind.CBuffer, Storage.Uniform);
        Symbol = new Symbol(sid, pointerType, context.Bound++, OwnerType: table.CurrentShader);

View on GitHub (pinned to 96fad776d2)