perf(vk): optimize < 8 GB VRAM management, dynamic floor, 3-tier target pool, and DLSS CNN presets #637
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!637
Loading…
Reference in a new issue
No description provided.
Delete branch "flix331/Longshot:perf/longshot-optimus-6gb"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: perf · Risk: low · Tested on: Windows, RTX 3050 6 GB Laptop · Needs: Linux, AMD, > 8 GB GPUs
perf(vk): optimize < 8 GB VRAM management, dynamic floor, 3-tier target pool, and DLSS CNN presetsto WIP: perf(vk): optimize < 8 GB VRAM management, dynamic floor, 3-tier target pool, and DLSS CNN presets- vram_compact.h (`blocks_at_start`): lower initial texture pool preallocation on < 8 GB heaps from 15% (up to 24 blocks / 768 MB) to 8% (2-12 blocks / up to 384 MB) so `returnable()` stays positive and `compact_step` can compact and return empty blocks above 384 MB instead of pinning 768 MB permanently. - vk_vma.cpp (`vma_init`): use 32 MB `preferredLargeHeapBlockSize` instead of 64 MB on <= 8 GB device-local heaps (via `device_local_heap_bytes()`) to improve block reclamation for device buffers and sparse mirror blocks. - vk_vma.cpp (`target_images_pool`): use 16 MB blocks instead of 32 MB on <= 8 GB heaps to halve trapped free space from transient render targets. Observed on RTX 3050 6 GB Laptop (`5,233 MB` allowance): - VMA `free in the blocks` dropped from 1,448 MB to 532-649 MB. - `texture_pool` compacted down to 22 blocks (704 MB holding 673 MB of textures). - `target_image_pool` wasted space dropped from 467 MB to 242 MB, allowing `pool_pressure` to recover out of critical (`VRAM short: halved`). - Worst-case 600-frame spikes dropped from 603.9-1,168.4 ms to 75.0-99.6 ms.695e9b819370a78f9339WIP: perf(vk): optimize < 8 GB VRAM management, dynamic floor, 3-tier target pool, and DLSS CNN presetsto perf(vk): optimize < 8 GB VRAM management, dynamic floor, 3-tier target pool, and DLSS CNN presets@Beanstalk
Beanstalk test farm
Commit
70a78f9· tier reviewed · approved by HumanBean · dashboardWhat will be tested · AI plan (
gpt-6.1-terra)The plan compares VRAM use, frame pacing, load times and scene images in Castle Town Market and Hyrule Field on GPUs with at most 6 GB and more than 8 GB of VRAM. Each target takes about 13 minutes, including a short walk and camera movement in both areas. Crashes, Vulkan device loss, significant performance or VRAM regressions, and substantial image differences fail the plan. Both targets require Linux gameplay testers, so they will wait until matching PCs come online. The new DLSS presets and repository unit tests are not covered by this farm plan. No tester online now matches linux+vram-le6gb, linux+vram-ge9gb: those targets wait for one.
Started.
Thanks for digging into the 6 GB laptop case: the numbers from Castle Town Market show there's a real problem on small cards. I can't take the PR in its current shape, though, for three reasons:
It changes far more than the description says, and much of it applies to every card. The description lists five changes, but the diff also has these:
mkwvk_initthat accepts old hostsprograms_load_decideoverride for v14 hostsRX_VK_KEEP_FINALIZED_MBgoing from 512 to 128RX_VK_DEAD_MIN_UNUSEDgoing from 3 to 2RX_VK_IMPORT_LOWgoing from 75 to 70RX_VK_ASTC_BC_MBgoing from 2 to 1.gitignorechangeSeveral of these reverse values that the comments beside them say were measured. The description also mentions a
shared_vram_headroom()removal that isn't in the diff (it was added and removed within the branch), and calls the 96 MB target pool the existing value, when main's is 256 MB.8 GB cards are not untouched. The gates compare
allowed, the driver's allowance, againstGLOBAL_MIN_GRAPHICS_REQ = 8192 MB. An 8 GB card's driver allows about 7.3 GB, so the 42% budget, the damped contraction, the dynamic floor and the < 8 GBblocks_at_startrule all apply to 8 GB cards too. On top of that,texture_min_floorand the changed constants reach > 8 GB cards. Inline comments mark each spot.It conflicts with main, and the conflict needs a design decision, so I haven't pushed a rebase.
jj rebaseonto main gives two conflicts. Both start in the first commit (7173e807) and carry through every later commit:.gitignoreis trivial: keep main'stest-results/and_deps/and add the.vs/,.vscode/andCppProperties.jsonblock (or drop that block: IDE files belong in a personal global gitignore).vk_texture.cpp,texture_frame'sown(the texture budget) is not trivial. Main has replaced the inline 45%/70% expression withtexture_rule::dedicated_bytes()/integrated_bytes()ingpu/vk/texture_budget.h. Those size the budget from the card's VRAM (vram/12*7, capped at 3/4 of the allowance), not from a fixed share of the allowance. For a 6 GB card allowed 5,233 MB, that gives about 3,584 MB, against this PR's 42% (2,197 MB). Taking main's side drops the PR's budget change, and the PR's measurements were taken without main's rule. Porting the 42% intotexture_budget.hwould replace a rule main adopted after measuring it. Which one wins should be decided and measured on top of main, not guessed while resolving a conflict. So: rebase, take main'stexture_rulecall, and re-measure the market before adding any < 8 GB change on top of it.What I'd ask for: split this into separate PRs, each measured on its own on top of main, with an A/B on the same build:
device_vram_bytes()/heap size, notallowed)Drop the Optimus ICD override, the default-on presentation thread for NVIDIA, the write-back cap default and the host-version shim. Those are not < 8 GB tuning, and the shim breaks the versioned interface (inline comments).
DLSS CNN presets: the presets are opt-in, so nobody's default changes, which is fine. But the PR's evidence for them is not separated from the rest. The "after" column is CNN F plus every memory change, against Transformer K plus main's memory rules, so the 18-25 to 33-36 fps gain can't be credited to either. The 231 MB against 41-48 MB of DLSS memory is a real measurement and a good start. A preset PR should show K, E and F on the same build and in the same scene (GPU time, VRAM, plus screenshots for image quality, since CNN against transformer is mostly a quality trade). It should also confirm that the DLSS runtime we ship still honours presets 5 and 6: NVIDIA has been retiring the CNN presets in the 310.x SDKs, and a value the runtime no longer supports may be silently mapped to another preset. If CNN turns out to be clearly better on small cards, the sensible-defaults rule points to picking it automatically there, not to a menu row with a performance claim in its hint.
Testing: so far it has run only on the author's Windows RTX 3050 6 GB laptop. The Beanstalk run on
70a78f9(Linux, ≤ 6 GB and ≥ 9 GB) is queued and has no results yet. Before merging, each split PR would need a Linux run, an AMD run and a > 8 GB run, and an 8 GB NVIDIA run, since 8 GB cards are affected after all.I haven't run any tests here; this review is from reading the code. Requesting changes.
@ -233,3 +233,3 @@/// generation transformer, preset M (NVIDIA's own pick for Performance); 2 the same generation as tuned for Ultra/// Performance, preset L; 3 NVIDIA's default, preset 0 (the runtime picks per mode, and a runtime update can change it)enum { MODEL_K = 0, MODEL_M = 1, MODEL_L = 2, MODEL_DEFAULT = 3, kModels = 4 };enum { MODEL_K = 0, MODEL_M = 1, MODEL_L = 2, MODEL_DEFAULT = 3, MODEL_E = 4, MODEL_F = 5, kModels = 6 };Opt-in, so no default changes; that's fine. But please confirm that the DLSS runtime we ship honours presets 5 and 6. NVIDIA has been retiring the CNN presets in the 310.x SDKs, and a value the runtime no longer supports may be quietly mapped to another preset, so log the preset the runtime reports. Also please move this to its own PR, with K, E and F measured on the same build and scene (GPU time, VRAM, screenshots). The fps gain in this PR's table mixes CNN F with all the memory changes.
@ -53,0 +53,4 @@// NVIDIA Optimus hybrid laptop architecture (CPU-GPU PCIe bus for display): if no ICD is explicitly set,// locate NVIDIA's nv-vk64.json in DriverStore to bind Vulkan exclusively to the dGPU, bypassing Intel iGPU loader hooking// and preventing PCIe display contention.if (!getenv("VK_ICD_FILENAMES")) {This forces
VK_ICD_FILENAMESfor every Windows machine whose DriverStore has anynv*package with annv-vk64.json. Setting it makes the loader load only that ICD. (1) A PC with an AMD or Intel Arc card that once had an NVIDIA driver installed (Windows keeps the old packages in DriverStore) then sees no usable GPU. (2) DriverStore usually holds several NVIDIA driver versions, andFindFirstFileorder picks whichever folder comes first, not the installed one. An ICD JSON from an old package points at an oldnvoglv64.dllthat doesn't match the running kernel driver, so instance creation fails or the device is lost. (3) The variable is inherited by every child process, andC:\\Windowsis hard-coded. The Windows loader already picks up the NVIDIA ICD from the registry. Optimus GPU selection is a matter of device choice (or theNvOptimusEnablementexport), not of hiding ICDs. Please remove this block.@ -430,1 +450,3 @@const bool presenter_wanted = pt_env && atoi(pt_env) != 0; // (off by default: no queue made for it)// (hookshot) NVIDIA Optimus: enable dedicated presentation thread/queue by default on NVIDIA dGPUs (0x10DE)// to decouple swapchain presentation over PCIe from the replay render loop.const bool presenter_wanted = pt_env ? (atoi(pt_env) != 0) : (dev.props.vendorID == 0x10DE);This turns the presentation thread and queue on by default for every NVIDIA GPU, desktop cards included.
RX_VK_PRESENT_THREADis off by default on main, and this change has no measurement and isn't in the description. It belongs in its own PR with frame-pacing numbers from more than one NVIDIA card; otherwise keep main'spt_env && atoi(pt_env) != 0.@ -1590,3 +1616,2 @@if (!h) return nullptr;if (h->version != MKWVK_API_VERSION) {std::fprintf(stderr, "vk: host version mismatch: host has %u, expected %u\n", h->version, MKWVK_API_VERSION);if (h->version < 14 || h->version > MKWVK_API_VERSION) {This shim has to go.
MKWVK_API_VERSIONis 22. A v15-v21 host passes a smallerMkwVkHostthan v22's, butcopy_sizeissizeof(MkwVkHost)for every version except 14, so thememcpyreads past the end of the host's struct. Whatever comes after it then becomes function pointers the renderer will call. For a v14 host, every field afterwide_menusis zeroed, and any unchecked call through one of them is a null call.table.version = h->version(line 1667) then tells an old host that a v22 table is its own version. The interface is versioned on purpose (AGENTS.md: a change toMkwVkHost/MkwVkRendererbumps the version, and host and renderer must match). Supporting old hosts is a design change of its own, not part of < 8 GB tuning. Please restoreh->version != MKWVK_API_VERSION,host = h;andtable.version = MKWVK_API_VERSION, and drop thehost_v14override invk_programs.cppwith them.@ -3112,1 +3112,3 @@return mirror_rule::auto_bytes(device_vram_bytes());const uint64_t vram = device_vram_bytes();// Cards of 6 GB or less: allocate up to 1700 MB to hold open-area peaks without falling back to PCIe reads.if (vram && vram <= (6ull << 30) + (512ull << 20)) return std::min<uint64_t>(1700ull << 20, vram / 3);On a 4 GB card this nearly triples the mirror budget:
auto_bytesgives an eighth (512 MB) at 4.5 GB or less, and this givesmin(1700, vram/3)= 1,365 MB. Those are the cards from issue 519, where VRAM was already oversubscribed. On 6 GB cards it goes from 1,536 to 1,700 MB, which is the opposite of what the rest of the PR is trying to do. If it's needed, please gate it to the measured range (say 5.5-6.5 GB) and show the mirror and VRAM numbers for it; it's also not in the description.@ -3616,2 +3619,3 @@static const uint64_t v = [] {const uint64_t b = wb_learn::max_bytes(getenv("RX_VK_WB_MAX_KB"));// (hookshot) Default to 1024 KB cap to prevent ~68 MB/frame of unread culling write-backs over PCIeconst uint64_t b = getenv("RX_VK_WB_MAX_KB") ? wb_learn::max_bytes(getenv("RX_VK_WB_MAX_KB")) : (1024ull << 10);This turns the write-back cap on for every card.
wb_learn.hsays it is "off by default until it shows a gain on more than one machine", and it is a correctness-sensitive switch: data the CPU later reads that wasn't written back would come back stale. It isn't in the description, and it has no measurement beyond the comment's estimate. Please keep main'swb_learn::max_bytes(getenv("RX_VK_WB_MAX_KB")), and propose enabling it in its own PR with runs on several machines.@ -4368,3 +4372,3 @@#endifstatic const int high = std::clamp(env_int("RX_VK_IMPORT_HIGH", high_default), 0, 100);static const int low = std::clamp(env_int("RX_VK_IMPORT_LOW", 75), 10, 95);static const int low = std::clamp(env_int("RX_VK_IMPORT_LOW", 70), 10, 95);A change from 75 to 70 for every card, with no measurement, not in the description, and the comments above (lines 4197 and 4330) still say 75. Please revert it, or measure it and fix the comments.
@ -269,3 +269,3 @@}const char* tx_dlss_model(int v) {static const char* const t[] = {"Transformer (K)", "Transformer 2 (M)", "Transformer 2 (L)", "NVIDIA default"};static const char* const t[] = {"Transformer (K)", "Transformer 2 (M)", "Transformer 2 (L)", "NVIDIA default", "CNN (E)", "CNN (F)"};The hints ("runs faster on constrained RTX GPUs") are a performance claim, and so far no measurement separates it from the memory changes. Please keep the hint to what it is (NVIDIA's CNN model, smaller VRAM footprint) until the K/E/F comparison is in.
@ -350,2 +350,2 @@env_on("RX_VK_PROGRAM_CACHE_ZSTD")); // (as vk_progcache.cpp disk_init reads it)load_mode.store(c.create ? 1 : 0, std::memory_order_release);env_on("RX_VK_PROGRAM_CACHE_ZSTD"));const bool host_v14 = (!getenv("RX_VK_LOAD") && host && host->version <= 14);This only makes sense with the host-version shim in
mkwvk_init, which should go (see that comment). After that,host->versionis alwaysMKWVK_API_VERSION, so this is dead code. Its log text also says load-on-create needs host >= 21 but tests<= 14, so hosts 15-20 would still getcreate. Please remove it.@ -314,3 +314,3 @@}uint64_t pool_cap() {static const uint64_t mb = getenv("RX_VK_TARGET_POOL_MB") ? (uint64_t)std::max(0, atoi(getenv("RX_VK_TARGET_POOL_MB"))) : 256;static const uint64_t mb = getenv("RX_VK_TARGET_POOL_MB") ? (uint64_t)std::max(0, atoi(getenv("RX_VK_TARGET_POOL_MB"))) : 96;Main's target pool is 256 MB for every card. Dropping it to 96 MB also shrinks it on > 8 GB cards, contrary to the description's claim. The 3-tier pressure logic in
pool_trimis the useful part, and it works with any cap. Please keep 256, or gate a smaller cap to small cards with churn numbers (render target images: ... made new).@ -439,3 +440,3 @@};std::vector<Arena> arenas;constexpr uint64_t kArena = 64ull << 20;constexpr uint64_t kArena = 16ull << 20;16 MB arenas for every card: an area load's burst now takes four times as many
vkAllocateMemorycalls, and the PR's own comment at 4195 says those cost 70-108 ms each. The PR doesn't measure this. Please revert to 64 MB, or show load-time numbers for small and large cards.@ -2052,3 +2053,3 @@// less: BC3's 8 keep what they have) from 2 MB as RGBA8 (RX_VK_ASTC_BC_MB), the finer ones (UI, characters)// only from 64 MB)static const uint64_t low = (uint64_t)(getenv("RX_VK_ASTC_BC_MB") ? atoi(getenv("RX_VK_ASTC_BC_MB")) : 2) << 20;static const uint64_t low = (uint64_t)(getenv("RX_VK_ASTC_BC_MB") ? atoi(getenv("RX_VK_ASTC_BC_MB")) : 1) << 20;From 2 to 1 MB for every card, with no measurement and not in the description. Please revert it, or measure it in its own change.
@ -2509,1 +2510,3 @@static const int64_t budget_ns = (int64_t)(getenv("RX_VK_UPLOAD_BUDGET_MS") ? atof(getenv("RX_VK_UPLOAD_BUDGET_MS")) : 40.0) * 1000000;// Terrain materials (layer arrays > 1 or very large surfaces) must always upload synchronously// to prevent ground flickering. Only defer smaller single-layer NPC/character/prop textures.const bool is_terrain = (tex.layers > 1) || (tex.width > 1024 && tex.height > 1024);This bypasses the budget for every array texture and every texture over 1024x1024, on every card. An area load's burst of large or array textures is then never spread over frames, which is the stall the deferral exists to prevent. The comment above it also records that a lower budget (8 ms) made the streamed ground flicker, and line 2516 now sets 12 ms for everyone. The ground-flicker fix on main was the 40 ms budget, not a shape test. Please revert both lines, or put them in a measured change of their own (flicker and load stalls on more than one card).
@ -4255,2 +4271,3 @@if (!own) // (RX_VK_VRAM_MB, else dynamic budget from GLOBAL_MIN_GRAPHICS_REQ: 42% on < 8GB, 45% on 8GB, 70% on larger cards)own = getenv("RX_VK_VRAM_MB") ? (uint64_t)atoi(getenv("RX_VK_VRAM_MB")) << 20: known ? std::min<uint64_t>(allowed / 100 * (allowed <= (8704ull << 20) ? 45 : 70), 6144ull << 20): known ? std::min<uint64_t>(allowed / 100 * (allowed < vram_compact::GLOBAL_MIN_GRAPHICS_REQ ? 42 : (allowed <= (8704ull << 20) ? 45 : 70)),(The rebase conflict lands here.) Main has replaced this expression with
texture_rule::dedicated_bytes()/integrated_bytes()(gpu/vk/texture_budget.h), which sizes the budget from the card's VRAM. A 6 GB card allowed 5,233 MB gets about 3,584 MB there, against the 2,197 MB here. Note also thatallowed < 8192 MBis true for 8 GB cards, whose driver allows about 7.3 GB, so this 42% would hit them too. Please rebase, take main'stexture_rulecall, and re-measure before proposing a < 8 GB change on top of it, gated bydevice_vram_bytes().@ -4376,3 +4406,2 @@if (compact_on() && known && !by_blocks) {static const uint64_t pct = getenv("RX_VK_COMPACT_PCT") ? (uint64_t)std::clamp(atoi(getenv("RX_VK_COMPACT_PCT")), 50, 90) : 85;const uint64_t held = vram_compact::held_budget(budget, total_bytes, used, reclaim, allowed, pct);static const uint64_t pct = getenv("RX_VK_COMPACT_PCT") ? (uint64_t)std::clamp(atoi(getenv("RX_VK_COMPACT_PCT")), 50, 90) : 80;From 85 to 80 for every card. The comment just above records the measurement behind 85 (a Hyrule Field walk at DLAA 1440p: 11-12 frames over 33 ms, against 34-49). Please keep 85, or bring a new measurement on more than one card.
@ -4364,3 +4391,3 @@if (known && pressure > allowed / 10 * 9) {const uint64_t over = pressure - allowed / 10 * 9;budget = std::min(budget, total_bytes > over ? total_bytes - over : 0);const uint64_t damped_over = (allowed < vram_compact::GLOBAL_MIN_GRAPHICS_REQ) ? (over / 4) : over;allowed < GLOBAL_MIN_GRAPHICS_REQis true for 8 GB cards, so the 90% rule now gives back only a quarter of the excess per look on them too. The same applies toheld_budgetandupdate_dynamic_floor. If these are meant for cards under 8 GB, gate them by the card's size (device_vram_bytes()/heap), not by the driver's allowance, and say which cards were measured.@ -4409,3 +4439,2 @@const uint64_t over = used - count_line;budget = std::min(budget, total_bytes > over ? total_bytes - over : 0);rule_budget = std::min(rule_budget, total_bytes > over ? total_bytes - over : 0);const uint64_t floor = vram_compact::texture_min_floor(allowed);A floor on the 97% rule defeats it. This is the rule that keeps the count under what the driver allows: the
blocks_at_startnotes record a device loss at 6.6 GB of 6.5 GB allowed, and issue 519 records 4 fps from paging. Withtexture_min_floor, the textures can't go below 1,800 MB. On a 4 GB card the driver allows about 3.3 GB, and the game's other VRAM is the larger part of that (see the 45% comment), so the count then stays above the limit for good, which means paging or device loss. On > 8 GB cards the floor isallowed/4(about 3.8 GB on a 16 GB card), so this also changes big cards. Please leave the 97% rule without a floor; a floor belongs, if anywhere, only on the soft lines (90% and the hold).@ -4609,3 +4642,3 @@// time), VRAM at the driver's limit; the GPU's own use is checked below (last_value). RX_VK_DEAD_MIN_UNUSED=<frames>, 3)static const uint64_t dead_min_unused =getenv("RX_VK_DEAD_MIN_UNUSED") ? (uint64_t)std::max(1, atoi(getenv("RX_VK_DEAD_MIN_UNUSED"))) : 3;getenv("RX_VK_DEAD_MIN_UNUSED") ? (uint64_t)std::max(1, atoi(getenv("RX_VK_DEAD_MIN_UNUSED"))) : 2;From 3 to 2 for every card, with no measurement and not in the description. Please revert it, or measure it in its own change.
@ -4619,3 +4652,3 @@// may have put other things there since, and a write-back over scene data once crashed it)static const uint64_t keep_finalized_max =(uint64_t)(getenv("RX_VK_KEEP_FINALIZED_MB") ? std::max(0, atoi(getenv("RX_VK_KEEP_FINALIZED_MB"))) : 512) << 20;(uint64_t)(getenv("RX_VK_KEEP_FINALIZED_MB") ? std::max(0, atoi(getenv("RX_VK_KEEP_FINALIZED_MB"))) : 128) << 20;keep_finalizedis a correctness keep, not a cache: the comment below explains that OoT makes its terrain tiles' normal maps again over the same storage and samples them without drawing them, so a texture evicted beyond this cap is sampled wrong. Cutting it from 512 to 128 MB for every card risks bringing back wrong or flickering terrain in large areas. Please revert, or show that 128 MB covers Hyrule Field's peak (and log how often the cap is reached).@ -35,2 +43,3 @@ci.pVulkanFunctions = &f;ci.preferredLargeHeapBlockSize = 64ull << 20; // (64 MB blocks: Eglantine found 256 MB ones wasted VRAM)// (64 MB blocks on > 8 GB cards; 32 MB on <= 8 GB cards to halve fragmentation in default heap 0 blocks)ci.preferredLargeHeapBlockSize = device_local_heap_bytes() <= vram_compact::GLOBAL_MIN_GRAPHICS_REQ ? (32ull << 20) : (64ull << 20);<= GLOBAL_MIN_GRAPHICS_REQincludes 8 GB cards, which report an 8,192 MB heap (or just under). On those cards, VMA also gives anything over half a block (16 MB) its own dedicated allocation. The 16 MB target-pool blocks (line 84) then push render targets of 16 MB or more (2560x1440 RGBA16F is about 29 MB) out of the target pool, back to onevkAllocateMemoryeach. That is the churn the target pool exists to stop. Please measure it, or leave it out.@ -46,3 +55,3 @@// (RX_VK_IMAGE_BLOCK_MB=<n>, 16 and up: the block's size. VMA keeps one block empty for the next image, 128 MB of VRAM that a 64 MB block// would halve, at twice the memory objects: the driver allows ~4096, the imported pools take ~2400)const uint64_t kImageBlock = (getenv("RX_VK_IMAGE_BLOCK_MB") ? (uint64_t)std::max(16, atoi(getenv("RX_VK_IMAGE_BLOCK_MB"))) : 128u) << 20;const uint64_t kImageBlock = (getenv("RX_VK_IMAGE_BLOCK_MB") ? (uint64_t)std::max(16, atoi(getenv("RX_VK_IMAGE_BLOCK_MB"))) : 32u) << 20;This changes the image and texture block size from 128 to 32 MB for every card. On a 16 GB card,
blocks_at_startthen preallocates 24 x 32 MB = 768 MB instead of 19 x 128 MB = 2,432 MB. That brings back the in-playvkAllocateMemorystalls (the source comment's#2438, about 170 ms on AMD) that the preallocation removed. Every texture over 32 MB now falls outside the pool, so it gets no compaction and is allocated on its own. The memory object count also quadruples, and the comment just above says the driver allows about 4,096 objects and the imported pools take about 2,400 of them. The tests don't catch this because they use their ownkBlock. Please keep 128 MB; if small cards want smaller blocks, gate it by the heap size and measure it.@ -40,2 +45,3 @@inline uint64_t blocks_at_start(uint64_t heap, uint64_t block, bool holds_textures, bool integrated) {return holds_textures && block && !integrated ? std::clamp<uint64_t>(heap / 100 * 15 / block, 2, 24) : 0;if (!holds_textures || !block || integrated) return 0;if (heap < GLOBAL_MIN_GRAPHICS_REQ) return std::clamp<uint64_t>(heap / 100 * 8 / block, 2, 12);The comment above this rule says the prealloc exists because each block made during play is a ~170 ms
vkAllocateMemoryon the replay thread (the source comment's#2438). Cutting it to 8% and a maximum of 12 blocks puts those stalls back on small cards. The test's 8,061 MB iGPU-like case shows that 8 GB cards fall under this branch too. Please measure the stall count in play (logblocks made) before and after on a 6 GB card, or keep the 15% rule.@ -100,3 +152,3 @@/// camera turned: with the count held under a 7 GB allowance, a turning camera in Hyrule Field at DLAA let go of 2.3-2.6 GB of live/// textures every 600 presents and uploaded 6.8 GB in 50 seconds, against 1.4 GB with the compaction offinline constexpr uint64_t kHoldUnused = 600;inline constexpr uint64_t kHoldUnused = 180;From 600 to 180 presents (and the reserve from 1800 to 600) for every card. The comment just above records why it was raised to 600: a turning camera in Hyrule Field let go of 2.3-2.6 GB of live textures and uploaded 6.8 GB in 50 s. Three seconds is shorter than the camera turn that measurement describes. Please keep 600/1800, or bring a new Hyrule Field upload measurement (the uploads in MB per minute).
@ -90,0 +98,4 @@inline uint64_t texture_min_floor(uint64_t allowed) {if (!allowed) return 0;if (allowed < GLOBAL_MIN_GRAPHICS_REQ) return 1800ull << 20;return std::max<uint64_t>(1800ull << 20, allowed / 4);For
allowed >= 8 GBthis returnsallowed/4, andheld_budgetandupdate_dynamic_floorapply it to > 8 GB cards: a 16 GB card's hold can no longer go below about 3.8 GB. That contradicts the claim that cards of 8 GB and more are untouched. If the floor is for small cards only, return 0 here whenallowed >= GLOBAL_MIN_GRAPHICS_REQ.@ -95,0 +142,4 @@target = floor;}uint64_t res = std::min(budget, target);if (floor && res < floor) res = floor;held_budgetis documented as "never more than the budget was", but this line can return the floor abovebudget. For example, withbudgetat 1,000 MB and the floor at 1,800 MB it returns 1,800. Today's callers happen to ignore it (if (held < budget)), but the next caller won't know. Please returnstd::min(budget, std::max(target, floor)), and update the docs if the contract changes.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.