Skip to content

perf(vk): re-upload only the part of a texture a GPU write covered - #2876

Open
bluetoe-99 wants to merge 1 commit into
NotRexed:mainfrom
bluetoe-99:fix/vk-gpu-write-part-upload
Open

bluetoe-99 wants to merge 1 commit into
NotRexed:mainfrom
bluetoe-99:fix/vk-gpu-write-part-upload

Conversation

@bluetoe-99

@bluetoe-99 bluetoe-99 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

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_into reports the ranges it matched, clipped to the texture's storage and merged, in image-relative offsets.
  • texture_touch uploads 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 moves upload_gen past them, so a partial that skipped them would lose them permanently. An event where the CPU also wrote still uploads the full amount.
  • Sparse images keep the whole path; RX_VK_NO_PARTIAL and RX_VK_GPU_WRITE_WHOLE=1 restore the old behaviour for A/B.
  • A tally on the path, so anyone can see whether it is hot in their own scene rather than guessing: 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:

stock patched
per bind-only write-back event 15,360 KB (whole image) 1,800 KB (the bound range)
event where the CPU had also written 15,360 KB 15,360 KB (the union, by design)
the path over a tour, sample 1 150 events, 1,462 MB 150 events, 648 MB
the path over a tour, sample 2 150 events, 1,434 MB 150 events, 632 MB
retirement past the budget, last leg 2,910 textures (841 retire lines) 3,264 textures (840 lines): no eviction regression
fps leg medians 91.8/94.0/96.0/81.9/90.3, then 91.4/88.9/90.1/93.1/88.2 88.0/92.5/77.1/85.5/87.4, then 91.3/90.5/91.5/92.4/92.0

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's HOOKSHOT_SHADER_STAMP hashes the translator sources (eden/, sirit/, compat/, vk_shader.cpp, vk_bank.cpp, vk_state.h, vk_api.h) and deliberately not vk_texture.cpp, so existing translated programs, pipeline notes and hookshot_prepared.bin stay valid after this ships.

Tests. tests/test_gpu_write_part.py (new) cuts the recorder and the reporter, texture_gpu_wrote and gpu_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's getenv deprecation. python -I -B tests/run_all.py with the Vulkan and SPIR-V headers available: 492 passed, 0 failed, 35 skipped on 9b6072cd (the skips want spirv-val and 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.

@bluetoe-99
bluetoe-99 force-pushed the fix/vk-gpu-write-part-upload branch from 0a66760 to 8d108b3 Compare November 12, 2026 18:40
@bluetoe-99
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
bluetoe-99 force-pushed the fix/vk-gpu-write-part-upload branch from 0c457b3 to 8dde7b5 Compare November 12, 2026 19:09
@bluetoe-99 bluetoe-99 closed this Nov 12, 2026
@bluetoe-99 bluetoe-99 reopened this Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant