Skip to content

feat(libSceAgcDriver): storage image access to depth surfaces - #4500

Merged
oneandonlydean merged 5 commits into
boykopovar:mainfrom
ee6aba54:feat/agc-depth-surface-storage
Oct 13, 2026
Merged

oneandonlydean merged 5 commits into
boykopovar:mainfrom
ee6aba54:feat/agc-depth-surface-storage

Conversation

@ee6aba54

@ee6aba54 ee6aba54 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

Compute shaders bind the depth and stencil planes of a depth surface as storage images, to read the depth or to write it before later draws test against it. The driver kept depth on the GPU and refused such bindings ("storage image access to depth/stencil surface ... is not implemented").

A depth surface now owns one storage image per plane (R32_SFLOAT or R16_UNORM for depth, R8_UINT for stencil). Binding applies a pending fast clear and copies the plane into the storage image through a staging buffer (the depth image is created with VK_IMAGE_USAGE_TRANSFER_SRC_BIT for that copy); a written element is copied back into the depth image during write-back, so the next draw sees the shader's depth. A view of another extent, or of a format that is not the plane's, takes the ordinary texture path, as DepthSurfaceAt already decides for sampled views; a view of another level still throws.

A D16_UNORM_S8_UINT surface that the device stores as D24_UNORM_S8_UINT or D32_SFLOAT_S8_UINT holds 4 bytes per depth texel, so a storage view of its depth plane throws, naming both formats; its stencil plane is supported.

This is the storage counterpart of the depth copy that comparison sampling takes since d024709. The refused binding is the stop reported in #1409 (PPSA10112); that title is not tested here.

Tested

  • Ubuntu 24.04, Linux 7.0, GCC 13.3, Release, based on 1c5e62878. ctest: 996 tests pass.
  • New agc_driver_depth_storage (RTX 5080) checks both copy directions for depth and stencil, the reuse of the storage image, the fall-through for another extent or format and the refusal of another level; agc_driver_depth_storage_reuse and agc_driver_depth_compare_copy still pass.
  • agc_driver_graphics: on a device without D16_UNORM_S8_UINT, a storage view of the depth plane of a D16 surface with stencil throws.
  • PPSA15304 (engine: Unreal Engine 5): copies depth and stencil of a 2848x1600 D32_SFLOAT_S8_UINT target through storage images every frame; the dispatch that stopped the title now runs, and together with fix(agc): count every CP data write of a dispatch group as a write of the run #4586 and layered rendering into 3D color targets (the change feat(agc): render layered draws into 3D color targets #1077 proposes) the title reaches its main menu.
  • Windows not built or tested.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions github-actions Bot added the agc libSceAgc* label Oct 12, 2026
@xXPBSXx

xXPBSXx commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Windows build of head dfa876696 merged onto current main (d592d54e1, clean merge): Windows 10 (19045), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release. The driver and agc_driver_graphics_tests, agc_driver_depth_storage_reuse_tests and agc_driver_depth_compare_copy_tests build, but the new test does not compile:

tests/execution/DepthStorage.cpp:239:55: error: 'aligned_alloc' is not a member of 'std'; did you mean 'aligned_union'?
  239 |         auto* block = static_cast<std::uint8_t*>(std::aligned_alloc(65536, BlockBytes));

std::aligned_alloc is not provided by the MinGW-w64 C runtime. The neighbouring tests avoid it (DepthStorageReuse.cpp uses alignas(4096) std::array for the guest memory); a 64 KiB alignas array or a platform switch to _aligned_malloc/_aligned_free would build on both systems. std::free at line 249 would have to follow, since memory from _aligned_malloc needs _aligned_free.

Not run: the GPU tests (agc_driver_depth_storage, agc_driver_depth_storage_reuse, agc_driver_depth_compare_copy) are waiting for the machine to be free; I will post their result here. Related: #425 also addresses #1409 (sampling and storage access to depth surfaces) and breaks agc_driver_depth_storage_reuse on main (#425 (comment)); the two PRs overlap in DepthSurface.cpp and ShaderResources.cpp.

AI-assisted (Claude Code).

@ThatsLeo

Copy link
Copy Markdown
Contributor

@ee6aba54 I ran the stencil test from my follow-up to #425 against this PR (dfa87669, merged with main). It passes on Intel Arc (Mesa 26.2.3 ANV) and lavapipe, and on plain main it fails with storage image access to depth/stencil surface ... is not implemented. The other depth tests (depth_storage, depth_storage_reuse, depth_compare_copy, depth_fast_clear*, depth_bias_per_face, stencil_ref_export) pass on both devices with this PR.

It checks the one path DepthStorage.cpp doesn't: a stencil plane written through a storage image, and seen by the next draw's stencil test. On a D32S8 surface:

  1. a draw writes depth 0.5 and stencil 1;
  2. two compute dispatches write the first 32 texels, R32 through the depth plane and R8 through the stencil plane;
  3. draws then check:
    • a stencil test passes exactly those 32 texels;
    • the other texels still hold 1;
    • a depth test sees the 32 texels written through the depth plane, alongside the stencil writer.

It's a single file, tests/execution/DepthStencilStorage.cpp (agc_driver_depth_stencil_storage), on ThatsLeo/AnyPS5:feat/agc-depth-stencil-storage-v2. Take it into this PR if it's useful.

AI-assisted: yes.

MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 12, 2026
A texture over a depth surface could only sample one 2D slice of the
plane's own format; 2D arrays, cubes and the R16_UINT view of a D16
plane were refused ("sampling the depth plane of depth surface ... is
not implemented"). A one-slice 2D array now views the depth image as an
array. Several slices, a cube, or an integer view of a D16 plane are
copied into a DepthPlaneCopy image at every use: each slice from the
depth surface bound at its address (base plus one 64 KiB-block depth
slice per layer), otherwise from guest memory, detiled. A depth-bits
R16_UINT view (a depth swizzle) keeps the depth copy, since the shader
converts the sampled depth to its bits itself (GTA V's compute
0x242d4a200 samples a D16 plane array that way, reported by
@tibzejoker). Such a texture is not kept in resource templates.

Storage-image access to depth surfaces is no longer part of this
change; boykopovar#4500 implements it.

TechnicalDebt (Depth plane copies) and ARCHITECTURE describe the copy and
what still throws.
@MichonGoddijn231849

Copy link
Copy Markdown
Contributor

For coordination: #425 had a storage path for depth surfaces too. I've taken it out, so storage access to depth surfaces is this PR's alone. #425 now only samples depth planes as 2D arrays, cubes and R16_UINT views (DepthPlaneCopy). The two still touch DepthSurface.cpp, but in different functions (DepthSurfaceTexture and its copy class there, the storage seed and take-back here). One point to check against this PR: #425's copy needs VK_IMAGE_USAGE_TRANSFER_SRC_BIT on the depth image as well.

@ee6aba54

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. Updated to 0d32f7e00, rebased onto 2a5eca11c:

  • @xXPBSXx: the test's guest block is now a statically allocated buffer aligned to 64 KiB by hand, so std::aligned_alloc and std::free are gone.
  • @MichonGoddijn231849: right, the copy into the staging buffer reads the depth image, so the image is now created with VK_IMAGE_USAGE_TRANSFER_SRC_BIT.
  • @ThatsLeo: thanks for the stencil test; it covers a path DepthStorage.cpp does not. I would rather not take another author's file into this PR; it fits well as a follow-up of yours once this merges.

ctest: 960 tests pass on Linux (RTX 5080).

@ee6aba54

Copy link
Copy Markdown
Contributor Author

Updated to cdfba5144, rebased onto 5efd09afc:

ctest: 993 tests pass on Linux (RTX 5080).

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Data point from a title we test with, on main 682ef0c with the head before cdfba51: on main it stops before its title screen at storage image access to depth/stencil surface 0x53a500000 is not implemented (storage=960x540 guest format=22 mip=0 dimension=1 tile=4), compute shader 0x50073d500. With this PR it gets past that and stops at the depth plane sampling refusal that #425 covers. With both, 330 s with no stop, error line or device loss.

oneandonlydean pushed a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 12, 2026
A texture over a depth surface could only sample one 2D slice of the
plane's own format; 2D arrays, cubes and the R16_UINT view of a D16
plane were refused ("sampling the depth plane of depth surface ... is
not implemented"). A one-slice 2D array now views the depth image as an
array. Several slices, a cube, or an integer view of a D16 plane are
copied into a DepthPlaneCopy image at every use: each slice from the
depth surface bound at its address (base plus one 64 KiB-block depth
slice per layer), otherwise from guest memory, detiled. A depth-bits
R16_UINT view (a depth swizzle) keeps the depth copy, since the shader
converts the sampled depth to its bits itself (GTA V's compute
0x242d4a200 samples a D16 plane array that way, reported by
@tibzejoker). Such a texture is not kept in resource templates.

Storage-image access to depth surfaces is no longer part of this
change; boykopovar#4500 implements it.

TechnicalDebt (Depth plane copies) and ARCHITECTURE describe the copy and
what still throws.
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
@ee6aba54
ee6aba54 force-pushed the feat/agc-depth-surface-storage branch from cdfba51 to ac2ffc4 Compare October 13, 2026 00:33
@ee6aba54

Copy link
Copy Markdown
Contributor Author

Rebased onto 1c5e62878 (#425 merged): ac2ffc48f. The conflicts were adjacent additions in DepthSurface.cpp (the DepthPlaneCopy class next to Storage and StoreBack) and in DepthSurfaceReuse.cpp (both D16 checks are kept). No change to the code of this PR. ctest: 996 tests pass. @jdmagic #2076 needs the new head as its base.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
@fma-rc

fma-rc commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Astro's Playroom (PPSA01325) data point for head ac2ffc48f, Linux, RTX 5080.

On main 1c5e628 it enters its first level, renders 34 frames in 80 s and then stops with compute shader 0x50028e000: AGC graphics: storage image access to depth/stencil surface 0x54d420000 is not implemented (storage=1920x1080 guest format=22 mip=0 dimension=1 tile=4).

With this PR merged onto main 284a1c3 the stop is gone in four of four runs of about four minutes each (all with a local frame-capture patch, two also with an unrelated local driver change), and the frame I captured in the level's opening scene renders correctly. It is slow there: 0.4 to 0.8 fps, Graphics::Draw at 300 to 520 us per draw packet under APS5_PROFILE_DRAW=1. I have not looked at where that time goes, so I can't say whether this PR has a part in it.

agc_driver_depth_storage and agc_driver_depth_storage_reuse pass on RTX 5080, Radeon 610M (RADV) and lavapipe, on main 4430780 plus this head.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Pushed two commits on top of your rebase (848b97b), fast-forward, yours untouched:

  • 26dfc464a7: three cases the plane copy cannot serve now throw instead of going through. A storage view that is not 2D throws main's storage image access to depth/stencil surface ... is not implemented, as before this PR; a store to a multisampled depth surface throws; and StoreBack through a view that another view of the same plane replaced throws, since its writes would be dropped. The debt entry lists them.
  • 848b97b611: agc_driver_depth_storage_dispatch, which goes through dispatches: depth and stencil stores read back by depth- and stencil-tested draws and in guest memory after the flush, plus the three refusals. agc_driver_depth_storage alone still passed with the two DepthSurfaceStoreBack calls removed.

Linux, RTX 4090: ctest -R "agc|shader" 463/463, validation layer clean on the depth tests, and the new test passes on llvmpipe. Not built on Windows.

The 2D-array case is the one The Pathless (PPSA01825) hits in the Windows report above, so it stops there again until one-layer array views are handled; that is a separate change.

This will conflict with #2491 in DepthSurface.cpp once that is merged. I'll rebase it then; StoreBack also has to bump the write serial there.

ee6aba54 and others added 5 commits October 13, 2026 15:00
Compute shaders bind the depth and stencil planes of a depth surface as
storage images, to read the depth or to write it before later draws
test against it. The driver kept depth on the GPU and refused such
bindings.

A depth surface now owns one storage image per plane (R32_SFLOAT or
R16_UNORM for depth, R8_UINT for stencil). Binding copies the plane
into it through a staging buffer; a written element is copied back into
the depth image during write-back, so the next draw sees the shader's
depth.

A view whose extent differs from the surface's means the memory was
reused for another texture and takes the ordinary texture path; this
applies to sampled views of a depth surface too.

PPSA15304 copies depth and stencil through storage images every frame.
A D16_UNORM_S8_UINT surface is stored as D24_UNORM_S8_UINT or
D32_SFLOAT_S8_UINT on devices without the 16-bit format. Copying its
depth aspect yields 4 bytes per texel, so the 16-bit storage image of
the depth plane would receive the wrong layout. Such an access now
throws, naming the surface and both formats; the stencil plane keeps
its 8-bit layout and stays supported.
A 2D array or cube view with the plane's extent and format reached the
ordinary path and read guest bytes the depth was never written to; it
throws again, with the message main has for it. A multisampled surface
is refused before the plane copy, as sampling it already is. A written
view that another view of the same plane replaced had its results
dropped at store-back; that throws now.

Update the debt entry to what is supported and what still throws.
Draw depth and stencil, read and rewrite each plane in compute
dispatches, and check the stored values with depth-tested and
stencil-tested draws, a following dispatch and the flushed guest bytes.
The array view, multisampled and two-view refusals are checked through
dispatches as well.
A sampled copy of a depth plane is kept while the surface's write
serial is unchanged. Storing a written storage view back into the
depth image did not bump the serial, so a later draw or dispatch that
sampled the plane as an array or cube kept the values from before the
store. The store-back bumps it now.

The dispatch test samples the plane through a copy, stores to it in a
dispatch and samples it again.
@oneandonlydean
oneandonlydean force-pushed the feat/agc-depth-surface-storage branch from 848b97b to f15671e Compare October 13, 2026 02:22
@github-actions

github-actions Bot commented Oct 13, 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
#4551 docs/dev/debt/libSceAgcDriver.md L28-30
#4868 docs/dev/debt/libSceAgcDriver.md L28-30

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
@oneandonlydean
oneandonlydean merged commit 0d54077 into boykopovar:main Oct 13, 2026
8 checks passed
danielradosa added a commit to danielradosa/AnyPS5 that referenced this pull request Oct 13, 2026
127 files conflicted. Resolved per group (AgcDriver depth / graphics / execution,
shader recompiler + libSceAgc, libc, libkernel, net/np, media, Usbd, misc, docs),
each group checked against both sides by a second pass.

Kept from local: the Astro Bot perf work (draw prep helper, per-unit pending
image checks, depth plane copies only for changed slices, layer copy on the
device, register clear without the key-fill wait, GDS DMA_DATA without drains,
32-bit fault words for validated V# atomics, request version 22 with
frontFaceAllBits), local swscanf_s/vsscanf_s, cpuset_getaffinity, Mbed TLS,
the libSceUsbd transfer tracking on top of main's libusb path, the
libReactNative VSH modules main deleted (boykopovar#5010), the Ngs2 GeomApply and
AudioIn Hq/Async open implementations.

Took main: sceKernelFcntl (File::Fcntl), sceCoredumpDebugTextOut, asinl,
nan/nanf, the depth/stencil plane storage path (boykopovar#4500/boykopovar#2076) in
cachedStorageTexture, the epoll model in libSceHttp, Json2
setSpecialFloatFormatType storing any value (RE4, PRAGMATA call it),
the per-library debt files under docs/dev/debt.

Post-merge fixes: DrawKeyRegisters holds 50 ranges, draw prep passes the
hidden user words, duplicate VideoOut stubs and libc definitions removed,
APS5_LIST_ALL_COMPLETION_WRITERS also restores whole-write overlap tests in
the readers, tests adjusted where the kept implementation changed on purpose
(fcntl, pm4 message, pixel push slots, v19 request layout, font syllable
state, Json2, depth plane drawn note, atomic fault words).

Build clean, test_fast 1125/1125 (guest_raise_exception and
guest_video_out_high_frame_rate pass alone; parallel-load timing).
Progress 3248/3529 -> 3387/3716.
danielradosa added a commit to danielradosa/AnyPS5 that referenced this pull request Oct 13, 2026
…ed path

SILENT HILL: The Short Message (PPSA10112) stopped at
"compute shader 0x2007a70000: AGC graphics: storage image access to depth/stencil surface
0x205da70000 is not implemented (storage=1024x1024 guest format=7 mip=0 dimension=2 tile=4)":
a compute shader binds a depth surface as a 2D array storage image of more than one layer.
Main's depth plane storage path (boykopovar#4500/boykopovar#2076), which replaced our boykopovar#425 port in the
e965b46 merge, takes one layer only.

DepthSurface::Storage now leaves a depth (not stencil) 2D array from layer 0 to the ordinary
storage path, and cachedStorageTexture seeds that image from the depth slices again
(SeedStorageFromDepth: every layer that is a depth surface of its own is copied in, the
surface becomes its writer and takes its writes back when it is drawn to; the code the merge
left without a caller). Stencil arrays and arrays from a later layer still refuse.

Tests: agc_driver_depth_storage expects the two-layer depth view to be left to that path;
agc_driver_depth_storage_dispatch now stores through a two-layer 2D array view and requires
the depth test to see layer 0's stored row (was: expected the refusal). test_fast 1126/1127
(windows_import_modules = Defender WinError 225).
TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
Compute shaders bind the depth and stencil planes of a depth surface as
storage images, to read the depth or to write it before later draws
test against it. The driver kept depth on the GPU and refused such
bindings.

A depth surface now owns one storage image per plane (R32_SFLOAT or
R16_UNORM for depth, R8_UINT for stencil). Binding copies the plane
into it through a staging buffer; a written element is copied back into
the depth image during write-back, so the next draw sees the shader's
depth.

A view whose extent differs from the surface's means the memory was
reused for another texture and takes the ordinary texture path; this
applies to sampled views of a depth surface too.

PPSA15304 copies depth and stencil through storage images every frame.

(cherry picked from boykopovar#4500, commit 0d32f7e, onto local/main-sync-20261011b)
Conflicts with the local depth/stencil storage alias path (dd16d16):
both kept; the plane storage images are tried first, the cache plus
SeedStorageFromDepth handle the rest. This base has no StorageTexture::Create
(make_shared instead) and no RequireNoMultisampleTarget (no MSAA targets).
TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
…s path

lean-x10 died at 1230 s on compute 0x8000405d00: a 3840x2160
R16G16B16A16_SFLOAT storage image at the address of a D32 depth surface
of the same extent reached the local alias path (SeedStorageFromDepth ->
Transfer), which refused it. boykopovar#4500's plane storage already returns
nullptr for that view; the alias path now applies the same rule: a view
of another extent, or in a format that cannot hold the plane's bits, is
reused memory (ordinary storage image, no seed, no copy-back).

Glue between the two paths: a plane's own storage image is stored back
right after its writes and is skipped by the deferred copy-back, but the
ownership guard still sees it; binding it first lands pending alias
writes; a stencil plane with a programmed SW_MODE or HTILE keeps the
alias layout checks; plane copies fall back to a command batch when no
recorder is active and take the GPU lock like the rest of the file.
TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
lean-x10 went over the VRAM budget (vk_device 13.5 GB in 34k allocations,
23k images, 51k buffers) with no way to tell who holds it. Creation sites
now name their owner with a thread-local MemoryStats::OwnerScope (sampled
and storage textures, depth surfaces and boykopovar#4500's plane storage, attachment
proxies, depth copies, render targets, colour transfers, texture uploads,
guest-buffer staging shadows, mirrors, host imports, unit-shadow slabs,
draw snapshots and scratch, ShaderResources buffers, BDA, workgroup
memory, presentation). The device wrapper charges vkAllocateMemory to the
innermost owner (the buffer pool's own allocations to pool_device /
pool_host / pool_slab) and counts live VkImages/VkBuffers per owner;
pooled Buffer/DeviceBuffer objects count as live under the owner that
asked (pooled_<owner>), so a pool tier splits into users and retained
rest. The [mem] line gains devmem_/hostmem_/importmem_/img_/buf_/pooled_
fields (non-zero only) and vk_device_unattributed. Off unless
APS5_MEM_STATS is set: a scope then costs one static check; nothing
under core/shader/recompiler changes (SourceVersion 16c99fcab2ec7b8c).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants