WIP: perf(vk): the images' pool in 32 MB blocks, not 128 (RX_VK_IMAGES_BLOCK_MB) #421
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!421
Loading…
Reference in a new issue
No description provided.
Delete branch "kingo/Longshot:perf/image-block-sizes"
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?
Why
On an 8 GB card about 900 MB of VRAM sits empty inside the renderer's memory blocks while the texture budget is cut below the textures loaded. One easy part of it: the images' pool keeps a whole 128 MB block for 11-17 MB of images. Giving it 32 MB blocks returns about 96 MB to the textures. fps: no difference these runs can separate from this PC's run-to-run noise (Measurements).
What changed
gpu/vk/vk_vma.cpp: the images' pool (DLSS's, the frame interpolation's and the scratch images, since the textures and the render targets have pools of their own) gets its own block size,kImagesBlock, 32 MB by default (RX_VK_IMAGES_BLOCK_MB).images_pool()keepskImageBlock(128 MB) where the pool also holds the textures (RX_VK_COMPACT=0) or the render targets (RX_VK_TARGET_BLOCKS=0). The switch is read byblock_mb(), the same parse asRX_VK_IMAGE_BLOCK_MB(16 MB floor). An image larger than a block is allocated outside the pool, as before.tests/test_image_block_sizes.py(new) compilesblock_mb()and runs it with the switch unset, set, below the floor, 0 and negative, and checks each pool's size and the two exceptions.tests/test_blocks_at_start.py's check ofimages_pool()follows the reworded line.RX_VK_IMAGES_BLOCK_MBindocs/development.md; kingo indocs/worklist.md's 5.6 row as the owner of this piece; one file per measured run indocs/tested/(twelve).Flags and settings
RX_VK_IMAGES_BLOCK_MB=<MB>128is main'sRX_VK_TARGET_BLOCKS=0(existing, unchanged)RX_VK_IMAGE_BLOCK_MB's sizeMeasurements
main:
5e072fc8(2026-11-14)this PR: perf/image-block-sizes @
266e0946(the head adds only thedocs/tested/files for these runs)Hardware: RTX 5060 Ti 8 GB, driver 32.0.16.1088; Intel Core Ultra 7 265F; 64 GB; Windows 11 Home 26200
Scene: an adult save, copied fresh each run; 1920x1080; the game's own upscaler (no DLSS in this build), frame generation off, vsync off, 60 fps cap
Method: renderer-only builds (
RX_VK_ONLY) swapped into one game folder. Through the control bridge: Kokiri Forest, then teleports to Hyrule Field, Market, Hyrule Field, Kakariko, Graveyard, Hyrule Field, Kokiri Forest, about a minute each, Link running small loops and turning the camera both ways (Hyrule Field: camera only, the teleport lands on Kokiri's exit). Eight runs interleaved main, PR, PR, main, PR, main, main, PR. Numbers fromlongshot_log.txt(vk: VRAM:andvk: image blockslines every 600 presents, the first 3 skipped) and the frame-time file; per run, then the median of the runs. The first main run (361) is left out of the medians: the new build's first start (1200 shader programs translated) and a walk-out of Kokiri Forest; its pool read 112 MB empty like the others.How to reproduce: any save;
RX_VK_IMAGES_BLOCK_MB=128gives main's pool; compare thevk: image blocks:line (the one without a pool name), blocks MB minus images MB.5e072fc8(runs 364, 366, 367)266e0946(runs 362, 363, 365, 368)Runs on this PC fall in two groups, about 57 fps and about 54 fps with a p99 near 62 ms, in both arms (main 366, PR 363 and 368), with the GPU in the same state in every run (median 2782 MHz, 45-46 W, 39-44% busy; peak 62-63 °C), and the slow runs took longer (509-517 s against 475-489 s): our reading is that they are CPU-side, not the change's, but that is an inference, not a measurement. 2 of the 4 PR runs are slow against 1 of 3 main runs, which moves the medians. Over every run of this work at 128 MB and at 32 MB (17 runs, two builds before and after a rebase that left
vk_vma.cppalone, and the in-build switch), the fps medians are 57.1 (9 runs) and 56.8 (8 runs). Found after the runs: the session recorder on this PC writes each session's summary with a local model on the CPU (30-60 s, about half the CPU) once the game closes; with 10 s between runs it overlapped the next run's title and load, and in some runs its first seconds of play (run 368: 14 s), in both arms alike. It does not touch the images' pool numbers; it adds to the fps noise. The runner now waits for it. Earlier, main @0caf0618against this change @ 00c69897 (2 runs each; its renderer built from the working tree that was committed as 00c69897, only the message amended after): fps 56.9 / 57.0 against 57.3 / 57.3, 1% low 14.4 / 15.3 against 14.7 / 15.6; and one build with the switch (unset, 32, 32, unset): fps 56.2 / 57.7 against 56.1 / 57.7. The pool read 112-116 MB empty with 128 MB blocks and 16-20 MB with 32 MB (and 16 MB) ones in every report of every run (44-47 a run, title and loads included). "Free in all pools" moves 300 MB between runs of one build (the render targets' pool, below), so the pool's own line is the measure.(the 21st
image blocksreport of each run)Testing
Tested: the runs above, with a screenshot in every area of every run through the bridge's
shot(main and PR at the same spots: nothing missing, garbled or flashing), and the logs scanned for device lost,VK_ERROR, "not made" and "image over budget" (none).python -I -B tests/run_all.pyon Windows: 523 passed, 5 failed, the same 5 as main at5e072fc8(test_framegen_resources,test_module_ids,test_pad_layout,test_oot_state_tap,test_tested_configurations);test_image_block_sizes.pyfails on main and passes here.Not tested: DLSS and frame generation on (this build has no DLSS, so the pool held only scratch images): their images go in this pool, and one larger than 32 MB (4K) is now allocated outside it, as an image over 128 MB was before. Other GPUs and Linux.
Needs other testers:
vk: image blocks:line (no pool name) shows the pool's blocks and images; expecting fewer empty MB than withRX_VK_IMAGES_BLOCK_MB=128and no new hitch when DLSS or frame generation is turned on or the resolution changes (T-5VX7)vk: images from a pool of 32 MB blocks, nothing drawn wrong (T-JBZR)Screenshots
Nothing on screen.
Risk and rollback
An image over 32 MB (DLSS's or frame generation's at 1440p and up) no longer fits the pool and goes to VMA's default pool, which gives anything over half its 64 MB block dedicated memory (
vk_mem_alloc.h, "Allocate dedicated memory if requested size if greater than half of preferred block size"): freed whole, so no block kept empty, but one vkAllocateMemory each when DLSS or frame generation makes its images (not per frame). Untested with DLSS here (T-5VX7). The pool itself makes up to 4 blocks where it made one only if it holds over 32 MB; here it held 11-17 MB in one.RX_VK_IMAGES_BLOCK_MB=128is main's behaviour; reverting the commit is enough.Reviewer notes
Where the rest of the ~900 MB is, from VMA's detailed statistics on this card (one run, the medians match the log's total to the MB): the default pools 259 MB (mostly the sparse mirrors' 64 KB pages, #250), the render targets' pool 234 MB, the textures' pool 174 MB, this pool 111 MB. Smaller render-target blocks were tried in the same A/B: 64 MB left as much empty as 128 (376 / 420 against 461 / 345 MB), 32 MB left ~140 MB less but with worse 1% lows (13.8 / 11.7 against 16.6 / 15.6 fps), so they are not in this PR.
Issues
See https://github.com/NotRexed/Hookshot/issues/993 (worklist 5.6, the 8 GB side)
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view).mainand passing here.tests/that reads changed source updated in this PR, not weakened.getenv,atoiandstd::max, as the line it replaces, and no paths.)🤖 Generated with Claude Code
Measured again at the commits the description names (main @
5e072fc8against266e0946, 8 runs interleaved; the first main run left out: shader translation on the new build's first start and a walk-out). The images' pool: 112 MB empty on main in every run, 16 MB with this. fps medians 57.1 (3 runs) against 55.2 (4): runs here fall in two groups (~57 and ~54 fps) in both arms with the GPU in the same state, so the slow runs are CPU-side; over all 17 runs of this work at 128 and 32 MB the medians are 57.1 and 56.8. Eightdocs/tested/files added. Test requests opened: T-5VX7 (DLSS and frame generation), T-JBZR (a Linux build); both need thetesting wantedlabel to be claimable.5e072fc8and266e0946Checked by two independent passes: every run's numbers recomputed from the raw logs with separate scripts (all 16 runs and both medians match to 0.01), and the setup audited (each run's renderer hash and start-up block size, no switch carried between runs, no error lines, the diff, the docs files). What that changed here:
5e072fc8(it was at0caf0618): the same 5 failures.Added kingo to docs/worklist.md row 5.6 as the owner of this piece (the VRAM held empty in the image blocks), as the worklist asks in the PR that starts the work.
Added a note to Measurements: this PC's session recorder summarised each session with a local model on the CPU for 30-60 s after the game closed, overlapping the start of the next run in both arms (found afterwards with a new CPU log). It does not change the pool numbers; it adds to the fps noise. Later runs wait for it.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.