stride3d/stride · error · InvalidOperationException

Operator ' ' is not supported on matrix types

Error message

Operator '{op}' is not supported on matrix types

What it means

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.

Solutions

  1. Replace the unsupported operator with one of +, -, *, or / on the matrices
  2. 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)
  3. Use a library helper or explicit loop over matrix elements instead of a direct matrix operator
  4. Check the operator parsing in the shader source; a typo may be producing an unexpected Operator enum value

Example fix

// before
float4x4 r = a % b; // Operator 'Modulus' is not supported on matrix types
// after
float4x4 r = a / b; // or implement per-component:
// for (int i = 0; i < 4; i++) r[i] = a[i] % b[i];
Defensive patterns

Strategy: validation

Validate before calling

if (left is MatrixType && !new[]{Operator.Plus,Operator.Minus,Operator.Mul,Operator.Div}.Contains(op))
    throw new ArgumentException($"{op} is not supported on matrices; use +,-,*,/");

Type guard

bool IsMatrixSupportedOp(SymbolType t, Operator op) => t is not MatrixType || op is Operator.Plus or Operator.Minus or Operator.Mul or Operator.Div;

Try / catch

try { result = Builder.BinaryOperation(op, left, right); }
catch (InvalidOperationException ex) when (ex.Message.Contains("not supported on matrix types"))
{ /* decompose into columns or rewrite the expression */ }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/3e69d7e7da46ca07. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Expressions.cs:300

        (var operandType, var resultType) = AnalyzeBinaryOperation(table, leftType, op, rightType, info) ?? throw new InvalidOperationException("Type of binary operation could not be determined");

        // 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)
        //       We'll need to review those cases
        left = Convert(context, left, operandType);
        right = Convert(context, right, operandType);

        var resultTypeId = context.GetOrRegister(resultType);

        // Refresh types (after convert)
        leftType = context.ReverseTypes[left.TypeId];
        rightType = context.ReverseTypes[right.TypeId];

        // SPIR-V arithmetic ops (OpFAdd, OpFSub, OpFMul, etc.) only accept scalar/vector types.
        // For matrices, decompose into column vectors, apply the op per-column, then reconstruct.
        if (leftType is MatrixType matType)
        {
            if (op is not (Operator.Plus or Operator.Minus or Operator.Mul or Operator.Div))
                throw new InvalidOperationException($"Operator '{op}' is not supported on matrix types");

            var columnTypeId = context.GetOrRegister(new VectorType(matType.BaseType, matType.Rows));
            Span<int> columnResults = stackalloc int[matType.Columns];
            for (int i = 0; i < matType.Columns; i++)
            {
                var colLeft = Buffer.Insert(Position++, new OpCompositeExtract(columnTypeId, context.Bound++, left.Id, [i])).ToValue();
                var colRight = Buffer.Insert(Position++, new OpCompositeExtract(columnTypeId, context.Bound++, right.Id, [i])).ToValue();
                columnResults[i] = BinaryOperation(table, context, colLeft, op, colRight, info).Id;
            }
            return Buffer.Insert(Position++, new OpCompositeConstruct(resultTypeId, resultId, [.. columnResults])).ToValue();
        }

        var leftElementType = leftType.GetElementType();
        var rightElementType = rightType.GetElementType();

        var spirvOp = SelectBinaryOp(op, leftElementType);
        // All SPIR-V binary ops share the same 5-word layout (wordcount|opcode, resultType, resultId, operand1, operand2),
        // so we create one and patch the opcode.

View on GitHub (pinned to 96fad776d2)