vk: VRAM, system RAM, GPU and replay CPU time: streams and staging out of VRAM, memory given back when idle, redundant barriers left out, draw setup cached #585

Closed
BlAnK182C-bak wants to merge 8 commits from vram-streams-staging into main
BlAnK182C-bak commented 2026-11-08 13:05:59 +00:00 (Migrated from github.com)

vk: VRAM, system RAM, GPU and replay CPU time: streams and staging out of VRAM, memory given back when idle, redundant barriers left out, draw setup cached

Refs #519 (4 GB RTX 3050 Laptop: textures 432 MB (budget 0 MB) with 961 MB of it free in the blocks); likely related: #254, #394.

This PR started with the first four commits (VRAM held by recording streams and staging; part 1 below). It now also carries a second set (part 2): system memory given back when idle, redundant full barriers left out (the GPU-time win), a GPU pass-timing profiler, and an anisotropy override; and a third (part 3): less CPU per draw on the replay thread. Every behaviour change has an env switch back to the old behaviour.


Part 1: ~1 GB of VRAM back on resizable-BAR cards (commits 1-4)

What was using the VRAM

On an RTX 4050 Laptop (6 GB, the driver allows ~5.7 GB) the renderer sat at the driver's limit (VRAM: 5668 MB used of 5708 MB allowed), and the texture budget was squeezed to ~2.0-2.2 GB, while textures (2.2 GB) and mirrors (752 MB) explained only ~3 GB of the ~4.8 GB VMA had allocated. The rest:

What Where VRAM
48 recording streams x 24 MB, created host-visible with UBO/SSBO/index usage draw_begin_recording -> create_buffer(host_visible) 1152 MB + 384 MB left free in their 64 MB blocks (two 24 MB streams a block)
staging arenas kept after loading (4 x 64 MB) staging() 256 MB
guest page table, 24 MB host-visible memory_guest_pages 24 MB (+40 MB free in its block)

With resizable BAR, NVIDIA exposes a DEVICE_LOCAL | HOST_VISIBLE type inside the VRAM heap, and VMA (VMA_MEMORY_USAGE_AUTO + HOST_ACCESS_SEQUENTIAL_WRITE, usage beyond TRANSFER) prefers it, so these CPU-written buffers land in VRAM. The streams' measured peak in Kokiri is ~760 KB of their 24 MB.

Changes

  1. 24 recording slots, not 48, unless submits are batched (RX_VK_RECORDINGS_48=1: 48). The 48 came with RX_VK_BATCH for RADV; 24 was the non-batched count before. Stays 48 with batching or RX_VK_PARALLEL > 1.
  2. Streams and the guest page table get memory objects of their own (RX_VK_BLOCK_BUFFERS=1: shares of 64 MB blocks), so a 24 MB stream no longer leaves 16 MB of a block empty.
  3. The stream's peak is logged, and RX_VK_STREAM_MB=<n> sets its size. Log: vk: the recording streams: N of M MB, the most one held X KB. (Part 2 makes the streams growable, see below.)
  4. On cards of 6 GB or less, idle staging arenas beyond one (64 MB) are given back (RX_VK_STAGING_KEEP_MB=256: as before).

Results (part 1)

Kokiri Forest (Link on his house's balcony), 20 s settle, then 30 s standing and 15 s turning the camera. "Before" and "after" differ only by commits 1-4, measured on a local branch carrying other local changes, identical between the two runs.

before after
texture budget 2049 MB (standing) / 2192 MB (turning) 4036 MB
textures resident 2180 / 2345 MB 2954 / 3027 MB
textures made a frame while turning (evict/remake churn) 4.5 1.3
VRAM, whole GPU (nvidia-smi) 5813 / 5836 MiB 5593 / 5594 MiB
driver-counted use 5668 / 5691 of 5708 MB (at the limit) 5440 of 5701 MB
fps standing / turning 38.7 / 39.9 39.5 / 42.6
frame time median / 99% / worst 25.2 / 37.0 / 108.6 ms 24.7 / 30.2 / 49.2 ms
slow submits / slow presents while turning 19 / 7 8 / 1

Part 2: memory given back when idle, redundant barriers left out (commits 5-7)

GPU time: redundant full barriers left out (on by default, RX_VK_BARRIER_DEDUPE_OFF=1: as before)

  • New gpu/vk/vk_work.h counts GPU work recorded on a command buffer (every work-doing vkCmd* goes through its macros; all files that record work include it). A full barrier that directly follows another on the same command buffer with nothing recorded between them is left out; transfer_barrier goes through the same check. Off while parallel replay is on. The work count is a fetch_add (two threads can record at once).
  • In Kokiri: ~615 full barriers a frame, 192 of them left out (31%). The frame log line: a frame: N full barriers recorded, M left out (nothing recorded since the one before).
  • This is the most correctness-sensitive change in the PR; the switch turns it off.

System RAM and VRAM given back when idle (each on by default, with its switch)

Change Switch back
Recording streams start at 2 MB and grow (moved to a bigger stream when a recording needs it) instead of a fixed 24 MB that skipped draws when full; extra streams are given back after 120 recordings that fit the first RX_VK_STREAM_GROW_OFF=1 (24 MB, skips), RX_VK_STREAM_KEEP=1
Capture buffers in size classes from 2 MB (they were never under 16 MB, for ~1 MB captures: 25 kept ones were 413 MB of resident host-cached memory); dedicated memory from 16 MB; spares capped at 32 MB on cards of 6.5 GB or less RX_VK_CAP_MIN_MB=16, RX_VK_CAP_BLOCKS=1, RX_VK_CAP_SPARE_MB=0
The 160 MB BC3 scratch is made on demand in dedicated memory and given back after 20 s without an upload RX_VK_BC_IDLE_S=0, RX_VK_BC_POOL=1
The texture budget counts what the allocator gave (about 8% more than the texel estimate), so it stops overshooting RX_VK_COUNT_BY_ESTIMATE=1
A replaced TAAU history image is destroyed 120 presents later (it was leaked) RX_VK_TAAU_HIST_LEAK=1
flush_texture reads back into cached host memory, not through the sequential-write buffer RX_VK_FLUSH_UNCACHED=1
Staging arenas nothing staged into for 30 s are given back RX_VK_STAGING_IDLE_S=0
Shadow copies of texture pages no capture took in 256 submits are dropped once they total 32 MB (not only at the 256 MB cap) RX_VK_SHADOW_SWEEP_OFF=1
Linux (glibc): a worker runs malloc_trim(0) when glibc's arenas hold 192 MB free; a vk: malloc: census line every 1800 presents RX_VK_MALLOC_TRIM_MB=0
Knobs, off by default: RX_VK_DESC_POOL_DIV=<n>, RX_VK_IMAGE_BLOCK_MB=<n>, HOS_NO_THP=1 (Linux: transparent huge pages off for the process) -

tests/test_mem_trim.py checks the code keeps what these trims must: what is kept as an offset into a stream names its buffer or reserves its room first (a stream may move mid-recording), every user of the BC3 scratch asks for it first, and capture buffers are sized by class and counted out as well as in.

Diagnostics (off by default)

  • RX_VK_PASS_TIMING=1|2 (alias RX_VK_GPU_PROFILE): GPU timestamps around passes, dispatches, barriers (by origin), copies, mirror syncs and write-backs, reported every 600 presents, with the 25 costliest spans. =2 adds a timestamp per program run inside passes.
  • Every 120 presents: the game's barriers by their NVN flags, and the bytes the GPU read over PCIe from pools bound in guest memory.
  • RX_VK_ANISO=<n>: samplers with linear min and mip filters get at least n-times anisotropy (default 0: the game's own). The game asks for 0-2x. A vk: sampler anisotropy: the game asks for ... line is logged once per distinct value.

Results (part 2)

Same machine and method as part 1, save in Kokiri on Link's balcony, Firefox and Discord closed. Before = a local branch with part 1; after = the same branch plus these commits. Caveat: that local "after" branch also carried three other open PRs' commits (#479, #423, #435), which are not in this PR; with #435 the replay's descriptor time rose ~1 ms (3.35 -> 4.32 ms standing; its bookkeeping found almost nothing to reuse in this game), so the CPU replay figures below understate this PR. The exact branch pushed here has been compiled for Linux x86_64 (all gpu/vk/vk_*.cpp), tests/test_mem_trim.py passes, and a run of this exact branch is still to come; I'll post it as a comment.

before after
fps standing / turning 40.1 / 42.6 44.2 / 45.5 (re-run: 43.4 / 43.4)
GPU time a frame, standing / turning 20.3 / 18.9 ms 17.6 / 15.8 ms (re-run: 17.7 / 16.3)
GPU busy (nvidia-smi) 89 / 90% 86 / 84%
game RSS, standing / turning 10.15 / 9.49 GB 9.22 / 9.17 GB
RSS from smaps_rollup, 10 s into standing 9.79 GB 9.05 GB
Vulkan system-heap allocations 520 MB 151 MB (part of this may be #479's import retiring)
VRAM, whole GPU (nvidia-smi) 5567 / 5557 MiB 5445 / 5610 MiB (more of it holds textures: 3092 / 3635 MB resident vs 2501 / 3279)
CPU (whole process) 333 / 356% 340 / 363% (at ~9% more frames)
frame time median / 99% 24.3 / 31.6 ms 22.2 / 32.6 ms
flashes (frame log, large / all) 4 / 54 2 / 51

Where the GPU frame goes after this PR (RX_VK_PASS_TIMING=1, ~18 ms profiled): render passes 52%, compute dispatches 32%, write-back to guest memory 7% (13 MB a frame), barriers 6%, uploads 8%.

Tried and left out

  • Keeping the open render pass across the game's barrier when its draws stored nothing (~80 a frame qualify): with all of them kept, GPU time went up 2 ms (19.8 / 18.6 ms vs 17.7 / 16.3); with only barriers lacking INVALIDATE_TEXTURE/ORDER_INDIRECT_DATA kept (~3 a frame), no gain. On NVIDIA a barrier inside an open pass costs more than ending and restarting it.
  • No STORAGE usage on COMPRESSIBLE, non-IMAGE textures (so the driver might keep render targets compressed): the game's compute passes write into some of them, so their storage views fail (vk: view of texture ... not made) and the lighting goes wrong (dark, posterized, a black title movie); it was also slower (36 vs 43 fps).

Part 3: less CPU per draw on the replay thread (commit 8)

The replay thread is on the critical path (about 15 ms of a 22 ms frame here, ~2,400 draws). Four places did per draw what the draw before had already done; now they keep it, keyed by exactly what it depends on. Same descriptors, state, banks and programs come out.

Change Switch back
Pushed descriptors kept as one flat vector of words, compared in place (it was a vector of vectors: about a dozen allocations a draw on program changes) RX_VK_PUSH_STATE_OLD=1
Dynamic state's "same as the draw before" check keyed by the program's write-mask signature, not its pointer (every program change recomputed all dynamic state) RX_VK_DYN_PROG_KEY=1
A graphics stage's c0/c2 driver banks kept while their own inputs (texture/sampler/image handles; storage-buffer table) are unchanged, not refilled on every uniform-buffer bind RX_VK_BANK_GEN_OFF=1
The last-program-pick cache survives uniform rebinds for programs whose answers are plain c2 handles RX_VK_PICK_BIND_GEN=1
RX_VK_PROF_FINE=1 (off by default): the draw's time split into finer sections, and what each draw does -

tests/test_push_state.py, test_bank_gen.py, test_pick_tex_gen.py and test_same_dyn.py check the invariants. Program::wm_sig is atomic (replay threads may ask for the same program at once).

Measured so far only as part of a local build that carries these changes with others: the replay thread's time a frame went from ~16.1 ms to ~12.7 ms standing (and fps 43.4 -> 47.8, with the 3D scene at a higher resolution), but that comparison is not isolated (other changes and a different save). An isolated A/B of this exact change (same build, the four switches off vs on) is next; I'll post it as a comment. This branch's commit compiles for Linux x86_64 (all gpu/vk/vk_*.cpp) and its tests pass; it has not been run as this exact branch yet.


Tested on

  • Laptop: Intel Core i7-12650H (6P+4E, 16 threads), 16 GB RAM
  • GPU: NVIDIA GeForce RTX 4050 Laptop GPU, 6 GB (resizable BAR; the driver allows ~5.7 GB), PRIME offload from the Intel UHD iGPU (__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only)
  • Driver: NVIDIA 615.78.08, Vulkan 1.4.351
  • OS: CachyOS, Linux 7.3.0-4-cachyos, KDE Plasma (Wayland)
  • Display: external 2560x1440 @ 144 Hz, game fullscreen at 2560x1440
  • Game settings (Insert menu): Quality preset Custom, Resolution 100%, Upscaling Off, Sharpness 100, Handheld profile Off, Frame generation Off, Frame pacing Off, Game speed Real time
  • Measurement: scripted input (HOS_INPUT_LIVE), the game's 120-frame stat lines, nvidia-smi every 2 s, /proc/<pid>/smaps_rollup, and RX_VK_FRAMELOG + scripts/framelog.py for frame times and flashes.
  • Build: from source (nx2/make_game.py, Linux). This branch is on main 7e9bf73; every changed gpu/vk file compiles for x86_64 Linux.
  • Not tested: Windows, AMD, Intel. Nothing here is Linux-only except malloc_trim and HOS_NO_THP (both under #ifdef __linux__/glibc), but I have not built or run it elsewhere.

🤖 Generated with Claude Code

## vk: VRAM, system RAM, GPU and replay CPU time: streams and staging out of VRAM, memory given back when idle, redundant barriers left out, draw setup cached Refs #519 (4 GB RTX 3050 Laptop: `textures 432 MB (budget 0 MB)` with `961 MB of it free in the blocks`); likely related: #254, #394. This PR started with the first four commits (VRAM held by recording streams and staging; part 1 below). It now also carries a second set (part 2): system memory given back when idle, redundant full barriers left out (the GPU-time win), a GPU pass-timing profiler, and an anisotropy override; and a third (part 3): less CPU per draw on the replay thread. Every behaviour change has an env switch back to the old behaviour. --- ## Part 1: ~1 GB of VRAM back on resizable-BAR cards (commits 1-4) ### What was using the VRAM On an RTX 4050 Laptop (6 GB, the driver allows ~5.7 GB) the renderer sat at the driver's limit (`VRAM: 5668 MB used of 5708 MB allowed`), and the texture budget was squeezed to ~2.0-2.2 GB, while textures (2.2 GB) and mirrors (752 MB) explained only ~3 GB of the ~4.8 GB VMA had allocated. The rest: | What | Where | VRAM | |---|---|---| | 48 recording streams x 24 MB, created host-visible with UBO/SSBO/index usage | `draw_begin_recording` -> `create_buffer(host_visible)` | **1152 MB** + **384 MB** left free in their 64 MB blocks (two 24 MB streams a block) | | staging arenas kept after loading (4 x 64 MB) | `staging()` | 256 MB | | guest page table, 24 MB host-visible | `memory_guest_pages` | 24 MB (+40 MB free in its block) | With resizable BAR, NVIDIA exposes a DEVICE_LOCAL | HOST_VISIBLE type inside the VRAM heap, and VMA (`VMA_MEMORY_USAGE_AUTO` + `HOST_ACCESS_SEQUENTIAL_WRITE`, usage beyond TRANSFER) prefers it, so these CPU-written buffers land in VRAM. The streams' measured peak in Kokiri is ~760 KB of their 24 MB. ### Changes 1. **24 recording slots, not 48, unless submits are batched** (`RX_VK_RECORDINGS_48=1`: 48). The 48 came with RX_VK_BATCH for RADV; 24 was the non-batched count before. Stays 48 with batching or RX_VK_PARALLEL > 1. 2. **Streams and the guest page table get memory objects of their own** (`RX_VK_BLOCK_BUFFERS=1`: shares of 64 MB blocks), so a 24 MB stream no longer leaves 16 MB of a block empty. 3. **The stream's peak is logged, and `RX_VK_STREAM_MB=<n>` sets its size.** Log: `vk: the recording streams: N of M MB, the most one held X KB`. (Part 2 makes the streams growable, see below.) 4. **On cards of 6 GB or less, idle staging arenas beyond one (64 MB) are given back** (`RX_VK_STAGING_KEEP_MB=256`: as before). ### Results (part 1) Kokiri Forest (Link on his house's balcony), 20 s settle, then 30 s standing and 15 s turning the camera. "Before" and "after" differ only by commits 1-4, measured on a local branch carrying other local changes, identical between the two runs. | | before | after | |---|---|---| | texture budget | 2049 MB (standing) / 2192 MB (turning) | **4036 MB** | | textures resident | 2180 / 2345 MB | **2954 / 3027 MB** | | textures made a frame while turning (evict/remake churn) | 4.5 | **1.3** | | VRAM, whole GPU (`nvidia-smi`) | 5813 / 5836 MiB | **5593 / 5594 MiB** | | driver-counted use | 5668 / 5691 of 5708 MB (at the limit) | **5440 of 5701 MB** | | fps standing / turning | 38.7 / 39.9 | **39.5 / 42.6** | | frame time median / 99% / worst | 25.2 / 37.0 / 108.6 ms | **24.7 / 30.2 / 49.2 ms** | | slow submits / slow presents while turning | 19 / 7 | **8 / 1** | --- ## Part 2: memory given back when idle, redundant barriers left out (commits 5-7) ### GPU time: redundant full barriers left out (on by default, `RX_VK_BARRIER_DEDUPE_OFF=1`: as before) - New `gpu/vk/vk_work.h` counts GPU work recorded on a command buffer (every work-doing `vkCmd*` goes through its macros; all files that record work include it). A full barrier that directly follows another on the same command buffer with nothing recorded between them is left out; `transfer_barrier` goes through the same check. Off while parallel replay is on. The work count is a `fetch_add` (two threads can record at once). - In Kokiri: **~615 full barriers a frame, 192 of them left out (31%)**. The frame log line: `a frame: N full barriers recorded, M left out (nothing recorded since the one before)`. - This is the most correctness-sensitive change in the PR; the switch turns it off. ### System RAM and VRAM given back when idle (each on by default, with its switch) | Change | Switch back | |---|---| | Recording streams start at 2 MB and grow (moved to a bigger stream when a recording needs it) instead of a fixed 24 MB that skipped draws when full; extra streams are given back after 120 recordings that fit the first | `RX_VK_STREAM_GROW_OFF=1` (24 MB, skips), `RX_VK_STREAM_KEEP=1` | | Capture buffers in size classes from 2 MB (they were never under 16 MB, for ~1 MB captures: 25 kept ones were 413 MB of resident host-cached memory); dedicated memory from 16 MB; spares capped at 32 MB on cards of 6.5 GB or less | `RX_VK_CAP_MIN_MB=16`, `RX_VK_CAP_BLOCKS=1`, `RX_VK_CAP_SPARE_MB=0` | | The 160 MB BC3 scratch is made on demand in dedicated memory and given back after 20 s without an upload | `RX_VK_BC_IDLE_S=0`, `RX_VK_BC_POOL=1` | | The texture budget counts what the allocator gave (about 8% more than the texel estimate), so it stops overshooting | `RX_VK_COUNT_BY_ESTIMATE=1` | | A replaced TAAU history image is destroyed 120 presents later (it was leaked) | `RX_VK_TAAU_HIST_LEAK=1` | | `flush_texture` reads back into cached host memory, not through the sequential-write buffer | `RX_VK_FLUSH_UNCACHED=1` | | Staging arenas nothing staged into for 30 s are given back | `RX_VK_STAGING_IDLE_S=0` | | Shadow copies of texture pages no capture took in 256 submits are dropped once they total 32 MB (not only at the 256 MB cap) | `RX_VK_SHADOW_SWEEP_OFF=1` | | Linux (glibc): a worker runs `malloc_trim(0)` when glibc's arenas hold 192 MB free; a `vk: malloc:` census line every 1800 presents | `RX_VK_MALLOC_TRIM_MB=0` | | Knobs, off by default: `RX_VK_DESC_POOL_DIV=<n>`, `RX_VK_IMAGE_BLOCK_MB=<n>`, `HOS_NO_THP=1` (Linux: transparent huge pages off for the process) | - | `tests/test_mem_trim.py` checks the code keeps what these trims must: what is kept as an offset into a stream names its buffer or reserves its room first (a stream may move mid-recording), every user of the BC3 scratch asks for it first, and capture buffers are sized by class and counted out as well as in. ### Diagnostics (off by default) - `RX_VK_PASS_TIMING=1|2` (alias `RX_VK_GPU_PROFILE`): GPU timestamps around passes, dispatches, barriers (by origin), copies, mirror syncs and write-backs, reported every 600 presents, with the 25 costliest spans. `=2` adds a timestamp per program run inside passes. - Every 120 presents: the game's barriers by their NVN flags, and the bytes the GPU read over PCIe from pools bound in guest memory. - `RX_VK_ANISO=<n>`: samplers with linear min and mip filters get at least n-times anisotropy (default 0: the game's own). The game asks for 0-2x. A `vk: sampler anisotropy: the game asks for ...` line is logged once per distinct value. ### Results (part 2) Same machine and method as part 1, save in Kokiri on Link's balcony, Firefox and Discord closed. Before = a local branch with part 1; after = the same branch plus these commits. **Caveat:** that local "after" branch also carried three other open PRs' commits (#479, #423, #435), which are not in this PR; with #435 the replay's descriptor time rose ~1 ms (3.35 -> 4.32 ms standing; its bookkeeping found almost nothing to reuse in this game), so the CPU replay figures below understate this PR. The exact branch pushed here has been compiled for Linux x86_64 (all `gpu/vk/vk_*.cpp`), `tests/test_mem_trim.py` passes, and a run of this exact branch is still to come; I'll post it as a comment. | | before | after | |---|---|---| | fps standing / turning | 40.1 / 42.6 | **44.2 / 45.5** (re-run: 43.4 / 43.4) | | GPU time a frame, standing / turning | 20.3 / 18.9 ms | **17.6 / 15.8 ms** (re-run: 17.7 / 16.3) | | GPU busy (`nvidia-smi`) | 89 / 90% | 86 / 84% | | game RSS, standing / turning | 10.15 / 9.49 GB | **9.22 / 9.17 GB** | | RSS from `smaps_rollup`, 10 s into standing | 9.79 GB | **9.05 GB** | | Vulkan system-heap allocations | 520 MB | **151 MB** (part of this may be #479's import retiring) | | VRAM, whole GPU (`nvidia-smi`) | 5567 / 5557 MiB | 5445 / 5610 MiB (more of it holds textures: 3092 / 3635 MB resident vs 2501 / 3279) | | CPU (whole process) | 333 / 356% | 340 / 363% (at ~9% more frames) | | frame time median / 99% | 24.3 / 31.6 ms | **22.2** / 32.6 ms | | flashes (frame log, large / all) | 4 / 54 | 2 / 51 | Where the GPU frame goes after this PR (`RX_VK_PASS_TIMING=1`, ~18 ms profiled): render passes 52%, compute dispatches 32%, write-back to guest memory 7% (13 MB a frame), barriers 6%, uploads 8%. ### Tried and left out - **Keeping the open render pass across the game's barrier when its draws stored nothing** (~80 a frame qualify): with all of them kept, GPU time went **up** 2 ms (19.8 / 18.6 ms vs 17.7 / 16.3); with only barriers lacking INVALIDATE_TEXTURE/ORDER_INDIRECT_DATA kept (~3 a frame), no gain. On NVIDIA a barrier inside an open pass costs more than ending and restarting it. - **No STORAGE usage on COMPRESSIBLE, non-IMAGE textures** (so the driver might keep render targets compressed): the game's compute passes write into some of them, so their storage views fail (`vk: view of texture ... not made`) and the lighting goes wrong (dark, posterized, a black title movie); it was also slower (36 vs 43 fps). ## Part 3: less CPU per draw on the replay thread (commit 8) The replay thread is on the critical path (about 15 ms of a 22 ms frame here, ~2,400 draws). Four places did per draw what the draw before had already done; now they keep it, keyed by exactly what it depends on. Same descriptors, state, banks and programs come out. | Change | Switch back | |---|---| | Pushed descriptors kept as one flat vector of words, compared in place (it was a vector of vectors: about a dozen allocations a draw on program changes) | `RX_VK_PUSH_STATE_OLD=1` | | Dynamic state's "same as the draw before" check keyed by the program's write-mask signature, not its pointer (every program change recomputed all dynamic state) | `RX_VK_DYN_PROG_KEY=1` | | A graphics stage's c0/c2 driver banks kept while their own inputs (texture/sampler/image handles; storage-buffer table) are unchanged, not refilled on every uniform-buffer bind | `RX_VK_BANK_GEN_OFF=1` | | The last-program-pick cache survives uniform rebinds for programs whose answers are plain c2 handles | `RX_VK_PICK_BIND_GEN=1` | | `RX_VK_PROF_FINE=1` (off by default): the draw's time split into finer sections, and what each draw does | - | `tests/test_push_state.py`, `test_bank_gen.py`, `test_pick_tex_gen.py` and `test_same_dyn.py` check the invariants. `Program::wm_sig` is atomic (replay threads may ask for the same program at once). Measured so far only as part of a local build that carries these changes with others: the replay thread's time a frame went from ~16.1 ms to ~12.7 ms standing (and fps 43.4 -> 47.8, with the 3D scene at a higher resolution), but that comparison is not isolated (other changes and a different save). An isolated A/B of this exact change (same build, the four switches off vs on) is next; I'll post it as a comment. This branch's commit compiles for Linux x86_64 (all `gpu/vk/vk_*.cpp`) and its tests pass; it has not been run as this exact branch yet. --- ### Tested on - **Laptop:** Intel Core i7-12650H (6P+4E, 16 threads), 16 GB RAM - **GPU:** NVIDIA GeForce RTX 4050 Laptop GPU, 6 GB (resizable BAR; the driver allows ~5.7 GB), PRIME offload from the Intel UHD iGPU (`__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only`) - **Driver:** NVIDIA 615.78.08, Vulkan 1.4.351 - **OS:** CachyOS, Linux 7.3.0-4-cachyos, KDE Plasma (Wayland) - **Display:** external 2560x1440 @ 144 Hz, game fullscreen at 2560x1440 - **Game settings (Insert menu):** Quality preset Custom, Resolution 100%, Upscaling Off, Sharpness 100, Handheld profile Off, Frame generation Off, Frame pacing Off, Game speed Real time - **Measurement:** scripted input (`HOS_INPUT_LIVE`), the game's 120-frame stat lines, `nvidia-smi` every 2 s, `/proc/<pid>/smaps_rollup`, and `RX_VK_FRAMELOG` + `scripts/framelog.py` for frame times and flashes. - **Build:** from source (`nx2/make_game.py`, Linux). This branch is on `main` 7e9bf73; every changed `gpu/vk` file compiles for x86_64 Linux. - **Not tested:** Windows, AMD, Intel. Nothing here is Linux-only except `malloc_trim` and `HOS_NO_THP` (both under `#ifdef __linux__`/glibc), but I have not built or run it elsewhere. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
shayderrr commented 2026-11-08 18:24:57 +00:00 (Migrated from github.com)

Fix conflicts please

Fix conflicts please
BlAnK182C-bak commented 2026-11-08 18:51:18 +00:00 (Migrated from github.com)

@shayderrr rebased and resolved conflicts

@shayderrr rebased and resolved conflicts
BlAnK182C-bak commented 2026-11-09 05:29:47 +00:00 (Migrated from github.com)

Hi, @NotRexed was just curious as to why this PR was closed

Hi, @NotRexed was just curious as to why this PR was closed
Commenting is not possible because the repository is archived.
No description provided.