Skip to content

fix(recompiler): give wave64 vertex programs on 32-lane hosts a 32-lane s3 - #3546

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
AlxcBTW:fix/vertex-narrow-wave-entry-2
Oct 11, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
AlxcBTW:fix/vertex-narrow-wave-entry-2

Conversation

@AlxcBTW

@AlxcBTW AlxcBTW commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes #2783. A wave64 NGG vertex program on a 32-lane host subgroup stops on current main:

ShaderRecompiler::Recompile: SPIR-V emission failed: hash=0x142147780608 stage=1 opcode=ReadLane reason=v_readlane_b32 of lane 63 is outside the 32-lane host subgroup that runs this wave64 program at one lane per invocation

Titles that stop there on NVIDIA, and get past it with this change:

  • Minecraft Dungeons II (PPSA16064): confirmed by @MisterbALLIN's log (analysis).
  • High on Life (PPSA15388): confirmed by @Tamalero (comment).
  • Palworld (PPSA23126): confirmed by @queeeef on an RTX 2080 Ti, two vertex programs at the character creation screen (comment).
  • Taxi Chaos 2 (PPSA30279) runs the same Unreal culling program, but hasn't been run with this change.

The program keys a full-wave OR on lane < s3's primitive count and reads lanes 31 and 63. At one guest lane per invocation, lanes 32-63 have no invocation on a 32-lane host, but the vertex entry set s3 = 64 | 64 << 8.

  • s3: for a wave64 vertex program on exactly 32 host lanes, emitEntryPrologue sets s3 = 32 | 32 << 8, a wave the console can launch. The host subgroup width reaches ShaderVertexInputInfo as it already reaches the compute and mesh inputs. Every cache key already carries HostSubgroupSize. Wave32 programs and 64-lane hosts are unchanged.

  • The read: with the narrowed s3, the keys are 0 in lanes 32-63. When the strict rules don't match on a 32-lane host, MatchHalfWaveScan also accepts:

    • s_mov_b64 exec, -1;
    • bound_ctrl:1 row_shr steps whose fill is the reduction's identity;
    • keys proven 0 in lanes 32-63.

    Lane 63 then reads the identity, and lane 31 the subgroup reduction. Without subgroup arithmetic, lane 31 stays a shuffle.

  • TechnicalDebt: the two entries (vertex entry s3, lanes outside the host subgroup) describe this.

Related: #2261 (open) would route registered NGG programs to the mesh path, which would also cover this. #1825 only routes unregistered programs that use LDS or s_barrier, and this program is registered.

Tested

Windows 11 (26200), WinLibs GCC 15.2.0, Release, RTX 5070 Laptop (NVIDIA 616.92, 32-lane subgroups), on main 0f863c1:

  • agc_shader_memory (verifyNarrowVertexWave): fails on main with the lane-63 error and passes with this branch. It also checks the cases that must still throw: a bound of 33, a count from user data, the vertex index instead of the lane, and a step into another register without bound_ctrl:1.
  • New execution test agc_driver_ngg_vertex_wave_reduction: fails on main with the lane-63 error and passes with this branch. It skips on other subgroup widths.
  • GPU suites: agc_driver* and agc_shader* on NVIDIA: 306/306.
  • Non-GPU suite: 527/528. The one failure, windows_exceptions, fails the same way on main (CI run 38133926187), along with three Linux tests there.
  • Conventions: tools/check_conventions.py --base origin/main passes.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

🤖 Generated with Claude Code

AlxcBTW and others added 3 commits October 11, 2026 13:24
…ne s3

A wave64 vertex program runs at one guest lane per invocation, so on a
32-lane host subgroup its lanes 32-63 have no invocation. The vertex
entry still set s3 = 64 | 64 << 8, telling the program that its wave
holds 64 vertices and 64 primitives. An NGG culling program then keys a
full-wave OR on lane < s3's primitive count, lanes 32-63 hold real cull
results, and its v_readlane_b32 of lane 63 cannot be built (boykopovar#2783).

The entry now sets s3 = 32 | 32 << 8 for a wave64 program on a 32-lane
host, the wave the host runs; the console launches waves with fewer
vertices and primitives. The host subgroup width reaches the vertex
input the way it reaches the compute and mesh inputs
(BuildShaderStageInputInfo from HostSubgroupSize), and the source,
prepared-shader and disk cache keys already carry it for every stage.
Programs that do not read s3 are unchanged; wave32 programs and other
host widths keep the full wave.

The keys are then 0 in lanes 32-63, so the read is sound. On a 32-lane
host, when the existing half-wave scan rules fail, the scan may also:
- run under s_mov_b64 exec, -1 (EXEC constant true);
- use bound_ctrl:1 row_shr steps when 0, the fill, is the identity (OR,
  add, unsigned max), also into another register, whose old value never
  shows since such a step writes every lane;
- take keys shown to be 0 in lanes 32-63: built from 0 through selects
  under a lane id or v_mbcnt of -1 below at most 32, v_cmp_ne_u32 against
  0, ORs, DPP and permlanes, in stages whose entry EXEC is every lane.
Lane 63 is then the identity and lane 31 the subgroup reduction of the
host's invocations. A shuffle from lane 31 is not enough: an RTX 5070
runs the vertex warps of a non-indexed triangle list with 30 of their 32
invocations. A v_readlane_b32 outside the host subgroup of another value
that is 0 in lanes 32-63 reads 0. Only reads that used to throw change,
besides lane 31 of such scans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wave64 vertex program with the culling scan of boykopovar#2783 (lane < s3's
primitive count, row_shr bound_ctrl:1 OR scan, v_permlanex16_b32,
v_readlane_b32 of lanes 31 and 63) compares the wave OR with the ballot
of its keys and collapses its triangle when they differ. On a 32-lane
device every pixel of the grid must be covered.

Before the s3 change the program does not build on 32 lanes; with a
shuffle from lane 31 instead of the subgroup reduction no pixel is
covered, as the device leaves lanes of each vertex warp idle. Other
subgroup widths skip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The half-wave scan rules for 32-lane hosts made lane 31 of a matched
scan a subgroup reduction and threw on a device without subgroup
arithmetic, where the read used to build as a shuffle of the scan. Such
a device now keeps the shuffle for lane 31; lane 63 still reads the
identity, which needs no arithmetic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 11, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the bound_ctrl:1 row_shr scan and the culling program from the test verbatim on the HW oracle (gfx1035 wave64, s3 counts 0/1/31/32/33/64: lane 63 reads 0 whenever the count is ≤ 32, and the bound_ctrl:1 fill is the identity for OR/add/umax; 0 mismatches). agc_shader_memory and agc_driver_ngg_vertex_wave_reduction (forced to a 32-lane target on RADV with RADV_PERFTEST=gewave32) pass here and fail on main with the lane-63 error. Thanks @Tamalero and @MisterbALLIN for the runs on High on Life and Minecraft Dungeons II; merging.

@adriadam10
adriadam10 merged commit e44b7cf into boykopovar:main Oct 11, 2026
16 checks passed
oneandonlydean added a commit that referenced this pull request Oct 12, 2026
…up fixture

The fixture read lane 63 of v0, which the program never writes. Since #3546 a wave64 vertex program on a narrower host subgroup builds that read as 0, so the synchronous registration the test expects to fail succeeds. v5 holds the vertex index, which is not 0 in lanes 32-63, so the read still throws.
@AlxcBTW
AlxcBTW deleted the fix/vertex-narrow-wave-entry-2 branch October 12, 2026 18:56
Tamalero added a commit to Tamalero/AnyPS5 that referenced this pull request Oct 16, 2026
…n prepare

The registered-workgroup control registers a non-NGG copy of its fixture
in synchronous mode and expected the lane-63 refusal to prove that
preparation ran at registration. Since boykopovar#3546 a wave64 vertex program's
lane-63 read builds on a 32-lane host, so the refusal never comes and
agc_driver_prepared_shaders_registered_workgroup_sync fails on main on
every host narrower than 64 lanes.

The control now registers an LDS write followed by s_barrier, which a
vertex program cannot build on any host width, so it also runs on
64-lane hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

NVIDIA: wave64 NGG culling vertex program reads lane 63 of a full-wave OR reduction (Taxi Chaos 2, PPSA30279)

2 participants