Skip to content

fix(vk): let a catch-up go on only past met waits, not every stopped queue - #449

Merged
NotRexed merged 1 commit into
NotRexed:mainfrom
azlekayn:fix/catchup-stopped-queues
Nov 8, 2026
Merged

NotRexed merged 1 commit into
NotRexed:mainfrom
azlekayn:fix/catchup-stopped-queues

Conversation

@azlekayn

@azlekayn azlekayn commented Nov 8, 2026

Copy link
Copy Markdown

What this fixes

One-frame flashes in Kokiri Forest once the game runs at speed. The scene goes black, grey or blown out for a frame, with the HUD and Navi still drawn on top. It often alternates with normal frames for several seconds. This may be what #145, #305 and #307 describe.

Why

In sync mode, wait_queue_empty() calls sync_drain(2), which forces every queue stopped at an event wait past its wait. The callers are a queue finish, an invalidate that reads back GPU-written data, and a pool's mapping changing.

The work those waits are for is mostly not submitted yet, because the thread that's waiting is the one that would submit it. As a test, holding the waiter for up to 20 ms let only 32 of about 3,600 stopped queues go on by themselves. Forced past its wait, the stopped submission's lit passes read results before they're made.

A frame probe (RX_VK_PROBE=2) of the dark frames showed:

  • the same ~120 draws into the 1536x864 scene target produced 0;
  • that frame's 512x512 compute dispatches were recorded before the passes instead of after them.

At low frame rates the other queue's work had always arrived first, so this only shows once the game runs fast. For us that was after the udmabuf change.

Change

gpu/vk/vk_replay.cpp: in sync mode, a catch-up now only schedule()s what can go on, meaning waits that are met. Stopped work still goes on, as before, with:

  • its own queue's later work;
  • a present;
  • sync_watch's hold_ms.

RX_VK_CATCHUP_FORCE=1 forces, as before.

This complements #430, which does the same for releases (drain 1).

Tested

  • Method: a frame log (RX_VK_FRAMELOG) of 70 s of Kokiri gameplay, counting frames that differ from both neighbours but match the frame two back.
  • Hardware: RX 7800 XT, RADV, Linux.
  • Results:
waits gone past forced by "waits for the replay" flashing frames
before 10,000-40,000 600-2,900 250-1,100 (4 runs)
after 264-378 0 65-113 (3 runs)
  • Frame rate: 60 fps either way.
  • Stability: no hangs, stalls or device loss. A finish() that needs stopped work waits on the timeline until sync_watch moves it, but that never showed up as lost frames.
  • Remaining flashes: they line up with the presents' drain(0), 114-362 per run. AMD (RX 6600) fixes for Ocarina of Time: GPU hang, fog flicker, exposure #430's readback refresh may cover what remains.
  • Builds measured: the foliage-linux-updater release with this patch.
  • Against main: rebased onto current main, where hookshot_vk builds. I have not run a full main build.
  • Not tested: Windows, NVIDIA.

Analysis and patch made with Claude (Anthropic); I ran every test on my machine.

🤖 Generated with Claude Code

…queue

wait_queue_empty in sync mode (a queue finish, an invalidate that reads back what the GPU wrote, a pool's mapping changing)
called sync_drain(2), which forces every queue stopped at an event wait past it. The work those waits are for is mostly not
submitted yet: the thread waiting is the one that submits it (holding the waiter 20 ms let only 32 of ~3600 go on by
themselves). Forced past, the stopped submission's lit passes read what that work makes before it was made, and the scene
came out black, grey or blown out for a frame (HUD and Navi on top), every other frame for seconds in Kokiri Forest. At low
frame rates the other queue's work had always arrived first, so this shows once the game runs at speed.

A frame probe (RX_VK_PROBE=2) of the dark frames: the same ~120 draws into the 1536x864 scene target came out 0, with the
512x512 compute dispatches recorded before the passes instead of after them.

Now a catch-up only schedules what can go on; the rest goes on with its own queue's later work, a present, or sync_watch's
hold_ms, as before. RX_VK_CATCHUP_FORCE=1: forced, as before. Complements NotRexed#430, which does the same for releases (drain 1).

Measured over 70 s of Kokiri gameplay (frame log, RX 7800 XT, RADV, foliage-linux-updater release + this):
  before: 10,000-40,000 waits gone past, 600-2,900 forced by "waits for the replay", 250-1,100 frames flashing
  after:  264-378 waits gone past, 0 forced by catch-ups, 65-113 frames flashing (3 runs); 60 fps, no hangs or device loss
The flashes left line up with the presents' drain(0) (114-362 a run); NotRexed#430's readback refresh may be what remains.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@felipecsl

Copy link
Copy Markdown

Tested on Intel Arc B390 (Core Ultra X7 358H iGPU), Linux, Mesa 26.2.5 ANV, kernel 7.2.15, Hyprland (Wayland). This fixes the flashing here.

Setup: release no-visual-studio (e40306e) built with the Linux AppImage. I applied this PR's diff to e40306e and rebuilt only hookshot_vk.so (RX_VK_ONLY=1 RX_RELEASE=1 nx2/build.py --standalone, the AppImage's clang, headers at the versions deps.sh pins), then swapped it into the game folder. Test: Kokiri Forest save, standing still outside Link's house, 30 s RX_VK_FRAMELOG, counted with scripts/framelog.py. Default settings otherwise.

e40306e release e40306e + this PR
framelog.py events 1481 in 1020 frames 5 in 823 frames (all small, first 3 s)
Waits gone past (per 6000 stopped) ~4100 533
Made to go by "waits for the replay" hundreds 0
fps ~34-35 ~27
  • Before, the flashing alternated every other frame: steady frames at mean luma ~123, the frames between them 118-141. They were the whole scene brighter or hazier (about 128 of 144 blocks), with the HUD unchanged. This is the pattern described here.
  • With the patch, the "normal" frames also look different: the white/milky haze over Kokiri is mostly gone and the scene has proper contrast (mean luma 126 -> 75 at the same spot). Some of the white haze reported for this same chip in Intel GPU Issues #17 may be this bug.
  • fps dropped from ~35 to ~27 on this iGPU, unlike the 60 fps either way on the RX 7800 XT. I did not run a control with my own build of hookshot_vk.so without the patch, so part of that difference could come from my build, not the PR.

Thanks for the fix!

@pr0vId3nce

Copy link
Copy Markdown

Tested on an RTX 4080 / Windows 11 / Ryzen 9 7900X: main 4cfa08f with this PR merged, warm caches, Kokiri Forest standing still, 60 s RX_VK_FRAMELOG per run, windowed 1280x720, frame generation off. One run per build, plus two runs of plain main.

Build fps White frames Small flashes/min Device lost
main (2 runs) 37.2 / 40.5 0 3.0 / 4.0 0
main + #449 37.7 0 0 0

No regression in fps, no white frames or device lost; 0 flashes in this run. Plain main barely flickers on this card, so this can't confirm the fix, only that it does no harm here. waits gone past dropped from 403-553 on main to 147.

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.

4 participants