WIP: fix(vk): give memory back under Linux memory pressure and after the precompile #223

Draft
dukenheimer wants to merge 1 commit from dukenheimer/Longshot:brett-d/legendary-broccoli into main
First-time contributor

Why

On a 16 GB Steam Deck OLED, the game's guest memory (4.7-6.0 GB shmem), the renderer's anonymous heap (up to 8.9 GB on main) and kernel-pinned VMA blocks share one 16 GB pool while RADV's device-heap budget (~7.5 GB) reports VRAM under 50% used: loading into Kokiri Forest drove MemAvailable down to 354-390 MB with 2.8-4.6 GB in swap and terminated all 3 starts on main. This releases the precompile's shader modules on <= 20 GB RAM systems once their pipelines are built and gives back texture blocks, idle staging/capture buffers and idle guest-pool imports under Linux memory pressure before earlyoom or direct reclaim acts.

What changed

  • linux pressure (gpu/vk/linux_pressure.h): reads MemAvailable, SwapFree and MemTotal from /proc/meminfo on Linux, computes the shortfall below RX_VK_AVAIL_RESERVE_MB (default 1536 MB) and RX_VK_COMMIT_RESERVE_MB (default 2560 MB, matching Windows' commit rule), gates post-precompile module release (should_drop_modules: on by default for <= 20 GB RAM or active pressure, off on 32-64 GB desktops), and provides PressureHold (cuts the texture budget from the texture total at pressure entry with a 512 MB floor, holds it without per-frame ratcheting while VMA compaction frees blocks, and leaves pressure only once both reserves clear by 256 MB).
  • textures (texture_frame() in gpu/vk/vk_texture.cpp): samples /proc/meminfo every 30 presents and stores the shortfall in linux_shortfall_bytes (so imports_give_back() reads the atomic instead of opening /proc/meminfo under pools_mu). While PressureHold is active, applies the held budget to budget and rule_budget, drains the render-target pool (pool_pressed = true), wakes VRAM compaction at its pressed thresholds (compact.pressed = true, 192 MB free space, 120-present rest), frees idle staging arenas on pressure entry and after 5 s idle (instead of every pressed frame), checks malloc_trim(0) every 600 presents only when the previous trim freed >= 16 MB or free arena bytes grew by >= 64 MB, and logs vk: memory pressure on Linux: at most every 600 frames.
  • guest imports & capture buffers (cap_free_trim(), imports_give_back() in gpu/vk/vk_memory.cpp): under Linux memory pressure (linux_pressure_shortfall()), trims spare capture buffers (cap_free) down to 32 MB and adds the shortfall (linux_pressure::import_want(over, dead_import_bytes)) to imports_give_back() so idle unmirrored guest-pool imports are unpinned even on < 12 GB system-heap budgets.
  • shader modules after precompile (program_cache_read(), program_cache_drop_modules(), program_cache_complete_graphics() in gpu/vk/vk_progcache.cpp; precompile_run(), graphics_pipeline(), pipeline_cache_play() in gpu/vk/vk_draw.cpp; gpu/vk/vk_shader.h): records each uncompressed record's (record_at, record_size) and each stage's (code_at, code_words) in longshot_programs.bin (no change to LONGSHOT_SHADER_STAMP or kProgramCacheFormat). On Linux with <= 20 GB RAM or active pressure (should_drop_modules), once precompile_run() finishes (pipeline_cache_play()), destroys the VkShaderModule handles of ready programs whose pipelines were built in the precompile (modules_dropped = true), remakes them on demand from longshot_programs.bin via program_cache_complete_graphics() (checking the record head at record_at and marking p.failed = true and leaving the pipeline unbuilt on any read or vkCreateShaderModule failure), and calls malloc_trim(0).
  • tests (tests/test_linux_pressure.py, tests/linux_pressure_test.cpp): game-free source and compiled C++ checks for /proc/meminfo parsing (MemAvailable, SwapFree, MemTotal), should_drop_modules, PressureHold hysteresis/floor/no-ratchet, import_want, and program_cache_drop_modules() / program_cache_complete_graphics() (including failed make_module and stale record head checks).
  • docs (docs/development.md, docs/tested-configurations.md): documents RX_VK_AVAIL_RESERVE_MB, RX_VK_DROP_MODULES and RX_VK_KEEP_MODULES, updates RX_VK_COMMIT_RESERVE_MB for Linux, and adds the Steam Deck OLED rows.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
RX_VK_AVAIL_RESERVE_MB=<MB> Physical RAM (MemAvailable in /proc/meminfo, sampled every 30 presents) the renderer leaves free on Linux before trimming the texture budget (PressureHold), pools, staging/capture spares and idle imports; 0 turns the MemAvailable check off 1536 (on, Linux only) On by default: idle when MemAvailable >= 1536 MB 16 GB Linux / SteamOS handhelds and PCs (Steam Deck) Windows (does not read /proc/meminfo), 32+ GB Linux PCs with ample MemAvailable Linux with NVIDIA / Intel GPUs
RX_VK_COMMIT_RESERVE_MB=<MB> Extended to Linux (MemAvailable + SwapFree in /proc/meminfo) alongside Windows (ullAvailPageFile); 0 turns the check off 2560 (on) On by default: idle when MemAvailable + SwapFree >= 2560 MB Linux systems with small or nearly full swap/zram, Windows PCs near commit limit Systems with ample RAM + swap Linux with swap disabled
RX_VK_DROP_MODULES=0 or 1 Controls whether pipeline_cache_play() lets go of ready programs' VkShaderModule handles after precompile_run() on Linux (1 forces on any RAM size; 0 keeps them) Auto: on for <= 20 GB RAM or active pressure, off on > 20 GB desktops Auto by default: saves ~5.95 GB of anonymous memory on 16 GB Linux systems while leaving 32-64 GB desktops untouched 16 GB Linux / SteamOS systems Windows, 32-64 GB Linux desktops (unless =1) —
RX_VK_KEEP_MODULES=1 Alias for RX_VK_DROP_MODULES=0: keeps ready programs' VkShaderModule handles resident after precompile_run() on Linux 0 Off by default (0): for A/B testing A/B testing only 16 GB shared-memory handhelds —

Measurements

main: 08771847 (2026-11-13; this PR's base is 7315957c, a few commits newer with no memory changes)
this PR: brett-d/legendary-broccoli @ 05f4c4aa (6931f715 with docs/tested-configurations.md updated)
Hardware: Steam Deck OLED (16 GB shared RAM, 8 GB swap file, AMD Custom GPU 0932, RADV Mesa 26.2.0-devel, SteamOS 3.9.5, kernel 7.2.7-valve2, Gaming Mode 1280x720, stock clocks, mesh shaders on compute fallback)
Scene: Kokiri Forest moving route; Quality preset Auto (this PC), each build in its own folder and caches, without the Steam Deck profile (#225) so both keep all 25,303 graphics pipelines ready (RX_VK_LOAD=start)
Method: Full Linux build of host + renderer from source; memory sampled once a second from /proc (RSS, RssAnon, process VmSwap, MemAvailable, SwapFree) with the low-memory guard on (kills when MemAvailable < 500 MB for 5 s with < 4 GB SwapFree, or < 250 MB with < 2 GB SwapFree). One cold and one warm run on this PR @ 05f4c4aa; one cold and two warm runs on main @ 08771847 (main was killed by the guard in all 3 starts before reaching a measured window, so real fps are not comparable; full report in #223 (comment)).
How to reproduce: On a Steam Deck OLED in Gaming Mode (1280x720, stock clocks) with Quality preset Auto (this PC), run the Kokiri Forest moving route on a cold and a warm cache with main @ 08771847 and this PR @ 6931f715.

Metric (Kokiri Forest moving route, warm start) main @ 08771847 (2 warm runs) this PR @ 05f4c4aa (1 warm run) Δ
Outcome Killed by memory guard at 2 min in both runs Completed full route (0 kills, 0 crashes) Survived
Peak RSS 10,738 / 10,742 MB 8,094 MB -2,646 MB (-2.58 GB)
Peak anonymous memory (RssAnon) 8,888 / 8,902 MB 2,950 MB -5,945 MB (-5.81 GB)
Process swap peak (VmSwap) 2,818 / 3,475 MB 947 MB -1,871 to -2,528 MB
MemAvailable min (SwapFree then) 390 / 354 MB (< 4 GB swap free -> killed) 284 MB (7.2 GB swap free; 9 dips < 500 MB, all with > 4 GB swap free) +3.7 to +4.4 GB swap free
VkShaderModules let go after precompile 0 41,044 (47,797 program keys kept, start-up 8.2 s) +41,044
vk: memory pressure on Linux: lines — 3 (2,371 -> 1,465 -> 1,465 MB texture budget; no ratcheting) —
Real / shown FPS (p95) killed at 2 min (not reached) 14.8 / 27.3 (p95 90 ms) not comparable (main killed at 2 min)
Metric (Kokiri Forest moving route, cold start) main @ 08771847 (1 cold run) this PR @ 05f4c4aa (1 cold run) Δ
Outcome Killed by memory guard at 6 min (14.0 fps before kill) Completed full route (0 kills, 0 crashes) Survived
Peak RSS 9,371 MB 8,979 MB -392 MB
Peak anonymous memory (RssAnon) 6,069 MB 8,003 MB (finished route) +1,934 MB
Process swap peak (VmSwap) 4,598 MB 2,892 MB -1,706 MB (-1.67 GB)
MemAvailable min (SwapFree then) 387 MB (< 4 GB swap free -> killed) 610 MB (4.7 GB swap free; 0 kills) +223 MB
vk: memory pressure on Linux: lines — 4 (1,732 MB x3, then recovered to 2,560 MB once memory returned) —
Real / shown FPS (p95) killed at 6 min 14.4 / 27.0 (p95 96 ms) not comparable (main killed at 6 min)
vk: programs: 41044 shader modules let go after the precompile (RX_VK_KEEP_MODULES=1 keeps them)
vk: memory pressure on Linux: 1238 MB available, 7348 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 2371 MB of their 2668 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB)
vk: memory pressure on Linux: 779 MB available, 6995 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 1465 MB of their 2384 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB)
vk: memory pressure on Linux: 1780 MB available, 5098 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 1465 MB of their 1917 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB)

Testing

Tested:

  • In the game on a Steam Deck OLED (16 GB shared RAM, 8 GB swap, SteamOS 3.9.5, RADV Mesa 26.2.0-devel, mesh shaders on compute fallback): cold and warm moving-route comparison of main @ 08771847 vs this PR @ 05f4c4aa (#223 (comment)), confirming 41,044 shader modules let go on warm start (RssAnon down from 8.9 GB to 2.95 GB, swap down from 2.8-3.5 GB to 0.95 GB), PressureHold holding the texture budget without ratcheting (2,371 -> 1,465 -> 1,465 MB warm; 1,732 MB x3 -> 2,560 MB recovery cold), and 0 kills across both starts; plus a warm moving-route run combined with #225 (5,028 compute/fallback modules let go while vertex/fragment modules stayed resident, RssAnon peak 2,496 MB, swap peak 247 MB, MemAvailable >= 1,110 MB).
  • Game-free on Windows (clang++ and g++, llvm-mingw) and in the Linux Ubuntu 22.04 build container (clang++ and g++): tests/test_linux_pressure.py, tests/test_commit_limit.py, tests/test_vram_compact.py, tests/test_retired_give_back.py, tests/test_mesh_fallback.py, tests/test_prepare_cache_save.py, tests/test_precompile_pipeline_cleanup.py, tests/test_pipeline_play_cache.py, tests/test_oot_wide_save_picture.py, tests/test_mesh_args_cleanup.py.
  • Full suite on Windows (python -I -B tests/run_all.py on 6931f715, rebased on main @ 7315957c): 488 passed, 6 failed, 34 skipped. All 6 failures also fail on main without this change (test_fpx_sticky, test_texture_staging_publication, test_display_pointer, test_check_third_party, test_spawn_emit, test_release).

Not tested:

  • Multi-area 12-lap tours on 16 GB Linux desktops with discrete AMD / NVIDIA / Intel GPUs (32+ GB Linux desktops are unaffected by default since both should_drop_modules and PressureHold stay idle above 20 GB RAM).
  • An in-play graphics pipeline compile for a precompiled program whose VkShaderModule was dropped at pipeline_cache_play() and remade via program_cache_complete_graphics() on a 16 GB desktop GPU with RX_VK_LOAD=start (tests/test_linux_pressure.py covers the exact C++ reload and failure paths).

Needs other testers:

  • Linux desktop with 16 GB RAM and a discrete GPU (AMD RADV or NVIDIA): a route run across Kokiri, Hyrule Field and Kakariko on default settings, expecting vk: programs: ... shader modules let go after the precompile at start-up and no out-of-memory kill or new pipeline hitches (request to open).

Screenshots

Nothing on screen.

Risk and rollback

  • Under heavy Linux memory pressure (MemAvailable < 1536 MB), PressureHold lowers the texture cache budget (with a 512 MB floor and 256 MB clear hysteresis) and unpins idle unmirrored guest-pool imports, which can cause extra texture streaming or re-imports on a 16 GB system that would otherwise be killed by earlyoom. RX_VK_AVAIL_RESERVE_MB=0 and RX_VK_COMMIT_RESERVE_MB=0 turn the Linux pressure checks off.
  • On <= 20 GB Linux systems, if a precompiled program needs a new pipeline state variant in play after its VkShaderModule was dropped at pipeline_cache_play(), program_cache_complete_graphics() re-reads its SPIR-V from longshot_programs.bin on the pipeline build worker (and marks the program failed if the read or vkCreateShaderModule fails). RX_VK_KEEP_MODULES=1 (or RX_VK_DROP_MODULES=0) keeps all VkShaderModule handles resident as before. Reverting the commit is enough.

Reviewer notes

  • Scope and overlap with other active work: This PR is scoped strictly to Linux / Steam Deck shared-memory pressure (gpu/vk/linux_pressure.h, post-precompile VkShaderModule release on <= 20 GB Linux systems) and is kept separate from d4nkn3ss's work on Windows play-time memory growth across 12-area laps (worklist 5.6, https://github.com/NotRexed/Hookshot/issues/993) and HumanBean's shader-preparation set changes (Compile shaders: All / Most / None, #158).
  • Compatibility with #225: precompile_run() sets p.graphics_built = true only on programs whose graphics pipelines were built before the title (wanted.count(p.key)). On main alone, that keeps un-noted mesh-fallback programs' vertex/fragment modules resident; when combined with #225 (where wanted is empty when graphics pipelines are left to first draw), program_cache_drop_modules() drops only the compute and mesh-fallback compute VkShaderModules and keeps all vertex and fragment VkShaderModules in memory so first draws in play build from memory without reading longshot_programs.bin.

Issues

See #91
See #54
See https://github.com/NotRexed/Hookshot/issues/993
See https://github.com/NotRexed/Hookshot/issues/1000

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • A row in docs/tested-configurations.md for each measured run.
  • A game-free test added for the fix, failing on main and passing here.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.
## Why On a 16 GB Steam Deck OLED, the game's guest memory (4.7-6.0 GB shmem), the renderer's anonymous heap (up to 8.9 GB on `main`) and kernel-pinned VMA blocks share one 16 GB pool while RADV's device-heap budget (~7.5 GB) reports VRAM under 50% used: loading into Kokiri Forest drove `MemAvailable` down to 354-390 MB with 2.8-4.6 GB in swap and terminated all 3 starts on `main`. This releases the precompile's shader modules on `<= 20 GB` RAM systems once their pipelines are built and gives back texture blocks, idle staging/capture buffers and idle guest-pool imports under Linux memory pressure before `earlyoom` or direct reclaim acts. ## What changed - linux pressure (`gpu/vk/linux_pressure.h`): reads `MemAvailable`, `SwapFree` and `MemTotal` from `/proc/meminfo` on Linux, computes the shortfall below `RX_VK_AVAIL_RESERVE_MB` (default `1536` MB) and `RX_VK_COMMIT_RESERVE_MB` (default `2560` MB, matching Windows' commit rule), gates post-precompile module release (`should_drop_modules`: on by default for `<= 20 GB` RAM or active pressure, off on 32-64 GB desktops), and provides `PressureHold` (cuts the texture budget from the texture total at pressure entry with a 512 MB floor, holds it without per-frame ratcheting while VMA compaction frees blocks, and leaves pressure only once both reserves clear by 256 MB). - textures (`texture_frame()` in `gpu/vk/vk_texture.cpp`): samples `/proc/meminfo` every 30 presents and stores the shortfall in `linux_shortfall_bytes` (so `imports_give_back()` reads the atomic instead of opening `/proc/meminfo` under `pools_mu`). While `PressureHold` is active, applies the held budget to `budget` and `rule_budget`, drains the render-target pool (`pool_pressed = true`), wakes VRAM compaction at its pressed thresholds (`compact.pressed = true`, 192 MB free space, 120-present rest), frees idle staging `arenas` on pressure entry and after 5 s idle (instead of every pressed frame), checks `malloc_trim(0)` every 600 presents only when the previous trim freed `>= 16 MB` or free arena bytes grew by `>= 64 MB`, and logs `vk: memory pressure on Linux:` at most every 600 frames. - guest imports & capture buffers (`cap_free_trim()`, `imports_give_back()` in `gpu/vk/vk_memory.cpp`): under Linux memory pressure (`linux_pressure_shortfall()`), trims spare capture buffers (`cap_free`) down to 32 MB and adds the shortfall (`linux_pressure::import_want(over, dead_import_bytes)`) to `imports_give_back()` so idle unmirrored guest-pool imports are unpinned even on `< 12 GB` system-heap budgets. - shader modules after precompile (`program_cache_read()`, `program_cache_drop_modules()`, `program_cache_complete_graphics()` in `gpu/vk/vk_progcache.cpp`; `precompile_run()`, `graphics_pipeline()`, `pipeline_cache_play()` in `gpu/vk/vk_draw.cpp`; `gpu/vk/vk_shader.h`): records each uncompressed record's `(record_at, record_size)` and each stage's `(code_at, code_words)` in `longshot_programs.bin` (no change to `LONGSHOT_SHADER_STAMP` or `kProgramCacheFormat`). On Linux with `<= 20 GB` RAM or active pressure (`should_drop_modules`), once `precompile_run()` finishes (`pipeline_cache_play()`), destroys the `VkShaderModule` handles of ready programs whose pipelines were built in the precompile (`modules_dropped = true`), remakes them on demand from `longshot_programs.bin` via `program_cache_complete_graphics()` (checking the record head at `record_at` and marking `p.failed = true` and leaving the pipeline unbuilt on any read or `vkCreateShaderModule` failure), and calls `malloc_trim(0)`. - tests (`tests/test_linux_pressure.py`, `tests/linux_pressure_test.cpp`): game-free source and compiled C++ checks for `/proc/meminfo` parsing (`MemAvailable`, `SwapFree`, `MemTotal`), `should_drop_modules`, `PressureHold` hysteresis/floor/no-ratchet, `import_want`, and `program_cache_drop_modules()` / `program_cache_complete_graphics()` (including failed `make_module` and stale record head checks). - docs (`docs/development.md`, `docs/tested-configurations.md`): documents `RX_VK_AVAIL_RESERVE_MB`, `RX_VK_DROP_MODULES` and `RX_VK_KEEP_MODULES`, updates `RX_VK_COMMIT_RESERVE_MB` for Linux, and adds the Steam Deck OLED rows. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `RX_VK_AVAIL_RESERVE_MB=<MB>` | Physical RAM (`MemAvailable` in `/proc/meminfo`, sampled every 30 presents) the renderer leaves free on Linux before trimming the texture budget (`PressureHold`), pools, staging/capture spares and idle imports; `0` turns the `MemAvailable` check off | 1536 (on, Linux only) | On by default: idle when `MemAvailable >= 1536 MB` | 16 GB Linux / SteamOS handhelds and PCs (Steam Deck) | Windows (does not read `/proc/meminfo`), 32+ GB Linux PCs with ample `MemAvailable` | Linux with NVIDIA / Intel GPUs | | `RX_VK_COMMIT_RESERVE_MB=<MB>` | Extended to Linux (`MemAvailable + SwapFree` in `/proc/meminfo`) alongside Windows (`ullAvailPageFile`); `0` turns the check off | 2560 (on) | On by default: idle when `MemAvailable + SwapFree >= 2560 MB` | Linux systems with small or nearly full swap/zram, Windows PCs near commit limit | Systems with ample RAM + swap | Linux with swap disabled | | `RX_VK_DROP_MODULES=0` or `1` | Controls whether `pipeline_cache_play()` lets go of ready programs' `VkShaderModule` handles after `precompile_run()` on Linux (`1` forces on any RAM size; `0` keeps them) | Auto: on for `<= 20 GB` RAM or active pressure, off on `> 20 GB` desktops | Auto by default: saves ~5.95 GB of anonymous memory on 16 GB Linux systems while leaving 32-64 GB desktops untouched | 16 GB Linux / SteamOS systems | Windows, 32-64 GB Linux desktops (unless `=1`) | — | | `RX_VK_KEEP_MODULES=1` | Alias for `RX_VK_DROP_MODULES=0`: keeps ready programs' `VkShaderModule` handles resident after `precompile_run()` on Linux | 0 | Off by default (`0`): for A/B testing | A/B testing only | 16 GB shared-memory handhelds | — | ## Measurements main: 08771847 (2026-11-13; this PR's base is 7315957c, a few commits newer with no memory changes) this PR: brett-d/legendary-broccoli @ 05f4c4aa (6931f715 with `docs/tested-configurations.md` updated) Hardware: Steam Deck OLED (16 GB shared RAM, 8 GB swap file, AMD Custom GPU 0932, RADV Mesa 26.2.0-devel, SteamOS 3.9.5, kernel 7.2.7-valve2, Gaming Mode 1280x720, stock clocks, mesh shaders on compute fallback) Scene: Kokiri Forest moving route; Quality preset Auto (this PC), each build in its own folder and caches, without the Steam Deck profile (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225) so both keep all 25,303 graphics pipelines ready (`RX_VK_LOAD=start`) Method: Full Linux build of host + renderer from source; memory sampled once a second from `/proc` (`RSS`, `RssAnon`, process `VmSwap`, `MemAvailable`, `SwapFree`) with the low-memory guard on (kills when `MemAvailable < 500 MB` for 5 s with `< 4 GB` `SwapFree`, or `< 250 MB` with `< 2 GB` `SwapFree`). One cold and one warm run on `this PR @ 05f4c4aa`; one cold and two warm runs on `main @ 08771847` (`main` was killed by the guard in all 3 starts before reaching a measured window, so real fps are not comparable; full report in https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/223#issuecomment-14958). How to reproduce: On a Steam Deck OLED in Gaming Mode (1280x720, stock clocks) with Quality preset Auto (this PC), run the Kokiri Forest moving route on a cold and a warm cache with `main @ 08771847` and `this PR @ 6931f715`. | Metric (Kokiri Forest moving route, warm start) | main @ 08771847 (2 warm runs) | this PR @ 05f4c4aa (1 warm run) | Δ | |---|---|---|---| | Outcome | Killed by memory guard at 2 min in both runs | Completed full route (0 kills, 0 crashes) | Survived | | Peak RSS | 10,738 / 10,742 MB | 8,094 MB | -2,646 MB (-2.58 GB) | | Peak anonymous memory (`RssAnon`) | 8,888 / 8,902 MB | **2,950 MB** | **-5,945 MB (-5.81 GB)** | | Process swap peak (`VmSwap`) | 2,818 / 3,475 MB | **947 MB** | -1,871 to -2,528 MB | | `MemAvailable` min (`SwapFree` then) | 390 / 354 MB (`< 4 GB` swap free -> killed) | 284 MB (**7.2 GB** swap free; 9 dips `< 500 MB`, all with `> 4 GB` swap free) | +3.7 to +4.4 GB swap free | | `VkShaderModule`s let go after precompile | 0 | 41,044 (47,797 program keys kept, start-up 8.2 s) | +41,044 | | `vk: memory pressure on Linux:` lines | — | 3 (`2,371 -> 1,465 -> 1,465 MB` texture budget; no ratcheting) | — | | Real / shown FPS (`p95`) | killed at 2 min (not reached) | 14.8 / 27.3 (`p95` 90 ms) | not comparable (`main` killed at 2 min) | | Metric (Kokiri Forest moving route, cold start) | main @ 08771847 (1 cold run) | this PR @ 05f4c4aa (1 cold run) | Δ | |---|---|---|---| | Outcome | Killed by memory guard at 6 min (14.0 fps before kill) | Completed full route (0 kills, 0 crashes) | Survived | | Peak RSS | 9,371 MB | 8,979 MB | -392 MB | | Peak anonymous memory (`RssAnon`) | 6,069 MB | 8,003 MB (finished route) | +1,934 MB | | Process swap peak (`VmSwap`) | 4,598 MB | 2,892 MB | -1,706 MB (-1.67 GB) | | `MemAvailable` min (`SwapFree` then) | 387 MB (`< 4 GB` swap free -> killed) | **610 MB** (**4.7 GB** swap free; 0 kills) | +223 MB | | `vk: memory pressure on Linux:` lines | — | 4 (`1,732 MB` x3, then recovered to `2,560 MB` once memory returned) | — | | Real / shown FPS (`p95`) | killed at 6 min | 14.4 / 27.0 (`p95` 96 ms) | not comparable (`main` killed at 6 min) | ``` vk: programs: 41044 shader modules let go after the precompile (RX_VK_KEEP_MODULES=1 keeps them) vk: memory pressure on Linux: 1238 MB available, 7348 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 2371 MB of their 2668 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB) vk: memory pressure on Linux: 779 MB available, 6995 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 1465 MB of their 2384 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB) vk: memory pressure on Linux: 1780 MB available, 5098 MB swap free (reserves 1536 / 2560 MB); the textures' budget down to 1465 MB of their 1917 MB (RX_VK_AVAIL_RESERVE_MB, RX_VK_COMMIT_RESERVE_MB) ``` ## Testing **Tested:** - In the game on a Steam Deck OLED (16 GB shared RAM, 8 GB swap, SteamOS 3.9.5, RADV Mesa 26.2.0-devel, mesh shaders on compute fallback): cold and warm moving-route comparison of `main @ 08771847` vs `this PR @ 05f4c4aa` (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/223#issuecomment-14958), confirming `41,044` shader modules let go on warm start (`RssAnon` down from `8.9 GB` to `2.95 GB`, swap down from `2.8-3.5 GB` to `0.95 GB`), `PressureHold` holding the texture budget without ratcheting (`2,371 -> 1,465 -> 1,465 MB` warm; `1,732 MB` x3 -> `2,560 MB` recovery cold), and 0 kills across both starts; plus a warm moving-route run combined with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225 (`5,028` compute/fallback modules let go while vertex/fragment modules stayed resident, `RssAnon` peak `2,496 MB`, swap peak `247 MB`, `MemAvailable >= 1,110 MB`). - Game-free on Windows (`clang++` and `g++`, llvm-mingw) and in the Linux Ubuntu 22.04 build container (`clang++` and `g++`): `tests/test_linux_pressure.py`, `tests/test_commit_limit.py`, `tests/test_vram_compact.py`, `tests/test_retired_give_back.py`, `tests/test_mesh_fallback.py`, `tests/test_prepare_cache_save.py`, `tests/test_precompile_pipeline_cleanup.py`, `tests/test_pipeline_play_cache.py`, `tests/test_oot_wide_save_picture.py`, `tests/test_mesh_args_cleanup.py`. - Full suite on Windows (`python -I -B tests/run_all.py` on `6931f715`, rebased on `main @ 7315957c`): 488 passed, 6 failed, 34 skipped. All 6 failures also fail on `main` without this change (`test_fpx_sticky`, `test_texture_staging_publication`, `test_display_pointer`, `test_check_third_party`, `test_spawn_emit`, `test_release`). **Not tested:** - Multi-area 12-lap tours on 16 GB Linux desktops with discrete AMD / NVIDIA / Intel GPUs (32+ GB Linux desktops are unaffected by default since both `should_drop_modules` and `PressureHold` stay idle above 20 GB RAM). - An in-play graphics pipeline compile for a precompiled program whose `VkShaderModule` was dropped at `pipeline_cache_play()` and remade via `program_cache_complete_graphics()` on a 16 GB desktop GPU with `RX_VK_LOAD=start` (`tests/test_linux_pressure.py` covers the exact C++ reload and failure paths). **Needs other testers:** - Linux desktop with 16 GB RAM and a discrete GPU (AMD RADV or NVIDIA): a route run across Kokiri, Hyrule Field and Kakariko on default settings, expecting `vk: programs: ... shader modules let go after the precompile` at start-up and no out-of-memory kill or new pipeline hitches (request to open). ## Screenshots Nothing on screen. ## Risk and rollback - Under heavy Linux memory pressure (`MemAvailable < 1536 MB`), `PressureHold` lowers the texture cache budget (with a 512 MB floor and 256 MB clear hysteresis) and unpins idle unmirrored guest-pool imports, which can cause extra texture streaming or re-imports on a 16 GB system that would otherwise be killed by `earlyoom`. `RX_VK_AVAIL_RESERVE_MB=0` and `RX_VK_COMMIT_RESERVE_MB=0` turn the Linux pressure checks off. - On `<= 20 GB` Linux systems, if a precompiled program needs a new pipeline state variant in play after its `VkShaderModule` was dropped at `pipeline_cache_play()`, `program_cache_complete_graphics()` re-reads its SPIR-V from `longshot_programs.bin` on the pipeline build worker (and marks the program failed if the read or `vkCreateShaderModule` fails). `RX_VK_KEEP_MODULES=1` (or `RX_VK_DROP_MODULES=0`) keeps all `VkShaderModule` handles resident as before. Reverting the commit is enough. ## Reviewer notes - **Scope and overlap with other active work:** This PR is scoped strictly to Linux / Steam Deck shared-memory pressure (`gpu/vk/linux_pressure.h`, post-precompile `VkShaderModule` release on `<= 20 GB` Linux systems) and is kept separate from `d4nkn3ss`'s work on Windows play-time memory growth across 12-area laps (worklist 5.6, https://github.com/NotRexed/Hookshot/issues/993) and `HumanBean`'s shader-preparation set changes (`Compile shaders: All / Most / None`, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/158). - **Compatibility with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225:** `precompile_run()` sets `p.graphics_built = true` only on programs whose graphics pipelines were built before the title (`wanted.count(p.key)`). On `main` alone, that keeps un-noted mesh-fallback programs' vertex/fragment modules resident; when combined with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225 (where `wanted` is empty when graphics pipelines are left to first draw), `program_cache_drop_modules()` drops only the compute and mesh-fallback compute `VkShaderModule`s and keeps all vertex and fragment `VkShaderModule`s in memory so first draws in play build from memory without reading `longshot_programs.bin`. ## Issues See https://git.magicbean.dev/MagicBeanDev/Longshot/issues/91 See https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/54 See https://github.com/NotRexed/Hookshot/issues/993 See https://github.com/NotRexed/Hookshot/issues/1000 ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated.
fix(vk): give memory back under Linux memory pressure and after the precompile
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 21s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
5e2d92f8b6
On a 16 GB Steam Deck OLED (SteamOS, earlyoom on), guest shmem (4.7-6.0 GB),
renderer anonymous memory (up to 8.7 GB on main) and kernel-pinned VMA blocks
share one 16 GB memory pool while RADV's device-heap budget (~7.5 GB) stays
under 50% used. On Longshot main (39647e1a), loading into Kokiri Forest pushed
RSS to 10.3 GB (anon 8.7 GB, private 7.2 GB) and MemAvailable down to 277 MB
(17 dips under 500 MB with 2.7 GB swap free), triggering the low-memory guard /
earlyoom.

- linux_pressure.h: reads MemAvailable and SwapFree from /proc/meminfo on Linux
  and computes the shortfall below RX_VK_AVAIL_RESERVE_MB (default 1536 MB) and
  RX_VK_COMMIT_RESERVE_MB (default 2560 MB).
- vk_texture.cpp: when Linux memory pressure is active, lowers budget and
  rule_budget by the shortfall, drains the render-target pool (pool_pressed),
  runs VRAM compaction at its pressed thresholds (compact.pressed), frees idle
  staging arenas and checks malloc_trim every 120 presents (at >= 64 MB free).
- vk_memory.cpp: under Linux memory pressure, trims spare capture buffers to
  32 MB and adds the shortfall to imports_give_back() so idle unmirrored guest
  pool imports are unpinned even on < 12 GB system-heap budgets.
- vk_progcache.cpp, vk_draw.cpp: records each uncompressed stage's SPIR-V offset
  in longshot_programs.bin and, on Linux in pipeline_cache_play() after the
  start-up precompile finishes, lets go of ready programs' VkShaderModules
  whose pipelines were built in the precompile (remade on demand by
  program_cache_complete_graphics if play builds a new pipeline variant, while
  programs without a built graphics pipeline under RX_VK_PRECOMPILE_GRAPHICS=none
  keep their vertex and fragment modules for their first draw in play), keeps
  pcache intact so in-play builds hit the start's cache and RADV's on-disk
  cache, and calls malloc_trim(0).
- tests: adds tests/test_linux_pressure.py and tests/linux_pressure_test.cpp.
- docs: adds the switches to docs/development.md and the Steam Deck OLED runs
  to docs/tested-configurations.md.

Measured on Steam Deck OLED (16 GB shared, SteamOS, Kokiri moving route,
Quality preset Auto (this PC), warm caches, main @ 39647e1a vs this PR @
84f93b55):
- Peak RSS: 10,343 MB (main) -> 8,403 MB (this PR, -1.94 GB)
- Anon peak: 8,701 MB (main) -> 6,065 MB (this PR, -2.64 GB)
- Working set in Kokiri (vk: memory:): 10,073 MB / private 7,220 MB (main) ->
  4,450 MB / private 600 MB (this PR, 39,048 shader modules let go)
- Process swap peak: 2,860 MB (main) -> 914 MB (this PR, -1.95 GB)
- MemAvailable minimum: 277 MB, 17 dips < 500 MB (main, killed by guard ~3 min
  in) -> 854 MB, 0 dips < 500 MB, 6,487 MB swap free (this PR, completed route
  at 15.6 real / 29.6 shown fps, p95 68 ms).

Note on scope: keeps Linux/Deck memory pressure separate from Windows play-time
memory growth (d4nkn3ss, #993) and from shader-preparation set changes
(HumanBean, #158).

Co-authored-by: Copilot App <[email protected]>
HumanBean left a comment

Thanks. The Deck numbers are convincing: the guard killed main and this branch got through the route. I read the code only, without building or running it. Two things need changing before this leaves WIP, and a few smaller ones are below.

Needs changing

  1. A failed remake still goes on to build the pipeline. program_cache_complete_graphics() can return false: the read from longshot_programs.bin fails, or vkCreateShaderModule fails, which is most likely exactly when memory is short. All three callers in graphics_pipeline() (sync, GPL, worker job) ignore the result, so build_graphics() passes VK_NULL_HANDLE as a stage module. That is invalid usage and can crash the driver. program_cache_complete() marks the program failed so its draws are left out; this path needs to do the same (inline comments).
  2. The module drop runs on every Linux PC, not only under memory pressure. pipeline_cache_play() drops the modules unless RX_VK_KEEP_MODULES=1 is set, whatever the RAM. On a 32-64 GB desktop, every new pipeline variant of a precompiled program in play now pays a disk read and a vkCreateShaderModule, to save memory that machine doesn't need. The title says "under Linux memory pressure", and the PR lists desktops as untested. Please either gate the drop (on total RAM, on the handheld profile from #225, or on pressure), or measure in-play hitches on a desktop and say so in the PR.

Worth a look

  • The texture pressure rule has no hysteresis and no floor. Each frame the shortfall comes off the current total, and memory goes back to the OS late (only when compaction empties a block). It can keep cutting while MemAvailable hasn't moved yet, then return to the full budget as soon as MemAvailable crosses the line. Idle staging arenas are also freed every pressed frame and made again at the next load. Entering at the reserve and leaving at the reserve plus a margin would avoid the flapping.
  • /proc/meminfo is read every present in texture_frame(), and again every 16 submissions inside imports_give_back() with pools_mu held. One reading per N frames that both of them use would be cheaper, and would keep file I/O out from under pools_mu.
  • The code comments, the docs and the tests describe RX_VK_PRECOMPILE_GRAPHICS=none, which exists only in #225, not in main or here. Please say in the PR that this depends on it, or word those comments so they hold without it.
  • PR description: "See #993 / See #1000" link to Longshot's own numbers here. AGENTS.md asks for full URLs. The Measured box is rightly unticked: it still needs at least 2 interleaved runs of each (main was killed, so its fps aren't comparable).

Checked and fine: Windows behaves as before. read_meminfo returns false off Linux, imports_give_back stays off by default there, and the drop is #ifdef __linux__. With ample MemAvailable the pressure checks do nothing. code_at points at the right SPIR-V bytes for uncompressed records. The capture spares, idle staging arenas and idle imports that get freed are all ones the GPU has finished with, the same as before.

Context: #91, #54.

Thanks. The Deck numbers are convincing: the guard killed `main` and this branch got through the route. I read the code only, without building or running it. Two things need changing before this leaves WIP, and a few smaller ones are below. **Needs changing** 1. **A failed remake still goes on to build the pipeline.** `program_cache_complete_graphics()` can return false: the read from `longshot_programs.bin` fails, or `vkCreateShaderModule` fails, which is most likely exactly when memory is short. All three callers in `graphics_pipeline()` (sync, GPL, worker job) ignore the result, so `build_graphics()` passes `VK_NULL_HANDLE` as a stage module. That is invalid usage and can crash the driver. `program_cache_complete()` marks the program failed so its draws are left out; this path needs to do the same (inline comments). 2. **The module drop runs on every Linux PC, not only under memory pressure.** `pipeline_cache_play()` drops the modules unless `RX_VK_KEEP_MODULES=1` is set, whatever the RAM. On a 32-64 GB desktop, every new pipeline variant of a precompiled program in play now pays a disk read and a `vkCreateShaderModule`, to save memory that machine doesn't need. The title says "under Linux memory pressure", and the PR lists desktops as untested. Please either gate the drop (on total RAM, on the handheld profile from https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225, or on pressure), or measure in-play hitches on a desktop and say so in the PR. **Worth a look** - The texture pressure rule has no hysteresis and no floor. Each frame the shortfall comes off the *current* total, and memory goes back to the OS late (only when compaction empties a block). It can keep cutting while `MemAvailable` hasn't moved yet, then return to the full budget as soon as `MemAvailable` crosses the line. Idle staging arenas are also freed every pressed frame and made again at the next load. Entering at the reserve and leaving at the reserve plus a margin would avoid the flapping. - `/proc/meminfo` is read every present in `texture_frame()`, and again every 16 submissions inside `imports_give_back()` with `pools_mu` held. One reading per N frames that both of them use would be cheaper, and would keep file I/O out from under `pools_mu`. - The code comments, the docs and the tests describe `RX_VK_PRECOMPILE_GRAPHICS=none`, which exists only in https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225, not in `main` or here. Please say in the PR that this depends on it, or word those comments so they hold without it. - PR description: "See #993 / See #1000" link to Longshot's own numbers here. AGENTS.md asks for full URLs. The *Measured* box is rightly unticked: it still needs at least 2 interleaved runs of each (main was killed, so its fps aren't comparable). Checked and fine: Windows behaves as before. `read_meminfo` returns false off Linux, `imports_give_back` stays off by default there, and the drop is `#ifdef __linux__`. With ample `MemAvailable` the pressure checks do nothing. `code_at` points at the right SPIR-V bytes for uncompressed records. The capture spares, idle staging arenas and idle imports that get freed are all ones the GPU has finished with, the same as before. Context: https://git.magicbean.dev/MagicBeanDev/Longshot/issues/91, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/54.
@ -2249,6 +2254,7 @@ PipeEntry* graphics_pipeline(const Program& p, const NvnState& s, const Targets&
job->predicted = predicted;
jobs_push([job] {
// (over a linked one, which is left alone: draws recorded with it may still be on their way)
program_cache_complete_graphics(*job->p);
Owner

The return value is ignored here and at lines 2224 and 2233. When the SPIR-V read or make_module fails, build_graphics() runs with VK_NULL_HANDLE stage modules, which is invalid usage and can crash the driver. On false, leave the entry not ready and the program failed, as program_cache_complete() does for dispatches, instead of building.

The return value is ignored here and at lines 2224 and 2233. When the SPIR-V read or `make_module` fails, `build_graphics()` runs with `VK_NULL_HANDLE` stage modules, which is invalid usage and can crash the driver. On false, leave the entry not ready and the program failed, as `program_cache_complete()` does for dispatches, instead of building.
@ -9742,0 +9755,4 @@
// programs without a built graphics pipeline (!graphics_built, as under RX_VK_PRECOMPILE_GRAPHICS=none) keep their vertex
// and fragment modules so in-play pipeline builds do not read the disk. pcache is kept so in-play builds still hit the
// start's pipeline cache and RADV's disk cache without copying every pipeline into play_cache. RX_VK_KEEP_MODULES=1 keeps all
if (!env_on("LONGSHOT_PREPARE_ONLY") && !prepare_left_workers()) {
Owner

This runs on every Linux machine, whatever its RAM, so a 64 GB desktop also pays a disk read and a module creation for each new pipeline variant in play. Please gate it (total RAM, the Deck profile, or pressure) or measure in-play hitches on a desktop. As it stands it doesn't match the title ("under Linux memory pressure").

This runs on every Linux machine, whatever its RAM, so a 64 GB desktop also pays a disk read and a module creation for each new pipeline variant in play. Please gate it (total RAM, the Deck profile, or pressure) or measure in-play hitches on a desktop. As it stands it doesn't match the title ("under Linux memory pressure").
@ -841,0 +849,4 @@
for (const Stage& st : prog.stages) {
if (prog.fallback && (st.vk_stage & (VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT)))
continue;
if (!st.module && st.code_words) need = true;
Owner

st.module is read here without light_mu, while another worker building a different variant of the same program may be writing it under the lock below. That is a data race, harmless on x86 and ARM in practice but UB. Take the lock for the check, or make the check cheap some other way (an atomic flag on the Program that the drop sets).

`st.module` is read here without `light_mu`, while another worker building a different variant of the same program may be writing it under the lock below. That is a data race, harmless on x86 and ARM in practice but UB. Take the lock for the check, or make the check cheap some other way (an atomic flag on the Program that the drop sets).
@ -841,0 +866,4 @@
ok = fread(spirv.data(), 4, spirv.size(), disk_in) == spirv.size();
}
if (ok) st.module = make_module(spirv);
if (!st.module) return false;
Owner

This returns false without marking p.failed, so the next draw tries again. Under memory pressure that is a disk read and a module creation on every attempt. Also, nothing checks that the bytes at code_at are still this program's record: program_cache_read checks the record head against the key. A cheap check of the head at code_at would turn a stale offset into a clean failure instead of a module made from wrong bytes.

This returns false without marking `p.failed`, so the next draw tries again. Under memory pressure that is a disk read and a module creation on every attempt. Also, nothing checks that the bytes at `code_at` are still this program's record: `program_cache_read` checks the record head against the key. A cheap check of the head at `code_at` would turn a stale offset into a clean failure instead of a module made from wrong bytes.
@ -4156,2 +4165,2 @@
if (frame_no % 1800 == 0 && !busy.exchange(true))
jobs_push([] {
const bool log_malloc = frame_no % 600 == 0 && frame_no % 1800 == 0;
const bool check_malloc = log_malloc || (linux_pressed && frame_no % 120 == 0);
Owner

Under sustained pressure this calls malloc_trim(0) every 120 presents once 64 MB is free. glibc locks each arena while it trims (your log shows 47 ms), so allocating threads can stall every 2 s. Worth a minimum interval or a check that the last trim actually gave memory back. Nit: frame_no % 600 == 0 && frame_no % 1800 == 0 on line 4165 is just % 1800.

Under sustained pressure this calls `malloc_trim(0)` every 120 presents once 64 MB is free. glibc locks each arena while it trims (your log shows 47 ms), so allocating threads can stall every 2 s. Worth a minimum interval or a check that the last trim actually gave memory back. Nit: `frame_no % 600 == 0 && frame_no % 1800 == 0` on line 4165 is just `% 1800`.
@ -4383,0 +4404,4 @@
{
uint64_t avail_bytes = 0, swap_free_bytes = 0;
const uint64_t avail_res = linux_avail_reserve(), commit_res = commit_reserve();
if ((avail_res || commit_res) && linux_pressure::read_meminfo(&avail_bytes, &swap_free_bytes)) {
Owner

This opens and parses /proc/meminfo on every present, and imports_give_back() reads it again with pools_mu held. Read it once every ~30 frames here and keep the result in an atomic that imports_give_back() reads, so there is no file I/O under pools_mu.

This opens and parses `/proc/meminfo` on every present, and `imports_give_back()` reads it again with `pools_mu` held. Read it once every ~30 frames here and keep the result in an atomic that `imports_give_back()` reads, so there is no file I/O under `pools_mu`.
@ -4383,0 +4408,4 @@
const uint64_t over = linux_pressure::shortfall(avail_bytes, swap_free_bytes, avail_res, commit_res);
linux_pressed = over != 0;
if (over) {
budget = std::min(budget, total_bytes > over ? total_bytes - over : 0);
Owner

There is no hysteresis and no floor here. The shortfall comes off the current total every frame, and memory only goes back to the OS when compaction frees a whole block, so this can keep cutting while MemAvailable hasn't caught up yet. It then goes straight back to the full budget once MemAvailable crosses the reserve. Suggest leaving pressure only at the reserve plus a margin (say 256 MB) and holding the budget taken at the start of pressure, rather than recomputing it from total_bytes each frame.

There is no hysteresis and no floor here. The shortfall comes off the current total every frame, and memory only goes back to the OS when compaction frees a whole block, so this can keep cutting while `MemAvailable` hasn't caught up yet. It then goes straight back to the full budget once `MemAvailable` crosses the reserve. Suggest leaving pressure only at the reserve plus a margin (say 256 MB) and holding the budget taken at the start of pressure, rather than recomputing it from `total_bytes` each frame.
dukenheimer force-pushed brett-d/legendary-broccoli from 5e2d92f8b6
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 21s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 1e709d9b2d
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 24s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
2026-11-13 16:43:23 +00:00
Compare
Author
First-time contributor

Thanks for the review! Addressed every point in 6f6c631c (rebased onto main @ 9f64464b):

  1. Failed program_cache_complete_graphics() remake in graphics_pipeline() (gpu/vk/vk_draw.cpp, gpu/vk/vk_progcache.cpp):

    • program_cache_complete_graphics() now sets p.failed = true and logs on failure (matching program_cache_complete()), so subsequent draws skip the failed program immediately instead of retrying disk reads and vkCreateShaderModule on every draw.
    • All three callers in graphics_pipeline() (sync, gpl_wanted, and the worker lambda) now check the return value of program_cache_complete_graphics() and, on false, mark e->ready.store(true) with e->pipeline == VK_NULL_HANDLE and return without calling build_graphics() or gpl_link().
    • Also added Program::record_at and Program::record_size and verified the 16-byte record header (head[0] == p.key && head[1] == p.record_size) at record_at - head_size before reading st.code_at, turning any stale offset into a clean failure.
    • Covered in tests/linux_pressure_test.cpp (both make_module failure and a stale record_at key mismatch) and tests/test_linux_pressure.py.
  2. Lock-free fast path in program_cache_complete_graphics() without data race on st.module (gpu/vk/vk_shader.h, gpu/vk/vk_progcache.cpp):

    • Added std::atomic<bool> modules_dropped{false} on Program, set with memory_order_release by program_cache_drop_modules() and cleared with memory_order_release once program_cache_complete_graphics() finishes under light_mu. program_cache_complete_graphics() now checks prog.modules_dropped.load(std::memory_order_acquire) before taking light_mu instead of reading st.module outside the lock.
  3. Gate post-precompile VkShaderModule drop so 32-64 GB Linux desktops are untouched (gpu/vk/linux_pressure.h, gpu/vk/vk_draw.cpp):

    • Added linux_pressure::should_drop_modules(total_ram_bytes, current_shortfall, keep_env, drop_env): pipeline_cache_play() now drops modules by default only when physical RAM (MemTotal from /proc/meminfo) is <= 20 GB (kModuleDropMaxRam, covering 16 GB handhelds and PCs) or when shortfall > 0 at start-up. 32-64 GB Linux desktops keep their modules by default (RX_VK_DROP_MODULES=1 forces the drop on any RAM size; RX_VK_KEEP_MODULES=1 or RX_VK_DROP_MODULES=0 keeps them everywhere).
    • Also gated the malloc_trim(0) call in pipeline_cache_play() on if (dropped_modules).
  4. Hysteresis and floor for the texture budget hold, and staging-arena flapping (gpu/vk/linux_pressure.h, gpu/vk/vk_texture.cpp):

    • Added linux_pressure::PressureHold: on entering pressure, captures baseline_total = total_bytes and holds held_budget = max(512 MB floor, baseline_total - over) rather than subtracting over from the already-shrinking total_bytes on every frame while VMA compaction is still emptying blocks. If MemAvailable drops deeper while active (over > last_over), held_budget steps down from baseline_total; it leaves pressure only once both MemAvailable and MemAvailable + SwapFree clear their reserves by kClearMargin (256 MB).
    • Idle staging arenas are now freed once when entering pressure (linux_entered_pressure) and after 5 s of inactivity while under pressure (wait_s = 5), rather than on every pressed frame.
  5. Shared /proc/meminfo sampling every 30 frames, outside pools_mu (gpu/vk/vk_texture.cpp, gpu/vk/vk_memory.cpp, gpu/vk/vk_draw.h):

    • texture_frame() now reads /proc/meminfo once every 30 presents and stores the shortfall in std::atomic<uint64_t> linux_shortfall_bytes (linux_pressure_shortfall()). imports_give_back() reads linux_pressure_shortfall() directly, performing zero file I/O under pools_mu.
  6. malloc_trim(0) frequency and effectiveness check (gpu/vk/vk_texture.cpp):

    • Simplified log_malloc to frame_no % 1800 == 0.
    • Under pressure, checks malloc_trim at most every 600 presents (~10-40 s instead of 120 presents), and skips the call when the previous trim freed < 16 MB of private memory unless free arena bytes (fordblks) have grown by at least 64 MB since that trim.
  7. Comments and PR description links (gpu/vk/vk_progcache.cpp, gpu/vk/vk_shader.h, docs/development.md, PR description):

Thanks for the review! Addressed every point in `6f6c631c` (rebased onto `main @ 9f64464b`): 1. **Failed `program_cache_complete_graphics()` remake in `graphics_pipeline()` (`gpu/vk/vk_draw.cpp`, `gpu/vk/vk_progcache.cpp`):** - `program_cache_complete_graphics()` now sets `p.failed = true` and logs on failure (matching `program_cache_complete()`), so subsequent draws skip the failed program immediately instead of retrying disk reads and `vkCreateShaderModule` on every draw. - All three callers in `graphics_pipeline()` (`sync`, `gpl_wanted`, and the worker lambda) now check the return value of `program_cache_complete_graphics()` and, on `false`, mark `e->ready.store(true)` with `e->pipeline == VK_NULL_HANDLE` and return without calling `build_graphics()` or `gpl_link()`. - Also added `Program::record_at` and `Program::record_size` and verified the 16-byte record header (`head[0] == p.key && head[1] == p.record_size`) at `record_at - head_size` before reading `st.code_at`, turning any stale offset into a clean failure. - Covered in `tests/linux_pressure_test.cpp` (both `make_module` failure and a stale `record_at` key mismatch) and `tests/test_linux_pressure.py`. 2. **Lock-free fast path in `program_cache_complete_graphics()` without data race on `st.module` (`gpu/vk/vk_shader.h`, `gpu/vk/vk_progcache.cpp`):** - Added `std::atomic<bool> modules_dropped{false}` on `Program`, set with `memory_order_release` by `program_cache_drop_modules()` and cleared with `memory_order_release` once `program_cache_complete_graphics()` finishes under `light_mu`. `program_cache_complete_graphics()` now checks `prog.modules_dropped.load(std::memory_order_acquire)` before taking `light_mu` instead of reading `st.module` outside the lock. 3. **Gate post-precompile `VkShaderModule` drop so 32-64 GB Linux desktops are untouched (`gpu/vk/linux_pressure.h`, `gpu/vk/vk_draw.cpp`):** - Added `linux_pressure::should_drop_modules(total_ram_bytes, current_shortfall, keep_env, drop_env)`: `pipeline_cache_play()` now drops modules by default **only** when physical RAM (`MemTotal` from `/proc/meminfo`) is `<= 20 GB` (`kModuleDropMaxRam`, covering 16 GB handhelds and PCs) or when `shortfall > 0` at start-up. 32-64 GB Linux desktops keep their modules by default (`RX_VK_DROP_MODULES=1` forces the drop on any RAM size; `RX_VK_KEEP_MODULES=1` or `RX_VK_DROP_MODULES=0` keeps them everywhere). - Also gated the `malloc_trim(0)` call in `pipeline_cache_play()` on `if (dropped_modules)`. 4. **Hysteresis and floor for the texture budget hold, and staging-arena flapping (`gpu/vk/linux_pressure.h`, `gpu/vk/vk_texture.cpp`):** - Added `linux_pressure::PressureHold`: on entering pressure, captures `baseline_total = total_bytes` and holds `held_budget = max(512 MB floor, baseline_total - over)` rather than subtracting `over` from the already-shrinking `total_bytes` on every frame while VMA compaction is still emptying blocks. If `MemAvailable` drops deeper while active (`over > last_over`), `held_budget` steps down from `baseline_total`; it leaves pressure only once both `MemAvailable` and `MemAvailable + SwapFree` clear their reserves by `kClearMargin` (`256 MB`). - Idle staging arenas are now freed once when entering pressure (`linux_entered_pressure`) and after 5 s of inactivity while under pressure (`wait_s = 5`), rather than on every pressed frame. 5. **Shared `/proc/meminfo` sampling every 30 frames, outside `pools_mu` (`gpu/vk/vk_texture.cpp`, `gpu/vk/vk_memory.cpp`, `gpu/vk/vk_draw.h`):** - `texture_frame()` now reads `/proc/meminfo` once every 30 presents and stores the shortfall in `std::atomic<uint64_t> linux_shortfall_bytes` (`linux_pressure_shortfall()`). `imports_give_back()` reads `linux_pressure_shortfall()` directly, performing zero file I/O under `pools_mu`. 6. **`malloc_trim(0)` frequency and effectiveness check (`gpu/vk/vk_texture.cpp`):** - Simplified `log_malloc` to `frame_no % 1800 == 0`. - Under pressure, checks `malloc_trim` at most every `600` presents (~10-40 s instead of 120 presents), and skips the call when the previous trim freed `< 16 MB` of private memory unless free arena bytes (`fordblks`) have grown by at least `64 MB` since that trim. 7. **Comments and PR description links (`gpu/vk/vk_progcache.cpp`, `gpu/vk/vk_shader.h`, `docs/development.md`, PR description):** - Reworded the source comments and `docs/development.md` so they stand on their own on `main` without assuming `RX_VK_PRECOMPILE_GRAPHICS=none` from https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225, and updated the PR description to use full URLs for https://git.magicbean.dev/MagicBeanDev/Longshot/issues/91, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/54, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/158, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/225, https://github.com/NotRexed/Hookshot/issues/993, and https://github.com/NotRexed/Hookshot/issues/1000.
dukenheimer force-pushed brett-d/legendary-broccoli from 1e709d9b2d
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 24s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to abe9e348ad
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 30s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2026-11-13 16:44:19 +00:00
Compare
HumanBean added this to the v3.0.0 milestone 2026-11-13 16:53:10 +00:00
dukenheimer force-pushed brett-d/legendary-broccoli from abe9e348ad
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 30s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 05f4c4aafb
Some checks failed
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2026-11-13 16:53:12 +00:00
Compare
Author
First-time contributor

Steam Deck: 05f4c4a completes where main is killed for running out of memory.

Setup: Steam Deck OLED (16 GB, 8 GB swap file), SteamOS 3.9.5, kernel 7.2.7-valve2, RADV Mesa 26.2.0-devel. Programs load at the start: the GPU draws mesh programs through the compute fallback. Each build in its own folder and caches, the user's options (Quality preset Auto (this PC)), stock clocks, Gaming Mode 1280x720. The same moving Kokiri Forest route each time.

  • Memory guard: the game is killed if MemAvailable stays under 500 MB for 5 s with under 4 GB of swap free, or drops under 250 MB with under 2 GB free.
  • Sampling: memory once a second from /proc (RSS, anon, the process's VmSwap, MemAvailable, SwapFree).
  • Builds: this PR as pushed (05f4c4a on main 7315957) and main 0877184, both without the Steam Deck profile (#225), so both keep every graphics pipeline ready. The two mains differ by a few commits. Neither touches memory.
build start completed real fps shown fps p95 peak RSS peak anon process swap MemAvailable min (swap free then)
main 0877184 cold killed at 6 min (14.0 before) 9371 MB 6069 MB 4598 MB 387 MB
main 0877184 warm killed at 2 min 10738 MB 8888 MB 2818 MB 390 MB
main 0877184 warm killed at 2 min 10742 MB 8902 MB 3475 MB 354 MB
#223 05f4c4a cold yes 14.4 27.0 96 ms 8979 MB 8003 MB 2892 MB 610 MB (4.7 GB free)
#223 05f4c4a warm yes 14.8 27.3 90 ms 8094 MB 2950 MB 947 MB 284 MB (7.2 GB free)

The warm start keeps far less: vk: programs: 41044 shader modules let go after the precompile, 47797 program keys kept, start-up 8.2 s. Anon falls from main's 8.9 GB to 2.95 GB.

vk: memory pressure on Linux: lines:

  • Cold, 4 lines. Available / swap free → the textures' budget:
    • 552 MB / 5304 MB → 1732 of 2715 MB
    • 1355 MB / 3108 MB → 1732 of 2375 MB
    • 1497 MB / 2020 MB → 1732 of 2340 MB
    • 2277 MB / 419 MB → 2560 of 2866 MB
  • Warm, 3 lines:
    • 1238 MB / 7348 MB → 2371 of 2668 MB
    • 779 MB / 6995 MB → 1465 of 2384 MB
    • 1780 MB / 5098 MB → 1465 of 1917 MB
  • Reserves 1536 / 2560 MB in every line.
  • The budget never ratchets below its earlier low, never comes near the 512 MB floor, and comes back up once memory returns (cold: 1732 → 2560).

Not covered:

  • One warm run only, and main's runs were on 0877184, not 7315957.
  • The guard dipped 9 times in the warm run, all with over 4 GB of swap free, so none was a kill.
  • Real fps can't be compared: main never reached a measured window. It's in line with #225's runs on the Deck (13.6-15.5).
  • On the Deck this PR stacks with #225, which keeps no graphics pipelines ready (RSS 7.4-8.3 GB, no kills).
**Steam Deck: 05f4c4a completes where main is killed for running out of memory.** **Setup:** Steam Deck OLED (16 GB, 8 GB swap file), SteamOS 3.9.5, kernel 7.2.7-valve2, RADV Mesa 26.2.0-devel. Programs load at the start: the GPU draws mesh programs through the compute fallback. Each build in its own folder and caches, the user's options (Quality preset Auto (this PC)), stock clocks, Gaming Mode 1280x720. The same moving Kokiri Forest route each time. - **Memory guard:** the game is killed if MemAvailable stays under 500 MB for 5 s with under 4 GB of swap free, or drops under 250 MB with under 2 GB free. - **Sampling:** memory once a second from /proc (RSS, anon, the process's VmSwap, MemAvailable, SwapFree). - **Builds:** this PR as pushed (05f4c4a on main 7315957) and main 0877184, both without the Steam Deck profile (#225), so both keep every graphics pipeline ready. The two mains differ by a few commits. Neither touches memory. | build | start | completed | real fps | shown fps | p95 | peak RSS | peak anon | process swap | MemAvailable min (swap free then) | |---|---|---|---|---|---|---|---|---|---| | main 0877184 | cold | killed at 6 min | (14.0 before) | | | 9371 MB | 6069 MB | 4598 MB | 387 MB | | main 0877184 | warm | killed at 2 min | | | | 10738 MB | 8888 MB | 2818 MB | 390 MB | | main 0877184 | warm | killed at 2 min | | | | 10742 MB | 8902 MB | 3475 MB | 354 MB | | **#223 05f4c4a** | cold | **yes** | 14.4 | 27.0 | 96 ms | 8979 MB | 8003 MB | 2892 MB | 610 MB (4.7 GB free) | | **#223 05f4c4a** | warm | **yes** | 14.8 | 27.3 | 90 ms | 8094 MB | **2950 MB** | **947 MB** | 284 MB (7.2 GB free) | The warm start keeps far less: `vk: programs: 41044 shader modules let go after the precompile`, 47797 program keys kept, start-up 8.2 s. Anon falls from main's 8.9 GB to 2.95 GB. **`vk: memory pressure on Linux:` lines:** - Cold, 4 lines. Available / swap free → the textures' budget: - 552 MB / 5304 MB → 1732 of 2715 MB - 1355 MB / 3108 MB → 1732 of 2375 MB - 1497 MB / 2020 MB → 1732 of 2340 MB - 2277 MB / 419 MB → 2560 of 2866 MB - Warm, 3 lines: - 1238 MB / 7348 MB → 2371 of 2668 MB - 779 MB / 6995 MB → 1465 of 2384 MB - 1780 MB / 5098 MB → 1465 of 1917 MB - Reserves 1536 / 2560 MB in every line. - The budget never ratchets below its earlier low, never comes near the 512 MB floor, and comes back up once memory returns (cold: 1732 → 2560). **Not covered:** - One warm run only, and main's runs were on 0877184, not 7315957. - The guard dipped 9 times in the warm run, all with over 4 GB of swap free, so none was a kill. - Real fps can't be compared: main never reached a measured window. It's in line with #225's runs on the Deck (13.6-15.5). - On the Deck this PR stacks with #225, which keeps no graphics pipelines ready (RSS 7.4-8.3 GB, no kills).
dukenheimer force-pushed brett-d/legendary-broccoli from 05f4c4aafb
Some checks failed
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 6931f7155b
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2026-11-13 19:45:22 +00:00
Compare
dukenheimer force-pushed brett-d/legendary-broccoli from 6931f7155b
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to ebaad175e1
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
2026-11-13 19:57:49 +00:00
Compare
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
This pull request is marked as a work in progress.
Some workflows are waiting to be reviewed.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'brett-d/legendary-broccoli:dukenheimer-brett-d/legendary-broccoli'
git switch 'dukenheimer-brett-d/legendary-broccoli'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!223
No description provided.