WIP: feat(vk): a Recorder thread row on the Debug tab turns the recorder thread on (off by default) #564

Closed
klimovich008 wants to merge 5 commits from klimovich008/Longshot:feat/recorder-thread-setting into main
Contributor

Type: feat · Risk: low · Tested on: Windows 11, RX 9070 XT · Needs: Windows with an NVIDIA or Intel GPU; Linux, any GPU

Why

The recorder thread is the largest speed-up we have measured in Hyrule Field on a CPU-limited PC, but a player can only
turn it on with environment variables (RX_VK_RECORDER=thread with RX_VK_RECORDER_OWN=1, #162). This adds a menu row
for it, off by default. The tracker has no issue asking for it (searched "recorder", "RX_VK_RECORDER", "replay thread").

What changed

A "Recorder thread" row on the Insert menu's Debug tab, under Developer: On makes the renderer hand its Vulkan commands
to a second thread, after a restart. The environment variables keep working and go over the row for a run. Nothing changes
while the row is Off.

  • recorder: mode_from_env and mode_init in gpu/vk/vk_recorder.h read the row when RX_VK_RECORDER is unset. The row
    means thread, with the slot's own resets (RX_VK_RECORDER_OWN) on unless that variable is set; RX_VK_PARALLEL 2 or
    more refuses it, as it refuses the variable. RX_VK_RECORDER set to any value decides alone, exactly as before. The
    start line and the refusal line say which of the two chose (vk: the Recorder thread row (recorder_thread=1 ...) or
    vk: RX_VK_RECORDER=thread:).
  • how the recorder reads the row: g_row_on in vk_recorder.h, set by gpu/vk/vk_draw.cpp to its menu_env_on (the
    reader the Descriptor reuse and Sampler cache rows already use: the row's line in longshot_options.ini in the game
    folder), so there is no second parser. The mode is still read once, at its first use.
  • menu: the row in gpu/vk/vk_menu.cpp (Debug tab, Developer; Off/On; a restart row; preset-exempt) and
    opts.recorder_thread in vk_menu.h. hn_recorder_thread shows a line under the row when it was On at the start and
    the recorder is not running this time (the variable said otherwise, RX_VK_PARALLEL refused it, or its thread or ring
    could not be made).
  • translations: the row's name, help and hint in the 13 tables of gpu/vk/menu_i18n.h (machine-made, as the file's others).
  • docs: the README's option table and Developer paragraph; docs/development.md gets a row for RX_VK_RECORDER itself (it
    had none) and recorder_thread in the list of rows read at the start.
  • tests: tests/test_recorder_thread_row.py (new); tests/test_damage_debug_menu.py and tests/test_recorder_own_idle.py
    follow the new row and the new g_own line.

Not changed: gpu/vk/vk_api.h and every file the shader-cache stamp hashes.

Measurements

Verdict: it helps on this PC: 61.5 -> 71.4 fps (+16.1 %) in Hyrule Field with the row On, 3 alternating pairs, all
admitted; CPU per frame rises 5.6 ms (the work moves to the recorder thread).

Hardware: Windows 11, RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB
Scene: Hyrule Field, the standard test: the save loaded, the camera turning at full speed; 1280x720 window, 2048x1152 output
(16:9), no upscaler, no frame generation, frame rate unlocked (HOS_FPS_MAX=240)
Method: fps and CPU ms a frame from the present log and the process times over a 20 s window after the load; 3 alternating
pairs, no warm-up runs, the median; game clock 0.999-1.000 in every run
How to reproduce: two game folders from one base, differing only in longshot_vk.dll; in the PR's folder
recorder_thread=1 in longshot_options.ini (or the row On and a restart), no RX_VK_RECORDER* variable in either; our
standard-test script, the two alternated

Baseline: Longshot 839e85ff, the base our A/Bs share on this PC, without the row. Main was not the comparison: the PR's
column is its renderer commits on that base (test/recrow-839 @ 91bf45f3: the same gpu/ lines as this branch's), with
#162's commits under them, as the row needs them (RX_VK_RECORDER_OWN); #162 is not in 839e85ff.

Measured on main 839e85ff; main is at 546dc212 since. Of its commits in between, #162 is in the PR's column, and #60 and
#250 touch vk_recorder.h or vk_work.h without changing how the recorder runs its packets; the fps on today's main will
differ (#569 alone is 3-5 % less frame time), the difference between the columns is what the row turns on. A measurement
on the next base will replace these.

Before: Longshot 839e85ff, no recorder row After: this PR's renderer on 839e85ff + #162 @ 91bf45f3, Recorder thread On Δ
fps (median of 3) 61.54 71.44 +16.1 %
CPU ms a frame 63.82 69.45 +5.6 ms
p99 frame ms 22.33 20.61
worst frame ms, per run 518, 418, 50 45, 137, 55
frames over 250 ms in the window, per run 1, 1, 0 0, 0, 0
game clock 1.000 1.000

Two of the three control runs had a stall of about half a second in the window, which lowers their fps: against the control
run without one (64.04 fps) the row's median is +11.6 %. Those stalls come from this PC's drive, not from either build:
small file writes to it block for hundreds of ms for some minutes after a multi-gigabyte write (a runtime folder had just
been copied), and the test's own present trace is flushed on the presenting thread. An earlier A/B of the same switches by
the variables (#162's description) gave 66.52 -> 76.57 fps on this PC.

std_test recrow2, per run (fps, CPU ms a frame, p99 ms, worst ms)
no row   61.04 67.48 27.96 518.2 | 61.54 63.20 22.33 417.6 | 64.04 63.82 21.11 50.4
row On   71.24 72.47 20.61  44.9 | 71.44 69.45 21.06 136.5 | 71.64 69.17 19.22 55.3
row On:  vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini; RX_VK_RECORDER unset): vkCmd* packed on the replay thread and run in order by a recorder thread; ...
row On:  vk: recorder: 825770 packets run in 10.0 s (busy 0.32 s, ...), 0 calls made directly ...
no row:  no recorder line in the log

Flags and settings

One new menu row with its options-file key; three existing variables unchanged, now also related to the row.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
Debug tab, Developer: Recorder thread (recorder_thread in longshot_options.ini) a menu row, read at the start none Off On, after a restart: the renderer's vkCmd* calls are packed and run in order by a second thread, a recording slot's own resets made without the wait: what RX_VK_RECORDER=thread with RX_VK_RECORDER_OWN=1 do helps: RX 9070 XT with a Ryzen 7 5700X3D, Windows, Hyrule Field turning, 61.5 -> 71.4 fps (3 pairs). Doesn't help: by the variables, #389: a Ryzen 5 5600X on Linux, the Market while moving, fps flat. Untested: NVIDIA, Intel, Linux through the row, Steam Deck, CPUs with 8 threads or fewer off for everyone for now: on by choice. A default is a later decision per GPU and OS, once more of them are measured
RX_VK_RECORDER an environment switch (existing) unset unset unchanged; set to any value it goes over the row for the run - unchanged: for testing
RX_VK_RECORDER_OWN an environment switch (existing) unset unset unchanged with the variable; with the row, unset means on and a value decides - unchanged: for testing
RX_VK_PARALLEL an environment switch (existing) unset unset unchanged; 2 or more refuses the row as it refuses RX_VK_RECORDER=thread, with a log line that names the row - unchanged: for testing

Testing

Tested: game-free, on Windows (MSVC-target clang 23 and MinGW g++ on PATH):

  • tests/test_recorder_thread_row.py: the mode code cut from vk_recorder.h built with vk_draw.cpp's real menu_env_on and
    the line that hands it to the recorder, over a temporary game folder. All 135 cases of RX_VK_RECORDER {unset, thread, sync,
    off, 0} x the row {no file, 0, 1} x RX_VK_PARALLEL {unset, 1, 4} x RX_VK_RECORDER_OWN {unset, 0, 1}: the mode, the slot's
    own resets, the one start or refusal line and which source it names; mode() first called from four threads at once. The
    options line as it is read (no file, no line, 0, 1, a longer key with the same start). The row in the table once, a restart
    row with a slot, preset-exempt, default 0, its key recorder_thread; the real hint over start value x mode x a recorder
    that could not start. 12 broken variants of the cut code must fail, and do.
  • on main the test fails at its first check (0 lines set rec::g_row_on, want 1), before its behavioural parts run.
  • python -I -B tests/run_all.py on this head against main 5d3ecc78: 532 passed, 11 failed, 34 skipped here; 531 / 11 / 34 on main, the same eleven (test_appimage_player_build, test_cpm_discovery, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_oot_state_tap, test_psychov35_dlss, test_task_mesh_fallback_devices, test_tested_configurations, test_vk_occlusion_queries, test_walk_key).
  • rebased once more onto main 546dc212 (one commit: the mods page and its test): the nine tests that read this PR's files (test_recorder_thread_row, test_recorder_own_idle, test_damage_debug_menu, test_menu_i18n, test_menu_restart_slots, test_preset_exempt, test_readme_menu_rows, test_env_on, test_check_pr_description) rerun there: 9 passed.
  • vk_draw.cpp, vk_menu.cpp, vk_replay.cpp and vk_menu_i18n.cpp compile as whole files with the renderer's flags for
    Windows, and to objects on Linux (Ubuntu under WSL, clang 21). The renderer DLL the game runs below used was built with
    llvm-mingw from the row's code on 839e85ff (without the translations, which that older tree's tables do not take).
  • a second-opinion review (gpt-6.1-sol) of the four code, test and translation commits: none found.

In the game (Windows 11, RX 9070 XT, AMD 26.10.2): four runs of Hyrule Field with recorder_thread=1 in the options file and
no recorder variable (the A/B above and an earlier attempt's one run), about 70 s each: the row's start line and the
recorder's stats in each log, 0 calls made directly, no device lost, no error line; the control's logs have no recorder
line. The menu on screen (two windowless runs with the renderer's menu script and shots): the row on the Debug tab under
Developer with its help text; with RX_VK_PARALLEL=4 set, the refusal's log line and the row's "Not running this time" line.

Where the row is read, by reading the code (not by runs): the menu saves and the recorder reads the options file through the
same game_module_path, so on every start (the launcher, the AppImage, the menu's Restart now) the line saved is the line
read; make_game.py (--cli, --update) rewrites only its own compile_shaders= line and keeps the others. A shader
preparation makes no vkCmd* call through the recorder, so it should neither read the row nor start the thread.

Not tested: the row on this branch's head on main (the game runs are its commits on the base 839e85ff); the row changed
by hand in the menu and the restart question after it (the runs set the options file's line); the translations on screen;
a shader preparation with the row on; a full Linux build; long sessions through the row; no content check (picture
comparison) was run on the row-on path.

Earlier runs of the same recorder thread through the variables, for context: about 25 minutes of play on this PC without a
crash, and #369 / #389 (a Ryzen 5 5600X on Linux, the Market: +6.5 % standing, flat while moving, nothing drawn wrong).

Must be tested:

  • Windows, an NVIDIA or Intel GPU: the row On, then a restart; play 10 minutes in two areas (one of them Hyrule Field or the Market), expecting the log line vk: the Recorder thread row (...), nothing drawn wrong, no device lost, and fps at a fixed spot not lower than with the row Off (by hand: test request T-TTLW)
  • Linux, any GPU (a Steam Deck or a CPU with 8 threads or fewer is welcome): the same steps, expecting the same (by hand: test request T-XA4S)

Risks

What could break: nothing while the row is Off. With it On, the recorder thread's own risks: a second thread making the
driver's command calls (a driver that misbehaves with it), and less gain or a loss on CPUs without spare cores.
Turning it off: the row Off and a restart, or RX_VK_RECORDER=0 for a run without touching the saved options.
Compatibility: Nothing changes for a player who leaves the row Off. longshot_options.ini gains a recorder_thread=
line when the menu next saves; saves, the shader and pipeline caches and the vk ABI are untouched.

Screenshots

The row on the Debug tab with its help text, and the same with the line it shows when it is On but not running (refused by
RX_VK_PARALLEL=4): two pictures in
this comment, from this PR's renderer code
on 839e85ff @ 91bf45f3 (the After column's build). Main has no such row, so there is no Before picture.

Reviewer notes

  • Start with mode_from_env / mode_init in gpu/vk/vk_recorder.h, then hn_recorder_thread in gpu/vk/vk_menu.cpp.
  • Left out on purpose: any default; a switch that applies without a restart (the mode is read once and the thread starts at
    its first packet); a row for RX_VK_RECORDER_IDLE=wait, which cost fps when it was measured for #162.
  • Seen while here, not changed: tests/test_menu_i18n.py checks that every table key is a menu text, not that every menu
    text has a key, so a new row can stay English unnoticed; the Intel shader compiles row has no translations.

Issues

See #162

Checklist

Everyone

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

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md).
  • A game-free test added for the fix, failing on main and passing here.
  • 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

**Type:** feat · **Risk:** low · **Tested on:** Windows 11, RX 9070 XT · **Needs:** Windows with an NVIDIA or Intel GPU; Linux, any GPU ## Why The recorder thread is the largest speed-up we have measured in Hyrule Field on a CPU-limited PC, but a player can only turn it on with environment variables (`RX_VK_RECORDER=thread` with `RX_VK_RECORDER_OWN=1`, #162). This adds a menu row for it, off by default. The tracker has no issue asking for it (searched "recorder", "RX_VK_RECORDER", "replay thread"). ## What changed A "Recorder thread" row on the Insert menu's Debug tab, under Developer: On makes the renderer hand its Vulkan commands to a second thread, after a restart. The environment variables keep working and go over the row for a run. Nothing changes while the row is Off. - recorder: `mode_from_env` and `mode_init` in `gpu/vk/vk_recorder.h` read the row when `RX_VK_RECORDER` is unset. The row means `thread`, with the slot's own resets (`RX_VK_RECORDER_OWN`) on unless that variable is set; `RX_VK_PARALLEL` 2 or more refuses it, as it refuses the variable. `RX_VK_RECORDER` set to any value decides alone, exactly as before. The start line and the refusal line say which of the two chose (`vk: the Recorder thread row (recorder_thread=1 ...)` or `vk: RX_VK_RECORDER=thread:`). - how the recorder reads the row: `g_row_on` in `vk_recorder.h`, set by `gpu/vk/vk_draw.cpp` to its `menu_env_on` (the reader the Descriptor reuse and Sampler cache rows already use: the row's line in `longshot_options.ini` in the game folder), so there is no second parser. The mode is still read once, at its first use. - menu: the row in `gpu/vk/vk_menu.cpp` (Debug tab, Developer; Off/On; a restart row; preset-exempt) and `opts.recorder_thread` in `vk_menu.h`. `hn_recorder_thread` shows a line under the row when it was On at the start and the recorder is not running this time (the variable said otherwise, `RX_VK_PARALLEL` refused it, or its thread or ring could not be made). - translations: the row's name, help and hint in the 13 tables of `gpu/vk/menu_i18n.h` (machine-made, as the file's others). - docs: the README's option table and Developer paragraph; `docs/development.md` gets a row for `RX_VK_RECORDER` itself (it had none) and `recorder_thread` in the list of rows read at the start. - tests: `tests/test_recorder_thread_row.py` (new); `tests/test_damage_debug_menu.py` and `tests/test_recorder_own_idle.py` follow the new row and the new `g_own` line. Not changed: `gpu/vk/vk_api.h` and every file the shader-cache stamp hashes. ## Measurements **Verdict:** it helps on this PC: 61.5 -> 71.4 fps (+16.1 %) in Hyrule Field with the row On, 3 alternating pairs, all admitted; CPU per frame rises 5.6 ms (the work moves to the recorder thread). Hardware: Windows 11, RX 9070 XT, AMD 26.10.2 (Vulkan driver 0x8001a0), Ryzen 7 5700X3D, 64 GB Scene: Hyrule Field, the standard test: the save loaded, the camera turning at full speed; 1280x720 window, 2048x1152 output (16:9), no upscaler, no frame generation, frame rate unlocked (`HOS_FPS_MAX=240`) Method: fps and CPU ms a frame from the present log and the process times over a 20 s window after the load; 3 alternating pairs, no warm-up runs, the median; game clock 0.999-1.000 in every run How to reproduce: two game folders from one base, differing only in `longshot_vk.dll`; in the PR's folder `recorder_thread=1` in `longshot_options.ini` (or the row On and a restart), no `RX_VK_RECORDER*` variable in either; our standard-test script, the two alternated Baseline: Longshot 839e85ff, the base our A/Bs share on this PC, without the row. Main was not the comparison: the PR's column is its renderer commits on that base (test/recrow-839 @ 91bf45f3: the same `gpu/` lines as this branch's), with #162's commits under them, as the row needs them (`RX_VK_RECORDER_OWN`); #162 is not in 839e85ff. Measured on main 839e85ff; main is at 546dc212 since. Of its commits in between, #162 is in the PR's column, and #60 and #250 touch `vk_recorder.h` or `vk_work.h` without changing how the recorder runs its packets; the fps on today's main will differ (#569 alone is 3-5 % less frame time), the difference between the columns is what the row turns on. A measurement on the next base will replace these. | | Before: Longshot 839e85ff, no recorder row | After: this PR's renderer on 839e85ff + #162 @ 91bf45f3, Recorder thread On | Δ | |---|---|---|---| | fps (median of 3) | 61.54 | 71.44 | +16.1 % | | CPU ms a frame | 63.82 | 69.45 | +5.6 ms | | p99 frame ms | 22.33 | 20.61 | | | worst frame ms, per run | 518, 418, 50 | 45, 137, 55 | | | frames over 250 ms in the window, per run | 1, 1, 0 | 0, 0, 0 | | | game clock | 1.000 | 1.000 | | Two of the three control runs had a stall of about half a second in the window, which lowers their fps: against the control run without one (64.04 fps) the row's median is +11.6 %. Those stalls come from this PC's drive, not from either build: small file writes to it block for hundreds of ms for some minutes after a multi-gigabyte write (a runtime folder had just been copied), and the test's own present trace is flushed on the presenting thread. An earlier A/B of the same switches by the variables (#162's description) gave 66.52 -> 76.57 fps on this PC. ``` std_test recrow2, per run (fps, CPU ms a frame, p99 ms, worst ms) no row 61.04 67.48 27.96 518.2 | 61.54 63.20 22.33 417.6 | 64.04 63.82 21.11 50.4 row On 71.24 72.47 20.61 44.9 | 71.44 69.45 21.06 136.5 | 71.64 69.17 19.22 55.3 row On: vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini; RX_VK_RECORDER unset): vkCmd* packed on the replay thread and run in order by a recorder thread; ... row On: vk: recorder: 825770 packets run in 10.0 s (busy 0.32 s, ...), 0 calls made directly ... no row: no recorder line in the log ``` ### Flags and settings One new menu row with its options-file key; three existing variables unchanged, now also related to the row. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | Debug tab, Developer: **Recorder thread** (`recorder_thread` in `longshot_options.ini`) | a menu row, read at the start | none | Off | On, after a restart: the renderer's `vkCmd*` calls are packed and run in order by a second thread, a recording slot's own resets made without the wait: what `RX_VK_RECORDER=thread` with `RX_VK_RECORDER_OWN=1` do | helps: RX 9070 XT with a Ryzen 7 5700X3D, Windows, Hyrule Field turning, 61.5 -> 71.4 fps (3 pairs). Doesn't help: by the variables, #389: a Ryzen 5 5600X on Linux, the Market while moving, fps flat. Untested: NVIDIA, Intel, Linux through the row, Steam Deck, CPUs with 8 threads or fewer | off for everyone for now: on by choice. A default is a later decision per GPU and OS, once more of them are measured | | `RX_VK_RECORDER` | an environment switch (existing) | unset | unset | unchanged; set to any value it goes over the row for the run | - | unchanged: for testing | | `RX_VK_RECORDER_OWN` | an environment switch (existing) | unset | unset | unchanged with the variable; with the row, unset means on and a value decides | - | unchanged: for testing | | `RX_VK_PARALLEL` | an environment switch (existing) | unset | unset | unchanged; 2 or more refuses the row as it refuses `RX_VK_RECORDER=thread`, with a log line that names the row | - | unchanged: for testing | ## Testing **Tested:** game-free, on Windows (MSVC-target clang 23 and MinGW g++ on PATH): - `tests/test_recorder_thread_row.py`: the mode code cut from `vk_recorder.h` built with `vk_draw.cpp`'s real `menu_env_on` and the line that hands it to the recorder, over a temporary game folder. All 135 cases of `RX_VK_RECORDER` {unset, thread, sync, off, 0} x the row {no file, 0, 1} x `RX_VK_PARALLEL` {unset, 1, 4} x `RX_VK_RECORDER_OWN` {unset, 0, 1}: the mode, the slot's own resets, the one start or refusal line and which source it names; `mode()` first called from four threads at once. The options line as it is read (no file, no line, 0, 1, a longer key with the same start). The row in the table once, a restart row with a slot, preset-exempt, default 0, its key `recorder_thread`; the real hint over start value x mode x a recorder that could not start. 12 broken variants of the cut code must fail, and do. - on main the test fails at its first check (`0 lines set rec::g_row_on, want 1`), before its behavioural parts run. - `python -I -B tests/run_all.py` on this head against main 5d3ecc78: 532 passed, 11 failed, 34 skipped here; 531 / 11 / 34 on main, the same eleven (`test_appimage_player_build`, `test_cpm_discovery`, `test_lift_cleanup_error`, `test_linux_preflight`, `test_make_game_cache`, `test_oot_state_tap`, `test_psychov35_dlss`, `test_task_mesh_fallback_devices`, `test_tested_configurations`, `test_vk_occlusion_queries`, `test_walk_key`). - rebased once more onto main 546dc212 (one commit: the mods page and its test): the nine tests that read this PR's files (`test_recorder_thread_row`, `test_recorder_own_idle`, `test_damage_debug_menu`, `test_menu_i18n`, `test_menu_restart_slots`, `test_preset_exempt`, `test_readme_menu_rows`, `test_env_on`, `test_check_pr_description`) rerun there: 9 passed. - `vk_draw.cpp`, `vk_menu.cpp`, `vk_replay.cpp` and `vk_menu_i18n.cpp` compile as whole files with the renderer's flags for Windows, and to objects on Linux (Ubuntu under WSL, clang 21). The renderer DLL the game runs below used was built with llvm-mingw from the row's code on 839e85ff (without the translations, which that older tree's tables do not take). - a second-opinion review (gpt-6.1-sol) of the four code, test and translation commits: none found. In the game (Windows 11, RX 9070 XT, AMD 26.10.2): four runs of Hyrule Field with `recorder_thread=1` in the options file and no recorder variable (the A/B above and an earlier attempt's one run), about 70 s each: the row's start line and the recorder's stats in each log, 0 calls made directly, no device lost, no error line; the control's logs have no recorder line. The menu on screen (two windowless runs with the renderer's menu script and shots): the row on the Debug tab under Developer with its help text; with `RX_VK_PARALLEL=4` set, the refusal's log line and the row's "Not running this time" line. Where the row is read, by reading the code (not by runs): the menu saves and the recorder reads the options file through the same `game_module_path`, so on every start (the launcher, the AppImage, the menu's Restart now) the line saved is the line read; `make_game.py` (`--cli`, `--update`) rewrites only its own `compile_shaders=` line and keeps the others. A shader preparation makes no `vkCmd*` call through the recorder, so it should neither read the row nor start the thread. **Not tested:** the row on this branch's head on main (the game runs are its commits on the base 839e85ff); the row changed by hand in the menu and the restart question after it (the runs set the options file's line); the translations on screen; a shader preparation with the row on; a full Linux build; long sessions through the row; no content check (picture comparison) was run on the row-on path. Earlier runs of the same recorder thread through the variables, for context: about 25 minutes of play on this PC without a crash, and #369 / #389 (a Ryzen 5 5600X on Linux, the Market: +6.5 % standing, flat while moving, nothing drawn wrong). **Must be tested:** - Windows, an NVIDIA or Intel GPU: the row On, then a restart; play 10 minutes in two areas (one of them Hyrule Field or the Market), expecting the log line `vk: the Recorder thread row (...)`, nothing drawn wrong, no device lost, and fps at a fixed spot not lower than with the row Off (by hand: test request T-TTLW) - Linux, any GPU (a Steam Deck or a CPU with 8 threads or fewer is welcome): the same steps, expecting the same (by hand: test request T-XA4S) ## Risks **What could break:** nothing while the row is Off. With it On, the recorder thread's own risks: a second thread making the driver's command calls (a driver that misbehaves with it), and less gain or a loss on CPUs without spare cores. **Turning it off:** the row Off and a restart, or `RX_VK_RECORDER=0` for a run without touching the saved options. **Compatibility:** Nothing changes for a player who leaves the row Off. `longshot_options.ini` gains a `recorder_thread=` line when the menu next saves; saves, the shader and pipeline caches and the vk ABI are untouched. ## Screenshots The row on the Debug tab with its help text, and the same with the line it shows when it is On but not running (refused by `RX_VK_PARALLEL=4`): two pictures in [this comment](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/564#issuecomment-20681), from this PR's renderer code on 839e85ff @ 91bf45f3 (the After column's build). Main has no such row, so there is no Before picture. ## Reviewer notes - Start with `mode_from_env` / `mode_init` in `gpu/vk/vk_recorder.h`, then `hn_recorder_thread` in `gpu/vk/vk_menu.cpp`. - Left out on purpose: any default; a switch that applies without a restart (the mode is read once and the thread starts at its first packet); a row for `RX_VK_RECORDER_IDLE=wait`, which cost fps when it was measured for #162. - Seen while here, not changed: `tests/test_menu_i18n.py` checks that every table key is a menu text, not that every menu text has a key, so a new row can stay English unnoticed; the Intel shader compiles row has no translations. ## Issues See #162 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The recorder thread (RX_VK_RECORDER=thread with RX_VK_RECORDER_OWN=1) could only be turned on with environment switches, so a player who wants to try it on a CPU with cores to spare had to edit longshot_env.txt. The Debug tab's Developer section now has a Recorder thread row, Off by default, saved as recorder_thread in longshot_options.ini and read once with the recorder's mode (a restart row: the mode is read once and the thread starts at the first packet).

RX_VK_RECORDER set to any value still decides alone, as before. Unset, the row decides: On is thread mode, with RX_VK_RECORDER_OWN on unless it is set (then its value decides). RX_VK_PARALLEL>=2 refuses the row as it refuses the switch. The line is read with vk_draw.cpp's menu_env_on (handed to the recorder through a function pointer, so the recorder's test fixtures need nothing new), and nothing from the environment is written into the row. The start line names the source (the row or RX_VK_RECORDER), and the row's hint says when it was On at the start but the recorder is not running.

No change to vk_api.h or any file in the shader-cache stamp.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
The real mode code cut from vk_recorder.h, with vk_draw.cpp's menu_env_on and the line that hands it to the recorder, over every case of RX_VK_RECORDER x the row x RX_VK_PARALLEL x RX_VK_RECORDER_OWN (mode, own resets, the one line and its source, the first read on four threads); the options line (no file, no line, 0, 1, a longer key with the same start); the menu row (restart, slot, preset-exempt, default 0, its ini key through the real ini_key) and its hint through the real hn_recorder_thread. Twelve broken variants must fail it.

Two tests that read the same code follow it: test_recorder_own_idle's broken variant of RX_VK_RECORDER_OWN is anchored on the new line, and test_damage_debug_menu's Developer section has one row more (and a stub for the row's hint).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
The README's option table lists every Debug row, and docs/development.md had no row for RX_VK_RECORDER itself: both now say what the row does, that RX_VK_RECORDER set for a run goes over it, and that recorder_thread is read at the start.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
feat(vk): the Recorder thread row in the menu's 13 languages
Some checks failed
beanstalk/approval awaiting maintainer approval
PR description / Description follows the template (pull_request_target) Successful in 3s
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
5128ffe5bf
The neighbouring Developer rows (Descriptor reuse, Sampler cache, Robust buffers, ASTC as RGBA8) have their names, help and hint lines in every table of menu_i18n.h; the new row's name, help and its not-running line were English in all 13. Added in each table's sorted place, with each table's count, in the words its neighbours use for thread, driver, renderer, log and the restart; switch and file names kept as they are. Machine-made, like the rest of the file: to be checked by speakers.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
docs(tested): the Recorder thread row's A/B in Hyrule Field, row off and row on
Some checks failed
beanstalk/approval awaiting maintainer approval
PR description / Description follows the template (pull_request_target) Has been cancelled
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
392c179f4e
The standard test on base 839e85ff, 3 alternating pairs: the base's game folder
against the same folder with its renderer rebuilt with #162's commits and the
row's, recorder_thread=1 in the options file and no recorder environment switch.

Assisted-by: Claude Fable 5.2
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Author
Contributor

Measured: the standard test in Hyrule Field, 3 alternating pairs, all admitted: 61.54 -> 71.44 fps (+16.1 %) with the row On through the options file and no recorder variable; CPU 63.82 -> 69.45 ms a frame. Two of the three control runs had a half-second stall in the window (against the control run without one: +11.6 %); the description says where those come from as far as it is known. New commit 392c179f adds the two docs/tested files of this A/B. Still WIP for one thing: the screenshots of the row in the menu.

Measured: the standard test in Hyrule Field, 3 alternating pairs, all admitted: 61.54 -> 71.44 fps (+16.1 %) with the row On through the options file and no recorder variable; CPU 63.82 -> 69.45 ms a frame. Two of the three control runs had a half-second stall in the window (against the control run without one: +11.6 %); the description says where those come from as far as it is known. New commit 392c179f adds the two docs/tested files of this A/B. Still WIP for one thing: the screenshots of the row in the menu.
Author
Contributor

Description only: the flags table's empty cells filled and RX_VK_PARALLEL listed, why the numbers from 839e85ff still hold on today's main, and the earlier recorder runs moved out of Not tested. No code change.

Description only: the flags table's empty cells filled and RX_VK_PARALLEL listed, why the numbers from 839e85ff still hold on today's main, and the earlier recorder runs moved out of Not tested. No code change.
Author
Contributor

Test requests opened: T-TTLW (NVIDIA or Intel, Windows) and T-XA4S (Linux). They need the testing wanted label to be claimable.

Test requests opened: T-TTLW (NVIDIA or Intel, Windows) and T-XA4S (Linux). They need the testing wanted label to be claimable.
Author
Contributor

Screenshots of the row, from this PR's renderer code on 839e85ff (test/recrow-839 @ 91bf45f3: the same gpu/ lines, without the translations), two windowless runs with the renderer's own menu script and shots (HOS_HEADLESS, RX_VK_MENU_SCRIPT, RX_VK_SHOTS, RX_VK_SHOT_MENU), recorder_thread=1 in the options file and no recorder variable.

The row On with the recorder running (the log: vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini; RX_VK_RECORDER unset): ..., then the recorder's stats lines):

this PR on 839e85ff @ 91bf45f3, Recorder thread On, the recorder running

The same with RX_VK_PARALLEL=4 set for the run, which refuses the row (the log: vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini) refused: RX_VK_PARALLEL=4 ...): the row's line says it is not running this time.

this PR on 839e85ff @ 91bf45f3, Recorder thread On but refused by RX_VK_PARALLEL=4: the hint

Screenshots of the row, from this PR's renderer code on 839e85ff (test/recrow-839 @ 91bf45f3: the same `gpu/` lines, without the translations), two windowless runs with the renderer's own menu script and shots (`HOS_HEADLESS`, `RX_VK_MENU_SCRIPT`, `RX_VK_SHOTS`, `RX_VK_SHOT_MENU`), `recorder_thread=1` in the options file and no recorder variable. The row On with the recorder running (the log: `vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini; RX_VK_RECORDER unset): ...`, then the recorder's stats lines): ![this PR on 839e85ff @ 91bf45f3, Recorder thread On, the recorder running](https://git.magicbean.dev/attachments/b038b264-165b-4ac6-bff8-ce63639b7c23) The same with `RX_VK_PARALLEL=4` set for the run, which refuses the row (the log: `vk: the Recorder thread row (recorder_thread=1 in longshot_options.ini) refused: RX_VK_PARALLEL=4 ...`): the row's line says it is not running this time. ![this PR on 839e85ff @ 91bf45f3, Recorder thread On but refused by RX_VK_PARALLEL=4: the hint](https://git.magicbean.dev/attachments/dfc00575-0ab1-4857-8a55-94ed414bad54)
Author
Contributor

Rebased on main 546dc212 (no conflict; the five commits are otherwise unchanged) and the description moved to the new template. The full suite on the rebased branch against main: the same eleven failures on both, this PR's own tests pass. Screenshots of the row and of its hint are in the comment above.

Rebased on main 546dc212 (no conflict; the five commits are otherwise unchanged) and the description moved to the new template. The full suite on the rebased branch against main: the same eleven failures on both, this PR's own tests pass. Screenshots of the row and of its hint are in the comment above.
klimovich008 force-pushed feat/recorder-thread-setting from 392c179f4e
Some checks failed
beanstalk/approval awaiting maintainer approval
PR description / Description follows the template (pull_request_target) Has been cancelled
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to 51c4b43e08
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 6s
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
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
beanstalk/approval awaiting maintainer approval
2026-11-14 21:37:45 +00:00
Compare
Author
Contributor

Closing as superseded by #588, which merged while this was being finished: its Threaded rendering row (Auto / On / Off) turns the recorder thread on together with the new render thread, through its own hook in gpu/vk/vk_recorder.h. A second row for the recorder thread alone would duplicate half of that row, and this branch now conflicts with it in vk_recorder.h, vk_menu.cpp, the README and docs/development.md.

What stays useful from here, for whoever picks it up on top of #588:

  • the measurement: the recorder thread with a slot's own resets (RX_VK_RECORDER_OWN), turned on through the options file with no variable set, 61.5 -> 71.4 fps in Hyrule Field on an RX 9070 XT / Ryzen 7 5700X3D under Windows (3 alternating pairs; the description above has the table and its caveats). #588's row turns the recorder thread on without RX_VK_RECORDER_OWN;
  • tests/test_recorder_thread_row.py's matrix of the variable, the row, RX_VK_PARALLEL and RX_VK_RECORDER_OWN, which would port to g_setting_thread;
  • the translation gap it found: a new row's name, help and hint stay English unless added to the 13 tables of gpu/vk/menu_i18n.h, and tests/test_menu_i18n.py does not catch it.

The two test requests of this PR (T-TTLW, T-XA4S) are cancelled.

Closing as superseded by #588, which merged while this was being finished: its Threaded rendering row (Auto / On / Off) turns the recorder thread on together with the new render thread, through its own hook in `gpu/vk/vk_recorder.h`. A second row for the recorder thread alone would duplicate half of that row, and this branch now conflicts with it in `vk_recorder.h`, `vk_menu.cpp`, the README and `docs/development.md`. What stays useful from here, for whoever picks it up on top of #588: - the measurement: the recorder thread with a slot's own resets (`RX_VK_RECORDER_OWN`), turned on through the options file with no variable set, 61.5 -> 71.4 fps in Hyrule Field on an RX 9070 XT / Ryzen 7 5700X3D under Windows (3 alternating pairs; the description above has the table and its caveats). #588's row turns the recorder thread on without `RX_VK_RECORDER_OWN`; - `tests/test_recorder_thread_row.py`'s matrix of the variable, the row, `RX_VK_PARALLEL` and `RX_VK_RECORDER_OWN`, which would port to `g_setting_thread`; - the translation gap it found: a new row's name, help and hint stay English unless added to the 13 tables of `gpu/vk/menu_i18n.h`, and `tests/test_menu_i18n.py` does not catch it. The two test requests of this PR (T-TTLW, T-XA4S) are cancelled.
klimovich008 closed this pull request 2026-11-14 21:41:41 +00:00
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 6s
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
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
beanstalk/approval awaiting maintainer approval

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!564
No description provided.