stride3d/stride · error · InvalidOperationException

Two variables with same semantic

Error message

Two variables with same semantic {stream.Value.Semantic} have different types {stream.Value.Type} and {firstStream.Value.Type}

What it means

VariableMerger.MergeSameSemanticVariables merges interface variables that share the same semantic (e.g. TEXCOORD0). Merging is only valid if all variables with that semantic have identical types; if a later variable's Type differs from the first, merging would corrupt the interface layout, so it throws InvalidOperationException.

Solutions

  1. Make all declarations of the same semantic use the identical type across stages
  2. Rename one of the conflicting semantics (e.g. TEXCOORD1) if the data differs
  3. Check composed shader/mixin signatures for a drifted semantic type and align it
  4. If the difference is intentional (widening), perform an explicit cast/intermediate variable instead of relying on merging

Example fix

// before
// vertex: float2 TexCoord : TEXCOORD0;
// pixel:  float4 TexCoord : TEXCOORD0;
// after
// both stages: float4 TexCoord : TEXCOORD0; (or use separate semantics)
Defensive patterns

Strategy: validation

Validate before calling

var bySemantic = variables.GroupBy(v => v.Semantic);
foreach (var g in bySemantic)
    if (g.Select(v => v.Type).Distinct().Count() > 1)
        throw new InvalidOperationException($"Semantic {g.Key} declared with multiple types: {string.Join(", ", g.Select(v => v.Type))}");

Try / catch

try { merger.MergeSameSemanticVariables(module); } catch (InvalidOperationException ex) { logger.LogError(ex, "Semantic type conflict across stages"); throw; }

Prevention

When it happens

Trigger: Two or more variables decorated with the same semantic but declared with different types (e.g. float2 TEXCOORD0 in one stage, float4 TEXCOORD0 in another) reaching MergeSameSemanticVariables.

Common situations: Vertex/pixel shader signature mismatch for the same semantic; a composed shader (mixin/inheritance) where different stages declare the same semantic with different widths; library shaders and user shaders disagreeing on a semantic's type.

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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Cleanup/VariableMerger.cs:29

/// <summary>
/// Handles merging of variables with the same semantic and propagating streams between shader stages.
/// </summary>
internal static class VariableMerger
{
    /// <summary>
    /// Merges variables that have the same semantic into a single variable.
    /// </summary>
    public static void MergeSameSemanticVariables(SymbolTable table, SpirvContext context, SpirvBuffer buffer, AnalysisResult analysisResult)
    {
        Dictionary<int, int> remapIds = new();
        foreach (var streamWithSameSemantic in analysisResult.Streams.Where(x => x.Value.Semantic != null).GroupBy(x => x.Value.Semantic))
        {
            // Make sure they all have the same type
            var firstStream = streamWithSameSemantic.First();
            foreach (var stream in streamWithSameSemantic.Skip(1))
            {
                if (stream.Value.Type != firstStream.Value.Type)
                    throw new InvalidOperationException($"Two variables with same semantic {stream.Value.Semantic} have different types {stream.Value.Type} and {firstStream.Value.Type}");

                // Remap variable
                remapIds.Add(stream.Key, firstStream.Key);
            }
        }

        // Remove duplicate streams
        HashSet<int> removedIds = new();
        foreach (var i in buffer)
        {
            if (i.Op == Op.OpVariableSDSL && ((OpVariableSDSL)i) is { } variable && remapIds.ContainsKey(variable.ResultId))
            {
                SpirvBuilder.SetOpNop(i.Data.Memory.Span);
                removedIds.Add(variable.ResultId);
            }
        }

        // Remove OpName/OpDecorate

View on GitHub (pinned to 96fad776d2)