stride3d/stride · error · NotSupportedException

Unsupported element type for clamp

Error message

Unsupported element type for clamp: {context.ReverseTypes[x.TypeId].GetElementType()}

What it means

CompileClamp lowers the SDSL clamp() intrinsic to a SPIR-V GLSL.std.450 clamp opcode (FClamp/UClamp/SClamp) chosen by the element type of the operand. When the operand's element type is not Float, UInt or Int (e.g. bool, double, 64-bit ints), no opcode variant exists and the compiler throws NotSupportedException, reporting the resolved element type for diagnosis.

Solutions

  1. Check the operand's element type; convert to float/int/uint (e.g. cast or use asfloat) before clamp.
  2. If clamping a double, cast to float first or implement manual min/max with supported types.
  3. If the operand is bool, restructure logic so clamp only receives numeric values.
  4. If you believe the element type should be supported, extend the switch in IntrinsicImplementations.CompileClamp to map the missing ScalarType to the appropriate GLSL clamp opcode.

Example fix

// before (SDSL)
double x = ...;
float c = clamp(x, 0.0, 1.0); // throws
// after
float c = clamp((float)x, 0.0f, 1.0f);
Defensive patterns

Strategy: validation

Validate before calling

bool IsClampable(SpirvContext ctx, SpirvValue v) =>
    ctx.ReverseTypes[v.TypeId].GetElementType() is ScalarType { Type: Scalar.Float or Scalar.UInt or Scalar.Int };

Type guard

bool IsScalarOf(TypeBase t, Scalar k) => t.GetElementType() is ScalarType s && s.Type == k;

Try / catch

try { result = intrinsics.CompileClamp(table, ctx, builder, fnType, x, min, max); }
catch (NotSupportedException ex) { /* report shader source location & operand element type */ }

Prevention

When it happens

Trigger: Calling clamp() in SDSL/HLSL shader code whose argument resolves to a vector/matrix/scalar whose element type is not one of float, uint, int — e.g. clamp() on a bool vector, a double value, or an unexpected type from a generic/t intrinsic instantiation.

Common situations: Using clamp on a half/double typed value assumed to behave like float; passing a bool result of a comparison into clamp; a generic shader function where T instantiates to an unsupported scalar; typos producing a wrong overload resolution.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs:137

    {
        var instruction = context.ReverseTypes[a.TypeId].GetElementType() switch
        {
            ScalarType { Type: Scalar.Float or Scalar.Double } => builder.InsertData(new GLSLFMax(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), a.Id, b.Id)),
            ScalarType { Type: Scalar.UInt } => builder.InsertData(new GLSLUMax(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), a.Id, b.Id)),
            ScalarType { Type: Scalar.Int } => builder.InsertData(new GLSLSMax(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), a.Id, b.Id)),
            _ => throw new NotSupportedException($"Unsupported element type for max: {context.ReverseTypes[a.TypeId].GetElementType()}"),
        };
        return new(instruction);
    }

    public override SpirvValue CompileClamp(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, SpirvValue min, SpirvValue max, TextLocation location = default)
    {
        var instruction = context.ReverseTypes[x.TypeId].GetElementType() switch
        {
            ScalarType { Type: Scalar.Float } => builder.InsertData(new GLSLFClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, min.Id, max.Id)),
            ScalarType { Type: Scalar.UInt } => builder.InsertData(new GLSLUClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, min.Id, max.Id)),
            ScalarType { Type: Scalar.Int } => builder.InsertData(new GLSLSClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, min.Id, max.Id)),
            _ => throw new NotSupportedException($"Unsupported element type for clamp: {context.ReverseTypes[x.TypeId].GetElementType()}"),
        };
        return new(instruction);
    }

    public override SpirvValue CompileRadians(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLRadians, x);
    public override SpirvValue CompileDegrees(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLDegrees, x);

    public override SpirvValue CompileExp(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLExp, x);
    public override SpirvValue CompileExp2(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLExp2, x);
    public override SpirvValue CompileLog(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLLog, x);
    public override SpirvValue CompileLog10(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => MultiplyConstant(table, context, builder, functionType.ReturnType, CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLLog2, x), (float)Math.Log10(2.0));
    public override SpirvValue CompileLog2(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLLog2, x);
    public override SpirvValue CompilePow(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, SpirvValue y, TextLocation location = default) => CompileGLSLFloatBinaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLPow, x, y);

    // Vector math
    public override SpirvValue CompileDistance(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue a, SpirvValue b, TextLocation location = default)
    {
        var instruction = builder.Insert(new GLSLDistance(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), a.Id, b.Id));

View on GitHub (pinned to 96fad776d2)