Vulkan capture: preserve the game's sync mode, drop the capture throttle

The capture must never change vsync, and must not frame-limit itself.

- Removed the ~150 Hz capture throttle from VkCapture. It was wrong: vsync already
  paces capture (a 144 Hz FIFO game presents 144x/s, so we mirror 144x/s). The only
  limiter left is ring backpressure (skip a frame if the reaper is behind), which is
  correctness, not a cap, and never touches the game's present thread or sync mode.
- The layer/hook already pass VkSwapchainCreateInfoKHR straight through, so the
  present mode (= the sync mode) is untouched. Added a presentMode log to prove it.

Measured on Sphere Spectacle (direct launch, layer attached): presentMode=2 (FIFO),
steady 144.0 fps, and with the throttle gone the mirror now tracks it at 144/s
(was capped ~130). The earlier 400-600 fps reading was a direct-launch artifact --
a non-foreground windowed FIFO app isn't throttled by DWM -- not the layer, and not
the case through Steam (144). vk_validate now states the mirror follows the present
rate (no throttle) and still asserts a present-rate floor.

Full suite 21/21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 08:48:44 +02:00
parent d4412ff610
commit 9fee13789d
5 changed files with 30 additions and 35 deletions

View File

@@ -110,10 +110,6 @@ default** and covers anything the hooked path doesn't.
### Current tasks
- **The Vulkan capture must not change the swapchain's sync mode.** If the game vsyncs (e.g. FIFO at
144 Hz), it must still vsync with the layer/hook attached; if it didn't, it must not start. The
capture is forbidden from throttling or frame-limiting — that is vsync's job. Remove the ~150 Hz
capture throttle and prove (measure) the present mode/rate is identical with and without the layer.
- **DX12 capture is measurably slower than the other backends — find out why and improve it.** The
present-thread overhead guard shows DX12 ~0.34 ms vs DX11 ~0.05 ms / OpenGL ~0.09 ms (the D3D11On12
bridge). Investigate and reduce it.
@@ -133,7 +129,10 @@ rate while mirroring (guarded by `vk_capture_perf_test`; every GPU backend's hoo
present-thread overhead bound) — and the **inline-hook (suspended-inject) Vulkan path**, which now
captures Sphere Spectacle correctly once the harness launches the exe with the right working
directory and `vk_hook` intercepts present/swapchain resolved via `vkGetInstanceProcAddr` (not just
`vkGetDeviceProcAddr`). Both Vulkan paths are verified on the real game. See **Lessons learned** and
`vkGetDeviceProcAddr`). Both Vulkan paths are verified on the real game. The capture also **preserves
the game's sync mode** — it passes the swapchain's present mode through untouched and no longer
throttles the mirror (vsync paces it): Sphere Spectacle requests `FIFO` and holds a steady 144 Hz
with the layer attached. See **Lessons learned** and
the test suite for each. Open directions: per-game profiles, multi-guest virtual-pad mapping, and
continuous raw-mouse *movement* forwarding (the MKB event stream is position-based today).
@@ -537,10 +536,19 @@ Non-obvious things that cost time and constrain the design:
cadence; a hard FPS gate is meaningless"), which hid a real **144→3 FPS** stall: the read-back ran
on the present thread and spent ~370 ms/frame doing a CPU read of write-combined staging memory.
After moving the read-back to a reaper thread (`coop::hook::VkCapture`), `present_calls` (counted
every present, independent of the throttled mirror) reflects the game's true rate, so the tool now
**asserts** it stays healthy while capturing. The mirror is deliberately throttled to ~150 Hz, so
`copied < present_calls` is expected, not a drop. Lesson: a perf check must assert a bound — if you
find yourself explaining why a number is fine, make the test prove it.
every present) reflects the game's true rate, so the tool now **asserts** it stays healthy while
capturing. Lesson: a perf check must assert a bound — if you find yourself explaining why a number
is fine, make the test prove it.
- **The capture must not touch the game's sync mode — and must not throttle itself.** The swapchain's
**present mode** *is* the sync mode (`FIFO` = vsync, `IMMEDIATE`/`MAILBOX` = off); `VkCapture` and
the layer pass `VkSwapchainCreateInfoKHR` straight through, so whatever the game asked for is what
it gets — proven by logging `ci->presentMode` (Sphere Spectacle = `FIFO`, and it holds a steady
144 Hz with the layer attached). An earlier ~150 Hz *capture* throttle was wrong and is removed:
capture follows the present rate, which **vsync** already paces (a 144 Hz FIFO game presents 144×/s,
so we mirror 144×/s). The only limiter left is ring backpressure — if the reaper can't keep up we
skip a frame rather than stall the game — which is correctness, not a frame cap. (A direct-launch
window that isn't composited/foreground can present uncapped because DWM doesn't throttle a
non-foreground windowed FIFO app — that's the OS, not us, and not the case under Steam.)
- **A render client that predates our injection has no knowable format — measure it.**
We inject into already-running games, so we usually never see the game's
`IAudioClient::Initialize`; the render-hook then assumes the device mix format for that