Skip to content

fix(shader): read quad_perm DPP lanes in pixel shaders through quad operations - #6290

Open
jackthereaper2006 wants to merge 2 commits into
boykopovar:mainfrom
jackthereaper2006:fix/pixel-dpp-quad-reads
Open

jackthereaper2006 wants to merge 2 commits into
boykopovar:mainfrom
jackthereaper2006:fix/pixel-dpp-quad-reads

Conversation

@jackthereaper2006

@jackthereaper2006 jackthereaper2006 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

In a pixel shader, a DPP quad_perm read is now emitted as OpGroupNonUniformQuadBroadcast from each lane of the quad, with the result chosen by the lane's quad_perm entry. This covers the source EXEC check of a DPP move and of a DPP write condition as well as the value. Other DPP controls, other stages and devices without VK_SUBGROUP_FEATURE_QUAD_BIT keep the subgroup shuffle and ballot.

Until now the read was a subgroup shuffle plus a ballot of EXEC. Mesa gives a fragment shader full quads only for quad group operations, or for subgroup operations when the shader requires full quads (RequireFullQuadsKHR) (nir_gather_info.c, needs_full_quad_helper_invocations). On RADV the shuffle and the ballot therefore run without helper invocations, and a DPP read from a helper lane is wrong. The derivatives that ACO builds from DPP quad_perm reads are the visible case.

The same failure shows on AMD's Windows driver (LeandroLP23's run). NVIDIA, Intel ANV Gen9 and lavapipe already keep helpers in the shuffle, so on those drivers the change only moves the read to quad operations.

The device now lists GroupNonUniformQuad when it reports VK_SUBGROUP_FEATURE_QUAD_BIT, and shader validation maps that capability to the bit.

Tested

Linux (CachyOS, kernel 7.2.9), GCC 16.2.1, Release, BUILD_TESTING=ON. Rebased on main 6ef5a1c4c; the only conflict was ShaderMemory.cpp, where verifyFmaskImageMode and verifyPixelQuadPermReads were added at the same place. Runs that name no commit were on main 0afa6a607 before the rebase.

  • PS5 OpenGL Showcase (PPSA99005, catalog release 1.0.2, sha256 37a0ef4e…d388). Its instance pixel shader takes the normal from normalize(cross(dFdx(v_world), dFdy(v_world))) (main.c:259). In the translated program 0x229255000 that becomes the only shader with DPP moves.
    • RADV on main: the 8192 instances render as a white bloom cloud with black specks.

    • RADV with this change: they render as in the author's screenshot.

    • Checks on main:

      • With APS5_SKIP_SHADER=229255000 the rest of the frame is correct.
      • On lavapipe (Mesa 26.2.4) the instances render correctly.
      • RADV_DEBUG=nodcc, nofastclears and zerovram each leave the cloud.
      • Dropping only the ballot check, so the shuffle stays, also leaves the cloud.
    • Frames (APS5_DUMP_FRAMES dumps of the title's own display buffer, 960×540):

      main, RADV main + the fix, RADV
      main, RADV fix, RADV
      main, lavapipe main, RADV, APS5_SKIP_SHADER=229255000
      main, lavapipe main, RADV, instance shader skipped

      Full-size files: https://gist.github.com/jackthereaper2006/aec623dfe08f2a6d8e5207ac3e2a209b. Reference: the author's screenshot.

  • New agc_shader_memory check:
    • With the quad capability, pixel quad_perm reads, with full and partial row masks, use OpGroupNonUniformQuadBroadcast and no shuffle.
    • Without the capability they keep the shuffle.
    • A row shift keeps the shuffle.
    • It fails without the emitter change.
  • New agc_driver_pixel_quad_perm_helpers (5e25bc8, 3dd3a98 before the rebase): pixel quad_perm DPP reads of helper neighbours after s_wqm, with and without fi, wave64 and wave32. It passes on RADV, fails on RADV with the emitter from main (pixel 584 read 0 from quad_perm [1,0,3,2], its helper neighbour is pixel 585), and skips on lavapipe (8-lane subgroup) and without the quad capability. Whole ctest with it: 1227/1227 with Mesa patched for agc_driver_tessellation segfaults on RADV with Mesa 26.2+: nir_opt_varyings corrupts the TCS input offset's use list (Mesa bug) #5444.
  • Validation layer (on main 6ef5a1c4c, ctest -V -R 'quad|dpp|agc_shader_memory' -E audio_out with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1, validation layers 1.4.363):
    • main: 11/11 pass, no VUID- errors.
    • This change: 12/12 pass (the 11 plus agc_driver_pixel_quad_perm_helpers), no VUID- errors.
    • Neither log reports was not found but was requested by env var VK_INSTANCE_LAYERS; VK_LOADER_DEBUG=layer shows the layer inserted at instance and device level.
  • Whole ctest on main 6ef5a1c4c, RADV, stock Mesa 26.2.4: 1776 pass, 32 skip and 7 fail out of 1815. The 7 failures fail the same way with this change's files reverted to main:
  • Whole ctest on main 0afa6a607:

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: PPSA99005 PS5 OpenGL Showcase (PPSA99005) #6289
  • GPUs tested: AMD Radeon Graphics (RADV RENOIR, Ryzen 5 PRO 5650U iGPU, GCN 5, 0x1638), Mesa 26.2.4, CachyOS Linux 7.2.9; lavapipe, Mesa 26.2.4 (built locally), same machine
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

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

Copy link
Copy Markdown
Contributor

Windows and NVIDIA run, for the "GPUs tested" line (RADV and lavapipe on Linux only). Windows 11 (26200), RTX 3060 Laptop GPU (NVIDIA 617.42), WinLibs GCC 15.2.0 r7 (UCRT), Release, BUILD_TESTING=ON. Head 891885201 merged onto main 8856f180c (clean); ANYPS5_GPU=3060, and a verbose test log shows the RTX 3060 selected. vulkaninfo lists SUBGROUP_FEATURE_QUAD_BIT for this device, with subgroupQuadOperationsInAllStages = true.

  • agc_shader_memory: passes.
  • With the five non-test files (VulkanDevice.cpp, ShaderValidation.cpp, SpirvSubgroup.hpp, SpirvSubgroup.cpp, SpirvAluEmitterMath.cpp) from main and the PR's test (2 of 2 runs), agc_shader_memory fails: a pixel quad_perm read did not go through quad operations.
  • ctest -R "agc|shader|guest_memory|write_watch" -j4: 591/591 pass with the PR, and 591/591 on main. Both skip the same 3 tests (agc_host_import_sparse, agc_driver_sparse_dma_buf_import, agc_driver_stencil_ref_export).

Not checked: which of the GPU tests above run a pixel shader with a DPP quad_perm read, so the suite result does not show that the quad path ran on NVIDIA. Not run: PS5 OpenGL Showcase, Intel.

AI-assisted: yes (Claude Code)

This was referenced Oct 15, 2026
@LeandroLP23

Copy link
Copy Markdown
Contributor

Windows with AMD's own driver, the combination not covered yet: the Showcase artifact from #6289 shows up there on main too, and this change fixes it. The new test also works as a negative control on this driver.

Setup: Windows 11 Home 26300, AMD Radeon 860M (RDNA 3.5, 1002:1114, driver 32.0.13058.8001), WinLibs GCC 15.2.0, Release. Head 3dd3a9832 merged onto main 5d9dcc42d (clean merge). PS5 OpenGL Showcase, catalog 1.0.1, relinked with --windows, warm shader cache. Frame dumps use APS5_DUMP_FRAMES=5, APS5_DUMP_FRAMES_EVERY=20, from one 45 s run per build.

Title

Tests

Not tested: discrete RDNA, Linux.

AI-assisted: yes (Claude Code)

@odntht

odntht commented Oct 16, 2026

Copy link
Copy Markdown

An Intel run, for the "Not run: Intel" line. It passes, but Gen9 does not show the bug on main either.

Setup: Intel HD Graphics 520 (Skylake GT2, Gen9), Mesa 25.2.8 ANV, Ubuntu 24.04 (container), kernel 6.12.111, GCC 13.3, Release. Head 3dd3a9832 merged onto main f474fb776 (clean). ANYPS5_GPU="HD Graphics 520"; the log shows it selected, subgroup=32. ANV reports SUBGROUP_FEATURE_QUAD_BIT for the fragment stage.

  • With the PR: agc_driver_pixel_quad_perm_helpers passes 3 of 3 (4432 reads from helper neighbours over 4 draws for wave64, wave64 fi, wave32 and wave32 fi). agc_shader_memory, agc_shader_quad_mask_no_demote, agc_driver_quad_pixel_mask and agc_driver_vop3_quad_sad pass 3 of 3.
  • Control 1, the PR's tests with every source file from main: the new test skips (the device has no subgroup quad operations), because main never adds GroupNonUniformQuad. agc_shader_memory fails as expected (a pixel quad_perm read did not go through quad operations).
  • Control 2, the PR's VulkanDevice.cpp and ShaderValidation.cpp with main's SpirvSubgroup.* and SpirvAluEmitterMath.cpp, so the old shuffle path runs on a device that advertises quad: agc_driver_pixel_quad_perm_helpers also passes 3 of 3, with the same 4432 reads.

So on ANV Gen9, as on lavapipe, the shuffle path already reads helper neighbours correctly, and the PR keeps it working. The test does not catch the RADV failure on this device. I could not run the PS5 OpenGL Showcase here.

AI-assisted: yes (Claude Code). The runs are on my machine.

@adriadam10 adriadam10 self-assigned this Oct 18, 2026
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026

@klosixgaming-arch klosixgaming-arch 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.

Merged head 3dd3a9832 onto current main (40393b0f9) — clean merge, builds with WinLibs GCC 15.2.0, Release, Ninja, -j 8. Windows 11, NVIDIA GeForce RTX 5090 (selected device; RTX 4090 and an AMD iGPU are also present), 32-lane subgroups.

Tests run on the merged tree:

  • agc_shader_memory — pass (includes the new pixel quad_perm check)
  • agc_driver_pixel_quad_perm_helpers — pass
  • DPP/quad regression set, all pass: agc_dpp_write, agc_driver_dpp8, agc_driver_dpp8_pk_fmac, agc_driver_dpp16_inactive_source, agc_driver_dpp16_operands, agc_driver_dpp16_row_share_xmask, agc_driver_dpp_no_form, agc_driver_quad_pixel_mask, agc_driver_vop3_quad_sad
  • Sanity: request_serializer, agc_driver_gpu_buffer_format, agc_driver_layered_volume_draw — pass

Negative control (plain main 40393b0f9 plus only this PR's test changes):

  • agc_shader_memory fails with "a pixel quad_perm read did not go through quad operations"
  • agc_driver_pixel_quad_perm_helpers skips (exit 77, "skipped, the device has no subgroup quad operations") because main does not expose GroupNonUniformQuad

Note: on NVIDIA the exec test passes even without the emitter change (NVIDIA keeps helpers in the shuffle), so on this machine the failing negative control is the agc_shader_memory check; the RADV-specific failure was already demonstrated by @jackthereaper2006 and @LeandroLP23 above.

Read through the diff: capability exposure in VulkanDevice.cpp, validation mapping in ShaderValidation.cpp, and quad-broadcast emission for pixel-stage quad_perm reads (control ≤ 0xff) covering both the DPP move value and the write-condition source EXEC check — no problems found. Not tested here: AMD, Intel, Linux, titles.

AI-assisted: yes. Written with AI assistance and reviewed by me.

jackthereaper2006 and others added 2 commits October 22, 2026 08:35
…perations

A DPP quad_perm read in a pixel shader, such as the neighbour read of a
derivative, was emitted as a subgroup shuffle with a ballot of EXEC. Mesa
gives a fragment shader full quads only for quad group operations, or for
subgroup operations when it requires full quads (nir_gather_info.c), so on
RADV the shuffle and the ballot ran without helper invocations and reads
from helper lanes were wrong.

When the device supports quad operations, a pixel quad_perm read and the
source EXEC check of a DPP write now use OpGroupNonUniformQuadBroadcast.
Other DPP controls, other stages and devices without quad operations keep
the shuffle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draws triangles whose quads have helper pixels, turns whole-quad mode on
and reads the horizontal and vertical neighbour's pixel index through
v_mov_b32_dpp quad_perm, with and without fi, in wave64 and wave32. Every
covered pixel must read its neighbour's index, helper or not.

On RADV the shuffle path read 0 from helper neighbours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@klosixgaming-arch

Copy link
Copy Markdown
Contributor

Test run (not a code review). I merged this PR onto current main (49d1f9b), built it on Windows (Release, winlibs), and ran Tekken Dark Resurrection (PPSA31371, PSPHD) for 112 s.

  • Merges cleanly, builds, and the game stays alive for the full run.
  • The run log differs from a baseline main run by only about 120 lines out of ~650, which is run-to-run noise, nothing new or removed in the errors.
  • I did not verify any visual change. My screenshots of this run did not capture the game window, so I cannot say whether the issue this PR targets is fixed.

So: no regression found, and no evidence of a fix either way.

@TechLevelZero

TechLevelZero commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 5e25bc8 merged onto main 88e804b · tested 2026-11-02 13:55 UTC · 15 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,499 passed · 4 failed (0 new, 3 also fail on main, 1 unstable on main) · 45 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,548 of 3,234 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 (971 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_lookup_log_fits_build 35.8 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 2 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (1, not in the main baseline's validation pass)
agc_driver_pixel_quad_perm_helpers
Validation IDs already present on main (4 IDs in 497 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (497 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 489 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-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (45)
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_fps_limit: 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_o
Reproduce
git fetch origin 5e25bc870e6eda3f0cb994290a53f71cac0aaf8d 88e804b0a7df1f1ac373a9e2f72fb378d925fdd4
git checkout 88e804b0a7df1f1ac373a9e2f72fb378d925fdd4 && git merge --no-ff 5e25bc870e6eda3f0cb994290a53f71cac0aaf8d
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.

@fogydes

fogydes commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

OK

1. Freshness Envelope

  • Head SHA: 5e25bc870e6eda3f0cb994290a53f71cac0aaf8d
  • Merge-base SHA: 6ef5a1c4c92272598b18adeb9b5414112f8d15ed
  • Behind origin/main: 8,557 commits
  • Merge-tree status: exit code 0 (clean merge against origin/main, tree SHA 3e4ebcbd598e32642de4d0afb8a16caf0f5fab50)

2. Findings

  1. [verified] Clean merge tree with origin/main: git merge-tree --write-tree origin/main 5e25bc870e6eda3f0cb994290a53f71cac0aaf8d exits with code 0 (no conflicts).
  2. [verified] Conventions clean against merge-base: python tools/check_conventions.py --base 6ef5a1c4c92272598b18adeb9b5414112f8d15ed in an isolated scratch worktree reported 0 findings.
  3. [read-only] Full template checklist compliance: PR description adheres to .github/pull_request_template.md with all checklist items populated (Fixes: none, Unblocks: PPSA99005 #6289, GPUs tested: covering RADV and lavapipe).
  4. [author-reported] Focused Vulkan validation layer comparison (VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1) on RADV RENOIR with 0 new VUID- validation errors.
  5. [author-reported] PS5 OpenGL Showcase (PPSA99005) instance normal rendering fix verified on RADV and lavapipe.

3. Cross-PR Ledger

@Astronaut10

Copy link
Copy Markdown
Contributor

Thanks for this; it fixes the case we hit too (helper neighbours read through quad_perm DPP came back as the inactive-source value on RADV).

One neighbour read stays on the shuffle path here: row_xmask. On this PR's head (RX 9070 XT, RADV GFX1201, Mesa 26.2.5), helper-neighbour reads through row_xmask:1 / :2 come back wrong in 1,108 of 1,108 reads, wave32 and wave64; live neighbours are right. That is the lowering main already has, not something this PR introduces, but row_xmask 0-3 stays inside a quad, so it could take the same quad path.

A wider coverage note, not a regression report: in a helper lane, the value and the source EXEC a quad read uses, and whether the lane runs a block at all, can still come from operations Vulkan lets helper invocations stay inactive for: an EXEC built from compare masks (s_and_saveexec_b64 s, vcc, s_and_b64 exec, exec, vcc), s_cbranch_execz / vccz branches (lowered to ballots), v_readlane_b32 / v_readfirstlane_b32, and cross-lane reads outside a quad. Vulkan guarantees helper participation in those only with RequireFullQuadsKHR (VK_KHR_shader_quad_control) plus MaximallyReconvergesKHR (VK_KHR_shader_maximal_reconvergence). RADV does not show it: once a fragment shader has a quad operation, ACO keeps helpers in whole-quad mode through ballots and shuffles, and our helper-only execz / vccz / EXEC-ballot cases read right on this head. We have not tested other drivers.

We have a follow-up on top of this branch, held until this merges: row_xmask 0-3 through the quad path, the two execution modes for pixel programs where a helper lane can reach one of those operations (with a throw where the device lacks shaderQuadControl / shaderMaximalReconvergence), and an execution test, agc_driver_helper_lane_reads (wave32 and wave64). Happy to post the test here first if you would rather fold it into this PR.

@XairRrR XairRrR 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 — approved

Pixel-shader DPP quad_perm reads now go through OpGroupNonUniformQuadBroadcast when the device has quad operations. The change merges cleanly and builds. The focused tests pass, and the new agc_shader_memory check fails once the production files are reverted to main.

What I ran

Head 5e25bc870e6eda3f0cb994290a53f71cac0aaf8d, main 1c8c0efc8c41314402e6966da5bef6ea3d639d10, merged tree 2c00d3afe895b0453533a9f7e4019b234ca50c7d. The machine is Debian 13 (VM, 32 vCPU Xeon Gold 6136) with GCC 14.2.0, and Vulkan runs on lavapipe (Mesa 26.1.6, LLVM 19, 8-lane subgroups).

git diff --check 1c8c0efc8c41314402e6966da5bef6ea3d639d10...5e25bc870e6eda3f0cb994290a53f71cac0aaf8d
python3 tools/check_conventions.py --base 1c8c0efc8c41314402e6966da5bef6ea3d639d10
ctest --test-dir build -j8 --test-load 32 --output-on-failure -V -R "^(agc_driver_pixel_quad_perm_helpers|agc_shader_memory|agc_dpp_write|agc_driver_quad_pixel_mask|agc_driver_dpp16_inactive_source|agc_driver_pixel_helper_lanes|agc_shader_quad_mask_no_demote)$"
# negative control
git show 1c8c0efc8c41314402e6966da5bef6ea3d639d10:<path> > <path>   # the 5 production files
ninja -C build -j32 -l32 agc_shader_memory_tests
build/tests/agc_shader_memory_tests
git show 2c00d3afe895b0453533a9f7e4019b234ca50c7d:<path> > <path>   # restore
ninja -C build -j32 -l32 agc_shader_memory_tests agc_driver_pixel_quad_perm_helpers_tests
build/tests/agc_shader_memory_tests
git status --porcelain

Results

Check Result
git diff --check clean
check_conventions.py All conventions checks passed
Configure, build, libs (script) ok
Selected tests (script, 2177 run) 0 new failures, 0 timeouts. The 7 known failures (agc_driver_division, denormal-mode tests, vector_constant_factor_tininess, an unwrap test) also fail on main
Skips (script) 117, all expected per check_skips.py
Focused ctest (7 tests) 4 pass: agc_shader_memory, agc_dpp_write, agc_driver_quad_pixel_mask, agc_shader_quad_mask_no_demote. 3 skip on lavapipe with "subgroup size 8 cannot hold a wave32": agc_driver_pixel_quad_perm_helpers, agc_driver_pixel_helper_lanes, agc_driver_dpp16_inactive_source
Validation layer (script) layer loaded in 784 of 785 tests, missing in none. The only test with VUIDs not seen on main is agc_driver_image_gather_volume (VUID-VkSamplerCreateInfo-addressMode*). That test is marked "test not run on main", so the baseline has no comparison for it. It checks sampler address modes, which this change does not touch, so I don't count it against the PR
Negative control fails as expected (see below)
Worktree after restore git status --porcelain is empty
agc_driver_pixel_quad_perm_helpers on lavapipe
1415: Selected GPU name=llvmpipe (LLVM 19.1.7, 256 bits) vendor=0x10005 device=0x0 subgroup=8
1415: skipped, subgroup size 8 cannot hold a wave32
3/7 Test #1415: agc_driver_pixel_quad_perm_helpers ...***Skipped   0.10 sec

Negative control

I restored these five production files from main:

  • VulkanDevice.cpp
  • ShaderValidation.cpp
  • SpirvSubgroup.hpp
  • SpirvSubgroup.cpp
  • SpirvAluEmitterMath.cpp

I kept the PR's tests and rebuilt agc_shader_memory_tests. It fails with:

a pixel quad_perm read did not go through quad operations

After I restored the merged files, it passes again.

The GPU execution test agc_driver_pixel_quad_perm_helpers skips on this machine, so I couldn't run a negative control for it. Others ran it in the discussion:

  • It tells the two paths apart on RADV (the author) and on AMD's Windows driver (LeandroLP23).
  • On NVIDIA (screamm) and ANV Gen9 (odntht) it passes with the shuffle path too, so it can't tell the paths apart there. I can't add anything new on that point.

Findings

Blocking

None.

Non-blocking

  • The quad read emits more code than it needs to (core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp:317-329, used at core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp:1382-1386 and :196).
    • What it does now: each read emits four OpGroupNonUniformQuadBroadcast plus three selects. A move without fi emits that twice, once for the value and once for the source EXEC. That is why screamm counted 60 broadcasts where there were 15 shuffles before.
    • Why it's wasteful: control is a compile-time constant. When all four entries are the same (0x00, 0x55, 0xaa, 0xff), one OpGroupNonUniformQuadBroadcast with that index is enough. [1,0,3,2], [2,3,0,1] and [3,2,1,0] map directly to OpGroupNonUniformQuadSwap with direction 0, 1 and 2.
    • Impact: it's only code size and ALU work, not a correctness issue. LeandroLP23 measured no change in FPS. It could go in a follow-up.
  • The point that QuadBroadcast of EXEC is undefined when the source invocation is inactive was raised by screamm and answered by the author. I checked that pixel shaders keep the scalar control flow uniform here, and I have nothing new to add. Astronaut10's coverage notes on row_xmask and reconvergence are follow-up scope.

Not verified

  • The GPU execution path itself. On lavapipe, agc_driver_pixel_quad_perm_helpers skips because of the 8-lane subgroup, so I didn't run any pixel quad_perm read on a GPU here. The RADV, AMD Windows, NVIDIA and Intel results come from the discussion, not from my runs.
  • The PPSA99005 title result. I did not launch any title.
  • The validation-layer comparison for agc_driver_pixel_quad_perm_helpers. It skipped, so it reached no draw.

@jackthereaper2006

Copy link
Copy Markdown
Contributor Author

Thanks @XairRrR for the review and the negative control, and @Astronaut10 for testing on RDNA4.

row_xmask: you're right that this PR leaves it out. The quad path only takes quad_perm (DppQuadRead checks control <= 0xff), so row_xmask 1-3 still goes through the shuffle, even though lane ^ mask stays inside the quad. I'd rather keep this PR to quad_perm as it was reviewed, so please don't fold your test in here. Your follow-up after this merges sounds right to me. Once it's up, I can run it on GCN5 (RADV RENOIR), since the other runs so far have been on RDNA.

Fewer broadcasts: agreed. control is a constant, so a uniform pattern needs one OpGroupNonUniformQuadBroadcast, and the three swap patterns map to OpGroupNonUniformQuadSwap. It only affects code size, so I'll do it as a small follow-up instead of changing the head now. @Astronaut10, your follow-up touches the same EmitDppQuadRead. Whichever of us goes second will rebase on the other.

This branch has not been deployed

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

Labels

agc libSceAgc* review queue Next in the maintainer review queue: checks pass, no open change request, no conflict shader core/shader unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.