WIP: fix(vk): report GPU resource waits beyond recording-slot reuse #60

Draft
mooki wants to merge 5 commits from mooki/Longshot:fix/vk-gpu-wait-accounting into main
First-time contributor

Why

The old “GPU-bound wait” log could read zero while the renderer waited for other GPU resources. It counted recording-slot reuse only, so it could hide useful evidence in a performance report. This fixes the reporting, not the game's scheduling or frame rate.

Draft for one open question: in both ordinary pairs below the branch's run was slower than main's (37.9 against 44.0 fps and 42.3 against 49.5 fps at a 60 cap; the two overlay pictures show the same direction). The machine was under shared load and all four are far below the cap, so these runs cannot tell load from a cost of the added timing. A quiet paired measurement of that cost is owed before this is ready; it was never made for the original pull request either.

What changed

  • replay: log_frame_stats() in gpu/vk/vk_replay.cpp keeps the old number, explicitly labelled “recording slots only”. Add host time in GPU resource waits, split into fences, timelines, idle calls and blocking query reads.
  • accounting: gpu/vk/gpu_wait_stats.h, the macros in gpu/vk/vk_work.h and reflex_sleep() in gpu/vk/vk_reflex.cpp time the same five native calls after the existing recorder boundary. Nested waits count once; zero-timeout polls and Reflex pacing are excluded. No wait, rendering setting or synchronization rule is removed.
  • The new buckets are log-only. The FPS Breakdown and Dev levels are unchanged. Their GPU wait is a presenting-thread frame partition, including the host's GPU-poll sleeps; the new log sums explicit native-call host time across threads. Different scopes and reporting windows mean the numbers need not match. Do not add either to the other or to containing replay/capture phases.
  • Preserve current main's Auto tuning and live frame-cap callback. The carried accounting/test patches are rename-only relative to the GitHub topic; the added configuration rows record these new Longshot runs.
  • tests: tests/test_gpu_wait_stats.py and tests/gpu_wait_stats_test.cpp execute the actual wrappers; the recorder and Reflex fixtures keep their existing assertions with the new header dependency.

Flags and settings

No runtime flag, menu row, ini key or default is added or changed: the new counter is always collected at the existing native wait calls. The new authored test driver exposes these command-line options; none affects the running game. Existing runtime switches used in How to reproduce are unchanged.

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
--cxx in tests/test_gpu_wait_stats.py New test CLI option: Selects the C++ compiler for the authored fixture g++ directly; the suite supplies its compiler Off by default: test tool selection GCC/Clang accounting and portability checks Does not measure GPU performance Other compilers; current Longshot native Windows execution
--source in that test New test CLI option: Selects the checkout supplying the five real macros and source checks; the counter fixture stays this PR's This checkout Off by default: explicit main negative-control input Behavioral main/PR comparison without a GPU Does not change a running game Other platforms beyond the recorded checks
--out in that test New test CLI option: Keeps generated authored fixture files/executable in the selected output folder Temporary folder, removed afterwards Off by default: optional diagnostic artifacts Inspecting compiler inputs or failures No runtime or performance effect Other host filesystem combinations
--syntax-only in that test New test CLI option: Compiles for syntax without executing the behavioral fixture Off: compile and execute Off by default: behavior must run by default Cross-target syntax checks Is not native Windows execution or behavioral proof Current Longshot native Windows fixture execution

Measurements

Branch state: on main d725b3631982ed0035ea3ac9cbe1677e9fad260c; head e8a9913748b3e7f4d2338259e07da4bf318f10ca; whole game-free suite on this head: 508 passed, 4 failed, 20 skipped (main d725b3631982ed0035ea3ac9cbe1677e9fad260c: 510 passed, 1 failed, 20 skipped); the commits are unchanged by rebases apart from the results table.

main: d4a69456c9 (2026-11-12)
this PR: fix/vk-gpu-wait-accounting @ 0ab0541eba
Hardware: RTX 4090 24 GB, NVIDIA 615.78.08, Core i9-13900K, 96,233 MiB physical RAM, Arch Linux 7.2.15-1-omarchy
Scene: child Link in Hyrule Field by day, the same copied save and stationary view; game output requested 2048×1152, offscreen target 1920×1080, captures 1280×720; game's upscaler at 100%, frame generation off, cap 60, precompile/prediction off
Method: one warm-up per build, then before/after/before/after. After stable play and 15 seconds settling, each run records 14 seconds at Breakdown and 14 seconds at Dev, then quits. Take the last complete 120-present log report within each Dev phase, selected by its observed frame bounds, and report the median of the two logged values per build. Ordinary shared runs: this is diagnostic coverage, not a speed, overhead or GPU-utilization measurement; host descheduling contributes to the new counter.
How to reproduce: load the same copied child Hyrule Field save, stand at its spawn facing the castle, and use the scene settings above. For windowless captures set HOS_HEADLESS=1, HOS_OUTPUT_RES=2048x1152, HOS_DYNRES=0, HOS_FPS_MAX=60, RX_VK_DLSS=0, RX_VK_FRAMEGEN=0, RX_VK_PRECOMPILE=0, RX_VK_NO_PREDICT=1, RX_VK_SHOT_MENU=1 and RX_VK_SHOT_EVERY=120. Require 15 continuously stable seconds, set FPS counter to Breakdown for 14 seconds and then Dev for 14 seconds, and take the last whole 120-present report within Dev from longshot_log.txt; repeat main/this PR/main/this PR after a warm-up each. These reproduce counter coverage, not the still-owed quiet overhead comparison. Game-free: python -I -B tests/test_gpu_wait_stats.py; use --source ../main from this PR for main's actual macros, with ../main the named main checkout.

The previously published 12e0d807 added only evidence rows after measured runtime 0ab0541e; the accounting and fixture bytes were unchanged by the following rebase. The PR48 policy change after measured d4a69456 affected only scripts, docs and tests, with no game code, so the existing game observations still hold. No new game run or quiet overhead measurement is claimed.

main @ d4a69456 this PR @ 0ab0541e
Old recording-slot wait, run 1 / run 2 0.0 / 0.0 ms 0.1 / 0.0 ms
Median old recording-slot wait 0.000 ms 0.050 ms
New native resource-wait sum, run 1 / run 2 not recorded 0.19 / 0.27 ms
Median new resource-wait sum not recorded, not zero 0.230 ms
Accounting fixture with each build's actual macros, GCC and Clang 50 checks, 9 failures 50 checks, 0 failures
Run order Wall / exit Selected present Old slots, ms New resource sum, ms
before-1 77.8 s / 0 3240 0.0 not recorded
after-1 77.7 s / 0 3120 0.1 0.19
before-2 (incomplete) 58.7 s / 0 no accepted phase excluded excluded
before-2b 76.2 s / 0 3360 0.0 not recorded
after-2 75.4 s / 0 3120 0.0 0.27
before-1:
[hos Presentation Thread] vk: 3240 presents, 44.0 fps; a frame: 2425 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms; 710 pipelines (427 ms), 3 programs (0 failed), 1155 textures (2415 MB)
after-1:
[hos Presentation Thread] vk: 3120 presents, 37.9 fps; a frame: 2430 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.1 ms (recording slots only); 720 pipelines (462 ms), 3 programs (0 failed), 1170 textures (2463 MB)
[hos Presentation Thread] vk: host time in GPU resource waits: 0.19 ms a frame (fences 0.03, timelines 0.15, idle 0.00, queries 0.00; sum across threads, not frame time; excludes pacing and driver submit/present blocking)
before-2b:
[hos Presentation Thread] vk: 3360 presents, 49.5 fps; a frame: 2428 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms; 713 pipelines (210 ms), 0 programs (0 failed), 1156 textures (2413 MB)
after-2:
[hos Presentation Thread] vk: 3120 presents, 42.3 fps; a frame: 2429 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms (recording slots only); 710 pipelines (233 ms), 0 programs (0 failed), 1153 textures (2415 MB)
[hos Presentation Thread] vk: host time in GPU resource waits: 0.27 ms a frame (fences 0.03, timelines 0.24, idle 0.00, queries 0.00; sum across threads, not frame time; excludes pacing and driver submit/present blocking)

All seven games quit with exit 0; fatal, guest-crash, device-loss and kernel-fault counts are zero, with complete journal coverage. Virtual-core scheduler escape counts remain visible: warm-base=2, warm-branch=0, before-1=0, after-1=0, before-2=2, before-2b=2, after-2=0. These are not process core dumps. The incomplete before-2 attempt lost stable play during settling: the log records a 1,395.8-ms slow present, play -> loading at frame 1688 and loading -> play at frame 1700 before the requested quit. It has no accepted measurement phase and remains in the table. Only that missing pair was completed with before-2b and after-2, requiring 15 continuously stable seconds before sampling. The completed warm-ups and first pair were not repeated. None of these shared starts is used to claim faster play.

On the previously published 12e0d807, the whole suite was rerun on that exact committed head, documentation rows included, with the repository's default compiler. Its sole failure is tests/test_updater_asset_digest.py: the same local .NET missing-reference-pack NU1100 as main. The shared main suite additionally hit two timing-dependent tests/test_appimage_package_retry.py cases under heavy load; an isolated follow-up passed all nine cases, without erasing the original full-suite failure. That d4a69456 suite remains the shared measured-base record. The new rebased source-check comparison follows below; no configured Forgejo runner replaces the local whole suite.

Before: 507 passed, 2 failed, 20 skipped in 504 s (linux, 6 at once)
After: 509 passed, 1 failed, 20 skipped in 274 s (linux, 6 at once)
error NU1100: Unable to resolve 'Microsoft.AspNetCore.App.Ref (= 10.0.13)' for 'net10.0'.
Before accounting fixture: GPU wait accounting: 50 checks, 9 failures
After accounting fixture: GPU wait accounting: 50 checks, 0 failures
PASS: GPU resource waits, routing, nesting, polls, pacing and concurrent accounting
Windows syntax: 3 captured translation units, 0 failures, 0 unjudged drivers/files; Windows execution NOT TESTED

Historical platform evidence: the Hookshot fork run 43282975989 passed both Windows and both Ubuntu source-check parts at ec274e33a514950348948b8b933233a950ba0aaa. The published GitHub head bfa7e18d52f857b24280abc8c45bf936be006d96 carries the same topic patches; the Longshot topic differs only in names and new evidence rows, while retaining newer main. This is not a fresh Longshot Windows run.

Retained source-check history

The whole suite was rerun after rebase, including all documentation rows, with the repository's default compiler and a fresh test home; its current pinned result appears only in the branch-state block above. The shared baseline still has the local missing ASP.NET reference pack failure in tests/test_updater_asset_digest.py (NU1100).

That initial r2 run also failed nx2/test_unsigned_image_types.py and nx2/test_texture_phi_dominance.py because compiler processes were killed with SIGKILL, and tests/test_bake_mimo.py because shaderbatch compilation failed without a diagnostic. These three passed the shared main run. A separate failed-only run on the same source, with one test worker, then passed all three. The full-suite failure is retained, not relabelled as a full-suite pass; no additional whole-suite rerun is claimed.

Cause not established: the two IR fixtures and the bake compiler calls have no per-compile timeout, and the outer 600-second suite timeout was not the reported failure. Do not infer a timeout, memory kill or product regression merely from the shared load or the successful isolated follow-up. Passing those tests does not answer the quiet instrumentation-overhead question.

The isolated follow-up ran only those three checks, with one test worker:

3 checks, 0 skipped, 1 at once (linux)
pass      17.5s  tests/test_bake_mimo.py
pass       5.2s  nx2/test_texture_phi_dominance.py
pass       5.1s  nx2/test_unsigned_image_types.py
3 passed, 0 failed, 0 skipped in 28 s (linux, 1 at once)

Its child and descendants exited with code 0. This separate follow-up does not erase the whole-suite failure.

Testing

Tested: on the Arch Linux / RTX 4090 system named above, the whole game-free suite with the default compiler, GCC and Clang accounting fixtures (the main macros fail 9 of 50 checks; this PR passes 50), and the isolated three-test follow-up recorded above. Three changed C++ fixtures pass Windows-target syntax checks; the older Hookshot Windows/Ubuntu run is historical platform evidence, not current Longshot Windows execution. In the game, the copied child Hyrule Field save reached stable play, the Breakdown and Dev levels were inspected, and two accepted interleaved pairs supplied the log observations; the incomplete attempt and warm-ups remain recorded. Tested but not measured: the shared gameplay and overlay appearance; instrumentation overhead, a speed gain or regression and GPU utilization were not isolated. The live cap/descriptor/sampler controls were retained in source, not toggled during these runs.

Not tested: Windows gameplay and native execution of the current Longshot C++ fixtures, AMD/Intel gameplay, live Reflex/frame generation, deliberately failing drivers, long sessions and isolated instrumentation overhead. The available run environment was Linux/NVIDIA; this task did not exercise the other drivers or obtain a quiet paired interval, so no portability or overhead result is inferred. No new hardware test request has been posted. The new count excludes recorder/CPU locks, display acquire/pacing, startup cooperative-matrix probing and blocking hidden inside submit/present. A small count does not prove an idle GPU, and a sum across threads can exceed one frame.

Needs other testers:

  • Windows with NVIDIA or AMD Vulkan: run the accounting/recorder/Reflex fixtures and compare the same scene's log, expecting unchanged synchronization/rendering and the explicit resource-wait buckets without counting Reflex pacing (request to open).
  • AMD Vulkan on Linux or Steam Deck: check the same scene and log at ordinary settings, expecting the new buckets to report explicit resource waits while the image and wait behavior stay unchanged (request to open).
  • Intel Arc: check the renderer and resource-wait log on the discrete Intel driver, expecting the same call/result behavior and correctly labelled diagnostic buckets (request to open).

Screenshots

Nothing on screen is changed by this log-only counter; the images check integration with the existing overlay.

Both inspected before/after scenes show the same stationary Hyrule Field view. The inherited GPU wait remains on both levels; Dev adds VRAM, texture, draw/dispatch/pass and pipeline rows. That does not contradict the separate log-only sum. The frame cap is reported as 60 with no restart required; the source of the live cap, descriptor reuse and sampler-cache controls is unchanged from main. No live cap/descriptor/sampler toggle is claimed here.

main @ d4a69456 (left); this PR @ 0ab0541e (right): Breakdown.

Before and after, unchanged Breakdown counter. GPU wait is the presenting-thread partition, not this PR's new all-thread log sum. Displayed rates are not compared as performance.

main @ d4a69456 (left); this PR @ 0ab0541e (right): Dev.

Before and after with main's Dev level, its additional renderer rows intact. The new resource-wait buckets remain in the log.

Historical context: main @ 51da0464 and this PR @ 9d786cf1; the third image is the separate 75% profile, not a new Longshot comparison.

Historical Hookshot field evidence from 2026-11-12: old high-resolution pair and separate 75% profile. Not new Longshot measurements.

Risk and rollback

Two clock reads and an atomic accumulation around nonpolling waits can add CPU cost. Both shared pairs were slower on the branch, but the load prevents assigning a cause; the requested quiet paired cost measurement is still missing, so this remains WIP. The sum also overlaps across threads and can be misleading if read as frame time or GPU utilization. There is no runtime opt-out switch: reverting this PR removes the timing wrappers and extra log line while restoring the original native calls and recorder boundaries. No synchronization or safety wait should be disabled as a workaround.

Reviewer notes

Start with gpu_wait_stats::Scope, Exclude and timed() in gpu/vk/gpu_wait_stats.h, then confirm each of the five vk_work.h wrappers still drains the recorder before timing its own driver call. Check Reflex pacing exclusion and the fixture's nested/poll/error/concurrency cases. The old slot timer, the FPS presenting-thread partition and the new all-thread sum are different instruments, not additive costs. Current main's Auto tuning and live cap callback are preserved; overlay wording, Windows gameplay and the quiet overhead question remain explicit follow-up/verification boundaries.

Historical runtime evidence, Hookshot on 2026-11-12, not repeated as a new timing claim: main 51da0464 logged 0.010 ms in old slots; counter 9d786cf1 logged 0.000 ms in slots and 2.927 ms in native resource waits at 5120×2880. Those ordinary runs had different competing CPU loads. The original PR made no speed-gain claim; the deferred paired overhead measurement remains unperformed.

Baseline for that historical context: Hookshot main @ 51da0464; the changed build was Hookshot this PR @ 9d786cf1. It is retained provenance, not a replacement for the new Longshot comparison or the still-owed quiet overhead run.

Issues

See https://github.com/NotRexed/Hookshot/issues/995#issuecomment-6828404093. Original pull request: https://github.com/NotRexed/Hookshot/issues/2897. No matching GPU-wait-accounting issue was present in the checked Longshot inventory; these links do not refer to forge issue numbers.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main. — The tested base is frozen in branch state; a later mechanical refresh is deferred until readiness.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above). — The retained full run had additional compiler failures; those three pass separately, not in a new whole-suite pass.
  • Tested in the game, or what wasn't is named under Testing. — Tested in the game; remains WIP until the quiet cost measurement exists.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). — The section is filled; the listed requests still need opening.
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. — Diagnostic coverage is measured; the quiet paired instrumentation-cost measurement is still owed.
  • 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). — No runtime setting changed; the test driver's CLI is documented and docs/development.md explains the new log's scope.
  • 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.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.

Moved from NotRexed/Hookshot#2897.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]

## Why The old “GPU-bound wait” log could read zero while the renderer waited for other GPU resources. It counted recording-slot reuse only, so it could hide useful evidence in a performance report. This fixes the reporting, not the game's scheduling or frame rate. **Draft for one open question:** in both ordinary pairs below the branch's run was slower than main's (37.9 against 44.0 fps and 42.3 against 49.5 fps at a 60 cap; the two overlay pictures show the same direction). The machine was under shared load and all four are far below the cap, so these runs cannot tell load from a cost of the added timing. A quiet paired measurement of that cost is owed before this is ready; it was never made for the original pull request either. ## What changed - replay: `log_frame_stats()` in `gpu/vk/vk_replay.cpp` keeps the old number, explicitly labelled “recording slots only”. Add **host time in GPU resource waits**, split into fences, timelines, idle calls and blocking query reads. - accounting: `gpu/vk/gpu_wait_stats.h`, the macros in `gpu/vk/vk_work.h` and `reflex_sleep()` in `gpu/vk/vk_reflex.cpp` time the same five native calls after the existing recorder boundary. Nested waits count once; zero-timeout polls and Reflex pacing are excluded. No wait, rendering setting or synchronization rule is removed. - **The new buckets are log-only. The FPS Breakdown and Dev levels are unchanged.** Their `GPU wait` is a presenting-thread frame partition, including the host's GPU-poll sleeps; the new log sums explicit native-call host time across threads. Different scopes and reporting windows mean the numbers need not match. Do not add either to the other or to containing replay/capture phases. - Preserve current main's Auto tuning and live frame-cap callback. The carried accounting/test patches are rename-only relative to the GitHub topic; the added configuration rows record these new Longshot runs. - tests: `tests/test_gpu_wait_stats.py` and `tests/gpu_wait_stats_test.cpp` execute the actual wrappers; the recorder and Reflex fixtures keep their existing assertions with the new header dependency. ## Flags and settings No runtime flag, menu row, ini key or default is added or changed: the new counter is always collected at the existing native wait calls. The new authored test driver exposes these command-line options; none affects the running game. Existing runtime switches used in How to reproduce are unchanged. | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `--cxx` in `tests/test_gpu_wait_stats.py` | New test CLI option: Selects the C++ compiler for the authored fixture | `g++` directly; the suite supplies its compiler | Off by default: test tool selection | GCC/Clang accounting and portability checks | Does not measure GPU performance | Other compilers; current Longshot native Windows execution | | `--source` in that test | New test CLI option: Selects the checkout supplying the five real macros and source checks; the counter fixture stays this PR's | This checkout | Off by default: explicit main negative-control input | Behavioral main/PR comparison without a GPU | Does not change a running game | Other platforms beyond the recorded checks | | `--out` in that test | New test CLI option: Keeps generated authored fixture files/executable in the selected output folder | Temporary folder, removed afterwards | Off by default: optional diagnostic artifacts | Inspecting compiler inputs or failures | No runtime or performance effect | Other host filesystem combinations | | `--syntax-only` in that test | New test CLI option: Compiles for syntax without executing the behavioral fixture | Off: compile and execute | Off by default: behavior must run by default | Cross-target syntax checks | Is not native Windows execution or behavioral proof | Current Longshot native Windows fixture execution | ## Measurements <!-- branch-state --> Branch state: on main `d725b3631982ed0035ea3ac9cbe1677e9fad260c`; head `e8a9913748b3e7f4d2338259e07da4bf318f10ca`; whole game-free suite on this head: `508 passed, 4 failed, 20 skipped` (main `d725b3631982ed0035ea3ac9cbe1677e9fad260c`: `510 passed, 1 failed, 20 skipped`); the commits are unchanged by rebases apart from the results table. <!-- /branch-state --> main: d4a69456c9de5131589af1fcf9a09adba4c403d1 (2026-11-12) this PR: fix/vk-gpu-wait-accounting @ 0ab0541eba6e9a9d6e55fff539b6102a0453c879 Hardware: RTX 4090 24 GB, NVIDIA 615.78.08, Core i9-13900K, 96,233 MiB physical RAM, Arch Linux 7.2.15-1-omarchy Scene: child Link in Hyrule Field by day, the same copied save and stationary view; game output requested 2048×1152, offscreen target 1920×1080, captures 1280×720; game's upscaler at 100%, frame generation off, cap 60, precompile/prediction off Method: one warm-up per build, then before/after/before/after. After stable play and 15 seconds settling, each run records 14 seconds at Breakdown and 14 seconds at Dev, then quits. Take the **last complete 120-present log report within each Dev phase**, selected by its observed frame bounds, and report the median of the two logged values per build. Ordinary shared runs: this is diagnostic coverage, not a speed, overhead or GPU-utilization measurement; host descheduling contributes to the new counter. How to reproduce: load the same copied child Hyrule Field save, stand at its spawn facing the castle, and use the scene settings above. For windowless captures set `HOS_HEADLESS=1`, `HOS_OUTPUT_RES=2048x1152`, `HOS_DYNRES=0`, `HOS_FPS_MAX=60`, `RX_VK_DLSS=0`, `RX_VK_FRAMEGEN=0`, `RX_VK_PRECOMPILE=0`, `RX_VK_NO_PREDICT=1`, `RX_VK_SHOT_MENU=1` and `RX_VK_SHOT_EVERY=120`. Require 15 continuously stable seconds, set FPS counter to Breakdown for 14 seconds and then Dev for 14 seconds, and take the last whole 120-present report within Dev from `longshot_log.txt`; repeat main/this PR/main/this PR after a warm-up each. These reproduce counter coverage, not the still-owed quiet overhead comparison. Game-free: `python -I -B tests/test_gpu_wait_stats.py`; use `--source ../main` from this PR for main's actual macros, with `../main` the named main checkout. The previously published `12e0d807` added only evidence rows after measured runtime `0ab0541e`; the accounting and fixture bytes were unchanged by the following rebase. The PR48 policy change after measured `d4a69456` affected only scripts, docs and tests, with no game code, so the existing game observations still hold. No new game run or quiet overhead measurement is claimed. | | main @ d4a69456 | this PR @ 0ab0541e | |---|---|---| | Old recording-slot wait, run 1 / run 2 | 0.0 / 0.0 ms | 0.1 / 0.0 ms | | Median old recording-slot wait | 0.000 ms | 0.050 ms | | New native resource-wait sum, run 1 / run 2 | not recorded | 0.19 / 0.27 ms | | Median new resource-wait sum | not recorded, not zero | 0.230 ms | | Accounting fixture with each build's actual macros, GCC and Clang | 50 checks, 9 failures | 50 checks, 0 failures | | Run order | Wall / exit | Selected present | Old slots, ms | New resource sum, ms | |---|---|---:|---:|---:| | before-1 | 77.8 s / 0 | 3240 | 0.0 | not recorded | | after-1 | 77.7 s / 0 | 3120 | 0.1 | 0.19 | | before-2 (incomplete) | 58.7 s / 0 | no accepted phase | excluded | excluded | | before-2b | 76.2 s / 0 | 3360 | 0.0 | not recorded | | after-2 | 75.4 s / 0 | 3120 | 0.0 | 0.27 | ```text before-1: [hos Presentation Thread] vk: 3240 presents, 44.0 fps; a frame: 2425 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms; 710 pipelines (427 ms), 3 programs (0 failed), 1155 textures (2415 MB) after-1: [hos Presentation Thread] vk: 3120 presents, 37.9 fps; a frame: 2430 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.1 ms (recording slots only); 720 pipelines (462 ms), 3 programs (0 failed), 1170 textures (2463 MB) [hos Presentation Thread] vk: host time in GPU resource waits: 0.19 ms a frame (fences 0.03, timelines 0.15, idle 0.00, queries 0.00; sum across threads, not frame time; excludes pacing and driver submit/present blocking) before-2b: [hos Presentation Thread] vk: 3360 presents, 49.5 fps; a frame: 2428 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms; 713 pipelines (210 ms), 0 programs (0 failed), 1156 textures (2413 MB) after-2: [hos Presentation Thread] vk: 3120 presents, 42.3 fps; a frame: 2429 draws (0 skipped), 127 dispatches, 162 passes, 131 barriers, 6 submits, 0.0 uploads (0.0 MB), 80 ub updates (56 overlays), GPU-bound wait 0.0 ms (recording slots only); 710 pipelines (233 ms), 0 programs (0 failed), 1153 textures (2415 MB) [hos Presentation Thread] vk: host time in GPU resource waits: 0.27 ms a frame (fences 0.03, timelines 0.24, idle 0.00, queries 0.00; sum across threads, not frame time; excludes pacing and driver submit/present blocking) ``` All seven games quit with exit 0; fatal, guest-crash, device-loss and kernel-fault counts are zero, with complete journal coverage. Virtual-core scheduler escape counts remain visible: warm-base=2, warm-branch=0, before-1=0, after-1=0, before-2=2, before-2b=2, after-2=0. These are not process core dumps. The incomplete `before-2` attempt lost stable play during settling: the log records a 1,395.8-ms slow present, `play -> loading` at frame 1688 and `loading -> play` at frame 1700 before the requested quit. It has no accepted measurement phase and remains in the table. Only that missing pair was completed with `before-2b` and `after-2`, requiring 15 continuously stable seconds before sampling. The completed warm-ups and first pair were not repeated. None of these shared starts is used to claim faster play. On the previously published `12e0d807`, the **whole suite was rerun on that exact committed head**, documentation rows included, with the repository's default compiler. Its sole failure is `tests/test_updater_asset_digest.py`: the same local .NET missing-reference-pack `NU1100` as main. The shared main suite additionally hit two timing-dependent `tests/test_appimage_package_retry.py` cases under heavy load; an isolated follow-up passed all nine cases, without erasing the original full-suite failure. That d4a69456 suite remains the shared measured-base record. The new rebased source-check comparison follows below; no configured Forgejo runner replaces the local whole suite. ```text Before: 507 passed, 2 failed, 20 skipped in 504 s (linux, 6 at once) After: 509 passed, 1 failed, 20 skipped in 274 s (linux, 6 at once) error NU1100: Unable to resolve 'Microsoft.AspNetCore.App.Ref (= 10.0.13)' for 'net10.0'. Before accounting fixture: GPU wait accounting: 50 checks, 9 failures After accounting fixture: GPU wait accounting: 50 checks, 0 failures PASS: GPU resource waits, routing, nesting, polls, pacing and concurrent accounting Windows syntax: 3 captured translation units, 0 failures, 0 unjudged drivers/files; Windows execution NOT TESTED ``` Historical platform evidence: the Hookshot fork [run 43282975989](https://github.com/mooki000/Hookshot/actions/runs/43282975989) passed both Windows and both Ubuntu source-check parts at `ec274e33a514950348948b8b933233a950ba0aaa`. The published GitHub head `bfa7e18d52f857b24280abc8c45bf936be006d96` carries the same topic patches; the Longshot topic differs only in names and new evidence rows, while retaining newer main. This is not a fresh Longshot Windows run. ### Retained source-check history The whole suite was rerun after rebase, including all documentation rows, with the repository's default compiler and a fresh test home; its current pinned result appears only in the branch-state block above. The shared baseline still has the local missing ASP.NET reference pack failure in `tests/test_updater_asset_digest.py` (`NU1100`). That initial r2 run also failed `nx2/test_unsigned_image_types.py` and `nx2/test_texture_phi_dominance.py` because compiler processes were killed with SIGKILL, and `tests/test_bake_mimo.py` because shaderbatch compilation failed without a diagnostic. These three passed the shared main run. A separate failed-only run on the same source, with one test worker, then passed all three. The full-suite failure is retained, not relabelled as a full-suite pass; no additional whole-suite rerun is claimed. Cause not established: the two IR fixtures and the bake compiler calls have no per-compile timeout, and the outer 600-second suite timeout was not the reported failure. Do not infer a timeout, memory kill or product regression merely from the shared load or the successful isolated follow-up. Passing those tests does not answer the quiet instrumentation-overhead question. The isolated follow-up ran only those three checks, with one test worker: ```text 3 checks, 0 skipped, 1 at once (linux) pass 17.5s tests/test_bake_mimo.py pass 5.2s nx2/test_texture_phi_dominance.py pass 5.1s nx2/test_unsigned_image_types.py 3 passed, 0 failed, 0 skipped in 28 s (linux, 1 at once) ``` Its child and descendants exited with code 0. This separate follow-up does not erase the whole-suite failure. ## Testing **Tested:** on the Arch Linux / RTX 4090 system named above, the whole game-free suite with the default compiler, GCC and Clang accounting fixtures (the main macros fail 9 of 50 checks; this PR passes 50), and the isolated three-test follow-up recorded above. Three changed C++ fixtures pass Windows-target syntax checks; the older Hookshot Windows/Ubuntu run is historical platform evidence, not current Longshot Windows execution. In the game, the copied child Hyrule Field save reached stable play, the Breakdown and Dev levels were inspected, and two accepted interleaved pairs supplied the log observations; the incomplete attempt and warm-ups remain recorded. Tested but not measured: the shared gameplay and overlay appearance; instrumentation overhead, a speed gain or regression and GPU utilization were not isolated. The live cap/descriptor/sampler controls were retained in source, not toggled during these runs. **Not tested:** Windows gameplay and native execution of the current Longshot C++ fixtures, AMD/Intel gameplay, live Reflex/frame generation, deliberately failing drivers, long sessions and isolated instrumentation overhead. The available run environment was Linux/NVIDIA; this task did not exercise the other drivers or obtain a quiet paired interval, so no portability or overhead result is inferred. No new hardware test request has been posted. The new count excludes recorder/CPU locks, display acquire/pacing, startup cooperative-matrix probing and blocking hidden inside submit/present. A small count does not prove an idle GPU, and a sum across threads can exceed one frame. **Needs other testers:** - Windows with NVIDIA or AMD Vulkan: run the accounting/recorder/Reflex fixtures and compare the same scene's log, expecting unchanged synchronization/rendering and the explicit resource-wait buckets without counting Reflex pacing (request to open). - AMD Vulkan on Linux or Steam Deck: check the same scene and log at ordinary settings, expecting the new buckets to report explicit resource waits while the image and wait behavior stay unchanged (request to open). - Intel Arc: check the renderer and resource-wait log on the discrete Intel driver, expecting the same call/result behavior and correctly labelled diagnostic buckets (request to open). ## Screenshots Nothing on screen is changed by this log-only counter; the images check integration with the existing overlay. Both inspected before/after scenes show the same stationary Hyrule Field view. The inherited `GPU wait` remains on both levels; Dev adds VRAM, texture, draw/dispatch/pass and pipeline rows. That does not contradict the separate log-only sum. The frame cap is reported as 60 with no restart required; the source of the live cap, descriptor reuse and sampler-cache controls is unchanged from main. No live cap/descriptor/sampler toggle is claimed here. **main @ d4a69456 (left); this PR @ 0ab0541e (right): Breakdown.** ![Before and after, unchanged Breakdown counter. `GPU wait` is the presenting-thread partition, not this PR's new all-thread log sum. Displayed rates are not compared as performance.](https://git.magicbean.dev/attachments/62b5d04a-21c4-49a4-8500-69a363a477ac) **main @ d4a69456 (left); this PR @ 0ab0541e (right): Dev.** ![Before and after with main's Dev level, its additional renderer rows intact. The new resource-wait buckets remain in the log.](https://git.magicbean.dev/attachments/e61c7748-f9ab-460a-b821-39311b2897c5) **Historical context:** main @ 51da0464 and this PR @ 9d786cf1; the third image is the separate 75% profile, not a new Longshot comparison. ![Historical Hookshot field evidence from 2026-11-12: old high-resolution pair and separate 75% profile. Not new Longshot measurements.](https://git.magicbean.dev/attachments/3c83441a-e424-422a-b0b3-81ba83f6fe14) ## Risk and rollback Two clock reads and an atomic accumulation around nonpolling waits can add CPU cost. Both shared pairs were slower on the branch, but the load prevents assigning a cause; the requested quiet paired cost measurement is still missing, so this remains WIP. The sum also overlaps across threads and can be misleading if read as frame time or GPU utilization. There is no runtime opt-out switch: reverting this PR removes the timing wrappers and extra log line while restoring the original native calls and recorder boundaries. No synchronization or safety wait should be disabled as a workaround. ## Reviewer notes Start with `gpu_wait_stats::Scope`, `Exclude` and `timed()` in `gpu/vk/gpu_wait_stats.h`, then confirm each of the five `vk_work.h` wrappers still drains the recorder before timing its own driver call. Check Reflex pacing exclusion and the fixture's nested/poll/error/concurrency cases. The old slot timer, the FPS presenting-thread partition and the new all-thread sum are different instruments, not additive costs. Current main's Auto tuning and live cap callback are preserved; overlay wording, Windows gameplay and the quiet overhead question remain explicit follow-up/verification boundaries. Historical runtime evidence, **Hookshot on 2026-11-12, not repeated as a new timing claim**: main `51da0464` logged 0.010 ms in old slots; counter `9d786cf1` logged 0.000 ms in slots and 2.927 ms in native resource waits at 5120×2880. Those ordinary runs had different competing CPU loads. The original PR made no speed-gain claim; the deferred paired overhead measurement remains unperformed. Baseline for that historical context: Hookshot main @ 51da0464; the changed build was Hookshot this PR @ 9d786cf1. It is retained provenance, not a replacement for the new Longshot comparison or the still-owed quiet overhead run. ## Issues See https://github.com/NotRexed/Hookshot/issues/995#issuecomment-6828404093. Original pull request: https://github.com/NotRexed/Hookshot/issues/2897. No matching GPU-wait-accounting issue was present in the checked Longshot inventory; these links do not refer to forge issue numbers. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. — The tested base is frozen in branch state; a later mechanical refresh is deferred until readiness. - [ ] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). — The retained full run had additional compiler failures; those three pass separately, not in a new whole-suite pass. - [x] Tested in the game, or what wasn't is named under Testing. — Tested in the game; remains WIP until the quiet cost measurement exists. - [ ] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). — The section is filled; the listed requests still need opening. - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. — Diagnostic coverage is measured; the quiet paired instrumentation-cost measurement is still owed. - [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). — No runtime setting changed; the test driver's CLI is documented and docs/development.md explains the new log's scope. - [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] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated. --- Moved from NotRexed/Hookshot#2897. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <[email protected]>
Keep the old slot counter's value and clarify its scope. Log elapsed host time
inside fence, timeline, idle and waiting-query calls, after recorder drains, without
counting nested calls or Reflex pacing twice. A zero slot wait is not proof that
the GPU is idle.

Exercise the actual boundary macros and exact-clock accounting, including the
old-source negative control. Refs #995.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Keep every retirement assertion and compile the production pacing exclusion.
No runtime code changes.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Keep the ordinary run distinct from deferred paired timing evidence.

Assisted-by: GPT-6 Astra

Co-Authored-By: GPT-6 Astra <[email protected]>
The extracted fixture includes longshot_env.h. Declare the project CRT policy before any header so Windows fixture compilation uses the same intended contract. Runtime code and assertions are unchanged.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Keep the two interleaved log pairs, both counter levels, warm-ups and incomplete baseline attempt explicit. These shared observations prove accounting coverage, not speed or instrumentation overhead.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Author
First-time contributor

Updated the description to the newest template, including Testing and one branch-state block, while preserving the uploaded pictures, earlier measurements and the open quiet-overhead question. The rebase onto d725b363 is e8a99137, with all five patches unchanged; the game observations retain their measured commits. Its whole suite reports 508 passed, 4 failed and 20 skipped, and the three compiler-related failures pass in a separate one-worker follow-up without erasing the original full-suite result or the shared local .NET failure. This stays WIP for the quiet paired cost measurement and pending test requests; the branch is refreshed onto the newest main when it is marked ready.

Updated the description to the newest template, including Testing and one branch-state block, while preserving the uploaded pictures, earlier measurements and the open quiet-overhead question. The rebase onto `d725b363` is `e8a99137`, with all five patches unchanged; the game observations retain their measured commits. Its whole suite reports 508 passed, 4 failed and 20 skipped, and the three compiler-related failures pass in a separate one-worker follow-up without erasing the original full-suite result or the shared local .NET failure. This stays WIP for the quiet paired cost measurement and pending test requests; the branch is refreshed onto the newest main when it is marked ready.
mooki force-pushed fix/vk-gpu-wait-accounting from 12e0d807bb to e8a9913748
Some checks are pending
PR description / Description follows the template (pull_request_target) Waiting to run
2026-11-13 02:04:35 +00:00
Compare
Some checks are pending
PR description / Description follows the template (pull_request_target) Waiting to run
This pull request has changes conflicting with the target branch.
  • docs/tested-configurations.md
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'fix/vk-gpu-wait-accounting:mooki-fix/vk-gpu-wait-accounting'
git switch 'mooki-fix/vk-gpu-wait-accounting'
Sign in to join this conversation.
No description provided.