Vulkan: no exposure flashes without the frame wait (a capture waits only for the write-back of a page it copies) #645

Merged
hckrman101 merged 1 commit from flicker-capture-wait into main 2026-11-08 15:38:00 +00:00
hckrman101 commented 2026-11-08 15:33:35 +00:00 (Migrated from github.com)

This replaces #513's frame wait (each present waiting for its frame's GPU work), which is now on by default on Linux and Windows, with a wait only where the race actually is. The flicker stays gone and the replay can run a frame ahead of the GPU again.

What the frame wait was hiding. The one-frame exposure flashes come from one page that OoT's GPU and CPU both write: the exposure meter. The GPU adds the frame's light into it; the CPU sets the sums back to 0 and fills in the rest. The CPU runs a frame ahead, so its capture of that page could be taken before the GPU's write-back of it had reached guest memory. The mirror sync then copied the page whole, with the GPU's words from the frame before, over the GPU's newer ones, and the exposure stepped for a frame. Waiting at every present closed that window, but for every frame and every page.

Change.

  • capture_make checks the pages the CPU wrote against the submissions that write them back (the readback wait's table, wb_pages). When one of those submissions is still on the GPU, the copy waits for it (250 ms at most). Other captures don't wait.
  • vk_timeline_completed / vk_timeline_wait (bounded) in vk_replay.cpp for that.
  • The per-present wait is removed on all platforms. RX_VK_WAIT_GPU=<frames> still limits how far ahead the replay may run; unset means no wait, as before #513.
  • RX_VK_CAPTURE_WAIT=0 turns the new wait off.

About 1 capture in 6 waits, roughly 12 ms each, in Kokiri Forest.

Measured (Linux, RTX 3090, Kokiri Forest, fixed 60 s runs, one-frame flash events counted from the frame log):

Build Flash events fps
this PR on current main, run 1 0 56.0
this PR on current main, run 2 0 53.2
same build, RX_VK_CAPTURE_WAIT=0 (no wait at all) 394 60.0
main + #523 with the frame wait, 4 runs 3, 1, 4, 0 48.4–56.2
main + #523 with this instead, 4 runs 0, 0, 0, 0 48.3–55.8

In-session A/B on main + #523 (same session, toggled): 165 → 0.7 flashes per 1000 frames, normal exposure.

Note on the readback refresh (e84d467, from #430). Without any wait, refreshing the readback pages from guest memory at each submission makes this flicker much worse here: 16.9 → 192.5 flashes per 1000 frames in an in-session A/B with no wait. With this PR's wait it makes no difference (0.7 vs 0.0 per 1000). That is probably why Windows players saw the flashing come back once the frame wait was off there (issue 574).

Not tested: Windows and AMD. The wait only triggers on pages with a write-back still in flight, so it should cost less than the frame wait there too, but I couldn't measure it.

This replaces #513's frame wait (each present waiting for its frame's GPU work), which is now on by default on Linux and Windows, with a wait only where the race actually is. The flicker stays gone and the replay can run a frame ahead of the GPU again. **What the frame wait was hiding.** The one-frame exposure flashes come from one page that OoT's GPU and CPU both write: the exposure meter. The GPU adds the frame's light into it; the CPU sets the sums back to 0 and fills in the rest. The CPU runs a frame ahead, so its capture of that page could be taken before the GPU's write-back of it had reached guest memory. The mirror sync then copied the page whole, with the GPU's words from the frame before, over the GPU's newer ones, and the exposure stepped for a frame. Waiting at every present closed that window, but for every frame and every page. **Change.** - `capture_make` checks the pages the CPU wrote against the submissions that write them back (the readback wait's table, `wb_pages`). When one of those submissions is still on the GPU, the copy waits for it (250 ms at most). Other captures don't wait. - `vk_timeline_completed` / `vk_timeline_wait` (bounded) in `vk_replay.cpp` for that. - The per-present wait is removed on all platforms. `RX_VK_WAIT_GPU=<frames>` still limits how far ahead the replay may run; unset means no wait, as before #513. - `RX_VK_CAPTURE_WAIT=0` turns the new wait off. About 1 capture in 6 waits, roughly 12 ms each, in Kokiri Forest. **Measured** (Linux, RTX 3090, Kokiri Forest, fixed 60 s runs, one-frame flash events counted from the frame log): | Build | Flash events | fps | |---|---|---| | this PR on current main, run 1 | 0 | 56.0 | | this PR on current main, run 2 | 0 | 53.2 | | same build, `RX_VK_CAPTURE_WAIT=0` (no wait at all) | 394 | 60.0 | | main + #523 with the frame wait, 4 runs | 3, 1, 4, 0 | 48.4–56.2 | | main + #523 with this instead, 4 runs | 0, 0, 0, 0 | 48.3–55.8 | In-session A/B on main + #523 (same session, toggled): 165 → 0.7 flashes per 1000 frames, normal exposure. **Note on the readback refresh (e84d467, from #430).** Without any wait, refreshing the readback pages from guest memory at each submission makes this flicker much worse here: 16.9 → 192.5 flashes per 1000 frames in an in-session A/B with no wait. With this PR's wait it makes no difference (0.7 vs 0.0 per 1000). That is probably why Windows players saw the flashing come back once the frame wait was off there (issue 574). **Not tested:** Windows and AMD. The wait only triggers on pages with a write-back still in flight, so it should cost less than the frame wait there too, but I couldn't measure it.
Commenting is not possible because the repository is archived.
No description provided.