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
- Append to the geometry stage's output stream (Streams/Output kind), not to an input stream variable.
- Check the stream variable declaration; input streams (stage inputs) are read-only and cannot be append targets.
- 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
- Always append to the geometry stage's declared output stream, never to stage inputs.
- Name input and output stream variables distinctly (e.g. input vs outputStream).
- Review Append call receivers/arguments when refactoring geometry shaders.
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
- Use of undeclared stream variable
- ParameterKey type [ ] is not supported by SetStream
- StreamType [ ] is not supported
- Struct not found in shader
- Could not find compositions for expression
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)