stride3d/stride · error · NotSupportedException

Cannot simplify constant of type {value.GetType()}

Error message

Cannot simplify constant of type {value.GetType()}

What it means

ShaderMixer's SPIR-V constant simplification path has dedicated handling for a fixed set of literal constant types (e.g. bool, int, long, float, double via BitConverter.DoubleToInt64Bits) and packs their bits into an OpData buffer. When the boxed runtime value passed in is of a type outside that switch, the default arm throws NotSupportedException naming the concrete CLR type. It is a compile-time guard: the mixer cannot emit a SPIR-V constant word layout for that type.

Solutions

  1. Find the value whose type is named in the message in the SDSL shader being compiled and change it to a supported literal type (int, float, double, bool, etc.).
  2. Cast the constant explicitly before it reaches the simplification path (e.g. (double)myValue) so it hits an existing case.
  3. If the type legitimately needs support, extend the switch in ShaderMixer.cs with a new case that packs the bits correctly (e.g. ulong via unchecked BitConverter path) and add a test.

Example fix

// before
const uint SampleCount = 4; // boxed as uint -> default arm throws
// after
const int SampleCount = 4; // int case handles it
Defensive patterns

Strategy: type-guard

Validate before calling

static bool IsSupportedConstantType(object v) =>
    v is bool or int or long or float or double;

Type guard

if (value is not (bool or int or long or float or double))
    throw new ArgumentException($"Unsupported constant type {value.GetType()}\", nameof(value));

Try / catch

try { SimplifyConstant(value); }
catch (NotSupportedException ex) { Log.Warn($"Skipping constant: {ex.Message}"); /* fall back to non-simplified path */ }

Prevention

When it happens

Trigger: Calling the constant-simplification method in ShaderMixer.cs with a constant whose GetType() is not one of the explicitly cased literal types (e.g. char, uint, ulong, decimal, an enum, or a user struct) reaches the default: arm at ShaderMixer.cs:1191 and throws.

Common situations: SDSL source containing a constant of an exotic literal type that a mixer pass boxes; a refactor changed the value type (int -> uint or enum) feeding the constant path; a new numeric type was added upstream without extending the switch.

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/eed1fb1aaab78222. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.cs:1191

            {
                case int v: mem.Span[3] = v; break;
                case uint v: mem.Span[3] = unchecked((int)v); break;
                case float v: mem.Span[3] = BitConverter.SingleToInt32Bits(v); break;
                case long v:
                    mem.Span[3] = (int)(v & 0xFFFFFFFF);
                    mem.Span[4] = (int)(v >> 32);
                    break;
                case ulong v:
                    mem.Span[3] = (int)(v & 0xFFFFFFFF);
                    mem.Span[4] = (int)(v >> 32);
                    break;
                case double v:
                    var bits = BitConverter.DoubleToInt64Bits(v);
                    mem.Span[3] = (int)(bits & 0xFFFFFFFF);
                    mem.Span[4] = (int)(bits >> 32);
                    break;
                default:
                    throw new NotSupportedException($"Cannot simplify constant of type {value.GetType()}");
            }
            buffer.Replace(index, new OpData(mem));
        }
    }

    private static void RemoveInstructionWhere(SpirvBuffer buffer, Func<OpDataIndex, bool> match)
    {
        int insertIndex = 0;
        for (int sourceIndex = 0; sourceIndex < buffer.Count; sourceIndex++)
        {
            var i = buffer[sourceIndex];
            var remove = match(i);

            if (!remove)
            {
                if (insertIndex++ != sourceIndex)
                    // Note: we're not using Dispose() since we simply move it
                    buffer.Replace(insertIndex - 1, buffer[sourceIndex].Data, false);

View on GitHub (pinned to 96fad776d2)