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
- Split the cbuffer into two cbuffers: one with only staged members and one with only non-staged members.
- Ensure members added via composition/inheritance go into a differently named cbuffer than the base's stage-bound cbuffer.
- 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
- Keep each cbuffer uniformly staged or non-staged.
- Use distinct cbuffer names per stage scope and per mixin.
- After composition/inheritance, audit merged cbuffers for member provenance.
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
- Attribute is not supported
- foreach: Array type is expected
- foreach: collection and variable type not matching
- Unsupported parameter modifier
- Assign back to matrix swizzle is not implemented yet
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)