Skip to content

perf(agc): drop the guest byte snapshot of a large cached texture the write tracker covers - #6982

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
XairRrR:perf/agc-drop-tracked-texture-snapshot
Oct 20, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
XairRrR:perf/agc-drop-tracked-texture-snapshot

Conversation

@XairRrR

@XairRrR XairRrR commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

Each snapshot texture in the sampled texture cache (cachedTexture in ShaderResources.cpp) keeps a host copy of its guest bytes. The copy is used only when the write tracker reports the surface written, to tell a rewrite of the same bytes from a change. The cache budget counts only the GPU allocation, so these host copies grow with the cache without being counted. In Uncharted: Legacy of Thieves Collection (PPSA05684), they held 2.7 GB of heap in the first level (the boat).

  • A snapshot larger than 256 KiB is released after the upload when the write tracker can prove the surface unchanged: the entry has a write generation (watched or partially committed memory) or an untracked key.
  • The entry keeps the surface size (surfaceBytes) for the hit check and the generation check, because the byte vector can now be empty.
  • A tracked write to such a surface re-uploads it instead of comparing bytes (equalsCommitted returns false with no snapshot). The cost is that a same-bytes rewrite of a large texture is uploaded again.
  • Snapshots of 256 KiB or less, and surfaces the tracker cannot vouch for, keep their copy as before.

Overlap: #6937 changes the same unchanged line (it checks pending writes before both generation proofs). The two are independent; whichever merges second needs a one-line rebase.

Tested

Linux (7.2.9-zen), RTX 4070 Ti SUPER.

  • New agc_driver_large_tracked_snapshot (--large-tracked mode of agc_driver_partially_resident_snapshot_tests). It samples a 512x512 R32 surface (1 MiB) twice: once fully committed and once with only its first 64 KiB block committed. It checks that:

    • an unchanged surface is reused with no guest reads and the same texture;
    • a CPU write (first block and last committed block) is uploaded as a new texture;
    • a driver store of the bytes already there is uploaded again, which shows that no snapshot is kept (a 256 KiB surface compares its snapshot and keeps the texture, see agc_driver_partially_resident_snapshot).
  • Negative controls, each reverted afterwards:

    • With equalsCommitted treating an empty snapshot as equal (a tracked write is missed), the test fails with "sampling the large committed surface after a CPU write kept the texture uploaded before it".
    • Without this change (snapshot kept), it fails at the same-bytes driver store.
  • ctest -j4: 1315/1316 pass. The one failure is agc_driver_store_over_untracked, which also fails 3/3 on plain main on this machine (fix(agc): save pending storage results in every 64 KiB block that leaves the write watch #5671, fixed by test(agc): keep waiting for the neighbour copy case's result #6385).

  • python3 tools/check_conventions.py --base origin/main: passed.

  • PPSA05684: the 2.7 GB of snapshot copies was measured in the boat level with a heap profile before the change. The change has been in my local integration build, which plays the boat level. This branch on its own was not run in the title.

  • Performance capture, PPSA05684 main menu after the intro, 60 s window (APS5_TRACE_SECONDS=150:60), warm caches (each build ran the menu once and the title was restarted), no other APS5_TRACE_* or APS5_PROFILE_* switch. The title does not run on plain main yet, so both builds are my local integration branch (main 864729d + open PRs + local patches, the same APS5_* switches): 0a80c84ef6a4 carries this change (its driver code is identical to 6849eea; only the test differs), and 896ddc7a8982 is the same branch with this change reverted. The launch script writes the log to a file. Frame times are the same within noise (p50 108.8 -> 110.0 ms, p99 136.5 -> 131.1 ms, GPU busy 81.1 -> 81.3 ms per present). The saving is the host memory of large snapshots, which grows in levels, not in the menu. In both captures queue worker 0x0 filled its event buffer after about 8 s (events_lost, totals complete), so its slices after that are missing from the traces, and the summary numbers are complete.

    Without the change (896ddc7a8982):

{"commit":"896ddc7a8982","dirty":false,"env":{"APS5_COMPLETION_STORE_SYNC":"1","APS5_DISPATCH_CACHE_ENTRIES":"4096","APS5_DRAIN_COMPLETION_LABELS":"1","APS5_DUMP_FRAMES":"0","APS5_DUMP_FRAMES_EVERY":"40","APS5_HOST_IMPORT_MIB":"16384","APS5_IMAGE_TABLE_KEY_SCAN":"262144","APS5_LAZY_SHADER_PREPARE":"1","APS5_LOCAL_NULL_INVALID_BUFFERS":"1","APS5_LOCAL_REBASE_HUGE_ARRAYS":"1","APS5_NO_LABEL_BATCHING":"1","APS5_NO_LIBRARY_PREBUILD":"1","APS5_NO_PIPELINE_LTO":"1","APS5_NO_SNAPSHOT_CHECK":"1","APS5_PAD_INPUT_FILE":"","APS5_TRACE_DIR":"","APS5_TRACE_SECONDS":"150:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti SUPER","driver":"NVIDIA 615.78.08","os":"Linux 7.2.9-zen1-1-zen","resolution":"2560x1440","window":{"spec":"seconds 150:60","first_present":1625,"ms":60006.870},"presents":540,"frame_ms":{"p50":108.827,"p90":126.267,"p99":136.510,"max":219.750},"gpu_busy_ms_per_present":81.079,"worker_busy_pct":{"queue worker 0x0":99.329,"queue worker 0x43":99.861,"queue worker 0x21":4.303},"top_phases_ms_per_frame":{"Driver.Packet":226.047,"Driver.Packet.memory_wait":79.417,"Driver.Packet.dispatch":63.328,"Driver.Dispatch":48.361,"Driver.Packet.draw":33.292,"Driver.Draw":33.106,"Driver.Packet.device_lock":32.826,"Driver.Draw.shader_compile_and_link":22.071},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":7.796,"events_lost":1737049,"totals_full":false}},"trace":"aps5-trace-896ddc7a8982-412912.json.gz"}

With the change (`0a80c84ef6a4`):
```json
{"commit":"0a80c84ef6a4","dirty":false,"env":{"APS5_COMPLETION_STORE_SYNC":"1","APS5_DISPATCH_CACHE_ENTRIES":"4096","APS5_DRAIN_COMPLETION_LABELS":"1","APS5_DUMP_FRAMES":"0","APS5_DUMP_FRAMES_EVERY":"40","APS5_HOST_IMPORT_MIB":"16384","APS5_IMAGE_TABLE_KEY_SCAN":"262144","APS5_LAZY_SHADER_PREPARE":"1","APS5_LOCAL_NULL_INVALID_BUFFERS":"1","APS5_LOCAL_REBASE_HUGE_ARRAYS":"1","APS5_NO_LABEL_BATCHING":"1","APS5_NO_LIBRARY_PREBUILD":"1","APS5_NO_PIPELINE_LTO":"1","APS5_NO_SNAPSHOT_CHECK":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"150:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti SUPER","driver":"NVIDIA 615.78.08","os":"Linux 7.2.9-zen1-1-zen","resolution":"2560x1440","window":{"spec":"seconds 150:60","first_present":1638,"ms":60003.705},"presents":537,"frame_ms":{"p50":109.950,"p90":120.595,"p99":131.132,"max":214.145},"gpu_busy_ms_per_present":81.315,"worker_busy_pct":{"queue worker 0x0":99.329,"queue worker 0x43":99.856,"queue worker 0x21":4.081},"top_phases_ms_per_frame":{"Driver.Packet":227.043,"Driver.Packet.memory_wait":79.984,"Driver.Packet.dispatch":64.505,"Driver.Dispatch":49.302,"Driver.Packet.draw":47.192,"Driver.Draw":47.009,"Driver.Draw.shader_compile_and_link":21.515,"Driver.Dispatch.capture_resources":20.201},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":7.990,"events_lost":1702112,"totals_full":false}},"trace":"aps5-trace-0a80c84ef6a4-426781.json.gz"}

Traces and tools/trace_digest.py output: in the comment below.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

@github-actions github-actions Bot added build agc libSceAgc* labels Oct 17, 2026
@keravyn-ops

Copy link
Copy Markdown
Contributor

AMD / Windows run of this PR at 12e687d: Radeon 780M (RDNA 3, 0x15bf), AMD driver 25.10.20.01 (32.0.21020.1007), Windows 11 Home 10.0.26100, WinLibs GCC 15.2.0 r7, Release.

Every ctest entry built from the test targets this PR changes passes (4):

  • agc_driver_partially_resident_snapshot (0.21 s): 568: partially resident snapshot tests passed
  • agc_driver_partially_resident_pending_stores (0.11 s): 711: partially resident snapshot tests passed
  • agc_driver_large_tracked_snapshot (0.12 s): 712: partially resident snapshot tests passed
  • agc_driver_untracked_snapshot_hint (0.11 s): 713: partially resident snapshot tests passed

AI-assisted: yes (runs made on the device above).

… write tracker covers

Every snapshot texture in the sampled texture cache kept a host copy of
its guest bytes, used only when the write tracker reports the surface
written, to tell a rewrite of the same bytes from a change. The cache
budget counts the GPU allocation alone, so the host copies grew with it:
PPSA05684 held 2.7 GB of them in its first level.

A snapshot larger than 256 KiB is released after the upload when the
write tracker (a write generation or an untracked key) can prove the
surface unchanged. A tracked write to such a surface re-uploads it
instead of comparing bytes; smaller snapshots, and surfaces the tracker
cannot vouch for, keep their copy as before.

agc_driver_large_tracked_snapshot samples a 1 MiB surface, committed
and partially resident: an unchanged surface is reused without reading
guest memory, a CPU write is uploaded, and a driver store of the same
bytes is uploaded again because no snapshot is left to compare.

AI-assisted: yes
@XairRrR
XairRrR force-pushed the perf/agc-drop-tracked-texture-snapshot branch from 12e687d to 6849eea Compare October 17, 2026 16:56
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@fma-rc

fma-rc commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Title numbers for this change, Linux, RTX 5080 (NVIDIA 615.78.08). Both sides are main 51cee90, one with this PR's head 6849eea merged, one round per side.

Title frames main / PR peak RSS MiB main / PR
Astro's Playroom, 10 s window 111 / 125 16747 / 15335
Astro Bot, 60 s window 1123 / 1149 22430 / 18017

App VRAM is 189 MiB higher on Playroom and equal on Astro Bot. A Playroom burst of every 6th present shows the same picture on both sides. The Astro Bot frame difference is within run variance; the Playroom one comes from a single round. Demon's Souls gave no comparison: main hit the heap corruption that PR 6567 fixes.

agc_driver_large_tracked_snapshot fails on main with only the test hunks and passes on the PR, 3/3 each on a Radeon 680M (RADV).

One note on the test: it shows the release only through its cost, the re-upload after a same-bytes store. Nothing checks that the snapshot memory is actually freed for a tracked surface over 256 KiB, and the threshold itself has no measurement behind it in the PR.

@XairRrR

XairRrR commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @fma-rc for the title runs.

On the test: the same-bytes driver store is the observable for the release. equalsCommitted returns false only when the entry holds no bytes, so a store of identical bytes re-uploads only if the snapshot was dropped. With a snapshot it would be reused, which is what the small-surface case still does. I can add a direct check of the held snapshot bytes if you prefer, but that needs a new accessor on the sampled cache just for the test.

On the threshold: you're right, 256 KiB has no measurement behind it. It keeps the byte compare for small surfaces, where the copy costs little and a same-bytes rewrite is cheap to catch. The PPSA05684 figure (2.7 GB) came from surfaces far above it. I have no data that favours 256 KiB over another cut.

AI-assisted: yes

@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line and removed perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@XairRrR

XairRrR commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Traces of the two captures in the description, and tools/trace_digest.py output.

Both open in ui.perfetto.dev.

Without the change (896ddc7a8982):

Slowest frames (largest slices inside each, inclusive)

  • present 1859: 219.8 ms at 25925 ms: Driver.Packet (queue worker 0x43) 218.4, Driver.WorkerWait (queue worker 0x21) 215.5, Driver.WorkerWait.dequeue_order (queue worker 0x21) 215.4, Driver.Packet.dispatch (queue worker 0x43) 144.8
  • present 1987: 150.7 ms at 40223 ms: Driver.Packet (queue worker 0x43) 149.4, Driver.WorkerWait (queue worker 0x21) 147.4, Driver.WorkerWait.dequeue_order (queue worker 0x21) 146.5, batch (gpu) 81.7
  • present 1871: 139.5 ms at 27407 ms: Driver.Packet (queue worker 0x43) 138.3, Driver.WorkerWait (queue worker 0x21) 135.2, Driver.WorkerWait.dequeue_order (queue worker 0x21) 135.2, batch (gpu) 81.6
  • present 2141: 137.4 ms at 57361 ms: Driver.Packet (queue worker 0x43) 136.1, Driver.WorkerWait (queue worker 0x21) 133.2, Driver.WorkerWait.dequeue_order (queue worker 0x21) 133.2, batch (gpu) 81.8
  • present 2043: 137.3 ms at 46416 ms: Driver.Packet (queue worker 0x43) 135.9, Driver.WorkerWait (queue worker 0x21) 133.7, Driver.WorkerWait.dequeue_order (queue worker 0x21) 132.9, batch (gpu) 81.6

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 11.5 (10 %) 1.8 (2 %) 97.8 7.6 Driver.Packet 13.3, Driver.Completion 0.0
presenter 0.5 (0 %) 2.7 (2 %) 107.9 0.1 VideoOut.Flip 3.2, Driver.Present 0.0
queue worker 0x43 37.3 (34 %) 72.5 (65 %) 1.3 4.5 Driver.Packet 109.8, Driver.EndSubmission 0.0
queue worker 0x21 0.8 (1 %) 109.9 (99 %) 0.3 1.0 Driver.Packet 4.6, Driver.EndSubmission 0.0
gpu 81.1 (73 %) 0.0 (0 %) 30.0 0.0 batch 81.1

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 127.8, Driver.WorkerWait 106.1, Driver.WorkerWait.dequeue_order 105.9, batch 81.1, Driver.Packet.memory_wait 65.7, Driver.Packet.dispatch 42.8, Driver.Dispatch 27.7, Driver.Packet.prepare_memory 10.6, Driver.Dispatch.materialize 8.9, Driver.Dispatch.capture_resources 8.9

Longest waits

  • Driver.WorkerWait (queue worker 0x21) 215.5 ms at 25929 ms, present 1859
  • Driver.WorkerWait (queue worker 0x21) 147.4 ms at 40226 ms, present 1987
  • Driver.WorkerWait (queue worker 0x21) 135.2 ms at 27411 ms, present 1871
  • Driver.WorkerWait (queue worker 0x21) 134.5 ms at 14546 ms, present 1757
  • Driver.WorkerWait (queue worker 0x21) 133.7 ms at 46420 ms, present 2043
  • Driver.WorkerWait (queue worker 0x21) 133.2 ms at 57365 ms, present 2141
  • Driver.WorkerWait (queue worker 0x21) 132.1 ms at 39336 ms, present 1979
  • Driver.WorkerWait (queue worker 0x21) 131.3 ms at 14085 ms, present 1753
  • Driver.WorkerWait (queue worker 0x21) 131.0 ms at 15458 ms, present 1765
  • Driver.WorkerWait (queue worker 0x21) 129.8 ms at 21045 ms, present 1815

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 28390: 15.9 ms at 13230 ms, present 1745: GPU no ranges; recorded by no CPU slices
  • batch 62931: 15.8 ms at 29695 ms, present 1891: GPU no ranges; recorded by no CPU slices
  • batch 56769: 15.7 ms at 26711 ms, present 1865: GPU no ranges; recorded by no CPU slices
  • batch 124808: 15.5 ms at 58582 ms, present 2152: GPU no ranges; recorded by no CPU slices
  • batch 114177: 15.5 ms at 53525 ms, present 2107: GPU no ranges; recorded by no CPU slices

With the change (0a80c84ef6a4):

Slowest frames (largest slices inside each, inclusive)

  • present 1820: 214.1 ms at 20340 ms: Driver.Packet (queue worker 0x43) 212.9, Driver.WorkerWait (queue worker 0x21) 210.4, Driver.WorkerWait.dequeue_order (queue worker 0x21) 209.9, Driver.Packet.dispatch (queue worker 0x43) 131.4
  • present 1837: 134.5 ms at 22348 ms: Driver.Packet (queue worker 0x43) 133.4, Driver.WorkerWait (queue worker 0x21) 130.5, Driver.WorkerWait.dequeue_order (queue worker 0x21) 130.4, batch (gpu) 82.0
  • present 1711: 134.4 ms at 8153 ms: Driver.Packet (queue worker 0x43) 133.2, Driver.WorkerWait (queue worker 0x21) 130.4, Driver.WorkerWait.dequeue_order (queue worker 0x21) 130.4, batch (gpu) 81.3
  • present 1718: 131.3 ms at 8956 ms: Driver.Packet (queue worker 0x43) 130.1, Driver.WorkerWait (queue worker 0x21) 125.6, Driver.WorkerWait.dequeue_order (queue worker 0x21) 125.6, batch (gpu) 81.5
  • present 1696: 131.1 ms at 6443 ms: Driver.Packet (queue worker 0x43) 129.9, Driver.WorkerWait (queue worker 0x21) 125.5, Driver.WorkerWait.dequeue_order (queue worker 0x21) 125.5, Driver.Packet (queue worker 0x0) 122.3

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 11.5 (10 %) 2.2 (2 %) 98.0 7.5 Driver.Packet 13.7, Driver.EndSubmission 0.0
presenter 0.5 (0 %) 2.6 (2 %) 108.7 0.1 VideoOut.Flip 3.1, Driver.Present 0.0
queue worker 0x43 38.2 (34 %) 72.2 (65 %) 1.3 4.6 Driver.Packet 110.4, Driver.EndSubmission 0.0
queue worker 0x21 0.8 (1 %) 110.7 (99 %) 0.2 1.0 Driver.Packet 4.4, Driver.EndSubmission 0.0
gpu 81.3 (73 %) 0.0 (0 %) 30.4 0.0 batch 81.3

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 128.5, Driver.WorkerWait 107.2, Driver.WorkerWait.dequeue_order 106.9, batch 81.3, Driver.Packet.memory_wait 65.9, Driver.Packet.dispatch 43.8, Driver.Dispatch 28.4, Driver.Packet.prepare_memory 10.3, Driver.Dispatch.materialize 9.2, Driver.Dispatch.capture_resources 9.1

Longest waits

  • Driver.WorkerWait (queue worker 0x21) 210.4 ms at 20344 ms, present 1820
  • Driver.WorkerWait (queue worker 0x21) 130.5 ms at 22352 ms, present 1837
  • Driver.WorkerWait (queue worker 0x21) 130.4 ms at 8157 ms, present 1711
  • Driver.WorkerWait (queue worker 0x21) 127.0 ms at 59339 ms, present 2169
  • Driver.WorkerWait (queue worker 0x21) 126.9 ms at 35079 ms, present 1951
  • Driver.WorkerWait (queue worker 0x21) 126.3 ms at 2096 ms, present 1657
  • Driver.WorkerWait (queue worker 0x21) 125.8 ms at 48200 ms, present 2069
  • Driver.WorkerWait (queue worker 0x21) 125.6 ms at 8962 ms, present 1718
  • Driver.WorkerWait (queue worker 0x21) 125.5 ms at 6448 ms, present 1696
  • Driver.WorkerWait (queue worker 0x21) 125.4 ms at 44430 ms, present 2035

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 77683: 15.0 ms at 36738 ms, present 1966: GPU no ranges; recorded by no CPU slices
  • batch 7851: 15.0 ms at 3687 ms, present 1671: GPU no ranges; recorded by no CPU slices
  • batch 36732: 15.0 ms at 17307 ms, present 1793: GPU no ranges; recorded by no CPU slices
  • batch 52339: 15.0 ms at 24805 ms, present 1859: GPU no ranges; recorded by no CPU slices
  • batch 15181: 14.9 ms at 7143 ms, present 1702: GPU no ranges; recorded by Driver.Packet (queue worker 0x0) 0.0, Driver.Packet.prepare_memory (queue worker 0x0) 0.0, GpuMutex.wait.label (queue worker 0x0) 0.0

AI-assisted: yes

@oneandonlydean oneandonlydean self-assigned this Oct 19, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@XairRrR thanks, the fallback paths all re-upload and fail closed, so the change looks correct to me. One question before merge: in your own capture Driver.Draw goes from 33 to 47 ms/frame (+42%). The menu is GPU-bound, so fps hides it, but in a CPU-bound scene that cost would show. Could you post the number of large-snapshot re-uploads per 10 s (the [texture-cache] found changed / snapshot counters) with and without the change, in the same scene? If the re-uploads explain it, one option is to keep the copy for a surface once it has been rewritten with identical bytes, rather than always dropping it. Or say why that doesn't work.

@Mouhand-Kaddo

Copy link
Copy Markdown
Contributor

Tested on Demon's Souls (PPSA01342 01.004.000), Linux 7.2.11, RX 6900 XT (Mesa 26.2.5 RADV), i7-13700K, 32 GiB. Base main 36f60df + #7844 (without it this title aborts loading the level on 32 GiB machines), with and without this PR, same route (Continue → offline → loading Outpost Passage), jemalloc heap profiling (LD_PRELOAD=libjemalloc.so.2, MALLOC_CONF=prof:true,lg_prof_sample:21,lg_prof_interval:35). Dumps compared at the same allocation count (7th interval dump, ~224 GiB allocated, while the level loads):

live heap under cachedTexture (via resolveImageTables / resolveImageBinding)
without this PR 10085 MiB 4456 MiB (1772 + 1494 + 1190)
with this PR 5585 MiB not in the top 30 sites

With this PR the level loads (HUD, Outpost Passage) and the heap there is 7566 MiB; the largest holders are now shader results (DescriptorBindingBuilder::Populate and materialization, ~3 GiB), not texture snapshots. No visual difference in the captured frames. Without it, the same route on glibc reached 14 GiB of anonymous memory plus 3 GiB of swap in the level on this 32 GiB machine.

AI-assisted: yes

@XairRrR

XairRrR commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean I measured it, and the Driver.Draw difference does not come from re-uploads. It does not reproduce.

Same machine and scene as the captures in the description (PPSA05684 main menu, my integration build on main 7732434, APS5_PROFILE_DRAW=1 and APS5_TRACE_SUMMARY_EVERY=10 on every run, averages over the last 60 s of a 220 s run):

run this change [texture-cache] found changed / 10 s Driver.Draw ms/frame frame p50 ms peak RssAnon
A, first run of the series yes 0 46-48 106 6667 MiB
B no 0 31-33 105 6892 MiB
A again (same files) yes 0 31-32 104 6617 MiB
  • No surface is found changed, so no large snapshot is re-uploaded in this scene, with or without the change.
  • The 46-48 ms run is the first run of the series. In it, Shader.DrawStage takes about 20 ms per frame and is missing from every later run. The same files run again give 31-32 ms, the same as without the change. I don't know yet why that one run prepared draw stages on every frame. My captures in the description were each the first capture of a build after one warm-up run, so their 33 → 47 looks like the same effect, not the change.

So I have not added the keep-after-rewrite rule. Here it would never trigger, since nothing is rewritten. I did try it: keeping the snapshot whenever a lookup replaces a stale entry is a two-line change, and it gives the same 31-33 ms. I can add it if you'd still like it as a guard for titles that rewrite large textures with identical bytes.

@Mouhand-Kaddo thank you for the Demon's Souls numbers (live heap 10085 → 5585 MiB, and the level loads on 32 GiB).

AI-assisted: yes

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@XairRrR thanks, that settles the 33 → 47 ms: your A-again run matches B, so it was the first-run Shader.DrawStage effect, not this change.

No need to add the keep-after-rewrite rule now. One note on the counter: found changed also counts every erase and re-snapshot, including same-byte rewrites, so 0 in both runs means that menu never rewrote a cached texture. The cost path isn't exercised there in either direction. We'll measure it on our own gate titles instead: on main a same-byte rewrite of a large surface is a hit, and with this change it counts as found changed and re-uploads, so the difference between the two counts is exactly the extra work. If that turns out to matter there, we'll ask for your two-line version then. Otherwise this is fine as it is.

@oneandonlydean
oneandonlydean merged commit d0852de into boykopovar:main Oct 20, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants