Repository navigation
fix(vk): give back the import of a retired pool whose mirror was refused - #2795
Conversation
Players report shared GPU memory that only grows over a session. A retired pool's import is given back only when its mirror state is MIRROR_NONE, so a pool whose mirror was refused (over 1 GB, or no room in VRAM when it was asked for: mirror_make) kept its import, pinned system memory the driver counts as the GPU's shared memory, for the rest of the session. The more such pools, the fuller VRAM was when they were refused. A refused pool has no mirror either, so its import goes as a MIRROR_NONE pool's does, after the same waits; imports_give_back already treats the two alike. test_retired_give_back checks the condition. Run in the game (RTX 4070 SUPER, Windows 11, DLAA 1440p). This card never has a mirror refused, so both builds had a test patch making every budget refusal MIRROR_REFUSED, with RX_VK_MIRROR_MB=256. The "vk: imports:" line: main kept 20 retired pools imported (1,488 MB) after Castle Town to the field and 13 (992 MB) in a field walk, giving back 2; with this change 0 were left in those and in Kokiri, 21 (1,472 MB), 15 (1,040 MB) and 11 (512 MB) given back. No device lost. docs/tested-configurations.md has the row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
I merged this into current main and ran test_retired_give_back, test_import_buffer_binding and test_import_write_watch. All pass, and CI is green. The change at vk_memory.cpp:3187 matches what imports_give_back already does for live pools: it treats MIRROR_NONE and MIRROR_REFUSED alike. A refused pool never has a mirror, so it can never be inherit_pending, and the existing retired_value and last_value checks against mem_completed still guard the GPU side. I don't see a path that touches a refused retired pool's import after those values pass.
One clarification for the description: on current main, a budget refusal in mirror_make sets MIRROR_NONE. Only a pool over 1 GB, or a failed create_device_buffer, becomes MIRROR_REFUSED. So in normal play this frees fewer pools than the patched A/B suggests, and that's why you needed the test patch. The fix is still right, and it's cheap. With #2618 it only conflicts in docs/development.md, and with #2794 only in docs/tested-configurations.md.
|
Thanks, that's right: on current main a budget refusal is |
Port four upstream fixes that the GCN/RADV notes did not cover: - NotRexed#2685: import game memory in 4 MB chunks where a pool has a VRAM mirror (16 MB without one, as before). A mirror is its import's whole range, so the old 16 MB chunks left 28-39% of the mirrors outside every game pool; 4 MB cuts them by roughly a quarter at the same frame rate. RX_VK_IMPORT_CHUNK_MB still chooses. - NotRexed#2795: a retired pool whose mirror was refused gives its import back too, as a MIRROR_NONE pool does. Left out, every such pool kept its pinned userptr import (shared GPU memory) for the session. - NotRexed#2825: an import mostly outside every game pool, while VRAM is over 80% of its allowance, is trimmed to its live parts; the new mirrors inherit what the GPU wrote and the old one goes once every part has its own. RX_VK_MIRROR_TRIM=0 turns it off. - NotRexed#2839: on Linux at a 12 GB+ system heap budget, idle live imports are capped at 4500 MB (RX_VK_IMPORT_CAP_MB). On RADV/amdgpu 200+ idle unmirrored pools doubled queue submit time over a two-hour session; the measured run went from 44-48 to 63-68 fps (RX 9070 XT). Game-free fixtures: tests/import_chunk_test.cpp, tests/test_mirror_trim.cpp and the extended tests/retired_give_back_test.cpp run from builder/rust/tests/amd_vram.rs, which also source-checks the wiring, the refused-mirror condition and RADV's Linux AMD mesh default (issue 2245). The Vulkan backend builds with RX_VK_ONLY. No AMD hardware here: the RX 9070 XT and RX 580 measurements are upstream's.
…ifetime and refused imports Port upstream 8b9e549 (NotRexed#2131): RX_VK_WB_OBSERVE=1 exports one actual-difference flag per write-back chunk from shaders/wb_diff.comp, appended to the existing mapped table. Results are read only once the submission that wrote them is done, before the slot is reused; missing, out-of-bounds or invalid results reject the whole batch and keep the ordinary write-back. The 600-submission report adds the completed batches, chunks, changed chunks and changed-chunk bytes, with no extra submission, wait or thread. The SPIR-V header is regenerated from the shader with the pinned glslang 16.6.0 (exact match checked). Port upstream f1d0a67 (NotRexed#2767): a pool whose write-back was worth a 256 KB shadow keeps Pool::shadow_wanted after VRAM pressure takes it, and shadows_check_room makes wanted shadows again (up to 8 a check) once the pressure rule says there is room. A pool whose write-backs stay under the make size for good after a scene change no longer stays a whole copy for the session. Port upstream c2e9ed5 (NotRexed#2795): a retired pool whose mirror was refused is given back like a MIRROR_NONE pool. It has no mirror either, and its import otherwise stayed pinned for the rest of the session. Game-free fixtures, run by builder/rust/tests/vk_memory_fixtures.rs and directly with a C++ compiler: page ranges and part matching (wb_pages.h), fence/timeline ordering (rec_value.h part map), shadow lifetime (wb_shadow_policy.h), retired refused-mirror give-back, and completed observation lifetime (wb_observe.h). tests/wb_diff_observations_gpu.cpp executes the generated shader on a real Vulkan 1.3 device (NVIDIA RTX 4070 Ti here: off/mixed/reused/zero/65535-wide cases pass).
Players report shared GPU memory that only grows over a session. A retired pool's import is given back only when its mirror state is
MIRROR_NONE, so a pool whose mirror was refused kept its import, pinned system memory the driver counts as the GPU's shared memory, for the rest of the session. On current main that is a pool over 1 GB, or one whose mirror's VRAM allocation failed (mirror_make); a pool turned away by the mirror budget becomesMIRROR_NONEand was already given back. So in normal play this frees fewer pools than the A/B below, which needed a test patch to make them.Change.
gpu/vk/vk_memory.cpp,retired_imports_give_back: aMIRROR_REFUSEDpool's import goes as aMIRROR_NONEpool's does, after the same waits.imports_give_backalready treats the two alike ((p.mstate == Pool::MIRROR_NONE || p.mstate == Pool::MIRROR_REFUSED)a few functions below). A refused pool has no mirror and is neverinherit_pending, so nothing else reaches its import once the GPU is pastretired_valueandlast_value, as for aMIRROR_NONEone.Tested in the game. RTX 4070 SUPER 12 GB (617.42), Windows 11, DLAA 2560x1440, unattended. This card never has a mirror refused (no pool over 1 GB, no failed allocation), so both builds carried a test-only patch that makes every budget refusal
MIRROR_REFUSED, run withRX_VK_MIRROR_MB=256so that most pools were refused. The last "vk: imports:" line of each run:No device lost and no error in any run. Row added to
docs/tested-configurations.md.Tests.
tests/test_retired_give_back.pychecks the condition; it fails on main and passes here.With #2618 (open). #2618 turns a mirror that found no VRAM into
MIRROR_NONEand asks again, so after it landsMIRROR_REFUSEDis left for pools over 1 GB (andRX_VK_MIRROR_NO_RETRY=1): this still gives their imports back. The two touch different functions; they conflict only indocs/rows, if at all.Not tested. AMD and Intel on Windows, where
retired_modewaits before giving retired imports back and where refused mirrors happen without a patch; a real card with VRAM full.Part of #993.
🤖 Generated with Claude Code