Repository navigation
perf(agc): take the HTILE words pending fills leave instead of reading them - #6505
Conversation
60f9861 to
d6b25f2
Compare
|
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.
|
|
Windows / AMD results for this head ( Host: Windows 11 Pro 26200, Ryzen 7 5800X3D, Radeon RX 9070 XT (AMD proprietary driver 32.0.23033.1002), 32 GB, WinLibs GCC 15.2.0 r7, Release. What the regression looked like. ASTRO BOT (PPSA21564 01.007.000), The recorder syncs grew from 6.5 % to 26 % of the worker. As a diagnostic I made This PR. Same tree with this PR merged (clean), one relinked executable, toggling only
So this title's pending writes over its depth targets' HTILE are noted fills, and the bind no longer waits for the GPU. Frames. GPU frame dumps ( No stops or errors in the runs above. AI-assisted (Claude Code); the builds and runs are on my machine. |
|
Data point from Planet of Lana (PPSA19450) on Windows with NVIDIA. With this PR, the first meadow checkpoint goes from 7.06 to 8.89 fps. Before #5627, the same spot ran 12.7. Host: Windows 11 Home 10.0.26300, AMD Ryzen 9 5900HS, GeForce RTX 3070 Laptop GPU (NVIDIA 617.42), 15.4 GB RAM, WinLibs GCC 15.2.0 r7, Release. Trees:
Scene: a save in the first meadow, loaded through PLAY / Slot 1 / Resume, then standing with no input.
Runs with
At the checkpoint, the frame matches the base's: no stale depth and no missing clears. The reads that remain look like the bind-time store-back reading the whole HTILE after a fill's batch has been reaped. On Windows, this title's 640 KiB HTILE import is under the 1 MiB settle floor ( |
|
Added a depth and stencil case to
Note for reviewers: #6764 implements the same idea (taking the fast clear from the pending HTILE fill instead of reading it). |
|
Tested this on Windows (RX 9070 XT, AMD 26.8.1) with Stellar Blade, merged on top of current main together with other draw-pipeline work. The game binds a 2564x1444 depth target every frame, so before this PR every bind read back its 288 KiB of HTILE on the CPU and waited for the GPU to do it. Measured with
That's about 18% more frames in the menu and about 15% more in gameplay, and the game felt clearly smoother while playing. One ~8 minute run, no pipeline failures, and nothing looked wrong on screen. The PR's depth fast clear test passes on the merged tree and fails with 🤖 Generated with Claude Code |
|
Tested on GTA V (PPSA04264) in the open world (Los Santos, a story save resumed), Windows 11, RTX 4090 (driver 616.92), i9-13900K. Both HTILE PRs were measured on the same base, one run each, in the same session:
A committer profile of the base (sampling the AI-assisted (Claude Code). |
|
Data point from Linux / NVIDIA: on Diablo II: Resurrected (PPSA03912), this PR removes every HTILE wait, giving 47 → 60 presents per 10 s (+28%). Host: Ubuntu 26.10 (kernel 7.3), Ryzen 9 5950X, RTX 3080 Ti (NVIDIA 615.71.09 open), GCC 15.3.0, Release. Trees: Scene: in game with a saved character loaded, no input.
AI-assisted; the runs are from my machine(s). |
|
Linux / AMD data point on a Unity title: Subnautica (PPSA02453). CachyOS kernel 7.2.2, Ryzen 9 5950X, RX 6650 XT / RADV Mesa 26.2.4, Release, no env switches. Same spot in both runs: the burning lifepod right after the intro, standing still, Before this PR, gdb samples of the committer thread (
About 2x at this spot (~18k draws per frame). Both PRs merged cleanly onto AI-assisted (Claude Code). |
…g them A depth target's bind (storeBackClearedHtile) reads its HTILE words to find a fast clear and stores them back expanded. When a recorded fill over the HTILE was still pending, that read made the flush hook submit the open batch and wait for the GPU, under the GPU mutex. A title that clears its depth targets with buffer fill kernels pays that wait at every clear. Uniform fills now note their 32-bit word with the pending write (NotePendingWordFill; the byte value the DCC key paths use is unchanged). When every pending write over the HTILE is such a fill and the rest of the range already holds the same word, the bind takes the landed word from the notes, and stores the expanded words as a GPU fill recorded behind the pending ones. Any other pending write keeps the read. APS5_NO_PENDING_HTILE_FILLS=1 always reads. PPSA12544 on a Radeon 780M (Windows): 1271 pending-write syncs per 10 s waited 5.5 s at the bind, about 4.3 ms each; with this change there are none, and the scene runs at 60-61 fps instead of 44-46.
…ll leaves RecordedFillClearsWithoutWait now also runs with a D32_SFLOAT_S8_UINT target: a recorded SMem 0 fill (0xf0) over the HTILE clears depth and stencil at the bind without a GPU wait, and is stored back as expanded (0xfffff3ff). The stencil case alone fails with APS5_NO_PENDING_HTILE_FILLS=1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
7918fd4 to
b04f7b8
Compare
What
A depth target's bind (
storeBackClearedHtile) reads the target's HTILE words to find a fast clear, then stores them back expanded. When a recorded fill over the HTILE is still pending, that read makes the flush hook submit the open batch and wait for the GPU while theaps5-trace-0c4816f6119e-896.json.gz
aps5-trace-3dc3324c60ed-17292.json.gz
GPU mutex is held. A title that clears its depth targets with buffer fill kernels (the fill HLE) pays this wait at every clear. In #6045 that was 1271 syncs per 10 s at about 4.3 ms each, 5.5 s per 10 s with the lock held.
The change follows the DCC key path (
KeysAfterPendingWrites/StoreUncompressedOnGpu):VulkanDevice::FillBuffernotes a uniform fill with its 32-bit word (Recorder::NotePendingWordFill).PendingWritesOverreports the word next to the byte value, and the byte value the DCC key paths read is unchanged.storeBackClearedHtile: when a write is pending over the HTILE, it works out the landed word from the notes. That needs every pending write over the range to be a noted fill, and the bytes outside them to hold the same word. When the landed word is a clear, the expanded words are recorded as a GPU fill behind the pending fills, instead of being read and stored on the CPU. Any other pending write keeps the read, as before.APS5_NO_PENDING_HTILE_FILLS=1always reads, as before.DepthFastClearSubmitgets a case for this. A fill is recorded over a bound target's HTILE, then the target is drawn. The case checks that the depth is cleared, that the bind did not wait for the GPU (Recorder::ThreadHookWaits), that the HTILE holds the expanded words afterwards, and that a second draw keeps its depth.Tested
Windows 11 Home 10.0.26100, AMD Radeon 780M (Ryzen Z1 Extreme), AMD driver 25.10.20.01, WinLibs GCC 15.2.0 r7, Release. The change was measured on
main0c4816f (commit 3dc3324c60ed). It has since been rebased onto 03c4cb5 without conflicts, and both depth fast clear tests below pass 3 of 3 there.agc_driver_depth_fast_clear_submit_tests: passes 3 of 3. The new case fails withAPS5_NO_PENDING_HTILE_FILLS=1(the old path) at "the bind of the depth target waited for the GPU to run the fill recorded over its HTILE".agc_driver_depth_fast_clear_testspasses 3 of 3, andagc_driver_dcc_keys_first_write_testspasses.agc_driver_recorder_testscases pass:--pending-key-stores-only,--shadowed-key-stores-only,--pending-write-snapshot-only,--space-pending-writes-onlyand--storage-cpu-snapshot-only. The full run stops at "(9) a scan under a pending write was kept", onmainas well as with this change.main, which stays below 60 either way). Ten runs had no crash and no error in the log: the trace run below on exactly this commit, and nine runs of this change together with an unrelated local patch (APS5_STORE_QUIET_AT_FLIP, see Hollow Knight: Silksong (PPSA12544) #726). The inventory opens and closes back to the world, and the frames dumped at the bench look the same as onmain.Summary lines from the same machine and scene,
mainfirst and this PR second (APS5_THREAD_PRIORITY=highon both):{"commit":"0c4816f6119e","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_THREAD_PRIORITY":"high","APS5_TRACE_SECONDS":"90:20"},"gpu":"AMD Radeon Graphics","driver":"AMD proprietary driver 25.10.20.01 (LLPC)","os":"Windows 10.0.26100","resolution":"1920x1080","window":{"spec":"seconds 90:20","first_present":5620,"ms":20003.059},"presents":906,"frame_ms":{"p50":22.061,"p90":22.812,"p99":23.258,"max":23.637},"gpu_busy_ms_per_present":14.563,"worker_busy_pct":{"queue worker 0x0":98.792},"top_phases_ms_per_frame":{"Driver.Packet":21.787,"Driver.Packet.device_lock":9.602,"Driver.Packet.draw":8.813,"Driver.Draw":8.742,"Driver.Draw.draw_enqueued":6.876,"Driver.Present":3.671,"Driver.Packet.dispatch":2.054,"Driver.Present.gpu_mutex_wait":1.752},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":15.254,"events_lost":82203,"totals_full":false}},"trace":"aps5-trace-0c4816f6119e-896.json.gz"} {"commit":"3dc3324c60ed","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_THREAD_PRIORITY":"high","APS5_TRACE_SECONDS":"90:20"},"gpu":"AMD Radeon Graphics","driver":"AMD proprietary driver 25.10.20.01 (LLPC)","os":"Windows 10.0.26100","resolution":"1920x1080","window":{"spec":"seconds 90:20","first_present":6964,"ms":20009.982},"presents":1222,"frame_ms":{"p50":16.119,"p90":20.329,"p99":21.218,"max":22.043},"gpu_busy_ms_per_present":13.082,"worker_busy_pct":{"queue worker 0x0":86.160},"top_phases_ms_per_frame":{"Driver.Packet":14.085,"Driver.Present":9.074,"Driver.Packet.draw":7.632,"Driver.Draw":7.530,"Driver.Draw.draw_enqueued":5.061,"Driver.Present.acquire_image":4.782,"Vulkan.Acquire":4.780,"Vulkan.Acquire.acquire_fence_wait":4.623},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":8.539,"events_lost":352876,"totals_full":false}},"trace":"aps5-trace-3dc3324c60ed-17292.json.gz"}In this capture the presented rate sits at about 60 fps, with most of the frame time in
Vulkan.Acquire.acquire_fence_wait. A run started while the display was at 144 Hz, where the title can take its 119.88 Hz output mode, reaches 69-73 fps in the same scene. WithAPS5_PROFILE_DRAW=1, the[hooksync]line is gone and the untagged[lock]line reportslocked GPU waits 0 / 0 ms. Onmainthat line read 2161 / 5424 ms per 10 s.Traces:
aps5-trace-0c4816f6119e-896.json.gzandaps5-trace-3dc3324c60ed-17292.json.gz(attached).Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)