[Tracking] Low FPS with low GPU usage (CPU-bound), FPS drops in Castle Town / Hyrule Field, shader stutter #995
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
2 participants
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot#995
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
This issue collects every open report of low FPS while the GPU is not fully used, and shader stutter, so there is one place to follow it. The individual reports were closed pointing here; their logs, screenshots and discussion stay in each linked issue.
Summary
Linux/RADV-specific low fps is in the Linux tracker, and AMD handhelds have their own.
Affected GPUs and versions
Evidence from the logs
RX_VK_GPU_PROFILE, mirror write-back copies take 7.08 ms, 52.8% of GPU time (68.5 MB a frame). Changing the resolution changes nothing.HOS_PINto P-cores gives +1-2 fps on hybrid Intel CPUs.Workarounds
HOS_PIN(pin to P-cores on hybrid Intel CPUs): +1-2 fps in #470.RX_VK_WAIT_GPU=0helped some players.Reports merged here (16 issues, about 27 people)
Adding a report
Please update to v2.0.6 first (https://github.com/NotRexed/Hookshot/releases/tag/v2.0.6; Update next to Play in the launcher). Please add: CPU, GPU, RAM, the place and fps, GPU usage if you can see it, and
hookshot_log.txt.All tracking issues
v2.0.7 on RX 7800 XT (16 GB), Ryzen 7 5800X3D, 32 GB, 1440p, frame generation 3x. Note: this is a non-stock
hookshot_vk.dll(v2.0.7 plus my own patch), so numbers may differ from stock.Log available on request.
Low FPS with low GPU use: a small buffer-mirror budget costs a CPU↔GPU wait at every submit (v2.0.8-rc.5, RTX 3070 8 GB, Windows 11)
Scripted test, same every run: load a Hyrule Field save, then run the same 45 s route. Only
RX_VK_MIRROR_MBchanges. Times are fromRX_VK_FRAME_LOG. "Memory sync" is the per-frame figure from the log'sa frame's replay timeline.RX_VK_MIRROR_MBWhere the time goes.
submit_memory_work→memory_take_capture. At 1024 MB the log showsa submit's memory work (ms): capture taken 2.4-3.0, 6 submits a frame. In a local build I timed the parts oftake_captureapart:vk_timeline_wait(c->late_value)at its top: the CPU waits for the GPU to finish the submissions that write the pages being captured;pools_mucosts 0.000 ms.The fewer pools are mirrored, the more pages the GPU writes straight into guest memory, and the more often a submit waits for the GPU. Hence low FPS with the GPU half idle.
Who is likely hit.
mirror_budget()gives cards of ≤6.5 GB only VRAM/8: 768 MB on a 6 GB card.RX_VK_MIRROR_MBagainst VRAM pressure, as I did with 1024 yesterday. Removing it took the field from ~40 to ~55 FPS, and Castle Town got much smoother too.Possible directions (not tried).
Happy to test a build on this card.
Data point, AMD RX 7800 XT + Ryzen 7 5800X3D (8c/16t): replay CPU time roughly doubled between v2.0.7 and v2.0.10, possible cause: the render thread pinned to the reserved core (#1034)
Setup: Windows 11, RX 7800 XT, 5800X3D, 2560x1440, frame generation x2, frame pacing on. Both sessions below ran my own patched
hookshot_vk.dll(AMD memory/pool tweaks), not the stock one, so this is not a clean stock-vs-stock comparison. The scenes were also not identical (normal play, Hyrule Field / combat). Treat it as a hint, not a measurement.From
hookshot_log.txt(sessions of a few minutes each):submitCPU in heavy momentsdrawmemory syncThe GPU time is about the same; the extra time is on the CPU, so it fits the CPU-bound reports here.
What changed between the two for a 16-thread machine, from the log and the sources:
threads: Presentation Thread on the reserved core (logical processors 0xc000; HOS_PIN_RENDER=0: off). The v2.0.7 log has no such line. In the default synchronous mode the replay runs on that thread, and the reserved core is also the one the replay thread is pinned to.RX_VK_CAPTURE_THREADSdefaults to 4 helpers with 12 or more logical processors (it was 2 before).vkAllocateMemoryof a 128 MB block takes about 170 ms, which would match the occasional frames over 40 ms.What I changed, in
hookshot_env.txt, to try to get v2.0.7's behaviour back:(
RX_VK_IMAGE_PREALLOConly exists in my patched DLL.)Not verified yet. I have not measured these settings in game, so I do not know if they remove the stutter. If someone with a many-core Ryzen or another AMD card can compare
HOS_PIN_RENDER=0andRX_VK_CAPTURE_THREADS=2against the defaults on v2.0.8+, that would show whether the pinning is the cause. I will post my own result after I have tried it.Separate small finding: on this card the log says
matrix multiplies in software, so the defaultRX_VK_TAAU_KEYS=fd029a317a9a4b94uses the plain upscale stand-in instead of the temporal network, and the shadows of grass and bushes look blocky.RX_VK_TAAU_KEYS=-runs the real network (about 1.3 ms more GPU here).Data point for @lucasmazzoni45-del's question: no doubling of the replay time between v2.0.8-rc.3 (before #1034's pin) and v2.0.10 on Zen 3 + NVIDIA.
The setup:
0xc000), RTX 3070, Windows 11, stock settings;a frame's replay timeline, last three samples.HOS_PIN_RENDER=0Run-to-run spread is a few ms, so read this as "no regression, the pin neutral to slightly better" on this CPU. Hybrid Intel CPUs are another matter (see the E-core reports in #1158). A doubling like the one you saw may come from the patched
hookshot_vk.dllor the different scenes rather than #1034. A stock A/B with and withoutHOS_PIN_RENDER=0, same spot, would tell.Thanks @Nitnek, that is a useful A/B, and it does not support my guess: on your Zen 3 the pin is neutral to slightly better and
HOS_PIN_RENDER=0is not faster. I have takenHOS_PIN_RENDER=0andRX_VK_CAPTURE_THREADS=2out of my settings. As you say, the doubling I saw may come from my patched DLL or from the scenes being different; with the two logs I had I cannot separate those.What I can add from the same 7800 XT logs: in the v2.0.10 session the replay wrote back about 33-60 MB a frame and
memory syncwas 1.6 ms, against 0.45 ms in the older session, which fits the write-back diff being off by default in v2.0.9/v2.0.10. I am on v2.0.11 now (write-back diff on) and will post the same numbers from it here, with and without my DLL, so the comparison is stock against stock.@NotRexed @hckrman101 quick one that may save you time on this issue:
TL;DR: on hybrid Intel CPUs the "reserved core" from #1034 is an E-core. Setting
HOS_PIN_RENDER=0gives ~+24% game fps (heavy scenes: 33 → 48 fps). Quick fix: don't pin by default when the CPU has more than one EfficiencyClass, or pick a P-core for the reserved core.Intel hybrid CPUs: the render thread is pinned to an E-core, unpinning it gives ~+24% fps
Setup: i7-12700KF (8 P-cores + 4 E-cores), RTX 5070 12 GB, 32 GB DDR5-6000, Windows 11, NVIDIA 621 driver, output 2560x1440, frame generation 2x,
RX_VK_WB_DIFF=1andRX_VK_PRESENT_QUEUE=1in both runs. Hookshot v2.0.11 (same pinning seen on v2.0.9/v2.0.10).The log shows:
threads: Presentation Thread on the reserved core (logical processors 0x80000; HOS_PIN_RENDER=0: off)On this CPU, logical processors 0–15 are the P-core threads and 16–19 are the E-cores, so the "reserved core" (LP 19) is an E-core, the slowest kind, running the CPU-bound replay thread.
With
HOS_PIN_RENDER=0inhookshot_env.txt(the line disappears from the log), same version, walking around Kokiri Village:Caveats: the pinned run was the first start after updating, and the routes were not identical. But a longer earlier v2.0.10 session (pinned) averaged 39.2 fps at 3000+ draws, against 48.6 now.
Note: the workaround listed above (HOS_PIN to P-cores, +1-2 fps, #470) predates #1034. Since v2.0.8 the render thread gets the "reserved core", which is the last logical processor, and on hybrid Intel CPUs that is an E-core, so the gap is now much larger.
Suggested fix: on Windows,
GetSystemCpuSetInformationgives each logical processor'sEfficiencyClass(higher = faster core). When more than one class exists (Intel 12th–14th gen, Core Ultra), reserve a logical processor from the highest class instead of the last one, or skip pinning by default. This likely affects every Intel 12th gen and newer player. Logs available on request.Side note: updating v2.0.10 → v2.0.11 from the launcher brought back an older copy of my
hookshot_env.txt(a line I had added was gone), so players who set switches may lose them on update.@SaKenLpB this is what #1158 (@jordanella) addresses. It moves the replay onto the reserved core only while it replays, and it picks that core among the fastest kind (P-cores), keeping one only with 4+ of them. Its thread has hybrid data like yours: the 14700KF E-core pin, and a 13700K going from 48.7 fps to a vsync-capped 60. A run of that PR on your 12700KF, against
HOS_PIN_RENDER=0, would be a useful third hybrid data point. On a non-hybrid Zen 3 it made no difference (my numbers there).Thanks @Nitnek, and sorry @jordanella, I hadn't seen #1158 before posting. Good to see the same conclusion from another angle: the 12700KF numbers above (reserved core on an E-core vs
HOS_PIN_RENDER=0) can count as a third hybrid data point.I can't build PR branches myself, but I'll test #1158 as soon as it lands in a release and post the before/after numbers here, same route through Kokiri Village.
As promised, the stock-against-stock numbers on v2.0.11 (write-back diff on) on the 7800 XT. Short version: on a scripted route with no new areas, the stock DLL and my patched DLL both hold the 60 game fps cap and neither stutters. I could not show a difference between them, and this route does not reproduce my stutter.
Setup: RX 7800 XT, Ryzen 7 5800X3D (8c/16t), Windows 11, windowed 2560x1440, frame generation x2, frame pacing on,
RX_VK_WB_DIFF=1,RX_VK_TAAU_KEYS=-. The same scripted input (HOS_INPUT_SCRIPT: load the save, then walking, sword swings and camera for about 270 s) in both runs, 420 s each, started 150 s late because the first start after a DLL swap redoes the shader and pipeline preparation (155-156 s in both runs). Frame times fromRX_VK_FRAME_LOG, taken from second 300 on, after the load:submit/draw/memory sync(second half of the run)What this does and does not say:
SLOW compilelines (up to 12.8 s each, on the background threads) and the stock run has none, because the sharedmkw_vk_pipelines.binwas warm by then. That favours the stock run. The route stayed smooth in the patched run anyway.RX_VK_IMAGE_PREALLOC=30), capture-buffer reuse, the pipeline-cache read over 2 GB on Windows (commit0921695), and hand ports of #1130, #1129, #1046 and #840 (the latter with the review's points).Also, since this thread is about the reserved core: it did not matter on this 5800X3D either (both runs have the Presentation Thread pinned, as in stock). I will post again if I get a clean stock-against-stock run through a new area.
RX 9070 XT works (Windows), and two switches took Kokiri from ~35 to ~52 fps on a hybrid Intel CPU
The README's AMD row doesn't list the RX 9070 XT yet, so for the record: Hookshot v2.0.11 runs on an RX 9070 XT (RDNA 4). I've had no device lost or crash in Kokiri Forest, and the picture is right (lighting, foliage, no black or white artifacts).
Setup: RX 9070 XT 16 GB, AMD Adrenalin 26.10.2 (
32.0.32015.12012, Vulkan driver0x8001a0), Intel i5-13400F (6P + 4E), 32 GB, Windows 11 26H2. v2.0.11, output 2560x1440, resolution 100%, Lanczos, frame generation 3x, frame pacing on, game speed Real time, robust buffers Auto.Kokiri Forest, free walking. The fps is the game's own rate from
hookshot_log.txt, averaged over the 5 s samples with more than 1500 draws:hookshot_env.txtRX_VK_PRESENT_QUEUE=1RX_VK_PRESENT_QUEUE=1+HOS_PIN_RENDER=0GPU time stays at 11-14 ms a frame, so it's CPU-bound, as this issue describes.
1. The reserved core lands on an E-core on the i5-13400F. The stock log says:
Logical processor 15 is one of the four E-cores (0-11 are the six P-cores with HT). With
HOS_PIN_RENDER=0, replay time drops from ~22 to ~17-18 ms a frame and the average goes up by about 9 fps. That's another hybrid data point for #1158: it should pick a P-core on this CPU.2.
RX_VK_PRESENT_QUEUE=1is fine on AMD. Re #1161: with the separate present queue on this card, there's no black screen and no device lost, and it was worth about +7 fps here:If it stays off by default because of NVIDIA's 621 driver, turning it on by default for AMD might be worth considering.
These numbers come from free play, not a scripted route, so treat them as approximate. I'm happy to run a scripted route (
HOS_INPUT_SCRIPT) if that's more useful.run-A_defaults_hookshot_log.txt
run-B_mirror3072_hookshot_log.txt
run-C_capturewait0_hookshot_log.txt
RTX 3070 Laptop + i7-11800H, Linux, v2.0.11: in Hyrule Field the replay waits 2-3.5 ms per submit in
take_capturefor late write-back pages, ~20 ms of "memory sync" a frame.RX_VK_CAPTURE_WAIT=0removes the wait but brings the exposure flashes back.Setup
source 10f983ad, first line of the log), built with the Linux AppImage.gpu_device=2), and the external 2560x1440 120 Hz monitor is wired to it.hookshot_options.ini):fullscreen=1,gpu_device=2,frame_generation=2,frame_pacing=1,upscaling=1,sharpness=30,handheld_profile=1,robust_buffers=2. In the screenshots the overlay shows a 1706x960 game image and a 1600x900 (83%) 3D scene.game-performancewrapper, which only switches the power profile to performance.Where: Hyrule Field, walking and turning the camera in front of Hyrule Castle (young Link, just after the Deku Tree). Every time. Run B also went into Castle Town Market, which drops the same way.
What the log shows. The numbers are the medians of the log's per-interval lines (
a frame's replay time,GPU time,a submit's memory work) over the intervals with 1000 or more draws (1500 or more for B and C).hookshot_env.txtRX_VK_MIRROR_MB=3072RX_VK_CAPTURE_WAIT=0For comparison, earlier in run A (Kokiri Forest, around 670 draws): 50 fps, submit 12.8 ms, memory sync 1.5 ms, capture 0.1 ms per submit.
capture takengoes from 0.3-0.5 ms to 1.5-3.6 ms per submit, at 6 submits a frame. The mirrors stay at 1.4-1.76 GB, under the 2048 MB an 8 GB card gets. RaisingRX_VK_MIRROR_MBto 3072 in run B changed nothing: the mirrors stayed at about 1.5 GB and the wait was still there. So this does not look like the small-mirror-budget case @Nitnek measured above.RX_VK_CAPTURE_WAIT=0confirms it, and confirms why the wait is there. Memory sync falls to 1.4 ms and capture to 0.1 ms per submit, but the brightness flashes all the time while walking and turning the camera. That looks like the exposure-meter flashes the comment incapture_makedescribes (#1005). I went back to the default.Possible direction (not tried): the late wait seems to matter only for the exposure-meter page(s). If the replay waited for those pages alone, or once a frame instead of at every submit's capture, the Field could keep most of run C's memory-sync gain without the flashes.
Logs attached: run A (
hookshot_log.2.txtat the time), run B (hookshot_log.1.txt), run C (hookshot_log.txt). No game files.I ran the game and the tests.
Thanks @Nitnek for the numbers. Tomorrow I'll run as many tests as I can on
my RX 7800 XT + 5800X3D: a stock A/B with and without
HOS_PIN_RENDER=0inthe same spot (without my patched
hookshot_vk.dll), and I'll try to findthe cause of the CPU-bound FPS drops and the stutter. I'll post logs here.
On Mon, Nov 09, 2026 11:33 PM, Felipe Ferraz Lima @.***>
wrote:
RTX 4080 SUPER + i9-14900KF, Windows, 4K: the same
take_capturewait in Hyrule Field, and what the late pages are.RX_VK_CAPTURE_WAIT=0takes the field from 52.5 to 59.9 fps here.This matches @felipefl142's report above, on a faster machine, with some detail on which pages the capture waits for.
Setup
0x9a4a8000, which I read as 617.42), i9-14900KF (8P + 16E), Windows 11.RX_VK_FRAMEGEN=0), frame pacing off.7dbdcdcwith #1158 applied (before it merged), plus a local, unpublished change that reuses render-target images. That change only affects the camera spin.Result
RX_VK_CAPTURE_WAIT=0GPU time is about 12.4 ms a frame in both, so the default is not GPU-bound. I did not check for exposure flashes with the wait off. @felipefl142 saw them while walking, and I only stood and spun.
What the wait is here
vk_timeline_wait(c->late_value)at the top oftake_capture. 100 of 600 captures wait, 3.1-4.7 ms each.What the late pages are
With temporary logging, I compared each waiting capture's copy with guest memory after the wait:
SIGNAL_EVENTwrites throughgpu_write. The replay already tracks that value on the CPU.This was 15 s of standing and spinning, with no walking. The exposure-meter page must be among the late pages while moving, given the flashes reported above, so "did not change" here is not a safe rule on its own.
Tried and dropped
vkQueueSubmit: worse, 48.5 / 53.0 fps. The awaited submission is still running when recording ends, so the wait only moves, and GPU time rose to 14.4 ms.Hybrid CPU data point for #1158 (same scene, 30 s phases, before the capture work)
7dbdcdc, default (Presentation Thread on logical processor 31, an E-core)HOS_PIN_RENDER=0Direction I'm trying: note a page as late only when its write-back can change bytes the capture copies, decided by the kind of write (event signals and writes whose bytes the replay already knows are out; the exposure meter's storage write stays in). I'll check it for flashes while walking in the field, with
RX_VK_CAPTURE_WAIT=0as the control, and post the result. If someone is already working on this, say so and I'll send what I have.The runs were scripted and the analysis was done with Claude Code. Logs available on request.
Correction to my comment above: most of that wait came from the tree bug (#994), not from the capture.
I rebuilt on main at
581afaf. Same machine, scene and settings:7dbdcdc+ #1158 (my numbers above)581afaf581afafit is gone, and I think #994's fix is what cut the GPU time, though I did not bisect. With the GPU done sooner, the capture rarely finds a write-back still pending.RX_VK_CAPTURE_WAIT=0now gains about 1 fps in the spin here, within noise. Walking toward the castle holds 59.7 fps too.gpu_writebytes; wait only for pages that shaders, copies or clears write back), unbuilt and untested, if it helps anyone.The runs were scripted and the analysis was done with Claude Code.
run-D_castle-market-castle_hookshot_log.txt
Follow-up (RTX 3070 Laptop, v2.0.11): at Hyrule Castle the wait appears only after a visit to Castle Town Market, and stays. Same spot: 37 fps fresh, 25 fps after the Market.
@Zarigis thanks for the correction. These runs are on v2.0.11, without the #994 fix, and the floating trees are on screen at the castle, so this may have the same cause. I will measure again on the first release with the fix.
Setup: as in my comment above, with MangoHud loaded.
hookshot_env.txthadRX_VK_VRAM_MB=4096andRX_VK_IMAGE_BLOCK_MB=64(I was testing VRAM use). One launch: Hyrule Castle grounds, then the Market, then back to the castle grounds.Result (medians of the log's per-interval lines)
Log attached. No game files.
RX 9070 XT + i9-9900K: measured A/B results for a few switches, plus where the CPU time goes in a heavy frame 📊
Adding to DeAuthenticated's RX 9070 XT numbers above, from a different CPU. Everything below was run (scripted input, same save, alternating arms), not estimated, unless it says otherwise.
Setup: RX 9070 XT, i9-9900K (8c/16t), 32 GB DDR4-3200, Windows 11, Hookshot v2.0.9, fullscreen 4K TV. Base env for all arms:
RX_VK_WB_DIFF=1,RX_VK_PRESENT_QUEUE=1(no crashes on AMD across ~70 runs today, matching the comment above).1.
RX_VK_PREFETCH_CHUNKS=2helps on this card (8 runs, 4 on / 4 off)The comment near
RX_VK_PREFETCH_CHUNKSinvk_memory.cppsays import-ahead hurt on AMD. On RDNA 4 it was the other way around. Same 127 s movement recording (running around Kokiri), output 2048x1152:The fps gain is small (t ≈ 2.4), the stutter drop is the useful part. Not checked: other AMD generations, or any picture difference (it only moves when imports happen).
2.
RX_VK_CHUNK=480(default 120)Standing in Kokiri, ~54 fps with 480 vs ~51 with the default. 960 and up, and 0, were slower than 480.
3. The profile-guided Vulkan build (
RX_VK_PGO, #965) is worth it on AMD tooA local PGO build of
hookshot_vk.dll, profiled on our own movement recording: +3.6 fps standing. Long-run A/B over 2 rounds of the same recording: PGO build + the switches above 57.55 / 58.37 fps vs stock DLL with no env 54.82 / 55.14.4. Resolution 75% at 4K output: +10 fps here, because at 100% the GPU becomes the limit
Output 3840x2160, standing in Kokiri, ABBA:
So "lowering resolution doesn't help" in the summary is true when the CPU is the limit, but at 4K / 100% on this card it isn't. In a heavier spot (3,000-3,900 draws a frame) 100% gave 34 fps with ~20.5 ms of GPU time a frame.
5. Where the CPU time goes in a heavy frame (log analysis)
From a timer build (thread CPU + import time around the slow-submit points), 87 five-second windows grouped by draws a frame. Done by a second agent over our logs, so the fits are analysis, not a separate run:
Fit: lock hold ≈ 5.55 ms + 4.25 µs × draws (r² 0.94). So in heavy views, the per-draw cost is what has to shrink before raising the frame rate (#1367) pays off. The two biggest per-draw costs we saw in the counters:
program_for, because only one validated texture-handle set is remembered per key. Remembering a few sets per key would make most of those fast hits.RX_VK_PROF_FINErun pending).6. Hitches during real play are first-time costs
A 13-minute hand-played session (warm caches): every hitch the player called out was the first time something appeared (a new shader translated in play, a large first host-memory import, or both). None repeated in the same spot. At a new output resolution, ~10 compute pipelines were built in play on the first launch.
7. Tooling note for anyone profiling
A Windows WPR CPU capture (
wpr -start CPU) started during movement froze the game for 9-21 s in 3 of 3 runs. Standing captures were fine. Use a timer build or the present trace for movement.Happy to run any A/B someone wants checked on this card. 🙂
AI-assisted: yes (Claude Code)
run-E_main_multi-loop_hookshot_log.txt
run-F_main_texcapture0_hookshot_log.txt
Re-measured on main (
0828863, with the #994 fix): the late-page wait is mostly gone here too, but on this build a second capture cost grows with every trip between Hyrule Castle and Castle Town Market.RX_VK_TEX_CAPTURE=0removes it, and I saw no flashes.@Zarigis as you asked, on a build with #994's fix. The floating trees at the castle are gone.
Setup: same machine as above (RTX 3070 Laptop 8 GB, i7-11800H, Linux). main at
0828863, built locally with the v2.0.11 AppImage's tools (the Vulkan back end built the wayscripts/linux/appimage.shbuilds it). MangoHud loaded.hookshot_env.txthadRX_VK_VRAM_MB=4096andRX_VK_IMAGE_BLOCK_MB=64in both runs, plusRX_VK_TEX_CAPTURE=0in run F. Route: castle grounds and the Market, back and forth many times in one launch.1. The late-page wait is small now. The log's
GPU wait for late pagesis 0-1.3 ms (0-1.6 ms in run F). On v2.0.11 the whole capture took 2-5 ms per submit in the same places, so the #994 fix helps here as it did for you.2.
retires and stampsina capture takengrows instead (run E, default settings). Second launch in the attached log (line 5521 on; the first launch was a shader warm-up):retires and stampsper capture3. With
RX_VK_TEX_CAPTURE=0(run F) it stays flat. Over a similar number of trips,retires and stampswas 0.001-0.02 ms and memory sync 1-10 ms. The Market held 23-31 fps on every trip. The castle was 46-57 fps at first and about 35 fps later, when GPU time had risen from about 12 to 20 ms; memory sync stayed low throughout. Walking and turning the camera in both areas, I saw no flashes and no texture glitches.My guess from reading
take_captureinvk_memory.cpp(not verified): once the texture shadow reacheskShadowMax(65536 pages), every capture that copies texture pages walks the wholeshadowmap looking for pages older than 64 captures. When none can go, it stops, and the next capture walks it again. Each trip to a new area adds texture pages, which would fit a cost that steps up with every trip and stays up until a restart.Logs attached: run E (two launches in one file; texture capture on) and run F (
RX_VK_TEX_CAPTURE=0). No game files.The Intel hybrid (E-core) stutter part of this (#1286) is fixed in the v2.0.12 branch and will be closed when it ships. This tracker stays open for the rest of the CPU-bound FPS reports.
Castle Town Market: with a damaged texture in the dump, the game reloads the Fairy Fountain model about once a frame, and that keeps 1.2 to 1.6 CPU cores busy (RTX 3070, i7-14700KF, Windows 11, main at
08288630)While profiling the Market for this issue, I found a CPU cost that doesn't come from Hookshot's code but from the dump.
What the game does in the Market. With
HOS_FS_TRACE=1, Link standing in the Market (scripted teleport, 20 s window), theARes Loadingthreads open these two files over and over: 869 times each in one 20 s window (the model 1 145 times in another run), about one open per frame, for as long as Link stays there:Model/Land_FairyFountain_Base_A.Land_FairyFountain_Base_A_01.bfres.zsTex/CmnTex_Map_Soil_FairyFountain_Floor_D_Nrm.bntx.zsWhy. In my dump, that texture is one of the three damaged files from #1308 (the other two are there as well:
RBake_Scene_GanonStage_G_2_1_d0_t0andRock_WaterTemple_Gate_M_01_Nrm). It decompresses, but to 12 288 zero bytes and then data that isn't a BNTX. As far as I can tell, the model that uses it never finishes loading, and the game asks for it again on the next frame.What it costs. Per-thread CPU (
HOS_PROFILE=5) in the same spot, 4 runs:OptimizeChangeBancat 71-91 % of a core andParallelDecompThreadat 49-69 %, through the 20 s window.With the three files replaced. I used
HOS_FS_OVERLAYand #1308's own picker (nup.stand_in_for), so the files are the same ones it chooses. The result:(Each figure is the mean of the 5 s samples taken while Link stands in the view, about 2 900 draws a frame; I left out the camera turn at the end.)
Runs mostly land either around 30 or around 40 fps, in both arms, and I don't know why yet. So I'd read this as "about 4 fps, noisy" (a one-sided permutation test over the 20 runs gives p ≈ 0.05), not as an exact figure. The CPU freed is the clearer part.
At the castle, after the Market (@felipefl142's case): the reloads go on there, though far fewer: 60 opens of the model in the castle window after 1 145 in the Market's. With the stand-ins it was 1. The castle's frame rate showed no clear difference in 2 + 2 runs, so this doesn't explain the drop you saw.
Two things for #1308's check:
28 b5 2f fd), sodamaged_game_files(), which reads the first 4 bytes, doesn't find them. A check of the decompressed start (BNTXfor.bntx.zs) would find all three. I scanned all 20 667 textures: exactly these three fail it.--romfsfolder isn't checked at all. That's by design, but those players get this cost with no message.I don't know which dump mine is. I didn't test a dump whose files start with zeros, as #1308 describes, though I'd expect the same retry. To check your own: if
HOS_FS_TRACE=1showsLand_FairyFountain_Base_Aopened again and again in the Market, you have it.Follow-up, same machine (RTX 3070 8 GB, i7-14700KF, Windows 11, main
08288630with the three stand-ins from my comment above): why the Market's frame rate jumps between runs, which matters for anyone A/B-ing here1. Frame time in the Market ≈ GPU time + replay time. I took the 5 s samples with Link standing in the Market (about 2 900 draws a frame): 609 samples from 82 runs. The median difference between the logged fps and
1000 / (GPU time + replay submit time)is 0.06 fps. So here the CPU's replay of a frame and the GPU's work on it don't overlap. The replay is steady (median 11.1 ms).2. The run-to-run spread is GPU time, and it comes per launch. Each run lands either around 12-14 ms or around 18-23 ms of GPU time for the same scene, and stays there.
nvidia-smievery 500 ms in the 6 runs I logged: 1905-1920 MHz, P2, no throttle reason, in fast and slow runs alike. So it isn't clocks.3. In the slow runs, some passes get several times slower. With
RX_VK_PASS_TIMING=1:m passtotal1a7482c0ec9991ea4. VRAM is past what the driver allows in most runs, e.g.
used 5866 of 5833 MB, and 671 MB of that is free space inside VMA's blocks. On this PC about 2.2 GB of the card was already in use before the game started.With all three budgets lowered at once (
RX_VK_MIRROR_MB=1408,RX_VK_VRAM_MB=1400,RX_VK_WB_SHADOW_MB=256), alternating 6 + 6 runs:That fits the slow state being memory the driver moved out of VRAM, but I haven't seen it directly.
These didn't remove the spread:
RX_VK_MIRROR_MB=3072: worse, 33.2 against 39.1 fps (4 + 4 runs);RX_VK_VRAM_MB=1400alone: 2 slow runs of 6, against 3 of 6.A local experiment, not a PR. The driver (610.88) has
VK_EXT_memory_priorityandVK_EXT_pageable_device_local_memory, and Hookshot enables neither. With both on and the images' memory at priority 1.0, over 6 + 6 runs:So it's not conclusive yet.
For anyone comparing switches on an 8 GB card: read the GPU time of each run. One run per arm can land in either state, which is a 6-10 fps swing on its own.
AMD data point from reading a 27-minute play log (RX 7800 XT, Ryzen 7 5800X3D, 2560x1440, FG 2x, v2.0.11 plus #1130; VRAM budget 14.5 GB) for what still stalls. Steady play was fine: replay about 0.5-1 ms a submit.
All 17 submits of 40 ms or more were at loading or scene changes:
draw 717 pipelines 707), although 11406 pipelines had been precompiled.RX_VK_GPLis off by default and I have not tried it on AMD (see #1271 for NVIDIA);pools_mu). I added the timing of each part to the slow-submit line in a local build so the next log will say;vkAllocateMemoryinimport_pool): 53 of them took 665 ms, mostly early. The VRAM line showed 13.5 of 14.5 GB at the peak.Two local changes against
vkAllocateMemorystalls on AMD (about 170 ms a 128 MB block on this driver), not in upstream:RX_VK_IMAGE_PREALLOC(part of the image pool made at the start: 15% of VRAM, 2-24 blocks; 19 here) and capture buffers kept in 32/64/128/256 MB classes up to 768 MB. On an older build with a headless scripted walk the mean went 52.9 to 59.8 fps and the min 41.6 to 56.7; I have not measured them on v2.0.12 or in play, and they cost VRAM. If this is useful I can turn them into a small PR with switches to turn them off.Correction to my two comments above: they weren't measured with default settings.
The test folder had a local
hookshot_env.txtI'd forgotten about, so every run in both comments had:RX_VK_WB_DIFF=0,RX_VK_PER_QUEUE_FENCES=0,RX_VK_ASTC_BC_ALL=1,RX_VK_IMAGE_BLOCK_MB=32,RX_VK_KEEP_RETIRED=0.The Fountain reload loop in the first comment doesn't depend on these. Point 1 of the second one ("the CPU's replay of a frame and the GPU's work on it don't overlap") does: it comes from
RX_VK_PER_QUEUE_FENCES=0.Same Market spot, same stand-ins. Defaults, then each setting alone on top of them, 2 runs each:
1000 / (GPU + replay)RX_VK_PER_QUEUE_FENCES=0, fast runtake_capturefor the GPU (GPU wait for late pages). That wait counts in the replay time, yet the run is faster than1000 / (GPU + replay). So CPU and GPU overlap.RX_VK_PER_QUEUE_FENCES=0: 0-1 waits per 600 in both runs, and fps matches1000 / (GPU + replay). No overlap, as in my comment.RX_VK_WB_DIFF=0;RX_VK_ASTC_BC_ALL=1withRX_VK_IMAGE_BLOCK_MB=32;RX_VK_KEEP_RETIRED=0) still have the waits and beat the formula in all their runs.GPU time still lands per launch in a fast or a slow state (12.2-14.0 ms against 18.5-53.0 ms in these 10 runs). So 2 runs per setting don't rank the settings by fps.
Measurements from a low-end laptop: RX 5500M (4 GB VRAM), a 12-thread laptop CPU and 16 GB RAM, in Hyrule Field, on main
779bdcfwith the open fallback PRs. They may help show where the frame goes on this class of card.GPU wait for late pagesis 1.2-1.5 ms per capture, six captures a frame.RX_VK_MIRROR_MB=1024: 4.2 GB (-85%), GPU 22.5 → 21.2 ms, memory sync about 1 ms less, fps the same within noise.0x1178000000, 160 MB, where the tree culling's lists live) still didn't get a mirror.RX_VK_PASS_TIMING=2:So on this card the frame is bounded by the CPU-GPU round trips around the game's mid-frame readbacks, not by GPU throughput or resolution. Lowering settings won't help much here; fewer or later readback waits would. Happy to measure any change on this hardware.
Replay (CPU) times from logs on a Ryzen 7 5800X3D + RX 7800 XT, v2.0.12-based builds, to put numbers on the CPU-bound ceiling: the
a frame's replay timeline in the scenes with 2,400 draws or more a frame (3 sessions, 67% render scale, and one at 100% at 2560x1440):submitmean (min-max)drawSo the replay is about 15 ms a frame in heavy scenes whatever the render resolution, with
draw8-10 ms,descriptors2.4-3.1,commands3.2-4.0,programs1.3-1.7 andmemory sync1.5-2.0. That already leaves almost nothing under a 16.6 ms frame, and at 100% 1440p the GPU adds 13 ms (GPU time 12-13.5 ms in the same scenes), so the game ran at 45-51 fps there (28% of the 5-second samples under 50). Lowering the render scale only shortens the GPU part; it does not move the 15 ms.For anyone thinking of
RX_VK_PARALLEL: #1576 reports missing textures with it, and the code comments say too-bright frames with 4 threads, so I have not used it. Whether the 15 ms can be cut (descriptors, the draw loop, fewer mid-frame syncs) is the open question here; I can measure a change on this CPU if one is proposed.Follow-up as promised: v2.0.12 (#1158 merged), i7-12700KF + RTX 5070 (driver 621), 1440p, FG 2x, same Kokiri Village route.
Kokiri Village, frames with 2000+ draws:
The log shows "replays run on logical processors 0xc000", so a P-core as intended. Thanks @jordanella!
Note: with Frame generation 2x, v2.0.12 turned on FSR 3.1 frame interpolation by default. For me it made the HUD (hearts, top-left button) flicker and cost ~1.3 ms per frame in the village (46.9 fps). RX_VK_FSR_FG=0 in hookshot_env.txt fixed both; the numbers above are with it.
Two more observations from my 100% / 1440p log (RX 7800 XT, Ryzen 7 5800X3D; builds based on v2.0.12), both about the stalls at scene loads:
1. The memory-sync spikes are in the capture part and only at loads. In the 19-minute session 5 submits had
memory syncof 15 ms or more; the part breakdown I added to the slow-submit line shows them all incapture: 17.4, 17.4, 22.6, 66.8 and 107.7 ms, withmaps 0.0 (lock waits 0.0)andGPU wait for late pages 0.000. Each follows a load (one right aftera frame's uploads took 152 ms ... 503 MB, one after893 textures retiredand a 15 MB re-upload) and none occurs in steady play; they run onsead::MainThread. The sizes scale like an allocation: ~17-22, 67 and 108 ms. From readingcapture_make/cap_buf_get(vk_memory.cpp), a capture that needs a size class with no spare buffer makes a new host buffer as its own memory object, and on AMD's Windows driver that kind of allocation cost about 0.8 ms per MB in my earlier measurements (170 ms for a 128 MB device block); this is the explanation I suspect, not proven. Aslow capture: ... the buffer of M MB A ms, the copy C msline in a local build will separate the allocation from the copy.2. A cost after any frame that deferred textures (code reading, not measured).
texture_frame()(vk_texture.cpp~3383) doesdestroy_gen++after a frame in which ASTC uploads were deferred (RX_VK_UPLOAD_BUDGET_MS), which is right (the deferred texture is looked up again). But the same bump makes everyhandle_hitsentry and every reused descriptor stale, so the next frame re-resolves and rebuilds the views and descriptors of all draws (about 2,700 here). In an area load that defers over several frames, that may be a few ms of replay time on each of them. A per-texture invalidation would avoid it; I have no measurement yet.A practical note for triage and for anyone testing a stutter or a flicker report (CONTRIBUTING asks for tests without overlays, and the issue form asks "other software hooked into the game"): a PC can have several implicit Vulkan layers registered that are injected into every Vulkan game, without the user having anything "hooked" on purpose. On my machine (Windows 11, loader 1.4.357)
HKLM\SOFTWARE\Khronos\Vulkan\ImplicitLayerslists six: the GOG Galaxy overlay, OBS's capture hook (VK_LAYER_OBS_HOOK), the Epic Online Services overlay, a TikTok LIVE Studio media layer, the Steam overlay and Steam Fossilize.vulkaninfoprints loader warnings about three of them (a layer name that does not follow the naming policy, layers built for Vulkan 1.3). Each layer wraps calls such as submit and present.hookshot_env.txtcannot turn them off on purpose:env_line_allowed()inhost/hos_main.conly takesRX_,HOS_,HOOKSHOT_,EDEN_,NVN_,TOTK_andSDL_names, because the loader's variables can load code. A way to test without them is to set the loader's own switch in the environment of the process, for example a.batnext tohookshot.exe:(
~implicit~is documented in the Vulkan-Loader LayerInterface doc; single layers have their ownDISABLE_*variables in their JSON files, e.g.DISABLE_VULKAN_OBS_CAPTURE=1.)I have not measured whether these layers cost anything or change any result (I have not yet run the same route with and without); I am posting it because it is cheap to check and the stutter, flicker and overlay reports here all assume no hooks. If someone compares a session with and without
VK_LOADER_LAYERS_DISABLE=~implicit~, replaysubmittimes in heavy scenes would show it.Data point from a Ryzen 9 3950X (Zen 2, 16 cores) with an RTX 4090 on Linux, v2.0.12 built with the AppImage.
Setup: Ubuntu 26.04, kernel 7.0, NVIDIA 595.99.02, 62 GB RAM, output 2560x1440, no upscaler and no frame generation. Headless runs (
HOS_HEADLESS=1) with scripted input from the same Kokiri Forest autosave, then Cheats page teleports to Kakariko, Castle Town Market and Lake Hylia. 30 s standing in each place; values are the last four fps reports.RX_VK_PARALLEL=4RX_VK_TEX_CAPTURE=0RX_VK_ASYNC=1HOS_CORES=8At the same Kokiri spot, no
RX_VK_PARALLELgives 40-41 fps andRX_VK_PARALLEL=8gives 44-50, no better than 4.GPU time stays at 3-6 ms a frame everywhere. The replay takes about 18 ms a frame in Kokiri and 45-55 ms at Lake Hylia, where
memory syncis about 26 ms of it (around 2,900 mirror copies and 68 MB written back a frame). WithRX_VK_TEX_CAPTURE=0the write-back drops to 14 MB a frame.Comparing ten screenshots per run at the same present counts, with and without
RX_VK_TEX_CAPTURE=0(ground lighting in Kokiri, the light shaft at Lake Hylia), I found no measurable difference.Chiming in with another data point. I'm also experiencing poor performance that mirrors other reports.
GPU: RTX 5090
CPU: Intel i9 7980xe, 18 cores 4.2ghz (all)
RAM: 128GB DDR4 3200 MHz (quad channel)
OS: Windows 11
Hookshot: 2.0.14
Ingame the GPU is barely being 10% utilized. Hyrule field, town, and Kakariko barely squeak by with 30fps as others report. This is the same performance regardless of the render resolution or other features. Oddly, no single CPU core is being hammered either. I don't have memory leaks or video playback issues.
The user above me, davjack, has a nearly identical system to mine from the same era.
By contrast, if I play this on an Intel Ultra 265K with 32GB of RAM and a 5060 Ti, it fully maximizes its use of the GPU resources and gets full expected performance.
hookshot_log_pass-timing.txt
hookshot_log_unprofiled_fg-off.txt
Data point: Ryzen 5 5600XT + RTX 4060, v2.0.14, Frame generation Off: per-thread CPU times and GPU pass timing (write-back is about half of the GPU time in the slow stretch)
LOW MEMORYline)RX_VK_FSR_FG=0inhookshot_env.txt1. Unprofiled run,
HOS_THREAD_TIMES=10(269presentswindows, Frame generation turned off a minute after start-up; only the part after it is counted)GPU timea frameGPU-bound waitNo thread is near 100% in gameplay and the CPU waits almost nothing for the GPU, but the GPU is busy about three quarters of the frame. At start-up, 12 unnamed threads sit at 96-100% for a long stretch (shader and pipeline preparation using every logical core).
2. Separate run with
RX_VK_PASS_TIMING=1(20 reports of 600 presents, Frame generation Off from start to end)wb(write-back, mirror -> guest memory)wbdata a framepass(the draws)dispatchwbcopies 54-69 MB a frame and takes about half of the GPU time. The time in the draws barely changes (a fast block with 1814 draws ran at 55.9 fps; a slow one with 864 draws ran at 38.6 fps), so the draw count alone does not explain the drop.RX_VK_WB_MAX_KB(an RTX 3080 in Hyrule Field: about 66 MB and 4.4 ms a submit, 45-48 fps; 59-60 fps withRX_VK_WB_MAX_KB=1024).30911ca("perf(vk): skip write-backs of large GPU-written bindings"), but not in v2.0.14: GitHub's compare of v2.0.14...30911ca says diverged, 54 ahead, and I addedRX_VK_WB_MAX_KB=1024tohookshot_env.txton v2.0.14 and the log has novk: write-backs only of written bindings up to ...line. So I could not test it. I will test it when a release has it.Caveats
5251 spans not timed (a command buffer's timestamps ran out).wbcauses the drop. I do not know which area of the game each stretch is.No fix asked: this is a hardware data point for the CPU-bound / low-GPU-usage family, with the write-back share as the finding.
Logs attached:
hookshot_log_unprofiled_fg-off.txt(part 1) andhookshot_log_pass-timing.txt(part 2), with the Windows user name in file paths replaced.v2.0.14 log attached (from #1925). RTX 5060 Laptop 8 GB (driver 621.14), Core 7 240H, 16 GB, Windows 11.
Output 1920x1080, Render resolution Auto, Frame generation Off, cap 60. ~15 min in Castle Town and Kakariko.
Game fps: median 39.6, lowest 10% 27.4
GPU-bound wait: 0.0 ms median
Replay submit: median 16.8 ms, p90 28.1 ms
Memory sync: median 7.9 ms, p90 17.8 ms, max 74 ms
v2.0.13, same settings and areas: submit 19.1 / 28.7 ms, memory sync 8.9 / 16.6 ms, fps median 37.6.
hookshot_log_v2.0.14.txt
Stability data from a 112-minute real session (RX 7800 XT, Ryzen 7 5800X3D, 2560x1440, FG, v2.0.12 plus #1130 and my own non-stock
hookshot_vk.dllwith larger image-pool preallocation and capture-buffer size classes), analysed fromhookshot_log.txt:draw, 11 bydispatch, 8 bymemory sync.quick_pipeline) targets.I hand-ported #1946's
quick_pipelineonto v2.0.12 together with #1637, #1821, #1438, #1133 and the uniform-word cache fixes (diffs available on request), and kept the shader-cache stamp unchanged so the program cache stays valid. Scripted headless run, 150 s, same input each time: mean 59.7 / 59.8 fps, min 57.5 / 57.6, versus 59.0 / 59.3 mean and min 27.8 / 43.5 with my previous build. No regression, fewer low minimums.Not verified: the scripted route never hit a first-seen effect, so the "a waiting draw's pipeline built without the driver's optimizer" line never appeared and the gain on the 59-306 ms waits is unmeasured. Zone-load hitches and the other ported commits are also untested in real play. I will post a real-play log when I have one.
I have a 19 13900hx, with Rtx 4070 8g, 32g de ram. Cachyos.
When i start the game its pretty good 15 mns it gets unplayable, gets more and more laggy.
can go from 40/50 to 20/30 pretty fast.
hookshot_log.1.txt
hookshot_log.txt
sometimes it feels going in and out of zone dont help.
Second data set from my RX 7800 XT / 5800X3D logs (v2.0.12 + #1130, non-stock DLL), on two things I could not find already written up here:
1. The "111 failed" programs in the v2.0.12 prepare log (earlier builds: 0). I re-ran
--prepare-shaderswith the full log:SULD.P1D (17),IMNMXwith a wide immediate (3),TLD.MS(3), scratch register (3),LDG/STG [URn+imm](2), noEXIT(1),ISBERD.O(1).lower_to_maxwell.h). Cherry-picking 8 of those lowering commits onto v2.0.12 took the failed count from 111 to 72 (68 intended + 4 left) and compute variants from 10095 to 11125. I left outd0f3511(scratch register): with it--prepare-shaderscrashes with 0xc0000409 on my build, cause not found. In play the log only shows "10 failed" (the movie networks), so I cannot say the 43 are used in normal scenes.2. Skipped draws as a possible cause of missing or flickering grass and bushes. In a 112-minute session about 960 new pipelines were compiled in play (265 s of compile, 100-1500 ms each on AMD). Draws whose pipeline is not ready are skipped by design: 143 of 3367 log windows had skipped draws, up to 30 per frame on average in one burst. That fits grass and bushes appearing and disappearing in bursts. This is a hypothesis, not proven on foliage. Two untested things to try:
RX_VK_PIPE_SKIP=0(wait for the pipeline: stutter instead of holes) andRX_VK_WAIT_GPU=1. Compile queue depth was 0 during this, so more workers would not help.Also still unexplained: one program (
da7a7ab28b8e73d9, stage 1) logs "storage buffer NOT bound (reads zeros)" in every session, including around the time of the sky blocks in #1130; the bound address looks like a float, not a pointer. And with texture budget 6144 MB and about 5.8 GB of textures resident, the VRAM was 12.2 of 14.3 GB; a long session withRX_VK_LOG_REMADE=1is needed to see what gets evicted.Not verified in real play: none of the texture fixes I ported (#1672, #1568, #1133, #1505, #1637 and others) were exercised by my scripted route.
Summary of the measurements behind my earlier comments here (RX 7800 XT, Ryzen 7 5800X3D, 2560x1440 windowed, FG on, v2.0.12 + #1130, non-stock
hookshot_vk.dll). Scripted runs use the sameHOS_INPUT_SCRIPTeach time; one or two runs per build and scenes not identical, so this is an indication, not a benchmark. Not yet tested in a long real play session.Stutter pass: #1946
quick_pipelinehand-ported + #1637, #1821, #1438, #1133, uniform-word cache, flat texture-answer table, 6 worker threads (16+ hw threads), 150 s runReal 112-minute session on the previous build: mean 58.6 fps, 5% worst 52.5, GPU 5.7 ms, 35 hitches >= 40 ms (worst 408 ms), 53 of 79 slow submits dominated by
draw, 19 "a draw waited 59-306 ms for a pipeline".Texture / shader pass (upstream cherry-picks only): the no-translator-change build ran 58.8 fps mean, 0 errors, 0 device lost; the build with 8
lower_to_maxwell.hfixes ran 60.0 fps mean (min 59.6), 0 errors, failed programs 111 -> 72, compute variants 10095 -> 11125 (cache must be rebuilt).Not proven: the
quick_pipelinegain on the long waits (the scripted route never produced a first-seen effect), any of the texture fixes (compiled and started, not exercised), skipped draws as the cause of missing grass, and the cause of the sky blocks (#1130), one-frame black frames and texture eviction.I have the diffs (source only) and the session logs and can attach them on request or to a PR if the maintainers want them.
Where the changes are (all under
gpu/vk/of v2.0.12 + #1130; my own build, not stock), so anyone can look at the exact spots. Everything below is source-only and I can attach the full diffs or open a PR if wanted.Stutter pass (stutter build -> perf2), the parts that matter
vk_draw.cpp,build_graphics()/graphics_pipeline()/draw(): hand port of #1946quick_pipeline(switchRX_VK_QUICK_WAIT=0= old wait). Alsoskip_report_impl()(the "draws left out" counters) andprogram_pick().vk_jobs.cpp,worker(): 6 worker threads when the machine has 16+ hardware threads (RX_VK_WORKERS=<n>), and new programs queued as urgent.vk_memory.cpp:gpu_buffer_bind()(#1133 direct-buffer-write bookkeeping),shadow_room()/shadow_make()/shadows_reap()(#1637 shadows by allocated memory),ub_read()+struct UbWord(uniform-word cache).vk_vma.cpp,images_pool(),create_image(),create_device_buffer(): image pool preallocation (RX_VK_IMAGE_PREALLOC, 15% of VRAM) and capture-buffer size classes;vram_compact.h/vram_pressure.hcarry #1821 VRAM compaction.vk_shader.cpp,descriptor_handle(): flat texture-answer table. To keep the program cache valid I forced the old shader stamp innx2/build.py(env overrideRX_SHADER_STAMP_FORCE, not in the diff); avk_shader.cppchange otherwise makes the game drop the whole program cache.vk_texture.cpp/vk_texture.h:texture_frame(),Texture::create()(#1438 AMD ASTC),struct Texturefields.Texture / rendering pass (cherry-picks from upstream, resolved against #1130)
vk_texture.cpp:upload_astc_gpu()(#1505 failed BC3 upload retried instead of noise),sampler_make()(failed samplers not accepted),texture_touch(),texture_read_texels(),forget_gpu(),texture_frame(); newevict_reason.h(PR1879 diagnostics: budget / drawn over / dead targets / "goes with no copy of its texels").vk_memory.cpp:staging()(#1672 staging published before the GPU read),memory_end_submission(),memory_collect_written(),memory_cpu_owned(),import_pool()/import_dma_buf_pieces()(#1434 CPU write watches kept across imports),shadow_forget(),stamp_page(),copy_pages(),reap_mirrors(),bg_drain().vk_replay.cpp,replay(): write-back stream (#1568).host/nvn_layout.c,nvn_tex_layout()/nvn_tex_level_tiles(): texture storage size in 64 bits (the host part needs an exe rebuild, so my renderer-only build only has the vk part).vk_draw.cpp: my diagnostic line "new pipeline in play ..." (RX_VK_LOG_NEWPIPE=0turns it off), to see which programs/states are first seen in real play.Shader lowering (the 111 -> 72 failed programs; changes the translator, so the program cache must be rebuilt)
eden/shader_recompiler/frontend/ampere/lower_to_maxwell.h,LowerToMaxwellWith(): the main fixes (SULD.P1D,IMNMXwide immediate,TLD.MS,LDG/STG [URn+imm], missingEXIT).frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp,ISBE_nx2()/ISBERD()plusir_emitter.cppIsbeRead(),ir/opcodes.*andbackend/spirv/emit_spirv_mesh.cppEmitIsbeRead(): theISBERD.Opath.d0f3511(scratch register). With it--prepare-shaderscrashes (0xc0000409) on my build, cause not found.Test logs (headless 150 s scripted route, same input each time) show the build lines
vk: device 0: AMD Radeon RX 7800 XT ... mesh shaders yes ... pipeline libraries yes,46566 program keys kept(the cache stayed valid with the forced stamp), and zerodevice lost/ errors in the ported build. The "a waiting draw's pipeline built without the driver's optimizer" line never appears in them, i.e. thequick_pipelinestand-in path was not exercised yet. I will post a real-play log for that.RTX 4060 Laptop (8 GB) on Windows — about 17 FPS in an open outdoor area
My laptop is a Lenovo Legion R9000P 2023 with an AMD Ryzen 9 7945HX, an NVIDIA GeForce RTX 4060 Laptop GPU (8 GB VRAM), and 32 GB of RAM. I was testing around Hookshot v2.0.12–v2.0.13. I couldn't confirm the exact build for the 17 FPS reading.
I previously saw about 50 FPS without frame generation and about 100 FPS with 2× frame generation in Kokiri Forest. In a later open outdoor area I saw about 17 FPS. These were observations from different scenes.
A separate Windows Task Manager screenshot showed 43% GPU utilization, 7.3/8.0 GB dedicated GPU memory, 4.6/15.8 GB shared GPU memory, 12.0/23.8 GB total GPU memory, and a GPU temperature of 53 °C. This screenshot was taken at a different time from the FPS reading. The memory figures are for the whole GPU, so I can't determine from these readings whether VRAM pressure contributed to the slowdown.
I also saw rainbow textures in an earlier build, similar to #993. I don't have a
hookshot_log.txtfor this run.Could you look into the outdoor frame-rate drop on this configuration? Thanks for your work on Hookshot.
Follow-up on my earlier data point: v2.1.0 includes
RX_VK_WB_MAX_KB, and write-back is no longer half of the GPU timeSame PC as before (Ryzen 5 5600XT, RTX 4060, Windows 11), now on v2.1.0 (source 633eb435), Frame generation Auto, Frame pacing On, Reflex On.
hookshot_env.txthas onlyRX_VK_WB_MAX_KB=1024; the log showsvk: write-backs only of written bindings up to 1024 KB (RX_VK_WB_MAX_KB).1. Write-back share (
RX_VK_PASS_TIMING=1, 15 reports of 600 presents that had awbline)RX_VK_WB_MAX_KB=1024wba framewbshare of GPU timewbdata a framepass(the draws) is now 46-60% of the GPU time anddispatch17-21%. The three highestwbshares (10.5-14.5%) are in the last reports of the run, with about 10 MB a frame, i.e. bindings under 1 MB that the limit still copies.2. Frame rates (no profiling)
LOW MEMORY,0 failedin every window.RX_VK_FSR_FG=0removed fromhookshot_env.txt: no HUD ghosting or doubling, nofsr3filines in the log.Caveats
Logs available if useful, with the Windows user name replaced in file paths.
Ryzen 9 5900X @3.7 Ghz, boost to ~4.5 Ghz, sitting around 30% utilization
RTX 4090, 24G VRAM, also at about 30% utilization
32GB DDR4 @3600 Mhz
EDIT: My XMP profile lapsed at some point; I was running the ram at 2133 Mhz. After fixing that, I got a ~15% boost to around 40fps. Relevant, but seemingly not the whole cause. Maybe people getting 60+ native are using DDR5, and this is extremely memory speed dependant?
Just hanging in Kokiri Forest, near the entrance to the Deku Tree's area in front of Mido, looking back at the rest of the village. Getting around 35 fps just standing there. Changing resolution, DLSS scale, etc, doesn't generally make a difference.
hookshot_log.txt
hookshot_options.ini: https://pastebin.com/6b8XRhKX
OS: CachyOS x86_64
Host: Predator PHN16-71 (V1.18)
Kernel: Linux 7.3.3-1-cachyos
Display (BOE0A2C): 2560x1600 @ 1.5x in 16", 165 Hz [Built-in]
Display (LC27G5xT): 2560x1440 in 27", 144 Hz [External] *
CPU: 13th Gen Intel(R) Core(TM) i9-13900HX (16+16) @ 5.40 GHz
GPU: NVIDIA GeForce RTX 4070 Max-Q / Mobile 8g [Discrete]
Memory: 20.5 GiB / 31.05 GiB (14%)
I get a pretty big ram usage while playing.
Gpu usage not max, cpu pretty low.
Frames were very good in forest castle, but in cokiri village and forest. very laggy, 30-45 Fps without framegen but felt worse.
Thanks for Your work.
hookshot_log.txt
Edit : 8g Vram video card
The raw log lines behind my earlier comments here, as text, so they can be checked without downloading anything. RX 7800 XT (driver 0x8001a0), Ryzen 7 5800X3D, Windows 11, v2.0.12 + #1130, my own non-stock
hookshot_vk.dll. Scripted runs, one or two per build, scenes not identical: an indication, not a benchmark. Local paths removed.1. The "111 failed" in the v2.0.12
--prepare-shaderslog (first start, empty program cache)Reasons printed for the ones that do not translate (examples, same text as the log):
Count by reason: 68 "a network left out on this card" (movie networks, excluded on purpose on non-NVIDIA) + 43 that do not lower (SULD.P 1D 17, IMNMX wide immediate 3, TLD.MS 3, scratch register 3, LDG/STG [URn+imm] 2, no EXIT 1, ISBERD.O 1, others). With 8 lowering commits from main cherry-picked (not the scratch-register one: it crashed my prepare step with 0xc0000409) the count went 111 -> 72 (68 networks + 4).
2. Worst "slow submit" lines of two 150 s scripted runs on the same route
My previous build:
perf2 build (#1946
quick_pipelinehand-port etc.):Slow-submit line counts (>= 40 ms threshold of the log): 32 / 37 / 29 for my earlier builds, 28 / 26 for perf2. That is a small difference, within what two runs can give, and I do not claim the stutter is fixed. The 150-300 ms submits are still there in both.
Typical steady-state replay line in the perf2 run (CPU ms a frame):
3. Stock DLL against my DLL, same scripted route, v2.0.11, frame generation x2 (frame table from my runner)
So on this route: same median and p95, my build has fewer long frames (1 against 11, max 90 ms against 1172 ms), but its CPU time a frame is higher. One run each, so the long frames may be a first-start effect (shader step) rather than the DLL.
I can attach the full logs if a maintainer wants them; I did not want to upload multi-MB files here without being asked.
[Linux/NVIDIA] Hookshot v2.1.0 — CPU-side Vulkan replay limits Kokiri Forest to ~47–50 FPS on RTX 5080
Summary
Hookshot v2.1.0 runs successfully on my Ubuntu desktop, but demanding outdoor scenes in Kokiri Forest consistently run below the 60 FPS cap, despite substantial unused GPU capacity.
Profiling points to CPU-side Vulkan draw/descriptor replay overhead. Lowering render resolution does not meaningfully improve FPS.
This appears related to the existing CPU-bound performance tracking issue #995 and earlier investigation #384.
Hardware and software
Graphics configuration
Reproduction
hookshot_log.txt.Actual versus expected performance
The 60 FPS cap is not maintained in demanding village scenes.
I expected closer to 60 FPS given the available GPU capacity, although Hookshot's renderer is still experimental.
Vulkan diagnostics during the slow scene
Representative sustained measurements:
CPU profiling in the heavy scene:
Representative log:
Optimization experiments
RX_VK_WB_MAX_KB=1024RX_VK_ASYNC=1HOS_RESERVE_CORE=0Experiments were not fully identical automated benchmarks. Camera angle, draw count and run-to-run variation may affect comparisons.
With
HOS_RESERVE_CORE=0, representative logs showed around 17.0–17.4 ms CPU replay time and approximately 2,880 draws per frame.Interpretation
The available logs suggest the bottleneck is predominantly CPU-side Vulkan replay and descriptor/draw processing, rather than raw GPU throughput, shader compilation or VRAM exhaustion.
This is consistent with earlier investigations showing that replay cost increases with draw count.
Questions for developers
I can provide
hookshot_log.txtfrom a slow gameplay session and additional controlled comparisons if needed.No crashes or device-loss errors were observed during these tests.
Data point, v2.1.0 (source 633eb435), Windows 11: replay cost scales with draw count, write-back ruled out
PC: RTX 4070 Ti Super (16 GB), Ryzen 5700X3D, 32 GB DDR4-3600 (XMP on), page file fixed at 32000 MB, game on NVMe, single hookshot.exe instance.
Settings: Frame generation off, Frame pacing on, Reflex on, Render resolution 100%, RX_VK_WB_MAX_KB=1024 and RX_VK_PASS_TIMING=1 in hookshot_env.txt.
Place: Hyrule Field, standing still at the same spot (the little river after kokiri exit ), two passes of ~30 s each, looking in one direction that drops, then at the sky.
Slow direction, replay breakdown: draw 10.4 ms (descriptors 3.3), commands 4.4, dispatch 1.3, memory sync ~1.0.
Write-back with RX_VK_WB_MAX_KB=1024: 0.2 ms a frame, 2.5% of profiled GPU time (about 9 MB a frame). Unlike the v2.0.14 write-back reports in this thread, it is not the limit on this PC.
Cost is about 4 us per draw and the GPU is idle for more than half of each frame. Nothing in the log points to shaders: 0 waits, no pipeline stalls, slowest frame 23.5 ms.
hookshot_log.txt attached (includes RX_VK_PASS_TIMING=1; Windows user name replaced in paths).
hookshot_log.txt
For the reports here where fps is fine at first and drops after 10-15 minutes of play (for example @benjdidj's above): on NVIDIA Linux that may be what PR #2314 addresses. It makes the texture-shadow copies read pages staged in cached RAM instead of the write-combined capture buffer.
How to tell if it is this: in
hookshot_log.txt, compare thea frame's replay time (ms): ... memory sync Xvalue early in the session with a later one at the same spot. Ifmemory syncgrows from a few ms to 10 ms or more while draws stay about the same, it is likely this. Ifmemory syncstays low and flat through the session anddraw/descriptorsare high, it is the draw-recording cost tracked here, and #2314 will not change it.Measured with the PR on an RTX 4070 12 GB (driver 615.71.09, 5800X3D, 1440p, frame generation off), at one fixed Kokiri view, after about 10 minutes of exploring: 33.1 fps with the change off against 49.5 fps with it on (memory sync 15.9 ms against 6.3 ms). Fresh launches were about 60 fps either way. Details and the full table: #2314
The PR is still open, so this needs a build with it applied. It does not remove the whole slowdown: with it on, the same view still went from 59 to 49.5 fps after exploring.
The pieces of my build that came out of the measurements above are now four small PRs on main, one topic each:
texture_answerAll of them build on v2.1.0 and boot on an RX 7800 XT; the full test suite was not run and I have not played a long session on this build yet, so the effect in real play is still unproven.
More reports of this, collected here:
Kokiri Forest, CPU-bound at ~54 fps on i9-10900K + RTX 3080 (v2.1.0, Windows, 2560x1440) — measurements and what did not work
Disclosure: the analysis and the experimental patches were done with an AI assistant (Claude); all in-game testing was done by me on my own machine. Nothing here is a PR, just data in case it helps.
Setup: i9-10900K, RTX 3080, 32 GB, Windows, v2.1.0, Robust buffers off. Save loads at the heaviest spot of Kokiri Forest (~4,200 draws per frame).
Best clean configuration found:
RX_VK_PARALLEL=6+RX_VK_WB_MAX_KB=1024→ about 54 fps mean, no visual issues.RX_VK_PARALLEL=8was worse (51.7).Where the frame goes (heaviest spot):
Experiments (local patches on v2.1.0), in case they save someone time:
RX_VK_CAPTURE_WAIT=0reaches ~60 fps but gives constant full-screen white flashes.f_80357060: invalidate, read the luminance sum, write zero back). Handling those words separately — the CPU gets the GPU's value without waiting, and the word is kept out of write-backs and syncs — removed the white flashes without the wait.Conclusion: on this machine the late-page wait is acting as a general frame fence. Without it, the GPU reads guest data from the next frame somewhere other than the late pages and the exposure words; I could not identify where. So the remaining gap to 60 here looks like replay CPU cost on the submitting thread plus that fence, not the write-back handling itself.
Also: on v2.1.0 with parallel replay, the readback wait returned after the first segment's recording. That is already fixed on main by #2419, so these numbers should be re-measured on a build that includes it.
Log attached. Happy to run further tests on this hardware.
hookshot_log.txt
More reports of this, collected here:
Some data from an 8 GB card on the Castle Town market, mostly about which of the suggested switches help there. Short version: on 8 GB,
RX_VK_PARALLELmade it worse andRX_VK_ASYNC=1didn't help.Setup: RTX 5060 Ti 8 GB, driver 610.88, Core Ultra 7 265F (20 threads), Windows 11, 1080p. Built on main at
497060fwith #2482 and #2484 merged, so before #2419, #2602 and the other CPU changes that landed since. #2419 fixes the fence wait with a split submission, so theRX_VK_PARALLELnumbers below should be checked again on a build that has it; I'll do that on my next build. I compared fps at the same draw count, because the market's crowd swings between about 1,000 and 3,400 draws a frame.RX_VK_PARALLEL=6(this one on the build before #2484): worse on 8 GB. The market dropped to 11-17 fps (about 22 without it on that build). VRAM went to 7,667 MB of 6,975 allowed, the texture budget shrank to 314 MB, and memory sync went to about 30 ms a frame. I also saw a thin black line on the drawbridge going into the market. I think the extra workers just need VRAM an 8 GB card doesn't have spare. Efraim's 54 fps result was on a 10 GB 3080, so it may be fine there.RX_VK_PARALLEL=2: no real difference at the same draw count:RX_VK_ASYNC=1: no fps gain on this build, and a lot of flashing. A 45-secondRX_VK_FRAMELOGcapture in the market had 315 one-frame events in 1,719 frames (scripts/framelog.py), whole-screen brighter or darker. The comment inasync_on()still says 28 per 1000, from early November.From an all-thread sample in the market, the main thread is blocked about 87% of the time, mostly waiting on the presentation thread, which matches the alternating Efraim described. I also checked whether the texture view cache was being thrown away by every texture destroy (
texture_destroy_generationbumps on any eviction), sincetexture_view_ofwas the biggest part ofbuild_descriptors.RX_VK_LOG_HITS=1said no: 90-98.6% hits in the market, and misses from a destroyed texture mostly under 2%.(I used Claude to help read the logs and profiles; the playing and testing were mine.)
Where the GPU time goes on a card without mesh shaders (
RX_VK_PASS_TIMING=2): RX 5600 XT 6 GB (RDNA1), Windows 11, mainde866017, Hyrule Field, ~1,040 draws.Top single dispatches:
755a777ada52677d(58 a frame, 0.64 ms),435a0138b9013040(0.57),b612216258832b9c(0.44),27ee9883e2a20323(0.39),ab7699ee7f536755(0.37). The replay's submit is ~10 ms a frame at ~1,040 draws, andGPU-bound waitstays at 0.Tracker upkeep note (merged pull requests and code checked against main
51da0464; no new test run was made for it).Main now contains #1158's hybrid-CPU replay-affinity change, #2314's cached-memory shadow copies, #2437–#2440's capture/image/lookup/worker changes, and #2602's readback-wait repair. On
51da0464,gpu/vk/vk_replay.cppretains range-specific waits and whole-submission protection for direct readers. These can be marked as completed subcases, but they do not prove all stutter or low-FPS reports resolved. A remaining slow-scene report needs a matching current-build log and the same view, settings and cache state; several related optimizations already have owners. No new performance measurement was made for this note, and the tracker should stay open.A mid-range NVIDIA card on Linux that is GPU-limited in Hyrule Field, not CPU-limited, although
GPU-bound waitreads 0.0 ms. RTX 3060 12 GB, NVIDIA 615.71.09, Manjaro (kernel 6.12), Core Ultra 7 265KF, Wayland; main51da0464(AppImage built from source). Picture 2576x1080 (Aspect ratio: monitor's on a 3440x1440 screen), Hyrule Field from the same save, standing, ~3,400-3,750 draws.Frame rate and GPU time
RX_VK_PASS_TIMING=1nvidia-smi: 84-90 % busy, 1,965 MHz, ~122 WGPU-bound wait 0.0 msin every run, yet the capture waited for the GPU about 9 ms a frame (GPU wait for late pages 1.49 msa take, 100 of 600 takes). With #2622 merged locally that wait left the capture (memory sync 9 ms to 0.4 ms) and the frame rate stayed the same: the hold's flush then waited for the GPU instead (comment on #2622). So reports here that read "low GPU use" from that counter may be GPU-limited too.Where the GPU time goes (
RX_VK_PASS_TIMING=2, 75%, frame interpolation 2x, mesh shaders native): 20.1 ms a frame, of it passes 11.3, dispatches 3.3, barriers 1.1, write-back copies 0.7 (68.5 MB a frame), msync copies 0.7 (1,671 copies, 12.2 MB a frame).Four single-draw passes cost about the same at 100% and 75%, so they do not scale with resolution:
43ec86f35fd937b9d8e59341cf4f629c4f0c0b40ab3a32cd739acd678ae08655With Mesh shaders: Compute they left the list (passes 11.3 to 6.8 ms), but barriers rose to 2.1 ms and 6.8 ms appeared between the timed spans, and the total stayed the same (20.9 ms, 44.1 fps profiled against 43.3). So they look like the scenery's task/mesh programs, whose cost here is per meshlet, not per pixel, either path. That would explain why lowering Render resolution helps this card less than expected; the task shaders' culling (the depth-grid walk described for
RX_VK_MESH_NOCULL) may be the place to look.Also on Linux: the replay's reserved core is the last one, an E-core on hybrid Intel CPUs (#1158 pinned it on Windows only); #2805 picks a P-core there. Pinning it to a P-core by hand (
HOS_RESERVE_CORE_MASK=80) made no measurable difference on this machine, consistent with it being GPU-limited.Next performance work: four measured scenes
November 12 survey of main
51da0464: i9-13900K, RTX 4090, NVIDIA 615.78.08, Arch Linux; 2048×1152, 100% render scale, no frame generation, cap 240.Kokiri Forest, Hyrule Field, Castle Town Market and Kakariko each had two unprofiled runs around one CPU/GPU profile, alone on the card.
These are ranked opportunities, not additive frame costs or promised savings: CPU phases nest, GPU timestamps serialize work, and program lists are truncated.
Hyrule Field and Market repeats differed by 6.51% and 7.35%; they are not stable bands for declaring a small improvement.
One diagnostic finding, relevant to @PedroFlaminio's report above that
GPU-bound waitreads 0.0 ms on a GPU-limited card: that figure only counts waits for a recording slot. At 5120×2880 a counter for the other waits (fences, timelines, idle calls, blocking query reads) reported 2.93 ms/frame while the old value read 0.0 ms. #2897 adds that second line to the log and labels the old one; it changes what is reported, not how the game runs, and that one ordinary run proves a reporting gap, not a speed difference or an isolated bottleneck.No results here are re-labelled as current-main measurements after the later rebase; no further game runs were made.