fix(vk): a read-back after a timed-out one waits for the GPU, and its textures are kept #553

Open
zeldaootr wants to merge 3 commits from zeldaootr/Longshot:readback-timeout-reuse into main
Contributor

Type: fix · Risk: medium · Tested on: Linux x86-64 (game-free); Steam Deck OLED, SteamOS 3.9.5, RADV (a tester's run) · Needs: none

Why

When the GPU was stuck long enough for a texture read-back to time out (5 s), the next read-back reused its command buffer and fence while the GPU was still running them, which Vulkan forbids and can turn a stall into a crash or a device lost; and the textures waiting on it were evicted with nothing copied, so they came back empty. The tracker has no issue for it (searched).

How to reproduce

Seen on: the source, found by reading gpu/vk/vk_texture.cpp; a 5 s GPU stall cannot be made on demand in the game, so the game-free test reproduces it against a stub Vulkan
Settings and switches: defaults
Steps: at the test-only commit 08514708 (main aa95d35f plus the test), run python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++
Expected: a read-back after a timed-out one waits for the GPU to finish the old one, the textures waiting on it are kept, and nothing calls Vulkan after a device lost
Actual: on main the next read-back resets and resubmits the command buffer the GPU still runs (3 Vulkan rule violations), the textures are evicted with no copy, and after a device lost read-backs still call Vulkan (10 calls, 2 waits of 5 s)

What changed

A read-back now remembers that the one before it timed out or lost the device, and does not reuse its command buffer and fence until the GPU is past them. Textures whose write-back could not run because of it stay resident for a later pass instead of being evicted empty.

  • vk: readback_submit_wait() in gpu/vk/vk_texture.cpp notes a wait that timed out (rb_pending) or failed otherwise (rb_lost, the device lost). readback_begin() then checks the fence without waiting: while the old read-back still runs, no new one starts; once it has signalled, read-backs carry on; after a device lost, none starts and none is waited for.
  • vk: flush_textures() marks the write-backs that could not run because a read-back is still pending (Flush::busy), and texture_frame() keeps those textures (flush_later) for a later pass instead of evicting them with no copy; textures that need no write-back still go.
  • tests: tests/test_readback_timeout_reuse.py cuts the read-back state and functions, flush_textures and the end of texture_frame's eviction out of the source and runs them against a stub Vulkan that tracks what is pending: a read-back after a timed-out one, an eviction pass whose first batch times out, and a device lost.

Measurements

Verdict: pass. The fix is proven by the game-free test (3 of 3 cases fail on main, 0 with this PR). In the game it is a non-regression result: on a tester's Steam Deck OLED the timed-out read-back never happened (no did not complete in time line in 4 runs), and fps, faults and texture evictions were the same as main within main's own run-to-run spread.

Hardware: Steam Deck OLED, AMD Custom APU 0932 (RADV VANGOGH), 16 GB, Mesa 26.2.0-devel git-5c65d4fc31, SteamOS 3.9.5 (build 20261023.100), kernel 7.2.7-valve2, Game Mode, docked, stock clocks (dukenheimer's run, test request T-4H95); game-free: Linux x86-64 server, no GPU or game
Scene: a route through the control bridge: Continue into Kokiri Forest, then 10 debug teleports with about 45 s in each area (Hyrule Field, Kakariko, the Market, Hyrule Field, the Lost Woods, Goron City, Death Mountain Trail, Zora's River, Gerudo Valley, Kokiri Forest), about 11 minutes a run; the Minimum (4GB) preset (render 50% of 1280x720, a 2.2 GB VRAM budget, Handheld profile), the game's own upscaler, frame generation off
Method: fps from the bridge's status every 5 s, averaged over the time in play; the log lines from each run's longshot_log.txt; the kernel's amdgpu lines; one picture at each arrival. Four runs, interleaved (this PR, main, main, this PR), one test folder with warm caches and the binaries swapped; the median of each build's two runs. Game-free: python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++ at 08514708 and at 3b245fa7 (rebased onto main aa95d35f), with g++ 13.3 and with clang 23.1.5; before that, at 9fc90117 and 515a3fdf on main 53f96d78, and before the first rebase at ecacc39c and 93c285b7, twice each, alternating
How to reproduce: build main 7ff5256e and the combined build (main 7ff5256e with #550 to #558 merged in order, this PR at 93c285b7), both x86-64-v3 with the release play profile and LLVM 23.1.5; choose the Minimum (4GB) preset, then drive the route above; for the game-free check, run the test command at each commit

Before: main @ 7ff5256e After: this PR @ 93c285b7 (in the combined build)
fps over the route (run 1 / run 2; median) 16.6 / 17.7; 17.15 16.8 / 17.1; 16.95
did not complete in time lines 0 / 0 0 / 0
device lost / device fault / GPU stalled / crash 0 / 0 / 0 / 0 in both runs 0 / 0 / 0 / 0 in both runs
evicted, made again lines 17 / 14 17 / 14
garbled or rainbow textures in the arrival pictures none none

By area, the two builds' means are within 2 fps everywhere, in both directions (this PR +1.9 in Goron City, -1.6 in Gerudo Valley); main's own two runs differ by more than the builds do. The textures were under pressure in every run: 2.7 to 2.8 GB against a 2,560 MB texture budget, VRAM in use peaking at 4.5 to 5.1 GB of the 5.3 to 6.1 GB allowed, about 4.3 GB swapped, MemAvailable down to 281-471 MB, and two written back into their memory lines a run (8,640 KB each, 12 to 64 ms). The kernel's amdgpu lines were only the display's Losing delta precision while programming shaper LUT notices (6 a start, 12 in run 1): no reset, timeout, hang or fault. The tester's table, the fps by area and the pictures: #553 (comment)

Before: main @ aa95d35f (with the test, 08514708) After: this PR @ 3b245fa7
cases of 3 (begin, later, lost) that fail (g++ and clang the same) 3 (3 Vulkan rule violations; 10 calls and 2 waits of 5 s after a device lost) 0
08514708: FAIL: a later batch of the pass reset or resubmitted the read-back the GPU still runs (3 violation(s)) / FAIL: read-backs after the device was lost still called Vulkan (10 calls, 2 waits of 5 s) / read-back after a timeout: failed: begin, later, lost (exit 1, g++ and clang)
3b245fa7: read-back after a timeout, begin: ok / later: ok / lost: ok (exit 0, g++ and clang)

Flags and settings

None.

Testing

Tested: Linux x86-64, game-free. The new test fails on the test-only commit 08514708 and passes at 3b245fa7, this branch rebased onto main aa95d35f, with g++ 13.3 and with clang 23.1.5 (before, it did the same at 9fc90117 and 515a3fdf on main 53f96d78, and at ecacc39c and 93c285b7, twice each, alternating) (python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++). This PR's own tests and the checks its fix commit edits ran at both commits, and once more on land/integration 27553c83 (main 7ff5256e with this PR and twelve other fixes merged), where the thirteen fixes' tests and edited checks pass, 16 of 16; the full python3 -I -B tests/run_all.py ran on that branch too (see the checklist).

In the game: dukenheimer ran test request T-4H95 on a Steam Deck OLED (RADV VANGOGH, Mesa 26.2.0-devel, SteamOS 3.9.5, Game Mode, docked) and reported a pass on 2026-11-15 at 03:12 UTC (#553 (comment)). The build was the combined build of main 7ff5256e with #550 to #558 merged in order, this PR at its head then, 93c285b7 (3b245fa7 after the rebases, the same change by git patch-id), against main 7ff5256e: four runs of the 11-minute route above, interleaved. No did not complete in time line in any run, so the timed-out read-back this PR changes never happened there: it is a non-regression run, not a run of the fix. No crash, device lost, device fault or GPU stall, no kernel reset, timeout, hang or fault, and no garbled or rainbow textures in the 44 arrival pictures (they differ only in timing: an arrival still fading in, Navi's place, the area title part-way through its dissolve, which main shows the same way). fps was the same within main's own spread.

The branch has been rebased since the tester's run, onto main aa95d35f, with its commits re-authored under the forge's noreply address; each commit's change is identical to before by git patch-id, so the commits named above are the ones tested and the result stands for this head (a6ad0e0e).

Not tested: A real 5 s GPU stall or device lost, in the game or on any GPU: the fixed paths are proven only against the stub, since a stall that long cannot be made on demand. Windows in the game (the renderer was built for Windows, see the checklist).

Must be tested: None

Risks

What could break: While a read-back is pending, the textures that need a write-back stay resident and are tried again on a later pass; a GPU that hangs for good without reporting device-lost keeps them resident for the session (more VRAM held, not lost texels). Write-backs of virtual pages that cannot run while a read-back is pending are still lost, as on main. After a device lost, every later write-back fails at once. readback-timeout-keeps-source-textures (#554) changes the lines next to these in flush_textures (the timed-out branch); the two merge without a conflict in either order and are meant to land together or one after the other.

Turning it off: revert the commit

Compatibility: Nothing changes

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. Onto main aa95d35f with no conflict, the commits re-authored under the forge's noreply address: each of the three commits' changes (the test, the fix, the docs/tested/ file) is identical to before by git patch-id. Since 53f96d78, main changed gpu/vk/vk_texture.cpp in #735 (511e9b03), away from this PR's lines.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). Run on this branch at cbe9b9ce on main 53f96d78, before this rebase (a6ad0e0e now, the same patches by git patch-id; the Vulkan and SPIR-V headers from the CPM cache, 8 at once, on a loaded Linux x86-64 server): 530 passed, 16 failed, 48 skipped. 15 of them fail on main 53f96d78 in the same run: test_appimage_player_build, test_bake_portability, test_cpm_discovery, test_crash_dialog, test_env_on, test_lift_cache, test_lift_cleanup_error, test_make_game_cache, test_pipeline_tour, test_readback_late, test_vk_pgo, test_vram_budget_row, test_walk_key, nx2/test_mesh_first_types.py and nx2/test_texture_init_barrier.py. The 16th, test_pipeline_cache_save (a timing check: wait_until(play_saves, 3)), passed 2 of 2 when run alone on this branch, as on main; main's run timed out test_fpx_sticky instead, which passed here and passes alone on main. After the rebase onto main aa95d35f, at a6ad0e0e: 35 checks, this PR's test and those that read its changed files, with g++ 13.3 and with clang 23.1.5: 33 pass; the 2 that fail fail on main aa95d35f too (run alone there, both compilers): nx2/test_bc_storage_views.py (no clang++ on this PC's path), nx2/test_texture_init_barrier.py (#628).
  • 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. The tester's four Steam Deck runs (this PR, main, main, this PR); the game-free test twice each, alternating.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md). docs/tested/2026-11-15-readback-timeout-dukenheimer-t-4h95.md, the tester's run; scripts/tested_configurations.py and tests/test_tested_configurations.py pass with it. The same file, byte for byte, is in #554: the two branches merge into main together without a conflict.
  • 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. Built on Linux for both, in the combined branch 27553c83: RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windows with llvm-mingw 20261104 linked longshot_vk.dll (sha256 25117493...), and the same without RX_TARGET linked longshot_vk.so; the changed file has no new warning under -Wall -Wextra on either platform against main. The tester's combined build linked for Linux (x86-64-v3, LLVM 23.1.5). After the rebase onto main aa95d35f, gpu/vk/vk_texture.cpp at a6ad0e0e checked whole (-fsyntax-only -Wall -Wextra, clang 23.1.5 for Linux and llvm-mingw 20261104 for Windows): the same warnings as main aa95d35f's, none new.
**Type:** fix · **Risk:** medium · **Tested on:** Linux x86-64 (game-free); Steam Deck OLED, SteamOS 3.9.5, RADV (a tester's run) · **Needs:** none ## Why When the GPU was stuck long enough for a texture read-back to time out (5 s), the next read-back reused its command buffer and fence while the GPU was still running them, which Vulkan forbids and can turn a stall into a crash or a device lost; and the textures waiting on it were evicted with nothing copied, so they came back empty. The tracker has no issue for it (searched). ## How to reproduce **Seen on:** the source, found by reading `gpu/vk/vk_texture.cpp`; a 5 s GPU stall cannot be made on demand in the game, so the game-free test reproduces it against a stub Vulkan **Settings and switches:** defaults **Steps:** at the test-only commit 08514708 (main aa95d35f plus the test), run `python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++` **Expected:** a read-back after a timed-out one waits for the GPU to finish the old one, the textures waiting on it are kept, and nothing calls Vulkan after a device lost **Actual:** on main the next read-back resets and resubmits the command buffer the GPU still runs (3 Vulkan rule violations), the textures are evicted with no copy, and after a device lost read-backs still call Vulkan (10 calls, 2 waits of 5 s) ## What changed A read-back now remembers that the one before it timed out or lost the device, and does not reuse its command buffer and fence until the GPU is past them. Textures whose write-back could not run because of it stay resident for a later pass instead of being evicted empty. - vk: `readback_submit_wait()` in `gpu/vk/vk_texture.cpp` notes a wait that timed out (`rb_pending`) or failed otherwise (`rb_lost`, the device lost). `readback_begin()` then checks the fence without waiting: while the old read-back still runs, no new one starts; once it has signalled, read-backs carry on; after a device lost, none starts and none is waited for. - vk: `flush_textures()` marks the write-backs that could not run because a read-back is still pending (`Flush::busy`), and `texture_frame()` keeps those textures (`flush_later`) for a later pass instead of evicting them with no copy; textures that need no write-back still go. - tests: `tests/test_readback_timeout_reuse.py` cuts the read-back state and functions, `flush_textures` and the end of `texture_frame`'s eviction out of the source and runs them against a stub Vulkan that tracks what is pending: a read-back after a timed-out one, an eviction pass whose first batch times out, and a device lost. ## Measurements **Verdict:** pass. The fix is proven by the game-free test (3 of 3 cases fail on main, 0 with this PR). In the game it is a non-regression result: on a tester's Steam Deck OLED the timed-out read-back never happened (no `did not complete in time` line in 4 runs), and fps, faults and texture evictions were the same as main within main's own run-to-run spread. Hardware: Steam Deck OLED, AMD Custom APU 0932 (RADV VANGOGH), 16 GB, Mesa 26.2.0-devel git-5c65d4fc31, SteamOS 3.9.5 (build 20261023.100), kernel 7.2.7-valve2, Game Mode, docked, stock clocks (dukenheimer's run, test request T-4H95); game-free: Linux x86-64 server, no GPU or game Scene: a route through the control bridge: Continue into Kokiri Forest, then 10 debug teleports with about 45 s in each area (Hyrule Field, Kakariko, the Market, Hyrule Field, the Lost Woods, Goron City, Death Mountain Trail, Zora's River, Gerudo Valley, Kokiri Forest), about 11 minutes a run; the Minimum (4GB) preset (render 50% of 1280x720, a 2.2 GB VRAM budget, Handheld profile), the game's own upscaler, frame generation off Method: fps from the bridge's status every 5 s, averaged over the time in play; the log lines from each run's `longshot_log.txt`; the kernel's amdgpu lines; one picture at each arrival. Four runs, interleaved (this PR, main, main, this PR), one test folder with warm caches and the binaries swapped; the median of each build's two runs. Game-free: `python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++` at 08514708 and at 3b245fa7 (rebased onto main aa95d35f), with g++ 13.3 and with clang 23.1.5; before that, at 9fc90117 and 515a3fdf on main 53f96d78, and before the first rebase at ecacc39c and 93c285b7, twice each, alternating How to reproduce: build main 7ff5256e and the combined build (main 7ff5256e with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/550 to https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/558 merged in order, this PR at 93c285b7), both x86-64-v3 with the release play profile and LLVM 23.1.5; choose the Minimum (4GB) preset, then drive the route above; for the game-free check, run the test command at each commit | | Before: main @ 7ff5256e | After: this PR @ 93c285b7 (in the combined build) | |---|---|---| | fps over the route (run 1 / run 2; median) | 16.6 / 17.7; 17.15 | 16.8 / 17.1; 16.95 | | `did not complete in time` lines | 0 / 0 | 0 / 0 | | device lost / device fault / GPU stalled / crash | 0 / 0 / 0 / 0 in both runs | 0 / 0 / 0 / 0 in both runs | | `evicted, made again` lines | 17 / 14 | 17 / 14 | | garbled or rainbow textures in the arrival pictures | none | none | By area, the two builds' means are within 2 fps everywhere, in both directions (this PR +1.9 in Goron City, -1.6 in Gerudo Valley); main's own two runs differ by more than the builds do. The textures were under pressure in every run: 2.7 to 2.8 GB against a 2,560 MB texture budget, VRAM in use peaking at 4.5 to 5.1 GB of the 5.3 to 6.1 GB allowed, about 4.3 GB swapped, MemAvailable down to 281-471 MB, and two `written back into their memory` lines a run (8,640 KB each, 12 to 64 ms). The kernel's amdgpu lines were only the display's `Losing delta precision while programming shaper LUT` notices (6 a start, 12 in run 1): no reset, timeout, hang or fault. The tester's table, the fps by area and the pictures: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/553#issuecomment-22328 | | Before: main @ aa95d35f (with the test, 08514708) | After: this PR @ 3b245fa7 | |---|---|---| | cases of 3 (begin, later, lost) that fail (g++ and clang the same) | 3 (3 Vulkan rule violations; 10 calls and 2 waits of 5 s after a device lost) | 0 | ``` 08514708: FAIL: a later batch of the pass reset or resubmitted the read-back the GPU still runs (3 violation(s)) / FAIL: read-backs after the device was lost still called Vulkan (10 calls, 2 waits of 5 s) / read-back after a timeout: failed: begin, later, lost (exit 1, g++ and clang) 3b245fa7: read-back after a timeout, begin: ok / later: ok / lost: ok (exit 0, g++ and clang) ``` ### Flags and settings None. ## Testing **Tested:** Linux x86-64, game-free. The new test fails on the test-only commit 08514708 and passes at 3b245fa7, this branch rebased onto main aa95d35f, with g++ 13.3 and with clang 23.1.5 (before, it did the same at 9fc90117 and 515a3fdf on main 53f96d78, and at ecacc39c and 93c285b7, twice each, alternating) (`python3 -I -B tests/test_readback_timeout_reuse.py --cxx g++`). This PR's own tests and the checks its fix commit edits ran at both commits, and once more on `land/integration` 27553c83 (main 7ff5256e with this PR and twelve other fixes merged), where the thirteen fixes' tests and edited checks pass, 16 of 16; the full `python3 -I -B tests/run_all.py` ran on that branch too (see the checklist). In the game: dukenheimer ran test request T-4H95 on a Steam Deck OLED (RADV VANGOGH, Mesa 26.2.0-devel, SteamOS 3.9.5, Game Mode, docked) and reported a pass on 2026-11-15 at 03:12 UTC (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/553#issuecomment-22328). The build was the combined build of main 7ff5256e with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/550 to https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/558 merged in order, this PR at its head then, 93c285b7 (3b245fa7 after the rebases, the same change by `git patch-id`), against main 7ff5256e: four runs of the 11-minute route above, interleaved. No `did not complete in time` line in any run, so the timed-out read-back this PR changes never happened there: it is a non-regression run, not a run of the fix. No crash, device lost, device fault or GPU stall, no kernel reset, timeout, hang or fault, and no garbled or rainbow textures in the 44 arrival pictures (they differ only in timing: an arrival still fading in, Navi's place, the area title part-way through its dissolve, which main shows the same way). fps was the same within main's own spread. The branch has been rebased since the tester's run, onto main aa95d35f, with its commits re-authored under the forge's noreply address; each commit's change is identical to before by `git patch-id`, so the commits named above are the ones tested and the result stands for this head (a6ad0e0e). **Not tested:** A real 5 s GPU stall or device lost, in the game or on any GPU: the fixed paths are proven only against the stub, since a stall that long cannot be made on demand. Windows in the game (the renderer was built for Windows, see the checklist). **Must be tested:** None ## Risks **What could break:** While a read-back is pending, the textures that need a write-back stay resident and are tried again on a later pass; a GPU that hangs for good without reporting device-lost keeps them resident for the session (more VRAM held, not lost texels). Write-backs of virtual pages that cannot run while a read-back is pending are still lost, as on main. After a device lost, every later write-back fails at once. `readback-timeout-keeps-source-textures` (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/554) changes the lines next to these in `flush_textures` (the timed-out branch); the two merge without a conflict in either order and are meant to land together or one after the other. **Turning it off:** revert the commit **Compatibility:** Nothing changes ## 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`. Onto main aa95d35f with no conflict, the commits re-authored under the forge's noreply address: each of the three commits' changes (the test, the fix, the `docs/tested/` file) is identical to before by `git patch-id`. Since 53f96d78, main changed `gpu/vk/vk_texture.cpp` in https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/735 (511e9b03), away from this PR's lines. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). Run on this branch at cbe9b9ce on main 53f96d78, before this rebase (a6ad0e0e now, the same patches by `git patch-id`; the Vulkan and SPIR-V headers from the CPM cache, 8 at once, on a loaded Linux x86-64 server): 530 passed, 16 failed, 48 skipped. 15 of them fail on main 53f96d78 in the same run: `test_appimage_player_build`, `test_bake_portability`, `test_cpm_discovery`, `test_crash_dialog`, `test_env_on`, `test_lift_cache`, `test_lift_cleanup_error`, `test_make_game_cache`, `test_pipeline_tour`, `test_readback_late`, `test_vk_pgo`, `test_vram_budget_row`, `test_walk_key`, `nx2/test_mesh_first_types.py` and `nx2/test_texture_init_barrier.py`. The 16th, `test_pipeline_cache_save` (a timing check: `wait_until(play_saves, 3)`), passed 2 of 2 when run alone on this branch, as on main; main's run timed out `test_fpx_sticky` instead, which passed here and passes alone on main. After the rebase onto main aa95d35f, at a6ad0e0e: 35 checks, this PR's test and those that read its changed files, with g++ 13.3 and with clang 23.1.5: 33 pass; the 2 that fail fail on main aa95d35f too (run alone there, both compilers): `nx2/test_bc_storage_views.py` (no `clang++` on this PC's path), `nx2/test_texture_init_barrier.py` (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/628). - [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. The tester's four Steam Deck runs (this PR, main, main, this PR); the game-free test twice each, alternating. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). `docs/tested/2026-11-15-readback-timeout-dukenheimer-t-4h95.md`, the tester's run; `scripts/tested_configurations.py` and `tests/test_tested_configurations.py` pass with it. The same file, byte for byte, is in https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/554: the two branches merge into main together without a conflict. - [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. Built on Linux for both, in the combined branch 27553c83: `RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windows` with llvm-mingw 20261104 linked `longshot_vk.dll` (sha256 25117493...), and the same without `RX_TARGET` linked `longshot_vk.so`; the changed file has no new warning under `-Wall -Wextra` on either platform against `main`. The tester's combined build linked for Linux (x86-64-v3, LLVM 23.1.5). After the rebase onto main aa95d35f, `gpu/vk/vk_texture.cpp` at a6ad0e0e checked whole (`-fsyntax-only -Wall -Wextra`, clang 23.1.5 for Linux and llvm-mingw 20261104 for Windows): the same warnings as main aa95d35f's, none new.
Assisted-by: Claude
keep a timed-out read-back's rb_cmd and rb_fence until the GPU is past them, and the textures waiting on it
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
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
93c285b7c4
Assisted-by: Claude
Author
Contributor

Filled in: the full game-free suite on the combined branch, the Windows and Linux builds, and the test request T-4H95. Still work in progress until a Steam Deck run is in.

Filled in: the full game-free suite on the combined branch, the Windows and Linux builds, and the test request T-4H95. Still work in progress until a Steam Deck run is in.
Contributor

Test request T-4H95: pass

Built in a combined build of main 7ff5256e with #550 to #558 merged in order, this PR at its head 93c285b7; the comparison is main
7ff5256e. Setup and method below.

Result

run build fps over the route 'did not complete in time' device lost / device fault / GPU stalled / crash textures evicted and made again
1 combined 16.8 0 0 / 0 / 0 / 0 17
2 main 16.6 0 0 / 0 / 0 / 0 17
3 main 17.7 0 0 / 0 / 0 / 0 14
4 combined 17.1 0 0 / 0 / 0 / 0 14
  • 'did not complete in time': no such line in any of the four runs, so there are no lines to post. The timed-out read-back
    that this PR changes never happened here: this is a non-regression result.
  • fps: 16.8 and 17.1 with this PR, 16.6 and 17.7 on main. Main's own two runs differ by more than the builds do. By area, the
    two builds' means are within 2 fps everywhere, in both directions: the PR is ahead in Goron City (+1.9) and behind in Gerudo
    Valley (-1.6) (table below).
  • Texture pressure: textures were evicted and made again in every run (14 to 17 evicted, made again lines).
    • In play the textures reached 2.7 to 2.8 GB against a 2,560 MB texture budget.
    • VRAM in use peaked at 4.5 to 5.1 GB of the 5.3 to 6.1 GB allowed.
    • Memory was short too: about 4.3 GB swapped in every run, with MemAvailable down to 281-471 MB.
    • Two written back into their memory lines a run (8,640 KB each, 12 to 64 ms), in all four runs.
  • Picture: no garbled or rainbow textures in any of the 44 arrival pictures (sheet attached: one row a run, one column an area).
    They differ only in timing:
    • two arrivals were still fading in from black, one in each build (the second Field visit in runs 1 and 3);
    • Navi's place;
    • the area title. In run 1 the last picture caught "Kokiri Forest" part-way through its dissolve. The title dissolves in and
      out the same way on main: a quick series of pictures through that arrival on each build caught both at those stages
      (attached, with the four runs' own pictures of it below).
  • Faults: none. The kernel's amdgpu lines in every run were only the display's "Losing delta precision while programming
    shaper LUT" notices (6 a start; 12 in run 1). No reset, timeout, hang or fault.

Setup

  • Steam Deck OLED: AMD Custom APU 0932 (RADV VANGOGH), 16 GB; Mesa 26.2.0-devel git-5c65d4fc31; SteamOS 3.9.5 (build 20261023.100),
    kernel 7.2.7-valve2. Game Mode, docked, stock clocks.
  • Builds: main 7ff5256e, and the combined build: main + #550 to #558, merged in order, all clean. Both for x86-64-v3 with the
    release play profile and LLVM 23.1.5. One test folder (warm caches) with the binaries swapped.
  • Settings: the Minimum (4GB) preset, chosen through the menu's own setter in a start of its own. Every run then used that
    options file.
    • On this Deck main's own defaults (Auto) already have Minimum's render resolution (50% of 1280x720) and its 2.2 GB VRAM budget.
    • So Minimum changed the Profile to Handheld, the window scaling filter, and sharpness to 30.
    • Upscaler: the game's own; frame generation off.
  • Route, the same each run, through the control bridge: Continue into Kokiri Forest, then 10 debug teleports with about 45 s in
    each area: Hyrule Field, Kakariko, the Market, Hyrule Field, the Lost Woods, Goron City, Death Mountain Trail, Zora's River, Gerudo
    Valley, Kokiri Forest. About 11 minutes of play a run.
    • Link stood at each arrival point while the camera turned round, one way then the other.
    • Every teleport was taken on the first try.
  • Measured: fps is the bridge's status every 5 s (the game's frames over the last second), averaged over the time in play. One
    picture at each arrival. The log lines are from each run's longshot_log.txt.
  • Order: combined, main, main, combined.
fps by area (the mean of each build's two runs)
area run 1, combined run 2, main run 3, main run 4, combined combined main
Kokiri Forest 15.5 15.6 15.5 14.1 14.8 15.5
Hyrule Field 18.4 20.1 20.2 20.6 19.5 20.2
Kakariko 15.9 14.1 16.5 15.4 15.7 15.3
Market 15.2 15.1 15.9 15.5 15.4 15.5
Hyrule Field 16.5 16.9 18.8 17.7 17.1 17.8
Lost Woods 12.4 14.8 13.5 14.5 13.4 14.1
Goron City 23.0 19.3 24.5 24.7 23.8 21.9
Death Mountain Trail 16.2 14.9 16.0 15.8 16.0 15.4
Zora's River 16.7 15.8 15.9 15.2 15.9 15.9
Gerudo Valley 18.0 18.3 20.1 17.3 17.6 19.2
Kokiri Forest 17.9 18.5 20.0 17.7 17.8 19.2

t-4h95-arrivals.png

t-4h95-title-dissolve.png

Test request T-4H95: pass Built in a combined build of main 7ff5256e with #550 to #558 merged in order, this PR at its head 93c285b7; the comparison is main 7ff5256e. Setup and method below. **Result** | run | build | fps over the route | 'did not complete in time' | device lost / device fault / GPU stalled / crash | textures evicted and made again | |---|---|---|---|---|---| | 1 | combined | 16.8 | 0 | 0 / 0 / 0 / 0 | 17 | | 2 | main | 16.6 | 0 | 0 / 0 / 0 / 0 | 17 | | 3 | main | 17.7 | 0 | 0 / 0 / 0 / 0 | 14 | | 4 | combined | 17.1 | 0 | 0 / 0 / 0 / 0 | 14 | - **'did not complete in time':** no such line in any of the four runs, so there are no lines to post. The timed-out read-back that this PR changes never happened here: this is a non-regression result. - **fps:** 16.8 and 17.1 with this PR, 16.6 and 17.7 on main. Main's own two runs differ by more than the builds do. By area, the two builds' means are within 2 fps everywhere, in both directions: the PR is ahead in Goron City (+1.9) and behind in Gerudo Valley (-1.6) (table below). - **Texture pressure:** textures were evicted and made again in every run (14 to 17 `evicted, made again` lines). - In play the textures reached 2.7 to 2.8 GB against a 2,560 MB texture budget. - VRAM in use peaked at 4.5 to 5.1 GB of the 5.3 to 6.1 GB allowed. - Memory was short too: about 4.3 GB swapped in every run, with MemAvailable down to 281-471 MB. - Two `written back into their memory` lines a run (8,640 KB each, 12 to 64 ms), in all four runs. - **Picture:** no garbled or rainbow textures in any of the 44 arrival pictures (sheet attached: one row a run, one column an area). They differ only in timing: - two arrivals were still fading in from black, one in each build (the second Field visit in runs 1 and 3); - Navi's place; - the area title. In run 1 the last picture caught "Kokiri Forest" part-way through its dissolve. The title dissolves in and out the same way on main: a quick series of pictures through that arrival on each build caught both at those stages (attached, with the four runs' own pictures of it below). - **Faults:** none. The kernel's amdgpu lines in every run were only the display's "Losing delta precision while programming shaper LUT" notices (6 a start; 12 in run 1). No reset, timeout, hang or fault. **Setup** - Steam Deck OLED: AMD Custom APU 0932 (RADV VANGOGH), 16 GB; Mesa 26.2.0-devel git-5c65d4fc31; SteamOS 3.9.5 (build 20261023.100), kernel 7.2.7-valve2. Game Mode, docked, stock clocks. - **Builds:** main 7ff5256e, and the combined build: main + #550 to #558, merged in order, all clean. Both for x86-64-v3 with the release play profile and LLVM 23.1.5. One test folder (warm caches) with the binaries swapped. - **Settings:** the Minimum (4GB) preset, chosen through the menu's own setter in a start of its own. Every run then used that options file. - On this Deck main's own defaults (Auto) already have Minimum's render resolution (50% of 1280x720) and its 2.2 GB VRAM budget. - So Minimum changed the Profile to Handheld, the window scaling filter, and sharpness to 30. - Upscaler: the game's own; frame generation off. - **Route, the same each run, through the control bridge:** Continue into Kokiri Forest, then 10 debug teleports with about 45 s in each area: Hyrule Field, Kakariko, the Market, Hyrule Field, the Lost Woods, Goron City, Death Mountain Trail, Zora's River, Gerudo Valley, Kokiri Forest. About 11 minutes of play a run. - Link stood at each arrival point while the camera turned round, one way then the other. - Every teleport was taken on the first try. - **Measured:** fps is the bridge's status every 5 s (the game's frames over the last second), averaged over the time in play. One picture at each arrival. The log lines are from each run's `longshot_log.txt`. - **Order:** combined, main, main, combined. <details><summary>fps by area (the mean of each build's two runs)</summary> | area | run 1, combined | run 2, main | run 3, main | run 4, combined | combined | main | |---|---|---|---|---|---|---| | Kokiri Forest | 15.5 | 15.6 | 15.5 | 14.1 | 14.8 | 15.5 | | Hyrule Field | 18.4 | 20.1 | 20.2 | 20.6 | 19.5 | 20.2 | | Kakariko | 15.9 | 14.1 | 16.5 | 15.4 | 15.7 | 15.3 | | Market | 15.2 | 15.1 | 15.9 | 15.5 | 15.4 | 15.5 | | Hyrule Field | 16.5 | 16.9 | 18.8 | 17.7 | 17.1 | 17.8 | | Lost Woods | 12.4 | 14.8 | 13.5 | 14.5 | 13.4 | 14.1 | | Goron City | 23.0 | 19.3 | 24.5 | 24.7 | 23.8 | 21.9 | | Death Mountain Trail | 16.2 | 14.9 | 16.0 | 15.8 | 16.0 | 15.4 | | Zora's River | 16.7 | 15.8 | 15.9 | 15.2 | 15.9 | 15.9 | | Gerudo Valley | 18.0 | 18.3 | 20.1 | 17.3 | 17.6 | 19.2 | | Kokiri Forest | 17.9 | 18.5 | 20.0 | 17.7 | 17.8 | 19.2 | </details> ![t-4h95-arrivals.png](https://git.magicbean.dev/attachments/a7f1c747-7778-484e-9291-da87ddd33754) ![t-4h95-title-dissolve.png](https://git.magicbean.dev/attachments/c148f070-92f4-42b0-bf46-0a772a09b681)
Author
Contributor

Thank you, dukenheimer, for the Steam Deck runs of T-4H95 (#553 (comment)).

Rebased onto main 53f96d78 with no conflict: the test and fix commits are the same as before (git range-diff: ecacc39c is now 9fc90117, 93c285b7 is now 515a3fdf), and the run is added as docs/tested/2026-11-15-readback-timeout-dukenheimer-t-4h95.md in a third commit, cbe9b9ce (the same file as in #554; the two merge into main together without a conflict). The description now ticks the rebase and docs/tested/ boxes.

Tests run again after the rebase: tests/test_readback_timeout_reuse.py fails at 9fc90117 (begin, later, lost) and passes at the tip, with g++ 13.3 and with clang 23.1.5. The full python3 -I -B tests/run_all.py on this branch: 530 passed, 16 failed, 48 skipped; 15 of them fail on main 53f96d78 in the same run (named in the checklist), and the 16th, test_pipeline_cache_save, a timing check, passed 2 of 2 run alone, as on main. gpu/vk/vk_texture.cpp checked whole for Linux and Windows: the same warnings as main's, none new.

Why the Deck result still holds: main changed nothing in the lines this PR changes. Since the tested base 7ff5256e, main changed gpu/vk/vk_texture.cpp in one commit, b2911d33 (weak Intel laptops): system_ram_bytes(), the low-VRAM mip rule (an integrated GPU with 16 GB of RAM or less now counts as small at Minimum's budgets, which includes the Deck at the preset the tester used), a lower squeeze line on integrated GPUs and a squeeze after the driver refuses pipelines for want of memory, in texture_frame, and the integrated GPU budget's log line. None of it is in readback_begin, readback_submit_wait, flush_textures or the eviction lines this PR keeps textures in. Those rules change how many textures the Deck keeps and evicts on main and on this PR alike, so the fps and eviction counts measured on 7ff5256e are 7ff5256e's; the result itself, a non-regression run in which no read-back timed out, does not depend on them, and the fix stays proven by the game-free test.

Thank you, dukenheimer, for the Steam Deck runs of T-4H95 (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/553#issuecomment-22328). Rebased onto main 53f96d78 with no conflict: the test and fix commits are the same as before (`git range-diff`: ecacc39c is now 9fc90117, 93c285b7 is now 515a3fdf), and the run is added as `docs/tested/2026-11-15-readback-timeout-dukenheimer-t-4h95.md` in a third commit, cbe9b9ce (the same file as in https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/554; the two merge into main together without a conflict). The description now ticks the rebase and `docs/tested/` boxes. Tests run again after the rebase: `tests/test_readback_timeout_reuse.py` fails at 9fc90117 (begin, later, lost) and passes at the tip, with g++ 13.3 and with clang 23.1.5. The full `python3 -I -B tests/run_all.py` on this branch: 530 passed, 16 failed, 48 skipped; 15 of them fail on main 53f96d78 in the same run (named in the checklist), and the 16th, `test_pipeline_cache_save`, a timing check, passed 2 of 2 run alone, as on main. `gpu/vk/vk_texture.cpp` checked whole for Linux and Windows: the same warnings as main's, none new. Why the Deck result still holds: main changed nothing in the lines this PR changes. Since the tested base 7ff5256e, main changed `gpu/vk/vk_texture.cpp` in one commit, b2911d33 (weak Intel laptops): `system_ram_bytes()`, the low-VRAM mip rule (an integrated GPU with 16 GB of RAM or less now counts as small at Minimum's budgets, which includes the Deck at the preset the tester used), a lower squeeze line on integrated GPUs and a squeeze after the driver refuses pipelines for want of memory, in `texture_frame`, and the integrated GPU budget's log line. None of it is in `readback_begin`, `readback_submit_wait`, `flush_textures` or the eviction lines this PR keeps textures in. Those rules change how many textures the Deck keeps and evicts on main and on this PR alike, so the fps and eviction counts measured on 7ff5256e are 7ff5256e's; the result itself, a non-regression run in which no read-back timed out, does not depend on them, and the fix stays proven by the game-free test.
zeldaootr force-pushed readback-timeout-reuse from 93c285b7c4
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
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
to cbe9b9ced6
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
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 05:35:20 +00:00
Compare
zeldaootr changed title from WIP: fix(vk): a read-back after a timed-out one waits for the GPU, and its textures are kept to fix(vk): a read-back after a timed-out one waits for the GPU, and its textures are kept 2026-11-15 05:35:20 +00:00
zeldaootr force-pushed readback-timeout-reuse from cbe9b9ced6
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
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 a6ad0e0e66
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 4s
2026-11-15 17:11:11 +00:00
Compare
Author
Contributor

Rebased onto main aa95d35f (head a6ad0e0e); the commits are re-authored with the forge's noreply address: no code change, the same patches by git patch-id.
Tests re-run on the new base, g++ 13.3 and clang 23.1.5: test_readback_timeout_reuse fails at the test-only commit 08514708 (3 violations; 10 calls and 2 waits after a device lost) and passes at 3b245fa7; of the 35 checks that are this PR's or read its files, 33 pass and the 2 that fail fail on main too. The Deck run (T-4H95) stands; the description names the new commits.

Rebased onto `main` aa95d35f (head a6ad0e0e); the commits are re-authored with the forge's noreply address: no code change, the same patches by `git patch-id`. Tests re-run on the new base, g++ 13.3 and clang 23.1.5: `test_readback_timeout_reuse` fails at the test-only commit 08514708 (3 violations; 10 calls and 2 waits after a device lost) and passes at 3b245fa7; of the 35 checks that are this PR's or read its files, 33 pass and the 2 that fail fail on main too. The Deck run (T-4H95) stands; the description names the new commits.
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 4s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
Some workflows are waiting to be reviewed.
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 'readback-timeout-reuse:zeldaootr-readback-timeout-reuse'
git switch 'zeldaootr-readback-timeout-reuse'
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!553
No description provided.