perf(vk): age the texture spans once a present, not at every submission #2420
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!2420
Loading…
Reference in a new issue
No description provided.
Delete branch "perf/tex-spans-once-a-frame"
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?
On a slow CPU the renderer spends about 1 ms of every frame walking the list of texture spans again and again: the walk runs at each submission, about 20 times a frame, though what it looks for can only change once a frame. This makes it run once a frame.
What changed
publish_tex_spans(gpu/vk/vk_memory.cpp) is called frommemory_begin_submission, so at the start of every submission's recording. Each call walked all oftex_spans(306 spans in Kokiri Forest here) undershadow_muto drop the spans unused for 1800 presents.A span's age is counted in presents (
texture_frame_no()), so within one present a second walk finds nothing the first did not. The walk now runs only when the present number has changed. The rest of the function is as it was: a span noted between two submissions of one present (tex_spans_changed) is still merged and published by the next call.No switch, and nothing changes in what is dropped or published, only when the spans are looked over.
Where the time was
Timers around the five parts of
memory_begin_submission, Steam Deck, Kokiri Forest, average of 2000 calls:reap_mirrorsretired_imports_give_backimports_give_backpublish_tex_spansAt about 20 calls a frame that is about 1 ms a frame.
std::_Rb_tree_incrementwas the second hottest symbol of the presentation thread inperf(5.6% of its user time) before the change.How it was tested
Steam Deck LCD (Van Gogh APU, 4 cores / 8 threads, Mesa RADV, SteamOS), headless runs on a save in Kokiri Forest looking at the village, about 2940 draws a frame, 110 s measured per run. Before and after are the same renderer source with and without this change, runs alternated:
Hyrule Field, standing still, about 1600 draws, one pair: 20.46 to 20.81 fps.
The gain is small (about 0.3 fps here) and should be smaller on a faster CPU, where the walk costs less.
Flashes: one run with the camera turning, a 160x90 screenshot of every frame, 800 frames: no one-frame brightness step over 1.5 (largest 0.6).
Checks:
tests/test_tex_spans_once_a_present.py: cutspublish_tex_spansout of the source and counts how often the spans are gone through. 20 submissions of one present go through them once; a span noted mid-present is published; spans unused for 1800 presents are dropped with their shadow range, a later-used one is kept. It fails on currentmainand passes with the change.python -I -B tests/run_all.pyon Linux (a build container on SteamOS): 322 passed, 15 failed, 60 skipped with the change; 321 passed, 15 failed, 60 skipped onmainat7759a6f3. The 15 failures are the same on both; the one more pass is the new test.Not tested: Windows, and any GPU other than the Steam Deck's. The measurements are from a renderer built from
mainate19d969with a few local changes, with and without this one; the function is the same on currentmain.Looks good to me. A span's age is counted in presents (
texture_frame_no()), and the only caller (memory_begin_submission,gpu/vk/vk_memory.cpp:2992) passes the current present number, so a second walk within the same present can't drop anything the first one didn't. The rest of the function is unchanged: a span noted between two submissions still setstex_spans_changedand gets merged and published by the next call.sweptis a function-local static, but it's only read and written undershadow_mu, which the function already holds, so there's no race. I rantests/test_tex_spans_once_a_present.pyat the PR's head on Linux (g++), and it passes.Two minor notes, neither blocking:
gpu/vk/vk_memory.cpp:1036:sweptoutlives anything that resets the present counter. Iftexture_frame_no()ever restarts (for example, a renderer re-init in the same process) and happens to land on the stale value, that one present skips its sweep. The next present sweeps again, and the 1800-present bound makes a one-present delay harmless. I'm only mentioning it so nobody later relies onsweptfor anything more exact.g++and declares noRUN_ALL_ARGS. If you want it to follow the runner's compiler choice and get skipped (or fail under--strict) when there isn't one, addRUN_ALL_ARGS = ["--cxx", "{cxx}"]after the imports, astests/test_menu_keys.pydoes.github-sync referenced this pull request2026-11-12 06:22:54 +00:00