{"record":{"id":"7e94601bc1102a1d","repo":"stride3d/stride","slug":"can-t-opload-with-cbuffer","errorCode":null,"errorMessage":"Can't OpLoad with cbuffer","messagePattern":"Can't OpLoad with cbuffer","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.CBuffers.cs","lineNumber":323,"sourceCode":"                        if (i.Op == Op.OpAccessChain && (OpAccessChain)i is { } accessChain)\n                        {\n                            if (variables.TryGetValue(accessChain.BaseId, out var cbuffer) && cbuffer.MemberIndexOffset > 0)\n                            {\n                                // According to spec, this must be a OpConstant (and we only create them with int)\n                                var indexes = accessChain.Indexes.Elements.Span;\n                                var constantId = indexes[0];\n                                var index = cbuffer.MemberIndexOffset + (int)context.GetConstantValue(constantId);\n                                indexes[0] = context.CompileConstant(index).Id;\n\n                                // Regenerate buffer (since we modify accessChain.Indexes, it doesn't get rebuilt automatically)\n                                accessChain.UpdateInstructionMemory();\n                            }\n                        }\n                        // Out of safety, check for any OpLoad/OpStore on the variables (forbidden, only OpAccessChain)\n                        else if (i.Op == Op.OpLoad && (OpLoad)i is { } load)\n                        {\n                            if (variables.TryGetValue(load.Pointer, out var cbuffer))\n                                throw new NotSupportedException(\"Can't OpLoad with cbuffer\");\n                        }\n                        else if (i.Op == Op.OpStore && (OpStore)i is { } store)\n                        {\n                            if (variables.TryGetValue(store.Pointer, out var cbuffer))\n                                throw new NotSupportedException(\"Can't OpLoad with cbuffer\");\n                        }\n                    }\n\n                    // Update first variable to use new type\n                    cbuffersSpan[0].Variable.Data.IdResultType = mergedCbufferPtrStructId;\n                    cbufferMemberMetadata[cbuffersSpan[0].VariableId] = GenerateCBufferLinks(cbuffersSpan[0].VariableId, cbuffersSpan, mergedCbufferStruct);\n\n                    foreach (var i in buffer)\n                    {\n                        if (i.Op == Op.OpName && (OpName)i is { } name)\n                        {\n                            // Ensure cbuffer variable name is correct (it might still have a pending number such as Test.0 if there was multiple buffers with same name)\n                            if (cbuffersSpan[0].VariableId == name.Target)","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.CBuffers.cs#L305-L341","documentation":"While merging cbuffer declarations across mixed SDSL shader classes into a single SPIR-V struct, the mixer scans the SPIR-V for direct OpLoad instructions targeting the cbuffer variable pointer. Only OpAccessChain-based access is supported; a direct OpLoad on the cbuffer variable means the compiler cannot rewrite the access to point at the merged struct, so it throws NotSupportedException to fail loudly rather than corrupt the module.","triggerScenarios":"MergeCBuffers (called from MergeSDSL) encounters an OpLoad whose pointer operand resolves, via the collected `variables` map, to a cbuffer variable — i.e. shader code (or generated SPIR-V from a toolchain) loads the cbuffer's flat pointer instead of accessing it through an access chain.","commonSituations":"HLSL->SPIR-V front-ends (DXC/glslang) emitting a load of the constant buffer itself (e.g. binding a whole cbuffer to a UBO-typed function parameter or copying it); hand-written or tool-generated SPIR-V that copies a cbuffer; edge-case shader patterns that pass entire constant buffers around.","solutions":["Find the shader/access performing the direct load: disassemble the SPIR-V (spirv-dis) and look for OpLoad whose result type is the cbuffer struct pointer.","Rewrite the shader to only access cbuffer members through member access (HLSL field access), never the cbuffer object itself.","If the SPIR-V comes from a front-end (DXC/glslang), check its version and flags — update or adjust options so it doesn't emit OpLoad on the cbuffer.","If it's a legitimate pattern, extend the mixer to handle OpLoad on cbuffers by rewriting the result type, following the OpAccessChain rewrite logic."],"exampleFix":"// before: passing whole cbuffer (front-end may emit OpLoad on it)\nfloat4 DoThing(ConstantBuffer<MyCB> cb) { return cb.color; }\n\n// after: access members only\nfloat4 DoThing() { return MyCBuffer.color; }","handlingStrategy":"try-catch","validationCode":"// After SPIR-V generation, before mixing: reject direct loads on cbuffer variables\nforeach (var inst in module.Instructions)\n    if (inst.Op == Op.OpLoad && cbufferVariableIds.Contains(((OpLoad)inst).Pointer))\n        throw new InvalidOperationException(\"Shader performs OpLoad directly on a cbuffer; only member access (OpAccessChain) is supported.\");","typeGuard":"static bool LoadsCbuffer(Instruction i, HashSet<uint> cbufferVarIds) => i.Op == Op.OpLoad && ((OpLoad)i).Pointer != null && cbufferVarIds.Contains(((OpLoad)i).Pointer);","tryCatchPattern":"try { mixer.MergeSDSL(...); }\ncatch (NotSupportedException ex) { log.Error($\"Cbuffer merge failed: {ex.Message}. Check that the shader only accesses cbuffer members.\"); throw; }","preventionTips":["Never reference the cbuffer object itself in shader code — access members only.","Avoid passing entire constant buffers as function parameters in SDSL/HLSL.","Keep the HLSL->SPIR-V front-end (DXC/glslang) updated; older versions may emit OpLoad on the cbuffer.","Add a spirv-dis check in CI to detect OpLoad/OpStore on cbuffer variables before mixing."],"tags":["spirv","shader-compilation","cbuffer"],"backgroundTag":"unsupported-operation","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"}