feat(vk): RX_VK_MEMORY_PRIORITY tells the driver which memory to keep in VRAM when it runs short (off by default) #560
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
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/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!560
Loading…
Reference in a new issue
No description provided.
Delete branch "Jiro113/Longshot:feat/vk-memory-priority"
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?
Type: feat · Risk: low · Tested on: Windows 11, RTX 4090 (NVIDIA 621.24) · Needs: Windows with an NVIDIA card of 8 GB or less; Windows with an AMD or Intel card; Linux, any GPU
Why
When the graphics card runs short of memory, because other programs hold part of it or because the card is small for what the game has loaded, the system moves some of the game's memory out of the card and the frame rate collapses: in the test below, from 85 to 90 fps down to 27, then under 9, in the same scene. Today the game has no say in which of its memory goes. This PR lets it tell the driver what to keep (the pictures it draws into) and what may go first (the textures), behind a switch that is off by default, with the numbers from one PC.
See #586 (4 GB cards short of VRAM) and #130 (a whole session at the driver's VRAM limit on an 8 GB card, other programs holding part of it): both are the situation the switch is for. It has not run on such a card yet, so it closes neither. #441 (a 4 GB RTX 3050 laptop at 9 to 20 fps with a 4.8 GB budget) may be the same situation; its cause isn't known.
What changed
The renderer can now ask the device for
VK_EXT_memory_priorityand give its memory a priority: the render targets 1, the sampled textures' pool 0.25, the rest the 0.5 everything has today. It does so only withRX_VK_MEMORY_PRIORITYset; unset, the extension isn't asked for and nothing changes.gpu/vk/memory_priority.h(new, no Vulkan in it; read it first).memory_priority::parse()readsRX_VK_MEMORY_PRIORITY: off by default;1gives the render targets 1, the sampled textures' pool 0.25 and the rest 0.5;<targets>,<textures>sets the first two for a test, each a number from 0 to 1 with a decimal point. Anything else with a comma in it (0,5meant as a half,nan,0.25, three numbers) is not guessed at: it gives the usual 1 and 0.25, and the log says the value wasn't read.gpu/vk/vk_main.cppasks forVK_EXT_memory_priorityand its feature only with the switch on, and keeps the answer indev.memory_priority(gpu/vk/vk_internal.h).gpu/vk/vk_vma.cpp.memory_tiers()holds the rule, the allocator is made withVMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BITwhen the device has the extension, and every pool (pi.priority) and every allocation (create_image,create_buffer,create_buffer_cached,create_device_buffer,alloc_sparse_block) sets a priority. All of them, because with that bit VMA gives a custom pool or a dedicated allocation that sets none the priority 0: that nothing is left at 0 is the point to check, andtests/test_memory_priority.pyreads the source for it. One log line at the start says the three numbers, or that the device has no such extension with its feature.vkAllocateMemory: the sparse textures' 64 MB chunks invk_texture.cpp, two small buffers the CPU reads, the imports of guest memory), and an image its pool has no room for when it lands in one of VMA's shared blocks. A priority for the sparse chunks, which are textures, is left for a follow-up: not measured, so not added.tests/test_memory_priority.py(new) runs the rule and readsvk_vma.cppandvk_main.cppfor a pool or an allocation left without a priority;tests/test_blocks_at_start.pygains the pool's priority in its stub and two cases.docs/development.md, and twelve files indocs/tested/.Measurements
Verdict: it helped where the game's memory was being moved out of VRAM, on the one PC it ran on: with about 0.5 GiB out a frame took 16.5 to 21.6 ms with the switch on against 30.6 and 33.6 ms with it unset, and with about 1.3 GiB out 24 to 29 ms against 109 ms and more. With nothing short it cost nothing measurable (7.2 to 7.9 ms either way). The numbers are from the build named in Baseline, not from this head, which has not run in the game.
Baseline: both sides are one build, main @
5e072fc8(2026-11-14) with this PR's four renderer files on it (memory_priority.h,vk_internal.h,vk_main.cpp,vk_vma.cpp) and DLSS, withRX_VK_MEMORY_PRIORITYunset against set. main against this PR wasn't the comparison because the switch is off by default: with it unset the renderer doesn't ask for the extension and VMA ignores the priorities, which is main's behaviour. Comparing the switch off and on in one build keeps everything else the same.Since the runs: the PR is now on main @
82886e89, 29 commits after5e072fc8, and was not measured again. What main changed in the renderer since: a render thread for the replay (on by default from 12 logical processors, so on at this PC), a renderer built with a play profile, and the texture budget and its squeeze on integrated GPUs; in this PR's own files,vk_vma.cppnow starts the textures' pool with no block on an integrated GPU (a dedicated card's count is as it was) andvk_main.cppgained an Intel Arc case. None of it adds a pool or an allocation, or sets a priority (read off main's diff ofgpu/vk), and at the headtests/test_memory_priority.pystill finds none left without one: on a dedicated card the switch sets the same priorities on the same pools and allocations as in the runs. What that does to the frame time at the head was not seen: the frame times are the measured build's and would not be the same with the render thread, and the gain, though it is the switch off against on inside one build either way, is to be confirmed there (Must be tested, first item). Since the runs this PR's ownmemory_priority.hhas gained a comment and the pair's strict reading, which reads the three values that were run (1,0.5,0.25,1,0.5) as before, andvk_vma.cpptwo log lines.Hardware: RTX 4090 24 GB, NVIDIA 621.24, Core i9-13900K, 64 GB RAM, Windows 11 Home 10.0.26300
Scene: Hyrule Field as a child, standing still; output 2560x1440 with ultrawide on (presented 5120x1440, SDR), DLSS on (2560x720 to 5120x1440, preset K), frame generation off, V-Sync Auto (mailbox), quality preset 6, texture budget 6144 MB, uncapped (
HOS_FPS_MAX=1000),RX_VK_AUTO_TUNE=0Method: fourteen launches, twelve usable runs, in the order off, on, on, (off, void: the window's focus changed and the game stopped answering;
1,0.5, void: started beside that stuck game and stopped by hand), off,1,0.5,0.5,0.25; then, withRX_VK_IMAGE_PREALLOC=9, in the order1, unset, unset,1,0.5,0.25,1(the 5-minute hold). Each run starts the game, gets into play, teleports to the scene and waits 20 s there before its first window (the warm-up). In each run a 30 s window with nothing else on the card, then VRAM occupants in other processes (12288 MB, then 2048 MB ones), each followed by 20 s of settling and a 30 s window, until 800 MB or more of the game's device memory was out of VRAM (the driver's count of the game's use of the device-local heap minus Windows' Dedicated Usage for the process), then 3 more windows (10 in the last run), then one with the occupants gone. A number is the median of a window'svk: GPU time ... ms a framelog lines, with the mean of itsfpslines in brackets. A window is used only in play, on the same map, with the world drawn, no focus change and every occupant alive.How to reproduce: teleport to Hyrule Field as a child with the settings above and stand still; start processes that each allocate and touch VRAM (12288 MB, then 2048 MB at a time) until the game's Dedicated Usage in Windows falls 800 MB below the
vk: VRAM: ... MB usedline; read thevk: GPU timeandfpslines; repeat withRX_VK_MEMORY_PRIORITY=1.RX_VK_IMAGE_PREALLOC=9gives the textures' pool the 9 blocks of an 8 GiB card.With
RX_VK_IMAGE_PREALLOC=9on this 24 GB card (the textures' pool settles at 9 blocks, 1152 MB, holding 791 to 926 MB of images). GPU time a frame, one value a run:5e072fc85e072fc85e072fc8With the textures' pool as this card makes it (24 blocks, 3072 MB, which it keeps under pressure while holding about 800 to 930 MB of images):
5e072fc85e072fc85e072fc85e072fc8Read with these limits:
0.5,0.25did as well as1(one run with each pool size), and the render targets raised alone (1,0.5, one run) started as well, lost it within 90 s and ended at 67.55 ms, slower than the switch unset at that step (30.4 to 30.7 ms).1keeps the render targets at 1 all the same: the measured build has it, it did no harm in five runs, and changing it would need the runs again.Flags and settings
One new switch,
RX_VK_MEMORY_PRIORITY, in two forms; no menu row, ini key or build flag.RX_VK_MEMORY_PRIORITY=1VK_EXT_memory_priorityand gives the renderer's memory a priority: render targets 1, sampled textures' pool 0.25, the rest 0.5RX_VK_MEMORY_PRIORITY=<targets>,<textures>0.5,0.25did as well as1, in one run with each pool size. Doesn't help:1,0.5(the render targets raised alone) lost its gain within 90 s in its one run and ended at 67.55 ms a frame, slower than the 30.4 to 30.7 ms of the switch unset at that step. Untested: the same as aboveTesting
Tested:
python -I -B tests/run_all.py --jobs 12on Windows 11 at2e2e6490: 521 passed, 14 failed, 43 skipped. The fourteen fail the same way on main @82886e89on this PC (run on an exported copy of it, each failure's text read on both sides):tests/test_appimage_player_build.py,tests/test_bake_portability.py,tests/test_bug_report.py,tests/test_cpm_discovery.py,tests/test_env_on.py,tests/test_lift_cache.py,tests/test_lift_cleanup_error.py,tests/test_linux_preflight.py,tests/test_make_game_cache.py,tests/test_oot_state_tap.py,tests/test_readback_late.py,tests/test_tested_configurations.py,tests/test_vram_budget_row.pyandtests/test_walk_key.py. (With the default 32 checks at once, one or two small fixtures also hit their 10 s limit, a different one each run; each passes alone.)tests/test_memory_priority.pyandtests/test_blocks_at_start.pypass. The renderer alone (RX_VK_ONLY=1, no game) built from2e2e6490on both systems, 0 files failed: on Windows 11 with llvm-mingw 20261104 (longshot_vk.dll, built without the DLSS SDK), and on Ubuntu 22.04 (under WSL 2) the way CI's "longshot_vk.so (build check)" step builds it (RX_MARCH=x86-64-v3,RX_RELEASE=1, the shipped profile;longshot_vk.so); theretests/test_memory_priority.py,tests/test_blocks_at_start.pyandtests/test_utf8_paths.pypass too. In the game, on the build named in Baseline: the twelve runs above on the RTX 4090, Hyrule Field, DLSS on; every run's log has DLSS started and no device-lost line, and the frame time came back to 7.2 to 7.8 ms once the occupants were gone. With the switch on, the log says the three priorities.Not tested: this PR's head in the game (the runs' renderer is this PR's four files compiled on main @ 5e072fc8's tree; main's renderer has changed since, see Measurements, and the head's renderer was built but not run); a card with little VRAM of its own; AMD and Intel drivers; Linux in the game (built there, not run: no Linux with a GPU here); a device without the extension (the code path logs it and changes nothing; no such device here); the log line for a pair that isn't read (checked by
tests/test_memory_priority.py: the rule, and thatvk_vma.cpplogs it; not seen in the game); a scene that moves or loads; frame generation on; more than 5 minutes under pressure.Must be tested:
RX_VK_IMAGE_PREALLOC=9and VRAM occupants until about 0.5 GiB of the game's memory is out of VRAM,RX_VK_MEMORY_PRIORITYunset then=1, expecting thevk: memory priorityline at the start and a lowervk: GPU timewith it on, as in the tables (by hand, the author: two interleaved runs each way, thevk: GPU timeandfpslines)RX_VK_MEMORY_PRIORITYunset then=1, expecting the same or a better frame time with it on and no new stutter (by hand, test request T-MCA2: thevk: GPU time,fpsandvk: VRAMlines of the log)RX_VK_MEMORY_PRIORITY=1and play a few minutes, expecting thevk: memory priorityline at the start and the frame time of the switch unset (by hand, test request T-ATFL: the log'svk: memory priorityandvk: GPU timelines)RX_VK_MEMORY_PRIORITY=1and play a few minutes, expecting thevk: memory priorityline at the start and the frame time of the switch unset (by hand, test request T-FKWJ: the same lines)The three requests wait for a maintainer's
testing wantedlabel before a tester can take them. The last three decide the default more than the merge: with the switch unset, which is how it ships, nothing of this runs.Risks
What could break: nothing with the switch unset, which is how it ships: the extension isn't asked for. With it on, a driver may ignore the priorities, or use them in a way that suits another scene less (textures brought back late could stutter where the camera turns fast; not seen standing still). The test form can do worse than nothing:
1,0.5ended slower than the switch unset in its one run.Turning it off: unset
RX_VK_MEMORY_PRIORITY; reverting the commit removes it.Compatibility: Nothing changes: the switch is unset by default, and no save,
longshot_options.inikey, shader or pipeline cache is touched.MkwVkHostandMkwVkRendererare as they were, soMKWVK_API_VERSIONstays.Issues
See #586
See #130
See #441
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.1: 2 and 3 runs with 9 blocks, 2 and 2 with 24; the two test pairs have one run a pool size and are said so.)docs/tested/for each new measured run (format indocs/tested-configurations.md).tests/that reads changed source updated in this PR, not weakened.2e2e6490on both, see Testing; the change handles no path.)🤖 Generated with Claude Code
Pushed
f9fa4490: the twelve docs/tested rows now name this pull request (nothing else changed sincecb44f432). The description has the measurements, the log lines behind the 26624 MB step, and the renderer's build fromcb44f432on Windows (llvm-mingw) and Ubuntu 22.04. It stays WIP until the three test requests are open.Opened the three test requests: T-MCA2 (an NVIDIA card with 8 GB or less on Windows), T-ATFL (AMD or Intel on Windows) and T-FKWJ (Linux, a run in the game). They wait for a maintainer's
testing wantedlabel.Two commits (
99ec88a8,706c01bb) after a second AI agent, started with nothing from the work on this change, read the PR. What changed and why:RX_VK_MEMORY_PRIORITY=<targets>,<textures>made priorities out of0,5(meant as a half),nan,0.25,1,or three numbers. It now takes two numbers from 0 to 1 and nothing else; anything else with a comma in it gives the usual 1 and 0.25, and a line in the log says the value wasn't read. The values that were measured (1,0.5,0.25,1,0.5) read as before.tests/test_memory_priority.pyhas the refused cases.vkAllocateMemoryand keep the driver's 0.5, and so does an image that overflows its pool into one of VMA's shared blocks. The guide, the check and the description now say so; the code there is unchanged, and a priority for the sparse chunks is left for a follow-up.0.5,0.25and1,0.5have one run each a pool size; the description and the header's comment now say "looks like", and that1,0.5ended slower than the switch unset.docs/testedrows say what the renderer files gained since the runs.Checked again:
tests/run_all.pyat706c01bb(522 passed, 43 skipped, and the same 7 failures as on main), and the renderer built from99ec88a8on Windows and on Ubuntu 22.04, 0 files failed. The head has still not run in the game, so the numbers are those of the measured build. Still WIP.WIP: feat(vk): RX_VK_MEMORY_PRIORITY tells the driver which memory to keep in VRAM when it runs short (off by default)to feat(vk): RX_VK_MEMORY_PRIORITY tells the driver which memory to keep in VRAM when it runs short (off by default)Also, not sure about the proper process but reviews are requested, please !
706c01bb842e2e649085Rebased onto main @
82886e89(the head is now2e2e6490, was706c01bb) and the description moved to the new template. What changed and why:tests/test_blocks_at_start.py, where main added theintegratedcase to the list this PR addspriorityandpriority-compaction-offto: all three are kept. Nothing else conflicted, and the four commits are otherwise as they were (git range-diffshows that one line).5e072fc8with this PR's renderer files, and main is 29 commits further. Why the comparison still holds, and what doesn't carry over, is in Measurements under "Since the runs": main's renderer changes since (the render thread, the play profile, the integrated-GPU texture budget, an Intel Arc case) add no pool or allocation and set no priority, so on a dedicated card the switch sets the same priorities on the same memory; the frame times themselves are the measured build's, and the gain at this head is still to be seen. A run at this head on the same PC is the first item of Must be tested.Checked again at
2e2e6490: the renderer builds on Windows (llvm-mingw) and on Ubuntu 22.04 the way CI's build check does, 0 files failed;tests/test_memory_priority.pyandtests/test_blocks_at_start.pypass on both.tests/run_all.py --jobs 12on Windows: 521 passed, 14 failed, 43 skipped, and the fourteen fail the same way on main @82886e89here (named in Testing).The three test requests (T-MCA2, T-ATFL, T-FKWJ) still wait for a maintainer's
testing wantedlabel, and the workflows for a maintainer's approval.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.