Skip to content

perf(agc): stop pooling released device buffers over the memory budget - #4472

Open
solbjorg1245 wants to merge 4 commits into
boykopovar:mainfrom
solbjorg1245:upstream/vram-budget-1
Open

solbjorg1245 wants to merge 4 commits into
boykopovar:mainfrom
solbjorg1245:upstream/vram-budget-1

Conversation

@solbjorg1245

@solbjorg1245 solbjorg1245 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

The buffer pool's device tier keeps released device-local buffers up to an eighth of the largest device-local heap (at least 512 MiB), however full that heap is. Near the budget that VK_EXT_memory_budget reports, this retained memory is what pushes the process over it. Past the budget an allocation can still succeed while the OS starts paging device memory or DeviceMemoryType places it in host memory, so the out-of-memory trim in BufferPool::AllocateMemory comes late or not at all.

  • VramBudget (new, Graphics/src/VramBudget.cpp) holds the policy for the largest device-local heap: the target is the reported budget itself (never below a quarter of the heap or 1 GiB, whichever is smaller). It does not query the driver itself. The pool's existing heap budget read (heapOverBudget, at most every 100 ms, the one the image memory path already uses) feeds it, so buffer and image releases share one vkGetPhysicalDeviceMemoryProperties2 call.
  • BufferPool::Put: while the reported usage, minus what the pool freed from the device tier since that read, is over the target, the pool first gives back retained image memory (the image path already frees it over the reported budget, and image takes are not demand). Then the device tier keeps released buffers only up to a demand floor: what it handed out (hits and misses) in the last second, in eight 125 ms buckets, capped at a sixteenth of the heap. Anything beyond the floor is destroyed, least recently used first. A title that doesn't churn has a small floor and gives back everything else; one whose working set sits just over the budget keeps what it is about to ask for again instead of re-allocating it every frame. Host tiers, the small tier and slabs are unchanged, and a Put that lands in another tier is not counted as device memory released.
  • Every device-tier free the pool makes counts as released until the next read: Put evictions, image memory dropped or evicted in PutImageMemory, ReclaimIdleDevice under storage cache pressure, and trim() before an allocation retry. The bytes are counted under the pool mutex before the memory is destroyed, so Excess() never counts a free twice.
  • The pool behaves exactly as before in three cases: without the extension, on an integrated GPU (Context::integratedGpu, set from the device type: there the device-local heap is a carve-out the driver spills into system memory) and with APS5_NO_VRAM_BUDGET=1. APS5_VRAM_BUDGET_MIB caps the budget, which lets a large card behave like a smaller one. Both are in docs/dev/ENVIRONMENT.md.
  • A [vram] line is printed once, at the first read: the heap, its budget and usage. With APS5_PROFILE_DRAW, another [vram] line follows the [bufferpool] line every 10 s with the budget, usage, target, floor, the released device allocations not kept (count and MiB), the device allocations made while the pool was dropping (the churn) and the allocations placed in host memory, both by DeviceMemoryType and by the refused-allocation fallback in AllocateMemory (counted in HostPlacements() instead of two function-local statics).

When it matters. The pool only drops buffers once the device-local heap is over the budget VK_EXT_memory_budget reports. At default settings Minecraft Dungeons II on an 8 GB card settles below it, and nothing changes. It matters for a heap that is already full: a bigger title, a forced or oversized APS5_TEXTURE_CACHE_MIB, or other processes using VRAM. There, without this PR, the pool's retained buffers push new device allocations into host memory.

The crash it prevents (@AlxcBTW, RTX 5070 Laptop 8 GB, Minecraft Dungeons II world load + walk): with the device-local heap over its budget, the automatic budget evicted a 256 MiB 3D storage texture (1024x1024x256 R8). It was re-created in host memory, and a compute pass over it ran past the 2 s TDR limit (nvlddmkm 153 / LiveKernelEvent 141). Over the earlier rows that stack lost the device in 2 of 11; with this PR, 0 of 5. Logs: to be attached by @AlxcBTW.

Found with Demon's Souls on our fork: on a 16 GiB card the device-local heap reached 15.3 GiB at a level load while about 2 GiB of it was released buffers held for reuse. The demand floor replaces the first version's fixed headroom after @CnRJay's churn review; it is @parl0v's commit, with his lab rows in the thread (RTX 4060 8 GB, forced 3 GiB texture cache: walk 3.94 FPS with the headroom, 5.63 with the 1 s floor). Reclaiming cold cache entries would be a separate follow-up. The change itself has no title check.

Tested

On main 85a4d7f8c: agc_buffer_pool, agc_buffer_pool_small_device_shared, agc_buffer_pool_small_device_no_staging, agc_device_memory_fallback, the sampled and storage cache budget tests and agc_driver_pooled_buffer_range pass; python tools/check_conventions.py --base origin/main passes.

  • agc_buffer_pool budget tests (mock device, no GPU needed):
    • the target is the budget, with the quarter-heap / 1 GiB lower bound;
    • under the target, released device buffers are still kept;
    • over the target, the floor keeps what was taken in the last second and gives everything back once the window has passed;
    • over the target with no recent demand, released buffers are destroyed and the next request allocates;
    • a small device-tiered Put that evicts from the small tier leaves Excess() unchanged;
    • when usage rises after a fresh read, Put trims the oldest retained buffers;
    • an integrated GPU keeps its pool and the budget stays disabled;
    • a buffer release and an image release within 100 ms make one budget query;
    • image memory freed over the budget and buffers freed by ReclaimIdleDevice lower Excess() by their size;
    • over the target, retained image memory goes before the buffers the floor keeps.
  • Main's image pool, idle reclaim, attachment reclaim and host placement tests run unchanged.
  • Not yet run in a title on current main.

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

🤖 Generated with Claude Code

https://claude.ai/code/session_01VFVE5WbaUJ2vyj8NAW44CS

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Is there a before/after for this change alone? The 15.3 GiB figure is one reading from a fork that carries the whole budget, and this hasn't run in a title on main; perf PRs need numbers for the change itself (#218). The [vram] line and presents per 10 s at that level load, with and without APS5_NO_VRAM_BUDGET=1, would do.

@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Thanks, fair point. I could not get a clean before/after of this change alone on main, and I would rather say so than quote the 15.3 GiB figure as if it were one.

What I tried:

  1. Main does not run Demon's Souls on my setup. It aborts at boot because the presenter throws on a DB_RENDER_CONTROL depth resummarize state. With a local-only workaround on top of this PR (skip that draw instead of rejecting it, not part of the PR) the title boots on main, shows the intro logos, then aborts about seven minutes in with compute shader ...: runtime image descriptor is invalid. It never reaches a level load, so there is no level-load reading on main. The [vram] lines up to that point show usage of 4.8 GiB of a 14.9 GiB budget, so the pool trim never engaged.
  2. So the numbers below come from my fork, which carries the whole budget (this PR's pool trim plus cache reclaim and admission checks), and they are not this change alone. APS5_NO_VRAM_BUDGET=1 there turns the whole budget off, not only the pool part. They show what the budget is for, not what this PR contributes by itself.

Setup: Windows 11, RTX 5080 16 GiB (heap 15977 MiB, driver budget about 15.2 GiB), same binary and same save, interleaved on / off / off / on, 330 s settle after the load, APS5_PROFILE_DRAW=1 in all four runs (it prints the [present] and [vram] lines, so frame rates are a little lower than without it, equally in both arms).

run budget peak device-local usage windows over the driver budget [present] per 10 s, steady windows (mean / min / max) FATAL
1 on 13.5 GiB (94.7% of budget) 0 of 47 59.2 / 50 / 63 0
2 off 14.7 GiB (103.4%) 9 of 47 51.2 / 36 / 63 0
3 off 14.2 GiB (100.4%) 5 of 47 54.3 / 40 / 59 0
4 on 13.6 GiB (95.5%) 0 of 47 60.4 / 53 / 64 0

With the budget on the first [vram] line reads: heap 15977 MiB, budget 15209 MiB, target 14210 MiB. With it off, usage went past the budget the driver reports in 5 and 9 of the 10 s windows (allocations still succeeded; past that budget the OS is free to move device memory to system memory). Mean steady presents were 59.8 with the budget and 52.8 without, two runs each, so the spread between runs (36 to 64 inside a window) is about as large as the difference; I would call the frame rate a small gain at best and the memory behaviour the point.

What I can still do: my fork gets past both aborts above, so I can send those fixes as separate PRs first and then post the same two arms on main, or run them on a title that already boots on main. Without a title that loads a level there I cannot give a reading for the pool trim alone.

On #4446: the bot is right that both PRs touch tests/BufferPool.cpp. The conflict is two places where each PR adds lines next to the other's (the mock device fields and the list of calls in main). Keeping both sides resolves it, the source files merge cleanly. Whichever lands second will rebase.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for saying so plainly. Send the two boot fixes as their own PRs first; this stays open, and the two arms on main once the title reaches a level load will do.

@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Update: the two boot aborts I reported are already gone on current main, so there is nothing to send for them. My first try was on this PR's older base, sorry for the noise.

I rebuilt on main (c2565f0) with this PR on top and ran the title again on the RTX 5080 16 GiB. The depth resummarize draw is accepted now (that is #4049, which merged after my first try), and the compute shader abort did not show up in two runs. The title plays the intro, reaches the main menu and starts loading the save.

It then aborts during the load, in a pixel shader recompile: GetImageResource dword 0 is not a valid runtime value, where the image descriptor address comes from a 64-bit pointer loaded out of memory (the case #758 and #757 implement, both open and not merged). So I still cannot give the on/off numbers at a level load on main.

Up to the abort the budget never mattered: device-local usage stayed at about 4.7 GiB of the 14.9 GiB budget, nothing was released over the target, and there were no failures before the shader abort.

I will rerun the two arms (APS5_NO_VRAM_BUDGET unset and =1, interleaved) as soon as a title reaches a level load on main with this PR, and post the table here. The earlier numbers from my fork stay as they were: they carry the whole budget, not only this change.

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

One behaviour to check in the title run requested above: allocation churn near the budget. VK_EXT_memory_budget usage covers the whole process (textures, images, the pipeline cache, other pools), so the pool can be over target for reasons it can't fix. In that state every device-tier Put destroys its buffer, and the next Take of the same size calls vkAllocateMemory again. On Windows/WDDM that allocation can take milliseconds and is a known source of frame-time spikes. A level that sits just over the target would then alternate free and allocate every frame, which is worse than keeping a small pool.

Some ways to bound it:

  • Hysteresis: start dropping at the target, keep dropping until usage is some margin below it, and only then start pooling again.
  • Keep a small floor in the device tier (for example the sizes requested in the last N frames) even when over target.
  • Count the vkAllocateMemory calls that follow a dropped Put within a frame in the [vram] line, so the trace shows whether churn happens.

The rest looks good. The integrated-GPU and missing-extension paths are left alone, and reading the budget at most every 100 ms keeps vkGetPhysicalDeviceMemoryProperties2 (which can be a kernel call) off the per-Put path.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

#757 and #758 merged on 10-14, so the GetImageResource dword 0 is not a valid runtime value abort during the save load should be gone on main. Could you rebase and post the on/off numbers at the level load ([vram] line and presents per 10 s, with and without APS5_NO_VRAM_BUDGET=1)?

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

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 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 1896 docs/dev/ENVIRONMENT.md
PR 2434 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/PostCompile.cmake
PR 4847 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/PostCompile.cmake
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6185 docs/dev/ENVIRONMENT.md
PR 6186 docs/dev/ENVIRONMENT.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6290 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 6501 docs/dev/ENVIRONMENT.md
PR 6553 core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 6782 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6804 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 7275 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 7369 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 7457 docs/dev/ENVIRONMENT.md
PR 7464 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7514 docs/dev/ENVIRONMENT.md
PR 7604 docs/dev/ENVIRONMENT.md
PR 7622 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 7643 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 8004 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 8212 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 8246 docs/dev/ENVIRONMENT.md
PR 8484 docs/dev/ENVIRONMENT.md
PR 8748 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 8912 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 8963 docs/dev/ENVIRONMENT.md
PR 9012 docs/dev/ENVIRONMENT.md
PR 9154 docs/dev/ENVIRONMENT.md
PR 9248 docs/dev/ENVIRONMENT.md
PR 9289 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 9345 docs/dev/ENVIRONMENT.md
PR 9356 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 9387 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 9489 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 9508 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 9676 docs/dev/ENVIRONMENT.md
PR 9712 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 9852 docs/dev/ENVIRONMENT.md
PR 9875 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 9891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9898 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10043 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10069 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10193 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 10351 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 10442 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10443 docs/dev/ENVIRONMENT.md
PR 10472 core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 10652 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 10668 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10678 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 10757 docs/dev/ENVIRONMENT.md
PR 10867 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 10894 docs/dev/ENVIRONMENT.md
PR 11265 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 11351 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11435 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11436 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 11483 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 11557 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 11920 core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 11948 docs/dev/ENVIRONMENT.md
PR 11995 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12066 docs/dev/ENVIRONMENT.md
PR 12120 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 12132 docs/dev/ENVIRONMENT.md
PR 12323 docs/dev/ENVIRONMENT.md
PR 12324 docs/dev/ENVIRONMENT.md
PR 12478 docs/dev/ENVIRONMENT.md
PR 12544 docs/dev/ENVIRONMENT.md
PR 12567 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12568 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12662 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 12696 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12720 docs/dev/ENVIRONMENT.md
PR 12745 docs/dev/ENVIRONMENT.md
PR 12746 docs/dev/ENVIRONMENT.md
PR 12764 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12828 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12840 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 12848 docs/dev/ENVIRONMENT.md
PR 12849 docs/dev/ENVIRONMENT.md
PR 12855 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 12863 docs/dev/ENVIRONMENT.md
PR 12895 docs/dev/ENVIRONMENT.md
PR 12904 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 12921 docs/dev/ENVIRONMENT.md
PR 12930 docs/dev/ENVIRONMENT.md
PR 12944 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 12970 docs/dev/ENVIRONMENT.md
PR 12980 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 13021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 13023 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 13044 docs/dev/ENVIRONMENT.md
PR 13125 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 13145 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 13148 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
PR 13159 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 13169 docs/dev/ENVIRONMENT.md
PR 13263 docs/dev/ENVIRONMENT.md
PR 13646 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 13707 docs/dev/ENVIRONMENT.md
PR 13793 docs/dev/ENVIRONMENT.md
PR 13809 docs/dev/ENVIRONMENT.md
PR 13858 docs/dev/ENVIRONMENT.md
PR 13863 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 13920 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 13928 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 13929 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
docs/dev/ENVIRONMENT.md
PR 14051 docs/dev/ENVIRONMENT.md
PR 14076 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
PR 14217 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 14218 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 14219 core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 14220 docs/dev/ENVIRONMENT.md
PR 14235 docs/dev/ENVIRONMENT.md
PR 14260 docs/dev/ENVIRONMENT.md
PR 14262 docs/dev/ENVIRONMENT.md
PR 14263 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 14264 docs/dev/ENVIRONMENT.md
PR 14299 docs/dev/ENVIRONMENT.md
PR 14344 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 14367 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 14709 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 14794 docs/dev/ENVIRONMENT.md
PR 14815 core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
PR 14838 docs/dev/ENVIRONMENT.md
PR 14871 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 14880 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 14904 docs/dev/ENVIRONMENT.md
PR 14940 core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 14943 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 15004 docs/dev/ENVIRONMENT.md
PR 15015 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 15059 docs/dev/ENVIRONMENT.md
PR 15064 docs/dev/ENVIRONMENT.md

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Thanks @parl0v for the floor and the rebase, and @oneandonlydean for putting it on the branch. I've updated the description for the floor and added the when-it-matters note and the crash section. @AlxcBTW, could you attach your device-loss logs when you get a chance?

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@fdfawef324

Copy link
Copy Markdown
Contributor

I am updating #6553 after #5987 merged. Its remaining change checks the reported host-heap budget before a proactive host placement; main's actual-refusal fallback, pool trim and texture-cache reclaim routing are retained.

There is a shared DeviceMemoryType/buffer-pool test area with this PR; both branches also already need resolution against newer main. I propose #6553 first, then this PR can move successful-placement counters into HostPlacements while retaining HostMemoryTypeWithRoom, the host-budget diagnostics, and the separate diagnostic for keeping a device-local allocation when no host heap fits.

The guard's standalone and integrated tests do not establish combined behavior with this PR. Please let me know if the opposite landing order is preferable so we can compose and test it explicitly.

AI-assisted: yes

@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Thanks, #6553 first works for me. Once it lands I'll rebase this past it and #5987, keep HostMemoryTypeWithRoom and the host-budget diagnostics, and only move the successful-placement counters into HostPlacements.

@eliasbruno124-dev

Copy link
Copy Markdown
Contributor

I found storage-cache churn below the reported heap budget on a 6 GB NVIDIA GPU. This is coordination evidence for a possible follow-up; it is not a test of #4472.

GTA III, PPSA03527/01.003.000, stationary at the first "Give me Liberty" spawn beside the Kuruma, with HUD/radar visible. RTX 3060 Laptop 6 GB, NVIDIA 617.42, i5-11400H, 32 GB RAM, Windows 26300. Both runs use the same complete runtime, eb210d1e1756 = main 1b08a33b121e + #8527. The same initial pipeline cache and private profile were checked by SHA-256 before the countercheck; 726 pipeline recipes replayed on both runs. No source change or forced texture-cache budget was used.

Stationary diagnostic window Default device pool Existing APS5_STAGING_POOL_MIB=128
Window duration 91.5 s 81.3 s
Reported presents/s, median (range of eight intervals) 1.0 (0.9-1.1) 2.75 (1.8-3.1)
Idle device buffers retained, median 534 MiB 101 MiB
Logical cache-eviction write-back, median per storage interval 9,586.6 MiB 0 MiB
Device-pool hits / misses / evictions between first and last pool rows 4,317 / 0 / 0 0 / 0 / 0
Pipeline-cache hits / misses in the window 96,795 / 0 229,243 / 1

The last default-pool budget report before the window ended was 4,471 MiB used against 5,161 MiB reported, with a 512 MiB storage-cache budget. That is a logged snapshot, not continuous heap telemetry. It does not meet this PR's over-budget predicate. An earlier default-pool run also gave 1.0-1.1 presents/s, but its starting cache differed, so the table uses the subsequent matched-cache control.

These are profiled diagnostic intervals with a renderer readback every 30 presents, not clean performance captures. The write-back figures are logical surface traffic, not measured PCIe bandwidth. The bounded trace covered an earlier loading phase and overflowed event storage; it cannot support a gameplay speed claim. Neither setting is playable, and this does not justify shipping a fixed 128 MiB pool: #1147 documents a title that needs a larger pool.

The result suggests investigating reclamation of cold idle device buffers when the storage cache is actively evicting below the heap budget, while preserving recent buffer demand. I have not changed this PR's target or implemented a competing retention policy. Please flag any existing follow-up that owns this case so the work can stay coordinated.

Prepared with AI assistance.

@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Thanks for the data. Nothing I have open covers that case: #4472 only stops pooling released buffers once the heap is over budget, so reclaiming cold idle buffers while the storage cache evicts below budget would be a separate follow-up, and I'm not working on one. If you take it on I'll rebase this around it.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@solbjorg1245 This PR now conflicts with main in core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp, core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp, core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp, core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp, core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp. Main’s cache-reclaim interface now carries newImage and its buffer-pool and placement handling overlaps this PR’s VRAM-budget changes. Could you reconcile the budget policy and reclamation behavior with the current pool and placement interfaces?

…et read

Main's pool now keeps released image memory in the device tier and reads
the heap budget itself (at most every 100 ms) to drop that image memory
over the budget, frees idle device buffers under storage cache pressure,
and trims the pool before an allocation retry. The VRAM budget had its
own reader and only knew about the buffers its own Put destroyed.

VramBudget no longer queries the driver: the pool's heap budget read
feeds it, so a device buffer Put and an image Put within 100 ms share one
vkGetPhysicalDeviceMemoryProperties2 call, and the read happens under the
pool mutex like the image path's.

Every device-tier free the pool makes between two reads is counted as
released, so Excess() stays right until the next read: Put's evictions,
the image path's drops and evictions, ReclaimIdleDevice and trim(). The
bytes are counted under the pool mutex before the memory is destroyed, so
a read that lands in between can only see the memory still in use, never
count it twice.

Over the target, Put gives back retained image memory before the buffers
the demand floor keeps (main already drops it over the reported budget,
and image takes are not demand), and the floor compares against the
buffers alone.

The refused-allocation host fallback in AllocateMemory now counts in
HostPlacements() too, so the [vram] line reports both placement paths.
APS5_NO_VRAM_BUDGET and APS5_VRAM_BUDGET_MIB are documented in
ENVIRONMENT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWqixRjxAxzP2dBDkLsjZ6
@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.

@solbjorg1245

Copy link
Copy Markdown
Contributor Author

Merged current main into the branch, with the reconciliation in its own commit (c58f968) so it reads separately from the merge. The budget no longer has its own reader: it takes the pool's existing 100 ms heap budget read, so buffer and image releases share one query. Every device-tier free (image memory in PutImageMemory, ReclaimIdleDevice, the trim() before an allocation retry) now counts as released, and over budget the retained image memory goes before the demand floor applies to buffers. The pool, image, reclaim, cache budget and placement tests pass; #6553 is still open, so this is on plain main and I'll rebase around it if it lands first.

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

Copy link
Copy Markdown
Collaborator

@solbjorg1245 VramBudget tracks the largest device-local heap, but releasedBytes counts every device-tier free without checking which heap owned it. With two device-local heaps, freeing 64 MiB from the smaller heap can take Excess() from 64 MiB to zero while the tracked heap is still 64 MiB over target. PutImageMemory, image-first eviction and trim can pass those unrelated frees through; ReclaimIdleDevice already guards the single-device-heap case.

Please credit only actual frees from the tracked heap, or disable this policy when there is more than one device-local heap. Add a two-device-heap regression showing that freeing the other heap leaves the tracked excess unchanged, while freeing the tracked heap lowers it once. The current budget mock has only one device-local heap, so it cannot catch this.

If you take the guard route, it only needs to count device-local heaps that have a memory type the device tier can use (DEVICE_LOCAL without HOST_VISIBLE). On the common setups without resizable BAR, the second device-local heap is the small BAR window and all of its memory types are host-visible, so the policy can stay on there.

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

fogydes commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Verdict

stale+rebase

Findings

  1. [verified] Merge conflict against current origin/main (bb04a4f1ea): git merge-tree --write-tree origin/main c58f968028ccef496eb2270918aac49579780d6d exits with code 1. The branch is 4,231 commits behind main and conflicts on docs/dev/ENVIRONMENT.md.
  2. [verified] Conventions check passed: python tools/check_conventions.py --base 85a4d7f8c82cf3feeed07f59e7732218958db890 in a scratch worktree succeeds with 0 findings and 0 errors.
  3. [read-only] Missing speed claim artifacts: The PR is titled perf(agc): ... and makes specific frame rate claims ("walk 3.94 FPS with the headroom, 5.63 with the 1 s floor"). Per AGENTS.md, "A speed claim shows tools/trace_digest.py summaries of main and of the change from the same machine and scene" (including the two aps5-summary.jsonl lines). Neither the summary lines nor trace_digest.py summaries are provided in the PR description.
  4. [read-only] Vulkan validation layer comparison requirement: Per AGENTS.md, AGC changes require running the same focused tests on main and the branch with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation and comparing logs to verify no new VUID- validation errors are logged. The PR description reports mock device tests (agc_buffer_pool, etc., no GPU needed), but no Vulkan validation layer comparison.
  5. [read-only] Missing PR checklist lines: The PR checklist omits three required template lines from .github/pull_request_template.md:
    • - [ ] Fixes: <!-- issue it closes as Fixes #N, or none -->
    • - [ ] Unblocks: <!-- title ID and Game bring-up discussion, e.g. PPSA01325 #3232, or none -->
    • - [ ] GPUs tested: <!-- for AGC, shader recompiler or VideoOut changes: vendor, model, driver and OS of each GPU the GPU tests ran on; otherwise n/a -->
      Per AGENTS.md, all checklist lines must be retained and answered (GPUs tested is required for AGC changes).
  6. [author-reported] Author reports passing mock unit tests on main 85a4d7f8c (agc_buffer_pool, agc_buffer_pool_small_device_shared, agc_buffer_pool_small_device_no_staging, agc_device_memory_fallback, sampled and storage cache budget tests, agc_driver_pooled_buffer_range), but notes no title has been run on current main.

Freshness envelope

  • Head: c58f968028ccef496eb2270918aac49579780d6d
  • Base: 85a4d7f8c82cf3feeed07f59e7732218958db890
  • Behind origin/main: 4,231 commits
  • Merge-tree: exit 1 (conflict in docs/dev/ENVIRONMENT.md)

Cross-PR ledger

This branch has not been deployed

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

Labels

agc libSceAgc* build needs rebase Conflicts with main, rebase needed 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.

8 participants