Skip to content

perf(agc): reuse bounded texture snapshot allocations - #8369

Closed
Sam-mon7 wants to merge 2 commits into
boykopovar:mainfrom
Sam-mon7:perf/texture-snapshot-pool
Closed

Sam-mon7 wants to merge 2 commits into
boykopovar:mainfrom
Sam-mon7:perf/texture-snapshot-pool

Conversation

@Sam-mon7

Copy link
Copy Markdown
Contributor

What

Each sampled texture the texture cache keeps copies its guest bytes into a new, zero-filled snapshot vector, and evicting the entry frees it. A title that keeps re-creating large textures commits and zero-fills that memory again for every one.

  • APS5_TEXTURE_SNAPSHOT_POOL_MIB=<n> keeps up to n MiB of the snapshot allocations of evicted entries (64 KiB and larger; the largest are dropped first when over the limit) and gives a new snapshot the smallest kept one that is at least as large and at most twice its size. Unset or 0 keeps the current behaviour.
  • APS5_TEXTURE_CACHE_REPORT=1 prints the texture-cache counters every ten seconds without draw profiling, now with the snapshots made and reused, so the pool can be checked in a run.

Both are documented in docs/dev/ENVIRONMENT.md.

Related: #6982 releases the snapshot of a large write-tracked texture after its upload. The two do not conflict, but with both merged a released snapshot is freed there and never reaches this pool, so the pool would only reuse snapshots still held when their entry is evicted.

Tested

Windows 11 (10.0.26200), winlibs GCC 15.2, Release build of this branch on main at 1d41645.

  • The tests matching partially_resident|snapshot|texture_cache pass, including the new agc_driver_partially_resident_snapshot_pool, which runs the partially resident snapshot test with APS5_TEXTURE_SNAPSHOT_POOL_MIB=64 and APS5_TEXTURE_CACHE_REPORT=1.
  • Validation layer: those tests with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1 (ctest -V) on main and on this branch: 9 tests on main and 10 on this branch (the new one included) pass, with no VUID- line and no was not found but was requested by env var VK_INSTANCE_LAYERS in either log (VK_LOADER_DEBUG=layer shows VK_LAYER_KHRONOS_validation from Vulkan SDK 1.4.363.0 inserted).
  • No speed claim is made for current main, and no performance trace has been captured yet. On an older fork build, in a Demon's Souls (PPSA01341) level, these snapshot vectors were about half of the process's fresh commits (about 18 GB per 30 s), and with a pool 99% of new snapshots reused a kept allocation.

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: none
  • GPUs tested: NVIDIA GeForce RTX 4070, driver 610.88, Windows 11 (10.0.26200)
  • AI-assisted: yes

🤖 Generated with Claude Code

Each sampled texture the texture cache keeps copies its guest bytes into
a new, zero-filled snapshot vector, and evicting the entry frees it. A
title that keeps re-creating large textures commits and zero-fills that
memory again for every one.

APS5_TEXTURE_SNAPSHOT_POOL_MIB=<n> keeps up to n MiB of the snapshot
allocations of evicted entries (64 KiB and larger, the largest dropped
first when over the limit) and gives a new snapshot the smallest kept one
that is at least as large and at most twice its size. 0, the default,
keeps the previous behaviour.

APS5_TEXTURE_CACHE_REPORT=1 prints the texture-cache counters every ten
seconds without draw profiling, now with the snapshots made and reused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 20, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions

github-actions Bot commented Oct 20, 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 8026 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L900

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
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
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 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5431 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 5499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6146 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6177 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6498 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6658 docs/dev/ENVIRONMENT.md
PR 6682 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6683 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6747 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6804 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6946 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6947 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6951 docs/dev/ENVIRONMENT.md
PR 6972 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7002 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7085 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7154 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7446 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7464 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7484 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 7505 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 7644 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7647 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7800 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7922 docs/dev/ENVIRONMENT.md
PR 7948 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 8004 docs/dev/ENVIRONMENT.md
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8160 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8161 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8206 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md

@TheSovietWaffle

Copy link
Copy Markdown
Contributor

AMD data point: RTX 3080 (NVIDIA 617.42) and AMD Radeon RDNA 2 iGPU (1002:164e, 32.0.21045.5002), Windows 10 Pro 22H2, WinLibs GCC 15.2.0, Release, head 66b27c480: agc_driver_partially_resident_snapshot_pool passes 3 of 3 on both GPUs.

AI-assisted: yes (Claude Code).

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. A small point on how the pool is bounded:

spareSnapshot files a kept buffer under its size() and counts its capacity(), and takeSnapshot accepts a kept buffer when that stored size is at most twice the request. A reused buffer keeps its capacity when it is resized down, so the ratio can compound. A buffer allocated for a 32 MiB texture can be reused for 16 MiB, then 8 MiB, and so on, and end up holding a 64 KiB texture while its 32 MiB stay committed for as long as that cache entry lives. The texture cache accounts an entry by its Vulkan allocation, not its snapshot, so nothing else bounds that memory.

Keying spareSnapshots by capacity() and requiring capacity <= 2 * bytes when taking a buffer would keep the "at most twice its size" rule true for the memory actually held.

A reused snapshot keeps the capacity it was allocated with when it is
resized down, but the pool filed spare buffers under their size, so the
"at most twice the request" rule held for the size only and could
compound: a buffer allocated for a 32 MiB texture could serve 16 MiB,
then 8 MiB, and end up holding a 64 KiB texture with 32 MiB committed.
Spare buffers are now filed under their capacity, so the rule bounds the
memory a reused snapshot holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Sam-mon7

Copy link
Copy Markdown
Contributor Author

Good catch, thanks. Fixed in 48a3109: spare buffers are filed under their capacity, so the "at most twice the request" rule bounds the memory a reused snapshot holds and can't compound. For the perf label I'll capture a main vs change pair. In Demon's Souls on current main the texture cache barely churns in steady scenes, so I'll measure the load into the Nexus, where snapshots are made, and post what it shows.

@Sam-mon7

Copy link
Copy Markdown
Contributor Author

Measured, and I don't think this earns its place on current main, so I'm closing it.

Demon's Souls (PPSA01341 01.007.000), Windows 11, RTX 4070 (610.88), 1920x1080, +r_Wants4K=WantsPerf, debug start menu to the Nexus, warm caches, no APS5_PROFILE_* switches, APS5_TRACE_SUMMARY_EVERY=10 and a 30 s trace during the load. Both sides include #8394, which this title needs to start on main. Base 928a11bf2 (main + #8394), change 8d29a0f8d (+ this PR, APS5_TEXTURE_SNAPSHOT_POOL_MIB=512), alternated:

Run Build Load into the Nexus Nexus, presents per interval
1 base stopped while loading (VK_ERROR_DEVICE_LOST, unrelated to this change)
2 change 111 s 3 3 3 3 3 3
3 base 143 s 2 3 3 2 2 2
4 change 249 s 4 4 4 4 3 3
  • In the Nexus the cache makes no snapshots at all (0 snapshots made per 10 s), so the pool cannot change the steady frame rate there; the difference above is run-to-run noise (earlier base runs of the same scene measured 3 presents per interval).
  • During the load the pool does what it says: in one 10 s interval 805 snapshots were made (1,872 MiB) and 690 of them reused a kept buffer. But the load time varies far more between runs than anything that could save, so there is no measurable gain to report.

The large churn this was written against came from a texture cache that kept evicting on an older fork; current main doesn't do that here. If a title with steady snapshot churn turns up, the branch is still there.

@Sam-mon7 Sam-mon7 closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs perf numbers missing perf pull request without a before and after summary line perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants