Skip to content

feat(agc): draw tessellation followed by an NGG geometry stage - #6612

Merged
oneandonlydean merged 7 commits into
boykopovar:mainfrom
milescoviello:feat/tess-geometry
Oct 27, 2026
Merged

oneandonlydean merged 7 commits into
boykopovar:mainfrom
milescoviello:feat/tess-geometry

Conversation

@milescoviello

@milescoviello milescoviello commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Crimson Desert (PPSA25286, 01.000.568) stops on main 12 to 14 s in, while it resolves the shader ABIs of its first graphics stages:

terminate called after throwing an instance of 'std::runtime_error'
  what():  AGC graphics: VGT_SHADER_STAGES_EN=0x60212d: combined tessellation and geometry is unsupported by the reference path

Its tessellated draws feed an NGG geometry stage. From a throwaway print of the first one:

Register Value Meaning
VGT_SHADER_STAGES_EN 0x60212d LS_EN, HS_EN, ES_EN = DS, GS_EN, PRIMGEN_EN, HS_W32, GS_W32
SPI_SHADER_PGM_LO_LS (0x148) registered type 3 one program holding the local and the hull half; nothing at SPI_SHADER_PGM_LO_HS
SPI_SHADER_PGM_LO_ES (0xc8) registered type 2 merged domain and geometry program
VGT_LS_HS_CONFIG / VGT_TF_PARAM 0x1043f / 0x4004a 4 control points in and out, quad domain, fractional-odd
VGT_ESGS_RING_ITEMSIZE / VGT_GS_MAX_VERT_OUT / GE_MAX_OUTPUT_PER_SUBGROUP 4 / 6 / 192 a 4-dword item per vertex; up to a 6-vertex strip per triangle

main refuses the combination, and with that refusal lifted it would refuse wave32 tessellation next. The enum already has ShaderPath::TessellationGeometry, unused until now. This PR draws that path.

How

The geometry half needs the subgroup: its threads share LDS, use s_barrier and allocate their output with GS_ALLOC_REQ, which only the mesh path runs. A Vulkan geometry shader can't run it per triangle, and a mesh shader can't follow the tessellator. So the draw takes two passes:

  1. The tessellation pipeline, with rasterizer discard. The domain role compiles only the program's vertex half: the code up to the target of its first s_cbranch_execz, which skips the vertex threads' part. Its ESGS LDS writes (thread index × item size) become domain outputs. A generated geometry shader (BuildTessellationCaptureGeometryShader) appends each tessellated triangle's three items to a device-local buffer and keeps the mesh draw arguments in a small header: one group per primitivesPerGroup triangles, and three indices per triangle.
  2. The mesh path draws the same program as a mesh shader, with no vertex threads in s3. Its entry copies the group's captured items into LDS where the vertex half would have written them, then runs the geometry half unchanged. It is drawn with vkCmdDrawMeshTasksIndirectEXT from the header, after a geometry-shader-write to indirect/mesh-read barrier, in a new render pass.

The hull side:

The compile modes follow from fields the request already serializes (a mesh configuration on a domain program, a tessellation configuration on a mesh program). The request format doesn't change beyond #6679's version 37; the cache key gains a word for each new mode.

The approximations are in the technical debt files: triangles reach the geometry half in the order the capture claims them, at most 2^20 per draw, the geometry half's primitive ID is the captured triangle's index, and instanced draws throw.

Tested

Capture draw after an ordinary draw in one batch (004f498, after @oneandonlydean's review): the capture pass now keys its render pass by rasterizer discard, so it can't continue an open pass that has attachments. A new agc_driver_tessellation_geometry case records an ordinary tessellation draw and then a tessellation-geometry draw into the same resident target with no wait between, and compares the image with the ordinary draw alone.

  • Khronos layer 1.4.363 over the 12 tessellation and journal tests: with the new case and without the key change (a371244bf8), VUID-vkCmdDrawIndexed-colorAttachmentCount-06179 x2. With the change, no VUID- messages; 12 of 12 pass on both.

  • The 12 tests pass 3 of 3 on Linux. On the Windows 11 VM (GT 1030, WinLibs GCC 15.2.0 r7), the merge base passes 11 of 11 3 of 3 and this head 12 of 12 3 of 3.

  • Current head 1168a21b8c, rebased onto main 2dc8560, which changed Draw(): it takes a DrawTraceContext (feat(agc): trace shader failures through draw submission batches #5431) and the driver's Draw.cpp gained <algorithm>. drawTessellationGeometry now takes the draw's trace context and passes it to both of its inner draws, so traced tessellation-geometry draws keep their trace; the include conflict keeps main's <algorithm> and this branch's <map>. The journal format stays 12 (main is at 10), and the guards stay 904 / 160 (main's 832 / 112 plus this branch's fields), confirmed by the build. The 12 tests below pass 3 of 3 on this head. On the Windows 11 VM (GT 1030, WinLibs GCC 15.2.0 r7), the merge base passes 11 of 11 and this head 12 of 12, 3 runs each, with agc_driver_tessellation_geometry skipped there (no mesh shaders).

  • 75a06af9a9, rebased onto main c035bd5 after b76ab1f (one-fragment multisampled colour targets) grew State to 832 bytes there; the only conflict was the journal's size guard, which is now 904 (832 plus this branch's 72), with ShaderStages at 160. The 12 tests below pass 3 of 3 on this head.

  • d175c08f94, rebased onto main d0e7fe7 after feat(agc): apply the color logic operation of CB_COLOR_CONTROL #9348 took journal format 10; the only conflict was that line, and this branch keeps its provisional 12 (feat(agc): store the pipeline journal's modules compressed #8365 takes 11) until it is set to main+1 at landing. The 12 tests below pass 3 of 3 on this head.

  • b6f3131b54, rebased onto main c9b7582 after feat(agc): draw DB depth copies with a driver pixel program #8999, which conflicted in Draw(): main now opens it with DepthCopySourceScope, and this branch's early return for tessellation followed by a geometry stage comes right after that scope, so it covers those draws too. The 12 tests below pass 3 of 3 on this head.

  • ce4748f0fa: e6db936606 plus a fix from @oneandonlydean's review. ImpliesNot returned at a negated condition, so a predicate !!a (a complemented own-lane ballot bit of !a after lane-bit lowering) never proved !a false, alone or in either order of a conjunction; each check now falls through when it doesn't prove the negation. Three new agc_tessellation_lowering cases build that hull read and must lower at its address; with main's ImpliesNot all three are refused 3 of 3 (not a whole number of control points), and with the fix the 12 tests below pass 3 of 3.

  • e6db936606 was rebased onto main d098eca, which conflicted in TessellationLowering.cpp: main added ImpliesNot, which covers what this branch's Excludes did (a predicate that implies a condition's negation) and also a negated condition, so the branch calls ImpliesNot at its two sites and drops Excludes. The journal commit takes format 12. main is at 9, and 10 is also used by fix(agc): render layered mesh targets within device limits #6959, fix(agc): build one compute pipeline when results differ only in a table's size #8212 and fix(agc): start the context at its reset state for color targets and viewports #10420 and 11 by feat(agc): store the pipeline journal's modules compressed #8365, so this bump can't merge silently with any of theirs. Linux (Ubuntu 26.04.1, GCC 15.2, Release, RTX 4070 Ti, 615.71.09): the tessellation, pipeline journal, agc_driver_graphics and pixel LDS tests (12) pass 3 of 3.

  • Previous head ae86a022f6, on main 11d6efa: the same 12 passed 3 of 3 on Linux, and on f28839f3c5 on the Windows 11 VM (GT 1030, 582.66, WinLibs GCC 15.2.0 r7), with agc_driver_tessellation_geometry skipped there (no mesh shaders).

Earlier heads: Linux (Ubuntu 24.04, GCC 13.3, Release), RTX 4070 Ti, NVIDIA 595.99.

Crimson Desert, scripted pad input:

Build Result
main 5d9dcc4 stops 14 s in on the refusal above
the head before the restack (0eeb815) gets past it, and stops 123 s in on a registered NGG passthrough vertex program: SPIR-V emission failed: ... opcode=Barrier reason=s_barrier in a vertex program that accesses LDS or GDS or writes memory is not implemented
de9f0f7 with #6613 and #6178 (for memory) runs to the 240 s limit with no stop

The title is still building shaders and pipelines then and hasn't issued a draw, so it hasn't drawn the new path yet. With #6706 as well, which makes its large compute pipelines about 3x faster to create, it reaches its first draw at 597 s and stops there on AGC graphics: fragment shader must export float4 colors to its attachments, which is a separate change. In a 905 s run (the previous head with #6613 and #6178), the shaders finish after about 7 minutes. After that, eleven compute pipelines of 110,000 to 870,000 SPIR-V words take 2 to 137 s each to create on NVIDIA, about 570 s in all, and the first draw still hasn't come. That's a separate problem, which I'm looking at. The rendering is checked by the tests above only.

On the previous head (0eeb815), jotitadev ran it on an RTX 3070 (595.99.02) and on lavapipe (Mesa 26.0.3), Ubuntu 26.04, and confirmed the area tests there.

SleglJan reviewed the merged hull parts of de9f0f71c on an RTX 3080 (NVIDIA 615.78.08, CachyOS). ctest -R 'tess|hull|prepared_graphics|shader_memory|request' passed 10 of 10, and agc_driver_tessellation and agc_driver_tessellation_geometry passed 3 of 3 each.

On de9f0f7:

  • lavapipe (llvmpipe, LLVM 20.1.2): the 30 tests matching ^agc_driver_(prepared|graphics$|tessellation|ngg|mesh|vertex|registered) pass or skip, and tools/check_skips.py --job linux accepts the 6 skips. agc_driver_tessellation now runs its merged hull cases there, because feat(agc): run registered merged hull programs (binary type 3) #6679 runs the merged hull at one lane per invocation. agc_driver_tessellation_geometry still skips, since the capture needs subgroup operations in tessellation stages.
  • Intel VM (Arrow Lake iGPU, Mesa 26.1.6 ANV, Debian 13): agc_driver_tessellation and agc_driver_tessellation_geometry pass 3 of 3 with no skips.

@LeandroLP23 ran it on Windows 11 with an AMD Radeon 860M (RDNA 3.5, driver 32.0.13058.8001) (comment), in one build of main 6efb726 with this PR and six other pull requests: agc_driver_tessellation, agc_driver_tessellation_geometry, agc_driver_graphics, agc_driver_prepared_graphics and agc_driver_store_over_untracked pass 3 runs of 3.
@JJenkx ran this head, rebased unchanged onto main 25c1aa1, on an AMD Radeon RX 6650 XT (RADV NAVI23, Linux) with Khronos validation (comment). They used a Mesa 26.2.5 build with two nir_opt_varyings.c fixes, because system RADV already segfaults while linking main's tessellation test pipelines. The nine groups both builds share pass 3 of 3 on main, and the same groups plus agc_driver_tessellation_geometry pass 3 of 3 with this PR, with no VUID. Cocoon (PPSA08766) stops at about 82 s on both builds. On this PR it stops at the routing guard, since its VGT_SHADER_STAGES_EN 0x2135 sets ES_EN to 2 where this path takes 1.

@klosixgaming-arch ran Cocoon on Windows with this PR merged onto current main (comment): it builds, and the title still stops after about 80 s at this PR's guard on VGT_SHADER_STAGES_EN=0x2135. That value has LS and HS on with ES_EN = 2, a routing this PR does not take (it takes ES_EN = 1, the domain shader in the ES stage); see the note to JJenkx.

  • Capture capacity (b478c40, after @oneandonlydean's review): every domain now gets 2 · level² slots per patch, where triangle patches had level², too few from level 5 on (3n²/2 triangles). A new agc_driver_tessellation_geometry case draws the level-5 triangle patch alone at VGT_HOS_MAX_TESS_LEVEL 5 and must match the tessellation path's image. With the old sizing it fails 3 of 3 (a dropped triangle). With the fix it passes 3 of 3, as does agc_driver_tessellation.

Not checked on a console.

Other open PRs touch neighbouring lines in the same files (for example #6176 in ConstantFolder.cpp, #6188 in DrawCapture.cpp, #6215 in Draw.cpp). None of them draws this path; whichever lands second needs a small rebase.

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 (feat(agc): run registered merged hull programs (binary type 3) #6679, which this built on, is merged)
  • Unblocks: fix(shader): emit nothing for a vertex s_barrier that precedes every memory access #6613, for Crimson Desert
  • GPUs tested: NVIDIA GeForce RTX 4070 Ti, driver 595.99, Ubuntu 24.04; Intel Arrow Lake iGPU, Mesa 26.1.6 ANV, Debian 13 (tests only); lavapipe, Ubuntu 24.04 (tests only); NVIDIA GeForce RTX 3070 and lavapipe on Ubuntu 26.04 (jotitadev, previous head, tests only); AMD Radeon 860M, driver 32.0.13058.8001, Windows 11 (tests, by @LeandroLP23); AMD Radeon RX 6650 XT, RADV (Mesa 26.2.5 with two nir_opt_varyings fixes), Linux (tests, by @JJenkx)
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 15, 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
PR 6804 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp L185
PR 6959 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L3246
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp L41-42, L50
PR 8212 core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp L50
PR 8395 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1268, L1274-1275, L1277, L1290
PR 9020 core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp L41-42, L50
PR 9335 core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp L41-42, L50
PR 9743 core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp L81, L259
core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp L1054
PR 9806 core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp L81, L188
PR 9892 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp L34
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1244-1264
PR 9893 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp L34
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1244-1264
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp L34
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1244-1264
PR 10678 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L3244-3246
PR 11265 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp L361
PR 11557 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L2250

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 1896 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4040 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
docs/dev/debt/shader.md
PR 5371 docs/dev/debt/shader.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5609 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/libSceAgcDriver.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/shader/recompiler/Recompiler.hpp
PR 6194 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6235 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp
docs/dev/debt/shader.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 6481 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6488 core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 6548 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6924 core/shader/recompiler/Recompiler.cpp
docs/dev/debt/shader.md
PR 6970 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 7369 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7410 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp
core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/Translation/src/InstructionTranslator.cpp
docs/dev/debt/shader.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7682 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 7684 docs/dev/debt/shader.md
PR 8004 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
docs/dev/debt/shader.md
PR 8223 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8246 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8592 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9248 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
PR 9356 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9387 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 9431 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9508 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9673 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9674 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9675 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9676 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9748 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp
docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 9842 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9852 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/shader/recompiler/Recompiler.hpp
PR 9891 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9897 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 10026 docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp
core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/CacheKey.hpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10233 docs/dev/debt/libSceAgcDriver.md
docs/dev/debt/shader.md
PR 10249 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/shader/recompiler/Optimization/src/TessellationLowering.cpp
PR 10435 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10442 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 10757 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Recompiler.hpp
PR 11207 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two failures on Linux that the PR body doesn't list.

Machine: NVIDIA GeForce RTX 3070, driver 595.99.02, Vulkan 1.4.329, plus lavapipe (Mesa 26.0.3, LLVM 21.1.8) on the same machine. Ubuntu 26.04 (kernel 7.0.0-38), i7-8700, GCC 15.2, Release, BUILD_TESTING=ON. Head 89aa76a76 merged into main 5d9dcc42d (clean merge).

1. agc_driver_prepared_graphics fails with this PR on every run.

  • RTX 3070: 5/5 fail, also with ANYPS5_NO_SHADER_CACHE=1. lavapipe: 3/3 fail. Unchanged main passes it on both.
  • It stops at AGC driver: registered program refers to an incompatible shader binary type, the check at the end of RegisteredProgram in ShaderRegistry.cpp. The last lines before it are the ReportPreparedAtUse notes for stages 5 and 7.
  • The other 15 ^agc_driver_prepared tests pass.
  • The body's run (^agc_driver_|..., 468 of 469) should include this test, so maybe that binary was stale?

2. On lavapipe, which the Linux CI job uses, the new tessellation cases fail.

  • agc_driver_tessellation: AGC graphics: subgroup capability 61 is unsupported for shader stage 2.
  • agc_driver_tessellation_geometry: the same for shader stage 4.
  • Both fail 3/3. Capability 61 is GroupNonUniform, and lavapipe's subgroupSupportedStages is only fragment, compute, task and mesh, so it refuses the merged program's tessellation-control and tessellation-evaluation modules. The RTX 3070 has subgroups in those stages, and there both tests pass 3/3.
  • With the PR's sources and main's Tessellation.cpp (the separate local/hull pair only), agc_driver_tessellation passes 3/3 on lavapipe. So the existing coverage still works there, and only the new merged case and the new test need subgroup support in tessellation stages.
  • They probably need a skip (or a non-subgroup lowering) when subgroupSupportedStages lacks those stages. A skip message that names subgroup support already passes tools/check_skips.py.

Otherwise, on the RTX 3070:

  • agc_driver_tessellation, agc_driver_tessellation_geometry and agc_driver_graphics pass 3/3.
  • The test-only build doesn't compile against main (new state fields), as the body says.
  • ctest -R "^agc|shader|recompiler" -j1 (flip, display and lds_atomics64 excluded): 2 failures. One is prepared_graphics above. The other is store_over_untracked, which also fails on main here (#6347).

GPUs tested: NVIDIA GeForce RTX 3070 (595.99.02), lavapipe (Mesa 26.0.3, LLVM 21.1.8), Ubuntu 26.04

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@jotitadev Thank you, both are real, and both are fixed in 0eeb815.

1. agc_driver_prepared_graphics. Draw decode checked the local slot (0x148) for a merged type-3 hull by asking RegisteredProgram for type 3 alone. That throws registered program refers to an incompatible shader binary type whenever an ordinary type-5 local program is registered there, which is this test's setup. It now asks for type 3 or 5 and takes the merged local and hull roles only for type 3, so a separate pair decodes as on main. The test passes 3 of 3 here now. You're right that my earlier count was wrong: my binary of that test must have been stale. I've corrected the description.

2. lavapipe. As you found, the merged program and the captured domain half need subgroup operations in tessellation-control and tessellation-evaluation shaders. The merged case of agc_driver_tessellation and the whole of agc_driver_tessellation_geometry now skip when SubgroupStages() lacks the stage, with a message that names the missing subgroup support. On this machine's lavapipe, agc_driver_tessellation passes with the merged case skipped and agc_driver_tessellation_geometry skips. The 30 ^agc_driver_(prepared|graphics$|tessellation|ngg|mesh|vertex|registered) tests pass or skip. On the RTX 4070 Ti nothing changes: the area suite is 468 of 469, with only store_over_untracked failing, as on main.

I've added your runs to the description.

AI-assisted: yes

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixes confirmed on my machine at 0eeb8159b.

Machine: NVIDIA GeForce RTX 3070, driver 595.99.02, Vulkan 1.4.329, plus lavapipe (Mesa 26.0.3, LLVM 21.1.8) on the same machine. Ubuntu 26.04, i7-8700, GCC 15.2, Release, BUILD_TESTING=ON. Head 0eeb8159b merged into main 5d9dcc42d (clean merge), -j1 --timeout 30.

  • agc_driver_prepared_graphics: passes 3/3 on the RTX 3070 and 3/3 on lavapipe (previously 5/5 and 3/3 failures).
  • lavapipe: agc_driver_tessellation passes 3/3 with the merged case skipped ("needs subgroup operations in tessellation-control shaders"); agc_driver_tessellation_geometry skips 3/3.
  • RTX 3070: both tessellation tests pass 3/3, merged case included.
  • ^agc_driver_(prepared|graphics$|tessellation|ngg|mesh|vertex|registered): 30/30 on the RTX 3070. On lavapipe, 30/30 with 6 skipped: tessellation_geometry plus the 5 mesh/NGG/clip-distance tests that also skip on main. tools/check_skips.py --job linux on that lavapipe JUnit: all 6 skips expected.

GPUs tested: NVIDIA GeForce RTX 3070 (595.99.02), lavapipe (Mesa 26.0.3, LLVM 21.1.8), Ubuntu 26.04

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@SleglJan

Copy link
Copy Markdown
Contributor

Data from 007 First Light (PPSA11386 v01.018.000), which registers its tessellation hull programs as type 3 too, on Linux 7.2.10, RTX 3080, driver 615.78.08, in a bubblewrap sandbox. Stack: main 4186f06 plus open PRs, unpublished local fixes the title needs, local logging and scripted input, and the title's two feedback-wait limits widened in its executable as described in #6172 (local only). For this run my merged-hull change (#6679) was reverted and this PR's head 89aa76a merged instead, which brings main up to 5d9dcc4.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks @SleglJan, that settles it. #6612's type-3 split requires the hull user-data address, and 007 First Light leaves it unset. Your #6679 handles that and also keeps VGPRs live across the barrier, which my split can't. So #6679 should own registered merged hulls.

I'll restack #6612 on #6679: drop its type-3 split and keep only the tessellation-to-geometry path, marked "Depends on: #6679". Then I'll check that Crimson Desert gets past its 13 s stop on #6679's merged hull. Its merged program passes 16-bit values through LDS, so that's the part to check. A trial merge conflicts in 8 files (draw decode, registry, cache key, stage IO, tessellation lowering, recompiler), so it will take a while. I'll post here when it's done.

AI-assisted: yes

@SleglJan

Copy link
Copy Markdown
Contributor

Thanks, sounds good. One heads-up for the Crimson Desert check: #6679 keeps main's 32-bit limit on LDS accesses in tessellation stages, so a LoadSharedU16/WriteSharedU16 in the merged program fails there with only 32-bit LDS reads and writes are supported in tessellation stages. Your 16-bit handling from #6612 (the half-width access with ConvertU16U32/ConvertU32U16 around it) is the right approach, but it won't carry over as is. It only accepts LocalOutput and ControlInput, while #6679 lowers every LDS access in the merged program as ControlShared. Two things to adapt: the ControlShared bounds checks need the 2-byte end for a half access, and the write's read-modify-write has to read the lane's own record. A ControlShared read is emitted as a read of another control point's record (address / shared stride), so a read at an address that isn't indexed by invocation gets control point 0's record. Happy to review it on the restacked #6612.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Restacked on #6679 at de9f0f7 (the first commit is #6679's 8f93aa0). The type-3 split is gone; draw decode takes #6679's merged hull on the tessellation-plus-geometry path too.

@SleglJan, your two points are in: a ControlShared 16-bit write reads the lane's own record for its read-modify-write, and the bounds checks use the 2-byte end. The new agc_driver_tessellation case splits one 32-bit LDS write and one 32-bit read of your MergedHullCode into b16/d16_hi pairs. It fails on #6679 alone at the write (only 32-bit LDS ... at pc 0x84) and passes 3 of 3 here. Crimson Desert with #6613 and #6178 runs to my 240 s limit with no stop, like before the restack. Details and lavapipe/Intel results are in the description.

AI-assisted: yes

@SleglJan

Copy link
Copy Markdown
Contributor

I reviewed the parts of de9f0f7 that touch #6679's merged hull: the 16-bit ControlShared lowering, draw decode on the tessellation-plus-geometry path, and the new agc_driver_tessellation case.

The lowering looks right. A half write's read now uses InvocationId × shared stride + (offset & ~3), and the emitter turns that into the same [gl_InvocationID] element that the write stores to. A ControlShared write has to stay in its own record, so no two invocations write the same word. The 2-byte end is used for both reads and writes, and odd offsets are refused. Draw decode takes the merged hull through the same branch on the new path, and #6679's refusals are unchanged.

The new test case doesn't exercise the write side, though:

  • The split write is the local part's ds_write_b32 v7 at offset 16, and the hull part never reads offset 16 of a record. It reads its own record at 0 to 15, control point 2's at +8 (the level), and control points 1 and 2 at +0.
  • The split read at 72/74 reads the level. Levels 1, 0 and 5 have a zero low half, and every control point of a patch carries the same level.
  • On an RTX 3080 the case still passes 3 of 3 with either of two changes to rewrite():
    • the read-modify-write reads address, which is control point 0's record (the case from my earlier comment);
    • it merges into 0 instead of existing.

The variant below splits the x-position write instead. It writes the high half first, so the second write's read-modify-write has to keep it. The x position has non-zero low halves, differs per control point, and is read back through the own-record ds_read_b128 and the ds_read2_b32. It passes 3 of 3 on de9f0f7 and fails 3 of 3 under both changes:

alignas(256) constexpr auto MergedHullHalfRecord = [] {
    std::array<std::uint32_t, MergedHullCode.size() + 6u> code{};
    std::size_t at = 0;
    for (std::size_t index = 0; index < MergedHullCode.size(); ++index) {
        const auto word = MergedHullCode[index];
        if (word == 0xbf88001cu) {
            code[at++] = 0xbf880020u;
        } else if (word == 0xd9340000u) {
            ++index;
            for (const auto value : {0xda840002u, 0x0000080au, 0xd87c0000u, 0x0000080au, 0xd8340004u, 0x0000090au}) code[at++] = value;
        } else if (word == 0xd8d80048u) {
            const auto operands = MergedHullCode[++index];
            for (const auto value : {0xd8f00048u, operands, 0xda9c004au, operands}) code[at++] = value;
        } else {
            code[at++] = word;
        }
    }
    return code;
}();

That turns ds_write_b64 v10, v[8:9] into ds_write_b16_d16_hi v10, v8 offset:2, ds_write_b16 v10, v8 and ds_write_b32 v10, v9 offset:4, and moves s_cbranch_execz from 28 to 32. Your read split stays. It runs with the same RequirePatches(..., MergedTriangles, MergedHullHalfRecord) call as your case, either in place of MergedHullHalfWords or next to it.

A coverage gap on the lane side: this PR lets the merged hull run with HS_W32, which Crimson Desert's 0x60212d sets, but no test compiles or draws a wave32 merged hull. Tessellation.cpp compiles at 64. TessellationGeometry.cpp draws wave32 only with the separate local/hull pair and registers its merged hull with 0x212d (wave64). Registering one with HS_W32 there would cover it. agc_driver_graphics decodes 0x60212d without compiling it.

Tested on Linux 7.2.10 (CachyOS), RTX 3080, NVIDIA 615.78.08, GCC 16.2.1, Release:

  • ctest -R 'tess|hull|prepared_graphics|shader_memory|request': 10 of 10 pass, no skips.
  • agc_driver_tessellation and agc_driver_tessellation_geometry each pass 3 of 3, and agc_driver_graphics passes.
  • Your new case on 8f93aa0, with only the Tessellation.cpp change applied, fails 3 of 3 with only 32-bit LDS reads and writes are supported in tessellation stages at pc 0x84.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks @SleglJan, you're right on both counts. 2920dce adds your MergedHullHalfRecord case next to mine, with you as co-author on that commit. Here it fails under both of your rewrite() changes, own-record read replaced by address and merge into 0, each with the level-1 patch was not drawn as one flat triangle. Restored, it passes 3 of 3.

For the lane side, TessellationGeometry.cpp now resolves the registered merged hull a second time with HS_W32, on both VGT_SHADER_STAGES_EN and the hull's registration. A throwaway check that throws when a merged hull is compiled at wave32 fails with the new registration and doesn't trigger on the previous test, so that path is now compiled. Both tests pass 3 of 3, and I credited your review and your RTX 3080 runs in the description.

AI-assisted: yes

@milescoviello

Copy link
Copy Markdown
Contributor Author

Windows 11 VM (GT 1030, 582.78, WinLibs GCC 15.2.0 r7, Release) on b6f3131b54 against main c9b7582: the 12 tessellation, pipeline journal, agc_driver_graphics and pixel LDS tests pass 3 of 3, the three complemented-ballot cases among them (11 of them exist on main, and they pass 3 of 3 there). agc_driver_tessellation_geometry skips on that GPU, which has no mesh shaders.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Rebased onto main d0e7fe7 as d175c08f94 after #9348 took journal FileFormat 10. The only conflict was that line, and this branch keeps its provisional 12 (#8365 takes 11) until it is set to main+1 at landing. The 12 tessellation, pipeline journal, agc_driver_graphics and pixel LDS tests pass 3 of 3 on the new head (Linux, RTX 4070 Ti, 615.71.09).

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Rebased onto main c035bd5 as 75a06af9a9. b76ab1f (one-fragment multisampled colour targets) grew State to 832 bytes on main, so the journal's size guard was the only conflict. It's now 904, which is 832 plus this branch's 72, and ShaderStages stays 160. The build checks both. The 12 tessellation, pipeline journal, agc_driver_graphics and pixel LDS tests pass 3 of 3 (Linux, RTX 4070 Ti, 615.71.09).

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

Rebased onto main 2dc8560 (head 1168a21b8c). #5431 gave Draw() a DrawTraceContext parameter, which conflicted with this branch's early return to drawTessellationGeometry. That function now takes the trace context and passes it to both of its inner draws. The driver's Draw.cpp include conflict keeps main's <algorithm> and this branch's <map>. The journal format stays 12 and the size guards stay 904 / 160, confirmed by the build. Linux (Ubuntu 26.04.1, GCC 15.2, RTX 4070 Ti, 615.71.09): the 12 tessellation, journal, pixel LDS and agc_driver_graphics tests pass 3 of 3.

AI-assisted: yes (Claude Code)

@JJenkx

JJenkx commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Additional AMD Linux qualification of public head 1168a21b8c2e81becb3cc2709504d136f5ed127f, rebased unchanged onto main 25c1aa1e79329e0e2957b4fdd731144dad8be724 as 001b9b155d4ddd8f1756e244d1831a4d75a11000. All six commits compare unchanged in git range-diff. Reproducible reference branch.

Physical GPU: AMD Radeon RX 6650 XT (RADV NAVI23), Linux. Required Vulkan, no AnyPS5 shader cache, disabled Mesa shader cache, and VK_LAYER_KHRONOS_validation were set for the same focused suites on both builds. The logs confirm actual validation-layer insertion and the physical AMD driver. Nine common groups passed three runs each on main (27 invocations); the same groups plus agc_driver_tessellation_geometry passed three runs each on the change (30). Zero VUID errors, missing-layer warnings, skipped or not-run tests. The suites include CPU factor-ring/lowering checks alongside the driver execution and pipeline-journal checks.

Driver qualification: these passing results use a separate X11-capable Mesa 26.2.5 build with the previously isolated two nir_src_rewrite fixes in nir_opt_varyings.c. The source patch and exact driver hash/build flags are in the archive. Vanilla main's tessellation test segfaults during pipeline linking inside system RADV; the debugger frames are retained. System RADV was unchanged. These results qualify the isolated driver, not the stock-driver path.

Cocoon PPSA08766: a fresh private startup comparison on that same X11 driver, no input and cold private caches, still reaches no game scene. Both 45-second captures are black; both runs abort at about82s:

  • main: VGT_SHADER_STAGES_EN=0x2135: combined tessellation and geometry is unsupported by the reference path
  • change: VGT_SHADER_STAGES_EN=0x2135: unsupported tessellation and geometry routing

Here ES_EN=(0x2135>>3)&3 is2, while this path's guard requires1. This is useful coverage of the PR's tested path, but does not establish a Cocoon unblock or an FPS gain. Both native runs completed cleanup; original inputs were unchanged and no native process remained. No kernel GPU reset was observed.

Conventions pass. The test source already mixes CRLF/LF on main and preserves the upstream PR's bytes; default whitespace checking flags its CRLF additions, while git -c core.whitespace=cr-at-eol diff --check passes.

Download focused logs, driver patch, source pins and reviewed results; SHA-256 d7f7a7e791309d5edf6dd0eb44a6bd5bbbbc5be1b0d81127462c972b8abb5876. The archive contains no game binaries or assets.

AI-assisted: yes (Codex).

@milescoviello

Copy link
Copy Markdown
Contributor Author

@JJenkx thank you for the RX 6650 XT run, and for pinning the RADV linking crash on main to the two nir_opt_varyings.c fixes. I've added your run to Tested and the GPU to the checklist.

On Cocoon: this path takes ES_EN = 1, where the ES stage runs the domain shader after the hull. Cocoon's 0x2135 has LS and HS on with ES_EN = 2, the encoding that on GFX10 runs a vertex shader as ES. I have no capture of that combination, so the guard refuses it instead of guessing. If you can share which programs it binds (the ES program's address, and whether it reads the tessellation coordinates or vertex inputs), I'll look at handling it in a follow-up, with a test built from that draw.

AI-assisted: yes (Claude Code)

milescoviello and others added 6 commits October 25, 2026 08:32
Crimson Desert (PPSA25286) draws with VGT_SHADER_STAGES_EN 0x60212d:
local, hull, domain and NGG geometry stages, the hull halves merged in one
registered type-3 program, the domain and geometry halves in one type-2
program. The domain half runs as the tessellation evaluation stage and
captures each vertex's ESGS item, and a mesh shader runs the geometry half
on the captured items.

Registered type-3 merged hulls come from boykopovar#6679; this adds 16-bit LDS
reads and writes in tessellation stages, which Crimson Desert's merged
hull uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… hull

The first 16-bit case split a write that the hull part never reads back.
The new case splits the x position's write into its high and low halves,
so the second half's read-modify-write must keep the first, and reads it
back through the control point's own record. The registered merged hull
is also resolved with HS_W32, which Crimson Desert sets.

Co-Authored-By: Jan Šlégl <slegl.jan@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pipeline journal guards State and ShaderStages, which this branch
extends. The recipe codec and canonical state now carry the hull wave
size, the geometry stage after tessellation, the merged hull flag
(TessellationConfiguration keeps its size, so its guard can't see it)
and rasterizer discard, and the journal format becomes 12 (main took 5
for the recipe codec change of boykopovar#5656, 6 for the polygon modes of boykopovar#7869,
7 for the truncated coverage of boykopovar#8115, 8 for the point lists of boykopovar#6801
9 for the 4_4_4_4 UNORM target merges of boykopovar#8564 and 10 for the colour
logic operation of boykopovar#9348; boykopovar#8365 uses 11, and the number is set to main+1
when this lands). A draw's tessellation capture validates against that draw's
capture buffers, which a replay doesn't have, so it isn't journaled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entry was over the 1000-character limit the conventions check
warns about. Same content, tighter wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A triangle patch at level n tessellates into about 3n²/2 triangles,
more than the (n + 1)² slots its capture had from level 5 on, and the
capture shader drops the triangles past its capacity. Every domain now
gets 2(n + 1)² slots per patch, which covers both triangles and quads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sn't implied

ImpliesNot returned at a negated condition, so a predicate !!a against
the condition !a, or !!a in either order of a conjunction, never reached
the negated-predicate and conjunction cases, which prove it. Lane-bit
lowering makes such a predicate from a complemented own-lane ballot of
!a, and the address evaluator then read both arms of a select on !a
instead of the one the predicate implies. Each case now falls through
when it does not prove the negation. Three hull reads under that
complemented ballot bit, alone and conjoined on either side, check it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milescoviello

Copy link
Copy Markdown
Contributor Author

Rebased onto main 6f58a04 after #9597. The new head is b6da343d20, the same six commits. The only conflict was the size guard in PipelineJournal.cpp: #9597 grew State to 840 (chainDccAddress), and this PR adds 72, so the guard now reads 912; ShaderStages stays 160. The build's static_assert confirms 912. FileFormat stays at this PR's provisional 12 (main is at 10, and #8365 holds 11), to be set to main+1 when it lands.

  • Linux (Ubuntu 26.04.1, GCC 15.2, Release): the 12 tessellation, journal, pixel LDS and agc_driver_graphics tests pass 3 of 3.
  • Windows 11 VM (GT 1030, 582.78, WinLibs GCC 15.2.0):
    • On the merge base, 11 of 11 pass 3 of 3; agc_driver_tessellation_geometry doesn't exist there.
    • On this head, 12 of 12 pass 3 of 3; agc_driver_tessellation_geometry skips, since the GT 1030 has no mesh shaders.
  • tools/check_conventions.py passes.

AI-assisted: yes (Claude Code)

@klosixgaming-arch

Copy link
Copy Markdown
Contributor

Test run (not a code review). I merged this PR onto current main, built it on Windows, and ran Cocoon for about 80 s before it aborted.

It still aborts with:
AGC graphics: VGT_SHADER_STAGES_EN=0x2135: unsupported tessellation and geometry routing (std::runtime_error, then SIGABRT).

So this PR does not get Cocoon past that point in my run. Builds fine otherwise.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@klosixgaming-arch thanks for the Windows run. That abort is this PR's guard, and the value tells why: 0x2135 has LS and HS on with ES_EN = 2, which on GFX10 runs a vertex shader in the ES stage. This PR handles tessellation feeding a geometry stage with ES_EN = 1, where the ES stage runs the domain shader. So Cocoon uses a routing the PR doesn't take, and the guard refuses it rather than guessing (earlier note). I've added your run to Tested.

Handling ES_EN = 2 needs to know what that draw's ES program reads: tessellation coordinates or vertex inputs. I don't have Cocoon, so that would be a follow-up built from a capture of that draw.

AI-assisted: yes (Claude Code)

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@milescoviello Thanks, the two-pass draw and the wave32 hull admission read right to me. One case still looks open: Pipeline::Begin now opens a pass without attachments for a pipeline with rasterization disabled, but DrawRenderPassKey (Draw.cpp:226) doesn't include that rasterization-disabled flag, and the capture state keeps the draw's targets. So when an ordinary draw to the same targets is already in the open pass, StartDrawPass continues it, and the capture pipeline draws inside a rendering with attachments, which is the colorAttachmentCount-06179 case you fixed in Begin.

Could you either add the rasterization-disabled state to DrawRenderPassKey or force a new pass for the capture draw, and add a test that issues an ordinary draw and then a tessellation-geometry draw into the same target in one batch, run under the validation layer? The current TessellationGeometry.cpp cases issue each draw in its own pass, so they can't hit this.

…pass

The capture pass of a tessellation-geometry draw runs a pipeline with
rasterizer discard, which Pipeline::Begin opens without attachments. Its
render-pass key matched an ordinary draw to the same targets, so when one
was already in the open pass, StartDrawPass continued it and the capture
pipeline drew inside a rendering with attachments
(VUID-vkCmdDraw-colorAttachmentCount-06179). DrawRenderPassKey now mixes
in rasterizer discard, so the capture draw starts its own pass.

agc_driver_tessellation_geometry gains a case that records an ordinary
draw and then a tessellation-geometry draw into the same resident target
in one batch and compares the image with the ordinary draw alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milescoviello

Copy link
Copy Markdown
Contributor Author

@oneandonlydean thanks, you're right: the capture pass's key matched an ordinary draw's, so it could continue that draw's pass with attachments. I've added rasterizer discard to DrawRenderPassKey (004f498, not pushed yet). The capture draw now always starts its own pass, on the plain and the prepared paths alike.

agc_driver_tessellation_geometry gains the case you describe. On a resident tiled target, an ordinary tessellation draw and a tessellation-geometry draw are recorded into the same target with no wait between them, so they share one batch. The image is compared with the ordinary draw alone.

The Khronos comparison is queued now: that test on the current head without the key change, against the fix, over the 12 tessellation and journal tests. Then the tests 3 times on Linux and my Windows VM. I'll push and post the VUID counts from both sides when they're in.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor Author

@oneandonlydean pushed 004f498. DrawRenderPassKey now mixes in rasterizer discard, and agc_driver_tessellation_geometry has the batched case. It records an ordinary tessellation draw, then a tessellation-geometry draw into the same resident target, with no wait between them, and compares the image with the ordinary draw alone.

Under the Khronos layer 1.4.363, over the 12 tessellation and journal tests:

  • New case, without the key change (a371244bf8): VUID-vkCmdDrawIndexed-colorAttachmentCount-06179 x2. This is the capture pipeline drawing inside the ordinary draw's rendering.
  • With the change (004f498): no VUID- messages.

All 12 tests pass on both, and 3 of 3 on Linux with the change. On my Windows VM (GT 1030), the merge base passes 11 of 11 3 of 3 and this head 12 of 12 3 of 3. That GPU has no mesh shaders, so the tessellation-geometry case skips there. I've added this to Tested.

AI-assisted: yes (Claude Code)

@klosixgaming-arch

Copy link
Copy Markdown
Contributor

@milescoviello Here are the measured details from the Cocoon (PPSA08766) abort on current main:

1. Root Cause & Register State

The 0x2135 (VGT_SHADER_STAGES_EN) state is produced by AGC's sceAgcCreatePrimState bitwise-ORing gs->specials->vgt_shader_stages_en with hs->specials->vgt_shader_stages_en:

  • hs (Hull stage): 0x21e24f600 (type 7 HsBack, or in-place fused Hs type 3).
    • Specials: vgt_shader_stages_en = 0x0005 (LS_EN = 1, HS_EN = 1).
    • Context: 0x2db = 0x00040041 (VGT_TF_PARAM: Triangle domain, Integer partitioning, CW output topology), 0x2d6 = 0x0000c355 (VGT_LS_HS_CONFIG), 0x286 = 41700000 (VGT_HOS_MAX_TESS_LEVEL = 15.0), 0x287 = 3f800000 (VGT_HOS_MIN_TESS_LEVEL = 1.0).
    • Shader regs: 0x108 = 0x021e24f6 (SPI_SHADER_PGM_LO_HS).
  • gs (Geometry stage): 0x13d6a81d500 (type 2 Gs), dynamically fused by Unity calling sceAgcFuseShaderHalves on GsFront (type 4, at 0x21e220100) and GsBack (type 6, at 0x21e24e300).
    • Specials: vgt_shader_stages_en = 0x2130 (ES_EN = 2, GS_EN = 1, DYNAMIC_HS = 0x100, PRIMGEN_EN = 0x2000).
    • Context: 0x2ab = 0x00000004 (VGT_ESGS_RING_ITEMSIZE = 4), 0x2ce = 0x00000003, 0x1ff = 0x000000c0 (VGT_GS_MAX_VERT_OUT = 192), 0x2d3 = 0x00000001 (VGT_GS_MODE).
    • Shader regs: 0x0c8 = 0x3d6a81d5 (SPI_SHADER_PGM_LO_ES, the GsFront code), 0x088 = 0x3d6a81d7 (SPI_SHADER_PGM_LO_GS, the GsBack code), 0x0ca = 0x03000006 (SPI_SHADER_USER_DATA_ES_0).
  • Resulting primitive state: 0x2130 | 0x0005 = 0x2135.

2. Guard Mismatch

In core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp:

validate((value & ~0x0067ed2du) == 0 && (value & 0x02002000u) == 0x2000u && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation and geometry routing");

This requires ((value >> 3u) & 3u) == 1u (ES_EN = 1, ES_STAGE_DS) and bitmask 0x0067ed2d (bit 4 clear). In Cocoon, value = 0x2135 has ((value >> 3u) & 3u) == 2u (ES_EN = 2, ES_STAGE_REAL, bit 4 set), so validation throws at pipeline creation (deferred at ~6.0 s) and again at draw time (~146 s), triggering 0xC0000409.

3. What the ES Program Reads

  • The bound ES program address (0x0c8) is 0x21e220100 (GsFront, type 4).
  • In Sony AGC, GsFront fused into Gs (type 2) with ES_EN = 2 is a standard vertex shader half feeding the geometry shader half (GsBack) via the ES-GS ring. It allocates user SGPRs via SPI_SHADER_USER_DATA_ES_0 (0xca = 0x03000006) and is compiled by Unity with vertex input semantics and vertex buffers, rather than reading tessellation domain coordinates (gl_TessCoord).
  • Hardware on RDNA accepts 0x2135 because with both HS_EN=1 and GS_EN=1 set, the stage between HS and GS operates as domain evaluation feeding geometry, but Sony AGC's standalone fusion preserves ES_EN = 2 from the geometry half.

AI-assisted: yes. Written with AI assistance and reviewed by me.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@klosixgaming-arch thanks, that pins it down: the hull's specials give LS/HS (0x0005), the fused geometry stage's give ES_EN = 2 with GS_EN (0x2130), and sceAgcCreatePrimState ORs them into 0x2135.

What the guard can't tell is how the console runs that ES stage with tessellation on. ES_EN = 2 normally means the ES stage gets vertex-shader inputs (vertex and instance ids), while the tessellation path here runs it as a domain stage with tessellation coordinates. With HS_EN set, the hardware may treat it as the domain stage regardless, but that isn't measured.

An experiment you can run: exp/tess-geometry-es-real (04a6880bcb, on main b7105f7). It only lets ES_EN = 2 through the tessellation-and-geometry guard and runs the stage exactly as it runs ES_EN = 1, as the domain stage. agc_driver_graphics, agc_driver_tessellation and agc_driver_tessellation_geometry still pass on Linux. It's not a PR. In Cocoon, three outcomes would tell us different things:

  • The scene renders correctly: the ES half is a domain shader, and the console treats ES_EN = 2 as the domain stage when HS is on. That's a small PR.
  • It renders wrong geometry: spikes, missing or collapsed meshes where that draw is. Then the ES half really reads vertex inputs, and it needs a different path.
  • It stops on a new message: please post it.

A screenshot of the area around the draw that used to abort would help either way.

AI-assisted: yes (Claude Code)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants