Skip to content

perf(agc): take an aliased storage image's units on the device without a host import - #13251

Draft
EagleBearerYS wants to merge 16 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-storage-alias-device-handoff-v2
Draft

EagleBearerYS wants to merge 16 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-storage-alias-device-handoff-v2

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What

When a storage image is refreshed over memory where another storage image still has results on the GPU, StorageTexture::refresh can take those results on the device. borrowUnits records one vkCmdCopyImage of the other image's pending units. On main that path needs two images of the same shape over the same memory (for example an RGBA8 view and an R32 view of one surface), and it also needs the refreshed surface to have a host import (Texture.cpp L2196 and L2342 on 08a10d2).

Without an import, the refresh takes the round trip instead:

  1. it waits for the other image's writer;
  2. it reads that image back and stores it to guest memory, the storage refresh write-back;
  3. it uploads the whole refreshed image again from guest memory on the CPU path, the flushed upload.

The four commits:

  1. perf(agc): take the units on the device without a host import. The import wasn't needed for correctness, but removing it needs two other changes, because without an import:
    • The upload runs before the borrow. The upload is then the CPU path, and for a 2D image it replaces the whole image, borrowed units included. Units held from an earlier borrow that the upload replaced (forgetBorrowed) are borrowed again.
    • The refresh's own reads don't store the alias. The compare with the saved bytes and the CPU upload's ReadCommitted go through the recorder's memory-access hook, which would store the alias anyway. While a kept alias is being refreshed over, FlushPending leaves it out, exactly as the refresh's own flush already does. A thread-local refreshingAlias sits next to refreshing.
  2. fix(agc): advance a borrower's generation past its lender's write-back.
    • The gap: after a borrow, the borrower keeps the generation it had at the borrow. If the lender then stores those units (any flush or CPU read of the range), and the borrower is written without another refresh, the borrower's own write-back takes those blocks as CPU-stamped. It keeps the guest bytes and loses its newer results.
    • On main: the gap exists only with a write-watched host import. Commit 1 would widen it to every surface.
    • The fix: a lender now remembers its borrowers. Its write-back advances the units they still hold at the lent version past its own stamps, as it already does for adjacent pending images (advanceAdjacent).
  3. fix(agc): store a kept alias when the refreshed image's keys aren't uncompressed.
    • The gap: the alias is kept on the keys proved before the refresh's flush, and that flush can store keys itself (main's comment above L2196). If the keys proved after it aren't Uncompressed, the borrow doesn't run while the alias is still exempt.
    • The fix: the refresh ends the exemption there, drops the alias, stores it as a refresh without an alias does, and proves the keys again. All of this happens before anything reads the surface for the upload.
    • On main: the same gap exists on the import path.
  4. test(agc): measure the round trip and assert what the handoff removes (below).

Unchanged: the conditions for keeping the alias are main's.

  • The keys must be Uncompressed and the memory tracked.
  • Each pending unit of the alias must be either borrowable (unstamped since its generation, and not pending in the refreshed image) or stamped since. A stamped unit is one the alias's own write-back would drop anyway.
  • An untracked surface, a pending unit with generation 0, or an unstamped unit pending in both images still refuses the alias.
  • The alias stays pending until the refreshed image is written and supersedes those units, so a CPU read still stores it.

The fifth commit merges main (f7f03da). #12559 and #12927 had added tests at the same places in tests/Recorder.cpp and the AGC CMakeLists.txt, and the merge keeps both sides.

The last two commits take overlaps at other bases and sizes, one 64 KiB tile at a time.

  1. refactor(agc): keep a storage image's borrowed units per unit and per lender. No behaviour change on its own.
    • borrowedFrom/borrowedVersion become one BorrowedUnit{from, version, unit, held} per unit, because one image can now hold units from several lenders, and a lender's unit index can differ from the borrower's.
    • A write (markLayersPending) supersedes exactly the lender unit recorded for each unit it writes, while the lender is still at the version lent. The lender's write-back maps its stored units to each borrower's own units (borrowersUnchanged).
    • pendingAliases() returns alias plans (each unit mapped to a lender unit, with which units are copyable). refreshingAlias becomes a set, and FlushPending without an except leaves out every kept alias, as it did for the single one.
  2. perf(agc): borrow an aliased storage image's units across bases and sizes, one 64 KiB tile at a time.
    • When two units are one tile with the same layout. A unit of image A and the unit of image B over the same 64 KiB guest block hold the same elements at the same bytes when all of these hold:
      • both are block-unit storage images (64 KiB-aligned base, thin, tiled);
      • they have the same tile mode, bytes per element and pipe/bank XOR, and no block compression;
      • the mode's swizzle equation uses only coordinate bits inside the block (swizzleWithinBlock; for SW_64KB_R_X at 4 bytes, x and y bits 0-6 plus the slice);
      • in each image the unit is exactly one tile block of a mip that isn't a tail;
      • both lie in the same array slice;
      • the element rectangle each image holds in that tile is the same size.
    • Then one vkCmdCopyImage region, from B's tile to A's tile at their own tile coordinates, moves the block bit for bit.
    • Equal extents are required, not just "fits inside": a lender holding elements the borrower lacks couldn't be superseded when the borrower writes, and both images would stay pending over the block.
    • The refresh, per alias and per overlapping unit, carries the rules above over unit by unit:
      • an untracked range, a generation-0 pending unit, or an unstamped unit pending in both still refuses the alias;
      • a unit another kept alias also has pending refuses the later alias, so no block has two lenders;
      • a stamped unit stays the alias's own;
      • a pending, unstamped unit that the copy can't take is stored before the refresh's flush, over that unit's range only.
    • The same-surface image keeps priority.
    • Still on the round trip, per unit:
      • mip-tail blocks;
      • a short last unit;
      • an edge tile of another extent;
      • another array slice, element size, pipe/bank XOR or tile mode;
      • swizzles that reach outside the block (kStandard*, GCN and 4 KiB modes are refused this way, conservatively).

Prior art: #4286 (AlxcBTW, closed unmerged on 10-12) handed aliased storage memory over without the guest round trip, unit by unit and at any base. It retiled through a device buffer and needed a host import.

  • Its review asked for a before/after on a default configuration.
  • This change is on by default (APS5_NO_ALIAS_BORROW=1 turns it off), needs no host import, and copies image to image.
  • Its before/after is the round trip's bytes in the tests below. Village is covered below.

Resident Evil Village (PPSA01556)

Static menu, profiled boot (APS5_PROFILE_DRAW=1) on main ebb4075 plus #12478, without this change. Its 10 s [storage] lines at the menu show:

  • 2.4-4.8 GB of storage refresh write-backs;
  • as much again in flushed CPU uploads;
  • 0 bytes on the device alias path.

The highest window:

[storage] uploads by path (count/MiB, 10 s): clear 27/101.2 direct 353/1386.4 cpu 357/5096.9 alias 0/0.0; by reason: cpu 28/446.2 flushed 356/4819.4 keys 27/101.2 shadow 298/325.1 store 28/892.5; write-backs by reason (count/MiB): imported buffer region 54/443.8 memory access 27/860.6 readback before label 189/11.8 refresh 83/262.9 stencil plane store 27/121.5 storage refresh 1150/4691.4 written buffer range 55/165.8; ...

The first four commits alone don't take Village's pairs.

  • The check: a boot with them and a per-pair refusal trace ran on the menu tree. Every refusal was "base differs" or "size differs".
  • Why: Village's images are a transient pool at other bases and sizes over the same memory: 1920x1080, 960x1080 with 4 mips, 120x2160 and 120x68. All are format 36, tile mode 7, 64 KiB-aligned.
  • Commits 6 and 7 are for that shape: with the same 64 KiB tile mode and element size, a 64 KiB block holds one whole tile in both images. Only tail blocks, partial edge tiles and other slices keep the round trip.
  • No Village speed claim. A diagnostic build that skips this round trip altogether, copying nothing and leaving the stale image, made the menu's frame rate move only within its run-to-run spread. That diagnostic was combined with one other switch, against its own control. The committer's per-frame draw time didn't fall either. Village's menu is limited elsewhere, so this PR stands on the bytes it removes in the cases the tests cover.

Tested

New tests. Each runs agc_driver_recorder_tests with its own --…-only switch. All use a write-watched allocation with host imports off, and cached 256x256 RGBA8 and R32_UINT storage images over the same surface, with the same tiling.

  • agc_storage_alias_borrow (commit 1):

    • Case A: R32 is left pending on the GPU. The RGBA8 refresh must leave it pending, so it wasn't written back, and must sample its value.
    • Case B: R32 is pending on units 1-3 only, and a driver store hits unit 0.
      • The refresh must upload unit 0 and take units 1-3 after that upload.
      • A second store and refresh replaces the held units, and they must be borrowed again.
      • Then a write-back of the RGBA8 image leaves nothing pending, and guest memory holds the store and the R32 values.
  • agc_storage_alias_keys (commit 3): a pending linear image over the RGBA8 image's DCC keys turns them single when the refresh's flush stores it. The RGBA8 image must still sample the R32 image's value, and the R32 image must no longer be pending.

  • agc_storage_alias_round_trip (commit 4): four rounds of the pattern a frame makes. Each image is looked up, then written, over the same memory. Three pairs run in copied and imported memory, with images made directly and from the cache.

    • Content: after every lookup, read back on the GPU, and guest memory after a flush, in a second pass.
    • Bytes: the counters behind the [storage] line, per step.
    • Assertion, for the pair this change covers (cached RGBA8/R32, copied memory): 0 storage refresh write-backs, 0 flushed uploads, no other reason taking their place, and one device copy per lookup after a write (7 × 256 KiB).
    • Before this change: the same steps moved 7 × 256 KiB each way through host memory.
    • Printed, not asserted: the other pairs (different dimensions; tiled against linear), images made outside the cache, and imported memory. On lavapipe imported memory is compared, not watched, so the alias is refused as untracked. All of these keep the round trip.
  • agc_storage_alias_blocks (commit 7): R32 images in tile mode 7 (R 64 KiB X), cached, over copied write-watched memory.

    • Each case: X is written, Y looked up, Y written, X looked up, for two rounds.

    • Two runs per case: stored (FlushPending after every write, the old path) and borrowed (no stores). The patterns differ per texel, mip, image and round.

    • Content: after every lookup, the looked-up image holds the same texels in both runs, at every mip. After the final flush, guest memory is byte-identical between the runs.

    • Bytes, borrowed run:

      • every block taken on the device is unchanged in guest memory by the lookup;
      • every fallback block received the other image's results first;
      • the alias copies are exactly the taken units × 64 KiB;
      • with no fallback blocks there is no storage refresh write-back.
      Case (Village's shapes, scaled) Y lookup X lookup
      512x128 two units into 256x256 takes blocks 2-3 (128 KiB), no store takes blocks 2-3, no store
      128x128 at the base of 256x256 takes block 0 (64 KiB) takes block 0
      128x64 at the base of 256x256 (a partial tile) nothing taken; block 0 stored (old path) nothing taken; block 0 stored
      512x128 over a 256x256 four-mip chain takes blocks 1-3 (192 KiB); tail block 0 stored, then uploaded takes blocks 1-3; block 0 stored
  • agc_storage_alias_round_trip, extended (commit 7): its R32 256x256/512x128 pair (same base and size, other width) now asserts the handoff for cached images over copied memory: no storage refresh write-back, no flushed upload, and 7 whole-surface alias copies (7 × 256 KiB).

Reds (Linux). Each was built from the named commit with only the named change:

commit change result
1 Texture.cpp from main agc_storage_alias_borrow case A: "refreshing the RGBA8 image over the surface wrote the pending R32 image back instead of taking its units on the device"
1 APS5_NO_ALIAS_BORROW=1 case A, same message
1 the borrow before the upload, as on main case B: "the RGBA8 image sampled 16.000000 after a refresh that uploaded unit 0 from guest memory and took units 1-3 from the R32 image, expected 4"
1 held units not borrowed again case B: "... sampled 16.000000 in a unit it held from the R32 image after a second driver store over unit 0 and refresh, expected 4"
1 hook flushes don't leave the alias out case B: "refreshing the RGBA8 image after a driver store over unit 0 wrote back the R32 image pending over units 1-3"
2 commit 2's change reverted, test from commit 4 agc_storage_alias_round_trip: "... cached images, other format, same bytes per texel (R32_UINT / RGBA8 256x256, R 64 KiB X), flushing, round 1: Y does not hold X's last results after its lookup"
3 commit 1's Texture.cpp agc_storage_alias_keys: "the RGBA8 image sampled 17.000000 after a refresh whose flush turned its keys single over the R32 image's pending results, expected 48"
4 Texture.cpp from main agc_storage_alias_round_trip: "'storage refresh' write-backs 7x 1835008 bytes, although the device handoff covers this pair"
7 commit 5's code, commit 7's tests agc_storage_alias_blocks: "a 512x128 image two units into a 256x256 one, round 0, Y looked up: took 0 bytes from the other image on the device, expected 2 units"; agc_storage_alias_round_trip: "same format, other dimensions: 'storage refresh' write-backs 7x 1835008 bytes, although the device handoff covers this pair"
7 the lender's region taken at the borrower's tile coordinates (a mutation) agc_storage_alias_blocks content check: "lookup 0 (Y) holds 32768 texels other than the store-and-upload path gives (first 0x10000000 where 0x10020000)", that is, X's row 0 where row 128 belonged; agc_storage_alias_round_trip fails too

Linux runs (Ubuntu 24.04, GCC 13.3, Release, CI's flags, lavapipe/llvmpipe 20.1.2, as an unprivileged user):

  • the new tests ×10 on their commits;

  • the 52 DCC and storage tests ×10 on commit 3: 44 passed every time, 8 skipped;

  • ctest -R '^agc' once:

    • commit 1: 1,135 tests, 0 failed, 75 skipped by their own skip code (no udmabuf, llvmpipe);
    • commit 3: 1,136 tests, 0 failed, 75 skipped;
    • commits 1, 2 and 4 together: 1,136 tests, 0 failed, 75 skipped.
  • commits 6 and 7: ctest -R '^agc' once, 1,139 tests (agc_storage_alias_blocks added), 0 failed, 75 skipped, the same set as on commit 5; the 4 alias tests ×10, 40 of 40; commit 6 alone, the storage, clear hand-over, unit shadow and recorder tests, 18 of 18 (2 skipped, as on commit 5).

GitHub, ×10 on both runners:

  • Commits 6 and 7 against commit 5 (run): the alias tests and the storage tests around them.
    • Linux: 84 tests, all 10 runs passed, 8 skipped. With commit 5's code, agc_storage_alias_blocks and agc_storage_alias_round_trip fail with the messages above.
    • Windows: 100 tests, 4 run and passed, 96 skipped (no Vulkan device).
    • Suites: libc and libkernel pass: 198 and 304 tests on Linux, 225 and 326 on Windows.
  • Commit 5, with the merge of main (run): the three new tests, main's new agc_storage_typed_fill_clear, and the storage tests around them.
    • Linux: 84 tests, all 10 runs passed, 8 skipped.
    • Windows: 100 tests, 4 run and passed, 96 skipped (no Vulkan device).
    • Suites: libc and libkernel pass: 198 and 304 tests on Linux, 225 and 326 on Windows.
  • The four commits on 08a10d2 (run): the three new tests plus the storage tests around them.
    • Linux: 83 tests, all 10 runs passed, 8 skipped.
    • Windows: 99 tests, 4 run and passed, 95 skipped (no Vulkan device).
    • main's arm doesn't build there, because the round-trip test reads a test-only accessor in Texture.hpp. The reds above are the per-commit ones.
  • Earlier, commit 1 alone (run):
    • Linux: 81 tests ×10, all passed, 8 skipped.
    • Windows: 97 tests, 4 run and passed, 93 skipped. The runner has no Vulkan device, so the GPU tests skip there, these included. No Windows red is claimed.
    • An unrelated failure: in that run, main's code also failed agc_driver_storage_read_during_idle_store once, at its own precondition ("the GPU producer left no pending image"). It passed on main in the next run, and with this change in every run.

Validation layer (CONTRIBUTING's AGC rule), on GitHub's Linux runner (llvmpipe, Mesa 25.2.8, Ubuntu 24.04.5), commit 1 against main (run):

  • The layer loaded: inserted 80 times on main and 81 with the change, with 0 "was not found".
  • VUIDs: the only one is VUID-VkDeviceCreateInfo-pNext-pNext, on both. The change adds none.
  • Commit 3 against commit 1 (run, ×10): the same single VUID on both. The change adds none.
  • Commits 6 and 7 against commit 5 (run, the change ×20):
    • The layer loaded: inserted 77 times in commit 5's run and 1,408 times over the change's 20 runs, with 0 "was not found".
    • VUIDs: none on either side, so the change adds none.

Windows hardware, by DonRyuzaki (report): an AMD Radeon RX 7800 XT on Windows, head f6e8382 merged onto main a992b80, with ANYPS5_REQUIRE_VULKAN=1 and ANYPS5_NO_SHADER_CACHE=1. It is the first run of these tests on Windows hardware, since GitHub's Windows runner has no Vulkan device.

  • The four alias tests: agc_storage_alias_round_trip, agc_storage_alias_borrow, agc_storage_alias_blocks and agc_storage_alias_keys, 3 of 3 each, none skipped.
  • Profiled counts (APS5_PROFILE_DRAW=1): on the cached pairs over copied write-watched memory, 0 storage refresh write-backs, 0 flushed uploads and 7 alias copies (1,835,008 bytes). The uncached copied pairs keep their 7 write-backs and 7 flushed uploads. In agc_storage_alias_blocks, the offset 512x128 image takes 131,072 bytes by alias per lookup, the partial 128x64 tile falls back, and the four-mip image takes 196,608 bytes by alias with its tail on the fallback.
  • Not covered: the validation layer was requested and the focused logs show no VUID, but there was no paired main run, and the layer's insertion wasn't checked. There was no game or performance capture, so the same-scene capture the performance bot asks for is still outstanding.

Checks:

  • check_conventions.py and check_test_registration.py pass.
  • MinGW-w64 -fsyntax-only on Texture.cpp and tests/Recorder.cpp shows no new diagnostic. Recorder.cpp stops at the same _fflush_nolock declaration gap as on main.

Overlap: checked with git merge-tree of this head against every open pull request that changes Texture.cpp or Texture.hpp.

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 file
  • 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: perf(agc): upload a storage image over several host imports on the GPU #11485 (merge order only)
  • Fixes: none
  • Unblocks: none
  • GPUs tested: llvmpipe (LLVM 20.1.2, Mesa 25.2.8) on Ubuntu 24.04 (GitHub's runner and Linux VMs); the Resident Evil Village menu profiled on an RTX 3070 Laptop GPU (without commits 6 and 7)
  • AI-assisted: yes

…t a host import

A refresh of a storage image over memory where another image of the
same shape has results pending (same base, size, tiling, element size,
layers and mips, another format) takes those results on the device
(borrowUnits) instead of storing them to guest memory and uploading
them again. It did so only when the surface had a host import, because
the units were borrowed before the upload and a CPU upload of a 2D image
replaces the whole image, the borrowed units with stale guest bytes.

The alias is now kept whenever the refreshed image's keys are
uncompressed, and the units are borrowed after the upload. Units held
from an earlier borrow of the same alias version that the upload
replaced (it forgets them, as every upload path does for what it
overwrites) are borrowed again. Through an import the upload is per
unit and leaves the borrowed units alone, so the new order is
equivalent there.

The refresh's flush already leaves the alias's results on the GPU; its
own guest reads (the compare with the saved bytes, the CPU upload's read)
went through the memory hook, which stored them anyway. While a kept
alias is being refreshed over, a hook flush now leaves it out too, on
the same grounds: each of its pending units is either taken on the
device by this refresh or stamped since its results, which its own
write-back would drop.

agc_storage_alias_borrow makes an RGBA8 and an R32 image over the same
four 64 KiB units without a host import, clears the R32 one on the GPU
and refreshes the RGBA8 one: the R32 image must stay pending and the
RGBA8 one sample its texels. The R32 image is then stored, written again
over units 1-3 only, and a driver store hits unit 0: the next refresh
must upload unit 0 from guest memory and still sample the R32 results in
the units it takes after that upload, and after a second store over
unit 0 and refresh it must sample them in the units it held, which the
whole upload replaced. Its write-back must leave unit 0 with the store,
units 1-3 with the R32 results, and the R32 image no longer pending.
…rite-back

A refresh that takes an alias image's units on the device (borrowUnits)
sets the borrowed units' generation to the moment of the borrow. When
the lender later stores those units, for a flush or a CPU read of the
range, its stamps come after that generation although the borrower's
texels are the very bytes it stored. If the borrower is then written
without another refresh, its own write-back takes those blocks for CPU
stores and keeps the guest bytes, so its newer results are lost; and an
alias refresh over it finds the units stamped and neither borrows nor
stores them, so the refreshed image keeps the lender's older texels.

A lender now remembers its borrowers. At its write-back it takes, before
stamping, the borrowed units it stores at the version they were lent at
whose memory is unchanged since the borrower's generation, and once its
stamps are made it advances them past those stamps, as the write-back
already does for adjacent pending images (advanceAdjacent). Their saved
guest bytes no longer match the memory, so they stop vouching for it.
The case needed a host import before; with the device handoff for
surfaces without one it is reached by any such store between two
passes.

agc_storage_alias_round_trip's cached-image passes, in the next commit,
fail without this: the flushing pass of the RGBA8/R32 pair reports that
Y does not hold X's last results after its lookup.
…are not uncompressed

A refresh keeps a pending alias on the keys it proves before its flush, and
leaves the alias out of every flush until it ends so that the borrow can take
the alias's units on the device. That flush may store keys itself, though.
When the keys proved after it were not uncompressed, the borrow did not run,
yet the alias stayed exempt: its results never reached guest memory, and the
image uploaded the surface without them (a single-key surface is read from
guest memory).

The refresh now drops the alias as soon as the keys after the flush are not
uncompressed. It ends the alias's exemption, stores it over the surface as a
refresh without an alias does, and proves the keys again, all before
anything reads the surface for the upload. The store's stamps then mark the
units it wrote as changed, as the first flush's do.

The test refreshes a DCC image over a pending alias while a linear image,
pending over the image's keys and first unit, is stored by the refresh's own
flush and turns the keys single.
…at the handoff removes

agc_storage_alias_round_trip runs two storage images, X and Y, over the
same 256 KiB of write-watched guest memory through four rounds of: Y
looked up, Y written on the GPU, X looked up, X written. The pairs are
R32_UINT 256x256 against 512x128, R32_UINT against RGBA8 256x256 (both
R 64 KiB X), and tiled against linear. Each pair runs a measured pass,
which prints the storage traffic of every step from
StorageTexture::CumulativeTraffic() (the totals behind the [storage]
line, never cleared, counted under APS5_PROFILE_DRAW), and a flushing
pass, which also stores and compares guest memory after every lookup.
Both passes check the images' contents after every lookup and at the
end. It runs with copied and with imported guest memory, each once with
images made directly (StorageTexture::Create) and once through the
texture cache, as a title's images are.

For the RGBA8/R32 pair, with cached images and copied guest memory, the
measured pass now asserts what the device handoff removes: no 'storage
refresh' write-back and no 'flushed' upload, no other upload than the
two creations and the 'alias' device copies (one whole surface for each
of the seven lookups after a write, path 3), and no other write-back
than the test's final flush. The other runs keep printing their traffic
unasserted: images made directly are not cached and stay refused, the
other two pairs differ in dimensions or tiling, and imported memory that
the probe leaves compared has no write stamps for the handoff to rely
on.

The harness is BF-296's (fork test/agc-storage-alias-roundtrip), with
the cached-image runs and the assertions added.
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions

github-actions Bot commented Oct 30, 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
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5021 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5925 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 6187 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6280 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6970 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 7457 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7514 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7604 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8246 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8484 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 8707 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9289 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9508 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9712 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9875 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9884 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 9999 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10049 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10442 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 10443 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 10656 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10743 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10920 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 11444 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 12070 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 12746 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 13486 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 13793 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13796 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 13809 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13863 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13920 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13928 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13929 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 13963 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 14262 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 14330 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 14344 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 14628 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15058 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15136 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15169 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15438 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15441 core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15529 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15569 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15570 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 15572 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15621 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15674 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15710 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15729 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 15792 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
PR 15967 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16011 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16021 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 16183 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16253 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16286 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 16294 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16364 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 16411 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp

… lender

A refresh took an alias's pending units on the device from one image of
exactly its surface: one lender per borrower (borrowedFrom,
borrowedVersion) and the same unit index on both sides. The bookkeeping
now records, for each borrowed unit, the lender, the lender's version at
the borrow and the lender's unit (BorrowedUnit). The refresh works
through a list of alias plans (pendingAliases), each mapping the
refreshed image's units to a lender's units and saying which of them a
device copy can take:

- a refresh's hook exemption (refreshingAlias) is a set of images;
- borrowUnits copies from each lender into the units mapped to it;
- markLayersPending supersedes each lender's own unit, borrowersUnchanged
  maps the units a lender stores to its borrowers' units, and
  forgetBorrowed resets units one by one;
- the refresh's checks on an alias (an untracked or generation-0 pending
  unit, a unit pending in both images) run per alias, a unit that two
  kept aliases have pending refuses the later one, and an alias's
  pending, unstamped units that a device copy cannot take are stored over
  their own range before the refresh's flush, as that flush would store
  them.

pendingAliases still returns at most the one cached, uncompressed image
of exactly this surface that pendingAlias returned, and every unit of
that image maps to the same unit and can be taken, so nothing changes:
agc_storage_alias_borrow, agc_storage_alias_keys and
agc_storage_alias_round_trip pass as before.
…izes, one 64 KiB tile at a time

A refresh took a pending alias's units on the device only from an image
of the same base, size and shape. A transient pool hands its memory to
images at other offsets and sizes: on Resident Evil Village's menu, R
64 KiB X images of one element size alias each other at other bases,
widths and mip counts. Every refresh there stores the other image's
results and uploads them again.

Under a 64 KiB tile mode whose swizzle equation reads only coordinate
bits inside the block (TextureSwizzleEquations.hpp), a tile block's byte
arrangement depends only on the texel's position inside the block, the
array slice and the pipe/bank XOR. Each mip that is not a tail lays its
blocks out in rows (ComputeTiledMipLayout). So when a 64 KiB unit of this
image and the alias's unit over the same guest block are each one whole
tile block of such a mip, have the same slice, and hold texel rectangles
of the same extent (unitTile), the two images hold the same texels in the
same bytes there. One vkCmdCopyImage region from the alias's tile to this
image's tile moves them bit for bit, at other tile coordinates.

pendingAliases now also returns the pending images that overlap this
surface with such units. They need the same tile mode, element size and
pipe/bank XOR, an equation inside the block, and no block compression,
and each plan says per unit whether the copy holds. The image of exactly
this surface stays first, so its path is unchanged. For the alias's
pending, unstamped units the copy does not cover (a mip tail, an edge
tile of another extent), the refresh first stores those units over their
own range, as its flush would, and then uploads them again. boykopovar#13251's
rules hold per unit:
- a unit taken must be unstamped since the alias's generation and not
  pending in this image;
- a stamped unit is dead;
- an untracked or generation-0 unit refuses the alias;
- this image's next write supersedes exactly the alias unit it took
  (BorrowedUnit), which holds the same texels.

agc_storage_alias_blocks runs four pairs of R32 R 64 KiB X images. Each
pair writes X, looks Y up, writes Y and looks X up, twice. It does so
once with a store after every write (the old path) and once without. The
patterns differ per texel, mip and round. After every lookup Y and X must
hold the same texels in both runs, and after the final flush guest
memory must hold the same bytes. Without the stores, every lookup checks
three things. The guest blocks it takes on the device must be unchanged
in guest memory: no round trip through memory. The blocks no tile of the
same extent covers must have received the other image's results first.
Only those blocks' image is stored, with one 'storage refresh'
write-back, and the device copies must move exactly the taken units:
- a 512x128 image two units into a 256x256 one: blocks 2-3 each way, and
  no other upload or 'storage refresh' write-back;
- a 128x128 image at the base of a 256x256 one: block 0 each way;
- a 128x64 image at the base (a partial tile): nothing taken, block 0
  stored, as before;
- a 512x128 image over a 256x256 chain of four mips: blocks 1-3 taken,
  the tail block 0 stored.
agc_storage_alias_round_trip's R32 256x256/512x128 pair now asserts the
handoff too: no 'storage refresh' write-back, no 'flushed' upload, and
one whole-surface device copy for each lookup after a write. Before this
change both fail. The blocks test's first lookup takes 0 bytes on the
device instead of two units, and the round trip stores 7x 1835008 bytes.
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 30, 2026
# Conflicts:
#	core/libs/prx/libSceAgcDriver/CMakeLists.txt
The conflict in core/libs/prx/libSceAgcDriver/CMakeLists.txt from boykopovar#12862's split is resolved with tools/agc_test_split.py resolve, which moves this PR's test registrations into tests/cmake/.
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
#	core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
EagleBearerYS added a commit to EagleBearerYS/AnyPS5 that referenced this pull request Nov 1, 2026
@ntamas94

ntamas94 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

@EagleBearerYS A data point for this PR from Crysis Remastered (CUSA18659, PS4 GCN), and a case the tile path does not reach yet.

The round trip. CryEngine renders the bloom chain one level at a time: the 1920x1080 10-level chain at 0x1258780000 (format 71, tile mode 18, 8 bytes per texel) is sampled whole, while its levels 0 to 4 are bound as CB0 one by one, each as its own surface (2048x2048 at the chain base, 1024x1024 at +0x2000000, 512x512, 256x256, 128x128). So the chain image's refresh finds five pending per-level images over its memory. On this branch (merged into a local tree that boots the title) pendingAliases lists them as candidates, but tiles stays false: swizzleWithinBlock has no equation for a GCN mode, and a GCN macro tile's bank bits reach past the 64 KiB tile anyway. Each refresh then stores the five images to guest memory and uploads the chain again: storage refresh write-backs 857 per 10 s, 4,906 MiB, plus the same volume of shadow uploads, every frame.

What made it work here. Two changes on top of this PR, in pendingAliases / borrowUnits:

  1. An alias that is one level of the refreshed chain (its base is the chain's level offset, same blocksPerRow and pitchBytes, a single level) qualifies for the tile path without swizzleWithinBlock: with the same base and pitch the two images tile the bytes identically, bank swizzle included, so each 64 KiB unit is the same texel rectangle in both. The check runs both ways (a level image refreshed from a pending chain too).
  2. For such a level alias only, copyable accepts a smaller tile on the refreshed side (mine <= theirs): the chain's level 1 is 960x540 inside the 1024x1024 target, so its edge tiles are clipped; the copy extent is already the refreshed image's. A unit in the chain's padding rows (POW2_PAD: the target's rows 540 to 1023, outside the level's texels) counts as copyable without a copy, so it forces no store and no upload. Unrelated images keep your equal-extent rule: relaxing it for them fails agc_storage_alias_blocks (the 128x64 image at the base of a 256x256 one, where the uncovered texels of the unit must still reach guest memory), and the texels a level alias leaves uncopied lie in the chain's padding, which the chain never reads.

Measured in the same scripted scene, 10 s windows at ~390 s:

this PR + the two changes
storage refresh write-backs 857 / 4,906 MiB 30 / 7.5 MiB
GPU storage-writeback 2,617 / 4,937 MiB 1,797 / 39 MiB
alias uploads (device copies) 0 828 / 4,899 MiB (125 ms GPU per 10 s)
copyable units per level (0..4) 0 512/512, 128/128, 32/32, 8/8, 2/2

Frame time did not move (p50 107 to 110 ms both ways, the title is CPU-bound on the draw committer), but the chain's round trip through guest memory is gone. Happy to send this as a follow-up on top of this PR (Depends on: #13251), or as a patch for you to fold in; the diff is about 40 lines in Texture.cpp plus two declarations.

AI-assisted: yes

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@ntamas94 thanks, that's a clear case, and the table shows the round trip going away. A follow-up on top of this PR (Depends on: #13251) sounds right to us. It's your change, and it keeps this PR to the aliases it covers now.

A heads-up before you base it: this branch is about to take a merge of main over #10886, which makes borrowedUnits a UnitFlags. The per-unit source moves to a borrowedSources vector beside it, which is read only when the unit's flag is set. pendingAliases is unchanged, but borrowUnits and the places that check a unit's borrow change shape. The merge is d79032a on upkeep/13251-main-2ff700fddf on our fork, and it goes onto this branch once its runs finish. Basing on it should save you a conflict.

On the two changes, the level rule (same base and pitch, a single level, checked both ways) and the padding-row units would each want a test. agc_storage_alias_blocks pins the equal-extent rule for unrelated images in the same way. A lavapipe case with a chain sampled whole and one level bound as a target would show the stores and uploads going away without the title.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 2, 2026
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
#	core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 2, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 3, 2026
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Nov 3, 2026
Texture.cpp conflicts in refresh, where boykopovar#14868 prunes the adopted sources
after the refresh's flush. This branch's flush with its alias plans stays,
and pruneAdoptedSources follows it.
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Nov 4, 2026
agc_driver_recorder_tests.cmake conflicts in the Windows
ENVIRONMENT_MODIFICATION list, where boykopovar#14261 adds agc_untracked_forget; the
list takes both sides' names, each once.
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 4, 2026
@DonRyuzaki

Copy link
Copy Markdown
Contributor

Independent native Windows / AMD Radeon RX 7800 XT focused run of #13251 head f6e8382ef402a1bdefa70d1f9f7c61d107d8424b, integrated onto upstream main a992b803767e7411181c5c75f8e34bdfdd7aa110 as local merge 9aa6582abf12a25fb757d1fe1b31f027042e0ef2. agc_driver_recorder_tests built successfully. The logs selected the RX 7800 XT, and each of the four focused CTests passed 3/3 runs without a skip: agc_storage_alias_round_trip, agc_storage_alias_borrow, agc_storage_alias_blocks, and agc_storage_alias_keys (ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1).

The round_trip CTest ran all four copied/imported × cached/uncached categories for four rounds each. With APS5_PROFILE_DRAW=1, the cached, copied, write-watched 256×256 R32/RGBA8 pair and the 256×256/512×128 pair each reported 0 storage refresh write-backs, 0 flushed uploads, and 7 alias device copies totalling 1,835,008 bytes. The uncached copied pairs retained 7 write-backs and 7 flushed uploads of the same total. Imported guest memory was reported as compared and retained the CPU round trip; the tiled-versus-linear cached pair also retained it. None of these variants printed “not tested.”

The blocks CTest ran with profiling too: the 512×128 image offset by two 64 KiB units took 131,072 bytes by alias per lookup, while the partial 128×64 tile took the flush/upload fallback. The four-mip case took 196,608 bytes by alias and kept a fallback for its tail. Its content and final-byte comparisons passed. The borrow and keys fixtures passed their guarded paths, including borrowing without a host import and dropping an alias after the keys change.

This adds real Windows AMD hardware evidence where the hosted Windows GPU cases were skipped. The validation layer was requested; these focused logs contain no VUID ID, but I did not run a paired main validation comparison or verify layer insertion. I did not run a game or measure frame rate. This fixture traffic is not the same-scene performance capture, traces and trace_digest.py output requested by the perf-label bot; those remain outstanding. AI-assisted local test; build and verbose CTest logs are retained locally.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

Thanks @DonRyuzaki. This is the first run of the four agc_storage_alias_* tests on Windows hardware, since GitHub's Windows runner has no Vulkan device and skips them. I'll add it to the description: an RX 7800 XT, head f6e8382 merged onto main a992b80, 3/3 for each test. On the cached, copied pairs, the profiled counts were 0 write-backs and 7 alias copies (1,835,008 bytes), while the uncached pairs kept their 7 write-backs and 7 flushed uploads. As you say, the same-scene capture the performance bot asks for is still outstanding.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 5, 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* build needs rebase Conflicts with main, rebase needed perf numbers missing perf pull request without a before and after summary line perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants