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
- Make all declarations of the same semantic use the identical type across stages
- Rename one of the conflicting semantics (e.g. TEXCOORD1) if the data differs
- Check composed shader/mixin signatures for a drifted semantic type and align it
- 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
- Keep semantic declarations identical across vertex/geometry/pixel stages
- Align mixin/inheritance shader signatures for shared semantics
- Use distinct semantics for differently-typed data
- Add cross-stage signature validation before merging
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
- Can't convert interface variable from
- 64bit integers
- An unknown EffectParameterClass was found.
- antialiasShaderName
- Assign back to matrix swizzle is not implemented yet
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/OpDecorateView on GitHub (pinned to 96fad776d2)