{"record":{"id":"c1b3386e85f3e8da","repo":"stride3d/stride","slug":"outputcontrolpoints-not-found-on-hull-shader-entrypoint-id","errorCode":null,"errorMessage":"outputcontrolpoints not found on hull shader {entryPoint.Id.Name}","messagePattern":"outputcontrolpoints not found on hull shader (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/InterfaceProcessor.cs","lineNumber":472,"sourceCode":"        static int FindOutputPatchSize(SpirvContext context, Symbol entryPoint, List<int> overrideChain)\n        {\n            for (var index = overrideChain.Count - 1; index >= 0; index--)\n            {\n                foreach (var i in context)\n                {\n                    if (i.Op == Op.OpExecutionMode && (OpExecutionMode)i is\n                        {\n                            EntryPoint: var target,\n                            Mode: ExecutionMode.OutputVertices,\n                            ModeParameters: { } m,\n                        } && target == overrideChain[index])\n                    {\n                        return m.Span[0];\n                    }\n                }\n            }\n\n            throw new InvalidOperationException($\"outputcontrolpoints not found on hull shader {entryPoint.Id.Name}\");\n        }\n\n        private EntryPointInfo GenerateStreamWrapper(SymbolTable table, SpirvBuffer buffer, SpirvContext context, ExecutionModel executionModel, Symbol entryPoint, AnalysisResult analysisResult, LiveAnalysis liveAnalysis, bool isFirstActiveShader)\n        {\n            var streams = analysisResult.Streams;\n\n            var stage = ExecutionModelToStageId(executionModel);\n\n            var entryPointFunctionType = (FunctionType)entryPoint.Type;\n            var overrideChain = ResolveOverrideChain(table, entryPoint);\n            // TODO: check all parameters instead of hardcoded 0\n            int? arrayInputSize = executionModel switch\n            {\n                ExecutionModel.Geometry => ((ArrayType)((PointerType)entryPointFunctionType.ParameterTypes[0].Type).BaseType).Size,\n                ExecutionModel.TessellationControl or ExecutionModel.TessellationEvaluation => ((PatchType)((PointerType)entryPointFunctionType.ParameterTypes[0].Type).BaseType).Size,\n                _ => null,\n            };\n            int? arrayOutputSize = executionModel switch","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/InterfaceProcessor.cs#L454-L490","documentation":"When generating a stream wrapper for a hull shader, InterfaceProcessor.FindOutputPatchSize scans the hull entry point for an outputcontrolpoints attribute to determine the output patch array size. If no such attribute is found on the hull entry point it throws, because HS output signatures require the control-point count.","triggerScenarios":"MergeSDSL with a hull shader (HSMain) whose entry point class does not declare an outputcontrolpoints (patch constant/control point count) attribute, so the scan over the entry point's execution-mode/decoration instructions finds nothing.","commonSituations":"Porting an HLSL hull shader and omitting the outputcontrolpoints property; a hull mixin that inherits from a base which normally provides the attribute but the override lost it; composing a domain shader's hull counterpart from a class missing stage attributes.","solutions":["Add the outputcontrolpoints attribute to the hull shader entry point class (e.g. [outputcontrolpoints(3)] or the SDSL equivalent HullDomainControlPoints)","Ensure the hull entry point is the class actually carrying the attribute (check composition overrides)","Verify the SDSL syntax — the attribute must be on the entry-point method/class, not a helper function"],"exampleFix":"// before\n[WaveSize(64)]\npublic override void HSMain() { ... } // no outputcontrolpoints\n// after\n[outputcontrolpoints(3)]\npublic override void HSMain() { ... }","handlingStrategy":"validation","validationCode":"// Ensure the hull entry point class declares the control point count\nvar hsClass = composition.Classes.First(c => c.GetMethods().Any(m => m.Name == \"HSMain\"));\nif (!hsClass.GetCustomAttributesData().Any(a => a.AttributeType.Name.Contains(\"outputcontrolpoints\"))\n    && !hsClass.GetMethods().Any(m => m.CustomAttributes.Any(a => a.AttributeType.Name.Contains(\"outputcontrolpoints\"))))\n    throw new InvalidOperationException(\"Hull shader must declare [outputcontrolpoints(N)].\");","typeGuard":null,"tryCatchPattern":"try\n{\n    mixer.MergeSDSL(shaderSource, options, log, out var bytecode, out _, out _, out _);\n}\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"outputcontrolpoints not found\"))\n{\n    log.Error(\"Hull shader missing outputcontrolpoints attribute: \" + ex.Message);\n}","preventionTips":["Always pair hull shaders with [outputcontrolpoints(N)] on the entry point","When porting HLSL hull shaders, carry over all patch attributes, not just patchconstantfunc","Add a reflection-based test that compiles all hull shaders in the project"],"tags":["spirv","shader-compilation","hull-shader","missing-attribute"],"backgroundTag":"missing-required-argument","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}