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
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!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?
Type: perf · Risk: low · Tested on: Linux (SteamOS), Steam Deck OLED (RADV VANGOGH), two Decks · Needs: a desktop card of 4 to 8 GB, Windows or Linux
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
The scratch image and buffer of the ASTC to BC3 re-encode are made for the layers a run uses (one by default) instead of always eight: 20 MB instead of 160. A copy of several layers into a BC3 texture now goes one run a layer, as an upload already did.
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/.Measurements
Verdict: it helps memory and costs nothing: 139 MiB less GPU memory held on a Steam Deck OLED (the median of 7 pairs over three scenes), fps and start time unchanged. A second Steam Deck OLED measured 224 MB less on a moving route (dukenheimer's run, under Testing). Passed.
Hardware: SteamOS 3.9.5 (kernel 7.2.7-valve2), Steam Deck OLED: AMD Custom APU 0932, RADV VANGOGH, Mesa 26.2.0-devel, 16 GB shared with the GPU; 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/meminfo. Medians of 3 on the heavy view, the mean of the two elsewhereHow 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)this PR: perf/bc-scratch-one-layer @ 000cc9f2 (the change on main
fbd42024; on the fork asmeasured/bc-scratch-000cc9f2). The branch is now on main9e6f231d(1a2cef33and a docs commit):git range-diffshows the same patch, with only a neighbouring row ofdocs/development.mddiffering, and main has changed nothing in the scratch's code in between (bc_scratch_make,bc_scratch2_make,bc_scratch_layout,upload_astc_gpu,texture_copy_into_bc), so the numbers hold. Not measured again on this main.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. A run's GPU memory varies by about 100 MB from one start to the next, hence the pairs.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.
Flags and settings
No new flag: an existing switch now sizes the scratch too.
RX_VK_BC_LAYERS=<1..8>(existing)=8is the old size, with runs of 8 layersTesting
Tested:
python -I -B tests/run_all.pyon macOS with the Vulkan headers given, on the rebased head: 507 passed, 48 failed, 39 skipped. The 48 fail the same way on main9e6f231don that machine (no/proc, noposix_fallocate, Python 3.9, the Linux launch scripts and the like).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).a9b0c880(the same patch), Game Mode, docked, a scripted moving route in Kokiri Forest, 2 runs each, mirrored. GPU memory in the measured window: 5,846 MB with against 6,070 without (medians, -224 MB); peak 6,081 against 6,229 (-148 MB). Every run logged the BC3 route; none loggedVRAM fullor a failed upload; the route's screenshots show no texture error in either arm.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.414f56e2): nothing wrong seen on screen.b0b7f29a; clang, x86-64-v3, on the Steam Deck): 0 failed. Windows:longshot_vk.dllcross-builds from the same head with llvm-mingw 20261104 on Linux: 0 failed. Not run on Windows.Not tested:
RX_VK_UNEVEN_BC=0, AMD's uneven block levels), which is off by default.Must be tested:
RX_VK_BC_LAYERS=8against unset on one build of this PR)Risks
What could break: the textures that go through the scratch (terrain and other big ASTC textures kept as BC3), if a user of the scratch still assumed 8 layers: both users ask
bc_layers(), and the test runs their loops. 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).Turning it off:
RX_VK_BC_LAYERS=8brings back the old size and the old copy; reverting the commit removes the change.Compatibility: Nothing changes: saves and
longshot_options.iniare untouched, no shader or pipeline cache is rebuilt (no stamped file is touched),MKWVK_API_VERSIONis as it was.Reviewer notes
fix/vk-bc-copy-extent) also editstexture_copy_into_bc(). 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
See #130
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md).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 160Steam Deck OLED test, with and without this PR (dukenheimer's Deck)
Mesa 26.2.0-devel git-5c65d4fc31. Stock clocks. Game Mode, docked: the game gets a 1280x720 surface.
543846eb+ #2270fc2e8c9(with #225) + #22352a61f40+ #2323ccb48a8+ #303995aab15, with#384
a9b0c880, #468e5889e76, #488abf2227fand #4967fb4e2c1merged on top. All four merged clean. Linux suite: 544 passed,main's 5 known failures. Built with x86-64-v3 and the release play profile, LLVM 23.1.5 (glibc 2.35).
git revert -m 1), built the same way. Only that file is swappedin one warm test folder: the host
longshotfor #496,longshot_vk.sofor #384 and #468.upscaler, frame generation 3x, frame rate cap 60, vsync on.
RX_VK_READBACK_EARLY=1(#303) andHOS_VIDEO_LOG=1.In runs 2 (-#496) and 4 (-#468) the scripted route went into a house after its start, so most of those two windows is an interior.
Results for this PR (#384)
The process's GPU memory:
drm-memory-gtt+drm-memory-vramfrom/proc/<pid>/fdinfo, every 5 s.Medians: window 5,846 MB with, 6,070 without (-224 MB); peak 6,081 with, 6,229 without (-148 MB). The other four runs also have this
PR (they are without #496 or without #468); their window medians are 5,987, 5,864, 5,932 and 6,014 MB.
Reading. GPU memory is lower with this PR, consistent with a 140 MB smaller scratch. On a moving route the measurement is rough:
textures stream in, so GPU memory grows 55 to 300 MB a minute in the window, the Deck swaps (MemAvailable minimum 600 to 920 MB),
and runs of the same arm differ by 100 to 250 MB.
big ASTC textures are kept as BC3. None loggedVRAM full, a texture upload failure (its upload failed) orany other error; the only error lines were Steam's 32-bit overlay preload warning, the same in every run.
73.6 ms, rising toward the middle whatever ran there.
p0lux referenced this pull request2026-11-14 19:46:14 +00:00
p0lux referenced this pull request2026-11-14 19:46:32 +00:00
a9b0c8804db0b7f29a08Rebased onto main
9e6f231d(headb0b7f29a): one conflict, indocs/development.md: main rewrote the row next to this PR's (RX_VK_VRAM_MB); main's row is kept and this PR's added under it.git range-diffshows the same code patch. The numbers are the ones measured before; the description says why they still hold.The description now follows the new template: the status line, a summary under What changed, the verdict, Flags and settings under Measurements, Must be tested, and Risks with Compatibility. dukenheimer's Steam Deck OLED run (224 MB less GPU memory on a moving route, no texture error) is now under Testing, and the note on
texture_copy_into_bc()names #672.On the rebased head:
python -I -B tests/run_all.pyon macOS gives 507 passed, 48 failed, 39 skipped, the 48 failing the same way on main9e6f231don that machine; the back end builds for Linux (clang, on a Steam Deck) and for Windows (llvm-mingw 20261104, cross-built), 0 failed.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.