Repository navigation
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 into
Conversation
BlAnK182C-bak
force-pushed
the
vram-streams-staging
branch
from
November 8, 2026 14:18
bed2cd6 to
322051c
Compare
Collaborator
|
Fix conflicts please |
…are batched (RX_VK_RECORDINGS_48=1: 48) Each recording slot holds a 24 MB stream (vk_draw.cpp: uniform banks, uniform copies, quad indices) made with VMA_MEMORY_USAGE_AUTO + HOST_ACCESS_SEQUENTIAL_WRITE and uniform/storage/index usage. VMA reads that as "the GPU reads it, the CPU writes it in order": DEVICE_LOCAL | HOST_VISIBLE preferred, which on an NVIDIA card with resizable BAR (the RTX 4050 Laptop: memory type 4, in the 6 GB heap) is VRAM. 48 slots, all touched within 8 frames (6 submits a frame), are 1152 MB, and 24 MB in 64 MB blocks is two to a block: 1536 MB of the card's 5.7 GB held by the streams alone (the low-vram log: 4839 MB allocated = 2450 textures + 752 mirrors + 1152 streams + 256 staging + 160 BC3 scratch + 24 page table + 8; 607 MB free in the blocks, ~420 of it theirs). The driver's count sat at its limit and squeezed the texture budget to 2318 MB of ~4000. The 48 came with RX_VK_BATCH (held submits wait for a slot's turn: RADV). Without batching the game waits for the last frame's GPU work before it submits the next, so ~18 slots at most are in flight and 24 was the number before. Batched submits and a parallel replay (RX_VK_PARALLEL > 1: a slot a segment) keep 48. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts of their own (RX_VK_BLOCK_BUFFERS=1: shares of 64 MB blocks) A 24 MB buffer is under half of VMA's 64 MB block (over it VMA goes dedicated by itself), so two fit in a block and 16 MB of it stays free: with the streams in VRAM (previous commit) 24 blocks of 1536 MB held 1152 MB, and the page table (24 MB) sat in a block of its own with 40 MB free. Those free bytes are what the log's "N MB of it free in the blocks" counts, and the driver counts them as the process's. create_buffer takes a `dedicated` flag now; the streams and the page table pass it. Their count of memory objects (~25) is nothing against the 4096 the driver allows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… line), RX_VK_STREAM_MB=<n> sets its size (24) The 24 MB per recording was never full in any log (no "stream is full" line), but nothing says how much a recording uses: the banks c0/c2 (at most 2560 B a stage a draw, repeated ones shared), the c1s, the uniform overlays (57 of up to 64 KB a frame) and the quad indices. Logged every 3000 recordings (~10 s): the streams made, their size, the most one held. With a peak of a few MB, RX_VK_STREAM_MB=8 (or 4) takes another 380 MB (or 480) of VRAM off the 24 streams; a recording that overflows skips its quads and reads null uniforms, so size it by that peak with room. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… are given back (RX_VK_STAGING_KEEP_MB=256: as before) The staging arenas are create_buffer(TRANSFER_SRC | STORAGE, host visible): STORAGE is device access, so VMA prefers DEVICE_LOCAL | HOST_VISIBLE for them, and on a card with resizable BAR that is VRAM (the RTX 4050 Laptop's memory type 4, in the 6 GB heap). The low-vram log: arenas 3 and 4 (288 and 64 MB: a 12288 x 12288 texture's upload at the scene load) were made between presents 2280 and 2400, and "staging 256 MB in 4 arenas" stands at every 600-frame look after that, with no arena made again: ~nothing is staged in Kokiri gameplay. 192 MB of the 5.7 GB VRAM the texture budget fights for, idle. A scene change that stages again makes its arenas again (a 64 MB host-visible allocation each); the ones over 64 MB were already given back when idle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h, staging, glibc heap) - A recording's stream is 2 MB, and a recording that outgrows it moves to a bigger one instead of skipping draws; extra streams are given back after 120 recordings that fit the first (RX_VK_STREAM_GROW_OFF=1: 24 MB and skips; RX_VK_STREAM_KEEP=1: kept). - A capture's buffer by size class (2 MB up), not 16 MB each; dedicated memory from 16 MB; the spare buffers capped at 32 MB on a card of 6.5 GB or less (RX_VK_CAP_MIN_MB=16, RX_VK_CAP_BLOCKS=1, RX_VK_CAP_SPARE_MB=0: as before). - The BC3 scratch (160 MB) is made at the first upload that needs it, in memory of its own, and given back after 20 s without one (RX_VK_BC_IDLE_S=0: kept; RX_VK_BC_POOL=1: from the pool). - The texture budget counts the allocator's size of an image, not its texels (RX_VK_COUNT_BY_ESTIMATE=1: the texels). - The TAAU stand-in's replaced history image is destroyed 120 presents later (RX_VK_TAAU_HIST_LEAK=1: left). - flush_texture reads back into cached host memory (RX_VK_FLUSH_UNCACHED=1: the sequential-write buffer). - Shadow copies of texture pages no capture took in 256 submits are dropped once there are 32 MB of them (RX_VK_SHADOW_SWEEP_OFF=1: only at the 256 MB cap). - A staging arena nothing has staged into for 30 s is given back, the last one too (RX_VK_STAGING_IDLE_S=0: as before). - Linux (glibc): malloc_trim(0) on a worker when the arenas hold 192 MB or more free, and a "vk: malloc:" census line, every 1800 presents (RX_VK_MALLOC_TRIM_MB=0: never). - Knobs, defaults unchanged: 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: the stream move, the BC3 scratch's users and the capture buffers' counts keep their invariants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is left out; RX_VK_PASS_TIMING GPU profile - vk_work.h counts the GPU work recorded on a command buffer (an atomic add; every work-doing vkCmd goes through its macros). A full barrier right after another one on the same command buffer, nothing recorded between, is left out; the last-barrier state is per thread, and transfer_barrier goes through the same check. Off during parallel replay. RX_VK_BARRIER_DEDUPE_OFF=1: all recorded. The frame log tells the barriers recorded and left out. - 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, both kinds of command buffer summed, reported every 600 presents with the costliest spans; =2 adds one per program run inside passes. - The frame log counts the game's nvnCommandBufferBarrier calls by their NVN bits, and the 'bound in guest memory, by pool' line adds the bytes of the bindings (what the GPU may read over PCIe). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and mip levels get at least n-times anisotropy (default 0: the game's own); the log tells each anisotropy the game asks for The sampler's key and its per-slot cache's key carry n, so a cached sampler is never one made for another value. A sampler with a nearest min filter or mip filter keeps the game's value (a depth-compare sampler too unless it filters that way). n is clamped to the device's limit (and to 16) and needs samplerAnisotropy. "vk: sampler anisotropy: the game asks for ..." is logged once for each value (16 at most), to learn what the game sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e by what the program masks, banks by their bindings' generation, program picks across uniform rebinds A draw's CPU cost on the replay thread, from four places where most of the work found what the draw before it had found. Same descriptors, state, banks and programs as before; only what is kept, and what it is kept by, changes. - Pushed descriptors, one vector of words (RX_VK_PUSH_STATE_OLD=1: the vector of vectors). A program change pushes every write and remembers them for the draws after it, which push only the writes that changed. The memory was a vector of vectors resized to each program's writes (3 allocations a write grown word by word, the ones cut off freed, a dozen of each a draw), and the changed-only path built every write's words into a scratch vector to compare them. Now the words of all writes are in one vector with each write's start, built without allocating once grown; a later draw compares each write against its words in place and stores the new ones where they differ. Default on: same pushes, same words. tests/test_push_state.py. - Dynamic state by what the program masks (RX_VK_DYN_PROG_KEY=1: by the program). dynamic_state returns early when the packed inputs equal the draw before's, but the pack held the program's pointer (its write masks come from the fragment stage), so every program change, most draws, worked the whole state out again. It holds program_wm_sig now: the fragment stage's stored outputs and their components, all target_write_mask reads of the program (tests/test_same_dyn.py keeps the two in step). Default on. Program::wm_sig is atomic: replay threads may ask for the same program at once. - Driver banks by the generation of the bindings they are made from (RX_VK_BANK_GEN_OFF=1: by bind_gen). uniform_range kept a stage's c0 / c2 bank only while bind_gen stood, and OoT binds a uniform buffer for nearly every draw, so each draw filled the banks of all its stages again to find, mostly, the same words. A graphics stage's c2 is made from its texture, sampler and image handles and its c0 from its storage buffer table, so NvnState keeps what bind_gen was when each last changed (tex_gen, ssbo_gen per stage) and the bank stands while that does. Compute, task and mesh stages stay on bind_gen. A state's generations are made afresh with its bind_gen when the replay takes another queue's state. Default on. tests/test_bank_gen.py. - Program picks across uniform rebinds (RX_VK_PICK_BIND_GEN=1: as before). The pair of last picks in program_for stood only while bind_gen did. A graphics program whose every answer is a plain c2 handle (answer_stage_mask: no handle program, no other bank) now stands while those stages' tex_gen do; handle programs, compute and task / mesh programs keep bind_gen. The "program picks" log line counts the picks taken this way. Default on; builds on the banks' tex_gen. tests/test_pick_tex_gen.py. - RX_VK_PROF_FINE=1 (diagnostic, off by default; off, each site is a test of one constant): a frame line "a frame (fine)" with the descriptor build split into the driver banks, c1, user uniform buffers, storage buffers and textures, and the commands split into hazards and the pass, the pipeline bind, the descriptor bind and its push call, the push constants, dynamic state, the vertex and index binds and the draw call, in ms a frame; and a second line with what a draw does (descriptors and writes built, bank fills and bytes, stream copies, full and changed-only pushes, pipeline binds, dynamic state from the draw before or worked out and its commands, vertex and index binds, pipeline keys from the fast pair or the thread's map). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BlAnK182C-bak
force-pushed
the
vram-streams-staging
branch
from
November 8, 2026 18:50
11d2c1a to
0a3b57a
Compare
Contributor
Author
|
@shayderrr rebased and resolved conflicts |
NotRexed
added a commit
that referenced
this pull request
Nov 8, 2026
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 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed
added a commit
that referenced
this pull request
Nov 8, 2026
NotRexed
added a commit
that referenced
this pull request
Nov 8, 2026
perf(linux): hand guest memory to the GPU as udmabuf dma-bufs, not host pointers Conflicts with #585 resolved: both include sets kept in host/hos_main.c and gpu/vk/vk_memory.cpp (the Linux headers before vk_work.h, which goes last). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed
added a commit
that referenced
this pull request
Nov 8, 2026
…counter #585's DYN_CHANGED counts its calls (FINE_X(FC_DYN_CALLS, 1)): the test's fixture defines both as nothing, as it stubs the Vulkan commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
madebyjordan
added a commit
to madebyjordan/Longshot
that referenced
this pull request
Nov 8, 2026
Resolves the conflict with NotRexed#585 (BC3 scratch made on demand, given back when idle): the second scratch for resized textures now follows the same lifecycle, made with the first when a resized upload needs it and given back with it after RX_VK_BC_IDLE_S seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Hi, @NotRexed was just curious as to why this PR was closed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)with961 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:draw_begin_recording->create_buffer(host_visible)staging()memory_guest_pagesWith 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
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.RX_VK_BLOCK_BUFFERS=1: shares of 64 MB blocks), so a 24 MB stream no longer leaves 16 MB of a block empty.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.)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.
nvidia-smi)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)gpu/vk/vk_work.hcounts GPU work recorded on a command buffer (every work-doingvkCmd*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_barriergoes through the same check. Off while parallel replay is on. The work count is afetch_add(two threads can record at once).a frame: N full barriers recorded, M left out (nothing recorded since the one before).System RAM and VRAM given back when idle (each on by default, with its switch)
RX_VK_STREAM_GROW_OFF=1(24 MB, skips),RX_VK_STREAM_KEEP=1RX_VK_CAP_MIN_MB=16,RX_VK_CAP_BLOCKS=1,RX_VK_CAP_SPARE_MB=0RX_VK_BC_IDLE_S=0,RX_VK_BC_POOL=1RX_VK_COUNT_BY_ESTIMATE=1RX_VK_TAAU_HIST_LEAK=1flush_texturereads back into cached host memory, not through the sequential-write bufferRX_VK_FLUSH_UNCACHED=1RX_VK_STAGING_IDLE_S=0RX_VK_SHADOW_SWEEP_OFF=1malloc_trim(0)when glibc's arenas hold 192 MB free; avk: malloc:census line every 1800 presentsRX_VK_MALLOC_TRIM_MB=0RX_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.pychecks 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(aliasRX_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.=2adds a timestamp per program run inside passes.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. Avk: 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.pypasses, and a run of this exact branch is still to come; I'll post it as a comment.nvidia-smi)smaps_rollup, 10 s into standingnvidia-smi)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
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.
RX_VK_PUSH_STATE_OLD=1RX_VK_DYN_PROG_KEY=1RX_VK_BANK_GEN_OFF=1RX_VK_PICK_BIND_GEN=1RX_VK_PROF_FINE=1(off by default): the draw's time split into finer sections, and what each draw doestests/test_push_state.py,test_bank_gen.py,test_pick_tex_gen.pyandtest_same_dyn.pycheck the invariants.Program::wm_sigis 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
__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only)HOS_INPUT_LIVE), the game's 120-frame stat lines,nvidia-smievery 2 s,/proc/<pid>/smaps_rollup, andRX_VK_FRAMELOG+scripts/framelog.pyfor frame times and flashes.nx2/make_game.py, Linux). This branch is onmain7e9bf73; every changedgpu/vkfile compiles for x86_64 Linux.malloc_trimandHOS_NO_THP(both under#ifdef __linux__/glibc), but I have not built or run it elsewhere.🤖 Generated with Claude Code