Skip to content

fix(vk): stop the VRAM compaction's hold from evicting textures in use - #2198

Merged
hckrman101 merged 9 commits into
NotRexed:mainfrom
SadTransGirl:fix/vram-hold-cold
Nov 11, 2026
Merged

hckrman101 merged 9 commits into
NotRexed:mainfrom
SadTransGirl:fix/vram-hold-cold

Conversation

@SadTransGirl

@SadTransGirl SadTransGirl commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

With RX_VK_COMPACT=1 on a card with little room, textures that were still in use were thrown out and loaded again every time the camera turned, and at some places the game could freeze for a few seconds. Both came from the rule that holds VRAM at 85%; this fixes that rule and gives it a small reserve. With that fixed, the compaction is now on by default (RX_VK_COMPACT=0 turns it off), as hckrman101 suggested in Discord.

Tracking: #995. Follow-up to #1821 and to @hckrman101's 6 GB result there (4-5x the frames over 33 ms with the switch on).

What went wrong

The hold sets the texture budget to "what the textures hold, less the excess over 85% of the allowance". Whenever the count stays over that line (a small card, a burst of new textures, another program taking VRAM), every pass is a pass over the budget, and two things follow:

  1. What went had been unused for 60 presents. Right for a budget that is a size. Under the hold, every texture out of view for a second went and came back.
  2. Dead render targets were never taken. A pass over the budget takes the oldest textures first and stops at the first one past the excess. The render targets another has drawn over (a frame's scratch targets) are the youngest, and the sweep that takes them only runs under the budget. The other rules cut to 7/8 once, so sweeps resume on the next pass; the hold stays over. With other programs holding VRAM (10.2-10.4 GB allowed), where a new stretch of Hyrule Field comes in, the render targets' pool went from 896 to 3,456 MB in 120 presents while 2,600 live textures went, the count passed what the driver allows, and 179 of 291 frames were over 25 ms (60-128 ms).

The change

  • A pass the hold asked for counts what the dead render targets hold first and lets all of them go, and takes live textures only for the excess that is left, and only ones unused for 600 presents (vram_compact::hold_met, hold_keeps, kHoldUnused).
  • A reserve: a second line five points of the allowance under the hold's (80 for 85), over which textures unused for 1,800 presents go (reserve_pct, hold_target, kReserveUnused). With one line the cache sat at it and whatever came in next went over.
  • Passes the other rules ask for (the budget itself, the 90% and 97% rules) are as they were.
  • The compaction is on unless RX_VK_COMPACT=0 (vram_compact::enabled: not set is on, 0 or empty is off). It was opt-in while the hold could let go of textures in use.

Numbers

Built from this branch (v2.0.11-782-gf0c6e73) and from main (v2.0.11-780-g3932421). Unattended runs with scripted input, DLAA 2560x1440, RTX 4070 SUPER (617.42), Ryzen 9 9950X3D, Windows 11, nothing else running, two runs each. Frames from RX_VK_FRAME_TRACE. RX_VK_TEST_ALLOWED_MB stands in for a smaller card or for other programs.

Camera turning in place in Hyrule Field, RX_VK_COMPACT=1 RX_VK_TEST_ALLOWED_MB=7000, 50 seconds:

fps textures uploaded textures retired frames over 25 ms 1% low
main, switch off 60.0 1,313 MB 3,481 6 42.5
main 59.0, 59.2 6,573, 5,954 MB 13,701, 12,622 42, 27 34.8, 35.3
this branch 60.0, 60.0 1,284, 1,284 MB 4,044, 3,814 7, 5 42.4, 44.3

Five-minute walk across Hyrule Field, RX_VK_COMPACT=1 RX_VK_ASTC_BC_ALL=1:

allowance frames over 25 / 33 / 50 ms shadows given back windows with GPU time over 12.5 ms 1% low
main 9,500 MB (capped) 120/29/10, 93/16/3 4, 5 times 8, 11 of 124 30.6, 35.5
this branch 9,500 MB (capped) 86/17/1, 92/13/1 0, 0 0, 2 of 125 35.9, 36.9
main 11.2 GB (real) 93/24/7, 97/28/11 2, 2 times 1, 4 of 125 33.2, 31.2
this branch 11.2 GB (real) 96/19/0, 65/8/1 0, 0 2, 2 of 125 35.9, 38.2

No validation, device-lost or "not made" line in any run.

Those runs were on main as it was yesterday (3932421). After rebasing onto today's main (4d68bc8) I ran each once more, built from this branch (v2.1.0-rc.3-50-g3acf477a) and from main (v2.1.0-rc.3-48-g4d68bc83), counted from the end of the load:

fps textures uploaded frames over 25 / 33 / 50 ms shadows given back 1% low
spin at 7,000 MB, main 59.6 6,842 MB 16/0/0 0 39.1
spin at 7,000 MB, this branch 60.0 1,187 MB 0/0/0 once, while the save loaded 47.2
walk at 9,500 MB, main 59.5 7,383 MB 104/26/6 5 times 33.6
walk at 9,500 MB, this branch 59.9 4,753 MB 29/8/1 0 42.3

In that spin run the branch's count passed the allowance once while the save loaded (7,016 of 7,000 MB), which is what gave the shadows back; main's run did not, and neither did the four spin runs on the earlier base. One run, so I can't say whether it is the branch or chance.

On by default against off

Built from this branch after the default changed (v2.1.0-rc.3-58-g06ce5785, main 49f75dc merged in). No switch set against RX_VK_COMPACT=0, DLAA 2560x1440, the same card, nothing else running, counted from the end of the load.

Five-minute walk across Hyrule Field, two runs each:

fps frames over 25 / 33 / 50 ms 1% low VRAM in the mean (share of the allowance) shadows given back
on (the default now) 59.8, 59.9 66/12/2, 36/2/1 37.7, 40.9 9.05-9.07 GB (85%) 0, 0
RX_VK_COMPACT=0 56.0, 56.0 633/60/15, 408/72/28 25.8, 24.1 10.21-10.29 GB (95-96%) 8, 8 times

Camera turning in place with RX_VK_TEST_ALLOWED_MB=7000, one run each: on 60.0 fps, 1,203 MB uploaded, 1 frame over 25 ms; off 59.9 fps, 2,660 MB, 8 frames. No validation, device-lost or "not made" line in any of these.

On my local build (the same two commits on an older base, with a measuring hook that holds 768 MB of the card), the reserve alone took the share of a walk spent over 85% of the allowance from 47-68% to 2-5%, and over 87% from 7-26% to 0-1%, with uploads and frame times unchanged.

Limits

  • One Tier 1 configuration (NVIDIA RTX 40, Windows), Hyrule Field only, unattended runs without a window; plus played sessions in a real window on the local build (DLAA, with and without a recorder): no error, and the multi-second freezes gone.
  • Not tested by me: AMD, Intel, Linux, a real small card, Kokiri Forest outdoors (the scene of the 6 GB report), cutscenes. The test reports in this thread cover Linux with an RTX 4070 Ti, an RTX 5080 and an RX 6600 8 GB, each with RX_VK_COMPACT=1.
  • On by default is measured on this one card only. Nobody has run it yet on a 4-6 GB card, an Intel or integrated GPU, or the Steam Deck: RX_VK_COMPACT=0 is the way back there.
  • The small allowance is simulated on a 12 GB card, as in the report.
  • On today's main each test was run once, not twice.
  • 600 presents was tried against 300 (alike); the reserve at 80 with 1,800 presents; other values were not.
  • With VRAM really short (the count at 93-96% of the allowance), stretches of 10-20 s with the GPU at 18-21 ms a frame remain: nothing is left that the cache can let go. This PR does not address that.

Test

python -I -B tests/test_vram_compact.py: the rules' arithmetic (hold_keeps, hold_met, reserve_pct, hold_target), and source checks that the hold's pass counts the dead targets before it chooses, does not stop at the excess, judges a live texture only once it knows whether a target drew over it, and that the driver's limit clears both lines; and enabled: not set is on, 0 or empty is off. Fails on main.

🤖 Generated with Claude Code

SadTransGirl and others added 3 commits November 11, 2026 06:18
…ly long-unused textures

With RX_VK_COMPACT=1 the texture budget holds the driver's count at 85% of
what it allows by letting go of the excess. Two things went wrong whenever
the count stayed over that line:

- A pass over its budget takes the oldest textures first and stopped at the
  first one past the excess. The render targets another has drawn over (the
  frame's scratch targets) are the youngest, so it never reached them: the
  sweep that takes them only runs under the budget. Live textures went in
  their place and the dead targets piled up.
- What went had gone unused for 60 presents, so every texture out of view
  for a second went and came back when the camera turned.

The hold's pass now counts what the dead targets hold first and lets all of
them go, and takes live textures only for the excess left after that and
only ones unused for 600 presents. Passes the other rules ask for (the 90%
and 97% rules, the budget itself) are as they were.

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>
…ng-unused textures

With one line the texture cache sat at it, and whatever came in next (a new
stretch of the field, a spawn, a recorder started beside the game) went
over it. The hold now has a second line five points of the allowance lower
(80 for RX_VK_COMPACT_PCT=85): over it, textures unused for 1800 presents
are let go, down to that line. The hold's own line still takes those unused
for 600, and the 90% and 97% rules are as they were.

In a five-minute walk across Hyrule Field at DLAA with 768 MB of the card
held by a test allocation, the driver's count was over 85% of the allowance
for 47-68% of the time and over 87% for 7-26% (peaks of 91-93%). With the
reserve: over 85% for 5%, over 87% for 0-1%, peaks of 87-88%, no more
uploads and no more compaction runs.

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>
…ainst main (RTX 4070 SUPER, DLAA, Hyrule Field)

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>
@Joey-Magnus

Copy link
Copy Markdown
Contributor

Tested on Linux, which the PR lists as untested. It holds up there too.

Setup:

  • PC: Ubuntu 26.04.1, KDE Plasma 6.6 on Wayland, three monitors. RTX 4070 Ti 12 GB with driver 595.99.02, Ryzen 7 9800X3D, 32 GB.
  • Build: this PR's two code commits cherry-picked onto a main-based build, renderer hookshot_vk.so only (v2.0.11-795-g8baccd6b host). The docs commit was left out.
  • Test: DLAA at 2560x1440, RX_VK_COMPACT=1, a player's own daytime Hyrule Field save. 50 seconds of the camera turning in place after the load, frames from RX_VK_FRAME_TRACE. A and B differ only in the renderer, and the runs alternated between them.
Run fps Frames over 25 / 33 / 50 ms 1% low Uploaded Retired
Before, RX_VK_TEST_ALLOWED_MB=7000 59.9 23 / 13 / 0 31.4 8,491 MB 14,316
Before, RX_VK_TEST_ALLOWED_MB=7000 59.9 24 / 10 / 0 31.7 7,034 MB 12,637
This PR, RX_VK_TEST_ALLOWED_MB=7000 60.0 2 / 1 / 0 48.2 1,290 MB 2,324
This PR, RX_VK_TEST_ALLOWED_MB=7000 60.0 2 / 1 / 0 47.5 1,260 MB 2,459
Before, real allowance (~9 GB) 60.0 4 / 1 / 0 45.9 1,276 MB 1,760
This PR, real allowance 60.0 2 / 0 / 0 48.7 1,294 MB 1,942

Results:

  • Under the 7,000 MB allowance, frames over 25 ms dropped by about 90%, and uploads went from 7-8.5 GB to 1.3 GB.
  • At the card's real allowance the PR is neutral to slightly better.
  • No validation, device-lost or crash lines in any run.
  • tests/test_vram_compact.py and the full tests/run_all.py pass (330 passed, 0 failed, 10 skipped).

🤖 Generated with Claude Code

@nullifyshot

Copy link
Copy Markdown
Contributor

I tested this on Windows with an RTX 5080 (16 GB), using the same test as Joey-Magnus above. It holds up here too.

Setup:

  • PC: Windows 11, RTX 5080 16 GB.
  • Build: this PR's head (18a5be0) built with the Builder's llvm-mingw. The "before" renderer is main 4d68bc8 with the two code files reverted, built into the same folder. Only hookshot_vk.dll differs between A and B. The runs alternated: before, PR, before, PR.
  • Test: DLAA at 1920x1080, frame generation off, RX_VK_COMPACT=1, capped at 60 fps (HOS_FPS_MAX=60), daytime Hyrule Field save. 50 seconds of the camera turning in place after the load, with frames from RX_VK_FRAME_TRACE. Headless (HOS_HEADLESS=1), with the save loaded and the camera turned through HOS_INPUT_LIVE.
Run fps Frames over 25 / 33 / 50 ms 1% low Uploaded Retired
Before, RX_VK_TEST_ALLOWED_MB=7000 60.0 0 / 0 / 0 45.1 7,887 MB 17,231
This PR, RX_VK_TEST_ALLOWED_MB=7000 59.9 4 / 0 / 0 43.3 708 MB 3,264
Before, RX_VK_TEST_ALLOWED_MB=7000 58.7 11 / 0 / 0 39.8 7,844 MB 16,994
This PR, RX_VK_TEST_ALLOWED_MB=7000 59.9 3 / 0 / 0 43.7 699 MB 3,769
Before, real allowance (15,209 MB) 60.0 1 / 0 / 0 44.1 568 MB 2,894
This PR, real allowance 60.0 3 / 0 / 0 44.1 577 MB 3,151

(1% low here is the average frame rate of the slowest 1% of frames. Uploaded is the sum of the trace's upload_mb, and retired is the sum of the log's "N textures retired" lines, both over the 50 seconds.)

Results:

  • Under the 7,000 MB allowance: uploads dropped by about 91% (7.8–7.9 GB down to about 0.7 GB) and retired textures by about 79%. The churn is the same as Joey's "before" runs, and the PR removes it.
  • Frame times at 7,000 MB: about the same. The 5080 absorbs the extra uploads: one before run had 11 frames over 25 ms and a 1% low of 39.8, the other had none. The PR runs had 3–4 frames just over 25 ms and none over 33 ms. So on a fast card the gain is mostly in the work saved, while Joey's 4070 Ti shows what that churn costs in frame times.
  • At the card's real allowance: neutral. Uploads, retirements and the 1% low match within noise.
  • No validation, device-lost or "not made" lines in any of the six runs.

🤖 Generated with Claude Code

@Tempus3214

Copy link
Copy Markdown

Tested this on an 8 GB AMD card, where the hold matters most, and it removes most of the camera-turn hitches with RX_VK_COMPACT=1.

Setup

  • RX 6600 8 GB (Navi 23), driver 32.0.21045.16010 (Vulkan 0x800161), Ryzen 5 4500, Windows 10 21H2. The driver allows 7.23–7.26 GB.
  • 1920x1080 window, Handheld profile (1536x864).
  • Renderer rebuilt from the v2.1.0 tag (source 633eb435) with this PR's vk_texture.cpp and vram_compact.h changes. The docs hunk conflicts with the release tag and was left out. Built with the builder's llvm-mingw (clang 23.1.5), RX_VK_ONLY=1 RX_RELEASE=1. tests/test_vram_compact.py --cxx clang++ passes.
  • Both arms: RX_VK_FORCE_MESH_FALLBACK=1, RX_VK_CHECKPOINTS=1, RX_VK_RECORDER=thread. Only RX_VK_COMPACT differs.

Method

  • The same save in Kakariko Village at night (~3,600 draws a frame).
  • An identical 62 s HOS_INPUT_LIVE script after the load: camera sweeps left and right plus two short walks. Both arms ended in the same spot.
  • Frame times from RX_VK_FRAME_LOG.
  • Both arms had RX_VK_SHOT_EVERY=60 on, and each shot costs a 55–65 ms frame. Those presents (and the one after) are left out of the numbers below.
62 s, same script RX_VK_COMPACT off RX_VK_COMPACT=1 with this PR
Frames 1,998 2,223
Mean fps 32.2 35.9
Median frame 29.8 ms 27.4 ms
p99 frame 57.3 ms 42.8 ms
Frames over 40 ms 191 42
Frames over 50 ms 44 6
Frames over 70 ms 8 2
Frame-to-frame jumps over 10 ms 154 64
Free space inside the blocks (VRAM: lines) 553–1,021 MB 315–588 MB

With the PR, textures made settled at 0.4–0.8 a frame, so the churn described above did not show up. Half of the remaining frames over 50 ms came in one burst on the first camera turn, as new views streamed in. No device loss, validation or "not made" lines in either run. A normal play session afterwards showed no visual problems.

Caveats

  • One pair of runs, and the compaction arm ran first, with colder OS file caches.
  • I did not run v2.1.0's compaction without this fix, so this compares the fixed compaction against compaction off, not against the old hold.

@buasfe

buasfe commented Nov 11, 2026

Copy link
Copy Markdown

Hi! I've been troubleshooting the persistent FPS degradation on an RTX 3070 8 GB and wanted to share my results, as I think they might be relevant to this PR.

System:

  • GPU: RTX 3070 Gaming X Trio, 8 GB VRAM
  • CPU: Ryzen 7 5800X3D
  • RAM: 32 GB DDR4
  • OS: Windows 11
  • Resolution: 3440×1440
  • Hookshot: v2.1.0
  • Using the Fritz v3 60 FPS patch

The issue:

The game can run smoothly at approximately 50–60 FPS while exploring Hyrule Field or Kakariko Village. However, transitioning between these areas can cause a significant, persistent performance drop.

For example:

  1. Load directly into Hyrule Field and explore normally: approximately 50–60 FPS.
  2. Enter Kakariko Village through the loading transition.
  3. Performance sometimes drops to 20–30 FPS and doesn't fully recover.
  4. Returning to Hyrule Field doesn't necessarily restore performance, even in areas that previously ran at 50–60 FPS.

I've also compared two recordings in the same location using essentially the same route. The main difference was whether I'd travelled through Kakariko beforehand. The session that had gone through Kakariko performed significantly worse.

This makes me suspect something is remaining allocated or otherwise affecting performance after an area transition.

What I've tested:

Initially, I experimented with the following settings:

  • RX_VK_VRAM_MB=2560
  • RX_VK_FAST_RECLAIM=1
  • RX_VK_FAST_PRESSURE=80
  • RX_VK_FAST_UNUSED=8

These helped somewhat, but the persistent degradation still occurred.

I also experimented with RX_VK_MIRROR_MB=1024, which didn't resolve the problem, so I removed it.

Enabling RX_VK_COMPACT=1 made the biggest difference.

With compaction enabled alongside the earlier settings, the game became noticeably smoother. Areas that previously struggled could run at approximately 50–60 FPS again, and performance after transitions initially seemed substantially better.

I then tested RX_VK_COMPACT=1 by itself, without the other VRAM overrides.

Initially, this also performed very well. However, during a longer session, I experienced another prolonged FPS collapse. Performance eventually recovered, but there were periods around 12–21 FPS.

The longer log (hookshot_log(8).txt) contains 17 instances of:

vk: over the driver's video-memory allowance

The reported allocation sometimes reached approximately 7.6 GB against an allowance of about 7.1 GB.

Compaction was actively running and returning memory to the driver, but it wasn't consistently preventing these episodes.

Why I'm posting here:

I've read the description of this PR and noticed the problem involving textures being evicted after only 60 presents, then repeatedly reloaded when needed again.

I'm wondering whether that could explain why performance sometimes remains degraded after travelling between Kakariko and Hyrule Field.

I understand that the current fix has been tested with camera movement within an area, but I thought the loading-screen transition behaviour might also be worth investigating.

I've attached logs from the different configurations, including one where compaction substantially helped and a longer run where performance degraded again.

I'd be happy to test the proposed fix on my RTX 3070, particularly with repeated Kakariko ↔ Hyrule Field transitions, if a suitable build becomes available.

Thanks for the work on this project — the improvements over the last few days have already been crazyyy
03_Additional_long_test.txt
02_Compaction_only_long_test.txt
01_Compaction_plus_tweaks.txt
!

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is a clear diagnosis and the fix for the hold's pass reads right. Counting the dead render targets first and then not stopping at the first texture past the excess fixes the starvation you describe. The prepass uses the same conditions as the loop (need_dead, last_value > completed, shifted/drawn_over), so hold_dead is exactly what the loop then lets go: no dead target is counted and then kept. The fence check (tex->last_value > completed) still comes before any texture is chosen, so nothing the GPU may still read gets evicted. The header is plain integer math and builds the same with llvm-mingw and gcc/clang. Joey-Magnus (Linux, 4070 Ti), nullifyshot (Windows, 5080) and Tempus3214 (RX 6600 8 GB) all confirm it on hardware you didn't have. CI is green.

One thing I'd like settled before merge. The description says passes from the other rules (the budget, 90%, 97%) are unchanged, and the test says the driver's limit clears both lines. In practice that clearing almost never happens. The 90% rule and the reserve share the same pressure, so the reserve's excess is always at least the 90% rule's plus 10% of the allowance. Whenever the 90% rule fires under RX_VK_COMPACT, reserve < budget holds and the pass becomes a hold pass. The 97% rule only wins (budget < evict_to) when reclaim is over ~17% of the allowance. So past 90% and even past 97%, a live texture now has to be unused for 600 presents before it can go, where main let it go after min_unused (60). That is exactly the situation of the 8 GB reports: your own Limits note (93-96% stretches, nothing left to let go) and buasfe's log with the count over the allowance 17 times on a 3070. Paging past the allowance costs more than re-uploads. I'd keep the 600/1800 thresholds for the part of the excess that only the 85/80 lines ask for, and let textures past need_unused go for the part the 90%/97% rules ask for. For example, record the budget those rules set before the hold applies (rule_to) and give hold_target a third tier for it, so that rule's excess is served as before. A test case for "count over 90%: a texture unused 100 presents still goes" would pin it.

Nits:

  • buasfe's question (Kakariko <-> Hyrule Field transitions on an 8 GB 3070) isn't answered yet. A test build for them would cover the scene change and the small real card, both listed as untested.
  • The if (budget < evict_to) evict_to = hold_to = 0; comment ("the driver's limit asks for more") reads as if this is the usual path near the limit. A word that it rarely fires once the reserve is set would help the next reader.

Comment thread gpu/vk/vk_texture.cpp
// (and a reserve under that line, for the textures long unused: vram_compact.h. The pass below takes the others only
// for the hold's own line)
const uint64_t reserve = vram_compact::held_budget(budget, total_bytes, used, reclaim, allowed, vram_compact::reserve_pct(pct));
if (reserve < budget) budget = evict_to = reserve;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reserve's excess is always at least the 90% rule's (same pressure, a line 10 points lower), so whenever the 90% rule set budget above, this replaces it, and the pass that follows protects live textures for 600 presents instead of min_unused. See the review body.

Comment thread gpu/vk/vk_texture.cpp Outdated
budget = std::min(budget, total_bytes > over ? total_bytes - over : 0);
}
if (budget < evict_to) evict_to = 0; // (hookshot, RX_VK_COMPACT: the driver's limit asks for more: 7/8 of that, as before)
if (budget < evict_to) evict_to = hold_to = 0; // (hookshot, RX_VK_COMPACT: the driver's limit asks for more: 7/8 of that, as before)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only fires when the 97% rule's excess (by used) beats the reserve's (by used - reclaim, 17 points lower), which takes reclaim > ~17% of the allowance. So over 97% the hold pass, with its 600/1800-present thresholds, usually still decides. Unless that's intended, the driver-limit case needs its own target (review body).

Comment thread gpu/vk/vk_texture.cpp Outdated
// (hookshot, RX_VK_COMPACT: the hold's pass, above. A live texture stays once the excess over its line is covered: the
// reserve's line for one long unused, the hold's own for one unused ten seconds; one used since then stays)
if (by_hold && !overwritten) {
const uint64_t to = vram_compact::hold_target(frame_no - last, hold_to, evict_to);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: a third tier here, where a texture unused for need_unused may go while total_bytes - going is above what the 90%/97%/size rules alone asked for. Then the hold only changes what its own lines take.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2a407e1: rule_target / the need_unused tier in hold_target, with the worked 94% case in the test. Resolved from my side.

SadTransGirl and others added 3 commits November 11, 2026 12:35
It was opt-in while its hold could let go of textures in use. With the hold taking the dead render targets first and live
textures only when long unused, it is on unless RX_VK_COMPACT=0. The rule is vram_compact::enabled, which
tests/test_vram_compact.py runs: not set is on, 0 or empty is off.

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>
…VK_COMPACT=0 (RTX 4070 SUPER, DLAA, Hyrule Field)

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up. Since my last review the branch picked up a merge of main, the switch to on-by-default (vram_compact::enabled, with tests for unset / 1 / 0 / empty) and the new tested-configurations rows. The default flip itself is clean: enabled() matches the docs and the test, RX_VK_COMPACT=0 is a clear way back, and the on-vs-off walk numbers (633/408 frames over 25 ms down to 66/36, shadows never given back) are a good case for it.

Still open, and it matters more now:

  • The 90%/97% rules' passes still go through the hold's thresholds. The budget code (3817-3843) and hold_target are unchanged, so what I described last time still holds: whenever the 90% rule fires, the 80% reserve sets a lower budget from the same pressure and the pass becomes a hold pass, where a live texture has to be unused 600 presents (not min_unused, 60) before it can go for the hold's line, and 1,800 for the reserve's. The 97% rule only takes over when reclaim is above ~17% of the allowance. Before this push that only affected people who set RX_VK_COMPACT=1. With the default flipped it affects every 8 GB card, which is where the count really sits past 90% (your own Limits note on 93-96% stretches, buasfe's 3070 log with 17 "over the driver's allowance" lines). I'd still like the third tier (record what the 90%/97%/size rules asked for before the hold applies, and let textures past need_unused go for that part of the excess) plus a test like "count over 90%: a texture unused 100 presents still goes" before this ships on by default. If you think 600 presents is right even past 90%, say why and I'm happy to look at numbers, but I'd want them from a real 8 GB card, not the simulated allowance.

Nits, both still open:

  • buasfe's question (Kakariko <-> Hyrule Field transitions on an 8 GB 3070) still has no reply. With the default on, a test build for them is the easiest way to cover the scene change on a small real card.
  • The comment on if (budget < evict_to) evict_to = hold_to = 0; (3843) is unchanged; it still reads as the usual path near the limit, when it rarely fires once the reserve is set.

CI: everything passes except appimage, which died on a Launchpad connection reset (RemoteDisconnected) while installing packages, not on the build. A re-run should clear it. Mergeable, no conflicts.

Comment thread gpu/vk/vk_texture.cpp
bool compact_on() { // (hookshot, RX_VK_COMPACT=1: the textures in a pool of their own, compacted: compact_step)
static const bool v = getenv("RX_VK_COMPACT") && atoi(getenv("RX_VK_COMPACT")) != 0;
bool compact_on() { // (hookshot: the textures in a pool of their own, compacted: compact_step. On unless RX_VK_COMPACT=0)
static const bool v = vram_compact::enabled(getenv("RX_VK_COMPACT"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this on by default, the 90%/97% point from my last review (the hold's 600/1,800-present thresholds deciding passes the 90%/97% rules asked for) now applies to every small card without the switch set. I'd land the third tier before the default flips, or flip the default in a follow-up once that's in.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the third tier in, the 90%/97% passes again let textures unused 60 presents go for their part, so the default flip is fine by me.

@hckrman101

Copy link
Copy Markdown
Collaborator

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? The point in my last review (the 90%/97% rules' passes still using the hold's 600/1800-present thresholds, now on by default for every 8 GB card) is still open as well. Thanks!

Joey-Magnus added a commit to Joey-Magnus/Hookshot that referenced this pull request Nov 11, 2026
…p, 1,800-present age limit, 512 MB default

- The candidates' bytes are summed before the pass; walking least recently used first, the oldest go until the rest fit, so
  the cap is spent on the most recently used (as keep_finalized does).
- Only one used in the last 1,800 presents is a candidate: the threshold after which NotRexed#2198's reserve lets a long-unused
  texture go.
- RX_VK_KEEP_RESAMPLED_MB defaults to 512.
- resampled is cleared wherever released goes back to false (the texture is drawn again).
- keep_finalized leaves out what keep_resampled counts, so no texture is in both sums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SadTransGirl and others added 2 commits November 11, 2026 18:34
# Conflicts:
#	docs/tested-configurations.md
… the 90%/97%/size rules ask

Review of NotRexed#2198. The reserve's line is ten points under the 90% rule's and
counts the same pressure, so whenever the 90% rule set the budget the
reserve set a lower one and the pass became the hold's, where a live
texture has to be unused 600 presents for the hold's line and 1,800 for
the reserve's. The 97% rule only took the pass back with more than ~17%
of the allowance free in the textures' pool. So past 90% and past 97% of
the allowance nothing used in the last ten seconds went, where main lets
go of textures unused for 60 presents. With the compaction on by default
that is every card whose count sits there.

The budget the size, the 90% rule and the 97% rule ask for on their own
is now kept apart from the hold's (rule_budget), and hold_target has a
third tier: a texture unused for the eviction's own time (need_unused)
may go until the textures are down to 7/8 of that budget
(vram_compact::rule_target), which is what a pass over the budget cuts to
with the compaction off. The hold's 600 and 1,800 presents only decide
what the 85% and 80% lines add to that.

tests/test_vram_compact.py: the three tiers, with "the count over 90%: a
texture unused 100 presents still goes" and a worked case at 94% of a
7,000 MB allowance; source checks that rule_budget is taken before the
hold's lines and joined by the 97% rule's. The comment on the line that
clears the hold for the driver's limit says how rarely it fires.

Not run in the game: the rules' arithmetic and the source checks only.

Assisted-by: Claude Mythos 5.2
Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com>
@SadTransGirl

Copy link
Copy Markdown
Contributor Author

Thanks for the review. You were right, and it mattered more once the default flipped. Pushed:

  • Main merged in (2ed98c4), with the conflict in docs/tested-configurations.md resolved. It's a merge and not a rebase because I can't force-push from here.
  • The third tier (2a407e1). The budget that the size, the 90% rule and the 97% rule ask for on their own is now kept apart from the hold's (rule_budget). hold_target lets a texture unused for need_unused go until the textures are down to 7/8 of that budget, which is what a pass over the budget cuts to with the compaction off. The 600 and 1,800 presents now only decide what the 85% and 80% lines add on top.
  • Test: tests/test_vram_compact.py has "the count over 90%: a texture unused 100 presents still goes", a worked case at 94% of a 7,000 MB allowance, and source checks that rule_budget is taken before the hold's lines and joined by the 97% rule's.
  • The comment on if (budget < evict_to) evict_to = hold_to = 0; now says it rarely fires once the reserve is set, and why.

Not run in the game: this push is the rules' arithmetic and the source checks only, and I still have no real 8 GB card. The AppImage failure was a Launchpad connection reset; this push should re-run it.

@buasfe sorry for the slow answer. Your log (the count over the allowance 17 times on the 3070) is the case this push is for: past 90% the cache can again let go of textures unused for a second. I can't attach a build myself, but if you're able to build from this branch, a few Kakariko ↔ Hyrule Field transitions on your card would cover the two things nobody has tested here, a scene change and a real 8 GB card.

🤖 Generated with Claude Code

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is what I was after. Since my last review: I merged main into the branch (40d8e17) to clear the conflict; the only conflict was the docs/development.md table, where I kept your RX_VK_COMPACT row and main's new RX_VK_REACTIVE row. Nothing else in the merge differs from what main brought in.

Earlier points:

  • 90%/97% passes going through the hold's thresholds: fixed (2a407e1). rule_budget is taken after the size and 90% rules and before the hold's lines, the 97% rule joins it, and hold_target now gives a texture unused for need_unused the rules' part (rule_target, 7/8 of their budget, the same cut a pass makes with the compaction off), then the hold's line at 600 and the reserve's at 1,800, taking the lowest that applies. The dead render targets still count first through hold_met, and the fence and keep_rendered / finalized checks still come after, so nothing in use by the GPU is newly exposed. The 94%-of-7,000 MB case in the test works the numbers through, including "unused 100 presents still goes" and stopping once the rules' part is covered. With this, the description's "passes the other rules ask for are as they were" is true again.
  • The comment on if (budget < evict_to) evict_to = hold_to = 0;: fixed, it now says it's rare once the reserve is set and why.
  • buasfe's Kakariko <-> Hyrule Field question: answered. A run of this head on their 3070 would still be the best check, since the third tier is source checks and arithmetic only so far and no real 8 GB card has run it (Tempus3214's RX 6600 run was on the earlier version).

Overlaps: #1879 landed on main right after the merge; a trial 3-way merge of vk_texture.cpp is clean (it only adds overwritten to struct Evict / evict.push_back and the retire tally), and GitHub shows no conflict. #2368 inserts its keep check just above the hold check in the same loop, so whichever lands second resolves that spot and should make the hold's pass count the kept bytes, as agreed there.

CI: all green except Python source checks (windows-2022, part 1/2), which fails tests/test_native_astc_texture_count.py (its fixture's scanf under -Werror on Windows). Main fails the same test since #2288, so it isn't this PR.

Approving.

@hckrman101
hckrman101 merged commit 3e95ca6 into NotRexed:main Nov 11, 2026
11 of 12 checks passed
Joey-Magnus pushed a commit to Joey-Magnus/Hookshot that referenced this pull request Nov 11, 2026
…he second review

- Conflicts with NotRexed#2198 (compaction on by default): this rule still comes before the hold.
- The age limit is vram_compact::kReserveUnused itself and strict (unused < 1,800): a texture
  this rule keeps is never one the compaction's reserve may take; before, both claimed 1,800.
- Past this rule's cap, a small finalized resampled texture falls back to keep_finalized, as
  before the rule (joins its total at that point). Only while the rule is on: with it off the
  pre-pass already counts it.
- tested-configurations: the row says it measured the first version (1 GB, oldest first, no age limit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants