stride3d/stride · error · InvalidOperationException
Can't figure array output size for tessellation shader
Error message
Can't figure array output size for tessellation shader
What it means
For tessellation shaders, the SPIR-V entry-point wrapper needs the output (TessellationControl) or input (TessellationEvaluation) array size to declare arrayed function/Output variables. The stream layout's ArrayOutputSize (hull shader patch output count) was not provided, so the generator cannot compute the array length and throws InvalidOperationException.
Solutions
- Set streamLayout.ArrayOutputSize explicitly (the patch constant count, e.g. [outputcontrolpoints(N)] equivalent) for the tessellation-control entry point.
- Ensure the hull shader declares a constant OutputPatch/array size in the source so the layout generator can populate ArrayOutputSize.
- Verify the layout construction code that builds streamLayout for tessellation pipelines and populate ArrayInputSize/ArrayOutputSize there.
- Clear shader caches / recompile shaders after fixing the declaration, so stale layouts without the size are not reused.
Example fix
// before (programmatic layout)
var streamLayout = new StreamLayout { OutputType = outputStruct }; // ArrayOutputSize left null
// after
var streamLayout = new StreamLayout { OutputType = outputStruct, ArrayOutputSize = 4 }; // matches outputcontrolpoints(4) Defensive patterns
Strategy: validation
Validate before calling
// Before invoking GenerateWrapper for tessellation-control stages
if (executionModel == ExecutionModel.TessellationControl && streamLayout.ArrayOutputSize is null)
throw new InvalidOperationException("Hull shader layout requires ArrayOutputSize (patch output control point count)");
if (executionModel == ExecutionModel.TessellationEvaluation && streamLayout.ArrayInputSize is null)
throw new InvalidOperationException("Domain shader layout requires ArrayInputSize"); Type guard
static bool HasTessellationArraySizes(ExecutionModel model, StreamLayout l) =>
model == ExecutionModel.TessellationControl ? l.ArrayOutputSize.HasValue
: model == ExecutionModel.TessellationEvaluation ? l.ArrayInputSize.HasValue
: true; Try / catch
try
{
var wrapper = EntryPointWrapperGenerator.GenerateWrapper(...);
}
catch (InvalidOperationException ex) when (ex.Message == "Can't figure array output size for tessellation shader")
{
log.Error("Stream layout missing tessellation array size; regenerate layout with explicit patch counts");
throw new ShaderCompilationException("Tessellation layout lacks ArrayOutputSize/ArrayInputSize", ex);
} Prevention
- Always declare constant output-control-point counts in hull shaders ([outputcontrolpoints(N)] equivalent) so sizes are inferable.
- Populate ArrayOutputSize/ArrayInputSize in your layout-builder rather than relying on defaults.
- Validate tessellation stream layouts in a pre-generation pass over all pipeline state.
- Invalidate shader caches after changing tessellation declarations so stale layouts are not reused.
When it happens
Trigger: GenerateWrapper runs with executionModel == TessellationControl and streamLayout.ArrayOutputSize is null — i.e. the interface/streams layout for a hull shader was generated without an explicit patch output count.
Common situations: Hull shaders whose output patch count is not inferable from the SDSL/HLSL source (e.g. declared via custom streams or OutputPatch without a constant size); pipeline definitions built programmatically where ArrayOutputSize was left unset; older shader caches produced before array sizes were tracked.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Hull OutputPatch can only be used in once place (constant…
- Can't process argument
- spirv_to_dxil_pipeline failed; SPIR-V dumped to
- Can't OpLoad with cbuffer
- Unsupported composite type
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/161a819f0c78cd2b.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Generation/EntryPointWrapperGenerator.cs:160
inputFieldValues[inputIndex] = BuiltinProcessor.ConvertInterfaceVariable(buffer, context, stream.InterfaceType, stream.Info.Type, inputFieldValues[inputIndex]);
}
inputValues[arrayIndex] = buffer.Add(new OpCompositeConstruct(context.GetOrRegister(streamLayout.InputType), context.Bound++, [.. inputFieldValues])).ResultId;
}
var inputsData1 = buffer.Add(new OpCompositeConstruct(context.GetOrRegister(new ArrayType(streamLayout.InputType, streamLayout.ArrayInputSize.Value)), context.Bound++, [.. inputValues])).ResultId;
return inputsData1;
}
var inputsData = ConvertInputsArray();
buffer.Add(new OpStore(inputsVariable, inputsData, null, []));
var entryPointTypeId = context.GetOrRegister(entryPoint.Type);
if (executionModel == ExecutionModel.TessellationControl || executionModel == ExecutionModel.TessellationEvaluation)
{
var arraySize = executionModel == ExecutionModel.TessellationControl
? streamLayout.ArrayOutputSize ?? throw new InvalidOperationException("Can't figure array output size for tessellation shader")
: streamLayout.ArrayInputSize.Value;
bool hullTessellationOutputsGenerated = false;
int GenerateHullTessellationOutputs()
{
if (hullTessellationOutputsGenerated)
throw new InvalidOperationException("Hull OutputPatch can only be used in once place (constant patch)");
hullTessellationOutputsGenerated = true;
var outputsVariable = buffer.Insert(variableInsertIndex++, new OpVariable(context.GetOrRegister(new PointerType(new ArrayType(streamLayout.OutputType, arraySize), Specification.StorageClass.Function)), context.Bound++, Specification.StorageClass.Function, null)).ResultId;
context.AddName(outputsVariable, "outputs");
for (int arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
{
for (var outputIndex = 0; outputIndex < streamLayout.OutputStreams.Count; outputIndex++)
{
var stream = streamLayout.OutputStreams[outputIndex];
var outputsVariablePtr = buffer.Add(new OpAccessChain(context.GetOrRegister(new PointerType(stream.Info.Type, Specification.StorageClass.Function)),
context.Bound++, outputsVariable,
[context.CompileConstant(arrayIndex).Id, context.CompileConstant(outputIndex).Id])).ResultId;View on GitHub (pinned to 96fad776d2)