Skip to content

feat(agc): import shared ranges whose backing offset is not 64 KiB aligned - #5390

Merged
oneandonlydean merged 3 commits into
boykopovar:mainfrom
hugodearaujo:feat/agc-sparse-unaligned-import
Oct 18, 2026
Merged

oneandonlydean merged 3 commits into
boykopovar:mainfrom
hugodearaujo:feat/agc-sparse-unaligned-import

Conversation

@hugodearaujo

@hugodearaujo hugodearaujo commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

Follow-up to #5309. A sparse buffer binds whole blocks (64 KiB on RADV), so #5309 takes the sparse path only for a shared range whose offset in the memfd is a multiple of the block size, and decides that before importing. Any other range larger than 64 MiB still goes to one UDMABUF_CREATE, which udmabuf refuses above its default size_limit_mb of 64, and is copied. ASTRO BOT (PPSA21567) has several such ranges with #5309, including a heap that is made again with each new allocation:

[gpu] host import of 0x3018f8000+0x11d2c000 failed at UDMABUF_CREATE (-1000072003); falling back to copies
[gpu] host import of 0x324608000+0x599f8000 failed at UDMABUF_CREATE (-1000072003); falling back to copies
[gpu] host import of 0x336244000+0x47dbc000 failed at UDMABUF_CREATE (-1000072003); falling back to copies

Each starts on a 16 KiB page, not on a 64 KiB block.

This PR starts the sparse buffer at the 64 KiB block below the range, inside #5309's decision:

  • createSparseDmaBufImport takes lead = offset % 64 KiB off the backing offset before deciding, and sizes the sparse buffer lead + bytes. The decision is otherwise feat(agc): import shared ranges above 64 MiB as sparse buffers over udmabuf chunks #5309's: a device whose sparse block size is larger than 64 KiB still declines before anything is imported, and failures after the decision still throw;
  • HostImport::lead is the distance from the start of the buffer to base (0 for every other import);
  • entry.address is the device address of base itself, so address-based (BDA) users are unchanged;
  • every offset into an import's buffer goes through a new HostImportOffset(import, address) (address - base + lead), instead of address - import->base: fills, copies, label stores, indirect arguments and counts, index and vertex inputs, detile offsets, the storage write-back's copies into each import segment, unit-shadow sources and seeds, DCC key stores, the in-place view binding and its copySourceBase;
  • a unit shadow keeps its import's lead with the import's base and buffer, and publishes its units at begin - importBase + importLead.

The leading bytes of the buffer belong to the memfd but are never addressed.

Measured

ASTRO BOT, RX 9070 XT (RADV), size_limit_mb left at its default of 64, 60 s from launch with no input, APS5_TRACE_HOST_IMPORT=1:

main (4224d240a), 1 run this PR (107538d89 and ad572ebb1, 2 runs each)
ranges above 64 MiB imported 6 15–16
ranges above 64 MiB falling back to copies (UDMABUF_CREATE) 20 (8 distinct ranges, one remade 13 times) 0

Measured earlier on the title screen against #5309's first head, per 10 s: image mirror refreshes dropped from 26,049 (914,912 blocks copied) to 6,623 (262,595), and guest memory reads from 9.4 GB to 3.2 GB with APS5_HOST_IMPORT_MIB=16384. The frame rate did not change (~20 FPS): the title screen is bound by GPU time and draw recording, not by these copies.

Tested

  • Linux: full CTest passes except agc_pending_key_stores and agc_driver_tessellation, which fail the same way on main (4224d240a), and agc_host_import_sparse, which needs test(agc): drop a shared range's backing when the range goes away #5759 (merged since; below). agc_host_import_sparse changes its unaligned case: a range 4 KiB into the memfd is now imported, with lead 4 KiB, the device address of the range itself, and its first bind at the block below it. A new case keeps the decision check: with a mocked sparse block of 128 KiB, a range 64 KiB into the memfd imports and binds no chunk. agc_driver_sparse_dma_buf_import registers a range 16 KiB into the memfd, larger than the current size_limit_mb, and fills 64 KiB across the first chunk boundary through the import by guest address, checking the backing memory at that address and the bytes on both sides. It then publishes a unit shadow of that import and checks the unit's guest bytes and the 16 KiB on each side; without the lead in the publish it fails there.
  • A device that imports shared memory through host pointers, such as lavapipe, imports the unaligned range that way, and a host-pointer import has no lead. agc_driver_sparse_dma_buf_import therefore checks the lead only for a dma-buf import (d7018a5, after @nicolasfalesy's report). On lavapipe (Mesa 26.2.4, /dev/udmabuf writable) it failed 2 of 2 runs before that commit and passes 3 of 3 after it, with a note that the import went through a host pointer; on RADV it passes 3 of 3 with the lead checked. The 21 tests matching ^agc.*(import|dma|sparse|shadow|unit) give the same result on both devices: 19 pass, agc_shadowed_key_stores skips, and agc_host_import_sparse fails without test(agc): drop a shared range's backing when the range goes away #5759.
  • On this branch's base (4224d240a), the test's SharedRange leaves dangling pointers in backings, which can make any case after the first resolve the wrong backing. test(agc): drop a shared range's backing when the range goes away #5759 fixes that and is merged now. With its change applied to this branch, agc_host_import_sparse passed 8 of 8 runs before, and 4 of 4 on the current head, both on RADV and under lavapipe (the test mocks the device).
  • Windows Server 2025 VM, WinLibs GCC 15.2, CI configure flags: the build passes, and CTest fails audio_out2_shutdown, audio_out_shutdown and guest_kqueue (0xc0000409) out of 1288, the same three as main (4224d240a) on the same VM. The GPU tests skip in this VM, and the sparse tests skip there since udmabuf imports are Linux only.
  • Hollow Knight: Silksong (PPSA12544), where every import has lead 0, runs as before. Hades (PPSA03355) stops at ~9 s as on main (fix(agc): settle dma-buf imports on Linux on the settle queue #5905).

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 (test(agc): drop a shared range's backing when the range goes away #5759, which agc_host_import_sparse needs, is merged)
  • AI-assisted: yes

🤖 Generated with Claude Code

@oneandonlydean

Copy link
Copy Markdown
Collaborator

#5309 is in, and it changes the path this PR builds on: the sparse path is now decided before any import, and failures after that throw instead of falling back. Could you rebase and redo the unaligned-offset handling on top of it? The old 00053ab commit no longer fits.

@hugodearaujo
hugodearaujo force-pushed the feat/agc-sparse-unaligned-import branch 2 times, most recently from 7254bfd to 353e68d Compare October 14, 2026 09:52
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@hugodearaujo
hugodearaujo force-pushed the feat/agc-sparse-unaligned-import branch from 353e68d to d4fa836 Compare October 14, 2026 10:03
@hugodearaujo

Copy link
Copy Markdown
Contributor Author

Rebased onto main and redone on top of #5309's decision. createSparseDmaBufImport takes the 64 KiB lead off the backing offset before deciding and sizes the sparse buffer lead + bytes. The rest of the decision and the throws after it are unchanged. In agc_host_import_sparse, the unaligned case now expects the import (lead, device address of the range, first bind at the block below). A new case keeps the decide-before-importing check with a mocked 128 KiB sparse block. ASTRO BOT at the default size_limit_mb: 23 fallbacks to copies with #5309 alone, none with this PR. The test fixture needs #5759, so it is listed under Depends on. Description updated.

@hugodearaujo

Copy link
Copy Markdown
Contributor Author

Rebased onto main (4224d240a) again, in two commits:

  • 107538d is the earlier commit on the new main. Texture.cpp conflicted: main now writes a storage image back through each import segment it spans, and the new copy offset into a segment's import (begin - segment.import->base) goes through HostImportOffset as well.
  • ad572eb fixes an offset the earlier head missed. Checking every offset into an import's buffer again, I found that a unit shadow publishes its units at begin - shadow->importBase, without the import's lead. A unit shadow of an unaligned import would have been published lead bytes early, over the guest bytes before the unit. The shadow now keeps the lead, and agc_driver_sparse_dma_buf_import publishes a unit shadow of its unaligned import and checks the unit and the 16 KiB on each side. Without the fix it fails there.

The description has the new numbers: ASTRO BOT on main still falls back to copies 20 times (8 ranges above 64 MiB), and not at all with this PR.

@nicolasfalesy

Copy link
Copy Markdown
Contributor

Tested on Linux (Arch), c++ (GCC) 16.2.1 20260810, Release, BUILD_TESTING=ON, main at 5d9dcc42d2ec5401aebd089028b8d4d7410a77b4. GPU tests ran on lavapipe (Mesa 26.2.2, llvmpipe with LLVM 22.1.8).

agc_driver_sparse_dma_buf_import passes on main (2 of 2) and fails on main plus this PR (2 of 2):

AGC graphics: sparse import: the unaligned import does not start its buffer at the block below it

That is the check at SparseDmaBufImport.cpp:188 of this PR. I printed the values: HostImportFor returns an import, but its lead is 0 where the test expects 16384. The test is skipped on Windows (#ifdef _WIN32), so only Linux runs it. The dma-buf import for the range at a 16 KiB offset does not carry the lead on lavapipe. The same assertion also failed in a combined build on an Intel Arc B390.

AI-assisted: yes

@hugodearaujo

Copy link
Copy Markdown
Contributor Author

Thanks for running it and printing the values, that pinned it down.

createImport tries a host-pointer import first and uses dma-buf only when that fails. RADV refuses a host-pointer import of the shared memfd mapping, so the unaligned range goes to the sparse dma-buf import, with its lead. lavapipe accepts it, so the range is imported through a host pointer, whose buffer starts at the range: lead is 0 and this PR's path is not taken at all. The test assumed a dma-buf import.

d7018a5 checks the lead only for a dma-buf import (HostImport::dmaBuf) and prints a note otherwise. With lavapipe from Mesa 26.2.4 on my machine (/dev/udmabuf writable), the test failed 2 of 2 runs before that commit, with your message, and passes 3 of 3 after it, with the note that the import went through a host pointer. On RADV it passes 3 of 3 with the lead checked. The other 20 tests matching ^agc.*(import|dma|sparse|shadow|unit) give the same results on both devices.

The Intel Arc B390 failure is probably the same, if ANV takes the host-pointer import there. I could not check that here; on that machine the note in the output would say which path the import took.

On a Linux host with /dev/udmabuf, agc_host_import_sparse also fails on this branch's base, because it needs #5759, which is merged now. The description has the numbers.

@nicolasfalesy

Copy link
Copy Markdown
Contributor

Checked d7018a53a on the Intel Arc B390 from my earlier comment. Linux (Arch), Mesa 26.2.2 (ANV), /dev/udmabuf writable, c++ (GCC) 16.2.1 20260810, Release, merged onto current main (c8044c3a7) together with #6784, because main doesn't build on Linux without it right now.

agc_driver_sparse_dma_buf_import and agc_host_import_sparse both pass, 2 of 2 runs. ANV takes the host-pointer path, as you guessed:

note: the device imported the unaligned range through a host pointer, which has no lead
AGC sparse dma-buf import tests passed

AI-assisted: yes

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 16, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

The ANV run explains the lavapipe failure: both devices take the host-pointer path, where the lead is 0, so checking the lead only for a dma-buf import in d7018a53a is right. The dma-buf path with its lead has still not run on a device that picks it, such as RADV. @hugodearaujo, it also conflicts with main again; please rebase, and say if anyone has run it on RADV.

…igned

A sparse import binds whole 64 KiB blocks, so a range whose memfd offset
is not a multiple of 64 KiB fell back to one UDMABUF_CREATE and, above
udmabuf's size limit, to copies. The sparse buffer now starts at the
block below the range: HostImport::lead holds that distance, the
import's device address points at the range itself, and every offset
into the import's buffer goes through HostImportOffset.
A unit shadow published its units into the import's buffer at the guest offset from the import's
base, without the import's lead, so a sparse import that starts below its range (HostImport::lead)
got the units lead bytes before their place. The shadow keeps the lead with the import's base and
buffer, and the shadow verify reads the guest bytes at the same place.

agc_driver_sparse_dma_buf_import publishes a unit shadow of its unaligned import and checks the
unit's guest bytes and the bytes around it; without the lead it fails there.
A device that imports shared memory through host pointers, such as lavapipe, imports the unaligned range that way, and a host-pointer import has no lead.
@hugodearaujo
hugodearaujo force-pushed the feat/agc-sparse-unaligned-import branch from d7018a5 to 52c4589 Compare October 17, 2026 18:11
@hugodearaujo

Copy link
Copy Markdown
Contributor Author

@oneandonlydean rebased onto main (07b465114) as ef622e2f8, 9eb7e2444 and 52c4589f8. The only conflict was in docs/dev/debt/libSceAgcDriver.md, where main's new "Settled dma-buf imports" item (#5905) sits next to this PR's "Sparse dma-buf imports" item; both are kept.

Run on RADV: AMD Radeon RX 9070 XT, Mesa 26.2.4, Arch Linux, /dev/udmabuf writable, size_limit_mb 64.

  • agc_driver_sparse_dma_buf_import passes 3 of 3 with no note, so the lead and the unit-shadow publish were both checked. With a print added locally next to the lead check (not in the branch), the unaligned range is a dma-buf import with lead 16384, and HostImportOffset of the range's address is 16384:
    diag: unaligned import dmaBuf=1 lead=16384 base=0x7f4844404000 address=0x7f4844404000 offset of address=16384
    AGC sparse dma-buf import tests passed
    
  • For comparison, the NVIDIA RTX 3060 Ti (615.78.08) in the same machine imports the range through a host pointer, as lavapipe and ANV do:
    note: the device has no settle queue, so dma-buf imports are not settled
    note: the device imported the unaligned range through a host pointer, which has no lead
    diag: unaligned import dmaBuf=0 lead=0 base=0x7f2dd2204000 address=0x7f2dd2204000 offset of address=0
    AGC sparse dma-buf import tests passed
    
  • agc_host_import_sparse passes 3 of 3 (it needed test(agc): drop a shared range's backing when the range goes away #5759, now in main). Of the 21 tests matching ^agc.*(import|dma|sparse|shadow|unit), 20 pass and agc_shadowed_key_stores skips.
  • Full CTest with -j8: 1363 of 1364 pass; agc_driver_tessellation segfaults, as it does on main on this GPU.

The "on RADV it passes 3 of 3 with the lead checked" in my earlier comment was this same device, but without printing the path. Windows was not rerun after the rebase; the sparse tests are Linux only.

AI-assisted: yes

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@oneandonlydean
oneandonlydean merged commit 66e71b6 into boykopovar:main Oct 18, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants