Skip to content

perf(agc): list a dispatch or draw as a copied writer only while it has a CPU write-back - #2792

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/agc-copied-writers-cpu-writeback
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/agc-copied-writers-cpu-writeback

Conversation

@alfalfa-star

@alfalfa-star alfalfa-star commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

What

A recorded dispatch or draw whose use needs completion work is listed as a copied writer (VulkanDevice copiedWriters, DrawCopiedWriters). Fills, CopyBuffer and indirect argument reads treat every write of a listed object as a store the CPU makes when its batch is reaped, and wait for that batch (SyncThrough).

Completion work is also needed for the BDA fault check alone (NeedsCompletion is bda != nullptr || HasCopiedWrites()). The written sub-ranges of GPU copies are copied back by the GPU in RecordCopyBacks, which MarkGpuWrites runs before the listing. Such an object has no CPU write-back (HasCopiedWrites() is false), and its stores are ordered by the barriers like direct writes, yet it was listed and its writes waited for. For draws, listed was decided before MarkGpuWrites, so a draw whose copied regions the GPU copies back was listed too.

Both lists now take an object only when HasCopiedWrites() holds after MarkGpuWrites. The completion action still runs, so the fault check is unchanged.

New execution test agc_driver_copied_writer_copy: it records a dispatch that reads through global (BDA) loads and stores through a bound buffer into registered memory, then calls VulkanDevice::CopyBuffer on that output before the batch runs. It checks that the copy is a transfer between the host imports (path 1) without a sync, and that the copied bytes match what the dispatch stored. Without this change it fails ("... waited for its batch, although no CPU write-back follows it"). It skips (77) without a Vulkan device or host imports.

Tested

Linux (Arch), RX 9070 XT, RADV, Release:

queue 0 GPU-mutex hold for copies presents
without ~3.5 s ~102
with this PR ~30 ms ~109

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

…as a CPU write-back

A recorded dispatch or draw whose use needs completion work goes into the
copied-writer lists (VulkanDevice's copiedWriters, DrawCopiedWriters).
Fills, buffer copies and indirect argument reads treat every write of a
listed object as a store the CPU makes when its batch is reaped, and wait
for that batch. Completion work is also needed for the BDA fault check
alone, and the written sub-ranges of GPU copies are copied back by the
GPU (RecordCopyBacks) before the listing. Such an object has no CPU
write-back (HasCopiedWrites is false), and its stores are ordered by the
barriers like direct writes, yet it was listed and waited for.

Both lists now take an object only when HasCopiedWrites holds after
MarkGpuWrites. The completion action still runs for the fault check.

agc_driver_copied_writer_copy records a dispatch that reads through
global (BDA) loads and stores through a bound buffer into registered
memory, then copies that output on the GPU (VulkanDevice::CopyBuffer)
before the batch ran. It checks that the copy is a transfer between the
host imports without a sync, and the copied bytes. Without the change the
copy waits for the dispatch's batch.

PPSA14632 (LEGO Horizon Adventures): every one of the DMA_DATA copy
waits counted over 75 s came from such writers. Queue 0's GPU-mutex hold
for copies drops from 3.5 s to 30 ms per 10 s, and presents rise from
about 102 to 109 per 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@popcoder565

Copy link
Copy Markdown
Contributor

Native Windows run, since no Windows results were posted. Setup: Windows 10 (19045), RTX 3060 Laptop (NVIDIA 616.92), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release. Head 127e0288, merged onto main 57f96fd in one build with 4 other new PRs.

  • This PR's test passes: agc_driver_copied_writer_copy.
  • Full ctest in that build: 711 of 711.

No title was run.

🤖 Addressed by Claude Code

MichalSy added a commit to MichalSy/AnyPS5 that referenced this pull request Oct 10, 2026
Adapt upstream PRs boykopovar#1315, boykopovar#2791 and boykopovar#2792 to the current descriptor ABI and synchronization paths. Bound immutable binding plans to 1024 entries while retaining live resource addresses and specialization identity.

Keep real CPU writebacks and fault completion active. Capture generation baselines only over settled guest bytes. Add focused cache, texture baseline and copy synchronization regressions.

@Dextroid17 Dextroid17 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.

Read against the consumers rather than the summary: the change is right, and there are three things I would want settled — none of them blocking.

The completion path is untouched, which is the claim that mattered. The new guard sits inside if (resources.NeedsCompletion()) — the block that registers the write-back — so the OnComplete action (erase from the list, then WriteBackBuffers) still runs for the same objects; only the listing changed. The BDA fault check therefore sees what it saw before, as the description says.

The two halves are consistent, and the ordering fix on the draw side is real. On the dispatch side MarkGpuWrites(recorder) already ran before the listing, so asking HasCopiedWrites() there is well-defined. On the draw side listed was decided before MarkGpuWrites, which is why that side needed the extra condition rather than a reordering to match.

The one consumer that is not a wait. Some of the places that read these lists (VulkanDevice.cpp:1618 and :1822) only decide whether to sync — waiting is a cost, and not paying it is the point of the change. The indirect-argument decision (std::any_of(... WritesOverlap(arguments, 12) ...)) is different in kind: it chooses whether the CPU reads the arguments or the GPU reads them in place. Dropping an entry there changes that choice, and what makes it safe — the GPU copies those regions back, so the barriers order them like direct writes — is the part I cannot see tested. The new case asserts the CopyBuffer path (a transfer between host imports with no sync) and that the copied bytes match, which is the sync consequence, not the argument-read one. It would help to say explicitly which of the two the change intends to affect, and whether a case for the other is easy to add.

A cross-PR note, since both are open at the same time: #2990 moves this same decision from the other side — it stops the look-ahead before an indirect dispatch whose arguments an earlier dispatch may write, precisely because once the CPU has read them it is too late. The two probably compose, but they are reasoning about the same CPU-read choice, and merging them with that in mind seems worth a sentence.

The cost this removes is already instrumented. The [indirect] report carries copied-writer lists scanned per decision: dispatch avg … (max …), draw avg … — writersScanned and maxWriters for the dispatch pair, drawWritersScanned for the draw average. A before/after on that figure would put a number on the win, which otherwise is argued rather than shown — and it is the honest way to see whether the conservatism is narrower than the change suggests.

Written with AI assistance.

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on the PR merged onto current main: agc_driver_copied_writer_copy passes on RADV and fails with the two source changes reverted; the libSceAgcDriver ctest scope is green. Confirmed by @popcoder565 on Windows (RTX 3060, ctest 711/711).

On the indirect-arguments point raised in review: the GPU copy-back in GuestBufferMemory.cpp ends with a barrier that includes VK_ACCESS_INDIRECT_COMMAND_READ_BIT, so an object with no CPU write-back stays ordered for a GPU read of the arguments. Merging.

@adriadam10
adriadam10 merged commit a28a79c into boykopovar:main Oct 10, 2026
15 of 16 checks passed
@alfalfa-star
alfalfa-star deleted the perf/agc-copied-writers-cpu-writeback branch October 11, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants