Repository navigation
perf(vk): age the texture spans once a present, not at every submission - #2420
Merged
Merged
Conversation
publish_tex_spans runs at the start of every submission's recording, about 20 times a present in OoT, and each time walked every texture span (about 300 in Kokiri Forest) to drop those unused for 1800 presents. A span's age is counted in presents, so the walk can only find something new when the present number has changed. It now runs once a present; a span noted between two submissions is still published by the next one. Steam Deck (Van Gogh, RADV), heavy view in Kokiri Forest, ~2940 draws: the replay goes from 40.0 to 39.3 ms a frame, 20.76 to 21.03 fps over three alternating pairs. Assisted-by: Claude Fable 5.2 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Joey-Magnus
approved these changes
Nov 11, 2026
Joey-Magnus
left a comment
Contributor
There was a problem hiding this comment.
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 sets tex_spans_changed and gets merged and published by the next call. swept is a function-local static, but it's only read and written under shadow_mu, which the function already holds, so there's no race. I ran tests/test_tex_spans_once_a_present.py at the PR's head on Linux (g++), and it passes.
Two minor notes, neither blocking:
- minor
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. - minor: CI hasn't run on this PR yet (only the title check), so the Windows run of the new fixture is still pending. Like many existing fixture tests, it defaults to
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.
Erfboom
pushed a commit
to Erfboom/longshot
that referenced
this pull request
Nov 11, 2026
elkarbeenus
pushed a commit
to elkarbeenus/LakuenRecompBuilder
that referenced
this pull request
Nov 11, 2026
…on (NotRexed#2420) 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 from `memory_begin_submission`, so at the start of every submission's recording. Each call walked all of `tex_spans` (306 spans in Kokiri Forest here) under `shadow_mu` to 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: | part | ms a call | |---|---| | `reap_mirrors` | 0.0005 | | `retired_imports_give_back` | 0.008 | | the clears | 0.0001 | | `imports_give_back` | 0.0005 | | `publish_tex_spans` | **0.049** | At about 20 calls a frame that is about 1 ms a frame. `std::_Rb_tree_increment` was the second hottest symbol of the presentation thread in `perf` (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: | run | before | after | |---|---|---| | pair 1 | 20.68 fps | 21.15 fps | | pair 2 | 20.77 fps | 20.97 fps | | pair 3 | 20.84 fps | 20.96 fps | | replay time a frame | 40.0 ms | 39.3 ms | 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: - New `tests/test_tex_spans_once_a_present.py`: cuts `publish_tex_spans` out 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 current `main` and passes with the change. - `python -I -B tests/run_all.py` on Linux (a build container on SteamOS): 322 passed, 15 failed, 60 skipped with the change; 321 passed, 15 failed, 60 skipped on `main` at `7759a6f3`. 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 `main` at `e19d969` with a few local changes, with and without this one; the function is the same on current `main`. Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com> (cherry picked from commit bebb0c6)
elkarbeenus
pushed a commit
to elkarbeenus/LakuenRecompBuilder
that referenced
this pull request
Nov 11, 2026
…neration off on first start; upstream NotRexed#2419 NotRexed#2420 NotRexed#2439 A Steam Deck ran ~20 fps short of CPU (the GPU waited on 0.4 ms a frame): short sleeps no longer spin in yield loops there (HOS_SHORT_SLEEP_TIMER decides), and made frames no longer take GPU work out of its power budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7fdTPXbwDRohew9qanvbp
dazemc
added a commit
to dazemc/Hookshot-rs
that referenced
this pull request
Nov 12, 2026
publish_tex_spans walked every texture span under shadow_mu at each of a frame's ~20 submissions (~300 spans; 0.049 ms a call, about 1 ms a frame on a Steam Deck) though a span ages by the present and within one present a second walk finds nothing the first did. The walk now runs only when the present number has changed; a span noted between two submissions of one present is still merged and published by the next call. Ported from upstream bebb0c6 (NotRexed#2420).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.