Skip to content

feat(shader): translate ds_swizzle_b32 FFT mode - #14081

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
frankea:feat/ds-swizzle-fft-mode
Nov 5, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
frankea:feat/ds-swizzle-fft-mode

Conversation

@frankea

@frankea frankea commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

What

ds_swizzle_b32 with an offset of 0xe000 or above threw in the decoder (DS swizzle FFT mode is not supported). This translates that FFT mode.

  • Semantics. Lane i reads lane reverse_bits(i & 0x1f) >> count_ones(mask) | (i & mask) | (i & 0x20), with mask = 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 for offset[4:0] 0x00, 0x10 and 0x1f. The formula reproduces all three, and they make reverse_bits a 5-bit reversal. LLVM draws the mode boundaries in the same place (ROTATE_MODE_LO = 0xC000, FFT_MODE_LO = 0xE000 in SIDefines.h), and on GFX9+ its printer shows any offset from 0xe000 as swizzle(FFT, offset & 0x1f) (AMDGPUInstPrinter.cpp).
  • Emission (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.
  • Not measured. No FFT offset has run on AMD hardware. The new debt entry says that masks other than 0x00, 0x10 and 0x1f, and the ignored offset bits 12:5, follow the pseudocode alone. Some of those masks give two lanes the same source, 0x01 for example. A gfx10.3 run of agc_driver_ds_swizzle would settle them.
  • Rotate mode (0xc000-0xdfff) is unchanged. The RDNA 2 guide's two mask=1 rotate 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 in DsSwizzle.cpp are 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.
  • fix(shader): throw for DPP, ds_swizzle, ds_bpermute and v_permlane16 reads of another lane on one-lane waves #14072. Its DsSwizzleFirstLaneSource decodes every offset from 0xc000 as rotate, as EmitDsSwizzleTargetLane does on main. 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-mac and cache-e1, 3,186 distinct guest programs) hold one ds_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, so agc_driver_ds_swizzle runs both kernels in all three wave modes there; lavapipe's 8-lane subgroups skip it, as before.

  • 38 ctest entries pass 3 times in a row on this branch (75e7b6424c) and on main 0f30281: the 21 entries of agc_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_derivatives and agc_driver_vertex_elected_atomic_max (skipped: the 4090 has vertex subgroups). They pass once more under VK_LAYER_KHRONOS_validation with ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1 (ctest -V) on both, with no VUID- lines and no was not found but was requested by env var VK_INSTANCE_LAYERS. The loader log for agc_driver_ds_swizzle in that environment shows the layer inserted.
  • agc_driver_ds_swizzle fails 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 the i & mask term dropped (lane 1 fft_1f is 0x1e7ea419).

Apple M5 Max, macOS 27.0.1, x86-64 under Rosetta 2: the 21 agc_shader_memory_tests entries 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 duplicate write_watch test registration removed as in #13220, and the GuestMemory.cpp part of #11447.

New checks:

  • agc_driver_ds_swizzle gets 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. On main it fails with DS 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.py passes on the branch. It merges cleanly with main ca073ec, whose 5 newer commits touch none of its files. tools/pr_overlap.py --branch feat/ds-swizzle-fft-mode finds no overlap: the 84 open pull requests that share a file merge cleanly with it. 20 pull requests that conflict with main themselves could not be compared, and they share only tests/ShaderMemory.cpp and docs/dev/debt/shader.md.

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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: NVIDIA RTX 4090 (Windows 11, driver 616.92, Vulkan 1.4.341)
  • AI-assisted: yes (Claude Code)

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 MarcZX10 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.

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=75e7b6424c8419983bf8c76f74cfbd811eccd084

Results

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

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.

@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 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 14900 docs/dev/debt/shader.md L21

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 5021 docs/dev/debt/shader.md
PR 5371 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 6280 docs/dev/debt/shader.md
PR 6412 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 6488 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 6924 docs/dev/debt/shader.md
PR 7085 docs/dev/debt/shader.md
PR 7223 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 7392 docs/dev/debt/shader.md
PR 7410 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 7642 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 7684 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 8004 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 9183 docs/dev/debt/shader.md
PR 9356 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 9387 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 9748 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 9889 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 9897 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 10193 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 10233 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 10359 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 10757 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 10867 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 11351 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 11483 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 12324 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 12548 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
docs/dev/debt/shader.md
PR 12790 docs/dev/debt/shader.md
PR 12828 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 12944 docs/dev/debt/shader.md
PR 13928 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 13929 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 14032 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 14212 docs/dev/debt/shader.md
PR 14561 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 14853 docs/dev/debt/shader.md
PR 14944 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp

@bunbeans2-arch

Copy link
Copy Markdown
Contributor

Ran on AMD RDNA 4 with this PR (75e7b6424c) merged onto main 3c8e5d9d42, and on main alone: Windows 11 Pro 10.0.22631, Radeon RX 9070 XT, MSYS2 UCRT64 GCC 16.1.0, Release, Vulkan validation layer on.

  • This card reports subgroups of 64 by default and runs wave32 programs on subgroups of 32. So here the wave64 kernels, the new FFT one included, run on a native 64-lane subgroup, which the 4090 cannot do, as well as on the 32-lane target.
  • agc_driver_ds_swizzle ran (no skip) and passed in 3 of 3 runs, as did agc_driver_wave32_subgroup and the 20 agc_shader_memory_tests entries: 66 of 66 results, with no validation errors.
  • On main alone, the same 22 tests also passed 66 of 66.

AI-assisted: yes (Claude Code)

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 75e7b64 merged onto main 0836dfd · tested 2026-11-01 01:05 UTC · 24 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,394 passed · 6 failed (0 new, 4 also fail on main, 2 unstable on main) · 44 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,444 of 3,087 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (924 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_lookup_log_fits_build 87.9 s, ⚠️ agc_driver_pipeline_journal 59.1 s, ⚠️ agc_driver_graphics_maps_fallback 30.9 s, ⚠️ agc_driver_graphics 30.3 s
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)
agc_submit_progress: failed in 7 of the last 10 main runs
agc_submit_progress_no_timeline: failed in 9 of the last 10 main runs
Failing on main too (4, not counted against this PR)
agc_driver_idle_store_yields
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (5 IDs in 476 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (476 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 468 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (44)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_main: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_fresh: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_f
Reproduce
git fetch origin 75e7b6424c8419983bf8c76f74cfbd811eccd084 0836dfdf4fecb1e0326100c490476e15d3a74da5
git checkout 0836dfdf4fecb1e0326100c490476e15d3a74da5 && git merge --no-ff 75e7b6424c8419983bf8c76f74cfbd811eccd084
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated 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.

@StarMrGamer

StarMrGamer commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Hardware run of the FFT model with tools/hw-oracle on RDNA3, not gfx10.3: AMD Radeon RX 7800 XT (gfx1101, HSA runtime hsa-rocr 7.2.4, CachyOS, kernel 7.3.1). ds_swizzle_b32 v10, v4 offset:X / s_waitcnt lgkmcnt(0), every lane active, lane i supplying 1000 + 7*i, so each result names the source lane. FP controls: ieee 0, dx10_clamp 1, denorm32 0, denorm16 3, round32 0, round16 0, fp16_overflow 0.

Compared with this PR's model, (i & (mask | ~0x1f)) | (bitreverse32(i) >> (27 + popcount(mask))):

offsets wave32 wave64
0xe000-0xe01f (all 32 masks) 32/32 match 32/32 match
bits 12:5 set: 0xe020 0xe030 0xe03f 0xe021 0xf000 0xf010 0xf01f 0xf001 0xffe0 0xfff0 0xffff 0xffe1 12/12 match 12/12 match

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 (0xe010 maps lanes 32.. to 32, 40, 36, 44, ...). Mask 0x01 does give two lanes the same source: lanes 1 and 9 both read lane 9 (0, 9, 4, 13, 2, 11, 6, 15, 1, 9, ...).

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 hsa-rocr)
import sys, os, json
sys.path.insert(0, "tools/hw-oracle")
from hw_oracle import run

def rev32(x):
    return int(f"{x & 0xffffffff:032b}"[::-1], 2)

def model(i, mask):
    return (i & (mask | ~0x1f)) | (rev32(i) >> (27 + bin(mask).count("1")))

BASE, STEP = 1000, 7
fp = dict(ieee=0, dx10_clamp=1, denorm32=0, denorm16=3, round32=0, round16=0, fp16_overflow=0)
offsets = [0xe000 | m for m in range(32)] + [0xe000 | (h << 5) | m for h in (0x01, 0x80, 0xff) for m in (0x00, 0x10, 0x1f, 0x01)]
report = {"target": os.environ.get("HW_ORACLE_TARGET", "auto"), "fp": fp, "results": {}}
mismatches = 0
for wave64 in (False, True):
    lanes = 64 if wave64 else 32
    rows = [(BASE + STEP * i, 0, 0, 0) for i in range(lanes)]
    for off in offsets:
        body = f"  ds_swizzle_b32 v10, v4 offset:{off:#x}\n  s_waitcnt lgkmcnt(0)\n"
        out = run(body, rows, wave64=wave64, **fp)
        got = []
        for r in out:
            v = r[0]
            got.append((v - BASE) // STEP if v >= BASE and (v - BASE) % STEP == 0 else f"raw:{v:#x}")
        exp = [model(i, off & 0x1f) for i in range(lanes)]
        ok = got == exp
        mismatches += not ok
        report["results"][f"w{lanes}_{off:#06x}"] = {"src_lanes": got, "matches_model": ok}
        print(f"wave{lanes} offset {off:#06x}: {'match' if ok else 'MISMATCH'}" + ("" if ok else f"\n  got {got}\n  exp {exp}"))
print("mismatches:", mismatches)
json.dump(report, open("fft_oracle_result.json", "w"), indent=1)

AI-assisted: yes (Claude Code)

@akkadbakkad89

Copy link
Copy Markdown
Contributor

Validated on NVIDIA RTX 3070 (driver 617.42, Windows 11, MinGW build of 75e7b64):

agc_driver_ds_swizzle_tests — ds_swizzle tests passed, covering the new FFT-mode patterns (0xe000, 0xe010, 0xe01f, 0xe018, 0xe01c, 0xe01e, 0xe001, 0xe00f, 0xe015, 0xe008, 0xffe0, 0xf550) on both wave32 and wave64 programs. Run under ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation; no VUID- lines and no missing-layer warnings in the log.

@RNK123

RNK123 commented Nov 2, 2026

Copy link
Copy Markdown

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 (0xe000 | (b << 5) | m, all 32 masks, b = 0, 1, 0x55, 0xff), five EXEC patterns (full, even lanes, low 16 of each half, edge pairs, lane 4 alone), wave32 and wave64, three repeats. 245,760 swizzle outputs, zero mismatches, including 15,024 active-destination/inactive-source observations, all zero. Another 17,280 per-row input/readback controls pass. Inputs were 1000 + 7 * row; emitted code objects were disassembled to confirm native ds_swizzle_b32 instructions.

For example, in wave64:

  • All lanes active, offset 0xe000: row 16 reads row 1 (1007); row 48 reads row 33 (1231).
  • Even lanes active, same offset: rows 16 and 48 both read 0, not the nonzero values of those inactive sources.
  • Even lanes active, identity offset 0xe01f: those same destinations still read their own 1112 and 1336, excluding an inactive destination or a no-op. The wave32 runs give the same within-half results.

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 75e7b6424c8419983bf8c76f74cfbd811eccd084 merged cleanly onto main 926f7dd492b0b432d9287f4d6faaaf1c9d3c86c5. agc_driver_ds_swizzle, agc_driver_ds_permute, agc_driver_wave32_subgroup pass 3/3 on each tree. The unchanged PR DsSwizzle.cpp alone on main fails 3/3 at DS swizzle FFT mode is not supported; the same fixture passes with the production changes, including FFT wave32, native wave64 and split-wave64 checks. Pristine main was restored/rebuilt and all three original controls passed again. All 24 Vulkan invocations loaded Khronos validation on the RX 9060 XT; zero skips, Khronos warnings/errors, VUIDs or sync hazards. Both trees retain the same unrelated liblsfg loader diagnostic and RADV testing warning.

Environment: CachyOS, kernel 7.3.1-1-cachyos; RX 9060 XT/gfx1200; HSA libhsa-runtime64.so.1.18.0, Clang 23.1.4. Vulkan check: RADV Mesa 26.2.5-arch3.1, Vulkan 1.4.354, VVL 1.4.363, GCC 16.2.1, Release, SPIRV-Tools OFF. No game/performance/full-suite or console claim.

Native reproduction and GFX12 wrapper adaptation

The 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 ieee=0, dx10_clamp=1. Explicit descriptor controls were denorm32=0, denorm16/64=3, round32=0, round16/64=0, fp16_overflow=0, LDS=4096. Only integer DS instructions were measured. See LLVM's AMDGPU kernel-directive/descriptor documentation for the GFX12 field changes.

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')
PY

Vulkan controls used ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation VK_LOADER_DEBUG=layer, the local validation manifest directory in VK_LAYER_PATH, and ctest --test-dir build-main -R '^(agc_driver_ds_swizzle|agc_driver_ds_permute|agc_driver_wave32_subgroup)$' -V --parallel 1 --repeat until-fail:3 --timeout 30 --no-tests=error on both trees. The negative control built only the new fixture on main and ran it three separate times.

AI-assisted independent hardware test run (GPT-6.1-sol).

@oneandonlydean oneandonlydean self-assigned this Nov 5, 2026
@oneandonlydean
oneandonlydean merged commit c4de0df into boykopovar:main Nov 5, 2026
15 checks passed
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.

8 participants