stride3d/stride · error · NotSupportedException

Unsupported element type for saturate

Error message

Unsupported element type for saturate: {functionType.ReturnType.GetElementType()}

What it means

CompileSaturate implements saturate() as a clamp between 0 and 1, selecting the GLSL.std.450 FClamp/UClamp/SClamp opcode from the function's RETURN type element type. If the return type element is not Float, UInt or Int (e.g. double, bool), the opcode cannot be chosen and NotSupportedException is thrown.

Solutions

  1. Cast the value to float before saturate, e.g. saturate((float)x).
  2. Avoid saturate on double; write min(max(x, 0.0), 1.0) manually if doubles are truly needed.
  3. Check overload resolution so saturate receives a float/int/uint vector or scalar.
  4. If a scalar kind should be supported, add a case to the switch in CompileSaturate.

Example fix

// before (SDSL)
double d = ...;
float s = saturate(d); // throws
// after
float s = saturate((float)d);
Defensive patterns

Strategy: validation

Validate before calling

bool IsSaturatable(TypeBase returnType) =>
    returnType.GetElementType() is ScalarType { Type: Scalar.Float or Scalar.UInt or Scalar.Int };

Type guard

bool IsFloatLike(TypeBase t) => t.GetElementType() is ScalarType { Type: Scalar.Float };

Try / catch

try { result = intrinsics.CompileSaturate(table, ctx, builder, fnType, x); }
catch (NotSupportedException ex) { /* suggest a cast to float */ }

Prevention

When it happens

Trigger: Calling saturate(x) where the expression's resolved return type has an unsupported element type — typically double (saturate of a double), or a bool/struct type from overload resolution gone wrong.

Common situations: Using saturate on double-precision math in a shader targeting an API without double clamp; a generic T function where T instantiates to an unexpected scalar; implicit conversions producing an unexpected return type.

Related errors


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

Appendix: source

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

        return new(instruction.ResultId, instruction.ResultType);
    }

    public override SpirvValue CompileRound(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLRound, x);
    public override SpirvValue CompileRsqrt(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLInverseSqrt, x);
    public override SpirvValue CompileSqrt(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default) => CompileGLSLFloatUnaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLSqrt, x);
    public override SpirvValue CompileStep(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue a, SpirvValue x, TextLocation location = default) => CompileGLSLFloatBinaryCall(table, context, builder, functionType.ReturnType, Specification.GLSLOp.GLSLStep, a, x);
    public override SpirvValue CompileSaturate(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default)
    {
        // Ensure 0.0 amd 1.0 constants have same type as x
        var constant0 = builder.Convert(context, context.CompileConstant(0.0f), functionType.ReturnType);
        var constant1 = builder.Convert(context, context.CompileConstant(1.0f), functionType.ReturnType);

        var instruction = functionType.ReturnType.GetElementType() switch
        {
            ScalarType { Type: Scalar.Float } => builder.InsertData(new GLSLFClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, constant0.Id, constant1.Id)),
            ScalarType { Type: Scalar.UInt } => builder.InsertData(new GLSLUClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, constant0.Id, constant1.Id)),
            ScalarType { Type: Scalar.Int } => builder.InsertData(new GLSLSClamp(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id, constant0.Id, constant1.Id)),
            _ => throw new NotSupportedException($"Unsupported element type for saturate: {functionType.ReturnType.GetElementType()}"),
        };
        return new(instruction);
    }
    public override SpirvValue CompileSign(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue x, TextLocation location = default)
    {
        var sourceType = context.ReverseTypes[x.TypeId];
        var instruction = sourceType.GetElementType() switch
        {
            ScalarType { Type: Scalar.Float or Scalar.Double } => builder.InsertData(new GLSLFSign(x.TypeId, context.Bound++, context.GetGLSL(), x.Id)),
            ScalarType { Type: Scalar.UInt or Scalar.Int or Scalar.UInt64 or Scalar.Int64 } => builder.InsertData(new GLSLSSign(context.GetOrRegister(functionType.ReturnType), context.Bound++, context.GetGLSL(), x.Id)),
            _ => throw new NotSupportedException($"Unsupported element type for sign: {sourceType.GetElementType()}"),
        };
        // FSign return float whereas HLSL sign() expects int
        return builder.Convert(context, new(instruction), functionType.ReturnType);
    }

    public override SpirvValue CompileSmoothstep(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue a, SpirvValue b, SpirvValue x, TextLocation location = default)
    {

View on GitHub (pinned to 96fad776d2)