Repository navigation
perf(vk): re-upload only the part of a texture a GPU write covered - #2876
Open
bluetoe-99 wants to merge 1 commit into
Open
bluetoe-99 wants to merge 1 commit into
bluetoe-99 wants to merge 1 commit into
Conversation
bluetoe-99
force-pushed
the
fix/vk-gpu-write-part-upload
branch
from
November 12, 2026 18:40
0a66760 to
8d108b3
Compare
bluetoe-99
marked this pull request as ready for review
November 12, 2026 19:02
A buffer bound for writing marks its whole bound range written, though the shader may touch less of it, and the game's scratch lands on the storage of a target the GPU drew and finalized: in Castle Town a 1.75 MB bind made the renderer re-upload a 15 MB 2560x1440 target every frame of the burst (NotRexed#2780). gpu_wrote_into now reports the ranges it matched, clipped to the image and merged, and texture_touch uploads those plus the CPU's writes since: the whole upload used to cover them, and the upload moves upload_gen past them, so carrying them along is required. Sparse images and RX_VK_NO_PARTIAL keep the old path; RX_VK_GPU_WRITE_WHOLE=1 asks for all of it again. The new "gpu-write uploads" line counts the path: over a five-area tour, run twice, 150 events copied 648 MB where the whole-image rule would have copied 1,462 MB (second sample 632 against 1,434). Per event 15,360 KB to 1,800 KB for a bind-only write-back, and an event where the CPU had also written still uploads its full 15,360 KB. No fps effect across two samples per side, and the tour showed up to 19 fps of run-to-run noise for the same build, so none is claimed. tests/test_gpu_write_part.py runs the marking and the range report against stand-ins.
bluetoe-99
force-pushed
the
fix/vk-gpu-write-part-upload
branch
from
November 12, 2026 19:09
0c457b3 to
8dde7b5
Compare
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.
Why. A storage buffer bound for writing is recorded as its whole bound range, and any texture whose storage overlaps that range is then re-uploaded whole. In Castle Town one of the game's compute pipelines binds a 1.75 MB write buffer whose range lands inside a finalized 2560x1440 render target's storage, and every frame of that burst the renderer copied 15 MB of guest memory over the image, on a path where nothing at all had been written by the CPU (
upload over a drawn image ... 0 KB dirty in 0 ranges, a GPU buffer wrote it). This is one of the costs measured under #2780, where the resident/mirror bookkeeping grows with each area change.What changes.
gpu_wrote_intoreports the ranges it matched, clipped to the texture's storage and merged, in image-relative offsets.texture_touchuploads those ranges instead of the whole image, and the CPU's writes since the last upload in the same pass. That second half is required: the whole upload used to cover those pages, and the upload movesupload_genpast them, so a partial that skipped them would lose them permanently. An event where the CPU also wrote still uploads the full amount.RX_VK_NO_PARTIALandRX_VK_GPU_WRITE_WHOLE=1restore the old behaviour for A/B.vk: gpu-write uploads: N (X MB copied, the whole image would have been Y MB).Measured (Linux, RTX 5070 Ti 16 GB, one save, one process, five area changes: Hyrule Field, Castle Town Market, Kokiri Forest, Hyrule Field, Castle Town Market). Both arms were built from the same tree with the same flags, differing only by this patch, and the tour was run twice per build:
So this is a copy and staging reduction on a path that arrives in bursts, not a frame-rate win on a 16 GB card: 56% less copied on the path, 802-814 MB not copied per tour. No fps effect is claimed, and none was measurable. On the 8-12 GB cards of #2690 and #2780 the same events compete with a texture budget that is already being evicted past, which is where the saved copying should matter more.
Cache cost for players: none.
nx2/build.py'sHOOKSHOT_SHADER_STAMPhashes the translator sources (eden/,sirit/,compat/,vk_shader.cpp,vk_bank.cpp,vk_state.h,vk_api.h) and deliberately notvk_texture.cpp, so existing translated programs, pipeline notes andhookshot_prepared.binstay valid after this ships.Tests.
tests/test_gpu_write_part.py(new) cuts the recorder and the reporter,texture_gpu_wroteandgpu_wrote_into, out of the source and runs them against stand-ins: a write inside reports its part; one spanning either end is clipped; adjacent writes merge; an unfinished submission is not reported until it completes; the same write is not reported to the same texture twice; a write elsewhere reports nothing. Plus source guards for the switch, the CPU-write union and the sparse exclusion. It compiles its fixture with-D_CRT_SECURE_NO_WARNINGS, the same flag the sibling tests pass for the MSVC CRT'sgetenvdeprecation.python -I -B tests/run_all.pywith the Vulkan and SPIR-V headers available: 492 passed, 0 failed, 35 skipped on9b6072cd(the skips wantspirv-valand a few Windows-only tools), and 411 passed, 13 failed, 67 skipped on the older base the measurements were taken on, those 13 failing identically on the pristine checkout of that base.Not claimed. No visual change is intended. The change copies a strict subset of what the old path copied, so the known hazards of partial upload (the rainbow-sky case, where an unrelated variable sharing an edge page re-uploaded garbage over a drawn LUT) get narrower, not wider.