Skip to content

perf(vk): eliminate rec_timeline write-back stalls and capture deadlock - #2458

Merged
NotRexed merged 2 commits into
NotRexed:mainfrom
Erfboom:perf/vk-rec-timeline-sync
Nov 12, 2026
Merged

NotRexed merged 2 commits into
NotRexed:mainfrom
Erfboom:perf/vk-rec-timeline-sync

Conversation

@Erfboom

@Erfboom Erfboom commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Problem

When vk_timeline_wait(v) waits for a write-back fence value v stamped on pages during burst recording execution, it submits a wait on dev.timeline (via VkSemaphoreWaitInfo). If an earlier submission in the queue is currently stopped at an event wait or synchronization point, dev.timeline does not advance to v, resulting in a pipeline stall or capture deadlock and timing out after 250 ms.

Additionally:

  • submit_recording() was recording sequence numbers using std::map::emplace(v, seq), which silently does nothing if v was already present from an earlier pass, leaving stale sequence numbers in value_rec.
  • wait_recorded() had a redundant check on per_queue_fences_off(), preventing timeline synchronization when per-queue fences were disabled even though rec_timeline was initialized.

Solution

  1. In vk_timeline_wait(), first query wait_recorded(v, 250000000ull, &ok) to wait on rec_timeline where the burst recording actually signalled completion. If the recording sequence is known, wait on that timeline semaphore directly and return early, bypassing stalls on dev.timeline.
  2. In submit_recording(), use direct subscript assignment value_rec[v] = seq; and value_rec[r.value] = seq; to ensure values are mapped to their most up-to-date recording sequence.
  3. In wait_recorded(), remove the per_queue_fences_off() gate so valid rec_timeline waits are honored.

Impact

  • Eliminates write-back timeline stalls and capture deadlocks during intensive burst recordings.
  • Prevents 250ms timeouts in GPU replay timeline synchronization.

When vk_timeline_wait waits on a write-back fence value produced by burst
recordings, waiting on dev.timeline deadlocks if an earlier queue submission
is stopped at an event wait. Wait on rec_timeline via wait_recorded() where
the burst recording actually signalled completion. Additionally, replace
std::map::emplace with direct subscript assignment in submit_recording to
ensure burst values map to their latest sequence number, and remove the
redundant per_queue_fences_off() gate in wait_recorded().

Co-Authored-By: erfboom <gabrielh.ltd@gmail.com>

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value_rec fix is already on main via #2419 (finished_in keeps the latest part's sequence number), which is the conflict. At this head, test_draw_stream_visibility.py fails because the test's multimap stub has no operator[] (vk_replay.cpp:389). Also:

  • Removing the per_queue_fences_off() gate (:2146) breaks the documented RX_VK_PER_QUEUE_FENCES=0 escape hatch.
  • Routing vk_timeline_wait through wait_recorded (:273) changes how capture write-backs wait on game threads, with no log or repro of the deadlock.

I'd rebase down to the vk_timeline_wait change with evidence, or close.

vk_replay.cpp: main (NotRexed#2419) already keeps each value's latest recording in
value_rec; keep main's version of that hunk.
@NotRexed
NotRexed merged commit 128c32e into NotRexed:main Nov 12, 2026
11 of 13 checks passed
notquiteog added a commit to notquiteog/Hookshot that referenced this pull request Nov 12, 2026
…s behind them

Upstream's main fails 15 of its own source checks at c356bef (PRs merged without a
rebase). These are the eleven outside the upscalers:

- vk_memory.cpp: RX_VK_CAPTURE_KEEP_OLD and RX_VK_CAP_BLOCKS are on/off switches and
  were read as set-means-on, so =0 turned them on (NotRexed#2437). env_on, as the rest.
- mouse_native.c: the mouse's kept angles are let go when the camera leaves states 3
  and 4 (first-person aim, Z-targeting): they were written back on return, and the
  view snapped to where it was before (NotRexed#2617; test_mouse_native's yaw).
- menu_i18n.h: three English texts changed in vk_menu.cpp (NotRexed#1930, NotRexed#2362, NotRexed#2435), so
  their 13 translations are keyed and translated again (machine-made, as the file's
  others).
- Fixtures that cut source by its text and missed what the source gained:
  window_title (NotRexed#2116), dead_import_bytes (NotRexed#1906), env_on in reverb_frame,
  hos_xinput_pads (NotRexed#2435), wait_recorded's forward declaration (NotRexed#2458); and the
  Dockerfile check's parser, which took --no-update for a PPA.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
dazemc added a commit to dazemc/Hookshot-rs that referenced this pull request Nov 12, 2026
… queue

Port upstream 128c32e (NotRexed#2458). vk_timeline_wait now asks wait_recorded
first: a burst recording whose write-back stamped pages does not advance
the main timeline while an older queue submission is stopped at an event
wait, so waiting there stalled the capture until the 250 ms timeout. It
waits on rec_timeline, where that recording signalled completion, and
logs a timeout as before. wait_recorded no longer refuses when
RX_VK_PER_QUEUE_FENCES=0: rec_timeline is initialized either way.

The value-to-recording map already keeps the newest part's sequence
(rec_value.h), so the stale-emplace part of the upstream fix is present.
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.

3 participants