perf(vk): a command buffer too short to split skips the dry pass #2781
Closed
github-sync
wants to merge 1 commit from
github/pr/2781 into main
pull from: github/pr/2781
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/2789
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!2781
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2781"
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
With parallel replay on (
RX_VK_PARALLEL), every command buffer first paid a dry pass - the whole stream applied for state only - and then its one segment was recorded on the replay thread anyway: the same stream walked twice for nothing. A submission of a few hundred draws is one segment, and on a title that submits six a frame that was most of them. This removes that second walk for those short command buffers, a recording cost on the CPU side of the replay path.Issue: #2278 - FPS Drops to 25-30 In-Game but Reaches 60 FPS in Menu (#2278). The report is collected in #995; this change addresses the replay path's share of it, with the measurement still to come.
The default path is untouched: with
RX_VK_PARALLELoff (the default), every command buffer is recorded exactly as before. This change only affects players who turned parallel replay on.What changed
gpu/vk/vk_replay.cpp:op_is_workis the draw/dispatch test the dry pass already counteddry_workwith, now shared.stream_workcounts a stream's draws and dispatches from its op headers without applying anything, stopping at the count asked.replay_handlecounts the stream before the dry pass. When it holds fewer than two segments (2 * RX_VK_PARALLEL_SEG, default 320 draws and dispatches), it records the command buffer once on the replay thread, as parallel replay off does, and skips the dry pass.RX_VK_NO_DRY_SHORT=1brings the old behavior back.split_off(thread-local) keepsmaybe_splitfrom chunking that recording: a segment is not split.tests/test_parallel_small_cb.py(new): checks the source wiring (the op test, the count before the dry pass, the serial recording, the switch,maybe_split) and compilesstream_workagainst a byte stream: counts draws and dispatches, stops at the count asked, ignores ops pastNOP_END, and returns 0 for a state-only stream.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 known environment failure declared in this repo's config on this PC (no MSVC SDK); it fails the same way on an untouched upstream main, proven with clang 23.1.5 (-Wunused-but-set-global).tests/test_precompile_compute_guard.pyandtests/test_recorder_sync.py, passed in this run.test_parallel_small_cbpasses. The verification run also showed the new check fails on upstreamee29dd55for the missingstream_work/op_is_workwiring and passes on the branch head.GPU/driver/area: NVIDIA GeForce RTX 5070 12 GB, driver 32.0.16.2114 (Vulkan 1.4), Windows 11 build 26200, i5-14600KF. Zone: Vulkan command-buffer replay (
gpu/vk/vk_replay.cpp), theRX_VK_PARALLEL=1path only.in-game A/B: pending, results will be added in a comment
AI assistance
Assisted-by: oc-impl-2 (opencode/deepseek-v4.1-flash), oc-upstream-1 (opencode/deepseek-v4.1-flash-free)
In-game A/B (short command buffers skip the parallel replay dry pass)
Path: "N short ones recorded here" 2329 per report with the patch (0 with the fallback), dry pass 0.22 -> 0.17 ms; segments per big command buffer unchanged (2.6-2.7). Replay CPU submit 4.84 -> 4.73 ms/frame (-2.3%), draw 3.69 -> 3.64, memory sync 1.29 -> 1.25. Costs: avg +1.5%, 1% low +4.7%, p99 -0.56 ms; the NO_DRY_SHORT=1 fallback lands within noise of control. No black-frame or exposure anomalies.
Setup: RTX 5070 12 GB, i5-14600KF, Windows 11 26200, 1440p, frame generation on, 240 cap, RX_VK_PARALLEL=4 (owner env), same save/route, 2 clean runs per build, CPU idle checks. DLL-only on the installed v2.1.0 host (MKWVK API 14): the vk_replay change was ported onto the v2.1.0 tree, because an upstream-main build (
51da0464, API 19) cannot load into that host.CI never ran on this head, so I merged it into current main and ran the whole tests/run_all.py with the Vulkan headers. 459 passed. The only failure is tests/test_lift_cache.py, which fails on main on this machine too. I traced the logic. The
stream_work(h, 2 * per) < 2 * pertest at vk_replay.cpp:1531 matches main's one-segment case: nseg is clamped from total / per, and stream_work counts the same ops as dry_work. NOP_JUMP and NOP_CALL do nothing in replay, so walking to NOP_END is linear and safe. The short path is the serial path players already run with parallel off. It fully replaces the old one-segment record_segment, and after a multi-segment buffer the next recording still starts with full_barrier. Turning off maybe_split matches main, where the one segment ran in Worker mode and wasn't split. The in-game A/B is on a v2.1.0 port, not this head, but the path is off by default, so that's acceptable. Heads-up: #2786 edits the same 240-buffer logf block, and the two conflict there.