stride3d/stride · error · InvalidOperationException

OpAccessChain on PatchType/array-of-Streams can have only 1…

Error message

OpAccessChain on PatchType/array-of-Streams can have only 1 element

What it means

When AnalyzeStreamReadWrites encounters an access chain whose base is a patch variable or a geometry input array, it treats index [0] as the stream selector and only that single index is allowed. An OpAccessChain with more than one index on such a base throws InvalidOperationException.

Solutions

  1. Split the access: first index the patch/array element, then access members via separate access chains
  2. Index patches only with a single selector index as the analyzer expects
  3. Check your SDSL source for nested indexing on patch/geometry-input arrays and flatten it
  4. If generated by a transformation, fix the code generator to emit one access chain per level

Example fix

// before
// OpAccessChain patch[0].member (2 indexes)
// after
var elem = patch[0];
var member = elem.member; // separate access chains
Defensive patterns

Strategy: validation

Validate before calling

if (accessChain.Indexes.Elements.Length > 1 && patchInstructionIds.ContainsKey(accessChain.Base))
    throw new InvalidOperationException("Patch/array-of-streams access chain must use exactly 1 index");

Try / catch

try { analyzer.AnalyzeStreamReadWrites(module); } catch (InvalidOperationException ex) { logger.LogError(ex, "Bad access chain on patch/stream array"); throw; }

Prevention

When it happens

Trigger: An OpAccessChain on a patch or array-of-streams base carrying 2+ indexes — e.g. patch[i].member or input[i][j] — inside AnalyzeStreamReadWrites.

Common situations: Hull/domain shader code indexing into a patch array and then a member through the same access chain; generated code emitting multi-level access chains on stream arrays instead of decomposing them.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Analysis/ReadWriteAnalyzer.cs:192

                if (analysisResult.Resources.TryGetValue(structureId, out var resourceInfo3))
                    resourceInfo3.UsedThisStage = true;
            }
            else if (i.Op == Op.OpStreamsSDSL && new OpStreamsSDSL(ref i) is { } streamsInstruction)
            {
                streamsInstructionIds.Add(streamsInstruction.ResultId, StreamsKindSDSL.Streams);
                methodInfo.HasStreamAccess = true;
            }
            else if (i.Op == Op.OpAccessChain && new OpAccessChain(ref i) is { } accessChain)
            {
                var currentBase = accessChain.BaseId;

                // In case it's a patch or geometry-input-array access, i.e. patch[0] or input[0],
                // mark the access as being a stream
                if (patchInstructionIds.TryGetValue(currentBase, out var patchStreamKind))
                {
                    var patchVariableId = accessChain.Indexes.Elements.Span[0];
                    if (accessChain.Indexes.Elements.Length > 1)
                        throw new InvalidOperationException("OpAccessChain on PatchType/array-of-Streams can have only 1 element");
                    streamsInstructionIds.Add(accessChain.ResultId, patchStreamKind);
                }
                else
                {
                    StreamsKindSDSL? currentStreamKind = null;
                    // In case it's a streams access, mark the stream as being the base
                    if (streamsInstructionIds.TryGetValue(currentBase, out var streamKind))
                    {
                        var streamVariableId = accessChain.Indexes.Elements.Span[0];
                        var streamInfo = streams[streamVariableId];

                        // Set this base for OpStore/OpLoad stream R/W analysis
                        currentBase = streamVariableId;
                        currentStreamKind = streamKind;
                    }

                    // Any read or write through an access chain will be treated as doing it on the main variable.
                    // i.e., streams.A.B will share same streamInfo as streams.A

View on GitHub (pinned to 96fad776d2)