Skip to content

perf(agc): import storage surfaces that cross the import window through a joined import - #6197

Open
mcsilva-dev wants to merge 1 commit into
boykopovar:mainfrom
mcsilva-dev:fix/import-crossing-window
Open

mcsilva-dev wants to merge 1 commit into
boykopovar:mainfrom
mcsilva-dev:fix/import-crossing-window

Conversation

@mcsilva-dev

Copy link
Copy Markdown

What

HostImportFor keeps an import run inside one 8 MiB import window (SetHostImportWindow(8 MiB)), and importAccess returns no import when the window run does not cover the range. A storage surface that crosses a window boundary therefore never has an import, and every upload of it takes the CPU path in StorageTexture::upload: GuestMemory::ReadCommitted of the whole surface, a staging copy, a detile in a CommandBatch of its own and SubmitAndWait.

EA SPORTS UFC 6 (PPSA23566) builds its transient render targets in a pool it reserves at 0x11c0000000 (+0x6f424000) and maps in 8 MiB pages (remap fixed ... +0x800000 prot=0xf3, each page on unrelated physical memory). The engine packs surfaces across page boundaries, so most of its storage surfaces cross a window. With APS5_PROFILE_DRAW=1 at the title screen, per 10 s:

[storage] uploads by path (count/MiB, 10 s): clear 0/0.0 direct 6696/5994.0 cpu 1729/6955.0 ...
cpu uploads by cause (count/ms): no import 1729/5135.5

i.e. about half of the queue worker's time went to these uploads. A temporary diagnostic that recorded the import refusal for each CPU upload named a single cause: every one was refused as not registered because the window run did not cover it, all inside the 0x11c0000000 pool. The pool is remapped only during start-up (82 remaps, none after the title screen appears), so an import over it stays valid.

HostImportSpanning keeps HostImportFor's per-window contract (importWindowTests still requires that a view crossing the window is not imported) and, when it finds no import, falls back to a joined import over the contiguous registered ranges the range covers (crossedRun → importJoined, as buffers already do). The storage texture paths that look up an import for a whole surface use it.

Tested

  • agc_driver_graphics_tests (Windows 11, WinLibs GCC 15.2.0, Release, AMD Radeon RX 9070 XT, driver 26.8.1): the new importSpanningTests fails on main with a surface crossing the import window was not imported through a joined import (checked by turning HostImportSpanning into a plain HostImportFor) and passes with this change; the whole executable passes (Graphics validation tests passed).
  • Conventions: tools/check_conventions.py --base origin/main passes.
  • UFC 6 title screen, Windows 11, i5-14600KF, 32 GB, RX 9070 XT (driver 26.8.1, LLPC), game on an NVMe SSD, warm shader cache, APS5_TRACE_SUMMARY_EVERY=10. Both builds are main 760ffa6 plus one local change that the title needs to reach the title screen on 32 GB (registered shaders prepared at first use, see AGC: registered shaders keep decoded programs and resource plans resident; UFC 6 (PPSA23566) exhausts host memory on a 32 GB machine #6139); they differ only by this commit:
10 s intervals, APS5_PROFILE_DRAW=1 frame p50 / p90 presents per 10 s CPU storage uploads per 10 s
main 760ffa6 (+ local change) 95 / 102 ms ~104 235 (958 MiB)
with this change 84 / 90 ms ~118 0 (direct uploads 4,152 / 6.6 GiB)

With the CPU uploads gone, the largest remaining storage cost on main is the alias round trip: storage refresh write-backs of ~8,800 surfaces (~9.6 GiB) per 10 s, the hand-overs #4286 targeted.

Together with #4286. On the UFC bring-up fork from #5996 (c222bd2), same machine and scene, without APS5_PROFILE_DRAW:

frame p50
fork c222bd2 71 ms
+ #4286 (alias hand-over, adapted to the per-layer original snapshots) 72 ms (neutral, as #4286 measured on Windows)
+ #4286 + this change 35 ms

The first two rows ran with the game on a hard disk and the third on the NVMe SSD; moving the same build between the two disks shortened start-up but did not change the title-screen frame time.

The hand-over takes an aliased surface's units on the device only when the surface is imported, and in this title the aliased surfaces were exactly the ones crossing a window. So this change is what lets #4286 act on Windows here; it may be worth another look at #4286 on top of it.

The title screen picture is the same with and without the change (compared on screenshots); I have not checked later scenes beyond the first cutscene starting.

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 under Silent stubs in their library's technical debt
  • 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
  • Unblocks: PPSA23566 EA Sports UFC 6 (PPSA23566) #5996 (performance)
  • GPUs tested: AMD Radeon RX 9070 XT, AMD proprietary driver 26.8.1 (LLPC), Windows 11 26200
  • AI-assisted: yes (Claude Code); the measurements are from runs on this machine

…gh a joined import

HostImportFor keeps a run inside one 8 MiB import window, so a storage
surface that crosses a window boundary had no import and every upload of
it took the CPU path: read guest memory, copy it into a staging buffer,
detile in a batch of its own and wait for it.

UFC 6 (PPSA23566) builds its transient render targets from a pool of
8 MiB pages and packs surfaces across page boundaries, so most of its
storage uploads took that path: 1,700 CPU uploads (6.9 GiB) per 10 s at
the title screen, half of the queue worker's time.

HostImportSpanning falls back to a joined import over the contiguous
registered ranges the surface covers, as buffers already do through
importJoined; the storage texture paths use it. HostImportFor keeps its
per-window contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 15, 2026 02:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 862 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1191 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1324 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 4449 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 5372 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 5480 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6108 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6182 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6185 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6186 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6187 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6218 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6280 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6373 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6412 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6457 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6472 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6479 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6490 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6499 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6546 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6548 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6566 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6628 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6646 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6682 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6683 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6718 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6730 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6772 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6786 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6820 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6848 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6911 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6913 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6914 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6917 core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6918 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6946 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6948 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6951 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6955 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 6960 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6971 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6973 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6987 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6989 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6995 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7048 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7082 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7154 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7233 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7254 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7261 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7292 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7310 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7332 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
PR 7371 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7427 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7446 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7457 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7465 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7466 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7473 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7489 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7514 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7533 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7579 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7604 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7618 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7629 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7647 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp

@github-actions github-actions Bot added the agc libSceAgc* label Oct 15, 2026
0xcrstn added a commit to 0xcrstn/AnyPS5 that referenced this pull request Oct 15, 2026
…window (mcsilva-dev)

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

# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
0xcrstn added a commit to 0xcrstn/AnyPS5 that referenced this pull request Oct 15, 2026
…he FPS candidate

Brings in ufc6/feedback (origin/main 760ffa6, boykopovar#6197, boykopovar#4286, boykopovar#5955) on top
of the four FPS topics and boykopovar#6168. Conflict resolutions:
- Execution Draw.cpp: keep the binding plan's single Regions() collection
  after the stage loop, adding the regions of reused or data-hit stages, and
  upstream's data-hit counters and cacheDrawStages signature.
- Graphics Draw.cpp: recordDrawCommands takes both upstream's firstIndex and
  the dirty-record bound state; a stencil replay, like a face or channel
  split, drops the bound state.
- The binding plan falls back to the full capture for stages whose plan uses
  image tables (ShaderMemory::UsesImageTables).

Unbuilt and untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0xcrstn added a commit to 0xcrstn/AnyPS5 that referenced this pull request Oct 15, 2026
…nd supersede handed-over units before saving

HostImportSpanning (boykopovar#6197) joined registered ranges whose boundary falls
inside a 64 KiB unit, so with unit shadows such surfaces took the
GPU-direct path that main refuses for them (agc_storage_split_writeback,
agc_driver_storage_write_back_overhang). The whole-surface import now
goes through surfaceImport, which keeps the per-window import, refuses
a join across a unit-misaligned boundary under unit shadows and joins
otherwise, so 64 KiB-aligned pages keep the spanning import.

The boykopovar#4286 port superseded handed-over units after main's capture of
unsaved layers, whose flush stored the alias's dropped results first
(agc_driver_alias_handover); the supersede now runs before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EagleBearerYS added a commit to EagleBearerYS/AnyPS5 that referenced this pull request Oct 15, 2026
@Celegans12

Copy link
Copy Markdown
Contributor

@mcsilva-dev I tried this on our Ghost of Yōtei branch today, and two of main's storage write-back tests fail with it. They fail on your head d7f5ffb by itself too, without our changes: agc_storage_split_writeback and agc_driver_storage_write_back_overhang, on Windows 11 with an RTX 3080 Laptop.

  • agc_storage_split_writeback stops at "64 KiB R_X 200x150, 3 mips, ranges split inside a 64 KiB unit: the write-back took the GPU-direct path". A split inside one 64 KiB unit now goes through the joined import instead of the CPU path.
  • agc_driver_storage_write_back_overhang stops at "byte 0x5 differs from the fully registered surface (15360 texels, 768 past pitch x height)". The write-back for the image does run, but its data only shows up in guest memory with the next submission. An extra flush and wait before the check doesn't help.

Replacing the six HostImportSpanning calls in Texture.cpp with HostImportFor makes both pass again, so it looks like it's in the joined-import path itself. Limiting the joined import to 64 KiB-aligned splits wasn't enough (an aligned split still lost data), and neither was turning unit shadows off. We've taken it back out of our game branch for now, but I'm happy to rerun a fix on Yōtei.

Details
  • Windows 11, RTX 3080 Laptop GPU (driver 610.62), MinGW-w64 GCC 15.2.0, Release.
  • Reproduced on your head d7f5ffb in a clean worktree, and on our branch (main a5e2a80 plus our stack) with the PR merged. Both tests pass on that branch with the merge reverted.

@AlxcBTW

AlxcBTW commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

With this PR two existing tests fail under Mesa lavapipe, on Linux and on Windows: agc_storage_split_writeback and agc_driver_storage_write_back_overhang. Both pass on the base of this PR and on current main. The stored data is right once the GPU work is complete; both failures are in the tests, which assumed the CPU write-back for a surface over several registered ranges. One line in each test makes both pass.

Runs. The fork CI builds the tests and runs the two ctest entries under lavapipe. Linux is ubuntu-latest (llvmpipe, LLVM 20.1.2). Windows is windows-latest with Mesa 26.2.4 lavapipe (llvmpipe, LLVM 23.1.2), where the test memory is private committed memory and the imports are host-pointer imports.

commit Linux Windows
main 5d9dcc4 both pass, 38839624114 both pass, 38845866370
760ffa6 (the base of this PR) both pass, 38843808385 both pass, 38845874118
d7f5ffb (this PR) both fail, 38841491145 both fail, 38845858868
d7f5ffb plus the two lines below both pass, 38846663460 both pass, 38846691864

Messages, the same on both platforms:

  • agc_storage_split_writeback: 64 KiB R_X 200x150, 3 mips, ranges split inside a 64 KiB unit: the write-back took the GPU-direct path. Nothing else is reported for that case, so its checks of the stored texels, of the words changed outside the surface and of the second store pass on the GPU-direct path.
  • agc_driver_storage_write_back_overhang: AGC graphics: storage write-back overhang: the fully registered surface received no texels.

Cause.

  1. splitWriteBackTests in tests/Recorder.cpp expects the CPU write-back when the two registered ranges split inside a 64 KiB unit and unit shadows are on (direct = !UnitShadowEnabled()). importSegments() gives up on that case (unitAligned && from % 65536 != 0): it would need two imports under one unit. writeBackLayers now asks HostImportSpanning first, which returns one joined import over both ranges, so the unit lies in a single import and the write-back goes GPU-direct. HostImportFor(...) == nullptr still holds (the test asserts it) and the GPU-direct path leaves both ranges covered by an import (the test asserts that too).
  2. Write() in tests/execution/StorageWriteBackOverhang.cpp ends with FlushAllPending("test") and AgcDriverWaitIdle_nid_postfix(), and the test then reads the guest memory through plain pointers. A GPU-direct write-back is recorded behind the work that produced the image and completes after that call. The other tests in tests/execution call AgcDriver::GuestMemory::FlushGpuWrites(address, bytes) after AgcDriverWaitIdle_nid_postfix() before they read raw memory for that reason. This test did not need it so far, because its split surface took the CPU write-back, which waits for the GPU and so also completed the GPU-direct write-back of the whole surface made before it. Builds of the same tree with and without this PR and a texel count after each Write: without it the whole surface has 0 texels after its own Write and both have 15360 after the second (38846022767); with it the split surface is GPU-direct as well, nothing waits, and both are still at 0 when the test reads (38846017110). With FlushGpuWrites added to Write() and nothing else changed, the test passes on the build with this PR, the two surfaces equal byte for byte and the padding kept (38846544700; that run is red only because agc_storage_split_writeback still fails there without its own change).

Change. One line in each test file; with both, all 34 tests of that job pass on a tree that contains this PR (38846598673):

--- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
+++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
@@ -4129,7 +4129,7 @@ void splitWriteBackTests(const Device& device, Recorder& recorder) {
         {"64 KiB R_X 200x150, 3 mips, off a 64 KiB boundary, ranges split at 132 KiB", TextureTileMode::kR64KBX, 3, 4096, 135168, true, true, false, false},
         {"linear 200x150, ranges split at 64 KiB", TextureTileMode::kLinear, 1, 0, 65536, true, true, false, false},
         {"64 KiB R_X 200x150, 3 mips, second range of another access", TextureTileMode::kR64KBX, 3, 0, 131072, false, false, false, false},
-        {"64 KiB R_X 200x150, 3 mips, ranges split inside a 64 KiB unit", TextureTileMode::kR64KBX, 3, 0, 135168, true, !UnitShadowEnabled(), false, false},
+        {"64 KiB R_X 200x150, 3 mips, ranges split inside a 64 KiB unit", TextureTileMode::kR64KBX, 3, 0, 135168, true, true, false, false},
         {"64 KiB R_X 200x150, 3 mips, two adjacent allocations split at 128 KiB", TextureTileMode::kR64KBX, 3, 0, 131072, true, true, true, false},
         {"64 KiB R_X 200x150, 3 mips, ranges split at 128 KiB, a second store outside the write watch", TextureTileMode::kR64KBX, 3, 0, 131072, true, true, false, true},
         {"linear 200x150, ranges split at 64 KiB, a second store outside the write watch", TextureTileMode::kLinear, 1, 0, 65536, true, true, false, true},
--- a/core/libs/prx/libSceAgcDriver/tests/execution/StorageWriteBackOverhang.cpp
+++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageWriteBackOverhang.cpp
@@ -75,6 +75,7 @@ void Write(std::uint8_t* texels) {
     AgcDriverWaitIdle_nid_postfix();
     AgcDriver::Graphics::StorageTexture::FlushAllPending("test");
     AgcDriverWaitIdle_nid_postfix();
+    AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(texels), SurfaceBytes);
 }

The first change makes the case expect the GPU-direct path whatever UnitShadowEnabled() says. The case's data checks pass on that path (the stored texels, the words changed outside the surface, the second store); whether a split inside a unit should leave the rule of importSegments() behind is for the author and the reviewers to decide. Tested on lavapipe only (Linux and Windows); no hardware GPU.

AI-assisted: yes

@milescoviello

milescoviello commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

A second title with storage surfaces that cross the import window: MXGP 26 (PPSA30839). Its 2560x1440 storage images are 15 MiB (0xf00000), so each one crosses an 8 MiB window. Without this PR, a local log line per CPU-path upload shows every one of them going through StorageTexture::upload's CPU path with HostImportFor returning null. That also keeps the alias exemption in StorageTexture::Refresh from applying, since it requires an import.

Linux 6.8, RTX 4070 Ti (595.99.02), Release, pad input, 130 s runs. main doesn't reach this scene, so the base is main 143f130 + #6814, #6094, #6160, #6308, #6184, #6455 + #6917. This PR's head (d7f5ffb043) merged on top, clean merge:

without this PR with this PR
CPU-path storage uploads per run (with the upload log) 9–13 2 in every run
runs where overlapping 2560x1440 images re-upload hundreds of times (#6998), with the upload log 4 of 12 0 of 6
100–110 s window, no APS5_PROFILE_* (2 rounds) 41 / 43 presents, p50 242.7 / 233.2 ms 42 / 43 presents, p50 240.6 / 232.3 ms
without 1: **seconds 100:10** from present 436: 41 presents, frame p50 242.7 / p90 254.5 / p99 332.4 / max 332.4 ms, GPU busy 113.0 ms per present
with 1:    **seconds 100:10** from present 1264: 42 presents, frame p50 240.6 / p90 251.9 / p99 262.6 / max 262.6 ms, GPU busy 115.6 ms per present
without 2: **seconds 100:10** from present 426: 43 presents, frame p50 233.2 / p90 243.1 / p99 246.0 / max 246.0 ms, GPU busy 109.1 ms per present
with 2:    **seconds 100:10** from present 445: 43 presents, frame p50 232.3 / p90 252.2 / p99 281.3 / max 281.3 ms, GPU busy 109.1 ms per present

In normal runs the frame rate doesn't change, because those runs upload only about 10 storage images in all. The difference is in the runs where several overlapping 2560x1440 images keep flushing and re-uploading each other (#6998): with this PR that didn't happen in 6 of 6. All 18 runs reached their limits.

AI-assisted: yes (Claude Code)

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the measurements on UFC 6 and MXGP 26 make the case for this clear. Before it can merge:

  • Conflicts with main in GuestBufferMemory.hpp and GuestBufferMemory.cpp (real content conflicts, not just adjacency). Please rebase and rerun agc_driver_graphics_tests after it.
  • Two existing tests fail with this change (reported above by @Celegans12 on Windows/RTX 3080 and by @AlxcBTW on lavapipe, Linux and Windows): agc_storage_split_writeback ("ranges split inside a 64 KiB unit: the write-back took the GPU-direct path") and agc_driver_storage_write_back_overhang. Both pass on your base. Please update them in this PR. @AlxcBTW's analysis and one-line fixes are in the thread: expect the GPU-direct path in splitWriteBackTests (Recorder.cpp, the "split inside a 64 KiB unit" row) and call AgcDriver::GuestMemory::FlushGpuWrites(...) at the end of Write() in tests/execution/StorageWriteBackOverhang.cpp.
  • @Celegans12 reports that an aligned split also lost data on their setup. Please confirm the stored texels are right once the GPU work is complete on a hardware driver (not only lavapipe), and state in the PR that the split-inside-a-unit case now deliberately leaves the importSegments() rule behind.

@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line unblocks title Names a title ID on the Unblocks line labels Oct 18, 2026

This branch has not been deployed

No deployments
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 perf numbers missing perf pull request without a before and after summary line perf Performance change unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants