Skip to content

fix(vk): give back the import of a retired pool whose mirror was refused - #2795

Merged
hckrman101 merged 2 commits into
NotRexed:mainfrom
SadTransGirl:fix/retired-refused-imports
Nov 12, 2026
Merged

hckrman101 merged 2 commits into
NotRexed:mainfrom
SadTransGirl:fix/retired-refused-imports

Conversation

@SadTransGirl

@SadTransGirl SadTransGirl commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

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 becomes MIRROR_NONE and 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: a MIRROR_REFUSED pool's import goes as a MIRROR_NONE pool's does, after the same waits. imports_give_back already 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 never inherit_pending, so nothing else reaches its import once the GPU is past retired_value and last_value, as for a MIRROR_NONE one.

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 with RX_VK_MIRROR_MB=256 so that most pools were refused. The last "vk: imports:" line of each run:

Main This change
Castle Town to Hyrule Field 20 retired still imported (1,488 MB), 2 given back 0 still imported, 21 given back (1,472 MB)
Hyrule Field walk, 5 min 13 retired still imported (992 MB), 2 given back 0 still imported, 15 given back (1,040 MB)
Kokiri Forest (not run) 0 still imported, 11 given back (512 MB)

No device lost and no error in any run. Row added to docs/tested-configurations.md.

Tests. tests/test_retired_give_back.py checks the condition; it fails on main and passes here.

With #2618 (open). #2618 turns a mirror that found no VRAM into MIRROR_NONE and asks again, so after it lands MIRROR_REFUSED is left for pools over 1 GB (and RX_VK_MIRROR_NO_RETRY=1): this still gives their imports back. The two touch different functions; they conflict only in docs/ rows, if at all.

Not tested. AMD and Intel on Windows, where retired_mode waits 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

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 Joey-Magnus 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.

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.

@SadTransGirl

Copy link
Copy Markdown
Contributor Author

Thanks, that's right: on current main a budget refusal is MIRROR_NONE, and only a pool over 1 GB or a failed create_device_buffer is MIRROR_REFUSED. I've corrected the description to say so, and that in normal play it frees fewer pools than the patched A/B.

@hckrman101
hckrman101 merged commit c2e9ed5 into NotRexed:main Nov 12, 2026
10 checks passed
dazemc added a commit to dazemc/Hookshot-rs that referenced this pull request Nov 12, 2026
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.
dazemc added a commit to dazemc/Hookshot-rs that referenced this pull request Nov 12, 2026
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants