Skip to content

fix(agc): drop resource-cache entries that hold released sampled textures - #6492

Merged
oneandonlydean merged 5 commits into
boykopovar:mainfrom
Pipodi:fix/resource-cache-released-textures
Oct 18, 2026
Merged

oneandonlydean merged 5 commits into
boykopovar:mainfrom
Pipodi:fix/resource-cache-released-textures

Conversation

@Pipodi

@Pipodi Pipodi commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

What

A ResourceCache entry keeps every sampled texture of its descriptor sets alive. The sampled texture cache passes over textures something else still holds, and once it is a quarter over its budget it drops them anyway. Textures it makes without caching (a first use while the least recently used entry was used this frame or the last) are never in it at all. In both cases the only owner left is the resource-cache entry, so the texture stays in device memory with no budget counting it. Nothing limits that except the 1,024-entry and 65,536-table-element counts. This is the sampled-texture side of #3256, which #3473 and #3474 fixed for storage images.

  • Texture gets a released flag. eraseTexture sets it on a texture something else still holds, and the sampled lookup sets it on a texture admitTexture refused.
  • ResourceCache::Insert refuses descriptor sets that hold a released texture. When a texture was released since the last insert, it also removes the entries holding one and hands them back through evicted, like RemoveImage does for evicted storage images. The caller then passes them to the recorder.
  • New releasedTests in agc_driver_sampled_cache_budget: an entry whose texture the cache drops is handed back and stops keeping the texture alive, and a descriptor set holding a released texture is not admitted. Without the change to ShaderResources.cpp, the test fails at "the sampled texture cache dropped a texture the resource cache holds without marking it released".

Technical debt

New entry "Resource cache entries holding released textures" in docs/dev/debt/libSceAgcDriver.md:

  • the removal waits for the next insert, so Find can still return those entries until then;
  • one process-wide flag starts the sweep: while the sampled cache is over budget, every insert scans every entry (cost not measured);
  • a draw or dispatch whose textures the cache refused rebuilds its descriptor sets on each use while the cache stays full;
  • the entry and element bounds still count entries, not bytes;
  • sampled textures made outside the texture cache (cache disabled, depth-surface copies) are never marked;
  • measured on one title and one GPU only.

Tested

Windows 11, NVIDIA GeForce RTX 4090, driver 617.42.

Run This PR Resource cache bounds Max heap 0 use Readings over budget Max sampled textures Max held outside the texture cache Result
run162 no default (1,024 / 65,536) 26,357 MiB 2 14,412 MiB 7,830 MiB aborts at the wolf fade-in (frame ~824): device memory spills into the shared host heap until vkAllocateMemory returns VK_ERROR_OUT_OF_DEVICE_MEMORY
run164 no lowered by env (256 / 16,384) 22,060 MiB 0 9,398 MiB 2,541 MiB passes the fade-in; brightness screen, then the next scene at frame ~900 (1,800 s)
run165 yes default (1,024 / 65,536) 22,695 MiB 0 9,407 MiB 2,836 MiB passes the fade-in, slightly ahead of run164 (frame 818 vs 802 at 307 s, 894 vs 881 at 1,523 s); stopped at 1,747 s on the brightness screen
run165 frames and memory
  • Frames: 818 at 307 s, 840 at 612 s, 859 at 916 s, 878 at 1,220 s, 894 at 1,523 s, 903 at 1,687 s. One cold pipeline build (11 s); no Vulkan errors.
  • [texture-cache] at frame 841: 1,662 cached, 7,001 used uncached, 1,154 evicted, 5,407 held ones passed over in 10 s; 6,572 MiB in 26,466 entries, budget 6,432 MiB. run162 passed over 72,852 to 166,319 held textures per 10 s at frame 824.
  • Process private memory stays 48-52 GB, as in run164, on a 31.7 GB machine. Host memory is separate from this change.

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: PPSA26344 Ghost of Yōtei (PPSA26344) #675
  • GPUs tested: NVIDIA GeForce RTX 4090, driver 617.42, Windows 11
  • AI-assisted: yes

🤖 Generated with Claude Code

Pipodi and others added 2 commits October 15, 2026 18:56
…ures

A resource-cache entry keeps every sampled texture of its descriptor sets alive. The sampled texture cache passes over textures something else holds and, past its ceiling, drops them anyway; textures it makes without caching are never in it. Entries holding either kept them alive with no budget counting them, bounded only by the 1,024-entry and 65,536-element counts, until device memory ran out.

The texture cache now marks such textures released. ResourceCache::Insert refuses descriptor sets holding one and hands the entries that hold one back to the caller, like RemoveImage does for evicted storage images.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 15, 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 6445 docs/dev/debt/libSceAgcDriver.md L177
PR 6973 docs/dev/debt/libSceAgcDriver.md L177
PR 6987 docs/dev/debt/libSceAgcDriver.md L177
PR 6989 docs/dev/debt/libSceAgcDriver.md L177
PR 6995 docs/dev/debt/libSceAgcDriver.md L177
PR 7002 docs/dev/debt/libSceAgcDriver.md L177
PR 7332 docs/dev/debt/libSceAgcDriver.md L177

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/Graphics/include/Texture.hpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
docs/dev/debt/libSceAgcDriver.md
PR 4650 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5499 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 5998 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 5999 docs/dev/debt/libSceAgcDriver.md
PR 6082 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6222 core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6373 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6414 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6472 docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6490 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 6529 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6563 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6741 docs/dev/debt/libSceAgcDriver.md
PR 6786 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6946 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6971 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 6972 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
docs/dev/debt/libSceAgcDriver.md
PR 7250 docs/dev/debt/libSceAgcDriver.md
PR 7426 docs/dev/debt/libSceAgcDriver.md
PR 7432 docs/dev/debt/libSceAgcDriver.md
PR 7505 docs/dev/debt/libSceAgcDriver.md

@fma-rc

fma-rc commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Death Stranding 2 (PPSA02015) reaches this on its loading screen, and with this PR it still runs out of device memory, only later. This is a local stack, since the title needs several unmerged PRs and a few local skips of refusals to get that far. Linux, RTX 5080, NVIDIA 615.78.08.

I counted live VkDeviceMemory per allocating call site. Without the PR, device-local memory is 14.0 GiB at 330 s and vkAllocateMemory texture fails with -2 at about 390 s. With it, the same run is at 6.8 GiB at 330 s, then climbs again and fails at 416 s.

At 405 s with the PR, allocations made by Texture::Texture hold 9.4 GiB (37,387 made, 23,255 freed), while [texture-cache] reports 6,373 MiB in the cache against a 6,201 MiB budget, with 44837 held ones passed over in that 10 s window. So about 3 GiB of sampled textures still live outside the cache. Re-creations of the same address and shape made up 42 of the 51.5 GB of texture allocations in the run without the PR.

@Pipodi

Pipodi commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Another data point from today, from a different integration base, so not a clean A/B:

Ghost of Yōtei run (RTX 4090, APS5_SPANNED_WAVE=1) Base After the difficulty selection
run176 (cold), run177 (warm) without this PR and without the sampled-budget change (#6783) vkAllocateMemory device buffer ... VK_ERROR_OUT_OF_DEVICE_MEMORY, abort. Device-local heap 25,281 MiB used of a 22,913 MiB budget, 13,149 MiB of it sampled textures
run178 (cold) with both runs on. 22,465 of 23,066 MiB, 9,323 MiB sampled, 0 allocation failures through the end of the 2,700 s run

The two bases also differ in #6416 and a few local commits, so this only supports the earlier run162 and run165 comparison in the description. It doesn't replace it.

🤖 Generated with Claude Code

@mrfelek

mrfelek commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Hot Wheels Infinite Rush (PPSA22408): still runs out of device memory on the loading screen with this PR, VRAM split by allocating call site

Setup

Result

Device-local memory (type 1) held by the driver at the failed allocation: 7.0 GB

Allocating function Live Made / freed in the run
makeSlab (buffer slabs) 2,228 MiB in 300 26,130 / 25,830
StorageTexture::StorageTexture 1,863 MiB in 187 11,501 / 11,314
BufferPool::AllocateMemory 1,796 MiB in 1,272 6,388 / 5,116
Texture::Texture 1,048 MiB in 743 1,013 / 270
  • nvidia-smi showed about 11.4 GB in use at the end. About 1.7 GB of that is the desktop, so roughly 2.7 GB was the NVIDIA driver's own memory (pipelines and pools), which the counter does not see.
  • Imported guest memory (bindImport, host-visible type 3) held 8.2 GB, in system RAM, not VRAM.

Observations

  • No single holder leaks. Four holders of 1 to 2.2 GB plus the driver's own memory reach the 12 GB limit together.
  • Storage textures are freed with these PRs (11,314 of 11,501), but buffer slabs and storage textures are created and freed again tens of thousands of times during the loading. That churn may explain why the loading is so slow.
  • Sampled textures are the one holder that mostly stays: 743 live, 270 freed out of 1,013 made.

AI-assisted: this comment was drafted with the help of Claude Code and checked against the logs by us.

@Pipodi

Pipodi commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for splitting it by call site, that answers the question this PR can't.

By your table, sampled textures (Texture::Texture) hold 1.0 GB of the 7.0 GB device-local memory at the failure. This PR only stops resource-cache entries from keeping sampled textures alive after the texture cache has let go of them. So it can't get this title under 12 GB, and its earlier stop without the PR is consistent with that. On our 24 GB card, the same holder was what overflowed (7.8 GB of sampled textures held outside the texture cache, in the table above). On yours, the other three holders are the larger part.

Of the four holders, only the sampled texture cache reads VK_EXT_memory_budget; #6783 adjusts how its budget leaves room for storage textures. The other three allocate without any device-memory budget:

  • buffer shadow slabs (makeSlab in UnitShadow.cpp);
  • storage textures;
  • BufferPool.

They react only once vkAllocateMemory fails. Then BufferPool reclaims what it can and retries; your log shows that reclaim freed 633 MiB, not enough for the 9 MiB request plus everything still growing. With about 2.7 GB of the driver's own memory and 1.7 GB of desktop on a 12 GB card, the four holders together cross the limit, as you say, without any of them leaking.

I'd treat that as its own issue: no device-memory budget across holders, so a 12 GB card runs out where a 24 GB one doesn't. It's a different change from this PR. Your table and the churn numbers (26,130 slabs and 11,501 storage textures made in one loading) are exactly what that issue needs. Do you want to open it, or should I, quoting your comment?

AI-assisted: yes (Claude Code)

@oneandonlydean

Copy link
Copy Markdown
Collaborator

This now conflicts with main (after #6875 and #4339 merged). Could you rebase onto current main? We'll re-run full CI and the gate on the new head.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@oneandonlydean

oneandonlydean commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks, this is ready apart from one line. The project keeps specific game titles out of its docs, and the new docs/dev/debt/libSceAgcDriver.md entry says "Measured only on <title> (PPSA26344) on one RTX 4090". Could you reword it to something like "Measured on one title on one RTX 4090" and push? Nothing else needs to change; we'll rerun CI on the new head and merge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@oneandonlydean
oneandonlydean merged commit dce6a3e into boykopovar:main Oct 18, 2026
9 checks passed
fisherjoey added a commit to fisherjoey/AnyPS5 that referenced this pull request Oct 19, 2026
…with the full walk

The fast proof of a cached ShaderResources proves each sampled texture
from write stamps, keys and the pending registry. It never asks whether
the sampled lookup would still return the bound Texture, so a template
whose surface memory is unchanged keeps binding an old object:

- After the sampled cache drops or replaces the entry (boykopovar#6492 marks the
  texture released), Find keeps serving the template until the next
  Insert, and its fast proof passes.
- When the lookup's answer changes without any lookup running (a
  snapshot whose surface became host-imported is viewed through its
  storage image from then on), nothing replaces the entry and the
  template keeps the snapshot until it leaves the resource cache.

The fast proof now fails with 'replaced texture' when a bound texture
is released. A template whose last full walk is older than
APS5_TEMPLATE_AUDIT_MS (default 1000, 0 turns it off) takes the full
walk instead of the fast proof ('audit'), so a different lookup answer
fails the proof and the template is rebuilt.
TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
…texture budget

The service evicted idle entries above a budget fixed at device creation (half the
sampled budget then: 4,158 MiB on the 16 GB card). In long5 at the intro (frame 840)
pinned reached it while 97 tables (1.0M records) were primed: 286,325 entries were
evicted down to 3/4, the next serves re-primed them (primer 44.6 s, serve 60.8 s).
By then the texture budgets had moved too (share 7.3 GB, sampled budget 5.0 GB).

Unless APS5_BINDLESS_BUDGET_MIB forces a size, the budget is now read at each
present from the texture caches' budgets: the share of the VRAM that sampled
textures may take (target less the storage reserve). The service's textures are
sampled textures, counted there already: in the cache (in use, never evicted) or
outside it (SampledTextureMemory), so boykopovar#6492/boykopovar#6783's accounting (sets holding
released textures dropped, textures held outside the cache reserved, storage
reserved at max(budget, held)) covers them; e0e3c93 has that part.
The frame line names the budget; new case in agc_driver_texture_service.
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.

4 participants