Skip to content

perf(agc): store a DMA_DATA data fill larger than 64 KiB as a buffer fill - #5788

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
TideOPL:perf/dma-fill-on-gpu
Oct 14, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
TideOPL:perf/dma-fill-on-gpu

Conversation

@TideOPL

@TideOPL TideOPL commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Minecraft Dungeons II (PPSA16064) gameplay: about 9 DMA_DATA fills per frame on the compute queue each drained the device. With this change, p50 is 79.1–79.5 ms in every run; before, runs land at 82.7–83.0 ms when the title issues these fills during the capture window.

What

A DMA_DATA packet that fills memory with an immediate value (SRC_SEL 2) is resolved as a packet store, which is limited to 64 KiB. A larger fill is refused there, so the queue worker drains the device (submits and waits for all recorded work) and Execute fills the memory on the CPU. Minecraft Dungeons II issues about nine such fills per frame on queue 0x20. Every one waits for the GPU and then stores on the CPU, which costs about 9 ms of the compute worker's time per frame. Queue 0 waits for that worker once per frame, so most of the cost lands on the frame.

  • A fill the store path refuses now goes through the same path as the fill kernels the fill HLE takes: the storage-texture classification, clear and flush, VulkanDevice::FillBuffer into the host import, and the DCC key memo. The body of Driver::fillBuffer's store is moved, unchanged, into Driver::storeBufferFill so both callers share it.
  • Only a fill into memory (not GDS) whose address and size are 16-byte aligned and whose range is accessible takes this path. Anything else drains as before.
  • When FillBuffer cannot record the fill (no import, for example), the existing fallback waits for the device and fills on the CPU, as the drain did.
  • The HTILE and CMASK fill notes for DMA_DATA fills stay where they were, earlier in preparePacketMemory, so storeBufferFill skips its own copy of them for this caller.
  • New [sync] counter: DMA fills stored as buffer fills.

Tested

Windows 11, WinLibs GCC 15.2, Release.

  • New check testLargeDataFill in tests/Submit.cpp (agc_driver): a 256 KiB GPU copy into a buffer, then a 256 KiB DMA_DATA data fill of the same buffer, then a label. It requires every word to hold the fill value (the fill lands after the copy), no device drain, and one DMA fill stored as a buffer fill. Without the change it fails with a DMA_DATA fill larger than 64 KiB drained the device.

  • Suite: ctest -R "^agc" passes 500/500 (agc_driver_stencil_ref_export skipped, as on main).

  • Conventions: tools/check_conventions.py passes.

  • MCD2, Squid Coast spawn: the upstream performance trace, APS5_TRACE_SECONDS=25:15 from 25 s after the gameplay HUD appears, interleaved runs, RTX 5070, driver 610.88. Both sides are our test build (main plus open PRs MCD2 needs, perf(agc): record a DMA copy of up to 64 KiB on the GPU when the GPU still stores into its source #5480 among them); "after" adds this change.

    Run before p50 / p90 / p99 ms, presents after p50 / p90 / p99 ms, presents
    1 78.8 / 82.6 / 90.6, 189 79.5 / 82.6 / 91.9, 188
    2 82.7 / 86.0 / 97.0, 180 79.3 / 83.0 / 91.5, 188
    3 83.0 / 86.2 / 94.6, 180 79.1 / 82.7 / 92.9, 186
    4 — (crashed while loading, a title bug seen on every build) 79.5 / 83.8 / 91.1, 187
    • With the change, [sync] reports 0 device drains (before: every drain was a DMA_DATA fill), and queue 0x20's DMA_DATA time drops from 1,021 to 86 ms per 10 s.
    • In "before" runs where the title issues these fills during the window, queue 0 waits about 7 ms per frame for queue 0x20 (run 2 and 3); in run 1 it did not issue them and the wait was 2.4 ms, as in every "after" run.
  • Not run: Linux; AMD (the GPU tests ran on NVIDIA only); this exact head in the title (the game runs used our test build above).

Checklist

…fill

A DMA_DATA fill with an immediate value is resolved as a packet store,
which is limited to 64 KiB. A larger fill was refused there, so the
queue worker drained the device and Execute filled the memory on the
CPU. Such a fill now goes through the fill kernels' store path
(Driver::storeBufferFill, moved unchanged out of Driver::fillBuffer):
FillBuffer into the host import, with the same CPU fallback.
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#5480 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp L33
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/SynchronizationStatistics.cpp L24, L42
core/libs/prx/libSceAgcDriver/tests/Submit.cpp L855

@github-actions github-actions Bot added the agc libSceAgc* label Oct 14, 2026
@adriadam10 adriadam10 self-assigned this Oct 14, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on the PR merged onto current main: scoped build and ctest of libSceAgcDriver (544/545), agc_driver including testLargeDataFill passes. The only failure, agc_driver_tessellation, fails the same way on main. storeBufferFill is the old fillBuffer store moved unchanged, the 16-byte alignment, accessibility and non-GDS guards fall back to the drain, and the test pins the fill landing after the preceding GPU copy; merging.

@adriadam10
adriadam10 merged commit 0c2fcc2 into boykopovar:main Oct 14, 2026
9 checks passed
@Ondra97 Ondra97 mentioned this pull request Oct 14, 2026
11 tasks done
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.

2 participants