vk: imports given back under system-heap pressure on 16 GB PCs #479

Merged
imbahzor merged 1 commit from feat/16gb-import-giveback into main 2026-11-08 19:22:24 +00:00
imbahzor commented 2026-11-08 05:08:56 +00:00 (Migrated from github.com)

Imports given back under system-heap pressure (16 GB PCs)

Problem

The renderer imports the game's memory pools with VK_EXT_external_memory_host. The driver pins those pages and counts them in the system heap the GPU reaches, whose budget is about half the PC's RAM: 7503 MB on an RTX 3060 with 16 GB. Imports are kept for the whole session, so on a 16 GB PC that heap fills up:

  • a vanilla session (no-visual-studio) climbed to 7199 of 7503 MB in under 20 minutes (2320 → 5267 → 6289 → 6796 → 7199 MB);
  • past the budget, new imports fail with VK_ERROR_OUT_OF_DEVICE_MEMORY (seen at 7758 of 7503 MB): terrain left out, load zones stuck while the import is retried.

Change

Every 16 submissions, memory_begin_submission asks VMA for the system heaps' use and budget (system_heap_usage, a fresh frame index so the driver is asked again). Past RX_VK_IMPORT_HIGH percent of the budget (default 85), the pools bound least lately give their imports back down to RX_VK_IMPORT_LOW percent (default 75):

  • only pools without a mirror (MIRROR_NONE / MIRROR_REFUSED), not bound for 600 submissions and done on the GPU (last_value <= mem_completed);
  • they leave the table as a retired pool does (page table entries 0, pools_gen bumped, the pool caches emptied) and are imported again at their next binding;
  • their buffer and memory are freed only once everything recorded until then is done (retired_value = mem_rec_value, checked against mem_completed), the same rule as the dead mirrors;
  • gpu_buffer() (texture uploads) now marks the pool in use too, so a pool only touched by uploads is not given back under them.

On by default only where the system heap's budget is under 12 GB (16 GB PCs and less), where it ran out; larger PCs are unchanged. RX_VK_IMPORT_HIGH=0 turns it off, RX_VK_IMPORT_HIGH=85 turns it on anywhere.

Caveat

last_value counts bindings, not shaders that reach a pool through the guest page table. A pool reached only that way could be given back while idle by bindings and would read zeros until it is bound again. I have not seen it in play (the pools given back were long unbound), but it is the reason this stays limited to small budgets and to pools idle for 600 submissions. If you know a better "in use" signal for page-table reads, I'd happily switch to it.

Testing

  • RTX 3060 12 GB, 16 GB RAM, Windows 11: this logic has run for several hours of play on the foliage and no-visual-studio releases (applied locally): the heap stays at or under ~85%, no terrain loss, no stuck load zones, no device lost attributable to it (the device losses seen came from other changes and were bisected away).
  • On current main the port builds cleanly (RX_VK_ONLY=1); I could not run it there, because MKWVK_API_VERSION went to 4 and my installed game is the release build. Happy to do a full build from main and test if you'd like.

🤖 Generated with Claude Code

## Imports given back under system-heap pressure (16 GB PCs) ### Problem The renderer imports the game's memory pools with `VK_EXT_external_memory_host`. The driver pins those pages and counts them in the **system heap** the GPU reaches, whose budget is about half the PC's RAM: **7503 MB** on an RTX 3060 with 16 GB. Imports are kept for the whole session, so on a 16 GB PC that heap fills up: - a vanilla session (`no-visual-studio`) climbed to **7199 of 7503 MB** in under 20 minutes (2320 → 5267 → 6289 → 6796 → 7199 MB); - past the budget, new imports fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY` (seen at 7758 of 7503 MB): terrain left out, load zones stuck while the import is retried. ### Change Every 16 submissions, `memory_begin_submission` asks VMA for the system heaps' use and budget (`system_heap_usage`, a fresh frame index so the driver is asked again). Past `RX_VK_IMPORT_HIGH` percent of the budget (default 85), the pools bound least lately give their imports back down to `RX_VK_IMPORT_LOW` percent (default 75): - only pools **without a mirror** (`MIRROR_NONE` / `MIRROR_REFUSED`), **not bound for 600 submissions** and **done on the GPU** (`last_value <= mem_completed`); - they leave the table as a retired pool does (page table entries 0, `pools_gen` bumped, the pool caches emptied) and are imported again at their next binding; - their buffer and memory are freed only once everything recorded until then is done (`retired_value = mem_rec_value`, checked against `mem_completed`), the same rule as the dead mirrors; - `gpu_buffer()` (texture uploads) now marks the pool in use too, so a pool only touched by uploads is not given back under them. **On by default only where the system heap's budget is under 12 GB** (16 GB PCs and less), where it ran out; larger PCs are unchanged. `RX_VK_IMPORT_HIGH=0` turns it off, `RX_VK_IMPORT_HIGH=85` turns it on anywhere. ### Caveat `last_value` counts bindings, not shaders that reach a pool through the guest page table. A pool reached *only* that way could be given back while idle by bindings and would read zeros until it is bound again. I have not seen it in play (the pools given back were long unbound), but it is the reason this stays limited to small budgets and to pools idle for 600 submissions. If you know a better "in use" signal for page-table reads, I'd happily switch to it. ### Testing - RTX 3060 12 GB, 16 GB RAM, Windows 11: this logic has run for several hours of play on the `foliage` and `no-visual-studio` releases (applied locally): the heap stays at or under ~85%, no terrain loss, no stuck load zones, no device lost attributable to it (the device losses seen came from other changes and were bisected away). - On current `main` the port builds cleanly (`RX_VK_ONLY=1`); I could not run it there, because `MKWVK_API_VERSION` went to 4 and my installed game is the release build. Happy to do a full build from `main` and test if you'd like. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Commenting is not possible because the repository is archived.
No description provided.