stride3d/stride · error · NotImplementedException
Unsupported OpSpecConstantOp inner op
Error message
Unsupported OpSpecConstantOp inner op: {op} What it means
ConstantExpression.ParseInstruction throws this NotImplementedException when an OpSpecConstantOp instruction uses an inner opcode that the expression builder does not implement. Only a fixed set of unary, binary, and OpSelect operations is supported; anything else (e.g. OpUMulExtended, OpSMulExtended, matrix ops) cannot be represented as a ConstantExpression tree.
Solutions
- Identify the inner op from the message and restructure the shader so the spec constant uses only supported arithmetic/bitwise/logical/select ops.
- Pre-fold the expression to a plain OpConstant before handing the buffer to ParseFromBuffer (e.g. constant-fold in the producing tool).
- If the op is legitimately needed, add a case to the switch in ConstantExpression.cs and a matching representation in the ConstantExpression hierarchy.
- Check whether a simple conversion (OpSConvert/OpUConvert) was intended and use one of the supported convert ops instead.
Example fix
// before: spec constant uses an unimplemented inner op (e.g. OpBitFieldUExtract) // after: fold it or use a supported op var supported = ConstantExpression.ParseFromBuffer(idOfIAddSpecConstant, buffer, context); // OpIAdd is in the supported set
Defensive patterns
Strategy: try-catch
Validate before calling
static readonly HashSet<Op> SupportedSpecOps = new()
{
Op.OpConvertFToS, Op.OpConvertFToU, Op.OpConvertSToF, Op.OpConvertUToF,
Op.OpSNegate, Op.OpFNegate, Op.OpNot, Op.OpLogicalNot,
Op.OpIAdd, Op.OpISub, Op.OpIMul, Op.OpUDiv, Op.OpSDiv,
Op.OpFAdd, Op.OpFSub, Op.OpFMul, Op.OpFDiv,
Op.OpShiftRightLogical, Op.OpShiftRightArithmetic, Op.OpShiftLeftLogical,
Op.OpBitwiseOr, Op.OpBitwiseXor, Op.OpBitwiseAnd,
Op.OpLogicalOr, Op.OpLogicalAnd, Op.OpLogicalEqual, Op.OpLogicalNotEqual,
Op.OpIEqual, Op.OpINotEqual,
Op.OpULessThan, Op.OpSLessThan, Op.OpUGreaterThan, Op.OpSGreaterThan,
Op.OpULessThanEqual, Op.OpSLessThanEqual, Op.OpUGreaterThanEqual, Op.OpSGreaterThanEqual,
Op.OpSelect
};
static bool IsSupportedSpecConstantOp(SpirvBuffer buffer, int id)
=> buffer.TryGetInstructionById(id, out var inst)
&& inst.Op == Op.OpSpecConstantOp
&& SupportedSpecOps.Contains((Op)inst.Data.Memory.Span[3]); Try / catch
try
{
var expr = ConstantExpression.ParseFromBuffer(id, buffer, context);
}
catch (NotImplementedException ex) when (ex.Message.StartsWith("Unsupported OpSpecConstantOp"))
{
// fall back to leaving the raw spec constant unresolved, or pre-fold upstream
} Prevention
- Restrict shader spec constants to simple arithmetic/bitwise/logical expressions and ternaries.
- Constant-fold complex expressions in the producing tool before emitting SPIR-V.
- Pre-scan OpSpecConstantOp inner opcodes with the supported set before parsing.
When it happens
Trigger: Parsing a SPIR-V buffer containing a specialization-constant operation outside the supported list in ConstantExpression.cs:161-219 — e.g. a spec constant computed with OpBitFieldInsert, OpUMulExtended, or any op not enumerated in the switch.
Common situations: Shaders compiled with aggressive constant propagation producing exotic spec-constant ops; SPIR-V from other compilers (glslang, DXC) using spec-constant operations Stride's SDSL pipeline never emits; using OpSpecConstantOp to encode casts like OpUConvert/OpSConvert that are missing from the conversion list.
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
- Unsupported constant type
- Unsupported float width
- Cannot parse constant expression from
- Cannot find type instruction for id
- Cannot find type for composite constant type id
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/d80821d7c702710f.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Core/ConstantExpression.cs:219
case Op.OpSGreaterThan:
case Op.OpULessThanEqual:
case Op.OpSLessThanEqual:
case Op.OpUGreaterThanEqual:
case Op.OpSGreaterThanEqual:
{
var left = ParseFromBuffer(inst.Data.Memory.Span[4], buffer, context);
var right = ParseFromBuffer(inst.Data.Memory.Span[5], buffer, context);
return new BinaryOpExpr(op, left, right);
}
case Op.OpSelect:
{
var cond = ParseFromBuffer(inst.Data.Memory.Span[4], buffer, context);
var trueVal = ParseFromBuffer(inst.Data.Memory.Span[5], buffer, context);
var falseVal = ParseFromBuffer(inst.Data.Memory.Span[6], buffer, context);
return new SelectExpr(cond, trueVal, falseVal);
}
default:
throw new NotImplementedException($"Unsupported OpSpecConstantOp inner op: {op}");
}
}
default:
throw new NotImplementedException($"Cannot parse constant expression from {inst.Op}");
}
}
}
/// <summary>
/// Integer constant. Covers int, uint, long, ulong — signedness determined at emission by SPIR-V type context.
/// </summary>
public sealed record IntConstExpr(long Value) : ConstantExpression
{
public override int Emit(SpirvContext context)
{
// For values that fit in int, use int (signed) — matches the common case for array sizes
if (Value is >= int.MinValue and <= int.MaxValue)View on GitHub (pinned to 96fad776d2)