WIP: perf(vk): a written storage buffer bound again in the same recording is not noted again #797

Draft
dyst wants to merge 1 commit from dyst/Longshot:perf/vk-written-ssbo-once into main
Member

Type: perf · Risk: medium · Tested on: Windows 11 (the game-free checks; the game A/B is next) · Needs: a game run on NVIDIA, AMD and Intel

Why

A storage buffer a shader writes was noted again at every descriptor build that bound it: a lock and a map lookup for the texture note, ParLock and the pools' lock for the write-back and hazard, and a page stamp that also threw away every thread's kept uniform copies. The culling dispatches bind the same written ranges one after another, and most draws rebuild their descriptors. Found in an audit of the renderer's per-draw path; no issue on the tracker.

What changed

texture_gpu_wrote, gpu_buffer_bind's written path and memory_stamp_gpu each keep a thread's last notes by range and leave a repeat as it was while nothing their note answers has moved (the recording, the submission, the pools, the mirror's sync, and any generation a reader kept since).

  • memory: WriteMemo, write_memo_keep, write_memo_hit, stamp_fence, StampSeen (gpu/vk/vk_memory.cpp)
  • textures: gpu_wrote_changes, GpuWroteSeen in texture_gpu_wrote (gpu/vk/vk_texture.cpp)
  • docs: the RX_VK_WRITTEN_EACH=1 row in docs/development.md
  • tests: tests/test_written_once.py; test_direct_buffer_writes runs the real repeat and each condition

Measurements

Verdict: not measured yet: the measurements are pending from the RX_VK_BENCH A/B of this PR @ 346b7434 against main @ b9e95173 (under way), which will replace this section.

Flags and settings

One switch for an A/B.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_WRITTEN_EACH=1 Every bind of a written storage buffer noted, as before - unset On: the old path measurement pending leave unset; =1 for an A/B or if flicker or cyan shows over compute-written memory

Testing

Tested: Windows 11 (Ryzen 9 3900X), llvm-mingw and the Vulkan headers of the builder's v2.0.14 tools: rebased onto main @ b9e95173 (this PR @ 346b7434; it stands alone on main, on no other PR). The 85 checks that read this diff (listed below: those under tests/ and nx2/ that name a changed file, and the ones this PR adds or changes), not the whole suite: python -I -B tests/run_all.py --jobs 6 <those checks> with RX_CPM at the builder's CPM cache: 79 passed, 6 failed; each failure (nx2/test_bc_storage_views.py, tests/test_crash_dialog.py, tests/test_oot_state_tap.py, tests/test_vk_occlusion_queries.py, tests/test_vk_pgo.py, tests/test_walk_key.py) also fails on main @ b9e95173 in the same checks, on the same PC and the same day.

The 85 checks

nx2/test_bc_storage_views.py nx2/test_import_windows.py nx2/test_texture_init_barrier.py tests/test_3d_view_create_flags.py tests/test_astc_bc_policy.py tests/test_bc_raw_targets.py tests/test_bc_upload_retry.py tests/test_bind_memo.py tests/test_blocks_at_start.py tests/test_capture_copy_helpers.py tests/test_capture_copy_nt.py tests/test_capture_copy_runs.py tests/test_capture_wait_log.py tests/test_check_pr_description.py tests/test_commit_limit.py tests/test_commit_room.py tests/test_compressed_over_rt.py tests/test_crash_dialog.py tests/test_dev_packs.py tests/test_direct_buffer_writes.py tests/test_drawn_page_map.py tests/test_drawn_writeback.py tests/test_dump_readback.py tests/test_evict_reason.py tests/test_frame_trace_waits.py tests/test_hold_gpu.py tests/test_image_view_retry.py tests/test_import_buffer_binding.py tests/test_import_chunk_default.py tests/test_import_span.py tests/test_import_write_watch.py tests/test_inline_write_order.py tests/test_keep_rendered.py tests/test_late_fix_build.py tests/test_late_fix_shaders.py tests/test_linux_pressure.py tests/test_low_vram_texture_mips.py tests/test_mem_trim.py tests/test_met_wait_capture.py tests/test_mirror_budget.py tests/test_mirror_gpu_only.py tests/test_mirror_inherit.py tests/test_mirror_trim.py tests/test_native_astc_texture_count.py tests/test_oot_state_tap.py tests/test_parallel_init_order.py tests/test_parallel_mirror_make.py tests/test_parallel_rb_refresh.py tests/test_pipeline_state_portable.py tests/test_pool_span.py tests/test_quick_pipelines.py tests/test_readback_part.py tests/test_report_words.py tests/test_retired_give_back.py tests/test_sparse_mirror.py tests/test_split_before_draw.py tests/test_split_ub_flush.py tests/test_staging_arena_fallback.py tests/test_submitted_page.py tests/test_target_image_pool.py tests/test_target_overlap.py tests/test_tex_spans_once_a_present.py tests/test_texel_buffer_ownership.py tests/test_texture_budget.py tests/test_texture_layout_sizes.py tests/test_texture_squeeze.py tests/test_texture_staging_publication.py tests/test_threaded_rendering.py tests/test_vertex_bind.py tests/test_vk_occlusion_queries.py tests/test_vk_pgo.py tests/test_vk_range_walk.py tests/test_vram_compact.py tests/test_vram_pressure.py tests/test_vulkan_clear_mip.py tests/test_walk_key.py tests/test_wb_diff_cpu_writes.py tests/test_wb_diff_split.py tests/test_wb_diff_stale_shadow.py tests/test_wb_learn.py tests/test_wb_observation_lifetime.py tests/test_wb_pages.py tests/test_wb_shadow_follows_sync.py tests/test_wb_shadow_remake.py tests/test_written_once.py

Not tested: the game yet. This PR is WIP until its A/B is in: the RX_VK_BENCH A/B against main is running on an RTX 3080 with a Ryzen 9 3900X, Windows 11; AMD and Intel need other testers.

Must be tested:

  • Any OS and GPU, defaults and once with RX_VK_WRITTEN_EACH=1: Kokiri Forest and Hyrule Field a few minutes, expecting the same picture (no one-frame flicker, no cyan or stale textures) and the same MB written back (vk: a frame: mirrors) (Test Farm: @Beanstalk any, those scenes with and without RX_VK_WRITTEN_EACH=1)

Risks

What could break: A write-back or hazard missed, or a kept uniform copy not refreshed: one-frame flicker or stale data over compute-written memory. Fewer cap_view bumps can delay a kept read's refresh of guest memory that an earlier submission's write-back is landing in at that moment; that timing was already not fixed.

Turning it off: RX_VK_WRITTEN_EACH=1

Compatibility: Nothing changes: no caches, settings or vk ABI involved.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • A game-free test added for the fix, failing on main and passing here.
  • Docs updated.

🤖 Generated with Claude Code

**Type:** perf · **Risk:** medium · **Tested on:** Windows 11 (the game-free checks; the game A/B is next) · **Needs:** a game run on NVIDIA, AMD and Intel ## Why A storage buffer a shader writes was noted again at every descriptor build that bound it: a lock and a map lookup for the texture note, ParLock and the pools' lock for the write-back and hazard, and a page stamp that also threw away every thread's kept uniform copies. The culling dispatches bind the same written ranges one after another, and most draws rebuild their descriptors. Found in an audit of the renderer's per-draw path; no issue on the tracker. ## What changed `texture_gpu_wrote`, `gpu_buffer_bind`'s written path and `memory_stamp_gpu` each keep a thread's last notes by range and leave a repeat as it was while nothing their note answers has moved (the recording, the submission, the pools, the mirror's sync, and any generation a reader kept since). - memory: `WriteMemo`, `write_memo_keep`, `write_memo_hit`, `stamp_fence`, `StampSeen` (`gpu/vk/vk_memory.cpp`) - textures: `gpu_wrote_changes`, `GpuWroteSeen` in `texture_gpu_wrote` (`gpu/vk/vk_texture.cpp`) - docs: the `RX_VK_WRITTEN_EACH=1` row in docs/development.md - tests: `tests/test_written_once.py`; `test_direct_buffer_writes` runs the real repeat and each condition ## Measurements **Verdict:** not measured yet: the measurements are pending from the RX_VK_BENCH A/B of this PR @ 346b7434 against main @ b9e95173 (under way), which will replace this section. ### Flags and settings One switch for an A/B. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_WRITTEN_EACH=1` | Every bind of a written storage buffer noted, as before | - | unset | On: the old path | measurement pending | leave unset; `=1` for an A/B or if flicker or cyan shows over compute-written memory | ## Testing **Tested:** Windows 11 (Ryzen 9 3900X), llvm-mingw and the Vulkan headers of the builder's v2.0.14 tools: rebased onto main @ b9e95173 (this PR @ 346b7434; it stands alone on main, on no other PR). The 85 checks that read this diff (listed below: those under tests/ and nx2/ that name a changed file, and the ones this PR adds or changes), not the whole suite: `python -I -B tests/run_all.py --jobs 6 <those checks>` with `RX_CPM` at the builder's CPM cache: 79 passed, 6 failed; each failure (`nx2/test_bc_storage_views.py`, `tests/test_crash_dialog.py`, `tests/test_oot_state_tap.py`, `tests/test_vk_occlusion_queries.py`, `tests/test_vk_pgo.py`, `tests/test_walk_key.py`) also fails on main @ b9e95173 in the same checks, on the same PC and the same day. <details><summary>The 85 checks</summary> `nx2/test_bc_storage_views.py` `nx2/test_import_windows.py` `nx2/test_texture_init_barrier.py` `tests/test_3d_view_create_flags.py` `tests/test_astc_bc_policy.py` `tests/test_bc_raw_targets.py` `tests/test_bc_upload_retry.py` `tests/test_bind_memo.py` `tests/test_blocks_at_start.py` `tests/test_capture_copy_helpers.py` `tests/test_capture_copy_nt.py` `tests/test_capture_copy_runs.py` `tests/test_capture_wait_log.py` `tests/test_check_pr_description.py` `tests/test_commit_limit.py` `tests/test_commit_room.py` `tests/test_compressed_over_rt.py` `tests/test_crash_dialog.py` `tests/test_dev_packs.py` `tests/test_direct_buffer_writes.py` `tests/test_drawn_page_map.py` `tests/test_drawn_writeback.py` `tests/test_dump_readback.py` `tests/test_evict_reason.py` `tests/test_frame_trace_waits.py` `tests/test_hold_gpu.py` `tests/test_image_view_retry.py` `tests/test_import_buffer_binding.py` `tests/test_import_chunk_default.py` `tests/test_import_span.py` `tests/test_import_write_watch.py` `tests/test_inline_write_order.py` `tests/test_keep_rendered.py` `tests/test_late_fix_build.py` `tests/test_late_fix_shaders.py` `tests/test_linux_pressure.py` `tests/test_low_vram_texture_mips.py` `tests/test_mem_trim.py` `tests/test_met_wait_capture.py` `tests/test_mirror_budget.py` `tests/test_mirror_gpu_only.py` `tests/test_mirror_inherit.py` `tests/test_mirror_trim.py` `tests/test_native_astc_texture_count.py` `tests/test_oot_state_tap.py` `tests/test_parallel_init_order.py` `tests/test_parallel_mirror_make.py` `tests/test_parallel_rb_refresh.py` `tests/test_pipeline_state_portable.py` `tests/test_pool_span.py` `tests/test_quick_pipelines.py` `tests/test_readback_part.py` `tests/test_report_words.py` `tests/test_retired_give_back.py` `tests/test_sparse_mirror.py` `tests/test_split_before_draw.py` `tests/test_split_ub_flush.py` `tests/test_staging_arena_fallback.py` `tests/test_submitted_page.py` `tests/test_target_image_pool.py` `tests/test_target_overlap.py` `tests/test_tex_spans_once_a_present.py` `tests/test_texel_buffer_ownership.py` `tests/test_texture_budget.py` `tests/test_texture_layout_sizes.py` `tests/test_texture_squeeze.py` `tests/test_texture_staging_publication.py` `tests/test_threaded_rendering.py` `tests/test_vertex_bind.py` `tests/test_vk_occlusion_queries.py` `tests/test_vk_pgo.py` `tests/test_vk_range_walk.py` `tests/test_vram_compact.py` `tests/test_vram_pressure.py` `tests/test_vulkan_clear_mip.py` `tests/test_walk_key.py` `tests/test_wb_diff_cpu_writes.py` `tests/test_wb_diff_split.py` `tests/test_wb_diff_stale_shadow.py` `tests/test_wb_learn.py` `tests/test_wb_observation_lifetime.py` `tests/test_wb_pages.py` `tests/test_wb_shadow_follows_sync.py` `tests/test_wb_shadow_remake.py` `tests/test_written_once.py` </details> **Not tested:** the game yet. This PR is WIP until its A/B is in: the RX_VK_BENCH A/B against main is running on an RTX 3080 with a Ryzen 9 3900X, Windows 11; AMD and Intel need other testers. **Must be tested:** - Any OS and GPU, defaults and once with `RX_VK_WRITTEN_EACH=1`: Kokiri Forest and Hyrule Field a few minutes, expecting the same picture (no one-frame flicker, no cyan or stale textures) and the same MB written back (`vk: a frame: mirrors`) (Test Farm: @Beanstalk any, those scenes with and without RX_VK_WRITTEN_EACH=1) ## Risks **What could break:** A write-back or hazard missed, or a kept uniform copy not refreshed: one-frame flicker or stale data over compute-written memory. Fewer `cap_view` bumps can delay a kept read's refresh of guest memory that an earlier submission's write-back is landing in at that moment; that timing was already not fixed. **Turning it off:** `RX_VK_WRITTEN_EACH=1` **Compatibility:** Nothing changes: no caches, settings or vk ABI involved. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perf(vk): a written storage buffer bound again in the same recording is not noted again
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 8s
Website / Website on Cloudflare (pull_request_target) Has been skipped
09c6e45841
A storage buffer a program writes was noted at every descriptor build that bound it, and most draws rebuild theirs while the
culling dispatches bind the same written ranges one after another: each time texture_gpu_wrote took its lock and a map lookup,
gpu_buffer_bind took ParLock and the pools' lock to push the same write-back and hazard again, and memory_stamp_gpu stamped the
pages again and bumped cap_view, which made every thread's kept uniform copies and capture reads go again.

Each of the three now keeps a thread's last notes by range and leaves a repeat as it was while nothing its note answers has
moved:
- texture_gpu_wrote: the same range in the same recording with no change to the map of GPU writes since (gpu_wrote_changes);
- gpu_buffer_bind's written path (write_memo): the same submission, recording, place in the queue and pools, the pool's mirror
  still in use and synced for this place, so no mirror_sync would run; the submission's write-backs are merged by range and the
  hazard table only asks whether one overlaps. Not with RX_VK_WB_MAX_KB, nor for a sparse mirror;
- memory_stamp_gpu: the same range before any reader keeps a generation or a capture gives pages one (stamp_fence): once
  stamped the range is newer than every kept generation, and a GPU write changes nothing in guest memory or a capture.
RX_VK_WRITTEN_EACH=1: every bind noted as before (docs/development.md).

The one difference beyond the numbering: fewer cap_view bumps can delay a kept read's refresh of guest memory that is being
written at that moment by the game or an earlier submission's write-back, whose timing was already not fixed.

tests/test_written_once.py runs the real texture_gpu_wrote and memory_stamp_gpu over random notes, stamps, kept generations,
captures and reader questions with and without the switch (every state and answer equal); test_direct_buffer_writes runs the
real gpu_buffer_bind's repeat and each condition that must take the full path.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Author
Member

The checks on this PC (Windows 11, RTX 3080, Ryzen 9 3900X, llvm-mingw from the builder's v2.0.14 tools): python -I -B tests/run_all.py on main @ aa95d35f fails these 19 here, and so does this branch: test_appimage_player_build.py, test_bake_portability.py, test_cpm_discovery.py, test_cpp_preflight.py, test_crash_dialog.py, test_env_on.py, test_fpx_sticky.py, test_lift_cleanup_error.py, test_linux_preflight.py, test_make_game_cache.py, test_make_game_prepare.py, test_missing_nup.py, test_oot_state_tap.py, test_options_budget_once.py, test_readback_late.py, test_spawn_emit.py, test_tested_configurations.py, test_vk_pgo.py, test_walk_key.py. Any other failure in a full run on this branch was rerun on its own and passed (the full runs shared the PC with dev_packs builds: timing checks).

Next: an interleaved A/B in the game against main on one dev_packs base of aa95d35f; the numbers will replace the Measurements section.

The checks on this PC (Windows 11, RTX 3080, Ryzen 9 3900X, llvm-mingw from the builder's v2.0.14 tools): `python -I -B tests/run_all.py` on main @ aa95d35f fails these 19 here, and so does this branch: `test_appimage_player_build.py`, `test_bake_portability.py`, `test_cpm_discovery.py`, `test_cpp_preflight.py`, `test_crash_dialog.py`, `test_env_on.py`, `test_fpx_sticky.py`, `test_lift_cleanup_error.py`, `test_linux_preflight.py`, `test_make_game_cache.py`, `test_make_game_prepare.py`, `test_missing_nup.py`, `test_oot_state_tap.py`, `test_options_budget_once.py`, `test_readback_late.py`, `test_spawn_emit.py`, `test_tested_configurations.py`, `test_vk_pgo.py`, `test_walk_key.py`. Any other failure in a full run on this branch was rerun on its own and passed (the full runs shared the PC with dev_packs builds: timing checks). Next: an interleaved A/B in the game against main on one dev_packs base of aa95d35f; the numbers will replace the Measurements section.
dyst force-pushed perf/vk-written-ssbo-once from 09c6e45841
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 8s
Website / Website on Cloudflare (pull_request_target) Has been skipped
to 346b74349a
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-15 19:28:01 +00:00
Compare
Author
Member

Rebased onto main @ b9e95173 (now 346b7434; stands alone on main, not stacked); the 85 checks that read this diff, not the whole suite: 79 passed, 6 failed, each failure also failing on main @ b9e95173 in the same checks. Stays WIP: measurements pending from the RX_VK_BENCH A/B. Heads-up: main has moved on since (b8d2b04e); #590's wrote_regions::mark in texture_gpu_wrote now conflicts with this PR's early return there, for the next rebase.

Rebased onto main @ b9e95173 (now 346b7434; stands alone on main, not stacked); the 85 checks that read this diff, not the whole suite: 79 passed, 6 failed, each failure also failing on main @ b9e95173 in the same checks. Stays WIP: measurements pending from the RX_VK_BENCH A/B. Heads-up: main has moved on since (b8d2b04e); #590's `wrote_regions::mark` in `texture_gpu_wrote` now conflicts with this PR's early return there, for the next rebase.
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request has changes conflicting with the target branch.
  • gpu/vk/vk_texture.cpp
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.
git fetch -u 'perf/vk-written-ssbo-once:dyst-perf/vk-written-ssbo-once'
git switch 'dyst-perf/vk-written-ssbo-once'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!797
No description provided.