Skip to content

fix(agc): bound the sampled depth-plane copies - #7705

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
AnthonyClemens:fix/agc-bound-depth-plane-copies
Oct 24, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
AnthonyClemens:fix/agc-bound-depth-plane-copies

Conversation

@AnthonyClemens

@AnthonyClemens AnthonyClemens commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What

A depth plane copy (a depth surface sampled as several slices, an integer view or a cube) stayed in planeCopies() until the device went away. Each copy also kept a staging buffer the size of the copy. A staging buffer it outgrew was moved into retiredStaging and never freed. So every depth surface a scene ever sampled this way kept its copy, and device memory grew with each new scene.

With this change:

  • Each copy shares its image ownership with the views it hands out, through the existing OwnedImage owner of the depth-plane Texture constructor. A copy can leave the cache while a draw or a cached resource set still samples it.
  • The recorder keeps the image and the staging buffer until the recorded copy has run. A copy evicted in the meantime, or a staging buffer it outgrew, never frees memory still in use.
  • Once the copies pass APS5_DEPTH_PLANE_COPY_MIB (default 256; 0 keeps every copy), the least recently used ones leave the cache. A copy made again refills from the depth surface and guest memory, as a new copy does.

DepthPlaneCopyCount() returns the number of cached copies, for the test.

In Resident Evil 2 (PPSA04288), these were the peak depth plane copy allocations under APS5_VK_MEMORY_TRACK:

Build Peak depth plane copy
Before the change 1228 MiB (two runs)
With the change 164–188 MiB (three runs)

The scene rendered the same either way. Without the bound, the staging buffers of the same size added roughly as much again under device buffer.

Tested

Windows 11 Pro (10.0.26200), AMD Radeon RX 9070 XT, WinLibs GCC 15.2.0, Release, on main 85029a0.

  • New: agc_driver_depth_plane_sampling_evicting. It runs the depth-plane sampling cases with APS5_DEPTH_PLANE_COPY_MIB=1 and --evicting. Every copy but the newest leaves the cache, so later cases rebuild evicted copies and must read the same values. It then requires one copy left in the cache.
  • Control (jotitadev's point). The same binary with APS5_DEPTH_PLANE_COPY_MIB=0, which keeps every copy as main does, fails: 8 depth plane copies were kept with a 1 MiB budget, expected only the newest.
  • Unchanged and passing: depth_plane_sampling, depth_plane_guest_layers, depth_plane_fill, depth_compare_copy, depth_storage, depth_storage_dispatch, depth_storage_reuse, depth_surface_from_storage, depth_bits_guest_memory.
  • Rebased onto current main. As MaxLikesCode noted, the prepared_shaders_abi_deferred_* failures came from the old base.
  • Conventions. The code comments the conventions check flagged were removed. Their content is in this description.

Thanks to MaxLikesCode, savvaniss and jotitadev for the NVIDIA, Intel/AMD and MoltenVK runs.

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
  • Fixes: none
  • Unblocks: PPSA04288 (device memory growth across scenes)
  • GPUs tested: AMD Radeon RX 9070 XT, Windows 11 (others in the comments: RTX 4090, Intel UHD 770, Apple M2 Max/MoltenVK)
  • AI-assisted: yes (Claude Code)

@github-actions

github-actions Bot commented Oct 19, 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 8999 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L2313
PR 10194 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L1377-1378
PR 10256 core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp L74
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L2313

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/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6617 docs/dev/ENVIRONMENT.md
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7307 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/ENVIRONMENT.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7484 docs/dev/ENVIRONMENT.md
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7677 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7814 docs/dev/ENVIRONMENT.md
PR 7922 docs/dev/ENVIRONMENT.md
PR 8004 docs/dev/ENVIRONMENT.md
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8077 docs/dev/ENVIRONMENT.md
PR 8223 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8365 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8939 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9348 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9356 docs/dev/ENVIRONMENT.md
PR 9387 docs/dev/ENVIRONMENT.md
PR 9400 docs/dev/ENVIRONMENT.md
PR 9431 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9432 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 9521 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9633 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9672 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9673 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9674 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9675 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9676 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/ENVIRONMENT.md
PR 9700 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9749 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9790 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9842 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9891 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9892 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9893 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9897 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9898 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 9969 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10193 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10231 docs/dev/ENVIRONMENT.md
PR 10244 docs/dev/ENVIRONMENT.md
PR 10254 docs/dev/ENVIRONMENT.md
PR 10264 docs/dev/ENVIRONMENT.md
PR 10272 docs/dev/ENVIRONMENT.md

@jotitadev jotitadev 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.

Apple GPU / MoltenVK run. agc_driver_depth_plane_sampling_evicting and the depth-plane tests pass on Metal. Two of the tests listed as unchanged, depth_plane_guest_layers and depth_storage, are skipped on macOS on main. With a loader patch they run and pass with this PR.

Machine: Apple M2 Max, macOS 27.2, x86-64 build under Rosetta. Head 0258a05, PR diff applied cleanly. Drivers: MoltenVK 1.4.2 (Vulkan SDK 1.4.363.0) and MoltenVK main + SPIRV-Cross #2704.

Tested: the tree is main a373e85 + mugurc's macOS series (ci/macos-series-2) + the PRs needed to build on macOS (#6667, #6668, #7524, #6969, and two files of #7332), not plain main (it doesn't build on macOS yet). Also in the same build: #7707, #7698, #7687, #7691 and #7705 (they touch different files and applied cleanly together), plus a test-only patch that lets nine AGC fixtures load the Vulkan loader on macOS (branch jotitadev:fix/agc-tests-vulkan-loader-macos). Base on that tree: 40 ^agc failures with MoltenVK 1.4.2 and 26 with MoltenVK main + #2704.

  • agc_driver_depth_plane_sampling_evicting (APS5_DEPTH_PLANE_COPY_MIB=1), depth_plane_sampling, depth_plane_fill, depth_plane_guest_layers, depth_storage, depth_compare_copy, depth_surface_from_storage and depth_bits_guest_memory: all pass 3 of 3 on both drivers. depth_storage_dispatch and depth_storage_reuse also pass in the full-suite runs.
  • DepthPlaneGuestLayers.cpp and DepthStorage.cpp load libvulkan.so.1 themselves, without portability enumeration. Without the loader patch they print skipped, no usable Vulkan device on macOS, so they don't cover this change there.
  • Full ^agc suite with the stack: 42 of 730 fail on MoltenVK 1.4.2 and 28 of 730 on MoltenVK main + #2704. That is the base failures plus agc_driver_pooled_buffer_range and agc_performance_overlay, which only run because of the loader patch and fail for unrelated reasons. No new failure from this PR.

I didn't do a without-the-fix control. Without the fix, the evicting test's variable does nothing, so the test would pass anyway.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2; MoltenVK main + SPIRV-Cross #2704)

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@MaxLikesCode

Copy link
Copy Markdown
Contributor

NVIDIA data: GeForce RTX 4090, driver 617.42, Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON with every execution test built. Head 0258a05, which already contains main 79d7d26.

AI-assisted: yes (Claude Code)

@savvaniss

Copy link
Copy Markdown
Contributor

Windows regression check on an Intel UHD 770 and an AMD RX 9070 XT: no new failures in the full CTest results.

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built. Intel UHD Graphics 770 (8086:a780), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550), driver 32.0.31041.1004, with VkCache emptied first. This head 0258a057b merged into main 6b8228b2b.

This PR adds no test, so I ran full CTest (--parallel 4, 1606 tests) with it merged:

AI-assisted: yes (Claude Code)

@AnthonyClemens
AnthonyClemens force-pushed the fix/agc-bound-depth-plane-copies branch from 0258a05 to f440e3b Compare October 19, 2026 14:06
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 19, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The full Build of this head fails on Linux (lavapipe) in the new test, which main does not have:

  • agc_driver_depth_plane_sampling_evicting (run 39348454151): AGC graphics: 2 depth plane copies were kept with a 1 MiB budget, expected only the newest (DepthPlaneSampling.cpp:458).
  • evictPlaneCopies runs right after the copy is constructed (DepthSurface.cpp:1881), before Refresh creates the staging buffer (DepthSurface.cpp:1024), so HeldBytes() of the newest copy leaves the staging out and the total is under-counted until the next lookup.
  • The test requires exactly one copy left at the smallest budget the variable allows (1 MiB). That depends on the driver's image sizes: on llvmpipe two copies fit. Either account after the staging exists and assert something that holds on every driver (e.g. fewer copies than the 8 kept with APS5_DEPTH_PLANE_COPY_MIB=0, and the newest one still present), or make the budget finer than 1 MiB for the test.

The rest (shared OwnedImage ownership, recorder keeping the image and staging until the copy runs, ENVIRONMENT.md entry) checked fine.

@jotitadev jotitadev 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.

Apple GPU / MoltenVK run of the current head. The new agc_driver_depth_plane_sampling_evicting fails here the same way adriadam10 saw on lavapipe, so the one-copy check does not hold on MoltenVK either. My earlier run (0258a057b) passed because that head did not have the count check yet.

Machine: Apple M2 Max, macOS 27.2, x86_64 under Rosetta 2. Vulkan loader + MoltenVK 1.4.2 (Vulkan SDK 1.4.363.0), and again against MoltenVK main + SPIRV-Cross #2704. Release, BUILD_TESTING=ON.

Base/method: PR #8658 feat/macos-port @ c5fa39620a (current main plus the macOS port), not main, because main does not build on macOS. This PR's diff at f440e3b238 (git diff merge-base..head -- . ':!docs') applied cleanly. The focused runs and the suite were done in one tree that also had #9443 and #9717/#9533. They touch other tests, and the evicting failure below is also there when the binary runs alone. Full rebuild first.

agc_driver_depth_plane_sampling_evicting: fails on every run, 4 per driver, on both drivers:

AGC graphics: 2 depth plane copies were kept with a 1 MiB budget, expected only the newest

Running the same binary by hand with --evicting:

APS5_DEPTH_PLANE_COPY_MIB MoltenVK 1.4.2 MoltenVK main + #2704
0 8 copies kept 8 copies kept
1 2 copies kept 2 copies kept

So the eviction does work here, from 8 copies down to 2. This matches what adriadam10 described. The newest copy's staging is not counted yet when evictPlaneCopies runs, and at a 1 MiB budget two copies fit on this driver too. Any assertion that holds on all drivers would have to accept 2 here.

Unchanged and passing (--repeat until-fail:3, both drivers): depth_plane_sampling, depth_plane_guest_layers, depth_plane_fill, depth_plane_partial_store, depth_compare_copy, depth_storage, depth_storage_dispatch, depth_storage_reuse, depth_surface_from_storage, depth_bits_guest_memory.

Full ^agc suite (877 tests), both drivers: the failures from the #8658 base, plus depth_plane_sampling_evicting. The suite also showed two failures from #9533's tests in the same tree, which I'm reporting on #9533. Nothing else is new.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2; and MoltenVK main + SPIRV-Cross #2704).

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@EagleBearerYS

Copy link
Copy Markdown
Contributor

@AnthonyClemens a note on merge order: #10043, an opt-in FSR 1 scaler for the presented image, adds its rows next to yours in docs/dev/ENVIRONMENT.md. It declares that it goes after this PR (Depends on: #7705, merge order only), and it will add its rows next to yours when it merges main. Nothing is needed from you.

AnthonyClemens and others added 3 commits October 24, 2026 04:24
A depth plane copy (a depth surface sampled as several slices, an integer view or a cube) was
kept until the device went away, with a staging buffer of its own size, and a staging buffer it
outgrew was never freed. Every depth surface a scene ever sampled this way held its copy, so
device memory grew with each new scene (0.4-1.2 GB in RE2 after a level load).

The copies now share their image's ownership with the views they hand out, the recorder keeps
the image and the staging buffer until the recorded copy has run, and past
APS5_DEPTH_PLANE_COPY_MIB (default 256; 0 keeps every copy) the least recently used copies
leave the cache. A copy made again refills from the surface and guest memory as a new one does.
agc_driver_depth_plane_sampling_evicting runs the plane sampling cases with a 1 MiB budget.

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

agc_driver_depth_plane_sampling_evicting passes --evicting, and the test then requires a single
depth plane copy left in the cache (DepthPlaneCopyCount) after the cases. With
APS5_DEPTH_PLANE_COPY_MIB=0, which keeps every copy, it fails with "8 depth plane copies were kept
with a 1 MiB budget, expected only the newest". The explanatory comments move to the pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The newest copy's staging buffer exists only after Refresh, so evicting before it under-counted the held bytes. The evicting test now asserts a count that holds on every driver: at least the newest copy and fewer than the 8 an unbounded cache keeps.
@adriadam10
adriadam10 force-pushed the fix/agc-bound-depth-plane-copies branch from f440e3b to c2b128b Compare October 24, 2026 02:45
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 24, 2026
@adriadam10
adriadam10 merged commit cd0591c into boykopovar:main Oct 24, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

The full Build failed agc_driver_depth_plane_sampling_evicting on lavapipe (DepthPlaneSampling.cpp:458, 2 copies kept), and jotitadev saw the same on MoltenVK. Pushed to your branch:

  • Rebased onto current main (conflicts in libSceAgcDriver/CMakeLists.txt and docs/dev/ENVIRONMENT.md).
  • evictPlaneCopies now runs after Refresh, so the newest copy's staging buffer counts toward the budget.
  • The test asserts at least one copy and fewer than the 8 an unbounded cache keeps, which holds whatever the driver's image sizes are.

Rest checked earlier (shared OwnedImage, recorder keeping image and staging until the copy runs, ENVIRONMENT.md entry); merged.

@seradavid

Copy link
Copy Markdown
Contributor

The depth-plane copy budget costs some scenes a lot once the copies exceed it

Posting here because the regression was bisected to this merge, in case it is useful to the author.

On Windows 11 / RX 9070 XT, ASTRO BOT (PPSA21567) drops from about 25 fps to 0.7 fps in the intro
cutscene after New Game on current main. Bisecting the first-parent chain lands on this merge:
b0f57be3c7 (its parent) runs the scene at frame p50 34-45 ms, cd0591c8f4 at 1480-1571 ms.

The eviction runs after every DepthSurfaceTexture lookup, and HeldBytes() counts the depth image
plus its staging buffer, so once the scene's copies together exceed the 256 MiB budget each lookup
discards every copy except the one in hand, and they are re-created and re-copied the next time they
are sampled (that shows up as storage-writeback 214 MiB against 84 MiB per present). The same
cd0591c8f4 build with APS5_DEPTH_PLANE_COPY_MIB=0 runs the scene at p50 34.3-44.8 ms.

Details, the bisect table and fix directions are in
#11461 (comment). Not asking for anything
here; flagging it so the budget does not stay as-is if the author wants to revisit the eviction.

AI-assisted: yes.

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.

7 participants