fix(vk): copy the late pages on the GPU instead of waiting for their write-back #2789
Open
github-sync
wants to merge 2 commits from
github/pr/2789 into main
pull from: github/pr/2789
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2789
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2789"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
On an RTX 3080 in heavy areas (#2655), one capture every frame made the replay wait for the GPU before copying its late pages - guest pages the CPU wrote that a submission still on the GPU writes back. The log showed
a capture taken (ms): all 1.973; GPU wait for late pages 1.957 (100 of 600 takes)at 600 submits a frame, and heavy areas ran at 31-34 fps while the GPU was not the limit. WithRX_VK_CAPTURE_WAIT=0the wait went away and it ran at 57-60 fps, but black flicker appeared. This records those copies on the GPU instead, in the submission that uses the capture, so the game's thread no longer waits for the write-back.Issue: #2655 - RX_VK_CAPTURE_WAIT=0 takes OoT from ~31 to 60 fps on an RTX 3080 (late-page wait every frame) (#2655)
The issue comment from blushOFF (#2655) is accounted for. Their simpler GPU-side attempt copied the pages live from guest memory into the mirror in the upload command buffer, and the Market flashed white in 1 of 4 runs: the CPU had already written a later frame's exposure page, so the GPU added into the wrong sums. This change does not copy into the mirror: it copies the late pages into the capture's own slots, from the imported pool's buffer, in the recording's upload command buffer after the write-back's submission in queue order - the same bytes the old CPU copy took once the wait ended. A CPU read of a page whose copy has not run yet falls back to guest memory (
late_page_pending), and the CPU wait stays wherever the GPU copy cannot be ordered: a page no imported pool holds, a page the texture shadow reads at this take, a burst recording (its uploads would run before its own write-backs), or a parallel replay. On blushOFF's RTX 3070 the wait was rare (6-30 of 600 takes) andRX_VK_CAPTURE_WAIT=0gave no Market gain, so the expected win is on setups like the reporter's (100 of 600); the A/B below will check both, Market included.What changed
gpu/vk/vk_memory.cpp:late_copy_runsmaps each late page, in capture order, to the imported pool that holds it and its 4 KB slot; a page no pool holds, or one whose run passes the pool's end, returns false and the caller keeps the CPU wait.late_copy_planresolves the pages underpools_mu, keeps each pool's import and mirror alive until the copy's submission is done (last_value), skips a plan that the texture shadow reads at this take, caps the pending pages at 4096, and refuses a burst, a parallel replay (RX_VK_PARALLEL> 1), orRX_VK_CAPTURE_GPU_LATE=0.late_copy_flush(first inmemory_begin_submission) records the copies withvkCmdCopyBufferin the recording's upload command buffer, before its first barrier and before the mirror syncs that may read the same capture buffer, then a full barrier; the pages stay listed until that submission's timeline value completes, and a CPU read of one meanwhile reads guest memory (late_page_pending).TRANSFER_DST; the 600-take log line now reports the copies done on the GPU and the GPU waits left separately.gpu/vk/vk_internal.h,gpu/vk/vk_replay.cpp:replay_burst_open()is declared and defined at namespace scope (the fork's first build failed to link it from the anonymous namespace), so a burst's recording, whose uploads run before its own write-backs, uses the CPU wait.tests/test_capture_late_copy.py(new): the runs in capture order, each pool's offsets, the no-pool and past-the-end fallbacks, the plan before the wait, the upload-command-buffer recording, the pending-page read,replay_burst_openat namespace scope, the parallel guard and the cap.tests/test_capture_wait_log.py: updated for the split log line and the fallback counters.Testing
Game-free, Windows, llvm-mingw, in the worktree:
python -I -B tests/run_all.py: 462 passed, 1 failed, 33 skipped.tests/test_input_background.py, a declared known environment failure on this PC (no MSVC SDK; gcc-Wunused-but-set-globalon the fixture). It fails the same way on an untouched upstream main.tests/test_precompile_compute_guard.pyandtests/test_recorder_sync.py, passed in this run.test_capture_late_copyandtest_capture_wait_logpass on HEAD. On upstream51da0464they fail for the missing wiring (test_capture_late_copyfails at once; 3 of 6test_capture_wait_logcases fail).GPU/driver/area: NVIDIA, Windows. Issue reporter: RTX 3080 10 GB, driver
0x9b404000, Windows 10 Pro 22H2; comment: RTX 3070 8 GB, Windows 11; development: RTX 5070 12 GB, driver 32.0.16.2114, Windows 11 build 26200, i5-14600KF. Zone: Vulkan capture and late-page copy (gpu/vk/vk_memory.cpp) on the Windows back end.in-game A/B: pending, results in a comment
AI assistance
Assisted-by: oc-impl-5 (unknown/unknown), oc-upstream-1 (opencode/deepseek-v4.1-flash-free)
In-game A/B (late pages copied on the GPU)
Default replay (RX_VK_PARALLEL=0, event waits/bursts on):
The GPU path never engages on the default replay: take_capture runs inside a burst (replay_burst_open() true), so late_copy_plan always takes the CPU wait; RX_VK_PARALLEL>1 disables it too (gpu_late_on()). N=0, wait counters unchanged; the mean difference is build noise (the change is inert), not a win.
Bursts off (RX_VK_PARALLEL=0; RX_VK_EVENT_WAITS=0):
With bursts off the copy path works: the late-page copy moves to the GPU, the CPU wait drops to 0 and the capture cost falls 0.47 -> 0.004 ms; GPU_LATE=0 restores the CPU wait (0.313 ms) as expected. But that replay mode costs ~14-20% avg FPS (83.6 vs 97.3 default), so no usable win here.
Diagnostic (no numbers claimed): RX_VK_CAPTURE_WAIT=0, default bursts: 118.26 avg / 94.34 1% low / 10.60 p99, capture 0.003 ms, no waits; stale-data mode unchanged, visual flicker not asserted.
Setup: RTX 5070 12 GB, i5-14600KF, Windows 11 26200, 1440p, frame generation on, 240 cap, same save/route, 2 runs per default config, CPU idle checks. DLL-only on the installed v2.1.0 host (MKWVK API 14): the vk change was ported onto the v2.1.0 tree (upstream-main builds are API 19 and cannot load into that host).
I merged this into current main and ran tests/run_all.py. Everything passes except test_lift_cache, which fails on main here too. The tests aren't the problem. Two things are:
p->buffer), which is live guest memory, when the GPU reaches the upload command buffer. That's the race blushOFF hit on #2655: the game thread had already written a later frame's exposure page, and the Market flashed white. Copying into the capture slot instead of the mirror doesn't change which bytes are read. So "the same bytes the old CPU copy took" isn't true: the old copy ran right after the wait, and this one runs later.The A/B was also a port onto v2.1.0, not this head. I'd suggest doing the word-by-word merge against the write-back diff's shadow that blushOFF described, or closing this.