WIP: perf(vk): the async present ring and event-gated readback delivery (RX_ASYNC_RENDER, RX_WB_RB_EVENTS), ported to current main #2910
Draft
blessedplummer
wants to merge 7 commits from
blessedplummer/Hookshot:async-port into main
pull from: blessedplummer/Hookshot:async-port
merge into: Hookshot:main
Hookshot:main
Hookshot:release/v2.2.0-rc.1-forgejo
Hookshot:release/v2.1.0-forgejo
Hookshot:github/pr/2893
Hookshot:github/pr/2890
Hookshot:github/pr/2887
Hookshot:github/pr/2878
Hookshot:github/pr/2875
Hookshot:github/pr/2874
Hookshot:github/pr/2873
Hookshot:github/pr/2871
Hookshot:github/pr/2756
Hookshot:github/pr/2897
Hookshot:github/pr/2896
Hookshot:github/pr/2718
Hookshot:github/pr/2842
Hookshot:github/pr/2691
Hookshot:github/pr/2895
Hookshot:github/pr/2743
Hookshot:github/pr/2765
Hookshot:github/pr/2708
Hookshot:github/pr/2732
Hookshot:github/pr/2755
Hookshot:github/pr/2817
Hookshot:github/pr/2876
Hookshot:release-please--branches--main
Hookshot:release-please--branches--main--release-notes
Hookshot:github/pr/2778
Hookshot:github/pr/2888
Hookshot:github/pr/2866
Hookshot:github/pr/2877
Hookshot:github/pr/2800
Hookshot:github/pr/2829
Hookshot:github/pr/2850
Hookshot:github/pr/2889
Hookshot:github/pr/2882
Hookshot:github/pr/2860
Hookshot:github/pr/2849
Hookshot:github/pr/2886
Hookshot:github/pr/2794
Hookshot:github/pr/2840
Hookshot:github/pr/2830
Hookshot:github/pr/2834
Hookshot:github/pr/2719
Hookshot:github/pr/2742
Hookshot:github/pr/2741
Hookshot:github/pr/2820
Hookshot:github/pr/2771
Hookshot:github/pr/2859
Hookshot:github/pr/2858
Hookshot:github/pr/2823
Hookshot:github/pr/2757
Hookshot:github/pr/2769
Hookshot:github/pr/2844
Hookshot:github/pr/2843
Hookshot:github/pr/2673
Hookshot:github/pr/2855
Hookshot:github/pr/2739
Hookshot:github/pr/2807
Hookshot:github/pr/2851
Hookshot:github/pr/2789
Hookshot:github/pr/2814
Hookshot:github/pr/2818
Hookshot:github/pr/2774
Hookshot:github/pr/2772
Hookshot:github/pr/2750
Hookshot:github/pr/2786
Hookshot:github/pr/2785
Hookshot:github/pr/2782
Hookshot:github/pr/2779
Hookshot:github/pr/2775
Hookshot:github/pr/2706
Hookshot:github/pr/2766
Hookshot:github/pr/2747
Hookshot:github/pr/2759
Hookshot:github/pr/2745
Hookshot:github/pr/2740
Hookshot:github/pr/2733
Hookshot:github/pr/2725
Hookshot:github/pr/2727
Hookshot:github/pr/2728
Hookshot:github/pr/2729
Hookshot:github/pr/2721
Hookshot:github/pr/2722
Hookshot:github/pr/2715
Hookshot:github/pr/2714
Hookshot:github/pr/2707
Hookshot:github/pr/2703
Hookshot:github/pr/2705
Hookshot:github/pr/2697
Hookshot:github/pr/2679
Hookshot:github/pr/2676
Hookshot:github/pr/2669
Hookshot:docs/what-hookshot-connects-to
Hookshot:fix/hdr-encode-test
Hookshot:fix/main-tests
Hookshot:fix/play-cache-files
Hookshot:perf/game-barrier-order
Hookshot:feat/heap-lazy
Hookshot:feat/texture-detail
Hookshot:perf/astc-opaque-bc1
Hookshot:fix/fs-dir-lock
Hookshot:fix/framegen-native-kept
Hookshot:fix/update-no-downgrade
Hookshot:port/k10-gcn-f16-mirrors
Hookshot:fix/framegen-mult-cap
Hookshot:chore/release-as-2.1.1
Hookshot:perf/pcache-load-switch
Hookshot:fix/prepare-progress-log
Hookshot:fix/update-closes-stale-game
Hookshot:fix/hdr-scaled-sampled
Hookshot:perf/first-start-preset
Hookshot:perf/zero-upload
Hookshot:fix/vcore-escape-2s
Hookshot:test/fake-gh-atomic
Hookshot:perf/batch-idle-flush
Hookshot:test/finish-stub-a
Hookshot:chore/record-2.1.0
Hookshot:fix/main-red-tests
Hookshot:fix/test-display-startup-driver-cache
Hookshot:feat/mesh-shader-mode
Hookshot:fix/menu-probes-at-startup
Hookshot:fix/fps-counter-classic
Hookshot:fix/test-log-batch-flake
Hookshot:fix/narrow-screen-hud
Hookshot:ci/prereleases
Hookshot:release/v2.0.15
Hookshot:hotfix/v2.0.15
Hookshot:fix/present-ahead-replay
Hookshot:release/v2.0.13
Hookshot:fix/bracket-paths-2013
Hookshot:fix/hdr-overlays-2013
Hookshot:fix/fps-counter-scale-2013
Hookshot:fix/stick-seen-at-rest
Hookshot:hotfix/menu-fits-screen
Hookshot:fix/f1-always-opens-menu
Hookshot:fix/ini-keep-lines
Hookshot:claude/cranky-pike-7cf247
Hookshot:perf/texture-answer-flat
Hookshot:fix/stamp-test-modules
Hookshot:fix/appimage-fonts
Hookshot:fix/display-awake
Hookshot:release/v2.0.12
Hookshot:fix/discord-windows
Hookshot:rc/v2.0.12-tests
Hookshot:fix/dlss-keep-feature
Hookshot:rc/v2.0.12
Hookshot: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
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
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
Hookshot/Hookshot!2910
Loading…
Reference in a new issue
No description provided.
Delete branch "blessedplummer/Hookshot:async-port"
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: the game's job threads stall on readbacks — invalidate()'s wait goes through the replay queue's catch-up, and every readback rode whole submissions until the per-part wait landed. This carries the async-render work as optional, default-off switches so it can be measured alone: a deeper present ring whose presenter paces presents on the display's vblank grid, and readback delivery that copies written binds into guest memory right after their pass, event-gated, so the CPU polls an event instead of waiting the submission.
What it does (every switch default OFF — with them off, main's paths run untouched):
RX_ASYNC_RENDER=1(+RX_ASYNC_FIF, 3..8, default 4): the present ring keeps N frame slots, the game's release event fires from its present (host/nvn_native.c,rx_release_due), the presenter thread lands those presents on the display's vblank grid, and invalidate()'s readbacks wait only their own write-back's timeline value — per-value ordering, never the replay queue. A phase-2 retry lets a submission parked at an event wait go on before the wait re-runs. A per-frame poll-only ledger (vk: async f=) measures every remaining wait; a per-frame summary reports fence/acquire waits and busy.RX_WB_RB_EVENTS=1: written binds whose range touches the readback history (the meters and counters the game reads every frame) are delivered into guest memory right after the pass that binds them — their write-back copy made early, each with its own event (memory_rb_deliver,memory_writeback_event); the CPU's readback polls the event before waiting the covering value.RX_VK_CAP_EAGER(the capture's pool list includes the mirror-wanted and not-yet-bound pools — closes the mirror-sync live-read race at import),RX_WB_RB_DIRECT(prototype: readback pages copy straight from the mirror),RX_VK_RB_EVER_PAGES,RX_VK_COND_CPU_WORDS.Testing status: incomplete — a draft on purpose.
test_readback_part.py's gate follows the ported choice block in this branch (the fixture stubs the async additions with the switch off; every assertion on the per-part wait is unchanged).test_appimage_package_retryfails in this Linux container — not yet checked against main on the same box, so its cause is unattributed so far.RX_VK_IMMEDIATE) and above the 60 Hz refresh on this GPU — main 65.30 median, port 60.00 flat in every run. That pair says nothing about the readback win; a 4K A/B where both arms sit below refresh is running now and its numbers will land in this description as a comment.RX_ASYNC_RENDERon, presents pace to the display's refresh; at loads above refresh that reads as a cap next to main's unpaced presents. An opt-out (present unpaced) may be worth a switch of its own if wanted.Row added to docs/tested-configurations.md for the 1080p runs.
The CPU frame loop round-tripped on a 3-frame-old present fence and on the 48-slot recording ring, and nothing reported how far ahead of the GPU the CPU actually ran, so "is the replay asynchronous?" could only be argued, not read. With RX_ASYNC_RENDER=1 the present path keeps RX_ASYNC_FIF frame slots (default 4, clamped 3..8; 3 when the switch is unset), so the fence a present waits for belongs to a slot several frames back, and every wait this path can take is timed on its thread and reported once a frame ("vk: async f= ... key=value", plus a 60-frame "vk: async summary" in the format results/glm's async-analyze.py already reads): present fence, acquire, recording-slot, nvnSyncWait, nvnQueueFinish, the readback's write-back wait, and the replay's own phase totals, with progress polled from timeline/rec_timeline (never waited on). Hardware: nvdrv.c's "GPFIFO submissions run as they are made" with a deep command-buffer queue (Lichii NX2_NOTES row 59: ~96 CBs in flight), cross-queue sync kept GPU-side as timeline releases, and nvnQueueWaitSync staying an ordered no-op for the CPU. Measured (RX 9070 XT, 6,200+ gameplay frames per run): lagv (command buffers CPU ahead of GPU) >= 4 for 4,172 consecutive frames, fifo >= 1 throughout; the render path's worst blocking wait 0.663 ms (budget 2 ms); the host's own "nvn: GPU waits in 5 s" showing 0 sync waits / 0 ms; stock GPU-bound wait 0.1 ms both with and without the flag; fps at parity (median 51.9 vs 50.5). Unset is stock: nothing else changes, frames_n stays 3, no timers run, no per-frame line prints, and the suite matches pristine55b976c0exactly (440 passed, 1 failed (upstream's own test_env_on), 33 skipped). Two test needles were updated for the loop's renamed index variable and to stub the ledger's timing line; their checks are unchanged. Co-authored-by: GLM-5.3-Flash <[email protected]>db35b1224e28bc6dee4K interim results (1 of 3 pairs) — parity so far.
At 4K both arms sit below the display's refresh, so the vblank-pacing confound from the 1080p runs (above) does not apply. First pair, 360 s each, warp-normalized Hyrule Field (windows = D >= 1000 draws):
db35b122(base-r1)Delta +0.00%, bootstrap 95% CI [-0.19 .. +0.20] (20k resamples). 19,205 async-ledger frames; mechanism health at 4K: present-fence max 0.047 ms all-time (0 frames over 2 ms), acquire max 0.160 ms, fifo_max 1, busy 69%; no faults, no device lost, no validation lines.
So far the readback-delivery win this branch carried from its original base does not reproduce on current main — consistent with main having absorbed the stall since (#2422's readback-aware submit flushing, the per-part wait, the occlusion-count fix). Two more pairs are running; the pooled 3-pair verdict lands here when they finish.
Measurement stopped here (author's call) — 1 of 3 pairs, and the verdict it gives is the honest one: parity.
The remaining pairs were cut: the first 4K pair (above) is +0.00% with a 95% CI of [-0.19 .. +0.20] over 231 pooled windows, and no further run was going to move a bound that tight. The readback-stall win this branch carried from its original base (+4.33% there) does not reproduce on current main — main absorbed the stall since, with #2422's readback-aware submit flushing, the per-part wait, and the occlusion-count fix. That is main getting better, and this branch declining to claim otherwise.
What the branch still offers, for the maintainers' judgment:
Leaving this open as WIP rather than closing: the port recipe (single merge onto main, one resolution pass), the gate fix and the pacing finding are on the record, and the branch is one command away from a re-measure if main's balance shifts again. If it is just noise at this point, close it with a clear conscience — the numbers above are the whole claim.
In conclusion, with the current, there is probably not a whole lot to gain performance-wise from this PR. I'll leave it here for reference in case any ideas here have some value. Otherwise, feel free to close it.
View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.