Perf: steadier frame rate #1109
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1109
Loading…
Reference in a new issue
No description provided.
Delete branch "perf/steadier-frame-rate"
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?
Hookshot PR: steadier 60 fps
On current upstream main (v2.0.8-rc.1), this PR takes Hyrule Field at 3440x1440 with 4x frame generation from 47.7 to 59.6 fps standing and from 44.2 to 57.3 fps running around. Without frame generation, running around goes from 55.2 to 58.8 fps.
Results
Standing now sits at the game's 60 fps cap, and running around keeps about 98% of frames under 22 ms.
GPU time fell from ~12.9 to ~9.4 ms a frame with 4x frame generation, and from 12.3 to 8.6 ms without it. Measured on upstream main
8b2e1d3against the same commit plus this PR: i7-14700K, RTX 5070 Ti, 48GB DDR5-7600, Windows 11, 175 Hz monitor, windowed, Hyrule Field save, scripted 20 s standing then 28 s running. Ranges are separate runs.Changes
Two renderer changes carry the gain. Switching each off in turn on upstream main shows its share.
gpu/vk/vk_memory.cpp, newgpu/vk/shaders/wb_diff.comp). Each frame's last submission copied every written storage-buffer range back to guest memory whole: ~69 MB, 4.4 of ~13 ms of GPU time. Pools written back now keep a VRAM shadow of guest memory, and a compute pass writes only the 16-byte vectors that differ, a subgroup's span at a time (word-by-word stores to system memory ran at ~1 GB/s). Pages the CPU wrote since are re-read into the shadow first, so the pass only ever writes fewer words than the copy did. Off:RX_VK_WB_DIFF=0. Its share: GPU 13.1 → 9.2 ms a frame; running 53.9 → 57.9 fps with 4x frame generation and 55.2 → 58.7 fps without. Readback refreshes also copy adjacent pages as one region (~530 single-page copies a frame before).vk_main.cpp,vk_replay.cpp,vk_internal.h,vk_framegen.cpp).vkQueuePresentKHRblocked withpresent_muheld, and the game's submits waited behind it. Presents now go on a compute-family queue with that lock released, the swapchain's images shared CONCURRENT by both families; a second graphics queue was tried first and blocked longer. The frame-generation display thread now waits on a high-resolution timer, assleep_foroverslept by up to 20 ms once presents stopped blocking. On by default with NVIDIA's driver on Windows;RX_VK_PRESENT_QUEUE=0or1forces it. Its share with 4x frame generation: running 55.4 → 57.9 fps, frames over 22 ms 8.1% → 1.7%. Without frame generation it changes nothing.nx2/build.py). libopus is built with the same-marchflags as the rest of the game, so its float code can use AVX2 where the PC has it.Commits
The branch
perf/steadier-frame-rateon ShroomKing/Hookshot is three commits on upstream main at8b2e1d3(v2.0.8-rc.1). The renderer commits' messages carry their measurements and switches.Testing
No exposure flashes turned up in frame logs on upstream main with this PR, with 4x frame generation or without it.
tests/run_all.py: 117 pass, 15 skipped. The 3 failures (test_fps_overlay_order,test_precompile_compute_guard,nx2/test_nup_paths) also fail on unmodified main on the same machine.tests/test_wb_diff_split.py: the diff's run splitting covers every byte once, in order, with vectors aligned and at most 64 KB. It fails on a split that drops a run's tail.RX_VK_FRAMELOG,scripts/framelog.py) in Hyrule Field: with 4x frame generation, 0 flash events in 11,300 frames. Without it, no whole-picture brightness steps; the detector's 309 small local events are foreground grass sweeping past the camera at 60 fps.Notes for reviewers
Measured on NVIDIA only (RTX 5070 Ti, driver 621.14, Windows 11). The write-back diff is on everywhere, with
RX_VK_WB_DIFF=0as the escape hatch; the compute-queue present is on by default only with NVIDIA's driver on Windows.wb_diff_comp_spv.hcomes fromglslangValidator -V --target-env vulkan1.3 --vn WB_DIFF_COMP_SPV -o wb_diff_comp_spv.h wb_diff.comp.RX_VK_WB_SHADOW_MB) and give way above 85–95% use. The town at night, with ~10 GB of textures, runs without them.vk: write-back diffstats line every 600 submits and avk: present failedlog line.🤖 Generated with Claude Code
At
228d35b, synthetic tests using the submitted SPIR-V and source-derived bookkeeping found a conditional stale-shadow case: after a CPU write, a later GPU result equal to the old shadow can be skipped, leaving the CPU-written value where the existing whole-copy path writes the GPU value. This is a compositional reproduction, not evidence that Ocarina reaches the sequence. It affects capture-off and a possible in-stream write path; I can provide the test details.Thanks for this, I believe I have seen the effect of this in-game once.
I think it has to do with area-transitions and maybe cut-scenes but I have no way of reproducing it yet. Loading a save will properly reload the shadows in the area when the issue happens.
I would appreciate some more details about your test so i can narrow it down.
Will try to debug and submit a fix by tonight.
Here is how the issue looks in-game if you're curious:


Thanks for looking into this. We haven’t reproduced the sequence in-game, so I can’t tell whether it explains your missing shadows. At
228d35b, we combined two synthetic tests:shadow_writtenbefore write-back produces 0. We tested both word and vector paths of the submitted SPIR-V on llvmpipe.RX_VK_CAPTURE=0,capture_makereturns beforeshadow_writtenwhilememory_collect_writtencan consume the dirty page; with capture enabled, a smallNOP_CLEAR_BUFcan write during recording after collection without callingmemory_cpu_ownedorshadow_writtenfor that write.These were separate synthetic fixtures, not a full Hookshot replay; we haven’t established that real gameplay reaches the combined sequence or that it explains the screenshots. It may be useful to inspect whether every guest CPU write between shadow refresh and diff is reported before
shadows_forget_written, including in-stream clears.Measured this PR on Windows / NVIDIA, in case it helps: RTX 3070 8 GB, i7-14700KF, Windows 11, 32 GB.
Build: origin/main
8b2e1d3bwith this PR applied on top of the build I play (it also carries #1035, #1063 and arebased #895, left Off). Both arms are the same build except for this PR; it applied cleanly.
Kokiri Forest, A/B in ABBA order (same save and settings, Frame generation 2x with Frame pacing; 30 s standing, then
40 s walking; the game's own present intervals from
HOS_PRESENT_TRACE):9-12 ms without it.
without).