Skip to content

feat(shader): read hull inputs at a control-point index computed from data - #4727

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
gjonn:feat/hull-dynamic-control-point-index
Oct 13, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
gjonn:feat/hull-dynamic-control-point-index

Conversation

@gjonn

@gjonn gjonn commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

What

Tessellation lowering refuses a hull LDS read whose input control-point index is computed from data. Its address is not affine in the invocation and relative patch, so the read fails with "the tessellation ring or LDS address is not affine". PPSA01317's hull programs loop over a per-patch mask and read input control points at an index loaded from memory, so main refuses these draws. Stop line: the lowering accepts such a read when the computed part of the address is a whole number of control points and does not depend on the relative patch index.

  • AddressEvaluator gives a hull LDS read (only that access kind) a dynamic term: a value that is not affine but is computed without the relative patch index, and the multiple it is scaled by. Terms of the same value add up, so (t << 1) + t of t = index << 3 is 24 * index. Terms of different values keep the gcd of their multiples. The dynamic term never passes through masks, shifts right, disjoint ORs, selects or phis whose arms differ.
  • The read is lowered from the program's own address (plus the instruction offset). It must still be inputs * localStride in the relative index. When the read has no invocation term, that also sets the local stride. requireRelativeIndependence accepts the relative index reaching such a read only with that stride, which the existing replacement of the index by 0 removes.
  • A computed term that is not a multiple of the local stride is refused: "reads LDS at a computed offset that is not a whole number of control points". The constant part must still fit in the patch, as before.
  • TessellationPointer clamps a hull input control-point index to inputControlPoints - 1 (UMin), on both the fast path and the general path. This differs from the console, where an index past the patch reads LDS of a neighbouring patch. The host has no such memory, and Vulkan leaves an out-of-range gl_in index undefined. The index is only known when the program runs, so only the offset can be refused at compile time. The clamp is recorded in docs/dev/debt/shader.md. For reads with a static index, the lowering already proves the index is in range, so the clamp has no effect on them.

HullSquaredAddress (address inv * inv) is now refused as "not a whole number of control points" instead of "not affine". It is still refused, and its expected reason is updated.

This overlaps the open tessellation stack #4082 → #4114 → #4163 → #4172 → #4201 → #4222 → #4450. In particular, #4201 edits requireRelativeIndependence. None of those PRs accepts a data-computed control-point index. This PR is based on main and will rebase on whichever merges first.

Tested

Windows 11, MinGW-w64 GCC, NVIDIA RTX 5090 Laptop (Vulkan), Release, ANYPS5_NO_SHADER_CACHE=1.

agc_driver_tessellation gains two triangle-patch hulls derived from its HullCode:

  • HullDynamicIndex reads its input control point at min(inv, 2) << 5 (v_min_u32 then v_lshlrev_b32). It must draw like HullCode (levels 1/0/5, culling, winding).
  • HullHalfSlotIndex scales the same index by 16, half the 32-byte slot. It must be refused as not a whole number of control points.

Before (main, with the new tests):

AGC graphics: the refusal named another reason: ShaderRecompiler::Recompile: tessellation lowering: the tessellation ring or LDS address is not affine in the control point and relative patch at pc 0x40

With the refusal check removed, the HullDynamicIndex draw fails on main with the same error:

ShaderRecompiler::Recompile: tessellation lowering: the tessellation ring or LDS address is not affine in the control point and relative patch at pc 0x40

After:

1/6 Test #158: agc_tessellation_factor_ring .......................   Passed
2/6 Test #197: agc_spirv_module ..................................   Passed
3/6 Test #209: agc_shader_control_flow ...........................   Passed
4/6 Test #212: agc_driver_graphics ...............................   Passed
5/6 Test #463: agc_driver_tessellation ...........................   Passed
6/6 Test #520: agc_shader_control_flow_iteration_selection_off ...   Passed
100% tests passed, 0 tests failed out of 6

Before this change, all four tessellation and control-flow tests passed on main (without the new fixtures). agc_structurizer, agc_shader_memory and agc_validate_program also pass with the change.

Not tested: console behaviour, or the full ctest suite.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA01317 Gran Turismo 7 (PPSA01317) #4697
  • AI-assisted: yes

… data

A hull program may read its input control points from LDS at an index it
computes from data, such as an index loaded from memory or min(invocation, n).
The tessellation lowering refused such a read because its address is not
affine in the invocation and relative patch.

The address evaluator now carries a dynamic term for a hull LDS read: a value
that is not affine but does not depend on the relative patch index, with the
multiple it is scaled by. Terms of the same value add up ((t << 1) + t of
t = index << 3 is 24 * index); terms of different values keep the greatest
common divisor of their multiples. The read is lowered from the program's own
address arithmetic, and the relative index may reach it only with the stride
of a patch's inputs. A computed offset that is not a whole number of control
points is refused.

The SPIR-V emitter clamps a hull input control-point index to the patch's last
input, recorded as technical debt: on the console an index past the patch
reads LDS of a neighbouring patch.
@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 12, 2026
@kr4y4

kr4y4 commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

A second-vendor run, to go with the RTX 5090 one.

Setup: Windows 11 (26200), WinLibs GCC 15.2.0 r7, Release, CI configure flags, an RTX 4070 Laptop GPU and an Intel Raptor Lake-S iGPU (tessellationAvailable=1). The head 0b7de0e merged into main 584cc4c.

  • agc_driver_tessellation, with the new HullDynamicIndex draw and HullHalfSlotIndex refusal, passes on both GPUs with --repeat until-fail:3.
  • Full suite (ctest --timeout 120 --parallel 4): 1018 of 1018 on both GPUs.

Written with AI assistance.

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#4082 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L331
#4114 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L331
#4163 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L331
core/shader/recompiler/Optimization/src/TessellationLowering.cpp L545-546
#4172 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L324-326, L331
core/shader/recompiler/Optimization/src/TessellationLowering.cpp L8, L545-546
#4201 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L324-326, L331
core/shader/recompiler/Optimization/src/TessellationLowering.cpp L8, L496-497, L545-546
#4222 core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L324-326, L331
core/shader/recompiler/Optimization/src/TessellationLowering.cpp L8, L496-497, L545-546

@oneandonlydean oneandonlydean self-assigned this Oct 13, 2026
@oneandonlydean
oneandonlydean merged commit 1268ba8 into boykopovar:main Oct 13, 2026
9 checks passed
@blawesom

Copy link
Copy Markdown
Contributor

Main regression found: agc_driver_tessellation segfaults deterministically on RADV since this PR

  • Culprit: 0b7de0e73 ("feat(shader): read hull inputs at a control-point index computed from data") — verified by checkpoint testing: its parent passes the test, the commit itself segfaults.
  • Failure: the test crashes inside libvulkan_radeon.so (backtrace is entirely RADV frames — the generated hull shader's SPIR-V drives the driver to a segfault, rather than our code faulting directly). Deterministic 3/3 on RX 9070 XT / RADV GFX1201, CachyOS, kernel 7.2.9, GCC 16.2.1, Release.
  • Impact: current main's full suite is red on RADV (1013/1014 with only this failing).

Reproduce:

ctest -R agc_driver_tessellation --output-on-failure

The failure is inside the driver, which usually means the emitted SPIR-V is malformed in a way lavapipe and the Windows drivers tolerate but RADV does not — the hull input indexing this PR adds is the prime suspect. Happy to provide the full backtrace, the generated SPIR-V dump, or bisect further within the commit if useful.

@blawesom

Copy link
Copy Markdown
Contributor

Additional data point on the regression: I checked whether the open #4723 (the hull input component-offset fix, rebased on top of current main including this PR) resolves the segfault — it does not; agc_driver_tessellation crashes identically with it applied. That points at the computed-index path this PR added (inPatch(...) on the vertex computation) rather than the constant-offset component indexing.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for the bisect. I think this is a Mesa 26.2 bug, not something wrong in the SPIR-V this PR emits:

  • All the hull modules pass spirv-val, and every computed index is in range ([3][2] uvec4 at Location 0, vertex index clamped to 2).
  • RADV 25.2.8 compiles the same modules without error. RADV 26.2.4 (built from the release tarball, gfx1201 through the AMDGPU drm-shim) fails on the dynamic-index hull only, at pipeline creation: a NIR validation assert at the end of nir_opt_varyings, reached from radv_graphics_shaders_link_varyings. A release build has no assert and goes on to the segfault you see.
  • By editing the hull module by hand: it needs a run-time attribute index together with a run-time component index on the input access chain. Either one constant and it compiles. The vertex index doesn't matter, and fix(shader): place hull input components by their offset in the control-point slot #4723 doesn't touch this path.

So it is best reported to Mesa; the four .spv stages and a small vkCreateGraphicsPipelines harness reproduce it without the emulator, and I can attach them here if you want to file it.

This was referenced Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants