Skip to content

perf(agc): record DMA_DATA copies to and from the GDS on the GPU in queue order - #2156

Closed
orbz22 wants to merge 1 commit into
boykopovar:mainfrom
orbz22:perf/agc-gds-dma-gpu-copy
Closed

orbz22 wants to merge 1 commit into
boykopovar:mainfrom
orbz22:perf/agc-gds-dma-gpu-copy

Conversation

@orbz22

@orbz22 orbz22 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

What

Small DMA_DATA copies between the GDS and memory either drained the device or waited for the GPU:

  • A copy into the GDS (from memory or from the GDS) drained the device. ResolveStore refused a GDS destination, so the packet fell through to the drain and the CPU copy.
  • A copy from the GDS into memory resolved as a store. Its source was read through the checked read path, which waits for the recorded GPU work that writes the GDS. The bytes were then recorded as a label store.

Titles move GDS counters in and out of memory with such packets between dispatches (append/consume counters, ordered counts). Each one cost a drain or a wait on the queue's worker.

This change:

  • Pm4::DecodeGdsCopy recognises a DMA_DATA with the GDS on at least one side and memory or the GDS on the other. It requires:
    • incrementing addresses, no register operand, a non-empty range;
    • GDS offsets inside the GDS, with the high address dword zero;
    • disjoint ranges.
      The GDS side maps to GdsAddress() + offset, the guest memory that backs the GDS here.
  • preparePacketMemory records such a copy with VulkanDevice::CopyMemoryOnGpu: a vkCmdCopyBuffer between the two host imports, in the open batch, in queue order.
    • It flushes pending storage results over both ranges, publishes unit shadows, and lands queued label and DCC key stores over both ranges first.
    • Barriers: everything recorded before (and host stores) precedes the transfer, and the copied bytes are visible to everything recorded after and to the host. These are the access masks CopyBuffer uses.
    • It notes a pending read of the source and a pending write of the destination, and stamps the destination written.
    • It refuses, and the packet falls back as before, when the recorder is idle, when either range is not imported, or when a completion store can still land over either range: a listed copied writer, or a label deferred behind completions. This is the check DumpSamplesOnGpu already uses.
  • ResolveStore now accepts a GDS destination for an immediate source (a fill of GDS counters) inside the GDS. It goes through the label store path like any other immediate DMA_DATA.

Why not CopyBuffer (the path #439 uses for large memory-to-memory copies): its completion-store test counts any pending label in a range as a completion store whenever completion labels exist anywhere. GDS ranges that labels write on the GPU then wait for their batch (SyncThrough). In PPSA01325 that took 2919 syncs in one run, and queue 0x3d still spent ~1.75 s per 10 s in DMA_DATA (numbers below). Changing that test would change every buffer copy, so it is left alone here.

The selector values come from DMA_DATA's control dword: SRC_SEL bits 30:29 and DST_SEL bits 21:20 (0 memory, 1 GDS, 2 immediate data for the source, 3 memory through L2). Pm4.cpp already decodes them (dmaSource, dmaDestination, DmaSelectGds). They match S_411_SRC_SEL/V_411_GDS/V_411_DATA/V_411_SRC_ADDR_TC_L2 and S_411_DST_SEL/V_411_DST_ADDR_TC_L2 in Mesa's src/amd/common/sid.h.

Tested

Windows 11, WinLibs GCC 15.2, RTX 3060 Laptop.

  • New agc_driver_gds_dma_copy_tests (tests/execution/GdsDmaCopy.cpp), run twice:

    1. Immediate DMA_DATA fills set four GDS counters.
    2. A dispatch appends and consumes them with ds_append/ds_consume (full and partial EXEC).
    3. A DMA_DATA copies the counters from the GDS into memory.
    4. An empty dispatch runs, then the memory is copied into another GDS range and that range out to memory again.

    Both memory results must hold the expected counters. The memory side is a registered, host-imported guest block.

    • The first copy falls back: the dispatch's GDS is a copied writer.
    • The last two copies take CopyMemoryOnGpu. With its vkCmdCopyBuffer removed, the test fails: GDS counter 0 copied to memory, back to GDS and out again reads 3735928559, expected 1064.
  • agc_driver_pm4_tests has a new testGdsCopyDecode:

    • These decode: GDS to memory, memory to GDS, GDS to GDS, and an L2 memory source.
    • These do not: immediate fills, memory to memory, a range past the GDS end, high GDS address bits, register operands, non-incrementing addresses, overlapping GDS ranges, and an empty copy.

    Two ResolveStore checks: an immediate GDS fill resolves to GdsAddress() + offset; one past the GDS end does not.

  • Full ctest on current main with this change: 494/495 pass. import_audit fails in my git worktree (source_commit is None because git cannot resolve the worktree's commit), which this change does not touch. tools/check_conventions.py --base upstream/main passes.

  • PPSA01325 (Astro's Playroom), title screen, 150 s runs. I measured on my local branch, since current main stops at boot on missing static shader ABI register 201 (fix(agc): defer shaders that cannot be prepared at registration to their first use #1836). The same change ran there behind a switch. Per 10 s:

    before after
    queue 0x3d (async compute) time in DMA_DATA 2149-4073 ms (~1900-4000 packets) 56-66 ms (~1900 packets)
    queue 0 time in DMA_DATA 398-623 ms 95-106 ms
    checked-read waits for pending GPU writes 401-584, waiting 2.3-5.2 s none reported
    GDS copies recorded / fallen back (whole run) — 25000 / 0

    The CopyBuffer variant of the same change gave 1691-1787 ms on queue 0x3d. The title's frame rate stays ~13.7 FPS: it is bound by queue 0's draws, not by these waits.

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 (Claude Code)

🤖 Generated with Claude Code

https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV

…ueue order

A DMA_DATA copying into the GDS drained the device, and one copying out of
the GDS read its source through the checked path, waiting for the recorded
GPU work that writes the GDS. Pm4::DecodeGdsCopy recognises copies with the
GDS on either side; preparePacketMemory records them with
VulkanDevice::CopyMemoryOnGpu, a transfer between the host imports in the
open batch, and falls back as before when a completion store is pending over
either range or a range is not imported. ResolveStore also accepts an
immediate fill of the GDS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: the DMA_DATA time goes down, but the frame rate in PPSA01325 stays at ~13.7 fps, and the branch now conflicts with main in Pm4.cpp. Reopen if a title shows a frame-time gain from it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants