Skip to content

perf(agc): refuse uncached textures while the sampled cache's least recently used entry is still in use - #7640

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
Pipodi:perf/texture-cache-scan-admission
Oct 20, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
Pipodi:perf/texture-cache-scan-admission

Conversation

@Pipodi

@Pipodi Pipodi commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

What

When a frame uses more sampled textures than the sampled texture cache's budget, admitTexture refused only the first use of a texture in a present. Every later use of that texture evicted the cache's least recently used entry, even when the same present had just used that entry. A working set larger than the cache then turns over every frame: strict LRU over a repeating scan bigger than the cache gets almost no hits, and every eviction destroys a texture the next draw needs again.

Ghost of Yōtei's loading phase uses about 33,000 textures (11–13 GiB) per frame against a sampled budget of about 5.4 GiB. On main the cache evicted about 2.7M textures an hour there and passed over about 13M held entries.

Now, while the cache is over its budget and within a quarter over it:

  • the first use of a texture the cache does not hold in a present is refused when the least recently used entry was used in this present or the last, as before;
  • a later use in the same present is refused when the least recently used entry was used in this present.

A refused texture is used for its draw and released, as before. So the cache keeps a stable subset of the working set instead of rotating through it. The +25% ceiling is checked before the refusal, so past it entries are evicted as before and a cache whose budget falls still trims.

OverrideCacheBudgetForTest (declared next to TextureCacheUsage) lets a test set the cache budget between inserts; forcedCacheBudget checks it before the one-time APS5_TEXTURE_CACHE_MIB read, and 0 clears it.

Tests in SampledCacheBudget.cpp:

  • a repeating scan over twice as many textures as the budget holds, each used twice in a row: on main 0 of 80 lookups hit; the test requires at least a quarter of them to hit;
  • after the budget falls from 2 MiB to 1 MiB with every entry in use, one insert still trims the cache to within the ceiling;
  • a texture used twice in a present is cached on its second use when the least recently used entry is from an earlier present;
  • the existing frame test's "second use is cached" case now expects the refusal while the cache's back is in use this present.

Measured

Ghost of Yōtei (PPSA26344), RTX 4090 (driver 617.42), Windows 11, warm shader cache, 3600 s per run, the same save and pad input. Local integration tree: main 1c523bd plus 17 local commits (#6492 before it merged, a host-heap fallback chain, a pad-input hook and logging). A1 and A2 are that tree; B is A plus this PR's rule; the commit pushed here differs from the measured build only in the test seam and the debt entry. Counters are from the [texture-cache], [texture] and [resources] images took lines after frame 620, where the loading phase starts.

Run This PR Frames (after 620) Evicted Passed over Cached Used uncached Textures created Image resource builds / time Placed in host memory
A1 run209 no 932 (312) 2.72M 12.97M 2.75M 2.94M 5.69M 1,149 / 2,688 s 217,814 MiB
B run210 yes 968 (348) 1.17M 3.09M 1.23M 4.57M 5.80M 1,158 / 2,608 s 176,268 MiB
A2 run211 no 941 (321) 2.72M 13.34M 2.76M 3.04M 5.81M 1,186 / 2,687 s 200,958 MiB

B reached 968 frames against 932 and 941, +10% frames after frame 620. Evictions fell 57% and passed-over entries 77%. The number of textures created is unchanged: the working set is still more than twice the budget, so the textures that do not fit are still created at each use. The gain comes from fewer evictions and less walking of the LRU list, and draw resource builds took 3% less time.

Budget and heap use
Run Sampled budget after 620, MiB min / median / max Heap 0 placement lines over budget Largest heap 0 use
A1 run209 2,150 / 5,364 / 9,365 23 of 23, by up to 1,193 MiB 24,234 MiB
B run210 2,048 / 5,390 / 10,348 22 of 23, by up to 1,047 MiB 24,070 MiB
A2 run211 2,048 / 5,445 / 10,060 21 of 23, by up to 953 MiB 23,976 MiB

The placement lines print only when a device-local allocation is placed in host memory, so they show the heap at its fullest; the three runs are alike.

A stricter form that refused every use of an uncached texture in the present (run212) reached 962 frames with the same eviction and creation counts (1.18M evicted, 5.78M created), so the second-use exception kept here costs nothing measurable.

Technical debt

New entry "Scan-resistant sampled texture admission" in docs/dev/debt/libSceAgcDriver.md:

  • the "current or previous present" window is a chosen threshold, not a measured one; the rule was measured once, as the A-B-A series above, on one title and one GPU;
  • a later use can evict an entry from the previous present and then still be refused when the next least recently used entry is from the current present, so that entry is created again in the next present;
  • a texture used more than once in a present still evicts an entry not yet used in that present, so a working set with interleaved multi-use textures rotates as on main;
  • the textures that arrive after the cache fills are created again at each use while the least recently used entry is from the current present, including in the present after a scene change;
  • after the budget falls, the cache can stay at up to 125% of the reduced budget until its entries stop being used;
  • a title whose working set only slightly exceeds the budget now re-creates the excess textures at every use.

Tested

Windows 11 Pro 26200, RTX 4090, driver 617.42. This branch on main 376c040:

  • Full suite: 1509 of 1510 pass, including agc_driver_sampled_cache_budget, agc_driver_sampled_cache_budget_memory and agc_driver_sampled_cache_budget_without_storage_image_multisample.
  • In game: the three runs above.

Related

The remaining churn is capacity: about 4.5M uncached creations an hour. #6982 (drop the guest snapshot of large cached textures) and #6937 (sampled budget, snapshot reuse) touch the same cache; neither changes admission.

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

@github-actions

github-actions Bot commented Oct 18, 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 7862 docs/dev/debt/libSceAgcDriver.md L58

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 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5499 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6146 docs/dev/debt/libSceAgcDriver.md
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 6202 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
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 6280 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 6340 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 6481 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 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 6661 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6683 docs/dev/debt/libSceAgcDriver.md
PR 6718 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6820 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6926 docs/dev/debt/libSceAgcDriver.md
PR 6937 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6970 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6972 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7332 core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7446 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7484 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7533 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7689 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7844 docs/dev/debt/libSceAgcDriver.md
PR 7927 docs/dev/debt/libSceAgcDriver.md
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8014 docs/dev/debt/libSceAgcDriver.md
PR 8099 docs/dev/debt/libSceAgcDriver.md

@github-actions github-actions Bot added docs agc libSceAgc* perf Performance change perf numbers missing perf pull request without a before and after summary line unblocks title Names a title ID on the Unblocks line labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
Pipodi and others added 2 commits October 19, 2026 18:43
…ecently used entry is still in use

Over budget but within a quarter over it, the sampled texture cache refused no use of a texture it does not hold, so a cyclic scan over a working set larger than the budget rotated the whole cache and hit almost never. The first use of a key in a present is now refused (used for its draw, never cached) when the least recently used entry was used in this or the previous present. A later use of the key is refused only when that entry was used in this present, so a texture used several times per present is still admitted on a later use. The refusal does not apply past the quarter-over ceiling, so a budget that falls still evicts.

The test lowers the budget through OverrideCacheBudgetForTest instead of the environment. The debt entry records the unmeasured present window, that a texture used more than once still rotates the cache, and the one A-B-A measurement.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the perf/texture-cache-scan-admission branch from ebeac30 to 8706cd8 Compare October 19, 2026 17:13
@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.

@adriadam10
adriadam10 merged commit b509767 into boykopovar:main Oct 20, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the admission change in admitTexture (the +25% ceiling is tested before the refusal, so trimming after a budget fall still works), the three new tests plus the inverted frame test, and the measured A-B-A series.

Added one commit on your branch: the new docs/dev/debt/libSceAgcDriver.md entry named the title and its PPSA id; it now says "one title" / "one RTX 4090", since the docs keep specific titles out.

Merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs perf numbers missing perf pull request without a before and after summary line perf Performance change unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants