Skip to content

fix(agc): build a partially committed sampled 3D view from its committed slices - #10064

Draft
EagleBearerYS wants to merge 10 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-3d-view-committed-slices-v2
Draft

EagleBearerYS wants to merge 10 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-3d-view-committed-slices-v2

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What

Ghost of Tsushima Director's Cut (PPSA02225) binds this sampled view about 9 minutes in, during its opening (milescoviello, Linux, RTX 4070 Ti, first comment):

[gpu] sampled view 0x1564050000 (320x1280 format 71, dimension 4, tile 7, slices 0..899) covers 4781 MiB
[gpu] view 0x1564050000 of 4781 MiB lies in the guest allocation 0x1500000000..0x1598600000 (2438 MiB, ...): 157 of its 900 slices fit in the range
  • The run ends at the cap: while the view is built, the host heap grows by about 3 GB in 2 s, and the run ends at the 20 GiB memory cap (563-572 s, twice).
  • This view is the one stop: with a local cut that kept only the 157 slices, the title went on to its intro cinematic (second comment).
  • Why that cut isn't a fix: a shallower volume moves every normalized r sample.

Why it costs that much: main already defines the texels past the committed range. ReadCommitted reads uncommitted pages as zeros. But it materializes the zeros at full size, and holds them three times on the way into the image:

  • a snapshot of the whole surface;
  • a host-visible staging buffer;
  • tiled and linear device buffers.

For this view that's about 9.3 GiB of host memory.

The change: a sampled k3D view whose range isn't wholly committed keeps its full extent, and only the slabs with committed memory go through the upload.

  • Eligibility: no storage-image source, Uncompressed keys and no DCC, no depth comparison, no sRGB expansion. Everything else takes main's path unchanged.
  • The slab list: committedSlabs splits the surface into its guest slabs. That's one slice in the per-slice Z/R 64KB_X branch or a linear volume, and blockDepth slices in a thick one.
    • It keeps every slab with at least one committed page, chosen by commitment, not by the allocation.
    • When every slab has one, main's path runs.
  • The snapshot packs only those slabs, one ReadCommitted per run of adjacent slabs. Uncommitted pages inside an uploaded slab still read as zeros.
  • The upload:
    • Staging, tiled and linear buffers cover only the uploaded slabs. Each slab is detiled from its packed offset, with its real slice index as the swizzle input.
    • Skipped layers get zeros copied from one vkCmdFillBuffer-zeroed device buffer with vkCmdCopyBufferToImage. That isn't a clear, so block-compressed formats take the same path.
  • The cache entry keeps the slab set and the view's committed ranges. A hit needs the same set, so a commit or decommit that changes which slabs are uploaded always rebuilds. It also needs every page that was committed when the snapshot was taken to be committed still, so a page decommitted inside a kept slab rebuilds and reads zeros. The compare and the write generation cover the uploaded bytes, newly committed pages included.
  • A log line next to covers … MiB, rate-limited the same way, says how many slabs were uploaded and how many zero-filled.
  • Unchanged:
  • Cost: one DescribeCommitted walk per lookup of an eligible k3D view (above), before the cache lookup, so hits pay it too, wholly committed views included. There's none when the lookup already holds the view's committed ranges.
    • For this view, if only the 157 slices are committed, the host side drops from about 9.3 GiB to about 1.6 GiB, twice the uploaded bytes.
    • The full-extent image itself is the same as main's.

#9318: this sits in the snapshot and upload path, not in #9318's view bound, so the two compose, and #9318 needs no k3D case. If BoundArrayWindow ever trimmed k3D views, it would run first and change the texels (a shallower volume).

Tested

Not tested: Ghost of Tsushima Director's Cut with this fix. milescoviello has it on test/ghost-9742-9717-3d (bc32434), the base he ran plus this commit (offer). That branch passes ^agc_driver_partially on Linux (run). On Windows, its older base fails to build a test that main has since fixed. This PR's own Windows build is the run below.

New modes of tests/execution/PartiallyResidentSnapshot.cpp (agc_driver_partially_<mode>, lavapipe):

  • Memory. Every volume is 64 deep, and each committed slab has its own pattern. The bytes read count the snapshot's guest reads; the bytes kept count what the upload holds (staging, tiled, linear and zeros):

    mode surface committed bytes read (main / this PR) bytes kept (main / this PR)
    committed-volume R_X, RGBA16F, 64x64, one 64 KiB slab per slice slices 0-21, plus 16 KiB of slice 22 4,194,304 / 1,507,328 12,582,912 / 4,587,520
    committed-volume-thick S_X thick, 4 slabs of 16 slices slab 0, plus half of slab 1 2,097,152 / 1,048,576 not reached on main / 3,178,496
    committed-volume-linear linear, 32 KiB slices 23, the last one half 2,097,152 / 753,664 not reached on main / 2,293,760
    committed-volume-compare as committed-volume 3 slabs (a 192 KiB snapshot, kept for comparison) 4,194,304 / 196,608 not reached on main / 655,360
    committed-volume-whole (control) as committed-volume all 64 4,194,304 / the same 12,582,912 / the same
    committed-array (control) a 2D array of 64 layers 23, the last one partial 4,194,304 / the same 12,582,912 / the same
  • Rebuilds (committed-volume):

    • a repeat lookup reads nothing;
    • committing slice 30 rebuilds and reads 24 slices;
    • decommitting slice 5 rebuilds and reads 23;
    • committing the rest of slice 22 rebuilds;
    • each repeat after those keeps the view.
  • Compares (committed-volume-compare): newly committed zero pages compare equal and keep the view, and a CPU write rebuilds it.

  • Texels (guest compute shaders sampling at slice centres, with z = 0, 5, 21, 22, 23, 30, 40 and 63, point-filtered):

    • Fully committed slabs read their pattern, and uncommitted slabs read zero. The partial slab reads its pattern or zero, and both occur.
    • An FNV-1a hash of every result word is identical on main and with this PR in every mode:
      • R_X, before and after commit changes;
      • BC1 at 256x256x64, where zero blocks read (0, 0, 0, 1) on both trees;
      • thick, linear, and two mip levels;
      • both controls.
    • The hashes are also identical with APS5_NO_TEXTURE_CACHE=1.
  • Red/green ×10 (the same test binary against each tree's libSceAgcDriver):

    • Red: on main, committed-volume, -thick, -linear and -compare fail 10/10 with "read N bytes of guest memory at its first use, expected M".
    • Green: the controls and every sampling mode pass on both trees, and with this PR all 15 modes pass 10/10. That run called the test binary's 15 modes directly. ctest registered 13 of them until 83c9480 added the mipmapped and BC1 cases.
  • Existing tests: agc_driver_partially_resident_snapshot, …_pending_stores and agc_driver_large_tracked_snapshot still pass.

  • GitHub's runners (run 39907685337, on this head, main 0db47b2; ^agc_driver_partially|^agc_driver_large_tracked|^agc_driver_image_sample|^agc_driver_texture):

    • Linux: red with main's implementation: on its one run, agc_driver_partially_committed_volume, …_thick, …_linear and …_compare fail and the other 23 of the 27 tests pass. Green with this PR: all 27 pass ×10, none skipped.
    • Windows: builds; the 27 tests skip there, since the runner has no Vulkan device.
    • The libc and libkernel suites pass on both, run with APS5_SKIP_TIMING_TESTS=1.
  • Validation layer (run 39913316066, Khronos 1.3.275 on Linux, this head; agc_driver_guest_memory_lifetime_copy, agc_driver_large_tracked_snapshot and ^agc_driver_partially): no new VUID.

    • The only VUID on either tree is VUID-VkDeviceCreateInfo-pNext-pNext, at device creation. It comes from an sType (1000574000) newer than the layer, and main's tree shows it too.
    • main's tree doesn't have this PR's new tests, so its arm passes; the red is the run above.
  • On a cloud Linux machine:

    • the texture, sample, snapshot, volume, tiling and image sets pass 141/141 (3 skipped for device features);
    • CI's Linux job mirrored: 1873/1877. The failures are the machine's four IPv6 network tests.
  • Windows: MinGW syntax checks of Texture.cpp, ShaderResources.cpp and the test show 0 errors.

  • check_conventions.py: passes. No comment lines are added.

  • AMD Radeon 860M on Linux (RADV, Mesa 26.2.5, Fedora 44), by @Lander0606 (comment): cd60c69 against its parent 0db47b2, the partially, snapshot and dcc_keys tests, three runs each with and without the Khronos layer. The base passes 8 of 8 and this PR 21 of 21 in every run. The seven -samples modes sample on the 860M with no skips, and the bytes read and kept match the table above. Under the layer neither side logs a VUID- line.

  • Windows on real GPUs, by @BKlaasWerkman (comment): cd60c69 merged into main 938812d (clean), Windows 11 Pro, WinLibs GCC 15.2.0, Release, ANYPS5_NO_SHADER_CACHE=1, with the Khronos validation layer (1.4.363) loaded. The 1,278 tests tools/select_tests.py picks for the PR's files, compared test by test with a full run of that main on the same GPU:

  • 4a90a1a, two merges of main (4be3da2, then 990f45e). Two ShaderResources.cpp conflicts, each kept both sides:

    • fix(agc): reclaim idle images safely during cache creation #7800 (merged) wraps the sampled texture's creation in a createTexture lambda that holds sampledCacheReclaimable. This PR's packed upload goes through it: createTexture(*context.detiler, resource, components, entry.bytes, depthCompare, packedSlabs(entry.slabs)).
    • perf(agc): reuse sampled snapshot byte buffers across cache entry replacement #8026 (merged) recycles snapshot buffers through a spare pool matched by size. A partly committed 3D view's snapshot holds only its committed slabs, so the pool is matched by that size, snapshotBytes(slabs, bytes), which is bytes whenever the view has no slabs.
    • On GitHub's runners (run), the partially resident, snapshot, DCC key and sampled-cache tests: on Linux, 27 of 27 pass 10 runs in a row; on Windows, 3 of 29 pass 10 runs in a row and 26 skip without a Vulkan device; none fail. The libc and libkernel suites pass on both, run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.
  • After thuanquang's review: 3871b5e, 0fbea36, c36790e and 83c9480 (review):

    • 0fbea36, fix(agc): a cached slab view is no longer returned after a page inside a kept slab is decommitted (the cache entry change above). The new agc_driver_partially_committed_volume_page_samples commits slabs 21 and 22, samples, decommits one 4 KiB page of slab 22, and samples again. Red with the fix's ShaderResources.cpp hunk reverted: "sample of level 0 slice 22 (thread 96) reads (23.000000, 712.000000, 1.000000, 1.000000), expected the zeros of its decommitted page". Green with it.
    • 3871b5e, test(agc): each element of the pattern encodes its position in the slab, and the -samples checks compare each texel with the same samples of a fully committed copy, which takes the full-size path. With the slab path's packed offset shifted by 0x100 in a local build, 5 of the 7 -samples cases fail (the other 2 don't take the slab path), where the earlier pattern passed all 7. An error shared by both paths, such as one in the common detile offset, still passes: these tests compare the two paths, and there's no CPU tiling oracle.
    • c36790e, test(agc): agc_driver_partially_committed_volume_empty and _empty_samples cover a volume with no committed slab. It takes the slab path with an empty slab list ("uploads the 0 of its 64 slabs"), reads 0 of its 4194304 guest bytes, and samples zeros.
    • 83c9480, test(agc): agc_driver_partially_committed_volume_mips and _volume_bc are registered with ctest. The BC1 case's own device now enables BC texture compression when it has it, and skips otherwise.
    • Each commit passes its own tests at that commit: 15, 16, 18 and 20 ^agc_driver_partially_ tests on lavapipe, on a Linux machine with CI's flags.
    • On GitHub's runners (run, ^agc_driver_partially_): Linux: with main's implementation of these files, agc_driver_partially_committed_volume_page_samples fails, and with this PR 20 run, 20 pass ×10, 0 skip, 0 fail. Windows: 20 run, 0 pass, 20 skip (no Vulkan device), 0 fail. The libc and libkernel suites pass on both, run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.
    • 6dd0c32, docs(debt): the debt entry now gives the view's guest range (4781 MiB, tiled and padded) where it said the image takes 4.7 GiB.
  • 52826ee, a merge of main 2fade7e. One conflict, in the includes of tests/execution/PartiallyResidentSnapshot.cpp: main (fix(agc): keep a rewatched import's memory compared once it is shared #9159) added <algorithm> and <span> there, which this PR also adds, so the merge keeps one of each and this PR no longer adds them. The PR's other added and removed lines are unchanged. On GitHub's runners (run), ^agc_driver_partially_ and agc_driver_large_tracked_snapshot:

    • Linux: with this PR, 21 run, 21 pass, 0 skip, 0 fail, 10 times in a row. With main's implementation of the changed files, 7 of the 21 fail, each on the guest bytes a partly committed volume reads at first use (for example, "the partially committed volume read 4194304 bytes of guest memory at its first use, expected 1507328 of its 4194304").
    • Windows: 21 run, 0 pass, 21 skip (no Vulkan device), 0 fail.
    • The libc and libkernel suites pass on both (143 and 205 tests on Linux, 170 and 221 on Windows), run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

Overlap (tools/pr_overlap.py --branch against main 5255264; this branch merges cleanly with main):

Checklist

…ted slices

Ghost of Tsushima Director's Cut (PPSA02225) on Linux with an RTX 4070 Ti
samples this view, as milescoviello reported in
boykopovar#9318 (comment)
and boykopovar#9318 (comment):

  [gpu] sampled view 0x1564050000 (320x1280 format 71, dimension 4, tile 7, slices 0..899) covers 4781 MiB

157 of its 900 slices fit in its guest allocation, and the run dies at
the 20 GiB cap. The snapshot path read the whole 4781 MiB range into a
host vector and copied it into a host-visible staging buffer of the same
size, about 9.3 GiB of host memory, then detiled it through a tiled and
a linear device buffer of that size again.

A sampled 3D view whose range is not wholly committed now reads, stages
and detiles only the slabs that hold a committed page, packed together
(a slab is one slice of a Z_X or R_X volume, blockDepth slices of a
thick one). Uncommitted pages inside such a slab still read as zeros.
The image keeps the view's full extent, and every other slice is copied
from one slice of zeros filled on the GPU, the texels its zero bytes
detiled to before. The upload's host memory is about twice the uploaded
bytes: about 1.6 GiB instead of 9.3 GiB if only the 157 slices inside
the allocation are committed.

The cache entry keeps the slab list, so a commit or decommit that
changes it rebuilds the view; the write generation and the compare
cover the uploaded slabs. DCC, fast-clear keys, depth comparison, sRGB
expansion and storage image views keep the whole-range path. The new
log line gives the uploaded slab count and the zero-filled slice count.

Whether the title gets past this view is not verified. Thanks to
milescoviello for the report and the logs.
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

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

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L361, L488-489, L491-495
PR 8930 docs/dev/debt/libSceAgcDriver.md L272
PR 9884 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp L62
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L975-976, L1007, L1015
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L337, L361, L417-420
PR 9891 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L451-453, L457
PR 9892 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L451-453, L457
PR 9893 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L451-453, L457
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L451-453, L457
PR 10773 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L416-420, L436-439, L447-448
PR 10800 docs/dev/debt/libSceAgcDriver.md L272
PR 10845 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp L62
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L974-976, L1007, L1015
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L337, L361, L417-420
PR 11068 docs/dev/debt/libSceAgcDriver.md L272

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 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6743 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6959 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6960 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6971 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
docs/dev/debt/libSceAgcDriver.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7689 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8161 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8998 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9020 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9176 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9248 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9287 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9289 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9335 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9356 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9393 docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9592 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9672 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9673 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9674 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9675 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9676 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9700 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9852 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9904 docs/dev/debt/libSceAgcDriver.md
PR 9905 docs/dev/debt/libSceAgcDriver.md
PR 9906 docs/dev/debt/libSceAgcDriver.md
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 9989 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10049 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10194 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10233 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10435 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10442 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10886 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10940 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md

@Lander0606

Copy link
Copy Markdown

A hardware data point next to lavapipe: AMD iGPU on Linux (RADV), head cd60c6916f against its parent 0db47b263d, without merging onto current main.

Setup

  • Radeon 860M (Ryzen AI 7 350, 1002:1114, RADV KRACKAN1), Mesa 26.2.5, Vulkan 1.4.354, Fedora 44, kernel 7.2.11.
  • Built in an Ubuntu 24.04 container with GCC 13.3, Release, BUILD_TESTING=ON, ANYPS5_ENABLE_SPIRV_TOOLS=ON. The tests ran on the Fedora host, with VK_DRIVER_FILES limited to RADV.
  • ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 ctest -j1 -V -R 'partially|snapshot|dcc_keys', three runs without and three with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (1.4.341).
Without the layer With the layer
base 0db47b263d (8 tests) 8/8, 8/8, 8/8 8/8, 8/8, 8/8
this PR cd60c6916f (21 tests) 21/21, 21/21, 21/21 21/21, 21/21, 21/21
  • No skips: the seven -samples modes selected the 860M and sampled on it.

  • Memory: the bytes match the table in the description exactly:

    mode bytes read bytes kept
    committed-volume 1,507,328 of 4,194,304 4,587,520
    committed-volume-thick 1,048,576 of 2,097,152 3,178,496
    committed-volume-linear 753,664 of 2,097,152 2,293,760
    committed-volume-compare 196,608 of 4,194,304 655,360
    committed-volume-whole 4,194,304 of 4,194,304 12,582,912
    committed-array 4,194,304 of 4,194,304 12,582,912
  • Validation: the loader inserted VK_LAYER_KHRONOS_validation 7 times per run on the base and 20 times on the head (VK_LOADER_DEBUG=layer). No log contains was not found but was requested by env var VK_INSTANCE_LAYERS, and neither side logs a VUID- line.

I didn't run Ghost of Tsushima.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@Lander0606 thank you, the seven -samples modes running on the 860M with no skips, and the bytes matching the table, are a useful check on real hardware. It is in the Tested section now.

@BKlaasWerkman

BKlaasWerkman commented Oct 23, 2026 •

Copy link
Copy Markdown

Windows run on NVIDIA and Intel.

Windows 11 Pro 10.0.26300, WinLibs GCC 15.2.0 r7, Release. NVIDIA GeForce RTX 3060 Ti (driver 617.42) and Intel Arc Graphics (Meteor Lake, driver 101.9150), selected with ANYPS5_GPU. Head cd60c69 merged into main 938812d (clean merge). ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (1.4.363); no was not found but was requested line.

Tests: the 1278 that tools/select_tests.py select picks for the PR's files, compared per test with a full run of the same main on the same GPU.

this PR compared with main
RTX 3060 Ti 1271 passed, 4 skipped, 3 failed same 3 failures (agc_driver_sample_order trio, #9056); no new or removed VUID-
Intel Arc 1250 passed, 26 skipped, 2 failed same 2 failures (#10126, #8604); no new or removed VUID-

All 13 new agc_driver_partially_committed_* tests pass on both GPUs, and none skips. That includes the seven -samples modes (_volume_bc_samples among them), _volume_compare and _volume_thick. Intel Arc's agc_driver_alias_copy_label failed in the main run and passed here; it is intermittent on that device (#10199).

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

Thanks, @BKlaasWerkman. All 13 new tests on an RTX 3060 Ti and an Intel Arc, with the seven sample modes running and nothing new under the layer, covers the vendors this PR hadn't reached yet. It's in the Tested section now.

# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp

@thuanquang thuanquang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A read-through at 4a90a1a1d0; I built and ran nothing. The slab mapping and the packed detile offsets look consistent with main's geometry, and the head merges cleanly into current main. A few things a reviewer will likely ask about:

Tests

  • PatternElement (PartiallyResidentSnapshot.cpp:499-504) depends only on the format and the slab, and CommitSlab (:516-521) repeats that one element across the whole slab. So the -samples checks can tell which slab a texel came from, or zero, but not where inside the slab it was read. The main new risk is the per-slab packed detile offset (slot.tiledOffset + mip.tiledOffset), and a misplaced offset inside a slab would still pass. An element value that encodes its position (for example x, y and z within the slab) would cover it.
  • No case has every slab uncommitted, so the branch where uploaded is empty (Texture.cpp:420-425, :443-447; no staging, tiled or linear buffers, zero snapshot bytes on the cache side) never runs.
  • CommittedCaseFor accepts --committed-volume-mips and --committed-volume-bc (:474-475), but CMakeLists.txt:2546 registers only their -samples variants. So the mip and BC byte accounting is never run by ctest, and the body's "all 15 modes" doesn't match the 13 registered tests. The -samples modes also pass on main; only the memory modes are red there.

Behaviour

  • Slabs are chosen by commitment (committedSlabs, ShaderResources.cpp:713-723), not by the view's allocation. If the range past the allocation is committed by neighbouring allocations, every slab has a committed page, :723 returns nullopt and the view takes main's full-size path unchanged. Has PPSA02225 been run with this head? The body's logs come from a stack with other open PRs, and the local cut that pointed at this view kept slices by allocation.
  • A question rather than a finding: when the packed snapshot falls under SnapshotCompareBytes (256 KiB, :1067), it is kept for comparison where main's full-size snapshot was parked. A later lookup with the same slab set then goes through equalsCommittedSlabs, which compares committed pages only. So if part of a slab is decommitted while the slab keeps other pages, is the cached texture returned with the old texels instead of rebuilt with zeros?

Body

  • The cost line ("only for k3D views whose range isn't wholly committed") reads narrower than the code: committedSlabs runs whenever slabbed holds (:924), including wholly committed k3D views with write generation 0, and before the cache lookup, so hits pay it too.
  • Depends on: lists #9253 (merged today) and #9884 (closed without merging); the Overlap section still describes composing with #9884.
  • The debt entry's "the image takes 4.7 GiB" is the guest (tiled, padded) byte count, not the VkImage size.

…tern

The committed-volume pattern repeated one element per slab, so a
-samples check could not tell where inside a slab a texel was read
from: a wrong packed offset for a slab still read that slab's value.

Each element now carries its slab and its element index within the
slab (BC1 blocks carry the slab and a hash of the block index). A
sample's expected texel is the same sample of a fully committed copy of
the view, which takes the full-size path; the checks also require that
reference to hold a texel of the sampled slab.
…b is decommitted

A partially committed volume's cached view was kept while the set of
slabs holding committed pages was unchanged and its committed pages
still matched the snapshot. The comparison only covers the pages
committed now, so a page decommitted inside a kept slab was skipped:
the lookup returned the old texture with that page's old texels, where
a fresh build reads zeros.

CommittedSlabs now also keeps the view's committed byte ranges,
relative to its base, as committedSlabs already reads them, and a
lookup is refused unless the ranges committed now still cover every
range committed when the snapshot was taken. Pages committed since are
left to the comparison, as before: the snapshot holds zeros there, so
zero pages committed later keep the view.

agc_driver_partially_committed_volume_page_samples commits two slabs,
samples them, decommits one page inside the second, and requires the
texels of that page to read zero and the slab's others to keep theirs.
No committed-volume case left every slab uncommitted, so the upload
with no packed slab (no staging, tiled or linear buffer, and an empty
snapshot in the cache) never ran. committedSlabs returns nullopt only
when every slab is committed, so such a view takes the slab path with
an empty slab list.

agc_driver_partially_committed_volume_empty checks that path: the
lookup reads no guest bytes, where the full-size path reads the whole
surface, it is kept on the next lookup, and the upload keeps only the
zero slice. agc_driver_partially_committed_volume_empty_samples samples
it and requires zeros everywhere.
CommittedCaseFor accepts --committed-volume-mips and
--committed-volume-bc, but only their -samples variants were
registered, so the mip and BC1 byte accounting never ran under ctest.
Both run now as agc_driver_partially_committed_volume_mips and
agc_driver_partially_committed_volume_bc, with their neighbours'
properties.

Registered, the BC1 case failed with "device does not support BC
compressed textures": the test's own Vulkan device, which the
non-sampling cases use, did not enable BC texture compression. It now
enables it when the device has it, and the BC1 case skips when it does
not.
…n image size

The entry said the Ghost of Tsushima Director's Cut view's image takes 4.7 GiB. That figure is the view's guest byte count, tiled and padded, not the size of the Vulkan image.
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@thuanquang thank you for the read-through, and in particular for the decommit question: you were right. EqualsCommitted compares only the pages committed now, so a page that was committed at the snapshot and decommitted since was skipped, and the cached view kept its old texels. main's full-size path has the same gap.

The head is now 6dd0c32, with five new commits:

  • Decommit (0fbea36): a hit on a slab view now also needs every page committed at the snapshot to be committed still, so a page decommitted inside a kept slab rebuilds and reads zeros. The new agc_driver_partially_committed_volume_page_samples decommits one page of a kept slab after the first sample. It fails without the fix ("reads (23.000000, 712.000000, …), expected the zeros of its decommitted page") and passes with it. Newly committed zero pages still keep the view through the compare, as committed-volume-compare checks, so this doesn't compare whole range lists.
  • Position-encoding pattern (3871b5e): each element now encodes its position in the slab, and the -samples checks compare against a fully committed copy of the view, which takes the full-size path. With the slab path's packed offset shifted by 0x100 in a local build, 5 of the 7 -samples cases fail; the old pattern passed all 7. An error both paths share, such as one in the common detile offset, still passes, since there's no CPU tiling oracle.
  • All slabs uncommitted (c36790e): --committed-volume-empty and -empty-samples. The view takes the slab path with an empty list, reads 0 of its 4194304 guest bytes, keeps only the one zero slice, and samples zeros.
  • Registration (83c9480): --committed-volume-mips and --committed-volume-bc are now ctest entries. The BC1 case's fixture now enables BC compression when the device has it, and skips otherwise. You were right about the body: the "all 15 modes" run called the test binary directly, and ctest had 13.
  • Debt entry (6dd0c32): it now gives the view's guest range (4781 MiB, tiled and padded) instead of an image size.

On your other points:

The GitHub run for the new commits passes 20 of 20 ^agc_driver_partially_ tests ×10 on Linux, with the decommit case red without the fix; on Windows they skip (no device) (run).

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 24, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 24, 2026
@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS about a merge order with #10845, which reads a large watched texture straight into its upload staging buffer instead of a snapshot vector. It overlaps this PR in cachedTexture (entry creation, the read, the createTexture call) and in Texture's snapshot constructor. #10845 gives Texture a constructor that takes the staging Buffer, which the span constructor delegates to. A packed-slab snapshot has to stay on the vector path, since its bytes aren't the surface's layout. So the merge would add !entry.slabs-style exclusion to #10845's staged condition (or compute staged after snapshotSize), and give the staging constructor your packedSlabs argument for the span constructor to forward. Since this PR is a draft, I propose #10845 goes first, merge order only, and I'll do that merge on your branch's side if it helps. Does that resolution match how you'd want the slabs handled?

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@milescoviello Thanks, #10845 first works for this PR, merge order only. Your resolution matches this PR's code. A packed-slab snapshot holds only the committed slabs, in order, so it isn't the surface's layout and has to stay on the vector path. Here slabs is computed before the entry is built, so the exclusion is a !slabs.has_value() term on #10845's staged. The slab-aware upload (packedSlabs, and the uploaded and zero-filled slices it picks) moves with the constructor body into your staging constructor, with the span constructor forwarding packedSlabs. Thanks for offering to do the merge too; since this PR is still a draft, I'll do it on our side when #10845 lands, and I'll add #10845 to this PR's Depends on.

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS thanks, and for the detail on where slabs is computed: a !slabs.has_value() term on staged is simpler than what I described. I've noted the order on #10845's checklist.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 25, 2026
Main's boykopovar#7618 adds its swept write-only staging entry at the end of the
debt file, where this branch adds its partially committed 3D textures
entry; the merge keeps both.

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 docs unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants