Skip to content

Vulkan: no one-frame fog/exposure flashes from OoT's queue ordering - #415

Open
hckrman101 wants to merge 1 commit into
NotRexed:mainfrom
hckrman101:flicker-queue-order
Open

hckrman101 wants to merge 1 commit into
NotRexed:mainfrom
hckrman101:flicker-queue-order

Conversation

@hckrman101

@hckrman101 hckrman101 commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes most of the remaining one-frame flashes on Linux (#371): haze and sun shafts appearing or vanishing for one frame, with the environment brighter or darker while the HUD and characters stay the same.

OoT's presentation queues submit a frame's work before two things it waits for are ready: the scene work (through event waits) and the CPU's writes for that frame (the CPU signals an event when it has finished). Two paths replayed that work against the previous frame's data.

Forced waits. Every NVN queue shared one fence timeline, and it only advanced as far as the oldest submission stopped at a wait. Three things waited on that timeline and pushed the presentation queues' stopped work past its waits:

Fences are now per queue: nvnQueueFenceSync takes the queue's own last submission. They are waited on through a second timeline that numbers the recordings. The readbacks no longer force anything through. RX_VK_PER_QUEUE_FENCES=0 restores the old behaviour.

Stale captures. The memory capture was taken at submit time, so it held the previous frame's data. Now a submission stopped at a wait for a CPU-signalled event takes a fresh capture of the CPU's writes when it resumes, because the console's GPU reads memory after its wait. RX_VK_RESUME_CAPTURE=0 turns this off.

Measured on Linux with an RTX 3090: Kokiri Forest save, standing still, 60 s frame log on a 75 Hz monitor, one-frame events counted with scripts/framelog.py.

Build Events Frames fps
Before (ba594da) 26 2900 ~48
After 10 3586 ~60

The frame rate goes up because the main thread no longer blocks on the other queues' stopped work.

Diagnosis:

  • 144 of 150 events followed a forced drain.
  • Once forcing was removed, 87 of 92 events followed a stop at the CPU event's wait.

Known gaps:

  • The 10 remaining events aren't explained yet.
  • A fence taken on a queue that is itself stopped still falls back to the old forced path.

The branch is rebased onto current main (after #416). The test now checks take_capture(), which both capture paths share, for cap_view_bump(). The numbers above were measured on ba594da.

OoT's presentation queues submit their frame's work before the scene work it
waits for (event waits) and before the CPU has finished writing that frame's
data (it signals a CPU event when done). Two things replayed that work from the
wrong frame, and the picture flashed for one frame (haze and sun shafts of the
frame before: 'the fog flickers in', environment brighter, characters not):

- Forced waits. Every NVN queue shared one fence timeline, signalled only as
  far as no older submission is stopped at a wait. A fence taken on the main
  queue, the exposure readback's catch-up (EvilOOT's write-back wait) and the
  texture readback's catch-up all waited for the presentation queues' stopped
  work and forced it past its waits, ahead of the scene work.
  Now fences are per queue (nvnQueueFenceSync takes the queue's own last
  submission, rx_nvn_fence_q) and are waited for on a second timeline that
  numbers the recordings; the readbacks wait there too and only let on what
  may go (sync_settle), never forcing. RX_VK_PER_QUEUE_FENCES=0: as before.

- Stale captures. A submission stopped at a wait for an event the CPU signals
  goes on with a fresh capture of the CPU's writes (memory_capture_now), as the
  console's GPU reads memory after its wait; the capture taken at the submit
  had the frame before's data. RX_VK_RESUME_CAPTURE=0: none.

Kokiri Forest 2:32 save, standing, 60 s frame log on a 75 Hz monitor
(scripts/framelog.py): see the PR for the counts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant