perf(vk): eliminate rec_timeline write-back stalls and capture deadlock #2458
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2458
Loading…
Reference in a new issue
No description provided.
Delete branch "perf/vk-rec-timeline-sync"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
When
vk_timeline_wait(v)waits for a write-back fence valuevstamped on pages during burst recording execution, it submits a wait ondev.timeline(viaVkSemaphoreWaitInfo). If an earlier submission in the queue is currently stopped at an event wait or synchronization point,dev.timelinedoes not advance tov, resulting in a pipeline stall or capture deadlock and timing out after 250 ms.Additionally:
submit_recording()was recording sequence numbers usingstd::map::emplace(v, seq), which silently does nothing ifvwas already present from an earlier pass, leaving stale sequence numbers invalue_rec.wait_recorded()had a redundant check onper_queue_fences_off(), preventing timeline synchronization when per-queue fences were disabled even thoughrec_timelinewas initialized.Solution
vk_timeline_wait(), first querywait_recorded(v, 250000000ull, &ok)to wait onrec_timelinewhere the burst recording actually signalled completion. If the recording sequence is known, wait on that timeline semaphore directly and return early, bypassing stalls ondev.timeline.submit_recording(), use direct subscript assignmentvalue_rec[v] = seq;andvalue_rec[r.value] = seq;to ensure values are mapped to their most up-to-date recording sequence.wait_recorded(), remove theper_queue_fences_off()gate so validrec_timelinewaits are honored.Impact
The
value_recfix is already on main via #2419 (finished_inkeeps the latest part's sequence number), which is the conflict. At this head,test_draw_stream_visibility.pyfails because the test'smultimapstub has nooperator[](vk_replay.cpp:389). Also:per_queue_fences_off()gate (:2146) breaks the documentedRX_VK_PER_QUEUE_FENCES=0escape hatch.vk_timeline_waitthroughwait_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_waitchange with evidence, or close.Thanks for this. I merged main into the branch (
f426c25a). The only conflict was insubmit_recording: #2419 had already madevalue_reckeep each value's latest recording, so the merge keeps main's version and drops yourvalue_rec[v] = seqhunk. That also clears thetest_draw_stream_visibility.pyfailure @Joey-Magnus found. What's left is two hunks: thewait_recordedpath invk_timeline_wait, and the removed gate.Is it still needed? There's no deadlock to fix.
vk_timeline_waitalready gives up after 250 ms (ea78039, before this branch was cut). The case that's still left is a stall plus possibly stale data.finish_recordingonly signalstimelineup tooldest_unfinished() - 1. So while an older submission on another queue is stopped at an event wait, a capture waiting for a write-back whose recording has already gone to the GPU waits the full 250 ms, then copies its late pages anyway. Waiting on that recording's number onrec_timelineis the right fix. It's whatinvalidate's readback wait already does on main.rec_seqis handed out underreplay_muin submit order on the one queue,value_reckeeps the last part's number since #2419, andwait_recordedsends the held batch before it waits. So I think the change is correct.What it's missing is evidence that this happens. Joey asked for the same thing. Could you post a log from main showing
vk: a capture's wait for write-back ... timed out, or the late-wait time in take_capture's stats, and the same numbers with this branch? Without that it's a plausible fix for a case nobody has seen yet.Two more things:
per_queue_fences_off()gate: both existing callers (waitandinvalidate's readback wait) already check!per_queue_fences_off()themselves, so removing it fromwait_recordedchanges nothing for them. Its only effect is that the new capture path ignoresRX_VK_PER_QUEUE_FENCES=0. That's the part of Joey's point that still applies. I'd keep the gate and guard the new call the wayinvalidatedoes (see inline).tests/test_recorded_wait_flush.py:vk_timeline_waitcallswait_recordedbefore it waits ontimeline.Nits: the title and description still talk about a deadlock and the
value_recchange. Please update them for what's left after the merge. The new timeout logs the same text as the old one, so a log can't tell you which wait gave up.Related: #2222 (merged) bounds
invalidate's fallback wait. It doesn't touchvk_timeline_wait, so there's no overlap, and the two fit together.CI is still running on the merge. If the numbers show the 250 ms stalls on main and they go away here, with the gate kept, I'm happy to approve. If the timeout never shows up in practice, closing is fine too.
@ -279,0 +280,4 @@// (a burst recording whose write-back stamped wb_pages with burst_value does not advance `timeline` while an older queue// submission is stopped at an event wait; wait on `rec_timeline` where that recording actually signalled completion)int ok = 0;if (wait_recorded(v, 250000000ull, &ok)) {Suggest guarding this the way
invalidatedoes, soRX_VK_PER_QUEUE_FENCES=0(and async /RX_VK_SYNC_WAITS=0) still gives the old behaviour for A/B:(
async_on/sync_waits_on/per_queue_fences_offwould need forward declarations here, likewait_recorded.)With the call above guarded, this check can stay. The existing callers check
!per_queue_fences_off()themselves anyway, so removing it only changed the new caller.github-sync referenced this pull request2026-11-12 06:22:54 +00:00