Repository navigation
Conversation
…age budget StorageTextureBudget reserved the larger of the sampled textures alive and their 2 GiB floor. The sampled budget already reserves TextureCacheBudget, at least the storage cap, for the storage cache, so on an 8 GB card the two caches each kept the other's maximum: with 3936 MiB used outside the caches on a 7189 MiB budget and 49 MiB of sampled textures, the storage budget came out at 306 MiB and sat at its 512 MiB floor while about 2.7 GB of the budget stayed free. The storage budget now reserves the sampled textures alive and gives room back as they grow, at its next read. Where they hold 2 GiB or more the budget is unchanged.
|
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.
|
|
Additional Linux/AMD validation of the production patch from I added a pressure sweep to After merging upstream main env ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 \
VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
VK_DRIVER_FILES=/usr/share/vulkan/icd.d/radeon_icd.json \
XDG_RUNTIME_DIR=/run/user/1000 \
ctest --test-dir build-full -R '^(agc_profile_output|agc_driver_graphics|agc_draw_inputs_in_place_driver|agc_draw_inputs_in_place|guest_ajm|guest_ajm_teardown|guest_rtc|unwind_callback_abi_.*)$' -VIndependently checked the full log: zero Native smoke checks reached the driving cutscene in Crimson Spires (PPSA03568) and the readable opening story in Lodoss (PPSA05347), with automatic texture budgets and ordinary close exiting 0. These runs used an existing correctness integration including #7817, #7627, #6959 and #7755, plus this patch and the test sweep (local head Optional test addition, inserted before resetting the device-local heap flags in for (const auto heapGiB : {2u, 4u, 6u, 8u, 16u, 32u}) {
memory.memoryHeaps[2].size = heapGiB * GiB;
for (std::uint64_t budget = GiB; budget <= heapGiB * GiB; budget += 512 * MiB) {
reported.heapBudget[2] = budget;
for (std::uint64_t sampled = 0; sampled <= budget; sampled += 256 * MiB) {
std::uint64_t previous = 0;
for (std::uint64_t outside = 0; outside <= budget + GiB; outside += 128 * MiB) {
reported.heapUsage[2] = outside + sampled + 512 * MiB;
const auto limit = StorageTextureBudget(memory, &reported, sampled, 512 * MiB);
Require(limit >= 512 * MiB, "pressure sweep lost the storage cache floor");
Require(limit <= std::max<std::uint64_t>(512 * MiB, budget / 4u), "pressure sweep exceeded a quarter of the reported budget above the floor");
Require(limit <= std::max<std::uint64_t>(2 * GiB, heapGiB * GiB / 4u), "pressure sweep exceeded the heap storage cache cap");
if (outside != 0) Require(limit <= previous, "additional non-cache memory usage grew the storage cache budget");
if (limit > 512 * MiB) Require(outside + sampled + budget / 8u + limit <= budget, "storage cache budget consumed live sampled memory or pressure headroom");
previous = limit;
}
}
}
}AI-assisted: yes. Additional native pressure check (2026-10-19)Repeated the Crimson Spires opening driving sequence in both run orders, with identical scripted inputs, automatic texture budgets, 3840×2160 rendering and the same trace window. Both runs with this patch and both controls closed normally with exit 0. This is still the prerequisite correctness integration described above, refreshed onto upstream The RX 6650 XT/RADV Mesa 26.2.4 logs exercise the intended low-sampled-memory case: the repeated control reports 101 MiB sampled textures, a 4392 MiB reported device-local budget, and its storage cache budget falling to the 512 MiB floor. The candidate starts with a 4351 MiB reported budget and a 1088 MiB storage cache budget, with no subsequent budget-change report during the run. The first pair showed the same control-floor/candidate-no-change-report pattern. These are budget diagnostics, not measurements proving absence of allocation pressure between reports. The repeated candidate screenshot reaches the same opening dialogue with readable text; animation progress differs between the timed captures. No isolated-main speed claim from these integration runs. AI-assisted: yes. Isolated Lodoss comparison (2026-10-19): pinned pure main Main: 25.42 FPS; this PR: 24.56 FPS. This single pair demonstrates no Lodoss gain and is insufficient to establish a regression. Both sides passed the same 12 focused tests with the required Vulkan validation layer and zero Summary lines: {"commit":"b106f6f8c04c","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_SPIRV_OPT":"full","APS5_TRACE_SECONDS":"200:10"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 200:10","first_present":9594,"ms":10030.603},"presents":255,"frame_ms":{"p50":36.975,"p90":48.536,"p99":65.373,"max":70.653},"gpu_busy_ms_per_present":2.244,"worker_busy_pct":{"queue worker 0x0":34.909},"top_phases_ms_per_frame":{"Driver.Packet":13.729,"Driver.Packet.device_lock":9.528,"Graphics.Draw":6.89,"Graphics.Draw.validate":5.182,"Driver.Packet.prepare_memory":3.438,"Recorder.Sync":2.394,"GpuMutex.wait.other":1.825,"GpuMutex.wait.label":1.8},"trace_dropped":false,"trace":"aps5-trace-b106f6f8c04c-3825443.json.gz"}
{"commit":"71f17c4d7976","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_SPIRV_OPT":"full","APS5_TRACE_SECONDS":"200:10"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","resolution":"3840x2160","window":{"spec":"seconds 200:10","first_present":9667,"ms":10017.988},"presents":246,"frame_ms":{"p50":39.986,"p90":49.004,"p99":63.075,"max":65.082},"gpu_busy_ms_per_present":2.267,"worker_busy_pct":{"queue worker 0x0":41.096},"top_phases_ms_per_frame":{"Driver.Packet":16.732,"Driver.Packet.device_lock":11.52,"Graphics.Draw":8.437,"Graphics.Draw.validate":6.67,"Driver.Packet.prepare_memory":4.355,"Recorder.Sync":2.816,"GpuMutex.wait.label":2.762,"GpuMutex.wait.other":2.491},"trace_dropped":false,"trace":"aps5-trace-71f17c4d7976-4063648.json.gz"}Artifacts: report, main trace, candidate trace, main tests, candidate tests. AI-assisted: yes. |
|
Thanks for the RADV run and the sweep. I'll add the sweep to this PR with you as co-author and post the RTX 4060 results with the validation layer once it's in. |
Checks the floor and caps, that more memory used outside the texture caches never raises the budget, and that a budget above the floor leaves room for the live sampled textures plus an eighth of the budget, on 2-32 GiB heaps. Co-authored-by: JJenkx <31495916+JJenkx@users.noreply.github.com>
fogydes
left a comment
There was a problem hiding this comment.
Verified the diff, test fixtures, and documentation in PR #7862:
- Mathematical Rationale: In
StorageTextureBudget(core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp), replacingstd::max<std::uint64_t>(2048ull << 20u, sampledBytes)withsampledByteseliminates double-reservation of the 2 GiB floor against both caches. On 8 GiB cards, this prevents the storage image cache from collapsing to its 512 MiB floor when sampled texture usage is low, while continuing to dynamically yield headroom as sampled textures grow. - Unit Testing: Unit test assertions in
tests/Graphics.cpp(storageTextureBudgetTests) andtests/execution/SampledCacheBudget.cppaccurately verify the revised budget calculations across varied usage levels, including the PPSA16064 8 GiB heap profile. - Documentation & Conventions: Both
docs/dev/debt/libSceAgcDriver.mdanddocs/user/USAGE.mdaccurately document the updated cache budget dynamics. No code comments introduced;tools/check_conventions.pypasses with 0 findings.
|
Independent data point from LEGO Builder's Journey (PPSA05496): RTX 2080 SUPER 8 GB, Windows 11, NVIDIA 616.92. It's the same storage-image thrash. In this title the quarter-budget cap binds as well, not only the 2 GiB sampled reserve, so this PR alone doesn't fix it here. First scene, same build and the same shader cache, with only the budget changed (5 s
At the old value, What I tried locally: keep your change (reserve what the sampled textures use, but with a 512 MiB floor), and replace the cap with AI-assisted: yes. The numbers are from my machine. |
|
@parl0v a 007 data point for the 10 GB case from #7071, which this PR discusses. Four gameplay runs from the same save, two without this change and two with it.
Setup:
Present numbers are the title's. A log line's present is bounded by the
All four runs print the Run 4's logging:
The stalls:
AI-assisted: yes (Claude Code) |
What
StorageTextureBudgetreserves the sampled textures alive instead of the larger of them and their 2 GiB floor. Everything else stays as #7071 set it: the heap's use outside the caches, the eighth of the budget, the caps and the 512 MiB floor. Whenever the sampled textures alive hold 2 GiB or more, the budget is the same as onmain.The sampled budget already reserves
TextureCacheBudget(at least the storage cap) for the storage cache, so one side keeps reserving the other's maximum, as #7071 says one side has to. The storage side no longer does the same in the other direction: as the sampled textures grow it gives the room back at its next read.Tests: three
StorageTextureBudgetcases are added (the 8 GB case below, which fails onmain; the same heap with the sampled textures grown to 2 GiB; the 10 GB pressure peak from #7071). The existing case that pinned the 2 GiB floor now expects the live sampled bytes, and the storage case inSampledCacheBudget.cppmoves its pressure point from 53 to 55 GiB in use so it stays under the cap on 8-12 GB devices. The debt entry andUSAGE.mddescribe the new reserve.Why
Minecraft Dungeons II (PPSA16064) gameplay on an RTX 4060 8 GB, Windows 10,
mainf78675a: the storage image budget sits at its 512 MiB floor in every 10 s window. One budget line: heap 0 has a 7189 MiB budget and uses 4491 MiB, 49 MiB of it sampled textures and 506 MiB cached storage images, so 3936 MiB is in use outside the caches.mainreserves 3936 + 2048 (the sampled floor) + 898 (an eighth) MiB, which leaves 306 MiB, so the floor. The sampled side gets the same 306 MiB for itself, because it reservesTextureCacheBudget(2048 MiB) for the storage cache, and sits at its own 2 GiB floor. Each cache keeps 2 GiB for the other out of 2354 MiB of room while the two hold 555 MiB, and 2698 MiB of the reported budget stays free.One present touches about 0.8-1.0 GB of storage images, so the cache evicts about 3,968 entries per 10 s and re-creates 96.7 % of them within two presents. With the budget forced to 1792 MiB (
APS5_TEXTURE_CACHE_MIB=1792) on the same build: 1.74 -> 3.05 FPS, evictions 3,968 -> 6 per 10 s, CPU-path uploads 204 -> 0, and the card stays at about 5.8 GB, inside the 7.2 GB budget. With this change the line above gives 7189 - 3936 - 49 - 898 = 2305 MiB, capped at a quarter of the budget: 1797 MiB. On the last budget line of each of my six gameplay runs (4334-4819 MiB in use, 48-119 MiB sampled)maingives 512 MiB and this change 1797 MiB.@AlxcBTW found the cause on #6194 on 2026-10-15: the two caches each reserve room for the other, and holding back the sampled cache's 2 GiB floor for a cache of ~33 MiB keeps an 8 GB card at its floor. That proposal targets #6194's growth cap; #7071 then reserved the same floor in
main's base formula, as I noted on #6194 on 2026-10-18. This change removes it there.mainWhy the 10 GB stop stays covered. #7071's protection is the term for the heap's use outside the caches, which this change keeps: at 007's level entry it took the storage budget from 2184 to 1795 MiB and to its floor by present 1298. On #7071's lines from the entry on the sampled textures alive held 2009-2404 MiB, so the reserve this change drops (
max(0, 2048 MiB - sampled)) is 0-39 MiB there. The one logged line with fewer (1427 MiB, the first pressure peak withAPS5_DRAW_PIPELINE=1 APS5_GPU_HEAP=1) is at the 512 MiB floor either way, and is now a unit case. Since #7071, #6174 (release both texture caches when a device allocation is refused, then retry) and #5987 (place a still-refused allocation in host memory) are onmain, and depth and render targets now allocate through that path (AllocateImageMemory); when #7071's runs stopped they did not.Trade-off. The storage budget no longer keeps room ahead of time for sampled textures that do not exist yet. When they grow it gives the room back at its next read (at most a second later) and the cache trims at its next insert, so a burst of sampled textures larger than the free room inside that window can exceed the reported budget. On PPSA16064 the free room at the new budget is 1407 MiB (the eighth, 899 MiB, plus 508 MiB under the cap) where
mainleft 2698 MiB; the sampled textures stayed at 0-119 MiB on every budget line of my runs. If a title shows such a burst, the reserve can follow the sampled cache's recent peak plus a step (SampledCacheHeadroomin @AlxcBTW's #6194 revision) instead of its current bytes; on PPSA16064 that also gives 1797 MiB.Measured
RTX 4060 8 GB (NVIDIA 617.42), Windows 10 19045, Minecraft Dungeons II (PPSA16064 01.100.100), a saved game and a scripted walk, automatic texture budgets,
main36f60df against this PR at 97aeb51. FPS is the mean of the gameplay 10 s windows of a row; the rows are interleaved over two sessions.main36f60dfWelch's t 3.08; a one-sided permutation test over the 12,870 splits of the 16 rows gives p = 0.0026. The rows with this change spread more (1.43-3.63 FPS against 1.23-1.87), and two of its eight rows fall inside
main's range.One traced row on each side, gameplay,
APS5_TRACE_SECONDS=85:12: frame p50 669.5 ms (18 presents in the window) onmainand 214.5 ms (51 presents) with this change, GPU busy 140.7 and 74.1 ms per present. This one window gains more than the row means do.APS5_LAB_HEAP_CENSUSin both summary lines is a variable of my run script; neither commit reads it.Traces (all four captures in this PR, they open in ui.perfetto.dev): https://github.com/parl0v/AnyPS5/releases/tag/perf-traces-2026-10-19
tools/performance_compare.py
Performance comparison
Configuration differences
Frame and GPU times (ms)
Top phases (ms per frame)
The configuration difference it reports is the window's first present (1651 against 1679): the window is set in seconds, so it starts at a different present.
tools/trace_digest.py, main 36f60df
seconds 85:12 from present 1651: 18 presents, frame p50 669.5 / p90 760.4 / p99 808.7 / max 808.7 ms, GPU busy 140.7 ms per present
NVIDIA GeForce RTX 4060, NVIDIA 617.42, Windows 10.0.19045, 3840x2160, commit 36f60df, dropped: no
{"commit":"36f60df9edb3","dirty":false,"env":{"APS5_LAB_HEAP_CENSUS":"1","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_PROFILE_DRAW":"1","APS5_TRACE_SECONDS":"85:12"},"gpu":"NVIDIA GeForce RTX 4060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 85:12","first_present":1651,"ms":12005.055},"presents":18,"frame_ms":{"p50":669.478,"p90":760.423,"p99":808.679,"max":808.679},"gpu_busy_ms_per_present":140.701,"worker_busy_pct":{"queue worker 0x0":99.705,"queue worker 0x20":97.928},"top_phases_ms_per_frame":{"Driver.Packet":1317.635,"Driver.Packet.memory_wait":425.336,"Driver.Packet.dispatch":366.131,"Driver.Dispatch":364.816,"Resources.Complete":358.141,"Graphics.Draw":331.012,"Graphics.Draw.draw_and_resource_release":299.075,"Driver.Packet.draw":257.558},"trace_dropped":false,"trace":"aps5-trace-36f60df9edb3-18888.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Driver.Packet 1309.1, Driver.Packet.memory_wait 425.2, Driver.Packet.dispatch 365.7, Driver.Dispatch 364.3, Resources.Complete 357.4, Graphics.Draw 330.4, Graphics.Draw.draw_and_resource_release 298.3, Driver.Packet.draw 257.0, Driver.Draw 256.6, Vulkan.Dispatch 250.5
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
tools/trace_digest.py, this PR 97aeb51
seconds 85:12 from present 1679: 51 presents, frame p50 214.5 / p90 320.1 / p99 400.4 / max 400.4 ms, GPU busy 74.1 ms per present
NVIDIA GeForce RTX 4060, NVIDIA 617.42, Windows 10.0.19045, 3840x2160, commit 97aeb51, dropped: no
{"commit":"97aeb515de30","dirty":false,"env":{"APS5_LAB_HEAP_CENSUS":"1","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_PROFILE_DRAW":"1","APS5_TRACE_SECONDS":"85:12"},"gpu":"NVIDIA GeForce RTX 4060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 85:12","first_present":1679,"ms":12024.254},"presents":51,"frame_ms":{"p50":214.534,"p90":320.135,"p99":400.431,"max":400.431},"gpu_busy_ms_per_present":74.141,"worker_busy_pct":{"queue worker 0x0":99.239,"queue worker 0x20":98.014},"top_phases_ms_per_frame":{"Driver.Packet":464.814,"Driver.Packet.memory_wait":147.959,"Graphics.Draw":143.263,"Graphics.Draw.draw_and_resource_release":126.705,"Driver.Packet.draw":108.416,"Driver.Draw":108.086,"Driver.Packet.dispatch":89.249,"Driver.Dispatch":88.059},"trace_dropped":false,"trace":"aps5-trace-97aeb515de30-30592.json.gz"}Slowest frames (largest slices inside each, inclusive)
Threads (ms per frame)
Largest slices (ms per frame, inclusive, all threads): Driver.Packet 456.6, Driver.Packet.memory_wait 147.9, Graphics.Draw 142.4, Graphics.Draw.draw_and_resource_release 125.4, Driver.Packet.draw 107.9, Driver.Draw 107.5, Driver.Packet.dispatch 88.9, Driver.Dispatch 87.6, batch 74.3, Driver.Packet.device_lock 71.5
Longest waits
Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)
RTX 3060 12 GB (NVIDIA 617.42), Windows 10 19045, PPSA16064 title screen,
APS5_TRACE_SECONDS=120:12, no other variables, arms alternating, a warm run before the first capture of each side. The storage budget is not at its floor on this card, so nothing changes, as expected:main36f60dfTested
agc_driver_graphics,agc_driver_sampled_cache_budget,agc_driver_sampled_cache_budget_memory,agc_driver_sampled_cache_budget_without_storage_image_multisample,agc_driver_storage_cache_budget,agc_driver_storage_cache_retained_batch,agc_driver_resource_cache_storage_admission,agc_driver_resource_cache_oversized_storage_admissionandagc_driver_resource_cache_storage_clearon the RTX 4060 (NVIDIA 617.42, Windows 10 19045, WinLibs GCC 15.2.0, Release withBUILD_TESTING=ON),ctest -VwithVK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 VK_LOADER_DEBUG=error,warn, Vulkan SDK 1.4.363.0: 9 of 9 pass onmain36f60df and 9 of 9 on 97aeb51. Neither log has awas not found but was requested by env var VK_INSTANCE_LAYERSline (the loader reports adding the layer in both), and both have 0VUID-lines. The storage case ofagc_driver_sampled_cache_budget_memoryprints 1024 MiB at 55 GiB in use of 64 GiB with this change.StorageTextureBudget's body put back tomain's and this PR's tests:agc_driver_graphicsfails on the 5632 MiB case that replaced the floor case ("does not leave 2304 MiB";maingives 1280 MiB), and with that case removed it fails on the PPSA16064 case (maingives 512 MiB where 1797 MiB is expected).agc_driver_sampled_cache_budget_memoryfails with "55 GiB in use of a 64 GiB budget leaves the storage image cache 512 MiB, not the 1024 MiB".storageTextureBudgetTests()(2-32 GiB heaps, changing budgets, live sampled bytes and outside usage; he ran it on an RX 6650 XT with RADV).agc_driver_graphicswith it passes on the RTX 4060 (NVIDIA 617.42, Windows 10).python3 tools/check_conventions.py --base origin/mainpasses.FAsyncLoadingThread, read access violation in the game's own code atapp_mcd2.exe+0x4d446b), against 0 of 9 onmain. MCD2 has crashed in its asynchronous loading onmainbefore (1-2 of 6 loads on 2026-10-18,TAsyncThread/ Background Worker).Related
StorageTextureBudgetwhile it evicts. This change does not touch its lines; after it the floor fix(agc): grow the storage cache budget while it evicts under pressure #6194 is asked to use rises from 512 to 1797 MiB on PPSA16064, and its own cap still needs the sampled headroom change from the review to grow past that on 8 GB cards.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file (none added).mdfiles or images added to the repository