Skip to content

feat(agc): storage image access to depth planes through 2D array views - #5003

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
tibzejoker:feat/depth-storage-array-views
Oct 14, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
tibzejoker:feat/depth-storage-array-views

Conversation

@tibzejoker

@tibzejoker tibzejoker commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

A storage view of a depth plane as a 2D array (base slice 0, level 0, the plane's extent and format) used to throw storage image access to depth/stencil surface ... is not implemented (... dimension=2 ...). It now gets a storage image over all its slices:

  • Each slice that has a depth surface at the plane's address plus a whole depth slice per layer (DepthSliceBytes, the same slice addressing as the sampled depth plane copies) is copied from that surface before every use. After the work it is copied back to the surface. Both directions go through a staging buffer.
  • The other slices hold guest memory, like any storage image. A written view stays pending, so the slices reach guest memory on the next flush, the surface slices included.
  • The image is kept per address, extent, slice count, format and tile mode until the device goes away.

Still refused, with their current messages:

  • cubes;
  • 2D arrays of the stencil plane;
  • arrays from a later slice;
  • other levels;
  • multisampled surfaces;
  • a layer pitch that differs from the depth slice size.

The debt entry says what is supported and what still throws.

Why

GTA V (PPSA04264) stops on current main in Story mode at compute shader 0x242d4a200:

storage image access to depth/stencil surface 0x2946a0000 is not implemented (storage=512x512 guest format=11 mip=0 dimension=2 tile=4)

Its storage image is a 5-slice R32 2D array in Z_64KB_X over its shadow depth surfaces (layers=5 base=0 dim=2 tile=4). The 2D path from #4500 can't serve it, and since 889e5d4 it throws rather than read stale guest bytes.

Related: #2076 (now in main) takes a one-layer 2D array view from slice 0 as the plane, for both planes; such views still go that way. This change covers the depth plane views with more slices. Since a two-layer view or one from slice 1 no longer reaches the 2D path, the agc_driver_depth_storage refusals #2076 added for them now expect this path's messages (layer pitch, later slice). #1368 is about sampling depth arrays, not storage views.

Tested

Windows 11, RTX 4090, MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, on main 665ad7d.

  • agc_driver_depth_storage_dispatch: the array-view case replaces the old refusal. It uses two depth surfaces on slices 0 and 1 and a guest-only slice 2 (Z_64KB_X, 64x32, 32-bit float). The dispatch stores through a 3-slice 2D array view into slice 1, then into slice 2. The test then checks:

    • a depth test against slice 1's surface sees the stored values;
    • slice 0's surface is unchanged;
    • after a flush, guest memory holds slice 0's surface depth, slice 1's surface depth with the stored row, and slice 2's guest depth with the stored row.

    On main it fails with the GTA message above: storage image access to depth/stencil surface 0x... is not implemented (storage=64x32 guest format=22 mip=0 dimension=2 tile=4). With the change it passes.

  • ctest -R "agc_driver_depth": 9/9 pass.

  • ctest -R "agc_driver|agc_masked|agc_shader": 386/386 pass (agc_driver_emulated_color_compare included).

  • tools/check_conventions.py --base upstream/main passes.

  • In game: GTA V on our test stack (main 37dd483 plus feat(agc): implement image and sampler tables indexed at runtime offsets #757 and the other open PRs it needs, our previous local revert of 889e5d4 dropped). With this commit, 0x242d4a200 dispatches and the Story prologue renders correctly for 150 s.

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: none
  • Unblocks: PPSA04264 (GTA V, Story prologue); reported in this thread by testers: PPSA01556 (Resident Evil Village, its first 2D array dispatch), PPSA22408 (HOT WHEELS Infinite Rush, title screen), PPSA29343 (Beast of Reincarnation, menu), PPSA15388 (High on Life, home base)
  • AI-assisted: yes (Claude Code); the change and test were reviewed and run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV

@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Windows test (MinGW GCC 15.2, Release, Windows 10, RTX 4080 SUPER). Built on main 2a2a3dc65 together with nine other open GPU PRs (their own commits applied, no merge commits; one further PR, #5024, conflicted with them and was left out), so only the tests that belong to this PR say something about it:

  • tests/execution/DepthStorageDispatch.cpp, which ctest runs as agc_driver_depth_storage_dispatch: 3 of 3 runs passed.

Not tested: no game run. The batch was built once, not per PR; nothing failed, so no bisect was needed.

AI-assisted (Claude Code).

@johnmaia

Copy link
Copy Markdown
Contributor

Linux test on a second title: Disney Epic Mickey: Rebrushed (PPSA15657, UE4) on an RX 7600 (RADV, Mesa 25.2.8).

Without this PR the title stops before the main menu on a D16 depth plane:

compute shader 0x200ac20000: AGC graphics: storage image access to depth/stencil surface 0x204c5c0000 is not implemented (storage=240x135 guest format=7 mip=0 dimension=2 tile=4)

With it, the title gets past that, reaches the menu, loads the save and plays the level. A 5-minute scripted play session ran without a throw. ctest passed 1033 of 1033.

Build: main ad653c9 + #3763 #4551 #4603 #4465 #4449 #5030 + this PR. The only conflict was docs/dev/debt/libSceAgcDriver.md, resolved by keeping both sides.

Not checked: whether the image is correct. On this main-based build the level renders with almost no blue channel. The same scene on an older base (main e7c70a2 plus the same Mickey PRs, without this PR) is correct, and we are bisecting main for that. Nothing points to this PR so far.

AI-assisted: yes (Claude Code)

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

Copy link
Copy Markdown
Contributor

A data point from Black Myth: Wukong (PPSA23226) on Windows. I cherry-picked this PR's commit onto my integration branch (main plus open PRs). The title still stops on the same refusal, so its view falls under one of the cases this PR keeps refused:

compute shader 0x1440ecde00: AGC graphics: storage image access to depth/stencil surface 0x3043f20000 is not implemented (storage=1920x1080 guest format=5 mip=0 dimension=2 tile=4)

Several compute shaders in the title hit this, over three different depth surfaces (0x3043c20000, 0x3043c90000, 0x3043db0000 in other runs), always 1920x1080, format 5, mip 0, dimension 2, tile 4. I have not yet checked which remaining condition it fails: a later base slice, the stencil plane, or a layer pitch different from the depth slice size.

@tibzejoker

Copy link
Copy Markdown
Contributor Author

@fdfawef324 thanks. It is most likely the stencil plane:

  • guest format 5 is 8_UINT, the stencil plane's format, while a depth plane view is 16 or 32 bits;
  • with this PR, a 2D array view is only routed to the layered depth path when its base address is not the surface's stencil address. Every other array view still reaches the old 2D path, which throws exactly this generic message (dimension=2 = k2DArray).

So 0x3043f20000 should be the stencil address of a 1920x1080 depth target. That is the "2D arrays of the stencil plane" case this PR keeps refused. #2076 handles a one-layer view of the stencil plane; if Wukong's view has one layer (baseArray == depthOrLastArray == 0), that PR may be the one to try.

AI-assisted (Claude Code).

@johnmaia

Copy link
Copy Markdown
Contributor

Follow-up to my PPSA15657 report above: the missing blue channel was not this PR. It was #4842 (the ancillary render-target index read as 0), and #5097 fixes it (test report: #5097 (comment)). On main 3faa9cb + #5097 #4551 #5030 the title boots and renders the level correctly without this PR, so it no longer hits the dimension=2 depth storage refusal there.

AI-assisted: yes (Claude Code)

@tibzejoker
tibzejoker force-pushed the feat/depth-storage-array-views branch from 9abfd89 to 4ad6423 Compare October 13, 2026 13:56
@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Evidence for the case this handles: saved runs from 2026-10-11/12 (before this change) stop with storage image access to depth/stencil surface ... is not implemented (storage=178x100 guest format=7 mip=0 dimension=2 tile=4) in 4 saved log files of PPSA26223 (10 lines), and with (storage=3840x... guest format=5 mip=0 dimension=2 tile=4) in one log file each of PPSA02100 and PPSA03001. Other titles (PPSA25936, PPSA10600, PPSA01530, PPSA01285) log the same message from an older build without the parenthesised details, so I cannot tell from those whether they are the same case. I have not run any of these with the PR applied; I only report that the message occurs.

AI-assisted (Claude Code).

@mrfelek

mrfelek commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Data point: Diablo II: Resurrected (PPSA03912, Linux, RTX 3080 Ti, main 0f2683c) hits the later-slice storage case this PR still refuses, right after the sampling case in #1186:

storage image access to depth/stencil surface 0x25f400000 is not implemented (storage=512x512 guest format=11 mip=0 dimension=2 tile=4 slices=1-1 levels=0-0)

A one-slice view (BASE_ARRAY=LAST_ARRAY=1) of a D16 array. Taking the depth surface at baseAddress + baseArray * DepthSliceBytes and using it as the plane for that slice gets the title past it; the change and a test are on https://github.com/mrfelek/AnyPS5/tree/pr/depth-storage-slice-view. I will rebase it as a follow-up once this PR is in, if the later-slice case is still wanted. Written with AI assistance (Claude Code).

@fdfawef324

Copy link
Copy Markdown
Contributor

Follow-up to my Wukong data point above. That view turned out to be a one-layer 2D array view of the stencil plane: guest format 5 (8_UINT), BASE_ARRAY 0, DEPTH 0, 1920x1080, tile 4, at the depth surface's stencil address. It comes from a "Clear Surfaces" compute pass. That is the shape #2076 accepts, and #2076 merged after my integration base. With #2076 the title takes the view without a refusal, so this PR is not needed for that title.

This PR now conflicts with main because of #2076. When I merged both, I resolved the conflict like this:

@tibzejoker

Copy link
Copy Markdown
Contributor Author

@fdfawef324 thanks for confirming. The PR is already rebased on main (4ad6423) with the same resolution: one-layer views from slice 0 of either plane stay on #2076's path, and #2076's refusal tests for a two-layer view and a view from slice 1 now expect this path's messages (layer pitch, later slice).

AI-assisted (Claude Code).

@EagleBearerYS

Copy link
Copy Markdown
Contributor

Resident Evil Village (PPSA01556, 01.210.000) needs this too. It hits it while loading its first scene, on a local branch past its earlier stops. A compute shader stores to a 1024x1024 R32 2D-array storage view over the game's shadow depth surfaces (guest format 22, Z_64KB_X):

[videoout] presentation failed: compute shader 0x203f1fb300: AGC graphics: storage image access to depth/stencil surface 0x2056800000 is not implemented (storage=1024x1024 guest format=22 mip=0 dimension=2 tile=4)

With this PR merged on that branch, that dispatch passes. A second compute shader then stores through a one-slice view that starts at slice 1 of the same array, which this PR still refuses:

[videoout] presentation failed: compute shader 0x203f1f7d00: AGC graphics: storage image access to the depth planes of depth surface 0x2056800000 through a 2D array view of slices 1-1 is not implemented

The small change below, on top of yours, gets the title past it. Together with local changes for other stops, it reaches its title screen.

Why it seems enough:

  • LayeredDepthStorage builds its StorageTexture from the view's resource, whose image spans slices 0..depthOrLastArray from the base address.
  • The storage view already starts at descriptor.baseArray.
  • The base slice joins the cache key because the StorageTexture keeps the view's descriptor.

One thing I haven't settled: two views with different base slices over the same array now get separate entries, and each copies every slice in and out. If both are written in one frame, the order of their copy-outs could matter. There is no test for this yet. Setup: Windows 11, WinLibs GCC 15.2.0 r7, Release, RTX 3070 Laptop.

Change on top of this PR (DepthSurface.cpp)
diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
index c18de16f4..4ea99be84 100644
--- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
+++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
@@ -520,8 +520,8 @@ private:
     }
 };
 
-std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, int, int>, std::unique_ptr<LayeredDepthStorage>>& layeredStorages() {
-    static auto* storages = new std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, int, int>, std::unique_ptr<LayeredDepthStorage>>();
+std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, int, int, std::uint32_t>, std::unique_ptr<LayeredDepthStorage>>& layeredStorages() {
+    static auto* storages = new std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, int, int, std::uint32_t>, std::unique_ptr<LayeredDepthStorage>>();
     return *storages;
 }
 
@@ -788,11 +788,6 @@ std::shared_ptr<StorageTexture> layeredDepthStorage(const Context& context, Dept
     const auto layers = resource.depthOrLastArray + 1u;
     const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
     const auto bytesPerTexel = d16 ? 2u : 4u;
-    if (resource.baseArray != 0) {
-        char text[256];
-        std::snprintf(text, sizeof(text), "AGC graphics: storage image access to the depth planes of depth surface 0x%llx through a 2D array view of slices %u-%u is not implemented", static_cast<unsigned long long>(target.address), resource.baseArray, resource.depthOrLastArray);
-        throw std::runtime_error(text);
-    }
     surface.RequireStorable(resource, mip);
     const auto geometry = DescribeSurface(resource);
     if (layers > 1 && geometry.GuestLayerOffset(1) != DepthSliceBytes(target.extent, bytesPerTexel)) {
@@ -800,7 +795,7 @@ std::shared_ptr<StorageTexture> layeredDepthStorage(const Context& context, Dept
         std::snprintf(text, sizeof(text), "AGC graphics: storage image access to the depth planes of depth surface 0x%llx with a layer pitch of 0x%llx bytes, not the depth slice size 0x%llx, is not implemented", static_cast<unsigned long long>(target.address), static_cast<unsigned long long>(geometry.GuestLayerOffset(1)), static_cast<unsigned long long>(DepthSliceBytes(target.extent, bytesPerTexel)));
         throw std::runtime_error(text);
     }
-    auto& entry = layeredStorages()[{context.device, resource.baseAddress, resource.width, resource.height, layers, resource.format, static_cast<int>(resource.tileMode), static_cast<int>(resource.dimension)}];
+    auto& entry = layeredStorages()[{context.device, resource.baseAddress, resource.width, resource.height, layers, resource.format, static_cast<int>(resource.tileMode), static_cast<int>(resource.dimension), resource.baseArray}];
     if (entry == nullptr) entry = std::make_unique<LayeredDepthStorage>(context, resource, target, bytesPerTexel, layers);
     else static_cast<void>(entry->storage->Refresh());
     const auto slices = layeredSlices(context, *entry);

This PR conflicts with current main only in docs/dev/debt/libSceAgcDriver.md.

@tibzejoker
tibzejoker force-pushed the feat/depth-storage-array-views branch from 4ad6423 to bfe9c4a Compare October 13, 2026 17:03
@tibzejoker

Copy link
Copy Markdown
Contributor Author

@EagleBearerYS thanks, that is the second title after @mrfelek's Diablo II: Resurrected that needs a view from a later slice, so it is clearly wanted.

I would keep it out of this PR and take it as the follow-up @mrfelek offered (https://github.com/mrfelek/AnyPS5/tree/pr/depth-storage-slice-view), so the two of you can compare approaches there. The concern you raise is real with a baseArray in the key:

  • Two entries over the same array each copy every slice they cover in and back out.
  • If both are written before a flush, the second copy-out overwrites the first one's slices with its stale copy.

So the follow-up probably needs one image per array that both views share, with a view per base slice, rather than one image per view.

AI-assisted (Claude Code).

@ps54all

ps54all commented Oct 13, 2026

Copy link
Copy Markdown

HOT WHEELS Infinite Rush (PPSA22408, UE) data point, Windows 11, RTX 3080 (610.74).

Without this PR (on main plus unrelated picks) it stops after about 210 s (past its splash) at storage image access to depth/stencil surface 0x11dbfb0000 is not implemented (storage=120x68 guest format=7 mip=0 dimension=2 tile=4). With this PR it reaches its title screen ("Press any button"), drawn correctly, and stays up for 360 s. The splash runs at about 60 FPS, the title screen at 2-4.

AI-assisted (Claude Code); I ran the above on my machine.

@mrfelek

mrfelek commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Follow-up for the later-slice case, rebased on top of this PR's head: https://github.com/mrfelek/AnyPS5/tree/pr/depth-storage-slice-view-v2 (one commit, 3d2c808).

It drops the refusal for a view whose base slice is above 0. The layered image already spans slices 0..last and the view starts at its base slice, so the change only copies the slices from the base slice to the last in and out, and adds the base slice to the cache key. DepthStorageDispatch stores through a view from base slice 2 and checks the other slices stay untouched; it fails without the change with the "through a 2D array view of slices 2-2 is not implemented" refusal. DepthStorage's old refusal case now pins the layer-pitch refusal for a later-slice view. agc_driver_depth_storage, depth_storage_reuse, depth_plane_sampling, graphics and depth_storage_dispatch pass; check_conventions passes.

It is not the single shared image per array suggested above: each base slice still gets its own image. Views that are written before a flush and share a slice can still overwrite each other (the later copy-out wins); the debt entry says so. Disjoint views, and views written one after the other with a flush in between, are fine.

Game check: Diablo II: Resurrected (PPSA03912, Linux, RTX 3080 Ti, NVIDIA 615.71.09) on current main plus #1186, #5205 and this branch gets from the main menu through Offline > New Character into the Rogue Encampment and ran 5+ minutes with no abort. It stops at the later-slice storage refusal on main without it. I will open a PR for this once this one is in, if it is still wanted.

AI-assisted; I reviewed the change and ran the tests and the title.

@GustavoGamarra95

Copy link
Copy Markdown
Contributor

One more data point: Beast of Reincarnation (PPSA29343, Unreal Engine), Linux, RTX 3050 Laptop (NVIDIA 615.78.08). On main dacd785 with #4722 (or #4873) and the title's other rendering fixes, without this PR it stops while loading its menu, at compute shader 0x1440440200:

storage image access to depth/stencil surface 0x3053100000 is not implemented (storage=32x32 guest format=7 mip=0 dimension=2 tile=4)

The view is a 6-slice 2D array (base slice 0, last slice 5) over a 32x32 D16 surface in Z_64KB_X: slice 0 is the live depth surface and slices 1-5 are guest memory with no surface at their addresses (16384 linear bytes and 64 KiB in memory per slice). With this PR (bfe9c4a) the title reaches its menu and draws it correctly.

@Tamalero

Copy link
Copy Markdown
Contributor

Another title this unblocks: High on Life (PPSA15388). On main 18c438b10 (plus #4487 and #5156), walking toward the portal in the home base aborted with:

compute shader 0x2031260000: AGC graphics: storage image access to depth/stencil surface 0x20b2580000 is not implemented (storage=1024x1024 guest format=7 mip=0 dimension=2 tile=4)

With this PR (bfe9c4afe) cherry-picked, that stop is gone. In two runs the driver created the same views over 1024×1024, 6-slice depth planes (format 7, tile=4) as storage images, with no refusal. The second run lasted about 25 minutes through the house and exited normally. agc_driver_depth_storage* and agc_driver_graphics pass on Linux with an RTX 4090.

Tested and written by Claude (AI assistant) on a contributor's machine.

A 2D array storage view of a depth plane from slice 0 threw. It now gets
a storage image over all its slices: each slice with a depth surface at
the plane's address plus a whole depth slice per layer is copied from
that surface before every use and back after the work; the other slices
hold guest memory like any storage image. Cubes, stencil arrays, later
base slices and layer pitches other than the depth slice size still
throw.

GTA V (PPSA04264) stops at compute shader 0x242d4a200 on a 5-slice R32
Z_64KB_X array over its shadow depth surfaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
@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
#4646 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L577
#5113 core/libs/prx/libSceAgcDriver/tests/execution/DepthStorage.cpp L249
#5496 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L577
#5524 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L577
#5554 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L577

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
@oneandonlydean oneandonlydean self-assigned this Oct 14, 2026
@oneandonlydean
oneandonlydean merged commit 29d5f06 into boykopovar:main Oct 14, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants