Performance: replay submit cost scales with draw count; pipeline-creation bursts cause hitches (NVIDIA, full session log) #384
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#384
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?
Builder version
foliage-linux-updater (source build of
ba594da)How you built
From source:
nx2/make_game.pypipeline equivalent — dump extracted, lift 100 %,test_insn.pyPASS (44172 cases), 0 failed compile units, MSVC target,RX_CPMat the bundled CPM cacheGame files given to it
A .nup (build
e0f3ccfa, v131072)The error
About 2 h of windowed play (Kokiri Forest → Lost Woods → Great Deku Tree interior), 118080 presents, RTX 5070 Ti, driver reports mesh shaders + host memory import + pipeline libraries. No crash, no DEVICE_LOST the whole session. Every number below is from the run's own
vk:log lines.Session: mean 53.4 fps, median 57.7. The fps loss splits cleanly by scene density, and the limiter is the CPU replay, not the GPU:
At 30–40 fps the frame budget is 25–33 ms but GPU time is only ~12 ms — submit-side replay (draw walk + descriptors + commands + dispatch) grows roughly linearly with draw count. Hyrule Field-scale scenes will hit this harder.
Pipeline-creation bursts mid-play. The seed
hookshot_shaders.bindoes not cover what progression builds. Over the session: 2551 pipelines / 33.5 s cumulative build time, in bursts, each one a hitch:A second run (warm
mkw_vk_pipelines.bin, 97 MB) hadtranslation 0 msat boot — so the cold-start experience is entirely cache warm-up, but the bursts during new areas still happen on the warm run (+1693 pipelines, 31 s).Related: #326, #334 (shareable pipeline manifest would fix the burst problem), #254.
Visual Studio or Build Tools version, and Windows SDK version
VS Build Tools 2022, MSVC 14.44, Windows SDK 10.0.26100
Operating system and free space on the output drive
Windows 11, ~60 GB free on D:
Confirmations
Where the frame goes on a fast NVIDIA desktop: half the GPU's time is write-back copies, and they sit on the frame's critical path
Setup: RTX 4080 16 GB, i9-14900KF, 32 GB RAM, Windows 11, 2560x1440. v2.0.5 with PR #761 merged locally (renderer DLL built from source with the release builder's toolchain). In-game: resolution 100%, frame generation off, frame pacing on, robust buffers off.
EDEN_NX2_GM_LEGACY=1andHOS_PIN=MainThread=FFFF,Presentation=FFFF,ModuleSystemWorker=FFFF(busiest threads on the P-cores) in every run. Same busy outdoor spot in every run (~3,000-3,500 draws a frame). Numbers are from the runs' ownvk:log lines; fps = the 2 s log windows with more than 300 draws.1. Nothing is saturated, and the in-game settings change nothing
submit)GPU-bound waitResolution 100% -> 50% and frame pacing off: fps unchanged (44-45 throughout), GPU time unchanged (13.4 ms).
2.
RX_VK_GPU_PROFILE=1: the write-back copies are half the GPU's timePer frame at ~3,530 draws:
m wb(mirror -> guest memory)m pass(all draws)m dispatchm barrieru msyncThat is why resolution does nothing here: the drawing itself is ~3.5 ms.
gpu_buffer_bindqueues every written storage buffer range for a copy back at the submission's end (vk_memory.cpp,writebacks.push_back), whether or not the CPU ever reads it.3.
RX_VK_LOG_WRITEBACK=1: what is copied backPer write-back submission (~3,180 draws, 45 fps): ~6.6 MB, almost all from pool
0x116b000000(144 MB pool, ~5.3 MB in 5 runs) and0x112e000000(32 MB pool, ~1 MB). Pool0x116b...is where the foliage culling keeps its per-category draw lists / task arguments (the0x116ba70000region discussed in #763), read by GPU task shaders, not by the CPU as far as I can tell. Big written storage buffers logged:0x116d5d6000: compute102f5b0abb0f1e63,78ccf14779ec3efb,7650519f88e59b19,abccf81b45c5eb500x1065000000: computee409733c45530d75,8306877b7dc8784b,14140d4c1dbe754f0x107f7e8000: compute4b03b3040d7cf076,26fe0c70ab9fc547(ssbo 0 of 3, c0+0x880)0x112e896000: compute7984161db3e053b5Possibly unrelated but odd: mesh program
43ec86f35fd937b9binds a "3109.2 MB" written storage buffer at0x42f3478ac208f7f5(c5+0x100, stage 7), which looks like a bad pointer read.4.
RX_VK_NO_WRITEBACK=1(experiment): 45 -> 57 fpsSame spot, ~3,480 draws: 56.6-57.5 fps (was ~45). GPU time spent drawing 3.95 ms, replay unchanged at 15.0 ms. The picture had extreme bloom / washout, as expected: the exposure meter's result never reaches the CPU. So the write-back is on the critical path: the frame was roughly replay (~15 ms) + the write-back at its end (~7 ms) that the next frame waits for.
5. Local patch: leave big written ranges out (
RX_VK_WB_MAX_KB=1024)Ranges over 1 MB are not queued for write-back; small ones (the exposure meter and other results the CPU reads) still are.
RX_VK_WB_MAX_KB=1024m wbPicture correct: no bloom, lighting normal, no visible glitches in that session (tester's report). What it left out: 4-15.8 MB ranges in pools
0x116b/0x1170(the culling lists),0x1065,0x112e.This is a blunt rule, posted as data rather than as a proposed fix. Two risks I can see: a skipped range's guest copy goes stale, so a CPU write into the same page (mirror syncs copy whole pages) or a mirror made again would bring stale data back; and a big buffer the CPU does read would break. A precise rule (ranges only GPU stages read, or a write-back done lazily on CPU access) would be the real fix.
The patch (vk_memory.cpp, on v2.0.5)
6. What is left: the CPU replay (~5 us a draw, no single hot spot)
With the write-back limit, same spot, 3,010 draws, 55.5 fps (
RX_VK_PROF_FINE=1): replay 15.2 ms = draw 9.0 (descriptors 3.9, commands 3.0, program picks 1.5, pipelines 0.2) + memory sync 2.3 (~2,870 page copies a frame) + dispatch 1.0 + queue submit 0.6 + ~2 not named. Fine sections: textures 1.76 ms, bind descriptors 0.95, user uniform buffers 0.66, storage buffers 0.45, vertex+index binds 0.40.Descriptors are built again for ~98% of draws, ~16 writes each, of which only ~4.3 differ from what was pushed last. Two local changes on top:
sampler_fortookreg_mu, copied and hashed the sampler registration and did a map lookup for every texture slot (~48,000 a frame). PR #901.tex_gen[stage], the texture and sampler registration generations and the texture destroy generation are unchanged (gpu_writtenchecked again for the hazard list). Kept for 62-63% of stage builds.With both: same spot (~3,155 draws) 55.5 -> 59-60 fps (the 60 cap), replay 15.2 -> 13.8 ms, descriptors 3.87 -> 3.24 ms, fine textures 1.76 -> 1.17 ms, no visual issues in one session, no device errors. The two weren't measured separately. I can share the texture-reuse patch once it has had more play time across areas.
7. The pipeline-hitch half of this issue
Mostly gone here on v2.0.5 + #761: 0 compute pipelines built in play, ~850 graphics pipelines built in play in 0.65 s in all over the session, slow submits 2.4 s in all, the worst 0.3 s, mostly area loads (draw/descriptor/upload work), not pipeline creation. The NVIDIA shader cache size was raised to 10 GB before these runs.
Overall at that spot: ~45 fps on v2.0.5 + #761 -> a steady 60 with the write-back limit and the descriptor changes.
Tracked in #995, follow there. That issue collects every report of low FPS while the GPU is not fully used, and shader stutter, and this one is linked from it with its GPU, version and log details. If it still happens on v2.0.6 (https://github.com/NotRexed/Hookshot/releases/tag/v2.0.6; Update next to Play in the launcher), please add a comment there with your
hookshot_log.txt.