perf(vk): the BC3 scratch has the layers a run uses, 20 MB instead of 160 #384
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
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/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!384
Loading…
Reference in a new issue
No description provided.
Delete branch "p0lux/Longshot:perf/bc-scratch-one-layer"
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?
Why
Every card without ASTC of its own (every PC card, and a Steam Deck) re-encodes the game's big ASTC textures to BC3 through a scratch image and its buffer, kept once made. They are made for 8 layers, 160 MB, but an upload has gone one layer at a time since the fix for Hyrule Field's dark tile outlines: 140 MB held for the session and never written, of VRAM on a card and of the 16 GB a Steam Deck shares with the game.
What changed
bc_layers()ingpu/vk/vk_texture.cpp, the layers of one run: 1, orRX_VK_BC_LAYERSup to 8.bc_scratch_make(),bc_scratch2_make()andbc_scratch_layout()make the two scratches and set their first layout for that many layers, not forkBcLayers: 16 MB of image and 4 MB of buffer a layer.upload_astc_gpu()takes its run frombc_layers(), which is what it read from the switch before.texture_copy_into_bc()goes by the scratch's layers too, so a copy of several layers into a BC3 texture is one run a layer, as an upload's.RX_VK_BC_IDLE_S) says the scratch's size as it is.tests/test_bc_scratch_layers.py(new) checks both scratches, their layout and both users askbc_layers(), runs it for the switch's values, and runs the two loops themselves:texture_copy_into_bc()whole and the lines ofupload_astc_gpu()that form a run are cut intotests/bc_scratch_batches_test.cpp(new) with a spy in place of Vulkan (a request larger than the scratch, a last partial run, source and destination layers that are not 0, an array's dirty layers with gaps, the resized path).tests/parallel_init_order_test.cppstubsbc_layers()in place of the constant.RX_VK_BC_LAYERSindocs/development.md's switch reference (it was not there); a run file indocs/tested/.Flags and settings
RX_VK_BC_LAYERS=<n>(existing)No new flag.
RX_VK_BC_LAYERS=8gives the scratch its old size, with runs of 8 layers.Measurements
main:
fbd42024(2026-11-13)this PR: perf/bc-scratch-one-layer @ 000cc9f2 (the change on that main). The branch is now that change rebased onto main
0caf0618(b7b039c8) and a docs commit; the rebase had no conflict, and main has not touched the scratch's code in between.Hardware: Steam Deck OLED (AMD Custom APU 0932, RADV VANGOGH, Mesa 26.2.0-devel), 16 GB shared with the GPU, SteamOS 3.9.5, kernel 7.2.7-valve2; the GPU's clock fixed at 1600 MHz
Scene: three saves, standing still: a heavy view (about 2,150 draws a frame), Kokiri Forest (about 2,600), Hyrule Field (about 1,600); no screen (headless), handheld profile, Minimum preset, the game's own upscaler, no frame generation, no cap
Method: the back end built alone from each commit, on a host and game folder of main
000f19fb(vk_api.his the same). Runs alternated main, this PR (and a third renderer that is not this PR's), 3 rounds on the heavy view and 2 on the others: 7 pairs. Each run starts the game, loads the save by script, waits 3 log windows, measures exactly 720 presents, then reads what the driver counts for the process (drm-memory-gttanddrm-memory-vramin/proc/<pid>/fdinfo: on this machine GPU memory is system memory) andMemAvailablein/proc/meminfoHow to reproduce: a save in Hyrule Field or any area with terrain; after the load,
grep drm-memory /proc/$(pgrep -x longshot)/fdinfo/*with main and with this PR (or, on one build of this PR, withRX_VK_BC_LAYERS=8against unset)fbd42024drm-memory-gtt), heavy view, median of 3MemAvailableof the whole system, the 7 pairs' differences, medianThe values are the kernel's counters in KiB divided by 1,024 (MiB).
MemAvailableis the whole system's and is there as context, not as a second saving to add to the first. The fps and the start times show no cost; they are not a claim of speed.The order of each round was main, this PR, then the third renderer, which is main with the six commits of #391 (nothing of this PR). A log window is 120 presents (the log's "N presents, X fps" line): the 3 windows waited are the first 360 presents after the save is in game, before the 720 measured. The measured commit is kept on the fork as
measured/bc-scratch-000cc9f2(https://git.magicbean.dev/p0lux/Longshot/src/branch/measured/bc-scratch-000cc9f2).The same 139 MiB (median of 7 pairs, the same scenes) came out a few hours earlier with this commit on top of other renderer-only changes against the same tree without them. A run's GPU memory varies by about 100 MB from one start to the next, hence the pairs.
Testing
Tested:
python -I -B tests/run_all.pyon macOS with the Vulkan headers given: 487 passed, 36 failed, 39 skipped; the 36 fail the same way on main0caf0618on that machine (no/proc, noposix_fallocate, Python 3.9, the Linux launch scripts and the like):nx2/test_nup_zstd_heads.py, and intests/:test_appimage_package_retry,test_appimage_source_headers,test_bake_portability,test_chunk_split,test_collect_wide,test_control,test_frame_rate_cap_step,test_framegen_resources,test_game_layout,test_gpu_hang_watch,test_kept_translation,test_lift_cache,test_linux_preflight,test_longshot_launch,test_low_memory_notice,test_make_game_cache,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_missing_nup,test_module_ids,test_movie_frame_count,test_oot_saves,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant,test_prompt_skip,test_sticky_hot_pieces,test_tested_configurations.test_bc_scratch_layers.pyfails on main (nobc_layers()) and passes here;test_parallel_init_order.py,test_mem_trim.pyandtest_bc_upload_retry.py, which read this code, pass.vk: big ASTC textures are kept as BC3).RX_VK_SHOTS), standing still on the three scenes: main twice, then this PR, the last capture both runs have compared pixel by pixel (the mean absolute difference of a channel, 0 to 255). Two runs of main differ by 18.3 to 18.9, since a scene starts in one of two states (Hyrule Field at 1,571 or 1,614 draws) and never stands still; this PR against the run of main in its own state: 8.1 (Hyrule Field, whose ground samples the terrain's material arrays), 12.0 (Kokiri Forest), 5.7 (heavy view); against the other run 18.9, 20.4 and 17.1, as main against main.514591a3) played on the Steam Deck OLED on 2026-11-14: nothing wrong seen on screen.longshot_vk.dllcross-builds with llvm-mingw 20261104 from a branch holding this commit and other renderer-only ones, 0 failed.Not tested:
RX_VK_UNEVEN_BC=0, AMD's uneven block levels), which is off by default.Needs other testers:
Screenshots
Nothing on screen.
Risk and rollback
The scratch can no longer hold 8 layers unless the switch asks for them, so a copy of several layers into a BC3 texture takes one run a layer instead of one run for up to 8: more commands for the same GPU work, and no longer the runs of several layers that an upload stopped making because they drew layers wrong (
upload_astc_gpu()'s comment; withRX_VK_BC_LAYERS=8the pictures of Kokiri Forest and the heavy view here differ from the default's by 19 to 27, well above the 7 to 8 of two runs alike).RX_VK_BC_LAYERS=8brings back the old size and the old copy; reverting the commit removes the change.Reviewer notes
fix/vk-bc-copy-extent, intexture_copy_into_bc()too (the team room, 2026-11-14); I have not seen its branch published yet. This PR's hunk there is the two lines of the loop over the layers (l0 += bc_layers()andstd::min(bc_layers(), layers - l0)), nothing of the extents. Whichever goes second, the combined version has to keepbc_layers()in that loop:kBcLayersthere would write scratch layers that are not made.bc_idle_s()'s comment says keeping the scratch costs "160-288 MB of VRAM held after the first decode" (issue 993); it is 20 to 36 MB with this.Issues
None.
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view).mainand passing here.tests/that reads changed source updated in this PR, not weakened.Read head
8b7e3c17against main0caf0618: the complete five-file diff, description, empty comments and reviews, scratch allocation and retirement, both callers and the ASTC/BC3/BC7 shader consumers; ran the new scratch test and the changed parallel-initialization fixture with GCC and Clang, plus the memory-trim and BC upload-retry checks. All six invocations pass. No game, renderer build or full suite was run.What's good
Must change
No patch-introduced functional defect found by tracing these paths or running the focused checks. It merges cleanly with main
0caf0618; the WIP status is not a merge conflict.Scratch users and larger requests
gpu/vk/vk_texture.cpp, lines 637-640, cachesRX_VK_BC_LAYERSon the first call: 1 when unset, bounded to 1 through 8 by the existing parsing. The images and buffer are allocated lazily at that capacity. Changing the environment later does not resize them. Optional idle retirement and later recreation use the same cached value. The second image can be added later, but that does not grow the layer capacity.bc_layers(), decode into scratch layers starting at 0, and encode to the original destination layer. A texture or dirty selection larger than the scratch becomes more batches, not a larger dispatch into the same storage. Gaps in a dirty selection end a batch.gpu/vk/vk_xfer.cpp, lines 264-276, passes the source/destination-clamped layer count totexture_copy_into_bc(). Its changed loop atgpu/vk/vk_texture.cpp, lines 2977-2986, splits that count, advances both source and destination byl0, and reuses scratch layers starting at 0. With capacity 4, a 9-layer copy is batches of 4, 4 and 1; with the default it is nine single-layer batches. The extra commands are an intentional trade-off, not dropped layers.bc_encode_into(), lines 811-830, dispatches exactly the batch'snlayers and copies those blocks frombc_out. Both BC3 and BC7 write 16 bytes per 4 × 4 block, packed by the batch's dimensions and layer number.bc_encode_resized(), lines 833-842, blits the samenlayers to the optional second scratch and uses that same output buffer. These helpers do not clamp independently, but their only callers supply the bounded batches. Existing image-selection and copy guards keep their dimensions within 2048 × 2048.These are source traces, not a GPU reproduction of a multi-layer or resized copy.
Measurements and smaller points
The memory comparison meets the basic pairing rule. It names baseline
fbd42024, candidate000cc9f2, one Deck and matched settings, and reports 3 interleaved pairs in the heavy view and 2 in each other scene. With two observations, the displayed mean is also the conventional median. All 7 process-GPU-memory differences are negative, from -209 to -63 MB in the displayed units, with median -139 MB. Within-scene ranges are separate: heavy view 3800-3908 versus 3675-3772 MB, Kokiri 4064-4077 versus 3901-3919 MB, Field 3482-3510 versus 3362-3371 MB. The heavy-view repeat spreads are 108 and 97 MB. That supports a saving around the intended allocation reduction here, not exactly 140 MB less total process memory in every run. The +145 MB medianMemAvailabledifference is whole-system context, not another saving to add to the GPU-memory result.Keep the evidence reproducible and the speed claim narrow. Retain the per-run memory-counter lines, timestamps and exact order including the third renderer, identify that renderer, and say what the three warm-up log windows cover. Label the conversion to MB or MiB. The memory table is useful, but the raw counter records and run logs are not attached. The small FPS differences do not establish a speed gain, and the 40 s versus 38 s startup row has no per-run values to check. No p99 frame-time or 1% low evidence is supplied. The current allocation and batching changes can be read directly, but
000cc9f2is not available from the fetched history or its public commit endpoint, so I could not independently compare the measured patch with this rebased head. Preserve that commit or its exact patch and build identity when carrying the numbers forward.Exercise the batch behavior, not only its spelling.
tests/test_bc_scratch_layers.py, lines 55-78, checks source strings and runs the real setting reader; its capacity calculation is a separate formula in the fixture. It does not execute allocation, the upload loop, the copy loop or an encoder. A game-free recording test using the production loops could check a request larger than capacity, a final partial batch, nonzero source/destination layers, sparse dirty layers and the resized path. The existing initialization test preserves its ordering checks, but its Vulkan spy does not check layer bounds. This is a coverage improvement, not an observed defect.Pin the other copy change before calling the rebase two lines. The description names
fix/vk-bc-copy-extent. The overlap on this side istexture_copy_into_bc(), lines 2958-2988, specifically the two batch-size lines at 2977-2978 beside the copy-region and encoder arguments at 2980-2986. This patch changes layer batching, not the copy extent. I could not locate the named counterpart in the 134 public open pull requests or fetched branch history, so its hunks and the claimed two-line rebase remain unchecked. Link its published head; any combined version must retain thebc_layers()bound rather than restorekBcLayersbatching against a one-layer allocation.Not checked
No Windows fixture execution, Vulkan validation, desktop GPU, BC7 gameplay, resized scratch, confirmed multi-layer copy or independent memory measurement here. The reported pixel-difference totals are not a visual inspection, and no pictures were supplied to inspect. A bounded, inspected run covering the actual copy path and the optional second scratch would settle the remaining runtime coverage. Keep the explicit WIP limitations, open the stated desktop test request before readying, and name the 36 inherited macOS test failures rather than only their general causes.
Played with the picture on the Steam Deck OLED: a build holding this change with other renderer-only ones (3a3a6475, on main
514591a3), nothing wrong seen on screen. Testing has it now; what is left before ready is a test on a desktop card.8b7e3c17faa9b0c8804d@mooki's review, point by point (head
a9b0c880):MemAvailableis marked as the whole system's, context and not a second saving. The fps and start times are stated as "no cost", not as a gain.measured/bc-scratch-000cc9f2. Against it this head has the rebase,RX_VK_BC_LAYERS's row indocs/development.mdand the new test; the scratch's code is the same.tests/bc_scratch_batches_test.cppruns the production loops with a spy in place of Vulkan (texture_copy_into_bc()whole, and the lines ofupload_astc_gpu()that form a run, cut in bytests/test_bc_scratch_layers.py): 9 layers into a scratch of 1, 4 and 8 layers, a last partial run, source and destination layers 3 and 10, the resized path, a copy the scratch cannot take, an array's dirty layers with gaps. It fails on the three mutations I tried: a copy by 8, an upload run of 8, a dropped last partial run.fix/vk-bc-copy-extentis not published yet as far as I can see; the note now names the two lines that overlap and says a combined version has to keepbc_layers()in that loop.Not done: the raw counter records as files (every value is in the description's block; say if you want them attached), p99 and 1% lows (no speed claim is made), and a GPU run known to make a multi-layer copy or to use the second scratch: I do not know that these scenes make one, which is what the desktop test is asked for.
WIP: perf(vk): the BC3 scratch has the layers a run uses, 20 MB instead of 160to perf(vk): the BC3 scratch has the layers a run uses, 20 MB instead of 160View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.