{"record":{"id":"3e69d7e7da46ca07","repo":"stride3d/stride","slug":"operator-op-is-not-supported-on-matrix-types","errorCode":null,"errorMessage":"Operator '{op}' is not supported on matrix types","messagePattern":"Operator '(.+?)' is not supported on matrix types","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Expressions.cs","lineNumber":300,"sourceCode":"        (var operandType, var resultType) = AnalyzeBinaryOperation(table, leftType, op, rightType, info) ?? throw new InvalidOperationException(\"Type of binary operation could not be determined\");\n\n        // TODO: Some specific cases where one of the operands doesn't need to have exact same type as resultType (such as shift in OpShiftRightLogical, or signedness for some other operations)\n        //       We'll need to review those cases\n        left = Convert(context, left, operandType);\n        right = Convert(context, right, operandType);\n\n        var resultTypeId = context.GetOrRegister(resultType);\n\n        // Refresh types (after convert)\n        leftType = context.ReverseTypes[left.TypeId];\n        rightType = context.ReverseTypes[right.TypeId];\n\n        // SPIR-V arithmetic ops (OpFAdd, OpFSub, OpFMul, etc.) only accept scalar/vector types.\n        // For matrices, decompose into column vectors, apply the op per-column, then reconstruct.\n        if (leftType is MatrixType matType)\n        {\n            if (op is not (Operator.Plus or Operator.Minus or Operator.Mul or Operator.Div))\n                throw new InvalidOperationException($\"Operator '{op}' is not supported on matrix types\");\n\n            var columnTypeId = context.GetOrRegister(new VectorType(matType.BaseType, matType.Rows));\n            Span<int> columnResults = stackalloc int[matType.Columns];\n            for (int i = 0; i < matType.Columns; i++)\n            {\n                var colLeft = Buffer.Insert(Position++, new OpCompositeExtract(columnTypeId, context.Bound++, left.Id, [i])).ToValue();\n                var colRight = Buffer.Insert(Position++, new OpCompositeExtract(columnTypeId, context.Bound++, right.Id, [i])).ToValue();\n                columnResults[i] = BinaryOperation(table, context, colLeft, op, colRight, info).Id;\n            }\n            return Buffer.Insert(Position++, new OpCompositeConstruct(resultTypeId, resultId, [.. columnResults])).ToValue();\n        }\n\n        var leftElementType = leftType.GetElementType();\n        var rightElementType = rightType.GetElementType();\n\n        var spirvOp = SelectBinaryOp(op, leftElementType);\n        // All SPIR-V binary ops share the same 5-word layout (wordcount|opcode, resultType, resultId, operand1, operand2),\n        // so we create one and patch the opcode.","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Expressions.cs#L282-L318","documentation":"When a binary operator is applied to matrix operands in the SPIR-V builder, only +, -, *, and / are supported; the matrix is decomposed into column vectors and the op applied per column. Any other operator (e.g. %, bitwise, shift) on a MatrixType is rejected because SPIR-V arithmetic instructions only accept scalar/vector operands and there is no per-column lowering defined for that operator.","triggerScenarios":"Using an operator other than Plus/Minus/Mul/Div between two matrix-typed values in shader code compiled through BinaryOperation (e.g. matrices % matrices, matrix << matrix, or matrix & matrix).","commonSituations":"Porting HLSL shader code that uses non-arithmetic matrix operators; writing custom stream/patch logic via PatchStreamsAccesses that combines matrices with unsupported operators; typos where a scalar modulus was intended but both operands were matrices.","solutions":["Replace the unsupported operator with one of +, -, *, or / on the matrices","If you need a component-wise non-arithmetic op, decompose the matrix into column vectors yourself (OpCompositeExtract), apply the op per column, and recompose (OpCompositeConstruct)","Use a library helper or explicit loop over matrix elements instead of a direct matrix operator","Check the operator parsing in the shader source; a typo may be producing an unexpected Operator enum value"],"exampleFix":"// before\nfloat4x4 r = a % b; // Operator 'Modulus' is not supported on matrix types\n// after\nfloat4x4 r = a / b; // or implement per-component:\n// for (int i = 0; i < 4; i++) r[i] = a[i] % b[i];","handlingStrategy":"validation","validationCode":"if (left is MatrixType && !new[]{Operator.Plus,Operator.Minus,Operator.Mul,Operator.Div}.Contains(op))\n    throw new ArgumentException($\"{op} is not supported on matrices; use +,-,*,/\");","typeGuard":"bool IsMatrixSupportedOp(SymbolType t, Operator op) => t is not MatrixType || op is Operator.Plus or Operator.Minus or Operator.Mul or Operator.Div;","tryCatchPattern":"try { result = Builder.BinaryOperation(op, left, right); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"not supported on matrix types\"))\n{ /* decompose into columns or rewrite the expression */ }","preventionTips":["Only apply +,-,*,/ directly to matrices in shader code","Rewrite modulus/bitwise/shift matrix math as per-component loops","Add unit tests covering each operator on matrix operands"],"tags":["spirv","shader-compiler","matrix","operator"],"backgroundTag":"unsupported-operation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}