WIP: perf(vk): read the GPU times of a run of finished recordings in one query call (RX_VK_TS_RUN=1, opt-in) #150

Closed
klimovich008 wants to merge 12 commits from klimovich008/Longshot:perf/gpu-time-run into main
Contributor

Why

The replay reads each recording's GPU timestamps with its own vkGetQueryPoolResults when the slot comes round: about 31
calls a frame (about 31.5 recordings a frame in a counter run of the Hyrule standard test). In sampled profiles of the same test,
the driver time under these calls was 0.16 ms of the presentation thread a frame (RX 9070 XT, AMD's Windows driver). This reads a
run of finished slots in one call instead. It is opt-in, RX_VK_TS_RUN=1, and the default is unchanged. A performance change,
not a bug fix.

Ported from NotRexed/Hookshot, where it was prepared and reviewed but not opened; the measurements below were made there.

What changed

  • replay: gpu_time_collect and gpu_time_read_run in gpu/vk/vk_replay.cpp. With RX_VK_TS_RUN=1 the slot's own times and
    those of up to 15 unread slots after it are read in one call with VK_QUERY_RESULT_WITH_AVAILABILITY_BIT. The times read ahead
    are kept (ts_read_ahead, ts_ahead) and counted when their own slot comes round, as the default counts them, so each
    120-present window the consumers read sees the same GPU time. A slot the GPU has not finished stays pending for its own turn.
    Each recording is counted once.
  • replay: a query's availability can still show a slot's previous use until the reset recorded in its next command buffer runs,
    so each slot's two queries are also reset on the host (vkResetQueryPool) when the slot comes round, and the pool once when
    it is made (gpu_time_begin). An available result is then always the current use's.
  • replay: host resets need hostQueryReset. The device enables every Vulkan 1.2 feature it reports (vk_main.cpp); without
    it, RX_VK_TS_RUN=1 falls back to one slot a call and logs that once (ts_run).
  • These times feed the FPS counter's Breakdown, the vk: GPU time log line, the movie network check and the Auto (this PC)
    preset's tuning in play (Hookshot #2850). With the switch unset none of
    them change.
  • tests: tests/test_gpu_time_run.py and tests/gpu_time_run_test.cpp (game-free, in run_all).
  • docs: the switch in docs/development.md (Time and profiles); two rows in docs/tested-configurations.md.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
RX_VK_TS_RUN (set) GPU times read for a run of finished recordings in one query call, not one a recording off off: no gain measured on this head - RX 9070 XT, Windows: 65.59 against 66.14 fps, CPU 63.13 against 60.87 ms a frame (3 pairs, diagnostic) NVIDIA, Intel, Linux (RADV), Steam Deck

Measurements

main: 839e85ff (2026-11-13)
this PR: perf/gpu-time-run, its commits on 839e85ff @ 05e79bfa (the same diff as this branch's)
Hardware: RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB, Windows 11
Scene: Hyrule Field, the standard test: the save loaded, the camera turning at full speed, frame rate unlocked
(HOS_FPS_MAX=240), 1280x720 window, 2048x1152 output (16:9), no frame generation
Method: a 20 s window after the load, warm caches, 3 alternating pairs, medians; fps and CPU ms a frame from the present log and
the process times.
How to reproduce: the build above, the switch unset against RX_VK_TS_RUN=1, alternated, with our standard-test script.

Baseline: Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN unset

Main was not the comparison: the switch unset is main's collector, on the same build.

Measured on main 839e85ff; main is at 514591a3 since. Its 36 commits in between add the occlusion query reports (#197, #260) and the Damage taken row, among others, and change none of the code this PR changes (gpu_time_collect, gpu_time_begin and the timestamp pool in vk_replay.cpp): the fps on today's main will differ, the difference between the arms is this PR's own. A measurement on the next base will replace these.

Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN unset Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN=1
fps 66.14 65.59
CPU ms a frame 60.87 63.13
p99 frame ms 20.36 23.08
worst frame ms 72.7 543.1
frames over 100 / 250 ms 0 / 0 4 / 1

No gain: the difference is under 5 % with 3 pairs, diagnostic, and goes the other way. The 543 ms frame and three more over
100 ms were in one run with the switch on (61.79 fps); without that run the two arms are level (66.79 and 65.59 against 66.29
and 65.59 fps).

std_test, per run (fps, CPU ms a frame, worst ms)
unset           66.14 60.10 72.7 | 66.29 60.87 48.2 | 65.59 61.24 49.2
RX_VK_TS_RUN=1  61.79 64.81 543.1 | 66.79 59.32 46.1 | 65.59 63.13 54.3

Earlier, on Hookshot and with the earlier collector (same query calls, accounting changed since; not this head):

Hardware: RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB, Windows 11
Scene: Hyrule Field, the standard test as it was then, at 32:9 (5120x1440; it is 16:9 since): the camera turning at full speed,
frame rate unlocked (HOS_FPS_MAX=240), no upscaler change, no frame generation
Method: 20 s window after the load, warm caches, 2 warm-ups, then alternating pairs. A run counts only if the game clock reads
1.000 ± 0.01 in the window and no frame there is over 250 ms. fps and CPU ms a frame from the present log and the process times.
How to reproduce: our std_test.py (claude-perf) with --arm each=RX_VK_TS_EACH=1 on a build of the earlier collector.

Then the run read was the branch's default and RX_VK_TS_EACH=1 gave the one-slot read. Since then the switch was flipped (which
path is the default), the fallback above was added, and the times read ahead are counted at their own slot's turn. An
independent probe through the real collector (360 frames, 312 reuses) shows the query calls unchanged: 20 with the switch vs 312
without, with the same sum and maximum as the one-slot path after every reuse.

Baseline: Hookshot 46780503 + earlier collector @ b4e4b082, RX_VK_TS_EACH=1

Hookshot 46780503 + earlier collector @ b4e4b082, RX_VK_TS_EACH=1 Hookshot 46780503 + earlier collector @ b4e4b082, run read
fps, re-run (5 of 6 pairs valid) 52.78 53.42 (+1.2 %, sd of the pair differences 0.53 fps; ahead 4, one tie)
fps, both A/Bs (8 of 12 pairs valid) +0.9 % (sd 0.66 fps; ahead 6 of 8)
CPU ms a frame, re-run 76.5 76.4

The fps difference is small; one more A/B could move it. Not measured on other GPUs, drivers, Linux or the Steam Deck.

Observation, not a claim: runs with a frame over 250 ms inside the window were 4 of 12 with the one-slot read and 1 of 12 with
the run read. In each, the window's clock reading over that stall dipped to 0.88-0.96. Too few to say the run read causes fewer.

ptsub4 (5 valid pairs, run read - one slot): paired d -0.64 fps sd 0.53 for one slot; CPU ms 76.39 / 76.53
ptsub2 + ptsub4 (8 valid pairs): paired d -0.47 fps sd 0.66; run read ahead 6/8

Testing

Tested: game-free: tests/test_gpu_time_run.py compiles the real gpu_time_collect block from vk_replay.cpp against a
fake query pool. Cases: a run of 20 finished slots counted once in at most 2 calls, slots the GPU is behind on, a slot whose own
queries were never written, a half-written slot, the ring's end, a slot never timed, stale availability after reuse, the sum and
the longest time after every reuse (cleared every 5 reuses, as a consumer's window) matching the default's in one call, the
default's one call a slot, and the switch on a device without hostQueryReset (one slot a call, no host reset, said once).
Controls: --broken drops the host reset of a slot that comes round and fails the stale case alone; main's vk_replay.cpp
fails run, ring_end and cadence (one call a slot, not one for the run) and no_host_reset (main has no switch to log about).
On this branch (3e653ca8, on main 39647e1a; Windows, llvm-mingw clang): the 10 cases pass, the renderer DLL compiles and
links, and tests/run_all.py gives 499 passed, 4 failed, 36 skipped. The 4 fail on main too: nx2/test_nup_paths.py,
tests/test_check_third_party.py, tests/test_setup_runtime.py, tests/test_release.py. The last commit adds only the
switch's line in docs/development.md.
Content (a second agent, on the same collector built on Hookshot 46780503, its collector and gpu_time_begin byte-identical to
this branch's):

  • Source: the first version counted the times it read ahead at once. A stub driving the real collector and the real movie
    network check showed that moving GPU time between 120-present windows: the movie decision came at 240 presents without the
    switch and at 360 with it, though the totals matched. With the times counted at their own slot's turn, the same probe gives
    the one-slot path's sum and maximum after every reuse and in all three windows.
  • On the GPU, outside the game: a small Vulkan program ran this collector and its timestamp writes, unchanged from the source,
    on the RX 9070 XT: 360 fenced submissions over the 48 slots (96 queries), with each slot's own queries read before every reuse
    as a reference. The one-slot path matched it at all 312 reuses; the first batched version read a wrong time at the first
    reuse (frame 49: 26,000 ns where the reference had 1,680 ns). This version, switch off and on, matches the reference at every
    reuse and the 120-present windows' totals and maxima, with 312 query calls off and 20 on. The Khronos validation layer was on
    and reported no errors in the four cases; that synchronization validation was active was not confirmed. Evidence for the
    component on real hardware, not a validation pass of the whole game.
  • In the game: Hyrule Field, Kokiri Forest and a temple, switch off and on, a 95 s run each. The captures show the geometry,
    foliage, Link, the HUD and the camera moving as expected, with nothing blank. No bit-exact frame comparison, and no speed
    claim from these runs.

Tested in the game: the speed above (RX 9070 XT, Windows 11, Hyrule Field).

Not tested: a game run with Vulkan validation (Hyrule, switch on) was stopped by our test
machine's memory guard before the first present: the layer loaded and reported no VUID before the stop, but that is not a
validation or synchronization-validation pass. A movie (the movie network check) and the Auto (this PC) preset's tuning in play
were not run in the game. The Longshot rename on this branch was not run in the game (the collector is unchanged by it). Not built on Linux (the change is portable Vulkan code, but no Linux build was made).

Needs other testers:

  • NVIDIA on Windows: the vk: GPU time line and the Breakdown's GPU time the same with RX_VK_TS_RUN=1 as without, in
    the same scene (request to open)
  • Linux, RADV: as above, and no vk: RX_VK_TS_RUN=1 needs hostQueryReset line (request to open)

Screenshots

Nothing on screen.

Risk and rollback

Off unless RX_VK_TS_RUN=1 is set; unset, the collector is main's. With it set, a wrong or late GPU time would show in the FPS
counter's Breakdown and the vk: GPU time line, and could move the movie network check or Auto (this PC)'s tuning; unsetting
the switch turns it off, and reverting the commits is enough.

Reviewer notes

Duplicate check (2026-11-13): GitHub NotRexed/Hookshot open and merged PRs and issues for vkGetQueryPoolResults, timestamp
query, query pool, GPU time and gpu_time_collect, and Longshot's open and closed PRs and issues. Nothing batches the timestamp
reads. Hookshot #2897 (#60 here) wraps vkGetQueryPoolResults to time calls
that wait (VK_QUERY_RESULT_WAIT_BIT); this change's call does not wait, and the two do not touch the same code.

Not a shader-cache change: it touches none of the files nx2/build.py hashes into the stamp.

Issues

None.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • 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.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • A row in docs/tested-configurations.md for each measured run.
  • A game-free test added for the fix, failing on main and passing here.
  • Docs updated.

🤖 Generated with Claude Code

## Why The replay reads each recording's GPU timestamps with its own `vkGetQueryPoolResults` when the slot comes round: about 31 calls a frame (about 31.5 recordings a frame in a counter run of the Hyrule standard test). In sampled profiles of the same test, the driver time under these calls was 0.16 ms of the presentation thread a frame (RX 9070 XT, AMD's Windows driver). This reads a run of finished slots in one call instead. It is opt-in, `RX_VK_TS_RUN=1`, and the default is unchanged. A performance change, not a bug fix. Ported from NotRexed/Hookshot, where it was prepared and reviewed but not opened; the measurements below were made there. ## What changed - replay: `gpu_time_collect` and `gpu_time_read_run` in `gpu/vk/vk_replay.cpp`. With `RX_VK_TS_RUN=1` the slot's own times and those of up to 15 unread slots after it are read in one call with `VK_QUERY_RESULT_WITH_AVAILABILITY_BIT`. The times read ahead are kept (`ts_read_ahead`, `ts_ahead`) and counted when their own slot comes round, as the default counts them, so each 120-present window the consumers read sees the same GPU time. A slot the GPU has not finished stays pending for its own turn. Each recording is counted once. - replay: a query's availability can still show a slot's previous use until the reset recorded in its next command buffer runs, so each slot's two queries are also reset on the host (`vkResetQueryPool`) when the slot comes round, and the pool once when it is made (`gpu_time_begin`). An available result is then always the current use's. - replay: host resets need `hostQueryReset`. The device enables every Vulkan 1.2 feature it reports (`vk_main.cpp`); without it, `RX_VK_TS_RUN=1` falls back to one slot a call and logs that once (`ts_run`). - These times feed the FPS counter's Breakdown, the `vk: GPU time` log line, the movie network check and the Auto (this PC) preset's tuning in play ([Hookshot #2850](https://github.com/NotRexed/Hookshot/pull/2850)). With the switch unset none of them change. - tests: `tests/test_gpu_time_run.py` and `tests/gpu_time_run_test.cpp` (game-free, in `run_all`). - docs: the switch in `docs/development.md` (Time and profiles); two rows in `docs/tested-configurations.md`. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `RX_VK_TS_RUN` (set) | GPU times read for a run of finished recordings in one query call, not one a recording | off | off: no gain measured on this head | - | RX 9070 XT, Windows: 65.59 against 66.14 fps, CPU 63.13 against 60.87 ms a frame (3 pairs, diagnostic) | NVIDIA, Intel, Linux (RADV), Steam Deck | ## Measurements main: 839e85ff (2026-11-13) this PR: perf/gpu-time-run, its commits on 839e85ff @ 05e79bfa (the same diff as this branch's) Hardware: RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB, Windows 11 Scene: Hyrule Field, the standard test: the save loaded, the camera turning at full speed, frame rate unlocked (`HOS_FPS_MAX=240`), 1280x720 window, 2048x1152 output (16:9), no frame generation Method: a 20 s window after the load, warm caches, 3 alternating pairs, medians; fps and CPU ms a frame from the present log and the process times. How to reproduce: the build above, the switch unset against `RX_VK_TS_RUN=1`, alternated, with our standard-test script. Baseline: Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN unset Main was not the comparison: the switch unset is main's collector, on the same build. Measured on main 839e85ff; main is at 514591a3 since. Its 36 commits in between add the occlusion query reports (#197, #260) and the Damage taken row, among others, and change none of the code this PR changes (`gpu_time_collect`, `gpu_time_begin` and the timestamp pool in `vk_replay.cpp`): the fps on today's main will differ, the difference between the arms is this PR's own. A measurement on the next base will replace these. | | Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN unset | Longshot 839e85ff + this PR @ 05e79bfa, RX_VK_TS_RUN=1 | |---|---|---| | fps | 66.14 | 65.59 | | CPU ms a frame | 60.87 | 63.13 | | p99 frame ms | 20.36 | 23.08 | | worst frame ms | 72.7 | 543.1 | | frames over 100 / 250 ms | 0 / 0 | 4 / 1 | No gain: the difference is under 5 % with 3 pairs, diagnostic, and goes the other way. The 543 ms frame and three more over 100 ms were in one run with the switch on (61.79 fps); without that run the two arms are level (66.79 and 65.59 against 66.29 and 65.59 fps). ``` std_test, per run (fps, CPU ms a frame, worst ms) unset 66.14 60.10 72.7 | 66.29 60.87 48.2 | 65.59 61.24 49.2 RX_VK_TS_RUN=1 61.79 64.81 543.1 | 66.79 59.32 46.1 | 65.59 63.13 54.3 ``` Earlier, on Hookshot and with the earlier collector (same query calls, accounting changed since; not this head): Hardware: RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB, Windows 11 Scene: Hyrule Field, the standard test as it was then, at 32:9 (5120x1440; it is 16:9 since): the camera turning at full speed, frame rate unlocked (`HOS_FPS_MAX=240`), no upscaler change, no frame generation Method: 20 s window after the load, warm caches, 2 warm-ups, then alternating pairs. A run counts only if the game clock reads 1.000 ± 0.01 in the window and no frame there is over 250 ms. fps and CPU ms a frame from the present log and the process times. How to reproduce: our `std_test.py` (claude-perf) with `--arm each=RX_VK_TS_EACH=1` on a build of the earlier collector. Then the run read was the branch's default and `RX_VK_TS_EACH=1` gave the one-slot read. Since then the switch was flipped (which path is the default), the fallback above was added, and the times read ahead are counted at their own slot's turn. An independent probe through the real collector (360 frames, 312 reuses) shows the query calls unchanged: 20 with the switch vs 312 without, with the same sum and maximum as the one-slot path after every reuse. Baseline: Hookshot 46780503 + earlier collector @ b4e4b082, RX_VK_TS_EACH=1 | | Hookshot 46780503 + earlier collector @ b4e4b082, RX_VK_TS_EACH=1 | Hookshot 46780503 + earlier collector @ b4e4b082, run read | |---|---|---| | fps, re-run (5 of 6 pairs valid) | 52.78 | 53.42 (+1.2 %, sd of the pair differences 0.53 fps; ahead 4, one tie) | | fps, both A/Bs (8 of 12 pairs valid) | | +0.9 % (sd 0.66 fps; ahead 6 of 8) | | CPU ms a frame, re-run | 76.5 | 76.4 | The fps difference is small; one more A/B could move it. Not measured on other GPUs, drivers, Linux or the Steam Deck. Observation, not a claim: runs with a frame over 250 ms inside the window were 4 of 12 with the one-slot read and 1 of 12 with the run read. In each, the window's clock reading over that stall dipped to 0.88-0.96. Too few to say the run read causes fewer. ``` ptsub4 (5 valid pairs, run read - one slot): paired d -0.64 fps sd 0.53 for one slot; CPU ms 76.39 / 76.53 ptsub2 + ptsub4 (8 valid pairs): paired d -0.47 fps sd 0.66; run read ahead 6/8 ``` ## Testing **Tested:** game-free: `tests/test_gpu_time_run.py` compiles the real `gpu_time_collect` block from `vk_replay.cpp` against a fake query pool. Cases: a run of 20 finished slots counted once in at most 2 calls, slots the GPU is behind on, a slot whose own queries were never written, a half-written slot, the ring's end, a slot never timed, stale availability after reuse, the sum and the longest time after every reuse (cleared every 5 reuses, as a consumer's window) matching the default's in one call, the default's one call a slot, and the switch on a device without `hostQueryReset` (one slot a call, no host reset, said once). Controls: `--broken` drops the host reset of a slot that comes round and fails the stale case alone; main's `vk_replay.cpp` fails run, ring_end and cadence (one call a slot, not one for the run) and no_host_reset (main has no switch to log about). On this branch (3e653ca8, on main 39647e1a; Windows, llvm-mingw clang): the 10 cases pass, the renderer DLL compiles and links, and `tests/run_all.py` gives 499 passed, 4 failed, 36 skipped. The 4 fail on main too: `nx2/test_nup_paths.py`, `tests/test_check_third_party.py`, `tests/test_setup_runtime.py`, `tests/test_release.py`. The last commit adds only the switch's line in `docs/development.md`. Content (a second agent, on the same collector built on Hookshot 46780503, its collector and `gpu_time_begin` byte-identical to this branch's): - Source: the first version counted the times it read ahead at once. A stub driving the real collector and the real movie network check showed that moving GPU time between 120-present windows: the movie decision came at 240 presents without the switch and at 360 with it, though the totals matched. With the times counted at their own slot's turn, the same probe gives the one-slot path's sum and maximum after every reuse and in all three windows. - On the GPU, outside the game: a small Vulkan program ran this collector and its timestamp writes, unchanged from the source, on the RX 9070 XT: 360 fenced submissions over the 48 slots (96 queries), with each slot's own queries read before every reuse as a reference. The one-slot path matched it at all 312 reuses; the first batched version read a wrong time at the first reuse (frame 49: 26,000 ns where the reference had 1,680 ns). This version, switch off and on, matches the reference at every reuse and the 120-present windows' totals and maxima, with 312 query calls off and 20 on. The Khronos validation layer was on and reported no errors in the four cases; that synchronization validation was active was not confirmed. Evidence for the component on real hardware, not a validation pass of the whole game. - In the game: Hyrule Field, Kokiri Forest and a temple, switch off and on, a 95 s run each. The captures show the geometry, foliage, Link, the HUD and the camera moving as expected, with nothing blank. No bit-exact frame comparison, and no speed claim from these runs. **Tested in the game:** the speed above (RX 9070 XT, Windows 11, Hyrule Field). **Not tested:** a game run with Vulkan validation (Hyrule, switch on) was stopped by our test machine's memory guard before the first present: the layer loaded and reported no VUID before the stop, but that is not a validation or synchronization-validation pass. A movie (the movie network check) and the Auto (this PC) preset's tuning in play were not run in the game. The Longshot rename on this branch was not run in the game (the collector is unchanged by it). Not built on Linux (the change is portable Vulkan code, but no Linux build was made). **Needs other testers:** - NVIDIA on Windows: the `vk: GPU time` line and the Breakdown's GPU time the same with `RX_VK_TS_RUN=1` as without, in the same scene (request to open) - Linux, RADV: as above, and no `vk: RX_VK_TS_RUN=1 needs hostQueryReset` line (request to open) ## Screenshots Nothing on screen. ## Risk and rollback Off unless `RX_VK_TS_RUN=1` is set; unset, the collector is main's. With it set, a wrong or late GPU time would show in the FPS counter's Breakdown and the `vk: GPU time` line, and could move the movie network check or Auto (this PC)'s tuning; unsetting the switch turns it off, and reverting the commits is enough. ## Reviewer notes Duplicate check (2026-11-13): GitHub NotRexed/Hookshot open and merged PRs and issues for `vkGetQueryPoolResults`, timestamp query, query pool, GPU time and `gpu_time_collect`, and Longshot's open and closed PRs and issues. Nothing batches the timestamp reads. [Hookshot #2897](https://github.com/NotRexed/Hookshot/pull/2897) (#60 here) wraps `vkGetQueryPoolResults` to time calls that wait (`VK_QUERY_RESULT_WAIT_BIT`); this change's call does not wait, and the two do not touch the same code. Not a shader-cache change: it touches none of the files `nx2/build.py` hashes into the stamp. ## Issues None. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [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. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
With a recording slot's own timestamps, gpu_time_collect now reads those of the
slots after it that are still unread and whose queries the GPU has written
(VK_QUERY_RESULT_WITH_AVAILABILITY_BIT), up to 16 slots, in the same
vkGetQueryPoolResults. A slot not finished yet stays pending for its own turn.
RX_VK_TS_EACH=1 reads one slot a call, as before.

Test: tests/test_gpu_time_run.py compiles the real function against a fake
query pool; main's version fails 3 of its 7 cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run read could count a slot's previous times again: availability can show the
previous use until the reset in the slot's next command buffer runs (sol review r1).
Each finished slot's queries are now reset on the host before it is recorded again,
and the pool once when made. Test: new case 'stale', with --broken dropping the reset.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The default reads one slot a call, as before; RX_VK_TS_RUN=1 turns on the
run read and the host resets of the timestamp queries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Times read with an earlier slot's are kept until their slot comes round, so each
120-present window sees the same GPU time as with one slot a call (Codex's
counterexample: the movie network check moved from 240 to 360 presents).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs(development): RX_VK_TS_RUN in Time and profiles
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
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
a969197f60
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Measured on Longshot (base 839e85ff + this PR as a pack of the same patch, Hyrule standard test at 16:9, 3 alternating pairs on an RX 9070 XT): RX_VK_TS_RUN=1 65.59 fps against 66.14 off (-0.8 %, diagnostic), CPU 63.13 against 60.87 ms a frame, and 4 frames over 100 ms (one 543 ms) against none. No gain on this PC; the switch stays off. Description updated; merging current main next.

Measured on Longshot (base 839e85ff + this PR as a pack of the same patch, Hyrule standard test at 16:9, 3 alternating pairs on an RX 9070 XT): RX_VK_TS_RUN=1 65.59 fps against 66.14 off (-0.8 %, diagnostic), CPU 63.13 against 60.87 ms a frame, and 4 frames over 100 ms (one 543 ms) against none. No gain on this PC; the switch stays off. Description updated; merging current main next.
Author
Contributor

Measured this head on Longshot 839e85ff (+ this PR's commits, 05e79bfa, same diff), Hyrule standard test at 16:9, 3 pairs: no gain. RX_VK_TS_RUN=1 65.59 fps / 63.13 ms CPU against 66.14 / 60.87 unset (diagnostic); one run with the switch had a 543 ms frame and three more over 100 ms, without it the arms are level. The earlier 32:9 numbers stay below, labelled as the earlier collector's. Rows added to docs/tested-configurations.md.

Measured this head on Longshot 839e85ff (+ this PR's commits, 05e79bfa, same diff), Hyrule standard test at 16:9, 3 pairs: no gain. RX_VK_TS_RUN=1 65.59 fps / 63.13 ms CPU against 66.14 / 60.87 unset (diagnostic); one run with the switch had a 543 ms frame and three more over 100 ms, without it the arms are level. The earlier 32:9 numbers stay below, labelled as the earlier collector's. Rows added to docs/tested-configurations.md.
docs/tested-configurations.md: main's new rows and this branch's kept; a row for the Longshot A/B of 2026-11-13.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs(tested): RX 9070 XT, the standard test at 16:9 with RX_VK_TS_RUN unset and set
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 5s
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
a0de0bebad
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Says why the 839e85ff numbers hold on today's main: main's later commits do not touch the timestamp collector.

Says why the 839e85ff numbers hold on today's main: main's later commits do not touch the timestamp collector.
docs(tested): one set of rows for the 839e85ff A/B (the per-arm ones)
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 5s
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
089fa4b241
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Closing: on Longshot this switch gives no gain. The standard test at 16:9 on an RX 9070 XT / Ryzen 7 5700X3D (base 839e85ff with this PR as a pack of the same patch, 3 alternating pairs): RX_VK_TS_RUN=1 65.59 fps against 66.14 off (-0.8 %, diagnostic), CPU 63.13 against 60.87 ms a frame, and 4 frames over 100 ms (one 543 ms) against none. The earlier Hookshot difference (+0.9 to +1.2 % at 32:9) does not hold here. The branch stays on the fork with its game-free test and the GPU probe results above, should a driver where per-slot query reads cost more ever make it worth another look.

Closing: on Longshot this switch gives no gain. The standard test at 16:9 on an RX 9070 XT / Ryzen 7 5700X3D (base 839e85ff with this PR as a pack of the same patch, 3 alternating pairs): RX_VK_TS_RUN=1 65.59 fps against 66.14 off (-0.8 %, diagnostic), CPU 63.13 against 60.87 ms a frame, and 4 frames over 100 ms (one 543 ms) against none. The earlier Hookshot difference (+0.9 to +1.2 % at 32:9) does not hold here. The branch stays on the fork with its game-free test and the GPU probe results above, should a driver where per-slot query reads cost more ever make it worth another look.
klimovich008 closed this pull request 2026-11-13 20:03:38 +00:00
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 5s
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

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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!150
No description provided.