WIP: feat(vk): live switches and ab (in session, or across restarts) for A/B runs; RX_VK_OCC_QUERIES the first #36

Draft
dyst wants to merge 2 commits from dyst/Longshot:feat/live-switches into main
First-time contributor

Why

Comparing a switch's two values meant two launches, a minute apart, with another walk to the scene and other warm caches, so differences of a few percent were lost. This makes a switch changeable while the game runs, so both values are measured in one session, alternating, with the game's own numbers for each half.

What changed

The diff against main holds #23 (the occlusion queries) under this PR's own commits; #23's part is reviewed there.

From #23 (stacked under this PR):

  • replay: occ_queries_on, occ_room, occ_take, occ_collect and occ_reset in gpu/vk/vk_replay.cpp give the draws between a samples-passed reset and its report Vulkan occlusion queries from a 65,536-entry ring (kOccPool), numbered in NvnState (occ_next, occ_first, occ_limit, occ_on, occ_overflow in gpu/vk/vk_state.h); replay's NOP_RESET_COUNTER and NOP_REPORT_COUNTER open and close a run, and begin_recording writes the finished sums into the game's slots (occ_collect). RX_VK_OCC_LOG logs their counts.
  • draw: draw() in gpu/vk/vk_draw.cpp begins the query in its pass and ends it as it returns (OccGuard); DrawArgs carries occ_pool and occ_query (gpu/vk/vk_draw.h).
  • device: Device::occlusion_precise and host_query_reset (gpu/vk/vk_internal.h), set in create_device (gpu/vk/vk_main.cpp).
  • tests: tests/test_vk_occlusion_queries.py (new in #23); tests/fsr_uniform_ownership_test.cpp stubs occ_collect and occ_value.

This PR:

  • live switches: gpu/vk/vk_live.h (new): LiveSwitch, live_switches(), live_on, live_find and live_marks. Each switch starts as its environment variable says (longshot_env.h's rule). The first is RX_VK_OCC_QUERIES: occ_queries_on asks live_on(LIVE_OCC_QUERIES) once a command buffer, before its dry pass (replay_handle's occ_limit, through occ_room), so the dry pass and its workers agree. Turned off, the run already open goes past its limit and its report stays "visible"; turned on, the next samples-passed reset opens a run; its query pool is made the first time it is on.
  • present: live_tick in gpu/vk/vk_replay.cpp, called from status_tick, takes an ask at the next frame and first logs the time since the last ask, e.g. vk: live: 590 frames in 9.5 s with RX_VK_OCC_QUERIES=0: 62.1 fps (1% low 51.0), GPU 8.12 ms a frame, 1830 draws a frame. The first half second after a change is left out. An ask for the value a switch already has changes nothing and still ends the period.
  • bridge: menu_setting in gpu/vk/vk_menu.cpp ends its list with the live switches (tab live, key env.<NAME>, live_fill) and the mark (live.mark, live_mark_fill); menu_setting_set takes those keys without menu_mu. They are not in the menu and not saved to longshot_options.ini. No interface change: setting/setting_set carry them, as in longshot_ctl.py option set RX_VK_OCC_QUERIES 1.
  • scripts: scripts/longshot_ctl.py ab NAME [--every 10] [--rounds 4] (ab, live_halves, ab_summary, ab_text, scene_flag, ab_counted): starts from the switch's value with a mark, flips it every N seconds R times each way, ends on the start value, and prints each half, each value's medians (fps, 1% low, GPU ms, draws), the difference, and the fps spread between halves of the same value (saying when the difference is within it). A half in which the game left play, changed map, loaded or opened a menu is marked, listed and left out of the medians.
  • scripts: longshot_ctl.py ab NAME --restart GAME_DIR [--values A,B] [--teleport 3] [--settle 15] (ab_restart, to_play, teleport_to, start_game), for switches that need a restart: A B then B A, each launch taken into play and teleported (asked again where refused), left to settle, measured between two marks, then quit; the same table as live mode.
  • tests: tests/test_live_switches.py (new); tests/test_vk_occlusion_queries.py: the switch is in vk_live.h now, and occ_queries_on asks it before the pool is made.

Flags and settings

Flag New or changed What it does Default now Goal: on by default? Helps on Doesn't help on Untested on Untested, but should help on
RX_VK_OCC_QUERIES New in #23; changed here: a live switch, changeable through the bridge (env.RX_VK_OCC_QUERIES) The GPU's counts in the game's samples-passed reports, so the game leaves out what is hidden off (unset) after more testing (#23: AMD, the Steam Deck, integrated GPUs and Linux first) RTX 3080, Windows, standing in Castle Town Market: about 15% fewer draws RTX 3080, Windows, Castle Town Market with the camera turning: GPU time about 17% up AMD, the Steam Deck, integrated GPUs, Linux -
RX_VK_OCC_LOG New in #23 Logs, every ~5,000 reports, how many were written, how many read 0 and how late off (unset) no (a developer switch) - - - -
RX_VK_X, RX_VK_Y New, test only Test only: stand-in names in tests/test_live_switches.py's fake bridge and fake games; the game reads neither - - - - - -
longshot_ctl.py ab New command A switch's two values compared, live in one session or with a launch a value (--restart) - no (a developer tool) - - Linux -

Measurements

Measured on the GitHub version (NotRexed/Hookshot#2932), not yet on Longshot: this PR stays WIP until it is.

These runs show the tool at work: both columns are this PR's build, with RX_VK_OCC_QUERIES off and on. main was not the comparison, since the change is the A/B itself.

Baseline: this PR (GitHub), RX_VK_OCC_QUERIES=0
Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11
Scene: Castle Town Market from a save, standing, then again with the camera turning
Method: live: one headless launch, ab RX_VK_OCC_QUERIES --every 10 --rounds 4; the medians of each value's halves, from the game's vk: live: lines
How to reproduce: python -I scripts/longshot_ctl.py ab RX_VK_OCC_QUERIES --every 10 --rounds 4 against a running game in Castle Town Market

Live, one launch this PR (GitHub), RX_VK_OCC_QUERIES=0 this PR (GitHub), RX_VK_OCC_QUERIES=1
standing: fps (median) 18.6 20.0
standing: fps spread 3.9 3.5
standing: GPU ms 27.2 25.4
standing: draws 2750 2325
turning: fps (median) 16.3 14.2
turning: fps spread 7.1 6.0
turning: GPU ms 30.5 35.8
turning: draws 1482 1364
  • On, the game leaves out about 15% of its draws standing. Turning, the GPU time goes up about 17%.
  • This agrees in direction with the four separate launches on NotRexed/Hookshot#2871 (turning: GPU time +25%). It came from one launch, a few minutes after reaching the scene.
  • The fps differences are within the spread between halves of the same value, and ab says so.
  • In the log: 16 changes taken, the query pool made once (at the first "on"), and no errors.

Restart mode, ab RX_VK_OCC_QUERIES --restart GAME_DIR --rounds 2 --teleport 3 --every 20: four launches, unattended, about 10 minutes.

Restart, a launch a value launches fps spread GPU ms draws
off 2 26.9 2.1 10.98 2632
on 2 29.9 1.7 11.23 2286
  • Draws are -13% on, as in live mode standing.
  • The fps difference (+10.8%) is more than the spread between launches of the same value.
  • Two earlier runs found what the changes since then fix: a press the intro does not read, and a teleport refused while Continue's event plays.
  • A third run found a load inside a launch's measured 20 s. It was not flagged at first, because both ends read play in the Market, and it took that value's median to -49%. That run is why state events now flag a half.

Testing

Tested: the runs under Measurements (on the GitHub version) and the game-free checks this description names.

Not tested: Linux. The occlusion queries underneath (#23) have not run on AMD, the Steam Deck or integrated GPUs.

Needs other testers:

  • Linux: longshot_ctl.py ab RX_VK_OCC_QUERIES and ab ... --restart, expecting their tables, with no lost device (request to open)

Screenshots

Nothing on screen.

Risk and rollback

  • RX_VK_OCC_QUERIES is read once a command buffer, from an atomic only live_tick writes (live_on); a change while the game runs leaves the run already open "visible", which costs culling, never a missing object.
  • live_tick runs at every present; it reads the asks and logs only when there is one.
  • The bridge's env. and live.mark keys take no menu lock and are not saved, so the options file is untouched.
  • Nothing changes unless RX_VK_OCC_QUERIES is set or the bridge asks for it; reverting the commits after #23 is enough to take the live switches and ab out.

Reviewer notes

Carried over from NotRexed/Hookshot#2932 with the move to Longshot: the same change on Longshot's main, with its rename applied. Its review and discussion are there.

  • Stacked on #23 (carried from NotRexed/Hookshot#2871): review the commits after it; this rebases to them alone once #23 is in. The diff against main holds both, so the flags and files from #23 are listed above as #23's.
  • The checks and the game runs above were done on the GitHub version; this branch is the same change with the rename applied (the same files and line counts). Forgejo runs no checks, and they were not run again here.
  • Checks run (on GitHub, not the whole run_all.py): every check that reads the changed files (179), through run_all.py, Windows: 176 pass. test_linux_driver_fault_guard and test_readback_wait_bounded fail the same way on main there, and test_input_background fails the same way at NotRexed/Hookshot#2871's head (its older base; it passes on that day's main).
  • tests/test_live_switches.py: vk_live.h, live_tick, live_fill and menu_setting_set's env. branch are cut out and run against stub counters (the start value from the variable; an ask taken at the next frame, not before; the settle time left out; each half's frame counts, fps, 1% low, GPU ms and draws, read back with longshot_ctl's own pattern; a mark; a half too short). ab runs against a fake bridge (the order of the asks, the halves kept, the medians and spread, a half with a map change marked, a menu row refused) and ab --restart against fake games (A B then B A, each launch's value and environment, its second mark counted, each one quit and waited for; a refused press and a refused teleport asked again; a load inside a launch flagged and left out; every launch flagged is an error). In the source: the tick is in the present, the listing comes after the rows, the ask is not saved and takes no menu lock, and only live_tick writes what the readers take. Two deliberate bugs fail it: no settle time, and an ask applied before its half is logged.
  • A switch is listed in vk_live.h only once its reader takes it where a change is safe (where a piece of work begins, with nothing made or sized at the start depending on it); restart mode covers the rest (thread modes, cache keys, resources sized at the start).
  • See NotRexed/Hookshot#2636 (the dev-loop proposal's in-session A/B, D4/D5 review) and NotRexed/Hookshot#2871.

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 table filled in; README / docs/development.md switch reference updated.
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • The SPIR-V headers regenerated with glslang (a shader changed).
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.

🤖 Generated with Claude Code

## Why Comparing a switch's two values meant two launches, a minute apart, with another walk to the scene and other warm caches, so differences of a few percent were lost. This makes a switch changeable while the game runs, so both values are measured in one session, alternating, with the game's own numbers for each half. ## What changed The diff against `main` holds #23 (the occlusion queries) under this PR's own commits; #23's part is reviewed there. From #23 (stacked under this PR): - replay: `occ_queries_on`, `occ_room`, `occ_take`, `occ_collect` and `occ_reset` in `gpu/vk/vk_replay.cpp` give the draws between a samples-passed reset and its report Vulkan occlusion queries from a 65,536-entry ring (`kOccPool`), numbered in `NvnState` (`occ_next`, `occ_first`, `occ_limit`, `occ_on`, `occ_overflow` in `gpu/vk/vk_state.h`); `replay`'s `NOP_RESET_COUNTER` and `NOP_REPORT_COUNTER` open and close a run, and `begin_recording` writes the finished sums into the game's slots (`occ_collect`). `RX_VK_OCC_LOG` logs their counts. - draw: `draw()` in `gpu/vk/vk_draw.cpp` begins the query in its pass and ends it as it returns (`OccGuard`); `DrawArgs` carries `occ_pool` and `occ_query` (`gpu/vk/vk_draw.h`). - device: `Device::occlusion_precise` and `host_query_reset` (`gpu/vk/vk_internal.h`), set in `create_device` (`gpu/vk/vk_main.cpp`). - tests: `tests/test_vk_occlusion_queries.py` (new in #23); `tests/fsr_uniform_ownership_test.cpp` stubs `occ_collect` and `occ_value`. This PR: - live switches: `gpu/vk/vk_live.h` (new): `LiveSwitch`, `live_switches()`, `live_on`, `live_find` and `live_marks`. Each switch starts as its environment variable says (`longshot_env.h`'s rule). The first is `RX_VK_OCC_QUERIES`: `occ_queries_on` asks `live_on(LIVE_OCC_QUERIES)` once a command buffer, before its dry pass (`replay_handle`'s `occ_limit`, through `occ_room`), so the dry pass and its workers agree. Turned off, the run already open goes past its limit and its report stays "visible"; turned on, the next samples-passed reset opens a run; its query pool is made the first time it is on. - present: `live_tick` in `gpu/vk/vk_replay.cpp`, called from `status_tick`, takes an ask at the next frame and first logs the time since the last ask, e.g. `vk: live: 590 frames in 9.5 s with RX_VK_OCC_QUERIES=0: 62.1 fps (1% low 51.0), GPU 8.12 ms a frame, 1830 draws a frame`. The first half second after a change is left out. An ask for the value a switch already has changes nothing and still ends the period. - bridge: `menu_setting` in `gpu/vk/vk_menu.cpp` ends its list with the live switches (tab `live`, key `env.<NAME>`, `live_fill`) and the mark (`live.mark`, `live_mark_fill`); `menu_setting_set` takes those keys without `menu_mu`. They are not in the menu and not saved to `longshot_options.ini`. No interface change: `setting`/`setting_set` carry them, as in `longshot_ctl.py option set RX_VK_OCC_QUERIES 1`. - scripts: `scripts/longshot_ctl.py ab NAME [--every 10] [--rounds 4]` (`ab`, `live_halves`, `ab_summary`, `ab_text`, `scene_flag`, `ab_counted`): starts from the switch's value with a mark, flips it every N seconds R times each way, ends on the start value, and prints each half, each value's medians (fps, 1% low, GPU ms, draws), the difference, and the fps spread between halves of the same value (saying when the difference is within it). A half in which the game left play, changed map, loaded or opened a menu is marked, listed and left out of the medians. - scripts: `longshot_ctl.py ab NAME --restart GAME_DIR [--values A,B] [--teleport 3] [--settle 15]` (`ab_restart`, `to_play`, `teleport_to`, `start_game`), for switches that need a restart: A B then B A, each launch taken into play and teleported (asked again where refused), left to settle, measured between two marks, then quit; the same table as live mode. - tests: `tests/test_live_switches.py` (new); `tests/test_vk_occlusion_queries.py`: the switch is in `vk_live.h` now, and `occ_queries_on` asks it before the pool is made. ## Flags and settings | Flag | New or changed | What it does | Default now | Goal: on by default? | Helps on | Doesn't help on | Untested on | Untested, but should help on | |---|---|---|---|---|---|---|---|---| | `RX_VK_OCC_QUERIES` | New in #23; changed here: a live switch, changeable through the bridge (`env.RX_VK_OCC_QUERIES`) | The GPU's counts in the game's samples-passed reports, so the game leaves out what is hidden | off (unset) | after more testing (#23: AMD, the Steam Deck, integrated GPUs and Linux first) | RTX 3080, Windows, standing in Castle Town Market: about 15% fewer draws | RTX 3080, Windows, Castle Town Market with the camera turning: GPU time about 17% up | AMD, the Steam Deck, integrated GPUs, Linux | - | | `RX_VK_OCC_LOG` | New in #23 | Logs, every ~5,000 reports, how many were written, how many read 0 and how late | off (unset) | no (a developer switch) | - | - | - | - | | `RX_VK_X`, `RX_VK_Y` | New, test only | Test only: stand-in names in `tests/test_live_switches.py`'s fake bridge and fake games; the game reads neither | - | - | - | - | - | - | | `longshot_ctl.py ab` | New command | A switch's two values compared, live in one session or with a launch a value (`--restart`) | - | no (a developer tool) | - | - | Linux | - | ## Measurements Measured on the GitHub version (NotRexed/Hookshot#2932), not yet on Longshot: this PR stays WIP until it is. These runs show the tool at work: both columns are this PR's build, with `RX_VK_OCC_QUERIES` off and on. main was not the comparison, since the change is the A/B itself. Baseline: this PR (GitHub), RX_VK_OCC_QUERIES=0 Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11 Scene: Castle Town Market from a save, standing, then again with the camera turning Method: live: one headless launch, `ab RX_VK_OCC_QUERIES --every 10 --rounds 4`; the medians of each value's halves, from the game's `vk: live:` lines How to reproduce: `python -I scripts/longshot_ctl.py ab RX_VK_OCC_QUERIES --every 10 --rounds 4` against a running game in Castle Town Market | Live, one launch | this PR (GitHub), RX_VK_OCC_QUERIES=0 | this PR (GitHub), RX_VK_OCC_QUERIES=1 | |---|---|---| | standing: fps (median) | 18.6 | 20.0 | | standing: fps spread | 3.9 | 3.5 | | standing: GPU ms | 27.2 | 25.4 | | standing: draws | 2750 | 2325 | | turning: fps (median) | 16.3 | 14.2 | | turning: fps spread | 7.1 | 6.0 | | turning: GPU ms | 30.5 | 35.8 | | turning: draws | 1482 | 1364 | - On, the game leaves out about 15% of its draws standing. Turning, the GPU time goes up about 17%. - This agrees in direction with the four separate launches on [NotRexed/Hookshot#2871](https://github.com/NotRexed/Hookshot/pull/2871) (turning: GPU time +25%). It came from one launch, a few minutes after reaching the scene. - The fps differences are within the spread between halves of the same value, and `ab` says so. - In the log: 16 changes taken, the query pool made once (at the first "on"), and no errors. Restart mode, `ab RX_VK_OCC_QUERIES --restart GAME_DIR --rounds 2 --teleport 3 --every 20`: four launches, unattended, about 10 minutes. | Restart, a launch a value | launches | fps | spread | GPU ms | draws | |---|---|---|---|---|---| | off | 2 | 26.9 | 2.1 | 10.98 | 2632 | | on | 2 | 29.9 | 1.7 | 11.23 | 2286 | - Draws are -13% on, as in live mode standing. - The fps difference (+10.8%) is more than the spread between launches of the same value. - Two earlier runs found what the changes since then fix: a press the intro does not read, and a teleport refused while Continue's event plays. - A third run found a load inside a launch's measured 20 s. It was not flagged at first, because both ends read play in the Market, and it took that value's median to -49%. That run is why state events now flag a half. ## Testing **Tested:** the runs under Measurements (on the GitHub version) and the game-free checks this description names. **Not tested:** Linux. The occlusion queries underneath (#23) have not run on AMD, the Steam Deck or integrated GPUs. **Needs other testers:** - Linux: `longshot_ctl.py ab RX_VK_OCC_QUERIES` and `ab ... --restart`, expecting their tables, with no lost device (request to open) ## Screenshots Nothing on screen. ## Risk and rollback - `RX_VK_OCC_QUERIES` is read once a command buffer, from an atomic only `live_tick` writes (`live_on`); a change while the game runs leaves the run already open "visible", which costs culling, never a missing object. - `live_tick` runs at every present; it reads the asks and logs only when there is one. - The bridge's `env.` and `live.mark` keys take no menu lock and are not saved, so the options file is untouched. - Nothing changes unless `RX_VK_OCC_QUERIES` is set or the bridge asks for it; reverting the commits after #23 is enough to take the live switches and `ab` out. ## Reviewer notes Carried over from [NotRexed/Hookshot#2932](https://github.com/NotRexed/Hookshot/pull/2932) with the move to Longshot: the same change on Longshot's `main`, with its rename applied. Its review and discussion are there. - **Stacked on #23** (carried from NotRexed/Hookshot#2871): review the commits after it; this rebases to them alone once #23 is in. The diff against `main` holds both, so the flags and files from #23 are listed above as #23's. - The checks and the game runs above were done on the GitHub version; this branch is the same change with the rename applied (the same files and line counts). Forgejo runs no checks, and they were not run again here. - Checks run (on GitHub, not the whole `run_all.py`): every check that reads the changed files (179), through `run_all.py`, Windows: 176 pass. `test_linux_driver_fault_guard` and `test_readback_wait_bounded` fail the same way on `main` there, and `test_input_background` fails the same way at NotRexed/Hookshot#2871's head (its older base; it passes on that day's `main`). - `tests/test_live_switches.py`: `vk_live.h`, `live_tick`, `live_fill` and `menu_setting_set`'s `env.` branch are cut out and run against stub counters (the start value from the variable; an ask taken at the next frame, not before; the settle time left out; each half's frame counts, fps, 1% low, GPU ms and draws, read back with `longshot_ctl`'s own pattern; a mark; a half too short). `ab` runs against a fake bridge (the order of the asks, the halves kept, the medians and spread, a half with a map change marked, a menu row refused) and `ab --restart` against fake games (A B then B A, each launch's value and environment, its second mark counted, each one quit and waited for; a refused press and a refused teleport asked again; a load inside a launch flagged and left out; every launch flagged is an error). In the source: the tick is in the present, the listing comes after the rows, the ask is not saved and takes no menu lock, and only `live_tick` writes what the readers take. Two deliberate bugs fail it: no settle time, and an ask applied before its half is logged. - A switch is listed in `vk_live.h` only once its reader takes it where a change is safe (where a piece of work begins, with nothing made or sized at the start depending on it); restart mode covers the rest (thread modes, cache keys, resources sized at the start). - See [NotRexed/Hookshot#2636](https://github.com/NotRexed/Hookshot/pull/2636) (the dev-loop proposal's in-session A/B, D4/D5 review) and [NotRexed/Hookshot#2871](https://github.com/NotRexed/Hookshot/pull/2871). ## Issues None. ## Checklist ### For everyone - [x] One topic. - [x] 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). - [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). - [ ] 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. - [ ] Flags table filled in; README / docs/development.md switch reference updated. - [ ] Screenshots for a change that shows on screen. - [ ] A row in `docs/tested-configurations.md` for each measured run. - [ ] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [ ] The SPIR-V headers regenerated with glslang (a shader changed). - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [ ] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ocarina of Time reads its z-prepass occlusion reports back on the CPU a frame
or two later and leaves out of its draw lists the objects whose count was 0.
The Vulkan renderer writes every samples-passed report as 4096 (PR #487), so
the game never culled anything.

With RX_VK_OCC_QUERIES=1 each draw from a samples-passed reset to its report
gets a Vulkan occlusion query, numbered in the replay state so a parallel
replay's dry pass and its workers agree. The report is still written as 4096
at replay; once its queries are done the replay thread writes their sum over
it, unless the slot was reported into again since. A report whose draw did not
run, or that took more than 256 draws, stays 4096. Off by default.

Kokiri Forest, standing still, RTX 3080: ~1,570 -> ~1,330 draws a frame,
~11.5 -> ~11.0 ms of GPU time, 26-28 -> 27.5-29 fps, the same picture; ~32% of
reports read 0, each written about one recording after the report.

tests/test_vk_occlusion_queries.py runs the ring's code against a fake query
pool and checks the wiring in the source; tests/fsr_uniform_ownership_test.cpp
stubs the harvest begin_recording now calls.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Carried over from github.com/jordanella/Hookshot feat/live-switches, stacked on fix/vk-occlusion-queries (its commits:
feat(vk): live switches for an A/B in one session;  RX_VK_OCC_QUERIES the first; feat(control): ab --restart for switches that need a restart;  live.mark; fix(control): ab --restart presses A again when the intro does not take it; fix(control): ab leaves out of its medians a half the game loaded or opened a menu in; fix(control): ab --restart asks for the teleport again while an event plays). Merged with Longshot's longshot_ctl.py (its age command).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
dyst changed title from feat(vk): live switches and ab (in session, or across restarts) for A/B runs; RX_VK_OCC_QUERIES the first to WIP: feat(vk): live switches and ab (in session, or across restarts) for A/B runs; RX_VK_OCC_QUERIES the first 2026-11-13 01:13:59 +00:00
Author
First-time contributor

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.
Author
First-time contributor

Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready.

Description brought to Longshot's PR template (#48). Still WIP: its numbers are from the GitHub version, and it isn't rebased on current main; it gets measured on Longshot (main against this PR, at named commits) before it is marked ready.
Author
First-time contributor

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/live-switches:dyst-feat/live-switches'
git switch 'dyst-feat/live-switches'
Sign in to join this conversation.
No description provided.