This repository was archived by the owner on Oct 28, 2026. It is now read-only.
Repository navigation
Conversation
…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>
Owner
Author
|
Rebased onto current main and opened upstream as boykopovar#3546. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes boykopovar#2783, option A from the issue: on a 32-lane host subgroup, the vertex entry gives a wave64 program the wave the host actually runs.
A wave64 vertex program runs at one guest lane per invocation, so on NVIDIA (and other 32-lane hosts) lanes 32-63 have no invocation. The vertex entry still set
s3 = 64 | 64 << 8, a wave of 64 vertices and 64 primitives. The stock Unreal NGG culling program in boykopovar#2783 keys a full-wave OR onlane < s3's primitive countand reads lanes 31 and 63, so lanes 32-63 held real cull results and the read of lane 63 could not be built. Taxi Chaos 2 (PPSA30279) and Minecraft Dungeons II (PPSA16064, boykopovar#2811) stop on it.s3. For a wave64 program on a 32-lane host,emitEntryProloguenow setss3 = 32 | 32 << 8. The console launches waves with fewer vertices and primitives than the wave size, so this is a wave the program can see on hardware. The host subgroup width reaches the vertex input the same way it reaches the compute and mesh inputs:BuildShaderStageInputInfogetsHostSubgroupSize(request)and stores it inShaderVertexInputInfo, as it does inShaderWorkgroupInputInfo. The source, prepared-shader and disk cache keys already carryHostSubgroupSize(request)for every stage (Recompiler.cppgetSource,PrepareShader,BuildPreparedShaderKey,ShaderDiskCache::BuildKey), so no key changes. The condition is the one the compute and mesh paths use for their two-lane layout: wave64 on exactly 32 lanes. Wave32 programs, 64-lane hosts and narrower hosts keepwaveSize | waveSize << 8. Programs that never reads3produce the same IR, since the write is dead.The read. With the narrowed
s3, the keys are 0 in lanes 32-63, which makes the read sound. On a 32-lane host, when the existing half-wave scan rules do not match,MatchHalfWaveScanalso accepts:s_mov_b64 exec, -1(a constant true EXEC);bound_ctrl:1row_shrsteps when 0, the fill, is the reduction's identity (OR, add, unsigned max), also when a step writes another register, since such a step writes every lane and its old value never shows;UpperLaneWalk): built from 0 through selects under a lane index (lane id, orv_mbcntof -1) below at most 32,v_cmp_ne_u32against 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. Keeping lane 31 a shuffle of the emulated scan is not enough: on an RTX 5070, every vertex warp of a non-indexed triangle list ran with 30 of its 32 invocations, so the shuffle read an idle invocation and the new execution test covered no pixel. On a device without subgroup arithmetic, lane 31 of such a scan stays a shuffle, as before, and lane 63 still reads the identity. A
v_readlane_b32outside the host subgroup of another value that is 0 in lanes 32-63 reads 0. Only reads that used to throw change, plus lane 31 of such scans on 32-lane hosts with subgroup arithmetic. The strict rules run first and are unchanged, and 64-lane hosts never reach the new ones.The two TechnicalDebt entries (vertex entry
s3, lanes outside the host subgroup) now describe this, including that idle host invocations count as the identity where the console would see the laness3claims.Sources: the
s3layout (vertex count in bits 0-7, primitive count in bits 8-15) is the one the existing entry already uses, taken from the prologue Mesa's ACO emits for NGG vertex shaders (see its TechnicalDebt entry).bound_ctrland the DPP/permlane lane rules are the onesEmitDppWriteCondition,EmitDppMoveU32andEmitPermlane16U32already implement; no instruction semantics change.Related: boykopovar#1825 and boykopovar#2261 (both open) route NGG programs that use LDS or
s_barrierto the mesh path, where wave64 runs at two lanes per invocation on 32-lane hosts. That would also cover this program, with shared LDS. This PR changes only the vertex path and does not conflict with them.Tested
Windows 11 26200, WinLibs GCC 15.2.0, Ninja Release, NVIDIA GeForce RTX 5070 Laptop (616.92, 32-lane subgroups).
verifyNarrowVertexWaveinagc_shader_memory: a synthetic wave64 NGG vertex program with the listing's shape (v_mbcntlane index,v_cmpx_lt_u32_sdwaagainsts3byte 1,s_mov_b64 exec, -1,v_cndmaskkey,bound_ctrl:1OR row scan,v_permlanex16_b32,v_readlane_b32of lanes 31 and 63) prepares on 32 lanes throughPrepareShader, with lane 31 as a subgroup OR, and keeps its DPP scan on 64 lanes. Onmainit fails withv_readlane_b32 of lane 63 is outside the 32-lane host subgroup. It also checks the vertex count byte, a constant bound of 32, a scan step written to another register, a device without subgroup arithmetic (lane 31 stays a shuffle), and a three-step scan read through the fallback; and that a bound of 33, a count from user data, the vertex index instead of the lane, and a step into another register withoutbound_ctrl:1still throw.s3is prepared for 32 and for 64 lanes in both orders with oneShaderPreparationContext. The two get separate sources,MatchesPreparedShaderrejects the other width, the second preparation equals a first one, and the two programs differ. With thes3change reverted, only that last check fails. A wave32 program gives the same SPIR-V on 32 and 64 lanes.agc_driver_ngg_vertex_wave_reduction: the listing's scan in a real draw; each vertex compares its wave's OR with the ballot of its keys and collapses its triangle when they differ, so every pixel must be covered. Passes on the RTX 5070. Onmainit fails to build the program; with lane 31 left as a shuffle no pixel is covered. It skips on other subgroup widths.agc_driver*on NVIDIA: 254/254 (the new and related tests rerun after a reboot: 4/4).ctest -E "^agc_driver|^video_out|^guest_video"): 456/456.check_conventions.pypasses.main): Windows, relinker, register-context and write-watch jobs pass (write-watch macOS-intel after a rerun of the knownwrite_watch_threadstimeout). Linux fails onlyagc_driver_flat_apertureswith a 120 s timeout on lavapipe, twice. That test came tomainafter this branch was cut, and the fork's ownmainrun of the same cycle (38091700844) fails it the same way.s3change got Taxi Chaos 2 past both culling programs to its intro on the RTX 5070.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository🤖 Generated with Claude Code