Repository navigation
feat(shader): translate ds_swizzle_b32 FFT mode - #14081
Conversation
Offsets 0xe000-0xffff follow the DS_SWIZZLE_B32 pseudocode of the RDNA 2 (12.13.1) and RDNA 4 ISA guides instead of throwing in the decoder. Rotate mode (0xc000-0xdfff) is unchanged.
MarcZX10
left a comment
There was a problem hiding this comment.
Reviewed — looks good
The FFT lane mapping follows the documented formula, and the author reports repeated RTX 4090 execution tests covering wave32, wave64, partial EXEC, and negative controls.
What I ran
Reviewed exact head 75e7b6424c8419983bf8c76f74cfbd811eccd084 against base ca073ec3ad0d766ea9f4658e418a5c977001a353. The no-build merge preview was clean. Environment: Linux x86-64, Python 3.11.2, Git 2.39.5. I did not configure or build locally.
python3 tools/pr_precheck.py --repo boykopovar/AnyPS5 --pr 14081 --output-dir /tmp/anyps5-14081-review
gh pr checks 14081 -R boykopovar/AnyPS5
gh api repos/boykopovar/AnyPS5/contents/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp?ref=75e7b6424c8419983bf8c76f74cfbd811eccd084
gh api repos/boykopovar/AnyPS5/contents/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp?ref=75e7b6424c8419983bf8c76f74cfbd811eccd084Results
| Check | Result |
|---|---|
| Diff check | Passed. |
| Conventions and PR template | Passed locally and in hosted CI. |
| Current-main merge preview | Clean. |
| Hosted Windows changed-tests workflow | Passed. |
| Hosted Ubuntu relinker and ASan | Passed; these do not exercise the FFT GPU path. |
| Hosted macOS/Windows relinker matrix | Pending when checked. |
| Linux and specialized jobs | Skipped. |
| Author-reported GPU tests | RTX 4090 tests passed three times on the PR head and comparison main; validation-layer run also passed with no VUIDs. |
| Author-reported negative controls | The execution test failed with the stated lane-level errors for a wrong reversal shift and for removing the mask term. I did not run these mutations. |
| Local build or tests | Not run. |
Unedited precheck output
All conventions checks passed.
Unedited hosted check summary
conventions pass 17s https://github.com/boykopovar/AnyPS5/actions/runs/41228093030/job/124616373319
eligibility pass 5s https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616373103
intake pass 5s https://github.com/boykopovar/AnyPS5/actions/runs/41228093029/job/124616373241
labels pass 8s https://github.com/boykopovar/AnyPS5/actions/runs/41228093041/job/124616373311
library-portability pass 1m43s https://github.com/boykopovar/AnyPS5/actions/runs/41228093238/job/124616372831
relinker (ubuntu-latest, g++) pass 3m26s https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844012
relinker-asan pass 2m50s https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844025
report pass 1m7s https://github.com/boykopovar/AnyPS5/actions/runs/41228093220/job/124616372736
relinker (macos-latest, clang++) pending 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844089
relinker (macos-latest, clang++, x86_64) pending 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844034
linux skipping 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844992
register-context skipping 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616845077
write-watch skipping 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616845170
relinker (windows-latest, g++) pending 0 https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616844006
windows pass 4m57s https://github.com/boykopovar/AnyPS5/actions/runs/41228093245/job/124616843960
Findings
Blocking
None found.
Non-blocking
No AMD GPU run was performed by me. The author reports the exact-head execution and validation runs on an RTX 4090; the remaining relinker matrix checks were still pending, and Linux/specialized product jobs were skipped.
Sources
- FFT lane calculation in the reviewed implementation
- Wave32/wave64 execution cases and expected lane values
- SPIR-V emission checks for FFT and rotate offsets
- Author’s reported GPU runs, validation, and mutation checks
- Hosted check run
Not verified
I did not reproduce the author’s RTX 4090 runs or mutations, run a local build, or test a physical PS5. The submitted debt note explicitly records that the newly supported FFT offsets have not been measured on AMD hardware.
|
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.
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.
|
|
Ran on AMD RDNA 4 with this PR (
AI-assisted: yes (Claude Code) |
✅ NVIDIA GPU test: passedPR head
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)Failing on main too (4, not counted against this PR)Validation IDs already present on main (5 IDs in 476 tests)Skipped (44)ReproduceAutomated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs. |
|
Hardware run of the FFT model with Compared with this PR's model,
So on gfx1101 the masks the guides don't give examples for follow the pseudocode, bits 12:5 are ignored, and in wave64 the upper half keeps lane bit 5 ( Not covered: inactive source lanes (all lanes were active here), and gfx10.x. The PS5 is gfx1013, and HW_ORACLE.md says RDNA3 differs from it in more places, so this backs the model rather than settling it for gfx1013. Script (run from the repo root on a Linux AMD GPU with
|
|
Validated on NVIDIA RTX 3070 (driver 617.42, Windows 11, MinGW build of 75e7b64):
|
|
Native AMD GFX1200 measurement: an active FFT destination reads zero when its selected source lane is inactive. This adds the inactive-source check explicitly excluded by the earlier RDNA3 oracle report, rather than repeating only the translated-Vulkan pass. It is not a gfx10.x/PS5 measurement. RX 9060 XT, native HSA assembly: 128 offsets ( For example, in wave64:
The source-lane formula in the description matches every measured active result, including the upper half of wave64. Only the four listed settings of bits 12:5 were sampled, not all 256. Separate Vulkan check: exact head Environment: CachyOS, kernel Native reproduction and GFX12 wrapper adaptationThe repository's oracle template currently rejects GFX1200's obsolete IEEE/DX10 kernel directives. I omitted just those two lines locally, without changing the repository. They are unavailable FP descriptor controls on GFX12, not applied settings; the required Python API arguments remain One reproduction of the full matrix, from the repository root with HSA and AMDGPU LLVM installed: cp -r tools/hw-oracle /tmp/anyps5-fft-gfx1200
sed -i '/^[[:space:]]*\.amdhsa_ieee_mode /d; /^[[:space:]]*\.amdhsa_dx10_clamp /d' /tmp/anyps5-fft-gfx1200/template.s
PYTHONPATH=/tmp/anyps5-fft-gfx1200 python3 - <<'PY'
from hw_oracle import run, target
assert target() == 'gfx1200'
fp = dict(ieee=0, dx10_clamp=1, denorm32=0, denorm16=3,
round32=0, round16=0, fp16_overflow=0)
rows = [(1000 + 7 * row, row, 0, 0) for row in range(64)]
offsets = [0xe000 | (middle << 5) | mask
for middle in (0, 1, 0x55, 0xff) for mask in range(32)]
checked = decisive = controls = 0
for wave in (32, 64):
masks = [(1 << wave) - 1,
sum(1 << lane for lane in range(0, wave, 2)),
sum(1 << lane for lane in range(wave) if lane % 32 < 16),
sum(1 << lane for lane in range(wave) if lane % 32 in (0, 1, 30, 31)),
1 << 4]
for active in masks:
for start in range(0, len(offsets), 15):
batch = offsets[start:start + 15]
body = ['v_mov_b32 v25, v4', f's_mov_b32 exec_lo, {active & 0xffffffff}']
if wave == 64:
body += [f's_mov_b32 exec_hi, {active >> 32}']
body += [f'ds_swizzle_b32 v{10 + index}, v4 offset:{offset}'
for index, offset in enumerate(batch)]
body += ['s_waitcnt lgkmcnt(0)', 's_mov_b64 exec, -1']
for repeat in range(3):
actual = run('\n'.join(body), rows, wave64=wave == 64, **fp)
for row, values in enumerate(actual):
assert values[15] == rows[row][0]
controls += 1
lane, base = row % wave, row - row % wave
for index, offset in enumerate(batch):
mask = offset & 31
source = (int(f'{lane & 31:05b}'[::-1], 2) >> mask.bit_count()) | (lane & mask) | (lane & 32)
on = bool(active & (1 << lane))
source_on = bool(active & (1 << source))
expected = rows[base + source][0] if on and source_on else 0
assert values[index] == expected, (wave, active, row, offset, values[index], expected)
checked += 1
decisive += int(on and not source_on)
assert (checked, decisive, controls) == (245760, 15024, 17280)
print(checked, decisive, controls, 'passed')
PYVulkan controls used AI-assisted independent hardware test run (GPT-6.1-sol). |
What
ds_swizzle_b32with an offset of 0xe000 or above threw in the decoder (DS swizzle FFT mode is not supported). This translates that FFT mode.ireads lanereverse_bits(i & 0x1f) >> count_ones(mask) | (i & mask) | (i & 0x20), withmask = offset[4:0], and an inactive source reads 0. This is the DS_SWIZZLE_B32 pseudocode of AMD's RDNA 2 ISA guide (12.13.1, page 207; pseudocode on page 209), which the RDNA 4 guide repeats unchanged (DS_SWIZZLE_B32, page 587). Both guides give example swizzles foroffset[4:0]0x00, 0x10 and 0x1f. The formula reproduces all three, and they makereverse_bitsa 5-bit reversal. LLVM draws the mode boundaries in the same place (ROTATE_MODE_LO = 0xC000,FFT_MODE_LO = 0xE000in SIDefines.h), and on GFX9+ its printer shows any offset from 0xe000 asswizzle(FFT, offset & 0x1f)(AMDGPUInstPrinter.cpp).EmitDsSwizzleTargetLane). The emitter builds(lane & (mask | ~31)) | (OpBitReverse(lane) >> (27 + popcount(mask))). For mask 0x1f the shift would be 32 and the swizzle is the identity, so the reversed term is left out. I checked this against the pseudocode offline for every offset from 0xe000 to 0xffff on all 64 lanes. The decoder no longer throws for these offsets.agc_driver_ds_swizzlewould settle them.mask=1rotate examples ({2,1,4,7,6,5,…}for left) disagree with its own pseudocode,j = (i & mask) | ((i + rotate) & ~mask). The RDNA 4 guide replaces them with{1,4,3,6,5,8,…}(left) and{1f,2,1,4,3,6,…}(right), which is what the emitter already computes. test(shader): execute ds_swizzle_b32 in all three offset modes #605 measured this model on gfx1035 (its 16 offsets plus 384 random offsets below 0xe000), and the existing expectations inDsSwizzle.cppare that hardware output. Mesa's ACO uses the mask the same way, as the lane bits kept for clustered rotates:ds_pattern_rotate(delta, ~(cluster_size - 1) & 0x1f)in aco_select_nir_intrinsics.cpp.DsSwizzleFirstLaneSourcedecodes every offset from 0xc000 as rotate, asEmitDsSwizzleTargetLanedoes onmain. In FFT mode lane 0 always reads lane 0. Once both are merged, that helper should therefore return 0 from 0xe000. Otherwise an FFT offset with bits 9:5 set would be refused on one-lane waves. fix(shader): throw for DPP, ds_swizzle, ds_bpermute and v_permlane16 reads of another lane on one-lane waves #14072 is not merged yet, so whichever of the two lands second adds that line. The two branches merge cleanly.Title: none. The Gran Turismo 7 (PPSA01317) shader caches from my MoltenVK runs (43,822 entries in
cache-macandcache-e1, 3,186 distinct guest programs) hold oneds_swizzle_b32, with bitmask offset 0x01e0, and none at 0xc000 or above. A program with an FFT swizzle throws before it can reach a cache, so I also searched the run logs: none of my GT7 runs on the Mac, and none of my GT7, Astro Bot, Onimusha, NHL 26, Ghostrunner and Horizon runs on the 4090 box, contains the decoder's message.Tested
Windows 11 on an RTX 4090 (driver 616.92, Vulkan 1.4.341), winlibs GCC 15.2.0, Release with
BUILD_TESTING=ON. The 4090 runs wave32 programs on subgroups of 32, soagc_driver_ds_swizzleruns both kernels in all three wave modes there; lavapipe's 8-lane subgroups skip it, as before.75e7b6424c) and onmain0f30281: the 21 entries ofagc_shader_memory_tests,agc_driver_ds_swizzle,wave32_wide_subgroup,agc_driver_read_lane,agc_driver_write_lane,agc_driver_lds_mask_waterfall,agc_driver_exec_host_lanes,agc_driver_ngg_vertex_wave_reduction,agc_driver_pixel_wave_reduction,agc_driver_pixel_row_reduction,agc_driver_wave32_subgroup,agc_driver_window_lane_spill,agc_driver_scalar_sop1,agc_driver_tessellation,agc_driver_tessellation_depth_range_remap,agc_driver_relative_ancillary,agc_driver_image_sample_derivativesandagc_driver_vertex_elected_atomic_max(skipped: the 4090 has vertex subgroups). They pass once more underVK_LAYER_KHRONOS_validationwithANYPS5_NO_SHADER_CACHE=1andANYPS5_REQUIRE_VULKAN=1(ctest -V) on both, with noVUID-lines and nowas not found but was requested by env var VK_INSTANCE_LAYERS. The loader log foragc_driver_ds_swizzlein that environment shows the layer inserted.agc_driver_ds_swizzlefails on two lane-level mistakes that the compile check below cannot see, each built on the 4090 from a local commit: a reversal shift one bit short (lane 1 fft_00 is 0x00000000) and thei & maskterm dropped (lane 1 fft_1f is 0x1e7ea419).Apple M5 Max, macOS 27.0.1, x86-64 under Rosetta 2: the 21
agc_shader_memory_testsentries pass 3 times in a row on this branch, built natively. They compile for explicit targets and run no GPU work. The build needed two local aids that are not part of this pull request: the duplicatewrite_watchtest registration removed as in #13220, and theGuestMemory.cpppart of #11447.New checks:
agc_driver_ds_swizzlegets a second kernel, run like the first in wave32, native wave64 and wave64 split into two wave32 halves. It has 12 FFT offsets under a full EXEC (masks 0x00, 0x10, 0x1f, 0x18, 0x1c, 0x1e, 0x01, 0x0f, 0x15 and 0x08, plus 0xffe0 and 0xf550, which set bits 12:5 over masks 0x00 and 0x10) and 4 under the existing partial EXEC (0x00, 0x10, 0x18 and 0x01). The expected values come from the pseudocode. For lanes 0-31 the 0x00, 0x10 and 0x1f columns follow the guide's example rows. The first kernel and its measured expectations are unchanged.verifyDsSwizzleFftMode(tests/ShaderMemory.cpp) builds wave32 programs. FFT offsets with a mask below 0x1f emit a lane bit reversal, and mask 0x1f and the rotate offsets 0xc000, 0xc021 and 0xdfff do not. 0xffe0 and 0xf550 build the same SPIR-V as 0xe000 and 0xe010. Onmainit fails withDS swizzle FFT mode is not supported. The test also fails, at the matching check, with each of these changes: the decoder throw restored, FFT offsets decoded as rotate, rotate offsets decoded as FFT, the mask read from bits 12:0, or the identity case given a shift of 32.tools/check_conventions.pypasses on the branch. It merges cleanly withmainca073ec, whose 5 newer commits touch none of its files.tools/pr_overlap.py --branch feat/ds-swizzle-fft-modefinds no overlap: the 84 open pull requests that share a file merge cleanly with it. 20 pull requests that conflict withmainthemselves could not be compared, and they share onlytests/ShaderMemory.cppanddocs/dev/debt/shader.md.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)