stride3d/stride · error · InvalidOperationException

Can't convert interface variable from

Error message

Can't convert interface variable from {sourceType} to {castType}

What it means

BuiltinProcessor.ConvertInterfaceVariable converts an interface variable's value to a cast type by resizing component vectors. Conversion is only allowed when base scalar types match; if castBaseType != sourceBaseType (e.g. float vs int semantic variable) the conversion is undefined and it throws InvalidOperationException.

Solutions

  1. Match the base scalar type of the variable to the semantic's expected type (float for most SV_* positions, uint for sample masks, etc.)
  2. Insert an explicit conversion in the shader body (e.g. (int4)streams.Position) instead of relying on interface-variable conversion
  3. Check the builtin remapping table entry that produced the incompatible castType
  4. If conversion should be supported, extend ConvertInterfaceVariable to emit a numeric conversion instead of throwing

Example fix

// before
int4 pos : SV_Position; // int vs float builtin
// after
float4 pos : SV_Position; // convert explicitly in body if int needed: var p = (int4)streams.Position;
Defensive patterns

Strategy: type-guard

Validate before calling

var (_, castBase) = ExtractSizeAndBaseType(castType);
var (_, srcBase)  = ExtractSizeAndBaseType(sourceType);
if (castBase != srcBase) throw new InvalidOperationException($"Interface cast requires matching base types: {srcBase} vs {castBase}");

Type guard

bool IsConvertibleCast(SdslType source, SdslType cast) => ExtractSizeAndBaseType(source).BaseType == ExtractSizeAndBaseType(cast).BaseType;

Try / catch

try { var v = processor.ConvertInterfaceVariable(value, castType); } catch (InvalidOperationException ex) { logger.LogError(ex, "Builtin type conversion rejected"); throw; }

Prevention

When it happens

Trigger: Applying a semantic/builtin cast where the target and source differ in base scalar type — e.g. a float4 semantic read and reinterpreted as an int4 (SV_Position to integer), or uint vs float builtin remaps.

Common situations: Shader code assigning a builtin/system-value to a variable of a different scalar type; remapping tables mapping SV_* semantics to differently typed variables; accidental int/float mixup in an interface struct.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Generation/BuiltinProcessor.cs:54

    /// <summary>
    /// Converts interface variables between types, handling vector and array size mismatches.
    /// Used when builtin types have different sizes than shader types.
    /// </summary>
    public static int ConvertInterfaceVariable(
        SpirvBuffer buffer,
        SpirvContext context,
        SymbolType sourceType,
        SymbolType castType,
        int value)
    {
        if (sourceType == castType)
            return value;

        var (castSize, castBaseType) = ExtractSizeAndBaseType(castType);
        var (sourceSize, sourceBaseType) = ExtractSizeAndBaseType(sourceType);

        if (castBaseType != sourceBaseType)
            throw new InvalidOperationException($"Can't convert interface variable from {sourceType} to {castType}");

        Span<int> components = stackalloc int[castSize];

        for (int i = 0; i < castSize; ++i)
        {
            components[i] = i < sourceSize
                ? (sourceType is ScalarType ? value : buffer.Add(new OpCompositeExtract(context.GetOrRegister(sourceBaseType), context.Bound++, value, [i])).ResultId)
                : context.CreateDefaultConstantComposite(sourceBaseType).Id;
        }

        return castType is ScalarType
            ? components[0]
            : buffer.Add(new OpCompositeConstruct(context.GetOrRegister(castType), context.Bound++, new(components))).ResultId;

        (int Size, SymbolType baseType) ExtractSizeAndBaseType(SymbolType castType)
        {
            return castType switch
            {

View on GitHub (pinned to 96fad776d2)