Skip to content

fix(agc): route registered NGG workgroups through mesh - #2261

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
Sanjaysrinivas:feat/registered-ngg-workgroup
Oct 11, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
Sanjaysrinivas:feat/registered-ngg-workgroup

Conversation

@Sanjaysrinivas

@Sanjaysrinivas Sanjaysrinivas commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Registered type-2 NGG vertex programs using LDS or s_barrier need the existing mesh path's workgroup storage and wave64 cross-lane operations. Preparing them as ordinary vertex shaders can reject a lane-63 read on a 32-lane host subgroup.

Defer their registration preparation until routing and primitive state are available, and share the workgroup decision between static ABI resolution and draw decoding. Reserved address bits and misaligned entries still throw. Workgroup use follows Mesa's NGG lowering; console routing remains unmeasured and is recorded in TechnicalDebt.

The branch contains one commit over main a11c8f7f70a0ed4e71504a89de69e8cdae687817, changing seven files. #1732/#1825 are already merged; their seven prerequisite commits are absent from this diff. Current main and existing PRs were checked for an equivalent registered-program extension. Related #3546 changes vertex-wave lowering; it does not implement this registration deferral or registered mesh routing. Depends on: none.

Two registration-only CTests call the actual registration export in asynchronous and synchronous modes, with no mesh-shading or subgroup-size gate. The same valid LDS/lane-63 fixture registers on lavapipe's eight-lane subgroup. A missing-user-data variant also registers with NGG routing; changing only the routing to non-NGG requires the normal eager vertex-ABI validation to throw. This control checks that deferral is limited to NGG on any subgroup width. On a synchronous host narrower than 64 lanes, the valid non-NGG header additionally must reach preparation and produce the lane-63 error. These checks need Vulkan initialization, available through lavapipe, but no mesh compilation or hardware GPU.

The separate wave64 mesh preparation fixture requires mesh shading and a supported 32- or 64-lane host subgroup. It reports when that subcase cannot run and executes before the failure-capture check. CPU routing checks remain unconditional and use main's registry with multiple shader headers per address.

Tested

  • Linux/WSL, Ubuntu 24.04, GCC 13.3, Release, Mesa lavapipe/llvmpipe, subgroup 8: 12/12 focused CTests passed (10.52 s), including both new registration tests. Zero CTest skips. The separate mesh preparation subcase does not run on this device; registration and CPU routing do run. WSL's unavailable async write protection limits the graphics fixture's internal write-watch mirror subcases.
  • Production mutation check: removed only the NGG registration-deferral line from ShaderRegistry.cpp. Both new registration tests failed on lavapipe: asynchronous mode reached the missing-user-data validation, and synchronous mode hit the eight-lane/lane-63 error. Restored the production source byte for byte, rebuilt, and obtained the 12/12 result above. This directly guards the behavior requested in review.
  • Windows, MinGW GCC 15.2, Release, RTX 4070 Laptop GPU, subgroup 32, Khronos core validation enabled and Vulkan required: 11/15 selected CTests passed (28.70 s). Both new registration tests pass. CPU routing, graphics, unregistered graphics, direct mesh rendering, deferred scenarios, conventions and test registration pass. The registered wave64 mesh preparation fixture passes in all five regular/debug invocations. No Vulkan validation errors or CTest skips.
  • The same four Windows prepared-shader scenarios (agc_driver_prepared_shaders, _indirect, _uncached, _debug) report "shader failure capture was incomplete". The previous same-environment baseline rebuild of main's registry/draw sources and original fixture reproduces all four failures. The Windows expected-failure scenario also stops at that capture error before its intended injection, so its CTest pass is not injection coverage; Linux covers the intended injection.
  • Both native builds, tools/check_conventions.py --base origin/main and git diff --check origin/main...HEAD pass. The original subgroup-gate mutation also reproduced the eight-lane/lane-63 error. macOS was not tested locally.

Automatic CI on the preceding head passed; full hosted Linux/Windows library jobs require a collaborator's manual Build dispatch. A new push restarts automatic checks and requires a new dispatch for full hosted builds.

These fixtures contain synthetic code. The private game source, installed runtime and title assets were preserved; no gameplay or FPS claim is made.

Checklist

  • Based on current main; no other open PR implements the same registered-program extension
  • One topic per PR; follow-ups go in a new PR
  • No new comments in code
  • Unimplemented paths throw; no silent stubs added
  • No notes, investigation files or images added to the repository
  • No new third-party code
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@Sanjaysrinivas
Sanjaysrinivas marked this pull request as ready for review October 9, 2026 14:09
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed labels Oct 10, 2026
@Mouhand-Kaddo

Copy link
Copy Markdown
Contributor

Additional platform validation on standalone PR head 029b2e41508bf5c23bd9a9497a0fcb243ea59e62:

Linux: failed tests — Arch Linux in WSL2 (kernel 6.6.87.2), GCC 16.2.1 20260810; CMake 4.3.1 / Ninja.

Release / BUILD_TESTING=ON: built the selected test targets; 7 CTest cases, 4 failures, 0 CTest skips.

Tests: agc_driver, agc_driver_graphics, agc_driver_prepared_shaders, agc_driver_prepared_shaders_indirect, agc_driver_prepared_shaders_uncached, agc_driver_prepared_shaders_debug, agc_driver_prepared_shaders_debug_failure.

Reproduce after building the test targets: ctest --test-dir <build> --verbose --no-tests=error --timeout 30 -R "^agc_driver($|_graphics$|_prepared_shaders)".

Vulkan: Mesa lavapipe 26.2.4 software Vulkan; no native Linux GPU/retail-title validation.

Internal limitation: write-watched guest-heap mirror subcases returned early because WSL userfaultfd async write protection is unavailable. The other graphics assertions ran; zero CTest skips does not include this internal early return.

Failed cases: agc_driver_prepared_shaders, agc_driver_prepared_shaders_indirect, agc_driver_prepared_shaders_uncached, agc_driver_prepared_shaders_debug.

The new RegisteredVertexWorkgroup case fails while preparing its wave64 shader: v_readlane_b32 of lane 63 is outside the 8-lane host subgroup. Lavapipe advertises mesh shaders but has subgroup size 8, and this test currently checks mesh support only. The driver, graphics and injected-failure cases passed; this is not a clean Linux pass.

Integration limitation: a local merge against main 948a526c2 has source conflicts; these results validate the standalone PR head, not a resolved merge.

@Sanjaysrinivas
Sanjaysrinivas force-pushed the feat/registered-ngg-workgroup branch from 029b2e4 to 5314908 Compare October 10, 2026 11:12

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks. Two things before this can land:

  • The branch still carries the seven prerequisite commits of #1732/#1825, which are now merged on main under different SHAs, and it conflicts with main. Please rebase so only the registered-NGG commits remain, and rerun the focused tests on the result.
  • tests/execution/PreparedShaders.cpp:500 (RegisteredVertexWorkgroup) only checks for MeshShadingEXT. On Mesa lavapipe (subgroup size 8) the wave64 shader fails to prepare with v_readlane_b32 of lane 63 is outside the 8-lane host subgroup, which fails agc_driver_prepared_shaders, _indirect, _uncached and _debug on Linux (reported by @Mouhand-Kaddo). Gate the case on the host subgroup size the mesh path actually supports (and keep a CPU-side check for what is independent of the device), or make it pass on an 8-lane subgroup.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rebase and the subgroup gate; the branch is now a single commit over main.

  • ShaderRegistry.cpp:625 is the actual behaviour change (a registered type-2 NGG vertex program using LDS/s_barrier is no longer prepared at registration), but nothing that runs on Linux CI pins it. I deleted that line locally and agc_driver, agc_driver_prepared_shaders* (all 8) still pass. RegisteredVertexWorkgroup (PreparedShaders.cpp:528) only runs with mesh shading and a 32/64-lane subgroup, and prints "not run" on lavapipe (subgroup 8).
  • Please add a test that does not need that device: e.g. register the same header/code as RegisteredVertexWorkgroup and assert the preparation is left pending (as DeferredRegistration does) instead of prepared, and that it is prepared as before with a non-NGG routing value. It should fail with the line removed.
  • Not verified by us: the wave64 GPU subcase and console routing (Windows RTX 4070 run is yours only). Another run on a 32-lane NVIDIA device would help, but the test above is what lets CI guard the change.

VertexWorkgroupRequired and its tests (Submit.cpp:632) look fine: they fail to build without the change and pin the reserved-bit/alignment cases.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 11, 2026
Defer registered NGG workgroup preparation until routing is known. Share the routing decision with static ABI resolution and draws. Gate the wave64 fixture on supported mesh subgroups; retain device-independent routing coverage.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked on the merged tree: libSceAgcDriver build and the agc_driver and agc_driver_prepared_shaders* tests pass, including both new registered_workgroup tests. With only the ShaderRegistry.cpp registration-deferral line removed, both new tests fail, so they pin the behaviour. TechnicalDebt entry is in place. Merging once CI is green.

@adriadam10
adriadam10 merged commit 9865c57 into boykopovar:main Oct 11, 2026
14 of 15 checks passed
@Amoenus

Amoenus commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Post-merge validation on pinned main a672688e94f3ba3503c58fc70871f37c4f198a28, in a local diagnostic integration: Windows 11, RTX 5080 / host subgroup 32, MinGW GCC 15.2 UCRT, Release / BUILD_TESTING=ON, Vulkan required, shader cache off.

Confirmed fixture problem. The unchanged agc_driver_prepared_shaders_registered_workgroup_sync fails in the full suite and 5/5 isolated runs because its final expected lane-63 rejection does not happen. An external diagnostic copy names that exact expectation; an independently reconstructed vertex request with the fixture's metadata prepares successfully (311 SPIR-V words). Its compiler IR contains:

WriteSharedU32 0x00000000, 0x00000000, true
ReadLane 0x00000000, 0x0000003f

The fixture reads lane 63 of constant zero. Current emitter now accepts a value proven zero in upper lanes, so this fixture no longer exercises the intended rejection.

Suggested test correction, validated locally in 0e79f49d9 (not pushed): increase its code array from eight to nine words and insert 0x7e000281u (v_mov_b32 v0, 1) immediately before v_readlane_b32:

alignas(256) static constexpr std::array<std::uint32_t, 9> code{
    0xd8340000u, 0u, 0x7e000281u, 0xd7600000u, 0x00017f00u, 0x7e020200u, 0xf80008cfu, 0x04030201u, 0xbf810000u
};

This preserves the LDS/workgroup marker and positive mesh-preparation path while keeping the unimplemented ordinary vertex lane read observable. Both registered-workgroup modes pass five times in each of a parallel and sequential group run. Build, libs, registration and convention checks pass; all 122 patched + 122 unpatched module hashes remain unchanged. This changes only the test, not shader semantics.

Reproduce with ctest --test-dir build-review -R "^agc_driver_prepared_shaders_registered_workgroup(_sync)?$" --repeat until-fail:5 --parallel 1 --timeout 30 -V. CTest supplies APS5_SYNC_SHADER_PREPARE=1 for the synchronous case.

Scope: this is not a build of the unchanged standalone #2261 head. The broader suite has separate intermittent 0xC0000374 heap-corruption failures: initially deferred preparation, subsequently debug preparation in a parallel run and undecodable preparation in a sequential run. These initially prevented a clean full-validation claim. The three production changes from existing #4029 are now applied locally: independent validation records100/100 selected heap-case runs and a full suite of863pass/0fail/5skip. Those crashes are not attributed to this PR, and finite clean campaigns do not prove every rare crash absent. No push or new PR.

@Tamalero

Copy link
Copy Markdown
Contributor

Confirmed on a real title: High on Life (PPSA15388 v01.120.000, Unreal Engine 4). Its NGG culling vertex programs are registered: LDS compaction plus v_readlane_b32 of lanes 31 and 63, and no s_barrier. Without this PR they ran on the vertex path, where their LDS becomes a per-invocation Function array, so each lane reads slots only other lanes wrote. The GPU stopped making progress right after the first level loaded: Xid 109 CTX SWITCH TIMEOUT, then VK_ERROR_DEVICE_LOST, in every run (10+).

With #1732, #1825 and this PR cherry-picked, the hang is gone. With #3738 as well, the title gets into the level: its audio, an opening cutscene, and in-engine rendering.

One note for anyone running the suite: on main, agc_driver_prepared_shaders_registered_workgroup_sync fails on 32-lane hosts, because #3546 made the lane-63 read its non-NGG control relied on build. #4142 swaps the control for a program no vertex stage can build.

How the hang was located

A local diagnostic put a VK_NV_device_diagnostic_checkpoints marker before every draw, dispatch and internal command, and read the markers back after device loss. In every run, the last completed and first unfinished markers were draws using one of two culling-program variants: for example, 1710-, 10404- and 88206-index depth-only draws into a 256x256 shadow map. Skipping every draw of one variant by its code moved the hang to the other variant's draws. The pixel shaders involved are a 25-dword alpha test with no loops.

Setup: CachyOS (Linux 7.2), RTX 4090 (NVIDIA 615.78, 32-lane subgroups), GCC, Release. The base is main f5c0fe00 plus local integration patches and ports of #423/#1077, #425, #454, #2904, #3202, #3546 and #3279. The 10 commits of #1732/#1825/#2261 needed two small conflict resolutions (one TechnicalDebt line, and the deferral line next to an older return nullptr in ShaderRegistry.cpp).

Tested and written by Claude (AI assistant) on a contributor's machine.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants