Skip to content

fix(agc): take RADV's fill-memory kernel as a buffer fill - #6543

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
karanvora2599:fix/agc-radv-fill-memory-hle
Oct 19, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
karanvora2599:fix/agc-radv-fill-memory-hle

Conversation

@karanvora2599

@karanvora2599 karanvora2599 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

The fill HLE now takes RADV's fill_memory compute kernel as a buffer fill. RADV initializes and fast-clears DCC keys with this kernel, so the DCC key tracking now sees those key fills where they are in the command stream. This fixes vkQuake's black frames: for the length of every item-pickup flash, the scene went black and only the HUD was drawn.

Title. vkQuake (PPSA99010): mihawk-99's PS5_vkQuake, rendering through their RADV fork PS5_Mesa (Mesa 25.2).

Cause. On a pickup, vkQuake's r_passes.c runs its screen effects:

  1. The scene renders into color_buffers[1] instead of [0].
  2. A barrier moves color_buffers[0] (0x4007000000, 3840x2160 A2B10G10R10, DCC keys at 0x4008fe0000) from UNDEFINED to GENERAL. RADV's radv_init_color_image_metadata then fills its keys with DCC_CLEAR_0000 through radv_fill_memory.
  3. The screen_effects compute pass reads [1], blends in the bonus-flash colour and writes [0] as a storage image.
  4. The UI pass draws the HUD on [0], and the post-process subpass presents it.

radv_fill_memory uses a compute kernel for 4 KiB and up (here 0x28000 bytes). The kernel stores through a global address, so as an ordinary dispatch its writes name no range: the recorder notes no write over the keys, and WaitForKeyWriters has nothing to wait for. The tracking first saw the new keys once the GPU had run the fill. That was partway through recording the UI pass, after the compute pass had already written [0]. StorageTexture::Refresh then read "uncompressed → 0000" as a fast clear made after those results: it dropped all 510 pending units and cleared the image to black.

Debug traces of one pickup frame (local prints, not in this PR):

  • the dispatch marks [0] dirty while its guest keys still read uncompressed, so no first-write store happens;
  • during the HUD draws the keys turn to 0000, and the refresh drops the results;
  • the only writes the recorder noted over the key range were AnyPS5's own uncompressed stores.

With APS5_STORE_AT_LABEL=1 the pickup frames render correctly, at about 11 fps.

How.

  • MatchClampedGlobalFill (BufferFill.cpp) matches the 16-byte variant of the kernel.
    • Code: 11 GFX10.3 words (v_mad_u32_u24, four v_mov_b32 of the value, v_lshlrev_b32 by 4, v_min_u32 with the largest offset, global_store_dwordx4, s_endpgm).
    • Dispatch: 64x1x1 threads with the group id, six user SGPRs: the address in user[2..3], the largest store offset in user[4] and the value in user[5], which is RADV's fill_constants.
  • Each thread stores 16 bytes at min(thread * 16, user[4]), so the fill covers min(threads * 16, user[4] + 16) bytes.
  • Only a 16-byte aligned address and largest offset are taken, as the other dword-fill matchers require. A two-dimensional dispatch or one in thread dimensions does not match.
  • fillBuffer tries it after MatchConstantDwordFill. From there the existing path applies: storeBufferFill classifies the range, a key fill reaches NoteKeysFill / NoteKeysFillOnGpu, and the fill is recorded on the GPU.
  • The first write after it is then @Celegans12's first-write rule (b8d3060): the compute pass stores uncompressed keys, and the UI pass keeps its results.
  • The matcher and its test follow @AlxcBTW's MatchConstantDwordFill (b83755a).
  • The debt note in libSceAgcDriver.md lists what is not covered:
    • RADV's 4-byte variant (fills below 16 bytes);
    • other Mesa versions' encodings and other compilers;
    • kernel words taken from vkQuake's submissions, not compared with the NIR builder for other targets.

Where it matters. Titles and ports that render through PS5 RADV. The fills RADV runs through this exact kernel now go through the fill HLE instead of a recompiled dispatch: DCC initializations, fast clears and vkCmdFillBuffer, when they use the 16-byte variant with this dispatch shape and a 16-byte aligned address and largest offset. RADV's source allows 4-byte alignment; those fills, and other encodings of the kernel, still run as dispatches.

According to @milescoviello's measurements, Ruffle (PPSA68091), Rush 2 (PPSA69423) and Wave Race 64 (PPSA69424) clear their CMASK, FMASK and DCC with the same kernel. On its own this PR makes Ruffle stop: its CMASK clear to 0xcc now lands before the target's first use, and main reads that code as a DCC key. #6669 reads multisampled CMASK by its MSAA codes and fixes that. @milescoviello and @screamm both ran Ruffle to the limit with this PR and #6669 together, so this PR depends on #6669.

Related: @gjonn's #5497 reads DCC keys behind another fill kernel's fast clear. It is a different kernel; neither PR depends on the other.

Tested

Windows 11, MinGW-w64 GCC 15.2.0, Release. The tests ran on this branch rebased onto main 51cee90; the vkQuake runs used the first head, on main 03c4cb5.

vkQuake (PPSA99010), RTX 4060 Laptop, driver 591.86, 3840x2160, demo loop with r_rtshadows 0 and ANYPS5_PRESENT_MODE=immediate. Each run dumps every second presented frame at 1/16 scale (APS5_DUMP_FRAMES=500 APS5_DUMP_FRAMES_EVERY=2 APS5_DUMP_SCALE=16), and a dump counts as black when its mean RGB is under 3 per channel:

  • main fa9365c (the change does not touch anything that differs between it and 03c4cb5): 25 of 500 dumps black, in two runs of 13 and 12 consecutive dumps, one at each of the demo's first two pickups. In an earlier run of a branch build, 30 of 500 were black, in three runs.

  • this PR: 0 of 500 black.

The demo's first two pickups ("You got the Grenade Launcher", "You get 3 rockets") show the scene under the pickup messages (attached image: top row main, bottom row this PR, at the same pickups). The [dcc-keys] trace now shows title fills keys 0x4008fe0000+0x28000 with 00000000 ... at each fill.

Tests

  • New agc_driver_clamped_global_fill, which submits everything through sceAgcDriverSubmitDcb:

    • Key order (vkQuake's sequence, on a write-watched 128x128 8888 surface with DCC keys):
      1. a 32-lane image store writes the surface under uncompressed keys;
      2. one submission runs RADV's kernel filling the keys with 0000, the same image store, and an end-of-pipe label;
      3. once the label has landed, so the fill has run on the GPU, the keys must read what the image was uploaded under (uncompressed, from the first-write store);
      4. after the write-back, the surface must hold the 128 written bytes over the clear.
    • Range: a flush right after five fills of a guest block (values 0, 0x5a5a5a5a, all ones, a byte ramp, zero groups) reads exactly the covered dwords, so a later reader waits for the fill.
    • Matcher:
      • the kernel matches with its address, length and value;
      • the following do not match: every single-word change, a truncated kernel, a seventh user SGPR, thread dimensions, a two-dimensional dispatch, no group id, an unaligned address, an unaligned largest offset;
      • the length is clamped both by the largest offset and by the threads launched.

    It passes 3 of 3 on the RTX 4060, and on the Intel UHD (VK_LOADER_DRIVERS_DISABLE=*nv-vk*) and lavapipe.

  • Negative check with APS5_NO_FILL_HLE=1, so the kernel runs as on main:

    • the key-order part fails 3 of 3 on the RTX 4060 and once each on the Intel UHD and lavapipe, with once the fill landed, the keys read 0000 against uploaded uncompressed, so the next key read drops the write;
    • the range part fails 3 of 3 with the flush did not wait for the fill's range, the race @fma-rc described.
  • RTX 4060, one at a time, on the first head: agc_dcc_key_sharing, agc_display_buffer_dcc, agc_storage_integer_clear, agc_metadata_pass, agc_driver_cmask_fast_clear, agc_driver_constant_dword_fill, agc_driver_dcc_keys_first_write, agc_driver_depth_fast_clear, agc_driver_depth_fast_clear_submit, agc_driver_depth_htile_write, agc_driver_dword_pattern_fill, agc_driver_fill_cover_keys, agc_driver_memory_value_fill, agc_driver_multisample_fill_host_only, agc_driver_pattern_buffer_fill, agc_driver_pipelined_fill, agc_driver_staged_clear_sampled_read, agc_driver_native_depth_clears, agc_driver_native_depth_clear_refusals and agc_driver_resource_cache_storage_clear pass. On the rebased head, cmask_fast_clear, constant_dword_fill, dcc_keys_first_write, dword_pattern_fill, fill_cover_keys, memory_value_fill, pattern_buffer_fill and pipelined_fill pass again.

  • Intel UHD and lavapipe: constant_dword_fill, dcc_keys_first_write, fill_cover_keys, pattern_buffer_fill and pipelined_fill pass.

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: fix(agc): read a multisampled target's CMASK by its MSAA codes #6669 (see above). perf(agc): match the dword pattern-fill kernel with its two compares in either order #6495 also edits BufferFill.cpp (the dword pattern-fill matcher), and many open PRs add lines to libSceAgcDriver.md; the overlap is textual only.
  • Unblocks: PPSA99010 (black frames on item pickups; it has no Game bring-up discussion)
  • GPUs tested: NVIDIA GeForce RTX 4060 Laptop GPU (Ada), driver 591.86, Windows 11; Intel UHD Graphics (Raptor Lake), driver 101.6790, Windows 11; lavapipe (Mesa 25.2.8), Windows 11
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

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 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5499 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6146 docs/dev/debt/libSceAgcDriver.md
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6292 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6451 docs/dev/debt/libSceAgcDriver.md
PR 6457 docs/dev/debt/libSceAgcDriver.md
PR 6479 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6563 docs/dev/debt/libSceAgcDriver.md
PR 6588 docs/dev/debt/libSceAgcDriver.md
PR 6612 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp
PR 6636 docs/dev/debt/libSceAgcDriver.md
PR 6683 docs/dev/debt/libSceAgcDriver.md
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6730 docs/dev/debt/libSceAgcDriver.md
PR 6762 docs/dev/debt/libSceAgcDriver.md
PR 6786 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6887 docs/dev/debt/libSceAgcDriver.md
PR 6941 docs/dev/debt/libSceAgcDriver.md
PR 6946 docs/dev/debt/libSceAgcDriver.md
PR 6947 docs/dev/debt/libSceAgcDriver.md
PR 6958 docs/dev/debt/libSceAgcDriver.md
PR 6965 docs/dev/debt/libSceAgcDriver.md
PR 6966 docs/dev/debt/libSceAgcDriver.md
PR 6973 docs/dev/debt/libSceAgcDriver.md
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6987 docs/dev/debt/libSceAgcDriver.md
PR 6989 docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 docs/dev/debt/libSceAgcDriver.md
PR 7008 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp
PR 7254 docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7258 docs/dev/debt/libSceAgcDriver.md
PR 7332 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7390 docs/dev/debt/libSceAgcDriver.md
PR 7432 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 docs/dev/debt/libSceAgcDriver.md
PR 7650 docs/dev/debt/libSceAgcDriver.md

@fma-rc fma-rc 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.

The matcher reads the kernel the way RADV writes it (I decoded the 11 words: thread id times 16, v_min_u32 against the largest offset, one global_store_dwordx4), and with the PR agc_driver_clamped_global_fill passes on an RTX 5080, RADV (Radeon 610M) and lavapipe on main 5d9dcc4.

The test does not show the problem the PR fixes, though. With APS5_NO_FILL_HLE=1, so the dispatch takes main's ordinary path, ExecuteTests fails on all three devices with mismatch at 0 ... got 3405691582. With a 200 ms sleep added after FlushGpuWrites, the same run passes 3 of 3 on each device: the ordinary dispatch writes the right bytes, only after the readback. WaitIdle waits for the driver to handle the submission (Lifecycle.cpp:167) and FlushForAccess only syncs on pending writes a descriptor named (Recorder.cpp:1024), so nothing waits for these global stores. The red on main is that race, not the late DCC key tracking. Could the test assert what a later reader sees, for example that the fill's range is noted as a pending write when the dispatch is decoded, so a flush or a key wait syncs on it?

Correction to my earlier note here, which said vkQuake did not get far enough to check: that run had r_rtshadows on. With your settings (r_rtshadows 0, the demo loop, ANYPS5_PRESENT_MODE=immediate, 500 dumps of every second present at 1/16 scale, black meaning a mean RGB under 3) on a laptop with a Rembrandt iGPU (RADV), main 5d9dcc4 gave 25 of 500 black dumps and this PR 0 of 500. The black dumps show only the HUD and the pickup text; the PR's dumps at the same points show the scene. I also ran the ps5-opengl showcase 1.0.2 (PPSA99005), Animal Well and Balatro to their menus on main and with this PR, 90 to 120 s each: same pictures, no stop. PS5_RetroArch 2.0.0-beta-2 (PPSA99169) exits at start on both builds here, so it tells nothing about this change.

@milescoviello

Copy link
Copy Markdown
Contributor

A title that runs this kernel, with one interaction to know about. Ruffle (PPSA68091), Rush 2 (PPSA69423) and Wave Race 64 (PPSA69424) all clear their CMASK, FMASK and DCC with it. Their dispatches match exactly: user data 0, 0, address, address high, last offset, pattern.

The interaction is with Ruffle's multisampled target, whose CMASK it clears to 0xcc every frame. With this kernel as a fill, that clear is ordered before the target's first use, and the CMASK seed then reads 0xcc. main doesn't model that code (0xcc is CMASK_MSAA_FMASK_CLEAR_0_COLOR_EXPANDED, which main reads as a DCC key), so Ruffle stops on it. Without this PR the seed usually reads before the clear lands, so main runs. Linux, RTX 4070 Ti, NVIDIA 595.99, 60 s with scripted pad input, three runs each:

Build Ruffle
main 5d9dcc4 runs to the limit (3 of 3)
+ this PR (2fc4fe5e0) stops 11 to 12 s in: the CMASK 0x4004600000 of a multisampled color target reads as mixed, which is not modeled (3 of 3)
+ this PR + #6669 runs to the limit (3 of 3)

#6669 reads the multisampled CMASK by its MSAA codes, so 0xcc, 0xdd and 0xee count as "no fast clear" like 0xff. With both, Ruffle runs about 1,250 of these fills per minute as buffer fills, and presents stay the same. Merging #6669 first, or together, avoids the regression.

AI-assisted: yes

@Amoenus

Amoenus commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Independent component validation of head 2fc4fe5e0adc822ea8f7689053ec289c765d1926, integrated onto main 5d9dcc42d2ec5401aebd089028b8d4d7410a77b4:

  • Linux composition: d9d5d43. Windows composition: 9c2c17e, additionally containing test(agc): align the indirect arguments block at run time #6452 for the Windows test build.
  • Full Windows build passed. Eight CTest groups, five repetitions per GPU selector, all passed: clamped_global_fill, constant_dword_fill, dword_pattern_fill, memory_value_fill, pattern_buffer_fill, pipelined_fill, fill_cover_keys, and dcc_keys_first_write (all prefixed agc_driver_). This is 80 test processes; longest was 5.54 seconds. Library, convention and test-registration checks passed.
  • For the new fixture, I verified both the preflight and submission device selections in each process: NVIDIA RTX 5080 (10de:2c02, driver 610.62) and AMD Radeon(TM) Graphics (1002:13c0, driver 32.0.21036.18), Windows 11 Enterprise 10.0.26200. Including the initial and restored checks, the new fixture passed 15 processes per physical GPU.
  • Integration omission control 6140697 removes only the new matcher's call from Driver::fillBuffer, keeping its implementation and the original public fixture. That produced the expected output assertion failure five times per GPU. Restoring the exact positive composition passed five times per GPU. This is an omission control, not an unmodified-main comparison.
  • Ubuntu 24.04/WSL2, GCC 13.3, Mesa 25.2.8 llvmpipe: the original fixture target built and passed five repetitions (maximum 1.36 seconds), with both device selections checked. This is selected software-Vulkan coverage, not a full Linux application build.
  • Cppcheck 2.20.0 followed by clang-tidy 23.1.3 completed without diagnostics or parser/configuration failures for BufferFill.cpp and ClampedGlobalFill.cpp (14.87 seconds total analysis).

I also cross-checked the public RADV caller and NIR builder at that pinned source revision. The 64-thread group, clamped offset and repeated value agree with the matcher's range calculation for its guarded 16-byte-aligned subset. This does not establish other compiler encodings.

Two coordination points:

  1. Please qualify the description's “every ... vkCmdFillBuffer of 16 bytes or more”: recognition also requires the exact kernel/dispatch shape and a 16-byte-aligned address and largest offset. RADV's source permits 4-byte alignment; the current matcher deliberately covers a narrower subset.
  2. fix(agc): read a multisampled target's CMASK by its MSAA codes #6669 reports a Ruffle interaction exposed by correctly ordering these fills. Please consider its landing order alongside this PR. The feat(agc): import uniform multisampled DCC fast clears #5939/fix(agc): take RADV's fill-memory kernel as a buffer fill #6543/fix(agc): read a multisampled target's CMASK by its MSAA codes #6669 component combination is now checked; the central report on #6669 records the single conflict resolution, 70 passing Windows processes, Linux selected checks and test proposal.

These are bounded public component tests; I did not rerun vkQuake, Ruffle or Sonic, and make no new title/FPS claim. Project shader caching was disabled; driver caches were not controlled. AI-assisted validation and report: yes.

Carry-forward validation on main585, 2026-10-17 UTC

Your exact2fc4 commit is now carried with original authorship into fork integration6146eef46b, on main5859889 plus our retained bring-up fixes. The historical broad stack already had an equivalent11-word constant-fill matcher; our narrower integration had omitted it. This restores the existing PR's implementation rather than creating a duplicate.

Full Windows/Linux relinker/library builds pass. clamped_global_fill, constant_dword_fill, fill_cover_keys and4-sample msaa_dcc_clear each pass3/3 on verified NVIDIA RTX5080 (10de:2c02, driver32.0.16.1062) and AMD Radeon Graphics (1002:13c0,32.0.21036.18), Windows11 Enterprise26200/GCC15.2r7:12 passes per GPU, zero skips. The same groups give12 passes on Ubuntu24.04/GCC13.3/llvmpipe LLVM20.1.2. Selected Cppcheck/clang-tidy parse both changed TUs cleanly; conventions pass.

A normal Sonic PPSA99902 RTX5080 run with all127 rebuilt libraries still reaches NOW LOADING and the resident8x DCC refusal at 0x40819f4000+0x87000 after28.101s. Seven captures, no input/timeout, exit0xC0000409. Standard a515 conversion and the isolated prior runtime assets/user state were reused; no fresh-state or isolated-PR causal comparison is claimed. Protected originals remain unchanged;142 prior code/frame/user/config files were hash-verified restored, and new code/captures are separately retained. This run has no producer observer, so it supplies no exact program-address attribution. #5336 tracks the remaining resident-clear transition. No AMD title run or gameplay result is claimed.

AI-assisted validation/reporting: Codex; author responsible.

@screamm

screamm commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Windows cross-check of this PR alone and with #6669, on main 5d9dcc4 (this PR 2fc4fe5e0, #6669 5fb0245a6, merged without conflicts). Windows 11 Pro 26220, RTX 4070, NVIDIA 596.21, WinLibs GCC 15.2.0, Release. Titles relinked per run, 125 s limit.

Ruffle (PPSA68091), Cross pressed at 8, 11 and 14 s ("Play the demo"):

Build Result
main runs to the limit, 3 of 3 (604 to 897 frames)
+ this PR stops 2 of 2, at 12.6 s and 23.2 s, exit 0xC0000409, right after the demo's game loop starts: what(): AGC graphics: the CMASK 0x4004600000 of a multisampled color target reads as mixed, which is not modeled
+ this PR + #6669 runs to the limit, 2 of 2 (783 and 873 frames)

So the Linux result above reproduces on Windows/NVIDIA: this PR should land together with or after #6669. With both, the demo's presented frames look the same as on main (on this busy host the demo runs at about 4 fps and presents black on main too; that is not from either PR).

vkQuake (PPSA99010), shareware data, demo loop, r_rtshadows 0, ANYPS5_PRESENT_MODE=immediate, 500 dumps of every second present at 1/16, black = mean RGB under 3:

Build Black dumps
main 48 and 53 of 500 (HUD and pickup text only)
+ this PR 0 and 0 of 500
+ this PR + #6669 0 and 0 of 500

One run with this PR stopped at 107.9 s on guest texture descriptor uses corner sampling which is not implemented; the same line also appears in an unrelated main run of vkQuake on this machine, so I don't count it against the PR.

Tests: agc_driver_clamped_global_fill 3 of 3 alone and on the stack. With the MatchClampedGlobalFill call removed from fillBuffer it fails 3 of 3 with mismatch at 0 (groups 1, largest offset 1008): got 3405691582, expected 0, the same race @fma-rc describes, so I agree the execution test doesn't show the key-ordering fix. On the stack, 12 related agc_driver_ tests (fills, DCC keys, CMASK, multisample) pass 3 of 3; agc_driver_store_over_untracked is flaky here on main too (46 of 65 pass on main, 23 of 45 on the stack, 13 vs 12 of 20 when interleaved). Non-GPU suite on the stack: 834 of 837 pass, 2 skipped, hardware_oracle_compare hit its 20 s ctest limit once and passed on rerun. check_conventions.py and git diff --check clean.

Not checked: AMD or Intel, Linux, the full GPU agc_driver_ suite, Rush 2 / Wave Race 64.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@milescoviello

Copy link
Copy Markdown
Contributor

A data point for this PR, on Linux with an RTX 4070 Ti (NVIDIA 595.99.02), base main 5859889.

With this PR (2fc4fe5), the CMASK and DCC clears of Rush 2 (PPSA69423), Wave Race 64 (PPSA69424) and Ruffle (PPSA68091) arrive as buffer fills. Without it, the multisampled target is imported before its clear lands, and all three stop at multisampled DCC CMASK compression or register clear is not implemented.

With #7250 on top, which takes those fills as the target's fast clear:

AI-assisted: yes (Claude Code)

@karanvora2599

Copy link
Copy Markdown
Contributor Author

Thanks, all. I've pushed 7bfb645, rebased onto main 51cee90.

@fma-rc, you're right: the old execution test failed on main only because of the read-back race, not because of the key order. The test now does vkQuake's sequence directly:

  1. a 32-lane image store writes a 128x128 surface with DCC keys;
  2. one submission runs RADV's kernel filling the keys with 0000, the same store, and an end-of-pipe label;
  3. once the label has landed (so the fill has run on the GPU), the keys have to read what the image was uploaded under, and the write has to survive the write-back.

With APS5_NO_FILL_HLE=1 it fails every time: 3 of 3 on an RTX 4060, and once each on an Intel UHD and lavapipe. The message is the keys read 0000 against uploaded uncompressed, so the next key read drops the write, which is the vkQuake failure. With the PR it passes on all three. The range part stays, now worded as what it checks: a flush right after the fill reads the filled dwords. Thanks also for the vkQuake run on the Rembrandt iGPU.

@milescoviello, @screamm, thanks for finding Ruffle. I've added #6669 under Depends on and noted why in the description: this PR orders Ruffle's 0xcc CMASK clear before the target's first use. Thanks too for Rush 2 and Wave Race 64, and for the Windows vkQuake numbers.

@Amoenus, done: the description now says only this kernel shape with a 16-byte aligned address and largest offset is taken, and that RADV's 4-byte-aligned fills and other encodings still run as dispatches.

@karanvora2599

Copy link
Copy Markdown
Contributor Author

vkQuake (PPSA99010) at the demo's first two pickups, from the dense dumps in the description (every second present at 1/16 scale). The top row is main fa9365c: the scene is black and only the HUD and the pickup text are drawn. The bottom row is this PR at the same pickups: the scene is drawn under the pickup messages.

vkQuake pickups: main (top) vs this PR (bottom)

@fma-rc fma-rc 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.

Reran agc_driver_clamped_global_fill on 7bfb645 on the Rembrandt iGPU (680M, RADV). With APS5_NO_FILL_HLE=1 it failed 3 of 3 with the keys read 0000 against uploaded uncompressed, so the next key read drops the write; without it, it passed 3 of 3. The test now fails for the vkQuake reason and not the read-back race, which was my objection, so I'm clearing my earlier review. I have not run it on NVIDIA or lavapipe this time.

@screamm

screamm commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Rechecked on head 7bfb645ff, merged onto main 0d078f75d without conflicts. Windows 11 Pro 26220, RTX 4070 (driver 596.21), WinLibs GCC 15.2.0 r7, Release.

Not checked: AMD, Intel, Linux, Rush 2, Wave Race 64.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@milescoviello

Copy link
Copy Markdown
Contributor

An update to my data point from yesterday, on the current heads. #7253 is in main now, so these two titles need only this PR and #7250. Linux, RTX 4070 Ti (595.99.02), 60 s with pad input:

Libraries Rush 2 (PPSA69423) Wave Race 64 (PPSA69424)
main 13e48ed stops at 2 s: multisampled DCC CMASK compression or register clear is not implemented same
main 385ed71 + this PR stops at 2 s: writes to a resident multisampled DCC target or its metadata are not implemented same
main 5057673 + this PR (7bfb645) + #7250 (5be36f0) runs to the limit, ends on its "Locate ROM / Exit" launcher presents until a segfault in guest code at 48 s

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added unblocks title Names a title ID on the Unblocks line needs rebase Conflicts with main, rebase needed labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
RADV initializes and fast-clears DCC keys with its fill_memory compute
kernel, which stores through a global address. Run as an ordinary
dispatch, its stores name no range. The DCC key tracking therefore saw
a key fill only once it had landed in guest memory, which could be
after later writes to the surface had already been recorded.

vkQuake (PPSA99010) hits this on every item pickup:
- the scene renders into a second color buffer;
- RADV's UNDEFINED -> GENERAL barrier fills color buffer 0's keys with
  DCC_CLEAR_0000;
- a screen-effects compute pass writes color buffer 0;
- the UI pass draws on color buffer 0.
The keys landed while the UI pass was being recorded. The refresh took
them for a clear made after the compute pass, dropped its results and
cleared the image to black. For the length of the pickup flash the
scene was black, with only the HUD drawn.

The fill HLE now matches the 16-byte variant of the kernel as a fill of
min(threads * 16, user[4] + 16) bytes. The kernel takes its address in
user[2..3], its largest store offset in user[4] and its value in
user[5]. A key fill through it is handled where it is in the command
stream.
@adriadam10
adriadam10 force-pushed the fix/agc-radv-fill-memory-hle branch from 7bfb645 to 2907ca6 Compare October 19, 2026 06:35
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@adriadam10
adriadam10 merged commit 75ec3b5 into boykopovar:main Oct 19, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (only conflict: the libSceAgcDriver.md debt adjacency) and rebuilt; agc_driver_clamped_global_fill and the rest of the suite pass on the rebased head. The matcher, its alignment and dispatch-shape guards and the key-order test check out, and the independent runs in the thread cover NVIDIA, RADV and lavapipe. Merging through the queue.

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

Labels

agc libSceAgc* docs unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants