perf(vk): make part of the image pool's blocks at the start #2438
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
2 participants
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2438
Loading…
Reference in a new issue
No description provided.
Delete branch "perf-image-pool-prealloc"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Each 128 MB block of the images' pool is a
vkAllocateMemoryon the replay thread, ~170 ms on AMD's Windows driver. A walk across Hyrule Field on an RX 7800 XT made ~70 of them and the frame rate dropped to the low 40s each time new ground came in. The pool now starts with 15% of the device-local memory as blocks (2 to 24; 19 on a 16 GB card).RX_VK_IMAGE_PREALLOC=<blocks>sets the count,0keeps the old behaviour; if the blocks do not fit the pool is made empty as before.Scripted 1080p walk, same route, measured on older trees together with other changes (v2.0.5-v2.0.12): mean fps 52.9 -> 59.8, min 41.6 -> 56.7, replay submit 14.5 -> 9.9 ms. I have not re-measured this change alone on v2.1.0. Log line on start:
images from a pool of 128 MB blocks (memory type 0), 19 made at the start. Costs VRAM up front (about 2.4 GB on this card), so a card with little free memory may want a lower value.Tested: the Vulkan back end builds (llvm-mingw,
RX_VK_ONLY) on top of v2.1.0, and a full build of that tree boots to the title at 60 fps on an RX 7800 XT / Ryzen 7 5800X3D (Windows 11, driver 0x8001a0) with the new log lines present. The repository test suite was not run, and I have not yet played a long session on this build, so the effect in real play is not shown. Related data: #995.🤖 Generated with Claude Code
Thanks for chasing the
vkAllocateMemorystalls on AMD's Windows driver; making blocks up front is a sensible way to take them off the replay thread. The fallback when the pool can't be made with its blocks is handled correctly, and the checks that readvk_vma.cpp(test_target_image_pool,test_vram_compact,test_texture_staging_publication) pass at this head. The block count, though, is sized from the heap's total size rather than what the process may actually use, and it lands badly on two configurations the repo already has special handling for. Details:blocking
gpu/vk/vk_vma.cpp:103-109: the blocks are made without regard to the memory budget. VMA'sCreateMinBlocksgoes straight tovkAllocateMemory(it only honours a heap size limit, neverVK_EXT_memory_budget), whereas every imagecreate_imagemakes asks forVMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT. When other programs already hold VRAM (the tested-configurations table has a 12 GB card with only 10.2-10.5 GB allowed), 2.4 GB made up front can push the process over what the driver allows. On Windows that doesn't fail the allocation: WDDM pages it out, which is the "GPU time 13 → 55 ms for good" failure the texture budget's comments invk_texture.cppdescribe. Please capnby the heap's free budget (vmaGetHeapBudgets:budget - usage, with some margin), not bymemoryHeaps[h].size.blocking
gpu/vk/vk_vma.cpp:103-106: the heap is the largest device-local heap, not the heap oftype(dev.memory.memoryTypes[type].heapIndex), and integrated GPUs aren't excluded. On UMA drivers the device-local heap is a share of system RAM, so a 32 GB laptop could pin up to 24 × 128 MB = 3 GB of RAM at the first image. That is more than the 2560 MB textures are capped at on an integrated GPU (vk_texture.cpp, the 780M laptop that ran out of memory), and the 2-block minimum alone takes a quarter of a Steam Deck's default 1 GB carve-out. I'd skip pre-making onVK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPUunlessRX_VK_IMAGE_PREALLOCasks for it, and use the type's heap.should fix: interaction with
RX_VK_COMPACT=1. With compaction on, textures go totexture_pool(create_image,textures && textures_pool()), soimages_poolkeeps only DLSS's, frame interpolation's and scratch images. The pre-made blocks then sit mostly empty for the whole session (VMA never frees blocks belowminBlockCount). Under compaction the texture budget only subtracts the textures' pool's free space (vram_compact::reclaimable(block_free, pool_free)), so those empty blocks count as pressure and the held budget evicts roughly that much of the textures. The two features work against each other: compaction exists to give blocks back. Pre-make nothing (or far fewer) when compaction is on, or pre-make in the pool the textures actually use.question: the numbers (52.9 → 59.8 mean, 41.6 → 56.7 min) come from v2.0.5-v2.0.12 trees together with other changes. Since #1436 (in v2.0.12), render targets have their own pool (
target_images_pool), and this change doesn't pre-make blocks there, so some of the ~70 allocations you counted may no longer be inimages_poolat all. Could you re-run the Hyrule Field walk on current main withRX_VK_IMAGE_PREALLOC=0vs. the default and post thevk: image blockslog lines? That would show how many blocks each pool really grows to. The PR also says the test suite wasn't run, and only the title check has run in CI here.minor
gpu/vk/vk_vma.cpp:109: the first image now pays for all the blocks at once on the replay thread (19 × ~170 ms ≈ 3 s on your card, per your own timing). That's probably fine while booting, but worth saying in the log line, and worth checking it can't land mid-gameplay (for example on the first image after a device-lost recovery, if the pools are re-made).The earlier review is still open, and I'd add:
vk_vma.cpp:103-109makes up to 24 x 128 MB blocks sized from the largest device-local heap. VMA's minimum blocks skipWITHIN_BUDGET, and that heap isn't necessarilytype's.RX_VK_COMPACT=1, textures go to the other pool, so these blocks sit empty and count as pressure.Thanks for measuring the allocation cost on AMD's Windows driver. I agree with Joey-Magnus's two blocking points (the blocks are made outside the memory budget, and the heap is the largest device-local one rather than
type's, with integrated GPUs not excluded), and they're still open at this head. One thing makes his compaction point weigh more than it did:Compaction is on by default on main now. The branch is based on v2.1.0, where it was opt-in (
RX_VK_COMPACT=1). On current mainvram_compact::enabled()is true unlessRX_VK_COMPACT=0, socreate_imagesends sampled textures totextures_pool()and render targets totarget_images_pool()(#1436). What's left inimages_poolis DLSS's, frame interpolation's and the scratch images. So in the default configuration this pre-makes ~2.4 GB (on a 16 GB card) in a pool that mostly stays empty, VMA never gives back blocks underminBlockCount, and the texture budget's hold only discounts the textures' pool's free space (reclaimable(block_free, pool_free)), so those empty blocks count as pressure and push textures out. The ~70 allocations in the Hyrule Field walk were very likely texture blocks, which are now in a different pool. If pre-making helps, it probably belongs ontexture_pool(with compaction's give-back in mind) or should be sized from whatimages_poolactually grows to. Thevk: image blocksstats line on current main,RX_VK_IMAGE_PREALLOC=0against the default, would answer that, as Joey-Magnus asked.Overlap with your sibling PRs: #2437 also holds more memory up front (capture spares up to 768 MB of host-visible memory instead of 64 MB, made ahead on a worker). The two add up on a low-VRAM or integrated machine, so it'd help to size both against the budget the same way. #2440 (worker count) touches only
vk_jobs.cpp, so there's no overlap. None of the three conflict at file level.Smaller points:
RX_VK_IMAGE_PREALLOChas no upper bound (atoiof a large value asks for that many 128 MB blocks). Clamping it likeRX_VK_WORKERSin #2440 would keep a typo from failing the pool.test_target_image_pool,test_vram_compactandtest_texture_staging_publicationread this file. Joey-Magnus reports they pass at this head, but a run on a branch rebased onto current main would be good.I'm leaving this as a comment. It needs the budget/heap/iGPU fixes and a re-measure on current main (with compaction on) before it can go in.
@ -103,0 +106,4 @@uint64_t n = std::clamp<uint64_t>(heap / 100 * 15 / kImageBlock, 2, 24);if (const char* e = getenv("RX_VK_IMAGE_PREALLOC")) n = (uint64_t)std::max(0, atoi(e));pi.minBlockCount = (size_t)n;if (n && vmaCreatePool(vma, &pi, &image_pool) != VK_SUCCESS) { // (no room for them: as before, made as needed)On current main, with compaction on by default, textures don't land in this pool any more (they go to
textures_pool()), so these blocks mostly hold nothing and stay allocated for the whole session (VMA keepsminBlockCountblocks). I'd size this from what the pool actually needs on main, and againstvmaGetHeapBudgetsfordev.memory.memoryTypes[type].heapIndex, not from the largest heap's total size.github-sync referenced this pull request2026-11-12 06:22:54 +00:00
On an 8 GB card (RTX 3070, Windows) the blocks made at the start cost a lot in Castle Town Market: 31-36 fps on main against 54-56 with
RX_VK_IMAGE_PREALLOC=0, because the texture budget drops to ~0.9 GB while the blocks hold ~1 GB free. Numbers and logs' lines in #2690.github-sync referenced this pull request2026-11-12 16:54:31 +00:00