{"record":{"id":"1f8f6c76c4f0ad2e","repo":"stride3d/stride","slug":"could-not-find-cbuffer-member-link-info-for-context-names","errorCode":null,"errorMessage":"Could not find cbuffer member link info for {context.Names[cbuffer.VariableId]}; it should have been generated during MergeCBuffers","messagePattern":"Could not find cbuffer member link info for (.+?); it should have been generated during MergeCBuffers","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.CBuffers.cs","lineNumber":446,"sourceCode":"                // Note: MemberIndexOffset is simply a shift in Members index, not something like a byte offset\n                .Select(x => (\n                    Variable: x,\n                    VariableId: x.Data.IdResult!.Value,\n                    StructTypePtrId: x.Data.IdResultType!.Value,\n                    StructType: context.ReverseTypes[x.Data.IdResultType.Value] is PointerType p && p.StorageClass == Specification.StorageClass.Uniform && p.BaseType is StructuredType s ? s : null,\n                    MemberIndexOffset: 0))\n                .Where(x => x.StructType != null)\n                .ToList();\n\n            foreach (var cbuffer in cbuffers)\n            {\n                int constantBufferOffset = 0;\n                var cb = cbuffer.StructType!;\n                var structTypeId = context.Types[cb];\n\n                var memberInfos = new EffectValueDescription[cb.Members.Count];\n                if (!cbufferMemberMetadata.TryGetValue(cbuffer.VariableId, out var cbufferMetadata))\n                    throw new InvalidOperationException($\"Could not find cbuffer member link info for {context.Names[cbuffer.VariableId]}; it should have been generated during {nameof(MergeCBuffers)}\");\n\n                for (var index = 0; index < cb.Members.Count; index++)\n                {\n                    // Properly compute size and offset according to DirectX rules\n                    var member = cb.Members[index];\n                    var memberSize = SpirvBuilder.ComputeBufferOffset(member.Type, member.TypeModifier, ref constantBufferOffset, SpirvBuilder.AlignmentRules.CBuffer).Size;\n\n                    DecorateMember(context, structTypeId, index, constantBufferOffset, memberSize, member.Type, member.TypeModifier);\n\n                    var metadata = cbufferMetadata[index];\n\n                    memberInfos[index] = new EffectValueDescription\n                    {\n                        Type = ConvertType(context, member.Type, member.TypeModifier, SpirvBuilder.AlignmentRules.CBuffer),\n                        RawName = member.Name,\n                        KeyInfo = new EffectParameterKeyInfo { KeyName = metadata.Link },\n                        Offset = constantBufferOffset,\n                        Size = memberSize,","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.CBuffers.cs#L428-L464","documentation":"During SPIR-V cbuffer reflection, ShaderMixer.ComputeCBufferReflection requires per-member layout metadata that MergeCBuffers was supposed to record for each composition cbuffer variable. If the dictionary lookup fails, the mixer's internal invariant ('metadata generated during MergeCBuffers') has been violated, so it throws InvalidOperationException naming the cbuffer.","triggerScenarios":"Calling ShaderMixer.MergeSDSL on a shader tree where a composition variable's cbuffer member-link metadata was never produced by MergeCBuffers — e.g. a cbuffer variable that survived mixing without passing through the merge step, or a mixer code path/pipeline stage run out of order (reflection executed before the CBuffers merge phase).","commonSituations":"Custom or forked shader compiler pipelines that invoke ComputeCBufferReflection/ComputeCBufferReflectionData directly instead of going through the full MergeSDSL flow; older or mismatched Stride.Shaders assemblies where MergeCBuffers was skipped due to an earlier exception being swallowed; hand-constructed MixinNode trees.","solutions":["Run the standard ShaderMixer.MergeSDSL pipeline end-to-end so MergeCBuffers executes before ComputeCBufferReflection","Check for earlier exceptions or result errors from MergeCBuffers that were ignored, leaving the metadata dictionary incomplete","If using a custom pipeline, replicate the MergeCBuffers step before invoking cbuffer reflection","Update Stride packages to a consistent version so mixer phases match"],"exampleFix":"// before: calling reflection without merging\nvar reflection = mixer.ComputeCBufferReflection(context, cbuffers, cbufferMemberMetadata);\n// after: ensure merge phase ran first\nvar mergeResult = mixer.MergeSDSL(mixinTree, ...); // runs MergeCBuffers internally\nif (mergeResult.HasErrors) throw mergeResult.Exceptions.First();","handlingStrategy":"validation","validationCode":"if (!mergeResult.HasErrors && cbufferVariableIds.All(id => cbufferMemberMetadata.ContainsKey(id)))\n    reflection = mixer.ComputeCBufferReflection(context, cbuffers, cbufferMemberMetadata);","typeGuard":null,"tryCatchPattern":"try { reflection = mixer.ComputeCBufferReflection(...); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"cbuffer member link info\")) { logMergePipelineBug(ex); throw; }","preventionTips":["Always run the full MergeSDSL pipeline; never call reflection phases standalone","Log and inspect errors from MergeCBuffers before proceeding","Keep Stride.Shaders.* package versions in sync"],"tags":["shaders","sdsl","spirv","compiler-pipeline"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}