Skip to content

fix(agc): bound the draw cache by bytes - #9129

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-draw-cache-byte-budget
Oct 28, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-draw-cache-byte-budget

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

The dispatch cache evicts by entry count or by bytes: past APS5_DISPATCH_CACHE_BYTES (256 MiB by default) it drops its least recently used entries. The draw cache evicts only by entry count, at 4,096 entries, so it holds whatever those entries capture.

When a draw's SRT walk captures a large table of guest addresses, each variant keeps a copy of it. In Resident Evil Village (PPSA01556)'s main menu, on a local tree of main plus our open PRs, the draw cache's own [draw-cache] profile line went from ~21 MiB to ~2,149 MiB of variants (4,096 entries, ~16,000 variants) at a ~4% hit rate.

The change:

  • insertDrawEntry evicts from the LRU end while size > drawCacheEntries() || (size > 1 && drawCacheVariantBytes > DrawCacheBytes()), the dispatch cache's condition. size > 1 keeps the entry just inserted, as the dispatch cache does.
  • APS5_DRAW_CACHE_BYTES sets the budget: 256 MiB by default, 0 keeps the default, read once. Its row is in ENVIRONMENT.md.
  • Evictions over the byte budget are counted separately and shown in both [draw-cache] lines, e.g. 126 entries (252 variants, ~254.3 MiB of a 256.0 MiB budget), 1473 evictions in total (1473 over the byte budget).
  • Driver::DrawCacheSize() returns entries and bytes under the cache's lock, the twin of DispatchCacheSize().

What the count covers. variantBytes, shared by both caches, counted the sizes of four vectors. It now counts the capacities of every vector a variant owns (runs, words, shared, data positions and slots, run bases, shader data patches, captured words and spans), plus a ShaderMemory's pages for the stamp-validated dispatch variants that keep one. Draw variants keep none. Measured per draw variant:

  • a 2 MiB table read back to back: +0.4%;
  • a stride-48 table of 32-byte records: the old count covered 75% (1,579,408 of 2,106,272 bytes), since words grows without a reserve.

No variant changes size after its insert, so the add and the remove compute the same number.

Two budgets, not one shared. The caches have their own locks and LRU lists. A shared budget would need a victim policy and a lock across both, and a phase heavy in one kind of work would evict the other's entries. The worst case is 512 MiB, and each budget can be tuned.

Limits:

  • Relocation aliases held shared_ptrs to entries, so an entry evicted while an alias named it stayed alive outside the count: @hugodearaujo measured about 2 GB held that way at 14 minutes in 007 First Light (PPSA11386) on main 11e8ad2 (see Tested). Since fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396, on main since 2026-10-24, an alias names a cached key and a lookup drops it once its entry is evicted, so this no longer applies when this PR merges: his rerun on main f95acfc keeps tagVariantRuns at about 253 MiB with this PR (see Tested).
  • DrawEntry, its decode and recipes stay outside the budget, as before.

Speed, from a scratch bench on Linux with lavapipe (fork commit 413c412's VillageDrawCost, with each draw's T# offset taken from a lane value so every variant captures ~1 MiB; 850 draws per frame over 6,800 keys, single runs):

draw cache µs per draw, median peak resident memory
main 5,215 MiB counted after 12 frames, still growing 20,176 10,789 MiB
this PR 254 MiB of 256 6,812 5,379 MiB
no draw cache (APS5_NO_DRAW_SRT_ENTRIES=1) none 3,755 4,142 MiB
  • Without such captures the bench's variants are ~1 KiB and the budget is never reached: 723 against 569 µs per draw, inside the bench's spread.
  • At its 0% hit rate the bench still pays 6.8 against 3.75 ms per draw for caching. perf(libSceAgcDriver): bypass the draw cache while its hit rate is under 5% #5404 bypasses lookups at low hit rates, which is that case.
  • On Linux, part of the remaining gap to the no-cache run is glibc keeping freed ~1 MiB blocks (4,329 MiB with a fixed mmap threshold). Windows builds use mimalloc.

Overlap: checked with git merge-tree against the 24 open PRs touching Draw/DrawCache.cpp, Draw/Draw.cpp or Draw/DrawCacheStatistics.cpp. This PR adds conflicts only with these:

The rest conflict with main already or merge cleanly.

Follow-up, not in this PR: tagVariantRuns could reserve its total word count first, as the dispatch insert does: −25% of words capacity on the stride-48 table.

Tested

  • New case SrtMovingPerDraw in the existing agc_driver_image_table_cache_bounds, beside SrtMovingPerDispatch. 64 keys, each drawn twice (a key is inserted on its second sighting), each capturing its own ~2 MiB of a 2 MiB image table, under a 16 MiB test budget. It checks after every draw that the cache stays within the budget, that the color comes from the table's T#, that evictions happen and are all byte evictions, and that an evicted key is looked up absent, inserted again and then hit with updated records.

    • Red, with only the loop condition put back and the test unchanged: "after draw 15 the draw cache kept 8 entries holding 16580 KiB, over its budget of 16384 KiB".
    • Green: "64 inserts, 57 evictions, 57 over the byte budget, at most 7 entries holding 14521 KiB".
  • Linux (Ubuntu 24.04, GCC 13.3, lavapipe, CI's flags), on the same diff before its rebase onto main fce8ecf:

    • the draw cache, image table, recipe and relocation tests: 20 of 20 (2 skips), as on main;
    • ^agc three times: 860, 862 and 860 of 862, against 861, 861 and 862 on main. The failures are agc_driver_pipelined_fill, agc_driver_storage_write_back_no_window and agc_driver_sgpr_pattern_fill_keys, timing tests that fail on main too and don't touch the draw cache;
    • the full CI ctest: 1,796 of 1,802 against 1,795 on main, the same six network failures (no IPv6 on that machine);
    • registration matches.
  • GitHub's runners (run, same diff before the rebase):

    • Linux: the case passes ×10, and the libc and libkernel suites pass.
    • Windows: it builds and the suites pass; the case skips there, as the Vulkan tests do on that runner.
    • With main's code the case doesn't build, because it uses the new DrawCacheSize().
  • Windows with an NVIDIA GPU, where the case runs instead of skipping, by @gdols (comment): GeForce RTX 3080, driver 616.92, Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, main 80565c5 (with test(agc): align the array window test's texels at run time #8310 to build) and this PR at f2c368b, with ANYPS5_REQUIRE_VULKAN=1 and ANYPS5_NO_SHADER_CACHE=1.

    • agc_driver_image_table_cache_bounds under VK_LAYER_KHRONOS_validation (SDK 1.4.363.0): 3 of 3 with no validation messages. SrtMovingPerDraw reports the green line above.
    • With only the loop condition put back, it fails 3 of 3 with the red line above.
    • ^agc_: 870 passed, 3 skipped and 3 failed, the same as on main. They name the three as agc_driver_sample_order*, which they say test(agc): read the sample order tests' depth through a sampled copy #9056 fixes.
    • Not run in a title.
  • MinGW -fsyntax-only on the five changed sources: no errors.

  • check_conventions.py passes, with no comment lines added.

  • Resident Evil Village (PPSA01556), static main menu, 11 min without input.

    • Both arms: Windows, RTX 3070, the same warm shader cache, and the same environment (APS5_LAZY_SHADER_PREPARE=1, APS5_NO_GPL=1, APS5_HOST_IMPORT_MIB=6400, APS5_PROFILE_DRAW=1).
    • Arm A is main 1ff5c8a with fix(agc): put a retired import's unchanged ranges back in the write watch #8926. Arm B adds this change. The PR itself is on current main.
    • GPU clocks weren't held, so the frame times are rough.
    • Each arm's last [draw-cache] profile line, from its entry count on:
      • A: 4096 entries (18017 variants, ~2596.3 MiB), 3655 evictions in total, 18 LRU moves
      • B: 282 entries (564 variants, ~255.0 MiB of a 256.0 MiB budget), 25447 evictions in total (25447 over the byte budget), 0 LRU moves
    A: without B: with this change
    [draw-cache] at the end 4,096 entries, 18,017 variants, ~2,596 MiB 282 entries, 564 variants, ~255.0 MiB of the 256 MiB budget, every eviction over the byte budget
    hits, menu +1 to +10 min 2.65% (4,342 of 163,617) 0% (0 of 184,094)
    private bytes, +1 to +10 min (rate: least-squares fit over the ~13 s samples) 19,314 → 25,087 MB, +654 MB/min 18,904 → 21,078 MB, +223 MB/min
    present interval over the same window, median / p90 ~2.73 s / ~3.27 s ~2.32 s / ~2.86 s
    • The budget cuts the menu's private-memory growth by about two thirds, from 654 to 223 MB/min, and keeps the cache at its budget.
    • The cost: the hit rate at that menu drops from 2.65% to ~0%. This pair was no slower with it. With clocks not held and ~200 presents per arm, I claim nothing stronger.
    • The remaining ~220 MB/min grows outside the draw cache, so this doesn't fix the menu's growth on its own. Its holder isn't found yet; that's for a separate change.
  • Merged with main 0db47b2 (73ec16a). One conflict, in the [draw-cache] line in DrawCacheStatistics.cpp: the merged line keeps perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946's shape-update count and this PR's byte budget and byte-budget evictions (format and arguments checked, 30 and 30). On GitHub's runners (run), agc_driver_image_table_cache_bounds and the seven agc_driver_draw_cache_* tests pass 10 runs in a row on Linux, none skipped; on Windows all 8 build and skip, as the runner has no Vulkan device. The libc and libkernel suites pass on both, run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • Ghost of Tsushima Director's Cut (PPSA02225), by @milescoviello (comment): Linux, RTX 4070 Ti, warm cache, a 20 GiB cap, on a stack with feat(agc): draw one-sample color targets beside a multisampled depth target with sample 0's coverage #8115, fix(agc): draw a PS_ITER_SAMPLE program once per pixel into a one-sample color target #8121, fix(agc): read the pipeline journal's modules at replay instead of holding the file #8213, perf(agc): take captureValidation's records in one pass over the lookup log #8608 and fix(agc): bound sampled array views to guest allocations #9318, with and without this PR at f2c368b.

    • Without it, the draw cache reached 3,657 entries and ~156 MiB by main's count within 10 minutes of the opening. A jemalloc profile of the same phase, at 1,158 s, put 1,011 MiB live under Driver::tagVariantRuns (from cacheDrawStages), so main's count undercounts these variants.
    • With it, the cache reached 3,696 entries and ~184 MiB by 10 minutes, then held between ~247 and ~256 MiB of the 256 MiB budget, keeping 264 to 659 entries of about 0.6 MB each.
    • The title still reaches its memory cap in the opening, from other growth (the NVIDIA driver, specialized modules, materialized results). The draw cache is no longer part of it.
  • 007 First Light (PPSA11386) 01.018.000, on Linux with an RX 9070 XT (RADV, Mesa 26.2.5, Release), by @hugodearaujo on main 11e8ad2 with this PR's code (comment), 15 minutes on the First Launch Options screen per run: [draw-cache] holds the cache at its budget (688 entries (1447 variants, ~255.6 MiB of a 256.0 MiB budget)), with 47,412 byte-budget evictions in 12 minutes. Estimated live heap at 14 minutes: 11,351 MiB on main, 7,272 MiB with this PR, and 5,294 MiB with this PR and APS5_NO_DRAW_RELOCATION=1. hugodearaujo reads the difference between the last two as evicted entries that relocation aliases still named, the case in Limits; his rerun below, on a main with fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396, no longer shows it.

  • 007 First Light (PPSA11386) 01.018.000, rerun by @hugodearaujo on main f95acfc, which has fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396, alone and with this PR's head fa02afe merged (clean), one run each, set up as before, 15 minutes from launch with X pressed every 30 s (comment). Estimated live heap at 14 minutes: 12,170 MiB on main and 7,329 MiB with this PR; allocated in tagVariantRuns: 3,004 MiB and 253 MiB. With this PR, tagVariantRuns holds 251-254 MiB from 3 minutes to the end, where on 11e8ad2 it climbed to 1,213 MiB, so with fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396 relocation aliases no longer keep evicted entries alive in this title. Graphics::Draw under commitDraw: 1,227 MiB on main, 172 MiB with this PR. RSS at 14 minutes: 14.6 GB on main, 9.2 GB with this PR. He observes that most of what remains with this PR is storage-texture upload buffers, about 4.9 GB in both runs.

  • 332b5f3, a merge of main f1f1737 (run), as oneandonlydean asked. The one conflict was in Draw/DrawCacheStatistics.cpp, where main added Driver::OverlayCounters() at the place this PR adds Driver::DrawCacheSize(); the merge keeps both functions, and this PR's own added and removed lines are the same. agc_driver_image_table_cache_bounds, agc_driver_pipelined_fill, agc_driver_sgpr_pattern_fill_keys, agc_driver_storage_write_back_no_window, agc_performance_overlay and agc_performance_overlay_statistics pass 10 runs in a row. Linux: 6 run, 6 pass, 0 skip, 0 fail. Windows: 6 run, 1 pass, 5 skip (the Vulkan tests, with no Vulkan device), 0 fail. Suites: libc and libkernel pass on Linux (161 and 264). On Windows libc passes (189) and libkernel passes 283 of 286: exec_arguments, guest_timed_join_import_libkernel_sys and guest_timed_join_import_libkernel_web fail at their setup in the verify's Windows build (FileNotFoundError starting the title, or copying the libraries they load), and pass in main's own Windows job at 1829def (job). The suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • Windows with a Vulkan device, by @milescoviello (comment): a Windows 11 VM with a GeForce GT 1030 (driver 582.78), WinLibs GCC 15.2.0, Release. 332b5f3 and its merge base, main f1f1737, each ran the six tests above 3 times, and all six pass in every run on both. The Vulkan ones run there instead of skipping, at 1 to 7 s each.

Checklist

  • Based on current main fce8ecf; 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 (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Related: perf(libSceAgcDriver): bypass the draw cache while its hit rate is under 5% #5404, perf(agc): compile uncached draws ahead of queue 0 #6188
  • Unblocks: none claimed (in Resident Evil Village (PPSA01556) it cuts the menu's private-memory growth by about two thirds, 654 → 223 MB/min; the rest grows outside the draw cache)
  • GPUs tested: lavapipe only so far; the change is CPU-side cache accounting
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 21, 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 9243 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp L67
PR 9248 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp L67

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 1896 docs/dev/ENVIRONMENT.md
PR 5609 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 6185 docs/dev/ENVIRONMENT.md
PR 6186 docs/dev/ENVIRONMENT.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp
core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp
PR 6804 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 7275 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 7369 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp
PR 7449 docs/dev/ENVIRONMENT.md
PR 7457 docs/dev/ENVIRONMENT.md
PR 7514 docs/dev/ENVIRONMENT.md
PR 7604 docs/dev/ENVIRONMENT.md
PR 7922 docs/dev/ENVIRONMENT.md
PR 8004 docs/dev/ENVIRONMENT.md
PR 8484 docs/dev/ENVIRONMENT.md
PR 8542 docs/dev/ENVIRONMENT.md
PR 9012 docs/dev/ENVIRONMENT.md
PR 9335 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 9345 docs/dev/ENVIRONMENT.md
PR 9356 docs/dev/ENVIRONMENT.md
PR 9387 docs/dev/ENVIRONMENT.md
PR 9508 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp
docs/dev/ENVIRONMENT.md
PR 9676 docs/dev/ENVIRONMENT.md
PR 9852 docs/dev/ENVIRONMENT.md
PR 9898 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp
docs/dev/ENVIRONMENT.md
PR 10069 docs/dev/ENVIRONMENT.md
PR 10193 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp
docs/dev/ENVIRONMENT.md
PR 10194 docs/dev/ENVIRONMENT.md
PR 10359 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 10442 docs/dev/ENVIRONMENT.md
PR 10547 docs/dev/ENVIRONMENT.md
PR 10757 docs/dev/ENVIRONMENT.md
PR 10894 docs/dev/ENVIRONMENT.md
PR 11351 docs/dev/ENVIRONMENT.md

@gdols

gdols commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

NVIDIA, Windows, where the new case runs instead of skipping. GeForce RTX 3080, driver 616.92, Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, main 80565c5 (+ #8310 to build), this PR at f2c368b, ANYPS5_REQUIRE_VULKAN=1 and ANYPS5_NO_SHADER_CACHE=1.

  • agc_driver_image_table_cache_bounds under VK_LAYER_KHRONOS_validation (SDK 1.4.363.0, inserted per VK_LOADER_DEBUG=layer): passes 3 of 3 with no validation messages. SrtMovingPerDraw reports "64 inserts, 57 evictions, 57 over the byte budget, at most 7 entries holding 14521 KiB", the numbers in the description.
  • With only the loop condition put back, it fails 3 of 3: "after draw 15 the draw cache kept 8 entries holding 16580 KiB, over its budget of 16384 KiB".
  • ^agc_: 870 passed, 3 skipped and 3 failed, the same as main; the three are agc_driver_sample_order*, fixed by test(agc): read the sample order tests' depth through a sampled copy #9056.

Not run in a title.

AI-assisted: yes

@EagleBearerYS
EagleBearerYS marked this pull request as ready for review October 21, 2026 14:26
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@gdols, thank you for running the new case on a GPU under the validation layer, with the revert check. It's now in Tested.

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS a second title for this PR: Ghost of Tsushima Director's Cut (PPSA02225), on your test/ghost-9742-9717 stack (with #8115, #8121, #8213, #8608 and #9318) and f2c368b242 merged. This is warm, 20 GiB cap, Linux, RTX 4070 Ti.

  • Without this PR: in the opening, the cache reached 3,657 entries and ~156 MiB by main's count within 10 minutes. A jemalloc profile of the same phase, at 1,158 s, put 1,011 MiB live under Driver::tagVariantRuns (from cacheDrawStages), the biggest item in our code. That fits your capacity count: main undercounts these variants.
  • With it: the cache reached 3,696 entries and ~184 MiB by 10 minutes, then held between ~247 and ~256 MiB of the 256 MiB budget. By then entries were about 0.6 MB each, so it kept only 264 to 659 of them. At 4,096 entries that would have been ~2.4 GB.

This title still runs out of its cap in the opening, because other things grow there too: the NVIDIA driver, specialized modules and materialized results. The bound does what it says, though, and takes the draw cache out of that list.

AI-assisted: yes (Claude Code)

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

I reviewed the scope and current evidence: the PR adds a byte budget alongside the entry limit, expands per-variant accounting, and includes a focused over-budget regression. The report now includes a Windows validation-layer run with an over-budget negative control and an independent Ghost of Tsushima measurement; hosted policy checks pass, while product jobs are skipped. Local diff whitespace, conventions, and registration checks pass. I could not run Vulkan tests in this host.

I cannot complete an integration review against the published branch yet: tools/check_main_sync.py --base upstream/main --head <head> reports it is 2 commits ahead / 1257 behind current upstream/main (merge base fce8ecf16), with a clean merge. Driver.hpp and ENVIRONMENT.md also changed on main since that base (#7233 and #9026). Please merge current main, preserve those current changes and the declared overlaps, and rerun the focused cache-bound test plus conventions on the resulting head. This is based on the repository's current-main branch rule, not on the open-PR count.

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

Copy link
Copy Markdown
Contributor Author

@milescoviello thank you for the Ghost of Tsushima Director's Cut (PPSA02225) pair and the jemalloc profile. The 1,011 MiB under tagVariantRuns, against main's ~156 MiB count, is the undercount this PR's per-variant accounting fixes, and it's good to see the budget hold at ~250 MiB of 256 in another title. I've added your numbers to the description.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10 adriadam10 self-assigned this Oct 23, 2026
@adriadam10
adriadam10 force-pushed the fix/agc-draw-cache-byte-budget branch from 73ec16a to a001a59 Compare October 23, 2026 20:19
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10
adriadam10 force-pushed the fix/agc-draw-cache-byte-budget branch from a001a59 to 45876fa Compare October 24, 2026 00:33
@EagleBearerYS
EagleBearerYS force-pushed the fix/agc-draw-cache-byte-budget branch from 45876fa to fa02afe Compare October 24, 2026 02:17
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@adriadam10 I re-authored your squashed commit with my GitHub noreply address (fa02afe, was 45876fa), so the commit links to my account. Your squash and the tree are unchanged: git diff 45876fa6de fa02afeb47 is empty, and the message and author date are the same.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@MarcZX10 73ec16a merged main 0db47b2 with the draw-cache tests passing 10 of 10 on Linux (in Tested), and the branch is now the squash onto 5e6ae6e (fa02afe), where conventions pass. Could you take another look?

@hugodearaujo

Copy link
Copy Markdown
Contributor

@EagleBearerYS a measurement of the relocation-alias limit in your Limits section, from 007 First Light (PPSA11386 01.018.000). Linux, RX 9070 XT, RADV Mesa 26.2.5, Release; main 11e8ad2 alone and with this PR merged (head a001a59, whose code diff is the same as fa02afe's; clean apart from docs/dev/ENVIRONMENT.md). Each run is 15 minutes on the title's First Launch Options screen.

With this PR, [draw-cache] shows the cache at its budget the whole time, e.g. 688 entries (1447 variants, ~255.6 MiB of a 256.0 MiB budget), with 47,412 evictions over the byte budget in 12 minutes.

I sampled live heap allocations by call stack with a small LD_PRELOAD sampler: one sample per 256 KiB allocated, dropped when freed. Estimated live MiB at 14 minutes, one run per row:

live heap allocated in tagVariantRuns Populate under materializeDrawStage Graphics::Draw under commitDraw
main 11,351 3,691 1,412 1,469
this PR 7,272 1,213 526 550
this PR, APS5_NO_DRAW_RELOCATION=1 5,294 253 under 150 under 150
  • With this PR, the tagVariantRuns allocations (the variants' words, about 1 MiB each here) stay near 260 MiB for the first 4 minutes, then climb in steps to 1.2 GB. With draw relocation off, they stay at 253 MiB.
  • The two other columns follow the same pattern, so I read them as held by the same entries.
  • So in this title, evicted entries that drawAliases still names hold about 2 GB at 14 minutes, outside the count. MaxAliases is 16,384, four times the entry limit.
  • What's left in the third run is mostly storage-texture upload buffers (about 2.4 GB in all three runs) and Populate on the dispatch path (707 MiB in all three).

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@hugodearaujo thank you, that measures the case the Limits section only described. Since #9396 (merged 2026-10-24, after the main 11e8ad2 you used), a relocation alias names a cached key instead of holding the entry, and a lookup drops an alias whose entry has been evicted. So with this PR on current main, an alias no longer keeps an evicted entry alive. I've updated the Limits section and put your numbers in the Tested section. If you rerun on a main that includes #9396 (be7e039 or later), I'd like to add that run's tagVariantRuns figure too.

@hugodearaujo

Copy link
Copy Markdown
Contributor

@EagleBearerYS the rerun on a main with #9396. 007 First Light (PPSA11386 01.018.000), set up as before: Linux, RX 9070 XT, RADV Mesa 26.2.5, Release, the same LD_PRELOAD sampler, 15 minutes from launch with X pressed every 30 s. Both runs reach the First Launch Options screen by 9 minutes and stay there. main f95acfc alone and with this PR's head, fa02afe, merged (clean), one run each.

Estimated live MiB at 14 minutes:

live heap allocated in tagVariantRuns
main f95acfc 12,170 3,004
this PR 7,329 253
  • With this PR, tagVariantRuns holds 251-254 MiB from 3 minutes to the end. On 11e8ad2 it climbed to 1,213 MiB, and 253 MiB is what it held there with APS5_NO_DRAW_RELOCATION=1. So with fix(agc): let draw relocation aliases name a cached key, not hold its entry #9396, aliases no longer keep evicted entries alive in this title.
  • Graphics::Draw under commitDraw follows the same pattern: 1,227 MiB on main, 172 with this PR. RSS at 14 minutes is 14.6 GB on main and 9.2 GB with this PR.
  • Most of what's left with this PR is storage-texture upload buffers, about 4.9 GB in both runs (about 2.4 GB on 11e8ad2).

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@hugodearaujo thank you for the rerun; tagVariantRuns staying at about 253 MiB with this PR is the check that was missing. I've added it to the Tested section and to the Limits note.

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

Copy link
Copy Markdown
Collaborator

@EagleBearerYS Thanks. This now conflicts with main in Draw/DrawCacheStatistics.cpp: main added Driver::OverlayCounters() where this adds Driver::DrawCacheSize(). Keeping both functions resolves it. Could you merge main?

# Conflicts:
#	core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCacheStatistics.cpp
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 28, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean merged main f1f1737 (332b5f3), keeping both Driver::OverlayCounters() and Driver::DrawCacheSize() in DrawCacheStatistics.cpp. The cache-bounds, fill and overlay tests pass 10 runs in a row on Linux (6 of 6). On Windows the one test that needs no device passes and the five Vulkan ones skip, as that runner has no device (run).

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS a Windows run with a Vulkan device, for the five tests your Windows runner skips. Setup: a Windows 11 VM with a GT 1030 (driver 582.78), WinLibs GCC 15.2.0, Release.

332b5f3d28 and its merge base f1f1737371 each ran the six tests 3 times: agc_driver_image_table_cache_bounds, agc_driver_pipelined_fill, agc_driver_sgpr_pattern_fill_keys, agc_driver_storage_write_back_no_window, agc_performance_overlay and agc_performance_overlay_statistics. All 6 pass in every run on both. ctest reports the Vulkan ones as passed rather than skipped, at 1 to 7 s each; for example, agc_driver_pipelined_fill takes 6.4 to 6.8 s and agc_driver_image_table_cache_bounds 2.1 to 3.4 s.

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@milescoviello thanks, that covers the five tests GitHub's Windows runner skips, and on both trees. I've added your run to Tested.

@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 28, 2026 06:50

Dismissing a stale review: the requested changes were addressed after it (new head) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit fddf0ff into boykopovar:main Oct 28, 2026
14 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked after main changed Draw/DrawCacheStatistics.cpp: the merge keeps both Driver::OverlayCounters() and Driver::DrawCacheSize(), and the [draw-cache] lines still carry the shape-update count, the byte budget and the byte-budget evictions. The rest of the diff is unchanged (size > 1 byte-budget eviction, capacity-based variantBytes, counters, ENVIRONMENT.md row); SrtMovingPerDraw fails without the loop change and passes on @gdols' RTX 3080 run, and @milescoviello ran the six related tests on this head on Windows with a Vulkan device. The branch contains main and the cache-bound test and conventions were rerun, which covers what @MarcZX10 asked; merged.

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.

7 participants