fix(vk): the page files' room to grow counts before the commit rule cuts the textures #346

Merged
HumanBean merged 1 commit from jarynclouatre/Longshot:fix/commit-room-growing-page-file into main 2026-11-15 18:03:11 +00:00
Contributor

Type: fix · Risk: medium · Tested on: Windows 11, RTX 4070 Ti SUPER; Linux x86-64 (game-free checks) · Needs: none

Why

Issue 336 reports brief freezes and black textures when opening the inventory menu or the list to switch items.

The issue's log shows the commit rule of #77 cutting the texture cache for about 90 minutes on a PC that was never at Windows' commit limit:

  • boot: 31849 MB of memory, 2048 MB of swap: a commit limit of 33.9 GB at the start.
  • 487 vk: commit limit near: lines (one every 600 frames at most) between presents 98,400 and 422,280, with 308 to 2,556 MB of commit left.
  • The game's own commit (private in the vk: memory: line) plus the commit left rose from 24.3 GB to 36.1 GB over those lines. That is above the 33.9 GB limit of the start with every other program counted as nothing, so the limit itself grew. No mem: commit ... at the commit limit line in the log: Windows refused no commit.
  • In that time the budget count of the textures retired lines adds up to 292,526 textures. 473 of the 707 VRAM: lines show the textures' budget cut, to between 0 and 4,180 MB (11,000 MB otherwise), with 6.8 to 10.7 GB used of 15.2 GB allowed. After present 246,720 three evictions in a row (416, 361 and 56 textures) took the cache from 2,378 MB to 502 MB.

The rule reads GlobalMemoryStatusEx's ullAvailPageFile: what is left under the limit as it is now, RAM plus the page files at their size of the moment. A page file Windows manages (the default, and what the game's own low-memory notice asks for: "let it manage the page file's size") grows when the commit nears the limit, and the limit with it. So on such a PC the reading stays small for as long as the game's commit grows, and the rule takes textures away that are then made again.

How to reproduce

Seen on: issue 336: RTX 5070 Ti, Ryzen 7 9800X3D, 32 GB, Windows, v3.0.0-rc.3.
Settings and switches: a page file Windows manages (the default), with room to grow.
Steps: Open the inventory menu or the list to switch items.
Expected: The texture cache is not cut merely because the current commit limit can still grow.
Actual: Issue 336: "Sometimes when I open my inventory menu or open the list to switch my items the game freezes for a second and the textures go black. It then goes back to normal."

What changed

The texture cache's commit rule counts how much the page files can still grow, limited by their maximums and free drive space. Fixed files, full drives and failed queries keep the old behavior.

  • vk (gpu/vk/commit_room.h, new; gpu/vk/vk_texture.cpp, commit_headroom): below the reserve, the headroom is what is left under the limit now plus what the page files can still grow by. Each file counts up to its maximum, and the files of one drive together no further than the drive has free. The sizes and maximums come from NtQuerySystemInformation's SystemPageFileInformationEx (0x90), the free space from GetDiskFreeSpaceExW; both are read at most once a second, and only while the commit left is below the reserve.
  • A file at its maximum, a fixed-size file, a full drive, a file whose drive is not in its name, or a failed query count for nothing. A PC at its real limit (#77's 16 GB PC with a fixed 32 GB page file, #333's with a fixed 16 GB one) is treated as before.
  • The log says vk: commit: N MB left under Windows' limit as it is now, and the page files can grow by N MB more: counted as room at most every 3600 frames while the growth is being counted.
  • docs: RX_VK_COMMIT_GROWTH=0 in docs/development.md's switch reference, and the RX_VK_COMMIT_RESERVE_MB row names it.
  • tests: tests/test_commit_room.py (new, with tests/test_commit_room.cpp) compiles and runs the rule: one file's growth, two files sharing a drive, a full drive, a file at or above its maximum, an unknown drive, the issue's numbers with and without the growth, and the reading of the query's records (a cut buffer, a name outside the buffer, a next offset that points back or past the end). It also checks that vk_texture.cpp uses the rule.

Measurements

Verdict: with the rule forced, main retires 9,118 and 9,259 textures for the budget; counting page-file growth retires none for it. The new game-free test passes here and fails on main, with no new suite failures.

Hardware: Windows 11, RTX 4070 Ti SUPER, Ryzen 7 9800X3D, 32 GB; the page file set to "Windows manages the size" for the runs (9,216 MB, a commit limit of 41,381 MB, 188 GB free on its drive)
Scene: Castle Town's gate and the guard house, by scripted input with one debug teleport, 22,200 presents a run, 1024x768
Method: 2026-11-14; branch fix/commit-room-growing-page-file. the same exe (built from main), main's longshot_vk.dll against this PR's, four runs interleaved (main, PR, main, PR), each after a shader preparation of its own. The route does not reach the commit limit by itself (the game's own commit ends at 19 to 24 GB), so the rule was forced on all four runs as #77's test did: RX_VK_COMMIT_RESERVE_MB=21061, which is the commit left under the limit with the PC idle (36,061 MB) less 15,000 MB, so the rule acts once the game's commit passes about 15 GB. Both runs of a side are given, not a median.
How to reproduce: the two DLLs, a page file Windows manages, RX_VK_COMMIT_RESERVE_MB in longshot_env.txt set to the idle "left under the limit" less 15000, any route of a few minutes

Before: main @ cf605c85 (run 1, run 2) After: this PR @ 24c64183 (run 1, run 2)
vk: commit limit near: lines 26, 26 0, 0
lowest commit left under the limit of the moment 16,049 MB, 16,094 MB no line; 20,292 MB and 20,355 MB at the first vk: commit: line
vk: commit: lines ("the page files can grow by 89088 MB more") none 5, 5
VRAM: lines with the textures' budget cut (of 37) 25, 25, down to 0 MB 0, 0 (11,000 MB throughout)
textures retired for the budget 9,118 and 9,259 0 and 0
textures retired in all 22,279 and 22,308 14,195 and 12,403
refused commits (mem: commit lines) 0, 0 0, 0
the game's own commit at the end 19,345 MB, 18,977 MB 24,175 MB, 23,626 MB
fps at the last line 60.0, 59.9 59.8, 60.0
present intervals over 40 ms 22, 22 23, 26
present intervals of 30 to 40 ms 136, 146 252, 259

The 30 to 40 ms intervals: on both sides every 300 presents have 3 to 6 of them from the start of the run. On main they stop (0 to 2 in 300) from the point where the rule has cut the cache to a few hundred MB; on this PR they go on as before that point. So this PR's count is what main does while the rule is off, and what they are is not looked at here.

What the query gives for the page file on this PC, from a probe that uses the same header and calls (total, min, max in pages of 4 KB):

Windows manages the size:
ram 32165 MB, limit 41381 MB, left under it 36052 MB
record at 0: next 0 total 2359296 in_use 0 peak 0 min 2359296 max 25165824 (pages) name_bytes 38
file 0: drive C size 9216 MB max 98304 MB
drive C: free 188623 MB (ok 1)
growth 89088 MB, room 125140 MB
registry PagingFiles: ?:\pagefile.sys

A fixed size (this PC's own setting):
ram 32165 MB, limit 72165 MB, left under it 63611 MB
record at 0: next 0 total 10240000 in_use 0 peak 0 min 10240000 max 10240000 (pages) name_bytes 38
file 0: drive C size 40000 MB max 40000 MB
growth 0 MB, room 63611 MB
registry PagingFiles: c:\pagefile.sys 40000 40000

The game-free suite, python3 -I -B tests/run_all.py --jobs 4 --timeout 120 on a Linux x86-64 PC (gcc 15.2, Python 3.14.4), two runs each, interleaved:

Before: main @ cf605c85 After: this PR @ 24c64183
tests/run_all.py (run 1, run 2) 473 passed, 4 failed, 82 skipped (both) 474 passed, 4 failed, 82 skipped (both)
failing checks test_bake_portability.py, test_menu_keys.py, test_module_ids.py, test_pad_layout.py the same four
tests/test_commit_room.py fails: vk_texture.cpp no longer has: #include "commit_room.h" passes

test_bake_portability.py fails here for a missing Python module (zstandard). test_menu_keys.py doesn't build with g++ and test_pad_layout.py's fixture lacks host/hold_toggle.h, both on main since #137 (#331 and #338 fix them). test_module_ids.py looks for a source marker that main no longer has.

Flags and settings

New RX_VK_COMMIT_GROWTH=0 restores main's headroom rule for comparing; growth counts by default.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_COMMIT_GROWTH=0 New comparison switch No switch; only what is left under the limit as it is now counts Unset (the growth counts) 0: the commit rule's headroom is what is left under the limit as it is now, as on main Helps: Windows PCs whose page file can still grow (Windows manages its size, or a custom size with a larger maximum). Doesn't help: a page file at its maximum or of a fixed size, a full drive; Linux (the rule is Windows only). Untested: a 16 GB PC; AMD, Intel The change is on by default; 0 is only for comparing

Testing

Tested: the game on Windows 11 with a page file Windows manages, main's DLL against this PR's, two runs each, as in Measurements: with the rule forced, main cuts the textures' budget to 0 and retires about 9,200 textures for it in six minutes; this PR counts the 89,088 MB the page file can grow by, leaves the budget alone and retires none for it. The query on the same PC with the page file managed (a maximum of 98,304 MB, three times the RAM) and fixed (maximum = size, growth 0). The game-free suite on a Linux PC. longshot_vk.dll builds with llvm-mingw 20261104 (nx2/build.py --standalone, RX_VK_ONLY=1), no warning in the changed files.

Not tested: Windows growing the page file in play: the route stays far below the limit of the moment (the file stayed at its starting size), so the runs show the rule's reading, not the growth itself. For the growth there is issue 336's log, where the limit rose by more than 12 GB in play with no refused commit. A PC with 16 GB, two page files, and a drive that is nearly full, outside the game-free test.

Must be tested: None.

Risks

What could break: If Windows cannot grow the file as fast as the game commits, a commit can be refused for a moment: hos_commit and the first-touch handler already wait up to 5 s for that (#77), and the issue's log, where Windows grew the limit by more than 12 GB in play, has no refused commit.

Turning it off: RX_VK_COMMIT_GROWTH=0 turns the change off; reverting the commit removes it.

Compatibility: With the growth counted, a PC whose page file can still grow keeps its textures until Windows has grown the file to its maximum or the drive is full; from there the rule acts as on main.

Reviewer notes

  • SystemPageFileInformationEx is not in Microsoft's documentation (the record's layout is in phnt's ntexapi.h). The call is looked up in ntdll.dll at run time and its result is read inside the buffer only; if it fails, the headroom is ullAvailPageFile alone.
  • #341 (a host allocation at the limit waits, then says out of memory) is about a limit that was really reached and does not overlap.
  • memory_notice in host/hos_main.c also reads the limit of the moment (ullTotalPageFile) for its low-memory notice at the start. Not changed here.

Issues

For #336

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • 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.

When it applies

  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.
**Type:** fix · **Risk:** medium · **Tested on:** Windows 11, RTX 4070 Ti SUPER; Linux x86-64 (game-free checks) · **Needs:** none ## Why Issue 336 reports brief freezes and black textures when opening the inventory menu or the list to switch items. The issue's log shows the commit rule of #77 cutting the texture cache for about 90 minutes on a PC that was never at Windows' commit limit: - `boot: 31849 MB of memory, 2048 MB of swap`: a commit limit of 33.9 GB at the start. - 487 `vk: commit limit near:` lines (one every 600 frames at most) between presents 98,400 and 422,280, with 308 to 2,556 MB of commit left. - The game's own commit (`private` in the `vk: memory:` line) plus the commit left rose from 24.3 GB to 36.1 GB over those lines. That is above the 33.9 GB limit of the start with every other program counted as nothing, so the limit itself grew. No `mem: commit ... at the commit limit` line in the log: Windows refused no commit. - In that time the `budget` count of the `textures retired` lines adds up to 292,526 textures. 473 of the 707 `VRAM:` lines show the textures' budget cut, to between 0 and 4,180 MB (11,000 MB otherwise), with 6.8 to 10.7 GB used of 15.2 GB allowed. After present 246,720 three evictions in a row (416, 361 and 56 textures) took the cache from 2,378 MB to 502 MB. The rule reads `GlobalMemoryStatusEx`'s `ullAvailPageFile`: what is left under the limit as it is now, RAM plus the page files at their size of the moment. A page file Windows manages (the default, and what the game's own low-memory notice asks for: "let it manage the page file's size") grows when the commit nears the limit, and the limit with it. So on such a PC the reading stays small for as long as the game's commit grows, and the rule takes textures away that are then made again. ## How to reproduce **Seen on:** issue 336: RTX 5070 Ti, Ryzen 7 9800X3D, 32 GB, Windows, v3.0.0-rc.3. **Settings and switches:** a page file Windows manages (the default), with room to grow. **Steps:** Open the inventory menu or the list to switch items. **Expected:** The texture cache is not cut merely because the current commit limit can still grow. **Actual:** Issue 336: "Sometimes when I open my inventory menu or open the list to switch my items the game freezes for a second and the textures go black. It then goes back to normal." ## What changed The texture cache's commit rule counts how much the page files can still grow, limited by their maximums and free drive space. Fixed files, full drives and failed queries keep the old behavior. - vk (`gpu/vk/commit_room.h`, new; `gpu/vk/vk_texture.cpp`, `commit_headroom`): below the reserve, the headroom is what is left under the limit now plus what the page files can still grow by. Each file counts up to its maximum, and the files of one drive together no further than the drive has free. The sizes and maximums come from `NtQuerySystemInformation`'s `SystemPageFileInformationEx` (0x90), the free space from `GetDiskFreeSpaceExW`; both are read at most once a second, and only while the commit left is below the reserve. - A file at its maximum, a fixed-size file, a full drive, a file whose drive is not in its name, or a failed query count for nothing. A PC at its real limit (#77's 16 GB PC with a fixed 32 GB page file, #333's with a fixed 16 GB one) is treated as before. - The log says `vk: commit: N MB left under Windows' limit as it is now, and the page files can grow by N MB more: counted as room` at most every 3600 frames while the growth is being counted. - docs: `RX_VK_COMMIT_GROWTH=0` in `docs/development.md`'s switch reference, and the `RX_VK_COMMIT_RESERVE_MB` row names it. - tests: `tests/test_commit_room.py` (new, with `tests/test_commit_room.cpp`) compiles and runs the rule: one file's growth, two files sharing a drive, a full drive, a file at or above its maximum, an unknown drive, the issue's numbers with and without the growth, and the reading of the query's records (a cut buffer, a name outside the buffer, a next offset that points back or past the end). It also checks that `vk_texture.cpp` uses the rule. ## Measurements **Verdict:** with the rule forced, main retires 9,118 and 9,259 textures for the budget; counting page-file growth retires none for it. The new game-free test passes here and fails on main, with no new suite failures. Hardware: Windows 11, RTX 4070 Ti SUPER, Ryzen 7 9800X3D, 32 GB; the page file set to "Windows manages the size" for the runs (9,216 MB, a commit limit of 41,381 MB, 188 GB free on its drive) Scene: Castle Town's gate and the guard house, by scripted input with one debug teleport, 22,200 presents a run, 1024x768 Method: 2026-11-14; branch `fix/commit-room-growing-page-file`. the same exe (built from main), main's `longshot_vk.dll` against this PR's, four runs interleaved (main, PR, main, PR), each after a shader preparation of its own. The route does not reach the commit limit by itself (the game's own commit ends at 19 to 24 GB), so the rule was forced on all four runs as #77's test did: `RX_VK_COMMIT_RESERVE_MB=21061`, which is the commit left under the limit with the PC idle (36,061 MB) less 15,000 MB, so the rule acts once the game's commit passes about 15 GB. Both runs of a side are given, not a median. How to reproduce: the two DLLs, a page file Windows manages, `RX_VK_COMMIT_RESERVE_MB` in `longshot_env.txt` set to the idle "left under the limit" less 15000, any route of a few minutes | | Before: main @ cf605c85 (run 1, run 2) | After: this PR @ 24c64183 (run 1, run 2) | |---|---|---| | `vk: commit limit near:` lines | 26, 26 | 0, 0 | | lowest commit left under the limit of the moment | 16,049 MB, 16,094 MB | no line; 20,292 MB and 20,355 MB at the first `vk: commit:` line | | `vk: commit:` lines ("the page files can grow by 89088 MB more") | none | 5, 5 | | `VRAM:` lines with the textures' budget cut (of 37) | 25, 25, down to 0 MB | 0, 0 (11,000 MB throughout) | | textures retired for the budget | 9,118 and 9,259 | 0 and 0 | | textures retired in all | 22,279 and 22,308 | 14,195 and 12,403 | | refused commits (`mem: commit` lines) | 0, 0 | 0, 0 | | the game's own commit at the end | 19,345 MB, 18,977 MB | 24,175 MB, 23,626 MB | | fps at the last line | 60.0, 59.9 | 59.8, 60.0 | | present intervals over 40 ms | 22, 22 | 23, 26 | | present intervals of 30 to 40 ms | 136, 146 | 252, 259 | The 30 to 40 ms intervals: on both sides every 300 presents have 3 to 6 of them from the start of the run. On main they stop (0 to 2 in 300) from the point where the rule has cut the cache to a few hundred MB; on this PR they go on as before that point. So this PR's count is what main does while the rule is off, and what they are is not looked at here. What the query gives for the page file on this PC, from a probe that uses the same header and calls (`total`, `min`, `max` in pages of 4 KB): ``` Windows manages the size: ram 32165 MB, limit 41381 MB, left under it 36052 MB record at 0: next 0 total 2359296 in_use 0 peak 0 min 2359296 max 25165824 (pages) name_bytes 38 file 0: drive C size 9216 MB max 98304 MB drive C: free 188623 MB (ok 1) growth 89088 MB, room 125140 MB registry PagingFiles: ?:\pagefile.sys A fixed size (this PC's own setting): ram 32165 MB, limit 72165 MB, left under it 63611 MB record at 0: next 0 total 10240000 in_use 0 peak 0 min 10240000 max 10240000 (pages) name_bytes 38 file 0: drive C size 40000 MB max 40000 MB growth 0 MB, room 63611 MB registry PagingFiles: c:\pagefile.sys 40000 40000 ``` The game-free suite, `python3 -I -B tests/run_all.py --jobs 4 --timeout 120` on a Linux x86-64 PC (gcc 15.2, Python 3.14.4), two runs each, interleaved: | | Before: main @ cf605c85 | After: this PR @ 24c64183 | |---|---|---| | `tests/run_all.py` (run 1, run 2) | 473 passed, 4 failed, 82 skipped (both) | 474 passed, 4 failed, 82 skipped (both) | | failing checks | `test_bake_portability.py`, `test_menu_keys.py`, `test_module_ids.py`, `test_pad_layout.py` | the same four | | `tests/test_commit_room.py` | fails: `vk_texture.cpp no longer has: #include "commit_room.h"` | passes | `test_bake_portability.py` fails here for a missing Python module (zstandard). `test_menu_keys.py` doesn't build with g++ and `test_pad_layout.py`'s fixture lacks `host/hold_toggle.h`, both on main since #137 (#331 and #338 fix them). `test_module_ids.py` looks for a source marker that main no longer has. ### Flags and settings New `RX_VK_COMMIT_GROWTH=0` restores main's headroom rule for comparing; growth counts by default. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_COMMIT_GROWTH=0` | New comparison switch | No switch; only what is left under the limit as it is now counts | Unset (the growth counts) | `0`: the commit rule's headroom is what is left under the limit as it is now, as on `main` | Helps: Windows PCs whose page file can still grow (Windows manages its size, or a custom size with a larger maximum). Doesn't help: a page file at its maximum or of a fixed size, a full drive; Linux (the rule is Windows only). Untested: a 16 GB PC; AMD, Intel | The change is on by default; `0` is only for comparing | ## Testing **Tested:** the game on Windows 11 with a page file Windows manages, main's DLL against this PR's, two runs each, as in Measurements: with the rule forced, main cuts the textures' budget to 0 and retires about 9,200 textures for it in six minutes; this PR counts the 89,088 MB the page file can grow by, leaves the budget alone and retires none for it. The query on the same PC with the page file managed (a maximum of 98,304 MB, three times the RAM) and fixed (maximum = size, growth 0). The game-free suite on a Linux PC. `longshot_vk.dll` builds with llvm-mingw 20261104 (`nx2/build.py --standalone`, `RX_VK_ONLY=1`), no warning in the changed files. **Not tested:** Windows growing the page file in play: the route stays far below the limit of the moment (the file stayed at its starting size), so the runs show the rule's reading, not the growth itself. For the growth there is issue 336's log, where the limit rose by more than 12 GB in play with no refused commit. A PC with 16 GB, two page files, and a drive that is nearly full, outside the game-free test. **Must be tested:** None. ## Risks **What could break:** If Windows cannot grow the file as fast as the game commits, a commit can be refused for a moment: `hos_commit` and the first-touch handler already wait up to 5 s for that (#77), and the issue's log, where Windows grew the limit by more than 12 GB in play, has no refused commit. **Turning it off:** `RX_VK_COMMIT_GROWTH=0` turns the change off; reverting the commit removes it. **Compatibility:** With the growth counted, a PC whose page file can still grow keeps its textures until Windows has grown the file to its maximum or the drive is full; from there the rule acts as on `main`. ## Reviewer notes - `SystemPageFileInformationEx` is not in Microsoft's documentation (the record's layout is in phnt's `ntexapi.h`). The call is looked up in `ntdll.dll` at run time and its result is read inside the buffer only; if it fails, the headroom is `ullAvailPageFile` alone. - #341 (a host allocation at the limit waits, then says out of memory) is about a limit that was really reached and does not overlap. - `memory_notice` in `host/hos_main.c` also reads the limit of the moment (`ullTotalPageFile`) for its low-memory notice at the start. Not changed here. ## Issues For #336 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [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. ### When it applies - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated.
fix(vk): the page files' room to grow counts before the commit rule cuts the textures
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_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 8s
beanstalk/approval Test farm not requested
24c6418328
The textures' budget gives commit back below a reserve of 2560 MB under
Windows' commit limit. The headroom was GlobalMemoryStatusEx's
ullAvailPageFile: the limit as it is now, RAM plus the page files at their
size of the moment. A page file Windows manages grows as the commit nears
that limit, and the limit with it.

In issue 336's log (32 GB, a 2 GB page file at the start, RTX 5070 Ti) the
rule was on for about 90 minutes with 0.3 to 2.5 GB left under the limit of the
moment. The game's own commit plus what was left rose from 24.3 to 36.1 GB,
above the 33.9 GB limit of the start, and no commit was refused. In that
time the rule took 292,526 textures out of the cache, with the budget down
to 0 at its lowest (2,378 MB of textures to 502 MB in three evictions): the
textures going black and coming back that the issue reports.

Below the reserve the headroom now adds what the page files can still grow
by: each file up to its maximum (NtQuerySystemInformation's
SystemPageFileInformationEx), the files of one drive together no further
than the drive has free. Read at most once a second, and only below the
reserve. A file at its maximum, a full drive or a failed query count for
nothing, so a PC at its real limit is treated as before.
RX_VK_COMMIT_GROWTH=0 is the old reading.
jarynclouatre changed title from WIP: fix(vk): the page files' room to grow counts before the commit rule cuts the textures to fix(vk): the page files' room to grow counts before the commit rule cuts the textures 2026-11-14 05:30:09 +00:00
First-time contributor

Windows compile and game-free checks for head 24c64183:

  • longshot_vk.dll built with the pinned llvm-mingw 20261104 (RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windows, --jobs 6): 0 failed, linked.
  • tests/test_commit_limit.py and tests/test_commit_room.py: pass.

No game run here, so the inventory freeze on a real Windows GPU still needs the scene from issue 336. The build and the two checks are clean on Windows.

Windows compile and game-free checks for head `24c64183`: - `longshot_vk.dll` built with the pinned llvm-mingw 20261104 (`RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windows`, `--jobs 6`): 0 failed, linked. - `tests/test_commit_limit.py` and `tests/test_commit_room.py`: pass. No game run here, so the inventory freeze on a real Windows GPU still needs the scene from issue 336. The build and the two checks are clean on Windows.
HumanBean approved these changes 2026-11-15 18:01:52 +00:00
HumanBean left a comment

First review, on the code at 24c64183. I didn't run any tests here.

The commit-room math holds. I checked each part:

  • The record layout. SystemPageFileInformationEx (0x90) on x64: the name's UNICODE_STRING buffer is at 0x18, MinimumSize/MaximumSize at 0x20/0x24, 0x28 in all. The static_assert pins this, and the sizes are converted from pages with dwPageSize. Your probe shows the same thing: a managed file reports max = 3×RAM, and a fixed file reports max = size.
  • The buffer. It is read inside its bounds: a cut record, a name outside the buffer, and a next that points backwards or past the end all stop the list safely. Too small a buffer or a failed query gives 0.
  • No overflow. The growth is clamped per file to max − size (0 when size ≥ max), and per drive to its free space (read once per drive, shared by that drive's files). The sum and the headroom both saturate at UINT64_MAX.
  • A fixed-size page file, no page file, an unknown drive, a full drive or a failed query each add 0. The headroom is then ullAvailPageFile as on main, so #77's and #333's PCs behave as before.
  • The query is cheap. It only runs below the reserve, at most once a second, from texture_frame's one call site, so the function statics don't race.

Can it run out of commit where main didn't? Only when Windows can't actually grow the file after we counted the growth as room. For example, the drive fills between reads, or Windows stops growing the file short of the last free bytes. Even then the rule still asks for avail + growth ≥ reserve, so a nearly full drive still triggers it. hos_commit and the first-touch handler also wait out a growth for up to 5 s (#77). The PR's VRAM blocks are commit that lives in VRAM, not in RAM, so letting the commit run up into the page file doesn't cost physical RAM. I've left one small optional margin suggestion inline. It doesn't block.

Rebase: the branch is 73 commits behind main. It rebases onto main @ ced37d40 with no conflicts: vk_texture.cpp's commit rule and the development.md row are unchanged on main, and the six files apply as they are. I tried to push the rebase to your branch, but it wasn't allowed from here (maintainer edits are off), so please rebase it yourself (jj rebase -b @ -d main@origin, or the forge's update button). Nothing needs resolving by hand.

Testing / readiness: you ran it in the game on Windows 11 (RTX 4070 Ti SUPER) with the rule forced: 0 textures retired for the budget, against about 9,200 on main. suporte_de_cadeira also posted a Windows llvm-mingw build and passing test_commit_limit.py and test_commit_room.py for this head. The runs never made Windows grow the file in play, and for that the evidence is issue 336's log from main. I'm not blocking on it, but a run that confirms the fix would be good before or right after merge: the inventory open/close scene from #336 on a PC whose managed page file actually grows (16 GB of RAM, page file starting small), checking for no mem: commit ... at the commit limit line and no black textures.

Approved on the code. RX_VK_COMMIT_GROWTH=0 is a kill/compare switch in the same style as RX_VK_EVICT_EXCESS=0, and its default (on) is right for everyone.

First review, on the code at 24c64183. I didn't run any tests here. **The commit-room math holds.** I checked each part: - **The record layout.** `SystemPageFileInformationEx` (0x90) on x64: the name's `UNICODE_STRING` buffer is at 0x18, MinimumSize/MaximumSize at 0x20/0x24, 0x28 in all. The `static_assert` pins this, and the sizes are converted from pages with `dwPageSize`. Your probe shows the same thing: a managed file reports max = 3×RAM, and a fixed file reports max = size. - **The buffer.** It is read inside its bounds: a cut record, a name outside the buffer, and a `next` that points backwards or past the end all stop the list safely. Too small a buffer or a failed query gives 0. - **No overflow.** The growth is clamped per file to max − size (0 when size ≥ max), and per drive to its free space (read once per drive, shared by that drive's files). The sum and the headroom both saturate at `UINT64_MAX`. - **A fixed-size page file, no page file, an unknown drive, a full drive or a failed query** each add 0. The headroom is then `ullAvailPageFile` as on main, so #77's and #333's PCs behave as before. - **The query is cheap.** It only runs below the reserve, at most once a second, from `texture_frame`'s one call site, so the function statics don't race. **Can it run out of commit where main didn't?** Only when Windows can't actually grow the file after we counted the growth as room. For example, the drive fills between reads, or Windows stops growing the file short of the last free bytes. Even then the rule still asks for avail + growth ≥ reserve, so a nearly full drive still triggers it. `hos_commit` and the first-touch handler also wait out a growth for up to 5 s (#77). The PR's VRAM blocks are commit that lives in VRAM, not in RAM, so letting the commit run up into the page file doesn't cost physical RAM. I've left one small optional margin suggestion inline. It doesn't block. **Rebase:** the branch is 73 commits behind main. It rebases onto main @ ced37d40 with **no conflicts**: `vk_texture.cpp`'s commit rule and the `development.md` row are unchanged on main, and the six files apply as they are. I tried to push the rebase to your branch, but it wasn't allowed from here (maintainer edits are off), so please rebase it yourself (`jj rebase -b @ -d main@origin`, or the forge's update button). Nothing needs resolving by hand. **Testing / readiness:** you ran it in the game on Windows 11 (RTX 4070 Ti SUPER) with the rule forced: 0 textures retired for the budget, against about 9,200 on main. suporte_de_cadeira also posted a Windows llvm-mingw build and passing `test_commit_limit.py` and `test_commit_room.py` for this head. The runs never made Windows grow the file in play, and for that the evidence is issue 336's log from main. I'm not blocking on it, but a run that confirms the fix would be good before or right after merge: the inventory open/close scene from https://git.magicbean.dev/MagicBeanDev/Longshot/issues/336 on a PC whose managed page file actually grows (16 GB of RAM, page file starting small), checking for no `mem: commit ... at the commit limit` line and no black textures. Approved on the code. `RX_VK_COMMIT_GROWTH=0` is a kill/compare switch in the same style as `RX_VK_EVICT_EXCESS=0`, and its default (on) is right for everyone.
@ -0,0 +33,4 @@
const PageFile& f = files[i];
if (f.drive < 'A' || f.drive > 'Z' || f.max_size <= f.size) continue;
const int d = f.drive - 'A';
if (!read[d]) read[d] = true, left[d] = drive_free(f.drive);
Owner

Optional, not blocking: the drive's free space counts all the way down to zero. Windows may stop growing the page file a little short of a full volume, and other writers (the shader cache, the browser) eat into it between reads. If that happens, the room we counted isn't there and the rule only acts once avail alone drops. A small margin would cover it, e.g. in page_file_growth()'s lambda: const uint64_t f = free_bytes.QuadPart; return f > (1ull << 30) ? f - (1ull << 30) : 0; (leave 1 GB of the drive out). The test growth(&managed, 1, ...10 GB) == 10 GB would still pass, since it uses the lambda it's given.

Optional, not blocking: the drive's free space counts all the way down to zero. Windows may stop growing the page file a little short of a full volume, and other writers (the shader cache, the browser) eat into it between reads. If that happens, the room we counted isn't there and the rule only acts once avail alone drops. A small margin would cover it, e.g. in `page_file_growth()`'s lambda: `const uint64_t f = free_bytes.QuadPart; return f > (1ull << 30) ? f - (1ull << 30) : 0;` (leave 1 GB of the drive out). The test `growth(&managed, 1, ...10 GB) == 10 GB` would still pass, since it uses the lambda it's given.
HumanBean deleted branch fix/commit-room-growing-page-file 2026-11-15 18:03:19 +00:00
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_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 8s
beanstalk/approval Test farm not requested
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 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!346
No description provided.