Skip to content

feat(agc): read hull patch constants in tessellation domain programs - #4163

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
Celegans12:feat/agc-tessellation-patch-inputs
Oct 13, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
Celegans12:feat/agc-tessellation-patch-inputs

Conversation

@Celegans12

@Celegans12 Celegans12 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

Stacked on #4114; please review the last commit only.

Tessellation lowering refuses domain programs that read the hull's patch constants ("domain shader reads of patch constants or other patches are unsupported"), so a domain stage that consumes per-patch data from its hull cannot be drawn.

  • Domain reads that move with the relative patch but are not control-point reads become a new PatchInput kind, read through Patch-decorated inputs that match the hull's Patch outputs.
  • Both stages place a constant at its off-chip address modulo the record stride, so the hull (its patch-output stride) and the domain (the relative coefficient of its patch reads) agree on the record layout without seeing each other. Each declares the whole record as ceil(|stride|/16) uvec4 from location ceil(controlStride/16). The per-module minimum tessPatchBase is gone.
  • Refused on the domain side: patch reads that do not move with the relative patch, reads spanning more than one record, reads reaching into the control-point region (including a neighbour's points), patch reads without control-point reads, and inconsistent strides.
  • The hull's patch-constant writes take the same bound as the domain's reads: above the control points of all NUM_PATCHES patches, where the hull checked only one patch's before.

A domain read of a field the hull never writes cannot be detected, because the stages compile separately; debt/shader.md records that.

PPSA26344's first terrain domain program reads its patch's constants at 0x8000 - 208 * (rel + 1) plus offsets 96-192, all of which its hull writes.

Tested

Based on main 4bb2c7f plus #4082 and #4114. Windows 11 (MinGW-w64 GCC 15.2.0) and Linux: configure -DBUILD_TESTING=ON, full build, --target libs, ctest. Measured on #4222's head, which contains this PR:

  • Windows NVIDIA: 1049/1051 pass; log_macros fails here on every branch and on main, and patched_libraries cannot load main's libkernel_sys.prx (error 127), which this chain does not touch
  • Linux (upstream's job in WSL, GCC 13.3, llvmpipe), merged with main 8299b2f: 1033/1034 pass; agc_driver_depth_compare_copy also fails on main

In agc_driver_tessellation, DomainPatchRead is now a rendering check (the domain colours each patch from a constant its hull wrote at a wrapping record offset), and three refusals are new: a neighbour's record, a fixed-address patch read, and a neighbour's control point. The descending-record hull from #4114 now renders at 128 patches per group and is refused at 129, where its lowest record would overlap the last patch's control points. They fail on #4114's head with the old refusal or bound.

Not tested: console behaviour.

Checklist

@tibzejoker

Copy link
Copy Markdown
Contributor

GTA V (PPSA04264) on this PR's head (b3d525e), Windows 11, RTX 4090, on main 325e0a8 with #4082, #4114 and the unrelated PRs GTA needs to reach Story mode: the domain program that stopped at pc 0x9c (domain shader reads of patch constants or other patches are unsupported) compiles, and a 150 s Story run has no tessellation error. So this PR covers GTA's domain too, and I am closing my #4175 in its favour.

AI-assisted (Claude Code).

@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto the freshly rebased #4114 (so the chain stays stackable), which itself sits on current main. Your commit's authorship is unchanged; the rebase applied cleanly (no conflicts).

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV):

  • builds; tools/check_conventions.py --base origin/main passes
  • full suite: 949/949 passed — including the tessellation tests

To pick it up (note: this builds on the rebased #4114, so pull that first if you take both):

git fetch https://github.com/blawesom/AnyPS5 rebase/4114 rebase/4163
git checkout <your 4114 branch> && git merge --ff-only FETCH_HEAD   # after fetching rebase/4114
git checkout <your 4163 branch> && git merge --ff-only FETCH_HEAD  # FETCH_HEAD now = rebase/4163

Heads: rebase/4114 = d0ea5f4d1, rebase/4163 = 652f2763d. I'll do #4172 and #4201 on top of this next if the chain order holds.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 12, 2026
@Celegans12
Celegans12 force-pushed the feat/agc-tessellation-patch-inputs branch from b3d525e to f4aa18d Compare October 13, 2026 01:44
@Celegans12

Copy link
Copy Markdown
Contributor Author

One behaviour change here after Pipodi's review on #4201. The hull's own check for patch-constant writes used to accept anything above one patch's control points. The domain's check, added in this PR, wants everything above the control points of all NUM_PATCHES patches. The hull now uses the domain's bound too, so a hull whose records would reach into another patch's control points is refused instead of lowered.

That moved one test from #4114: the hull with descending records now draws at 128 patches per group and is refused at 129, which is where its lowest record first overlaps the last patch's control points. Replayed offline, Yōtei's terrain hull passes the new bound and recompiles completely. I couldn't check its two domains the same way: offline they stop earlier, at their known image-table lookups.

@tibzejoker, GTA V's domain already passed the same bound, so I expect its hull to pass as well. If you have a moment to recheck Story mode on this head, I'd be glad to hear either way.

Details

@fdfawef324

Copy link
Copy Markdown
Contributor

GTA V (PPSA04264) on this PR's head f4aa18d. On the open question: GTA's hull passes the new bound. Windows Server 2025, RTX 3060, MinGW-w64 GCC 15.2.0.

Base. An integration branch on main 7e181fc with #4082 rebased (7e3eefa), #4114 (6b15b72) and this PR cherry-picked, plus other open PRs.

Conflicts. Against current main (fc1f063) this head conflicts in two files, both because of #4727 (hull inputs at a computed control-point index):

derive() merges cleanly next to #4727's agree line for dynamic inputs.

Tests. agc_driver_tessellation (including RequirePatchConstants and the 128/129 boundary), agc_driver_graphics, agc_shader_memory and agc_tessellation_factor_ring pass. check_conventions.py passes.

GTA V.

  • The draw: VGT_LS_HS_CONFIG 0xc338 (3/3 control points, NUM_PATCHES 56). Domain 0x25cb33200 reads, at pc 0x9c, the 336-byte record that hull 0x25cb4ea00 writes at 0x8000 - 0x150 * (rel + 1).
  • Both stages compile at 56 patches per group. I replayed the title's two requests offline, rewritten to version 21. The hull's lowest record, 0x8000 - 0x150 * 56 = 13952, is above the control points of all 56 patches (56 × 3 × 80 = 13440). The hull's patch outputs and the domain's patch inputs come out as the same 21-vec4 record at the same location.
  • At 85 patches per group both are refused, as expected: the hull with "outside its control points and patch constants" and the domain with "neither its patch's control points nor a patch-constant record". So the real NUM_PATCHES matters here.
  • In game, GTA no longer aborts on the domain at pc 0x9c. On the same branch with only feat(agc): implement quad tessellation with counter-clockwise output and level clamps #4082 and a hull bound, it aborted there 43 s in after 57 frames (21.4 FPS). With fix(agc): bound tessellation patch records by the draw's patch count #4114 and this PR there is no tessellation refusal in three runs (231 s, 150 s and 375 s). The loading screen reaches 351-445 frames at 23-29 FPS.

The next stop is not tessellation.

  • Presents stop. After those frames the game thread spends about 5.5 minutes in sceAgcCreateInterpolantMapping, where the driver prepares each program synchronously in ResolveShaderAbi: about 4,100 from the disk cache and 3,300 new compiles.
  • Abort. At 375 s it aborts on pixel shader 0x25cb2dc00: GetImageResource dword 0 is not a valid runtime value.

@Celegans12
Celegans12 force-pushed the feat/agc-tessellation-patch-inputs branch from f4aa18d to 85e874b Compare October 13, 2026 09:42
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
TessellationLowering proved ring and LDS addresses affine over a
relative patch index of up to 256. A hull that lays its patch-constant
records out downwards, at 0x8000 - 208 * (rel + 1) like the quad
particle hull in Ghost of Yotei, then reached a negative address for
the last slot and aborted as "not affine". The relative patch index of
hull and domain programs stays below VGT_LS_HS_CONFIG.NUM_PATCHES
(bits 7:0, patches per threadgroup), which is 63 for that draw.

DecodeShaderStages now carries NUM_PATCHES as patchesPerGroup in the
tessellation configuration (draw keys, hull and domain cache keys,
recompile request version 22) and refuses zero, and the lowering bounds
the relative patch index by it. Configurations without it keep 256.
TessellationLowering refused every domain-program read of the off-chip
ring outside its patch's control points, so domain programs that read
the hull's patch constants, like the quad terrain domain in Ghost of
Yotei at 0x8000 - 208 * (rel + 1), aborted.

Such reads now lower to a new PatchInput kind and become Patch inputs.
Hull outputs and domain inputs place a constant by its off-chip address
modulo the record stride, in a record-sized array at location
ceil(hsStride / 16); each program derives both on its own, so the two
interfaces match although they compile separately. Domain reads at an
address that does not move with the relative patch, across more than
one record or inside the control-point region still throw, as does a
domain program that reads patch constants but no control point. Hull
patch-constant writes now take the same bound: above the control points
of all NUM_PATCHES patches, not just one patch's.
@Celegans12
Celegans12 force-pushed the feat/agc-tessellation-patch-inputs branch from 85e874b to 247162d Compare October 13, 2026 12:17
@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
#5159 docs/dev/debt/shader.md L87-88

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on this PR merged onto current main: build, the libSceAgc/libSceAgcDriver ctest scope, and agc_driver_tessellation on CI's lavapipe (Mesa 25.2.8): passes, including the new RequirePatchConstants render and the three new refusals. Without the non-test changes the test fails. The PatchInput records, the modulo-stride placement and the shared hull/domain bound read correctly, and the debt line sits mid-section. Also confirmed by @tibzejoker and @fdfawef324 on GTA V (RTX 4090 / 3060). Merging after the full Build.

@adriadam10
adriadam10 merged commit ac6d1b2 into boykopovar:main Oct 13, 2026
8 checks passed
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.

5 participants