perf(vk): the staging arenas a load used a moment ago are kept, not made again every second frame #568

Open
p0lux wants to merge 2 commits from p0lux/Longshot:perf/staging-burst-keep into main
Contributor

Type: perf · Risk: low · Tested on: Linux (SteamOS), Steam Deck OLED (RADV VANGOGH) · Needs: a desktop card of 6 GB or less, Windows or Linux

Why

On a card of 6.5 GB or less, a load that uploads textures in frames that follow each other makes a new 64 MB staging arena about every second frame and frees it a frame later: 14 to 17 arenas an arrival on a Steam Deck, about 13 ms of the replay thread each. The arenas are needed again at once; freeing them between two frames of the same load buys nothing. See #395.

What changed

An idle staging arena that a load staged into in the last 2 seconds is kept, up to 256 MB of them, instead of being freed between two frames of the same load. Two seconds after the load's last upload the old 64 MB rule holds again; cards that already keep 256 MB see no change.

  • Why it happened. A staging arena can be reused once the GPU finished the submission that read it, which texture_frame learns at the next present, after the next frame has already staged. So frames that stage in a row need two arenas taking turns. Where 64 MB of idle arenas is kept (one arena: cards of 6.5 GB or less since the 64 MB rule), the idle one of the two went at every second present, and the frame after made a 64 MB arena again.
  • The rule (texture_frame, gpu/vk/vk_texture.cpp): an idle arena that was staged into in the last 2 s stays, up to 256 MB of them. 256 MB is what a card over 6.5 GB keeps anyway, so nothing changes there. Two seconds after a load's last upload the 64 MB rule holds again, and the 30 s rule after it. Arenas over 64 MB still go as soon as the GPU is done with them.
  • docs: a row for RX_VK_STAGING_BURST_S in docs/development.md; one run file in docs/tested/.
  • checks: tests/test_staging_burst.py (new) cuts staging() and the give-back loop out of vk_texture.cpp and runs frames through them on a clock of its own.

Measurements

Verdict: it removes the churn it targets: 16 arenas made an arrival become 4 in Hyrule Field and 15 become 5 in Kokiri Forest on a Steam Deck OLED, 120 to 170 ms of the replay thread less in the load's slow frames. The arrival as a whole is not measurably shorter, and GPU memory at the end of the run is the same. No frame rate gain is claimed. Passed.

Hardware: SteamOS 3.9.5 (kernel 7.2.7-valve2), Steam Deck OLED: AMD Custom APU 0932, RADV VANGOGH, Mesa 26.2.0-devel, 16 GB shared with the GPU; the GPU's clock fixed at 1600 MHz
Scene: the arrival in an area: a save in Hyrule Field and one in Kokiri Forest loaded by script; no screen (headless), handheld profile, Minimum preset, the game's own upscaler, no frame generation, no cap
Method: the game and host built from main 9c9d69ed; the back end built alone from each commit (clang, same flags) and swapped in that game folder. Each run starts the game and loads the save, with RX_VK_FRAME_TRACE=1. Arenas made are the log's a staging arena of 64 MB took ... ms to make lines over the whole run (the log stops at 24, never reached). Frame times are the trace's, over the 600 presents after the load's transition frame (the first frame over 600 ms that uploads over 60 MB). GPU memory is drm-memory-gtt from /proc/<pid>/fdinfo at the end of the run. Main, this PR, and a third renderer (another pull request's), alternated, 3 runs a scene; the median of 3
How to reproduce: RX_VK_FRAME_TRACE=1, load a save, then grep "staging arena of" logs/longshot_log.txt | wc -l; RX_VK_STAGING_BURST_S=0 for the old rule

this PR: perf/staging-burst-keep @ d3004735 (this PR's code commit on main 9c9d69ed; on the fork as measured/staging-burst-keep-d3004735). The branch is now on main 9e6f231d (be132765 and a docs commit): git range-diff shows the same patch. Main has changed texture_frame in between (#735: the texture budget is set by the card, and an integrated GPU's line for the driver's count), but not staging() nor the loop that gives idle arenas back, which is all this PR touches; so the numbers hold. Not measured again on this main.

Medians; each run in brackets, in the order run:

Hyrule Field Before: main @ 9c9d69ed After: this PR @ d3004735 Δ
staging arenas made in a run 16 (17 / 14 / 16) 4 (4 / 4 / 4) -12
time making them 225 ms (242 / 202 / 225) 58 ms (58 / 45 / 65) -167 ms
upload time in the frames over 60 ms 292 ms (341 / 262 / 292) 170 ms (163 / 170 / 171) -122 ms
time in frames over 60 ms 3.0 s (2.9 / 3.0 / 3.3) 3.0 s (2.9 / 3.4 / 3.0) none
longest frame after the transition 543 ms (571 / 529 / 543) 513 ms (491 / 513 / 540) within the spread
GPU memory at the end of the run 3,290 MiB (3,290 / 3,249 / 3,317) 3,316 MiB (3,316 / 3,323 / 3,313) within the spread
Kokiri Forest Before: main @ 9c9d69ed After: this PR @ d3004735 Δ
staging arenas made in a run 15 (15 / 15 / 15) 5 (5 / 5 / 5) -10
time making them 192 ms (184 / 198 / 192) 72 ms (78 / 72 / 64) -120 ms
upload time in the frames over 60 ms 320 ms (320 / 360 / 314) 231 ms (231 / 231 / 231) -89 ms
time in frames over 60 ms 8.6 s (6.0 / 9.1 / 8.6) 6.2 s (6.0 / 7.3 / 6.2) within the spread
longest frame after the transition 890 ms (871 / 1,275 / 890) 897 ms (897 / 945 / 885) none
GPU memory at the end of the run 3,899 MiB (3,925 / 3,899 / 3,875) 3,925 MiB (3,926 / 3,866 / 3,925) within the spread
vk: a staging arena of 64 MB took 15.5 ms to make

15 such lines in each Kokiri Forest run on main, 5 with this PR.

Read with care. The change removes ten to twelve arena creations an arrival and the 120 to 170 ms they cost, spread over the load's slow frames. It does not make the arrival measurably shorter on this machine: 3.0 s against 3.0 s in slow frames in Hyrule Field, and in Kokiri Forest main's own three runs go from 6.0 to 9.1 s. What it costs is under Risks.

Flags and settings

One new switch, on by default at 2 seconds.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_STAGING_BURST_S=<seconds> how long an idle staging arena stays after something was staged into it, up to 256 MB of them no such rule: an idle arena past the 64 MB kept goes at the next present 2; =0 is the behaviour before none on the picture; 10 to 12 fewer arenas made an arrival, up to 192 MB more kept while a load uploads and for 2 s after helps: cards the driver allows 6.5 GB or less, which keep 64 MB (Steam Deck above); doesn't help: cards over 6.5 GB (they keep 256 MB already); untested: desktop cards of 6 GB or less, Windows in the game leave unset (2): the arenas kept are ones the load is still using

Testing

Tested:

  • python -I -B tests/run_all.py on macOS with the Vulkan headers given, on the rebased head: 507 passed, 48 failed, 39 skipped. The 48 fail the same way on main 9e6f231d on that machine (no /proc, no posix_fallocate, Python 3.9, the Linux launch scripts and the like). The suite ran beside four others and 11 more checks timed out in that run (on main too, 10); each passes when run alone.
  • tests/test_staging_burst.py (new), on macOS (clang) and Linux (g++ 14): 20 frames that stage in a row make 2 arenas and give none back (the old rule makes 11 and gives 9 back, which is how it fails on main); the 256 MB cap after a frame that needs six arenas; an arena over 64 MB still goes; back to 64 MB 2 s after the last upload and to none after 30 s; with 256 MB kept nothing changes; RX_VK_STAGING_BURST_S=0 gives the old behaviour in each case. tests/test_staging_arena_fallback.py and tests/test_texture_staging_publication.py, which cut the same code, needed no change and pass.
  • In the game, a Steam Deck OLED, without a screen: the 6 runs above of this commit and 6 of main, no error, no device lost, 0 GPU timeouts in the kernel's log. With the picture, by a person: builds holding this change with other renderer-only ones played on the same Deck on 2026-11-14: reported smooth, nothing wrong reported.
  • Linux: the back end builds from the rebased head (ef080109; clang, x86-64-v3, on the Steam Deck): 0 failed. Windows: longshot_vk.dll cross-builds from the same head with llvm-mingw 20261104 on Linux: 0 failed. Not run on Windows.

Not tested:

  • A desktop card of 6 GB or less, where an arena is VRAM behind the BAR and the 64 MB rule came from: neither the gain nor the cost was measured there.
  • Windows in the game. A long session; a moving camera with textures streaming in, where frames that upload in a row are the common case and the old rule should have made an arena every second frame too (not measured).

Must be tested:

  • A desktop card the driver allows 6.5 GB or less (a 6 GB card or smaller), Windows or Linux: load a save, then change area a few times, expecting no new stall and the VRAM in use back at main's level a few seconds after each load (by hand: the driver's or the overlay's VRAM figure with main and with this PR, or with RX_VK_STAGING_BURST_S=0 against unset on one build of this PR)

Risks

What could break: memory on a small card during a load. Where 64 MB of idle arenas was kept, up to 256 MB now stay while a load is uploading and for 2 s after its last upload: up to 192 MB more, VRAM on a desktop card with resizable BAR, system memory on an APU. It is memory the load was making and freeing in turn, not new memory, and a card over 6.5 GB keeps that much all the time. On the Deck the GPU memory at the end of the run is the same.

Turning it off: RX_VK_STAGING_BURST_S=0 is the old rule; reverting the commit removes the change.

Compatibility: Nothing changes: saves and longshot_options.ini are untouched, no shader or pipeline cache is rebuilt (no stamped file is touched), MKWVK_API_VERSION is as it was.

Reviewer notes

  • RX_VK_STAGING_KEEP_MB=256 (existing) also stops the churn, by keeping 256 MB for good, which is what the 64 MB rule was made to avoid on small cards.
  • The rule's two numbers are the first tried, not tuned: most gaps between a load's uploading frames are under 0.3 s here, and 256 MB is the larger cards' own figure.
  • #395 is a crash after the driver refused 64 MB staging arenas eight times in a row during a load. This PR makes fewer of them during a load; it was not tested against that crash and does not claim to fix it.

Issues

See #395

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md).
  • A game-free test added for the fix, failing on main and passing here.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.
**Type:** perf · **Risk:** low · **Tested on:** Linux (SteamOS), Steam Deck OLED (RADV VANGOGH) · **Needs:** a desktop card of 6 GB or less, Windows or Linux ## Why On a card of 6.5 GB or less, a load that uploads textures in frames that follow each other makes a new 64 MB staging arena about every second frame and frees it a frame later: 14 to 17 arenas an arrival on a Steam Deck, about 13 ms of the replay thread each. The arenas are needed again at once; freeing them between two frames of the same load buys nothing. See #395. ## What changed An idle staging arena that a load staged into in the last 2 seconds is kept, up to 256 MB of them, instead of being freed between two frames of the same load. Two seconds after the load's last upload the old 64 MB rule holds again; cards that already keep 256 MB see no change. - **Why it happened.** A staging arena can be reused once the GPU finished the submission that read it, which `texture_frame` learns at the next present, after the next frame has already staged. So frames that stage in a row need two arenas taking turns. Where 64 MB of idle arenas is kept (one arena: cards of 6.5 GB or less since the 64 MB rule), the idle one of the two went at every second present, and the frame after made a 64 MB arena again. - **The rule** (`texture_frame`, `gpu/vk/vk_texture.cpp`): an idle arena that was staged into in the last 2 s stays, up to 256 MB of them. 256 MB is what a card over 6.5 GB keeps anyway, so nothing changes there. Two seconds after a load's last upload the 64 MB rule holds again, and the 30 s rule after it. Arenas over 64 MB still go as soon as the GPU is done with them. - docs: a row for `RX_VK_STAGING_BURST_S` in `docs/development.md`; one run file in `docs/tested/`. - checks: `tests/test_staging_burst.py` (new) cuts `staging()` and the give-back loop out of `vk_texture.cpp` and runs frames through them on a clock of its own. ## Measurements **Verdict:** it removes the churn it targets: 16 arenas made an arrival become 4 in Hyrule Field and 15 become 5 in Kokiri Forest on a Steam Deck OLED, 120 to 170 ms of the replay thread less in the load's slow frames. The arrival as a whole is not measurably shorter, and GPU memory at the end of the run is the same. No frame rate gain is claimed. Passed. Hardware: SteamOS 3.9.5 (kernel 7.2.7-valve2), Steam Deck OLED: AMD Custom APU 0932, RADV VANGOGH, Mesa 26.2.0-devel, 16 GB shared with the GPU; the GPU's clock fixed at 1600 MHz Scene: the arrival in an area: a save in Hyrule Field and one in Kokiri Forest loaded by script; no screen (headless), handheld profile, Minimum preset, the game's own upscaler, no frame generation, no cap Method: the game and host built from main 9c9d69ed; the back end built alone from each commit (clang, same flags) and swapped in that game folder. Each run starts the game and loads the save, with `RX_VK_FRAME_TRACE=1`. Arenas made are the log's `a staging arena of 64 MB took ... ms to make` lines over the whole run (the log stops at 24, never reached). Frame times are the trace's, over the 600 presents after the load's transition frame (the first frame over 600 ms that uploads over 60 MB). GPU memory is `drm-memory-gtt` from `/proc/<pid>/fdinfo` at the end of the run. Main, this PR, and a third renderer (another pull request's), alternated, 3 runs a scene; the median of 3 How to reproduce: `RX_VK_FRAME_TRACE=1`, load a save, then `grep "staging arena of" logs/longshot_log.txt | wc -l`; `RX_VK_STAGING_BURST_S=0` for the old rule this PR: perf/staging-burst-keep @ d3004735 (this PR's code commit on main 9c9d69ed; on the fork as `measured/staging-burst-keep-d3004735`). The branch is now on main 9e6f231d (be132765 and a docs commit): `git range-diff` shows the same patch. Main has changed `texture_frame` in between (#735: the texture budget is set by the card, and an integrated GPU's line for the driver's count), but not `staging()` nor the loop that gives idle arenas back, which is all this PR touches; so the numbers hold. Not measured again on this main. Medians; each run in brackets, in the order run: | Hyrule Field | Before: main @ 9c9d69ed | After: this PR @ d3004735 | Δ | |---|---|---|---| | staging arenas made in a run | 16 (17 / 14 / 16) | 4 (4 / 4 / 4) | -12 | | time making them | 225 ms (242 / 202 / 225) | 58 ms (58 / 45 / 65) | -167 ms | | upload time in the frames over 60 ms | 292 ms (341 / 262 / 292) | 170 ms (163 / 170 / 171) | -122 ms | | time in frames over 60 ms | 3.0 s (2.9 / 3.0 / 3.3) | 3.0 s (2.9 / 3.4 / 3.0) | none | | longest frame after the transition | 543 ms (571 / 529 / 543) | 513 ms (491 / 513 / 540) | within the spread | | GPU memory at the end of the run | 3,290 MiB (3,290 / 3,249 / 3,317) | 3,316 MiB (3,316 / 3,323 / 3,313) | within the spread | | Kokiri Forest | Before: main @ 9c9d69ed | After: this PR @ d3004735 | Δ | |---|---|---|---| | staging arenas made in a run | 15 (15 / 15 / 15) | 5 (5 / 5 / 5) | -10 | | time making them | 192 ms (184 / 198 / 192) | 72 ms (78 / 72 / 64) | -120 ms | | upload time in the frames over 60 ms | 320 ms (320 / 360 / 314) | 231 ms (231 / 231 / 231) | -89 ms | | time in frames over 60 ms | 8.6 s (6.0 / 9.1 / 8.6) | 6.2 s (6.0 / 7.3 / 6.2) | within the spread | | longest frame after the transition | 890 ms (871 / 1,275 / 890) | 897 ms (897 / 945 / 885) | none | | GPU memory at the end of the run | 3,899 MiB (3,925 / 3,899 / 3,875) | 3,925 MiB (3,926 / 3,866 / 3,925) | within the spread | ``` vk: a staging arena of 64 MB took 15.5 ms to make ``` 15 such lines in each Kokiri Forest run on main, 5 with this PR. Read with care. The change removes ten to twelve arena creations an arrival and the 120 to 170 ms they cost, spread over the load's slow frames. It does not make the arrival measurably shorter on this machine: 3.0 s against 3.0 s in slow frames in Hyrule Field, and in Kokiri Forest main's own three runs go from 6.0 to 9.1 s. What it costs is under Risks. ### Flags and settings One new switch, on by default at 2 seconds. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_STAGING_BURST_S=<seconds>` | how long an idle staging arena stays after something was staged into it, up to 256 MB of them | no such rule: an idle arena past the 64 MB kept goes at the next present | 2; `=0` is the behaviour before | none on the picture; 10 to 12 fewer arenas made an arrival, up to 192 MB more kept while a load uploads and for 2 s after | helps: cards the driver allows 6.5 GB or less, which keep 64 MB (Steam Deck above); doesn't help: cards over 6.5 GB (they keep 256 MB already); untested: desktop cards of 6 GB or less, Windows in the game | leave unset (2): the arenas kept are ones the load is still using | ## Testing **Tested:** - `python -I -B tests/run_all.py` on macOS with the Vulkan headers given, on the rebased head: 507 passed, 48 failed, 39 skipped. The 48 fail the same way on main 9e6f231d on that machine (no `/proc`, no `posix_fallocate`, Python 3.9, the Linux launch scripts and the like). The suite ran beside four others and 11 more checks timed out in that run (on main too, 10); each passes when run alone. - `tests/test_staging_burst.py` (new), on macOS (clang) and Linux (g++ 14): 20 frames that stage in a row make 2 arenas and give none back (the old rule makes 11 and gives 9 back, which is how it fails on main); the 256 MB cap after a frame that needs six arenas; an arena over 64 MB still goes; back to 64 MB 2 s after the last upload and to none after 30 s; with 256 MB kept nothing changes; `RX_VK_STAGING_BURST_S=0` gives the old behaviour in each case. `tests/test_staging_arena_fallback.py` and `tests/test_texture_staging_publication.py`, which cut the same code, needed no change and pass. - In the game, a Steam Deck OLED, without a screen: the 6 runs above of this commit and 6 of main, no error, no device lost, 0 GPU timeouts in the kernel's log. With the picture, by a person: builds holding this change with other renderer-only ones played on the same Deck on 2026-11-14: reported smooth, nothing wrong reported. - Linux: the back end builds from the rebased head (ef080109; clang, x86-64-v3, on the Steam Deck): 0 failed. Windows: `longshot_vk.dll` cross-builds from the same head with llvm-mingw 20261104 on Linux: 0 failed. Not run on Windows. **Not tested:** - A desktop card of 6 GB or less, where an arena is VRAM behind the BAR and the 64 MB rule came from: neither the gain nor the cost was measured there. - Windows in the game. A long session; a moving camera with textures streaming in, where frames that upload in a row are the common case and the old rule should have made an arena every second frame too (not measured). **Must be tested:** - A desktop card the driver allows 6.5 GB or less (a 6 GB card or smaller), Windows or Linux: load a save, then change area a few times, expecting no new stall and the VRAM in use back at main's level a few seconds after each load (by hand: the driver's or the overlay's VRAM figure with main and with this PR, or with `RX_VK_STAGING_BURST_S=0` against unset on one build of this PR) ## Risks **What could break:** memory on a small card during a load. Where 64 MB of idle arenas was kept, up to 256 MB now stay while a load is uploading and for 2 s after its last upload: up to 192 MB more, VRAM on a desktop card with resizable BAR, system memory on an APU. It is memory the load was making and freeing in turn, not new memory, and a card over 6.5 GB keeps that much all the time. On the Deck the GPU memory at the end of the run is the same. **Turning it off:** `RX_VK_STAGING_BURST_S=0` is the old rule; reverting the commit removes the change. **Compatibility:** Nothing changes: saves and `longshot_options.ini` are untouched, no shader or pipeline cache is rebuilt (no stamped file is touched), `MKWVK_API_VERSION` is as it was. ## Reviewer notes - `RX_VK_STAGING_KEEP_MB=256` (existing) also stops the churn, by keeping 256 MB for good, which is what the 64 MB rule was made to avoid on small cards. - The rule's two numbers are the first tried, not tuned: most gaps between a load's uploading frames are under 0.3 s here, and 256 MB is the larger cards' own figure. - #395 is a crash after the driver refused 64 MB staging arenas eight times in a row during a load. This PR makes fewer of them during a load; it was not tested against that crash and does not claim to fix it. ## Issues See #395 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated.
A staging arena is free for reuse once the GPU finished the submission that read it, which is after the next frame
staged: frames that stage in a row need two arenas taking turns. A card of 6.5 GB or less keeps 64 MB of idle arenas
between loads, one arena, so the idle one of the two went at every second present and the frame after made a 64 MB
arena again: 9 to 41 ms of the replay thread each on a Steam Deck, where an arena is system memory the driver clears.

texture_frame now keeps the arenas staged into in the last 2 s, up to 256 MB of them (what a card over 6.5 GB keeps
anyway, so nothing changes there). Two seconds after a load's last upload the 64 MB rule holds again, and the 30 s
one after it. RX_VK_STAGING_BURST_S=<n>: that long, 0: as before.

tests/test_staging_burst.py runs staging() and the give-back loop, cut out of vk_texture.cpp, through frames on a
clock of its own: 20 frames in a row make 2 arenas (11 before), the rule's 256 MB cap, the return to 64 MB.

Assisted-by: Claude Fable 5.2
Co-Authored-By: Claude Fable 5.2 <[email protected]>
docs(tested): the staging arenas kept through a burst, on a Steam Deck OLED
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
beanstalk/approval Test farm not requested
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
341ca45298
Main 9c9d69ed against the commit on it (d3004735), alternated, 3 runs a scene: the arrival in Hyrule Field and in Kokiri Forest.

Assisted-by: Claude Fable 5.2

Co-Authored-By: Claude Fable 5.2 <[email protected]>
p0lux changed title from WIP: perf(vk): the staging arenas a load used a moment ago are kept, not made again every second frame to perf(vk): the staging arenas a load used a moment ago are kept, not made again every second frame 2026-11-14 19:47:48 +00:00
p0lux force-pushed perf/staging-burst-keep from 341ca45298
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
beanstalk/approval Test farm not requested
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to ef080109f4
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 15s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Guide screenshots / Guide images and local UI source review (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
2026-11-15 09:05:48 +00:00
Compare
Author
Contributor

Rebased onto main 9e6f231d (head ef080109): no conflict; git range-diff shows the same patch. Main changed texture_frame in between (#735, the texture budget), not staging() nor the loop that gives idle arenas back. The numbers are the ones measured before; the description says why they still hold.

The description now follows the new template: the status line, a summary under What changed, the verdict, Flags and settings under Measurements, Must be tested, and Risks with Compatibility.

On the rebased head: python -I -B tests/run_all.py on macOS gives 507 passed, 48 failed, 39 skipped, the 48 failing the same way on main 9e6f231d on that machine; the back end builds for Linux (clang, on a Steam Deck) and for Windows (llvm-mingw 20261104, cross-built), 0 failed.

Rebased onto main 9e6f231d (head ef080109): no conflict; `git range-diff` shows the same patch. Main changed `texture_frame` in between (#735, the texture budget), not `staging()` nor the loop that gives idle arenas back. The numbers are the ones measured before; the description says why they still hold. The description now follows the new template: the status line, a summary under What changed, the verdict, Flags and settings under Measurements, Must be tested, and Risks with Compatibility. On the rebased head: `python -I -B tests/run_all.py` on macOS gives 507 passed, 48 failed, 39 skipped, the 48 failing the same way on main 9e6f231d on that machine; the back end builds for Linux (clang, on a Steam Deck) and for Windows (llvm-mingw 20261104, cross-built), 0 failed.
HumanBean force-pushed perf/staging-burst-keep from ef080109f4
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 15s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Guide screenshots / Guide images and local UI source review (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to 8ea0aea205
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 4s
Third-party parts / As their notes say (pull_request) Successful in 8s
2026-11-15 18:44:34 +00:00
Compare
Owner

Rebased onto main @ bbe6fdf2. No tests were run here; the test boxes stay yours. One conflict in gpu/vk/vk_texture.cpp, the staging arenas' release test: main's shorter wait under Linux memory pressure (wait_s, linux_entered_pressure) and this PR's burst keep (allowed) are both kept, so the condition is kept + it->size > allowed || stale || linux_entered_pressure.

Rebased onto `main` @ bbe6fdf2. No tests were run here; the test boxes stay yours. One conflict in gpu/vk/vk_texture.cpp, the staging arenas' release test: main's shorter wait under Linux memory pressure (`wait_s`, `linux_entered_pressure`) and this PR's burst keep (`allowed`) are both kept, so the condition is `kept + it->size > allowed || stale || linux_entered_pressure`.
HumanBean left a comment

Code review only (no tests or builds run). The logic is correct. In the give-back loop, kept counts every arena that stays, busy ones included, so the 256 MB cap holds for recent arenas. Arenas over kArena, stale ones (30 s, or 5 s under Linux pressure) and linux_entered_pressure still go, because those terms are untouched. used_at is set whenever something is staged, so "recent" means what the description says. Cards that keep 256 MB see no change, and RX_VK_STAGING_BURST_S=0 restores the old rule. I found no vk_api.h, generated-file or third_party change.

My only concern is the one the description already lists under "Not tested" and "Must be tested". The rule keeps up to 192 MB more staging memory for 2 s after each upload, and on a desktop card of 6.5 GB or less that memory is VRAM behind the BAR. The 64 MB rule came from that case, the RTX 4050 laptop and issue 519 cards. Those are the cards with the least headroom while a load is also filling the texture budget. All the measurements are on a Steam Deck, where the arena is system memory. I'm not asking for a code change. I'd hold the merge for the desktop run listed under "Must be tested" (6 GB or smaller card, VRAM back at main's level a few seconds after each load), so I'm leaving a comment review and not approving.

One small point: the 256 MB figure is hard-coded in texture_frame and is also the default keep for larger cards. A named constant would stop the two drifting apart, but it is not blocking.

Code review only (no tests or builds run). The logic is correct. In the give-back loop, `kept` counts every arena that stays, busy ones included, so the 256 MB cap holds for recent arenas. Arenas over `kArena`, stale ones (30 s, or 5 s under Linux pressure) and `linux_entered_pressure` still go, because those terms are untouched. `used_at` is set whenever something is staged, so "recent" means what the description says. Cards that keep 256 MB see no change, and `RX_VK_STAGING_BURST_S=0` restores the old rule. I found no `vk_api.h`, generated-file or `third_party` change. My only concern is the one the description already lists under "Not tested" and "Must be tested". The rule keeps up to 192 MB more staging memory for 2 s after each upload, and on a desktop card of 6.5 GB or less that memory is VRAM behind the BAR. The 64 MB rule came from that case, the RTX 4050 laptop and issue 519 cards. Those are the cards with the least headroom while a load is also filling the texture budget. All the measurements are on a Steam Deck, where the arena is system memory. I'm not asking for a code change. I'd hold the merge for the desktop run listed under "Must be tested" (6 GB or smaller card, VRAM back at main's level a few seconds after each load), so I'm leaving a comment review and not approving. One small point: the 256 MB figure is hard-coded in `texture_frame` and is also the default `keep` for larger cards. A named constant would stop the two drifting apart, but it is not blocking.
@ -4246,0 +4248,4 @@
// made a 64 MB arena again, 9 to 41 ms each on a Steam Deck. RX_VK_STAGING_BURST_S=<n>: that long, 0: as before)
static const int64_t burst_s = getenv("RX_VK_STAGING_BURST_S") ? atoll(getenv("RX_VK_STAGING_BURST_S")) : 2;
const bool recent = burst_s && std::chrono::steady_clock::now() - it->used_at < std::chrono::seconds(burst_s);
const uint64_t allowed = recent ? std::max<uint64_t>(keep, 256ull << 20) : keep;
Owner

This is where the 192 MB of extra arenas can land in VRAM on a 4-6 GB desktop card for 2 s after each upload. Please get the desktop-card run from the Must be tested list before merge.

This is where the 192 MB of extra arenas can land in VRAM on a 4-6 GB desktop card for 2 s after each upload. Please get the desktop-card run from the Must be tested list before merge.
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 4s
Third-party parts / As their notes say (pull_request) Successful in 8s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'perf/staging-burst-keep:p0lux-perf/staging-burst-keep'
git switch 'p0lux-perf/staging-burst-keep'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!568
No description provided.