stride3d/stride · error · InvalidOperationException

StreamOutput.Append() only accepts Streams or Output objects

Error message

StreamOutput.Append() only accepts Streams or Output objects

What it means

In geometry-shader stream output handling, StreamOutput.Append(value) compiles its argument and inspects its StreamsType kind. Only Streams and Output kinds are valid; if the argument's stream type kind is Input, the compiler throws this InvalidOperationException. Input streams cannot be appended to (they are read-only vertex inputs to the geometry stage).

Solutions

  1. Append to the geometry stage's output stream (Streams/Output kind), not to an input stream variable.
  2. Check the stream variable declaration; input streams (stage inputs) are read-only and cannot be append targets.
  3. Rename or restructure the shader so the value passed to Append comes from inputs but is appended to the output stream.

Example fix

// before (geometry shader SDSL)
inputStream.Append(position);   // appending to an Input stream

// after
outputStream.Append(position);  // append to the Streams/Output stream
Defensive patterns

Strategy: type-guard

Validate before calling

// guard before emitting Append
if (arg.ValueType is StreamsType st && st.Kind == StreamsKindSDSL.Input)
    throw new InvalidOperationException("Cannot Append to an Input stream");

Type guard

static bool IsAppendableStream(TypeBase t) => t is StreamsType st && st.Kind is StreamsKindSDSL.Streams or StreamsKindSDSL.Output;

Try / catch

try { CompileGeometryShader(gs); }
catch (InvalidOperationException e) when (e.Message.Contains("StreamOutput.Append()"))
{
    // re-target Append at the output stream
}

Prevention

When it happens

Trigger: Calling streamsOutput.Append(x) where x's ValueType is a StreamsType with Kind == StreamsKindSDSL.Input — i.e. appending to or appending an input stream in a StreamOutput.Append call.

Common situations: Geometry shader authors accidentally appending to an input stream variable instead of the stage's output stream, or passing the wrong stream variable as the Append receiver/argument.

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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/Expression.cs:1064

                        if (compiler == null)
                        {
                            ((MethodCall)accessor).ProcessParameterSymbols(table, null);
                            accessor.Type = ScalarType.Void;
                            break;
                        }

                        var (builder, context) = compiler;

                        // Emit OpAccessChain with everything so far
                        EmitOpAccessChain(accessChainIds, i - 1);

                        var output = methodCall.Arguments.Values[0].CompileAsValue(table, compiler);
                        var streamsType = (StreamsType)methodCall.Arguments.Values[0].ValueType!;
                        // Note: if it was a Streams, implicit cast it to Output
                        if (streamsType.Kind == StreamsKindSDSL.Streams)
                            output = new(builder.InsertData(new OpCopyLogical(context.GetOrRegister(new StreamsType(StreamsKindSDSL.Output)), context.Bound++, output.Id)));
                        else if (streamsType.Kind == StreamsKindSDSL.Input)
                            throw new InvalidOperationException("StreamOutput.Append() only accepts Streams or Output objects");
                        builder.Insert(new OpEmitVertexSDSL(output.Id));
                        result = default;
                        break;
                    }
                case (PointerType { BaseType: GeometryStreamType geometryStreamOutput },
                    MethodCall { Name.Name: "RestartStrip", Arguments.Values.Count: 0 }):
                    if (compiler == null)
                    {
                        ((MethodCall)accessor).ProcessParameterSymbols(table, null);
                        accessor.Type = ScalarType.Void;
                        break;
                    }

                    // Emit OpAccessChain with everything so far
                    EmitOpAccessChain(accessChainIds, i - 1);

                    compiler.Builder.Insert(new OpEndPrimitive());
                    result = default;

View on GitHub (pinned to 96fad776d2)