stride3d/stride · error · InvalidOperationException
Can't write value on input or output struct
Error message
Can't write value on input or output struct
What it means
ReadWriteAnalyzer validates stream (input/output interface) access chains during SPIR-V post-processing. Writing through an access chain whose base is an Input or Output streams struct is illegal at this stage (stage outputs are assembled elsewhere), so the analyzer throws InvalidOperationException.
Solutions
- Remove the store into the input/output streams struct in the shader code
- Write outputs only through the stage's declared output mechanism (e.g. the SDSL stage output assignment, not a raw access-chain store)
- Check that the variable's StreamsKindSDSL decoration (Input vs Output) matches the intended usage
- If this is a compiler transformation emitting the store, fix the transformation to route writes to the correct target
Example fix
// before (SDSL) streams.Position = position; // writing to input stream // after var p = streams.Position; // read only from input streams
Defensive patterns
Strategy: validation
Validate before calling
if (accessChain.IsStore && streams.TryGetValue(accessChain.Base, out var si) && si.Kind is StreamsKindSDSL.Input or StreamsKindSDSL.Output)
throw new InvalidOperationException("Store to input/output stream is not allowed"); Type guard
bool IsStreamReadable(StreamInfo s) => s.Kind is StreamsKindSDSL.Input or StreamsKindSDSL.Output;
Try / catch
try { analyzer.AnalyzeStreamReadWrites(module); } catch (InvalidOperationException ex) { logger.LogError(ex, "Illegal stream write"); throw new ShaderCompilationException("Input/output streams are read-only at this stage", ex); } Prevention
- Never assign to members of the `streams` input struct in SDSL code
- Use the stage's declared output assignment mechanism for outputs
- Validate StreamsKindSDSL decorations after every transformation
When it happens
Trigger: A shader writes to a variable in the `streams` input or output struct via an OpAccessChain + OpStore inside AnalyzeStreamReadWrites — e.g. assigning to an input stream member or storing directly into the output stream in an unexpected place.
Common situations: Shader code doing `streams.Normal = ...` on an input stream, or a pass/transformation writing the output stream where the analyzer doesn't expect a store; mislabeled StreamsKindSDSL decoration on a variable.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- OpAccessChain on PatchType/array-of-Streams can have only 1…
- Variable initializer is not supported on streams variable
- 64bit integers
- Can't add type
- Could not load shader
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/433b67e10d4a5556.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Analysis/ReadWriteAnalyzer.cs:138
if (variables.TryGetValue(accessChain.Base, out var variableInfo))
variableInfo.UsedThisStage = true;
if (analysisResult.Resources.TryGetValue(accessChain.Base, out var resourceInfo))
resourceInfo.UsedThisStage = true;
if (analysisResult.CBuffers.TryGetValue(accessChain.Base, out var cbufferInfo))
cbufferInfo.UsedThisStage = true;
}
else if (i.Op is Op.OpStore && new OpStore(ref i) is { } store)
{
// Check for indirect access chains
if (!accessChainBases.TryGetValue(store.Pointer, out var accessChain))
accessChain.Base = store.Pointer;
if (streams.TryGetValue(accessChain.Base, out var streamInfo))
{
var streamKind = accessChain.StreamKind!.Value;
// Write on input/output stream are not allowed
if (streamKind is StreamsKindSDSL.Input or StreamsKindSDSL.Output)
throw new InvalidOperationException("Can't write value on input or output struct");
streamInfo.Write = true;
}
if (variables.TryGetValue(accessChain.Base, out var variableInfo))
variableInfo.UsedThisStage = true;
if (analysisResult.Resources.TryGetValue(accessChain.Base, out var resourceInfo))
resourceInfo.UsedThisStage = true;
if (analysisResult.CBuffers.TryGetValue(accessChain.Base, out var cbufferInfo))
cbufferInfo.UsedThisStage = true;
}
else if (i.Op is Op.OpAtomicIAdd or Op.OpAtomicISub or Op.OpAtomicUMin or Op.OpAtomicUMax
or Op.OpAtomicSMin or Op.OpAtomicSMax or Op.OpAtomicAnd or Op.OpAtomicOr or Op.OpAtomicXor
or Op.OpAtomicExchange or Op.OpAtomicCompareExchange or Op.OpAtomicLoad or Op.OpAtomicStore
or Op.OpAtomicIIncrement or Op.OpAtomicIDecrement)
{
// Atomic operations reference a pointer (word 3 after opcode, i.e. first operand after ResultType+ResultId or just after opcode for OpAtomicStore)
// Extract the pointer and mark the underlying resource as used
var pointer = i.Op == Op.OpAtomicStoreView on GitHub (pinned to 96fad776d2)