Repository navigation
fix(agc): run a wait-free submission of another queue after queue 0's earlier submissions - #465
Merged
oneandonlydean merged 1 commit intoOct 4, 2026
Conversation
… earlier submissions PPSA21564 submits the queue 0 work of each intro video frame F first, then 1 to 5 ms later a 12 MiB DMA_DATA on queue 0x3a that refills the video texture F sampled, for frame F + 2 (two textures, used in turn). Nothing orders them but time: no label, wait or flip lies between the copy and the draw, which is the first packet of the frame's first submission. A GPU that keeps up starts each submission as it arrives, so the draw reads its texture right after the frame is submitted, and the copy submitted after it lands later, every frame. Here the queue workers record independently, so the copy was recorded either before or after the draw depending on how far queue 0's worker lagged: the video showed the new frame or the one before, and every switch between the two was a frame shown backwards. A submission with no waits on another queue now starts only after queue 0 has run every submission it received earlier, as on a GPU that keeps up. While queue 0 is blocked in a wait, the held submission is released when its queue stores the awaited label (it or a later submission of that queue), or when nothing else could store it: no other worker is running and no recorded GPU work or completion writes it. A GPU that keeps up would be blocked at that wait too and run the other queue's submission meanwhile, so the order holds only while queue 0 still has work it would already have done, and no wait can deadlock on it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A submission with no waits on a compute queue now starts only after queue 0 has run every submission it received before it.
The problem in PPSA21564's intro video
DMA_DATAon queue 0x3a. That copy refills the video texture frame F sampled, for frame F + 2; the two textures are used in turn.On a GPU that keeps up, each submission starts as it arrives. The draw reads its texture right after the frame is submitted, and the copy submitted after it lands later, every frame, so the video plays with a steady two-frame latency.
Here each queue's worker records on its own. The copy was recorded before or after the draw depending on how far queue 0's worker lagged behind the title. The video showed either the newest frame or the one before, and every switch between the two showed a frame backwards. That happened 24 to 36 times per 45 s of intro, counting every frame, and more often the faster the emulator runs.
The rule
WAIT_REG_MEM/WAIT_REG_MEM_64, no flip, no rendering wait and no rewind.Why this is the right model, and where it stops
The hardware gives no formal ordering between queues. Two queues run concurrently, and nothing but timing places queue 0x3a's copy after queue 0's draw. This PR does not claim an ordering the hardware guarantees. It reproduces the one a title can rely on in practice: the GPU drains each submission promptly, so work submitted earlier on queue 0 has started, and here finished, before an unrelated submission made later on another queue runs. A wait-free submission is the case where that timing is the only ordering; a submission with waits is already ordered by its waits and is never held.
The emulator's queue 0 can lag behind the title by much more than a GPU would. The hold makes the other queue wait for the work queue 0 would already have done. It stops where a GPU that keeps up would itself be stuck: while queue 0 is blocked in a wait, the held submission is released
So the order holds only while queue 0 still has work a faster GPU would already have done. No wait, on the GPU or on the CPU, can deadlock on it. The rewind tail's wait on its control word counts as a blocking wait too.
Costs
Tested
Linux (CachyOS, x86-64): full build with
BUILD_TESTING=ONandctestpass (145/145).Windows (MinGW, CI on my fork): build and
ctestpass.New tests in
agc_driver(tests/Submit.cpp):testWaitFreeSubmissionAfterEarlierQueue0Work: queue 0 gets 20000WRITE_DATApackets, then queue 0x20 gets one wait-freeWRITE_DATA; when the second lands, the first must be complete. Without the hold it fails with "a wait-free submission ran before queue 0's earlier submission finished".testWaitFreeSubmissionQueue0WaitsOn: queue 0 waits on a label that the held submission stores; it passes the wait at once.testWaitFreeSubmissionBehindHeldOne: the label comes from a second submission of the held queue, behind the held one; queue 0 passes at once, and the held one ran first.testWaitFreeSubmissionTheCpuWaitsFor: queue 0 waits on a flag the CPU sets only after the held submission has run; queue 0 passes well inside the 1 s wait timeout. With only the first release rule (the queue stores the awaited label) it fails with "a held submission the CPU waits for was not released while queue 0 waited on the CPU".PPSA21564: dense checks of the intro video (45 s, every frame kept) on my integration branch:
Those runs used the hold with the first release rule only (the held submission stores the awaited label or copies into it). The second rule, for anything else nothing could store, was added for this PR, after reviewing the deadlock cases above. With both rules, as in this branch, three more runs keeping every frame of the intro video had 0, 0 and 0 backwards steps and no torn or stale frames, two runs of my visual regression check passed, and the guest never hit the 1 s WAIT_REG_MEM give-up in a gate, a level or the world map.
Overlap: my fix(agc): record large memory-to-memory DMA_DATA copies on the GPU in queue order #439 records large
DMA_DATAcopies on the GPU in queue order. It touches the copy itself, not when a submission starts, so the two are independent and merge cleanly. Without fix(agc): record large memory-to-memory DMA_DATA copies on the GPU in queue order #439 the copy runs on the CPU at the 0x3a worker's turn, and the race is the same. No open PR touches the queue workers' start order. feat(agc): host import windows, least-recent eviction under the budget #290, perf(agc): draw cache keys on the user words a program reads, memoizes failures, serves data-only hits #291, perf(agc): memoize source failures of draw stages as for compute #422 and GTA V: the prologue plays (testing branch of the PRs it needs) #426 editDriver.hpporSubmission.hppand merge cleanly.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository (attach them to this PR)