WIP: perf(vk): a stage's descriptors are kept across draws in a recording part while what they were resolved from stands #794

Draft
dyst wants to merge 1 commit from dyst/Longshot:perf/vk-descriptor-cache 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

Every draw rebuilt its descriptors from scratch on the replay thread, because any bind anywhere counted as a change and the game binds a new uniform buffer for nearly every draw. In Kokiri Forest that rebuild (build_descriptors) is about 1.3 ms of the serial replay thread a frame (docs/cpu-profile.md), most of it resolving textures, samplers and buffers that had not changed since the draw before. Found in an audit of the renderer's per-draw path; no issue on the tracker.

What changed

Each program's stages now keep, for one recording part, the texture, storage-buffer and uniform-buffer descriptors their last build resolved, and use them while what they came from stands. Anything that has to be noted at each draw (written buffers, storage images) or that reads guest memory without a generation (handles from c3+, handle programs, compute/task/mesh stages) is still resolved every time.

  • draw: kept_program, KeptStage, kept_tex_holds, kept_buf_holds, kept_ubo_holds, desc_keep_textures, desc_keep_storage and the kept paths in build_descriptors (gpu/vk/vk_draw.cpp); desc_kept_part counted in draw_begin_recording
  • memory, replay: guest_pools_stamp() (gpu/vk/vk_memory.cpp), ub_list_generation() (gpu/vk/vk_replay.cpp), read by the keys
  • tests: tests/test_desc_keep.py; three fixtures that compile draw_begin_recording get the new counter

Measurements

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

Flags and settings

Switches:

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_DESC_KEEP=0 Every descriptor resolved at every build - unset (kept) Off: the old path measurement pending leave unset; =0 for an A/B or if a draw shows the wrong texture
RX_VK_TRACE_TEX, RX_VK_LOG_IMG, RX_VK_WATCH_PROG, RX_VK_DRAW_STATS, RX_VK_PEEK_PROG, RX_VK_LOG_TABLE, RX_VK_SLOW_DESC, RX_VK_ASYNC_IMPORT (existing, unchanged) Diagnostics that log each descriptor as it is resolved, and the async import that resolves storage buffers into stream copies as before as before; set, the descriptors are not kept Their logs and copies stay complete - unchanged

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 @ 6352d91c; it stands alone on main, on no other PR). The 230 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: 221 passed, 9 failed; each failure (tests/test_crash_dialog.py, tests/test_linux_driver_fault_guard.py, tests/test_oot_state_tap.py, tests/test_pipeline_tour.py, tests/test_readback_late.py, tests/test_readback_wait_bounded.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. Before main moved on, the whole suite ran on this PR rebased onto main @ 1310ecc6 (86389b35): 501 passed, 17 failed, the same 17 as main @ 1310ecc6's full run on this PC.

The 230 checks

nx2/test_cbuf_table_extent.py nx2/test_dump_ssbo_read.py nx2/test_import_windows.py nx2/test_mesh_dynamic_state.py nx2/test_timeline_modes.py tests/test_amd_subgroup_profiles.py tests/test_antilag.py tests/test_audio_mono.py tests/test_audio_volume.py tests/test_auto_settings.py tests/test_bake_ds.py tests/test_bank_gen.py tests/test_bc_raw_targets.py tests/test_bind_memo.py tests/test_bug_report.py tests/test_camera_speed.py tests/test_capture_copy_helpers.py tests/test_capture_copy_nt.py tests/test_capture_copy_runs.py tests/test_capture_dimensions.py tests/test_capture_readback_visibility.py tests/test_capture_resources.py tests/test_capture_slot_ownership.py tests/test_capture_wait_log.py tests/test_check_pr_description.py tests/test_commit_room.py tests/test_crash_dialog.py tests/test_crash_record.py tests/test_desc_clock.py tests/test_desc_counts.py tests/test_desc_keep.py tests/test_dev_packs.py tests/test_device_lost_exit.py tests/test_direct_buffer_writes.py tests/test_dispatch_clock.py tests/test_dlss_checks.py tests/test_draw_binds.py tests/test_draw_side_effects.py tests/test_draw_stream_visibility.py tests/test_drawn_page_map.py tests/test_driver_cache_room.py tests/test_driver_faults.py tests/test_dummy_views.py tests/test_dump_readback.py tests/test_equal_depth_margin.py tests/test_fallback_buffer_cleanup.py tests/test_fallback_depth_hold.py tests/test_fallback_loaded.py tests/test_fallback_pipeline_state.py tests/test_fallback_size_policy.py tests/test_flash_sampling.py tests/test_flat_map.py tests/test_flush_nothing_held.py tests/test_fps_stats.py tests/test_frame_log_headless.py tests/test_frame_trace_waits.py tests/test_frame_wait_bounds.py tests/test_framegen_preset.py tests/test_framelog_clock.py tests/test_framelog_sink.py tests/test_fsr_image_retirement.py tests/test_fsr_uniform_ownership.py tests/test_gpl_cold_library.py tests/test_gpl_library_cache.py tests/test_gpu_hang_watch.py tests/test_gpu_wait_stats.py tests/test_handle_lookup.py tests/test_handle_table_arrays.py tests/test_hdr_encode.py tests/test_hold_gpu.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_input_background.py tests/test_intel_compiler_gate.py tests/test_intel_compiles_auto.py tests/test_intel_quick_compute.py tests/test_jobs_failure.py tests/test_jobs_wait_runs_queued.py tests/test_late_fix_build.py tests/test_late_fix_shaders.py tests/test_linux_driver_fault_guard.py tests/test_linux_pressure.py tests/test_load_default.py tests/test_log_levels.py tests/test_longshot_ctl.py tests/test_masked_clear_failure.py tests/test_mem_trim.py tests/test_menu_env_on.py tests/test_menu_keys.py tests/test_mesh_args_cleanup.py tests/test_mesh_fallback.py tests/test_mesh_fallback_failed_once.py tests/test_mesh_query_feature.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_module_ids.py tests/test_mouse_settings.py tests/test_oot_damage.py tests/test_oot_fov.py tests/test_oot_state_tap.py tests/test_opening_skip.py tests/test_option_ids.py tests/test_parallel_dry_marks.py tests/test_parallel_init_order.py tests/test_parallel_mirror_make.py tests/test_parallel_rb_refresh.py tests/test_parallel_small_cb.py tests/test_pass_hazard.py tests/test_pcache_fetch_lock.py tests/test_pcache_header_read.py tests/test_pcache_stamp.py tests/test_pipeline_cache_save.py tests/test_pipeline_notes_open.py tests/test_pipeline_play_cache.py tests/test_pipeline_share.py tests/test_pipeline_state_computes.py tests/test_pipeline_state_counts.py tests/test_pipeline_state_portable.py tests/test_pipeline_states.py tests/test_pipeline_tour.py tests/test_png_publication.py tests/test_pool_span.py tests/test_precompile_compute_guard.py tests/test_precompile_pipeline_cleanup.py tests/test_predict_resume.py tests/test_predict_types.py tests/test_predicted_check.py tests/test_prepare_cache_save.py tests/test_prepare_cpus.py tests/test_prepare_seed_stamp.py tests/test_prepare_stall.py tests/test_prepare_workers.py tests/test_prepared_stamps.py tests/test_present_ahead.py tests/test_present_aspect.py tests/test_present_aspect_vulkan.py tests/test_present_device_loss.py tests/test_present_geometry_trace.py tests/test_present_semaphores.py tests/test_present_source_crop.py tests/test_present_thread.py tests/test_prof_clock.py tests/test_prof_sampling.py tests/test_program_cache_format.py tests/test_program_cache_one_variant.py tests/test_program_cache_reader.py tests/test_psychov35_dlss.py tests/test_psychov35_taau.py tests/test_push_state.py tests/test_quick_pipelines.py tests/test_radv_mesh_optimizer.py tests/test_reactive_mask.py tests/test_readback_late.py tests/test_readback_part.py tests/test_readback_settle.py tests/test_readback_wait_bounded.py tests/test_recomplauncher_json.py tests/test_recorded_wait_flush.py tests/test_recorder_own_idle.py tests/test_recording_admission.py tests/test_registration_host_copies.py tests/test_replay_copies.py tests/test_report_words.py tests/test_reserved_core.py tests/test_retired_give_back.py tests/test_rumble_menu.py tests/test_same_bind.py tests/test_same_draw.py tests/test_same_dyn.py tests/test_sampler_admission.py tests/test_scene_format.py tests/test_shader_module_guard.py tests/test_shader_stamp_cache.py tests/test_shader_stamp_inputs.py tests/test_shadow_mask_guard.py tests/test_skip_title.py tests/test_slow_frame_limit.py tests/test_sparse_mirror.py tests/test_split_before_draw.py tests/test_split_ub_flush.py tests/test_startup_progress.py tests/test_state_list_plain.py tests/test_status_line.py tests/test_stick_dead_zone.py tests/test_submitted_page.py tests/test_taau_env_lookup.py tests/test_taau_fxaa.py tests/test_taau_typed_dispatch.py tests/test_table_track.py tests/test_temporal_jitter.py tests/test_tex_spans_once_a_present.py tests/test_texel_buffer_ownership.py tests/test_text_size.py tests/test_text_speed.py tests/test_texture_handle_banks.py tests/test_texture_squeeze.py tests/test_third_party_objects.py tests/test_threaded_rendering.py tests/test_tls_objects.py tests/test_tree_shadow_mask.py tests/test_ub_gap.py tests/test_ub_memo.py tests/test_ub_threads.py tests/test_ub_word_index.py tests/test_upscale_fallback.py tests/test_upscale_independent.py tests/test_upscaler_rows.py tests/test_value_rec_last_part.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_pressure.py tests/test_vsync_row.py tests/test_walk_key.py tests/test_warm_bounded.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

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_DESC_KEEP=0: Kokiri Forest, Castle Town and a dungeon a few minutes each, expecting the same picture (no wrong or flickering textures, no missing objects) and a lower replay time (vk: a frame's replay time (ms): submit) (Test Farm: @Beanstalk any, those scenes with and without RX_VK_DESC_KEEP=0)

Risks

What could break: A kept descriptor that should have changed: an object drawn with another's texture or buffer for the rest of a recording part (about 120 draws). Every input the build reads is in a key or excluded from keeping; the parallel replay's workers keep their own.

Measured regression (open): the first in-game numbers (RTX 3080, Ryzen 9 3900X, Kokiri Forest; this PR and main @ aa95d35f on one dev_packs base; the comment of 18:33) are slower, not faster: descriptors 3.44 / 3.95 / 3.69 ms a frame against 2.78 / 2.52 / 2.89 on main in the fps A/B, and 1.79 against 1.25 ms (the whole replay 7.82 against 7.68) with RX_VK_BENCH=2600,100. Under investigation; this PR stays WIP until that is understood.

Turning it off: RX_VK_DESC_KEEP=0

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 Every draw rebuilt its descriptors from scratch on the replay thread, because any bind anywhere counted as a change and the game binds a new uniform buffer for nearly every draw. In Kokiri Forest that rebuild (`build_descriptors`) is about 1.3 ms of the serial replay thread a frame (docs/cpu-profile.md), most of it resolving textures, samplers and buffers that had not changed since the draw before. Found in an audit of the renderer's per-draw path; no issue on the tracker. ## What changed Each program's stages now keep, for one recording part, the texture, storage-buffer and uniform-buffer descriptors their last build resolved, and use them while what they came from stands. Anything that has to be noted at each draw (written buffers, storage images) or that reads guest memory without a generation (handles from c3+, handle programs, compute/task/mesh stages) is still resolved every time. - draw: `kept_program`, `KeptStage`, `kept_tex_holds`, `kept_buf_holds`, `kept_ubo_holds`, `desc_keep_textures`, `desc_keep_storage` and the kept paths in `build_descriptors` (`gpu/vk/vk_draw.cpp`); `desc_kept_part` counted in `draw_begin_recording` - memory, replay: `guest_pools_stamp()` (`gpu/vk/vk_memory.cpp`), `ub_list_generation()` (`gpu/vk/vk_replay.cpp`), read by the keys - tests: `tests/test_desc_keep.py`; three fixtures that compile `draw_begin_recording` get the new counter ## Measurements **Verdict:** not measured yet: the measurements are pending from the RX_VK_BENCH A/B of this PR @ 6352d91c against main @ b9e95173 (under way), which will replace this section. ### Flags and settings Switches: | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_DESC_KEEP=0` | Every descriptor resolved at every build | - | unset (kept) | Off: the old path | measurement pending | leave unset; `=0` for an A/B or if a draw shows the wrong texture | | `RX_VK_TRACE_TEX`, `RX_VK_LOG_IMG`, `RX_VK_WATCH_PROG`, `RX_VK_DRAW_STATS`, `RX_VK_PEEK_PROG`, `RX_VK_LOG_TABLE`, `RX_VK_SLOW_DESC`, `RX_VK_ASYNC_IMPORT` (existing, unchanged) | Diagnostics that log each descriptor as it is resolved, and the async import that resolves storage buffers into stream copies | as before | as before; set, the descriptors are not kept | Their logs and copies stay complete | - | unchanged | ## 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 @ 6352d91c; it stands alone on main, on no other PR). The 230 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: 221 passed, 9 failed; each failure (`tests/test_crash_dialog.py`, `tests/test_linux_driver_fault_guard.py`, `tests/test_oot_state_tap.py`, `tests/test_pipeline_tour.py`, `tests/test_readback_late.py`, `tests/test_readback_wait_bounded.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. Before main moved on, the whole suite ran on this PR rebased onto main @ 1310ecc6 (86389b35): 501 passed, 17 failed, the same 17 as main @ 1310ecc6's full run on this PC. <details><summary>The 230 checks</summary> `nx2/test_cbuf_table_extent.py` `nx2/test_dump_ssbo_read.py` `nx2/test_import_windows.py` `nx2/test_mesh_dynamic_state.py` `nx2/test_timeline_modes.py` `tests/test_amd_subgroup_profiles.py` `tests/test_antilag.py` `tests/test_audio_mono.py` `tests/test_audio_volume.py` `tests/test_auto_settings.py` `tests/test_bake_ds.py` `tests/test_bank_gen.py` `tests/test_bc_raw_targets.py` `tests/test_bind_memo.py` `tests/test_bug_report.py` `tests/test_camera_speed.py` `tests/test_capture_copy_helpers.py` `tests/test_capture_copy_nt.py` `tests/test_capture_copy_runs.py` `tests/test_capture_dimensions.py` `tests/test_capture_readback_visibility.py` `tests/test_capture_resources.py` `tests/test_capture_slot_ownership.py` `tests/test_capture_wait_log.py` `tests/test_check_pr_description.py` `tests/test_commit_room.py` `tests/test_crash_dialog.py` `tests/test_crash_record.py` `tests/test_desc_clock.py` `tests/test_desc_counts.py` `tests/test_desc_keep.py` `tests/test_dev_packs.py` `tests/test_device_lost_exit.py` `tests/test_direct_buffer_writes.py` `tests/test_dispatch_clock.py` `tests/test_dlss_checks.py` `tests/test_draw_binds.py` `tests/test_draw_side_effects.py` `tests/test_draw_stream_visibility.py` `tests/test_drawn_page_map.py` `tests/test_driver_cache_room.py` `tests/test_driver_faults.py` `tests/test_dummy_views.py` `tests/test_dump_readback.py` `tests/test_equal_depth_margin.py` `tests/test_fallback_buffer_cleanup.py` `tests/test_fallback_depth_hold.py` `tests/test_fallback_loaded.py` `tests/test_fallback_pipeline_state.py` `tests/test_fallback_size_policy.py` `tests/test_flash_sampling.py` `tests/test_flat_map.py` `tests/test_flush_nothing_held.py` `tests/test_fps_stats.py` `tests/test_frame_log_headless.py` `tests/test_frame_trace_waits.py` `tests/test_frame_wait_bounds.py` `tests/test_framegen_preset.py` `tests/test_framelog_clock.py` `tests/test_framelog_sink.py` `tests/test_fsr_image_retirement.py` `tests/test_fsr_uniform_ownership.py` `tests/test_gpl_cold_library.py` `tests/test_gpl_library_cache.py` `tests/test_gpu_hang_watch.py` `tests/test_gpu_wait_stats.py` `tests/test_handle_lookup.py` `tests/test_handle_table_arrays.py` `tests/test_hdr_encode.py` `tests/test_hold_gpu.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_input_background.py` `tests/test_intel_compiler_gate.py` `tests/test_intel_compiles_auto.py` `tests/test_intel_quick_compute.py` `tests/test_jobs_failure.py` `tests/test_jobs_wait_runs_queued.py` `tests/test_late_fix_build.py` `tests/test_late_fix_shaders.py` `tests/test_linux_driver_fault_guard.py` `tests/test_linux_pressure.py` `tests/test_load_default.py` `tests/test_log_levels.py` `tests/test_longshot_ctl.py` `tests/test_masked_clear_failure.py` `tests/test_mem_trim.py` `tests/test_menu_env_on.py` `tests/test_menu_keys.py` `tests/test_mesh_args_cleanup.py` `tests/test_mesh_fallback.py` `tests/test_mesh_fallback_failed_once.py` `tests/test_mesh_query_feature.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_module_ids.py` `tests/test_mouse_settings.py` `tests/test_oot_damage.py` `tests/test_oot_fov.py` `tests/test_oot_state_tap.py` `tests/test_opening_skip.py` `tests/test_option_ids.py` `tests/test_parallel_dry_marks.py` `tests/test_parallel_init_order.py` `tests/test_parallel_mirror_make.py` `tests/test_parallel_rb_refresh.py` `tests/test_parallel_small_cb.py` `tests/test_pass_hazard.py` `tests/test_pcache_fetch_lock.py` `tests/test_pcache_header_read.py` `tests/test_pcache_stamp.py` `tests/test_pipeline_cache_save.py` `tests/test_pipeline_notes_open.py` `tests/test_pipeline_play_cache.py` `tests/test_pipeline_share.py` `tests/test_pipeline_state_computes.py` `tests/test_pipeline_state_counts.py` `tests/test_pipeline_state_portable.py` `tests/test_pipeline_states.py` `tests/test_pipeline_tour.py` `tests/test_png_publication.py` `tests/test_pool_span.py` `tests/test_precompile_compute_guard.py` `tests/test_precompile_pipeline_cleanup.py` `tests/test_predict_resume.py` `tests/test_predict_types.py` `tests/test_predicted_check.py` `tests/test_prepare_cache_save.py` `tests/test_prepare_cpus.py` `tests/test_prepare_seed_stamp.py` `tests/test_prepare_stall.py` `tests/test_prepare_workers.py` `tests/test_prepared_stamps.py` `tests/test_present_ahead.py` `tests/test_present_aspect.py` `tests/test_present_aspect_vulkan.py` `tests/test_present_device_loss.py` `tests/test_present_geometry_trace.py` `tests/test_present_semaphores.py` `tests/test_present_source_crop.py` `tests/test_present_thread.py` `tests/test_prof_clock.py` `tests/test_prof_sampling.py` `tests/test_program_cache_format.py` `tests/test_program_cache_one_variant.py` `tests/test_program_cache_reader.py` `tests/test_psychov35_dlss.py` `tests/test_psychov35_taau.py` `tests/test_push_state.py` `tests/test_quick_pipelines.py` `tests/test_radv_mesh_optimizer.py` `tests/test_reactive_mask.py` `tests/test_readback_late.py` `tests/test_readback_part.py` `tests/test_readback_settle.py` `tests/test_readback_wait_bounded.py` `tests/test_recomplauncher_json.py` `tests/test_recorded_wait_flush.py` `tests/test_recorder_own_idle.py` `tests/test_recording_admission.py` `tests/test_registration_host_copies.py` `tests/test_replay_copies.py` `tests/test_report_words.py` `tests/test_reserved_core.py` `tests/test_retired_give_back.py` `tests/test_rumble_menu.py` `tests/test_same_bind.py` `tests/test_same_draw.py` `tests/test_same_dyn.py` `tests/test_sampler_admission.py` `tests/test_scene_format.py` `tests/test_shader_module_guard.py` `tests/test_shader_stamp_cache.py` `tests/test_shader_stamp_inputs.py` `tests/test_shadow_mask_guard.py` `tests/test_skip_title.py` `tests/test_slow_frame_limit.py` `tests/test_sparse_mirror.py` `tests/test_split_before_draw.py` `tests/test_split_ub_flush.py` `tests/test_startup_progress.py` `tests/test_state_list_plain.py` `tests/test_status_line.py` `tests/test_stick_dead_zone.py` `tests/test_submitted_page.py` `tests/test_taau_env_lookup.py` `tests/test_taau_fxaa.py` `tests/test_taau_typed_dispatch.py` `tests/test_table_track.py` `tests/test_temporal_jitter.py` `tests/test_tex_spans_once_a_present.py` `tests/test_texel_buffer_ownership.py` `tests/test_text_size.py` `tests/test_text_speed.py` `tests/test_texture_handle_banks.py` `tests/test_texture_squeeze.py` `tests/test_third_party_objects.py` `tests/test_threaded_rendering.py` `tests/test_tls_objects.py` `tests/test_tree_shadow_mask.py` `tests/test_ub_gap.py` `tests/test_ub_memo.py` `tests/test_ub_threads.py` `tests/test_ub_word_index.py` `tests/test_upscale_fallback.py` `tests/test_upscale_independent.py` `tests/test_upscaler_rows.py` `tests/test_value_rec_last_part.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_pressure.py` `tests/test_vsync_row.py` `tests/test_walk_key.py` `tests/test_warm_bounded.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` </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_DESC_KEEP=0`: Kokiri Forest, Castle Town and a dungeon a few minutes each, expecting the same picture (no wrong or flickering textures, no missing objects) and a lower replay time (`vk: a frame's replay time (ms): submit`) (Test Farm: @Beanstalk any, those scenes with and without RX_VK_DESC_KEEP=0) ## Risks **What could break:** A kept descriptor that should have changed: an object drawn with another's texture or buffer for the rest of a recording part (about 120 draws). Every input the build reads is in a key or excluded from keeping; the parallel replay's workers keep their own. **Measured regression (open):** the first in-game numbers (RTX 3080, Ryzen 9 3900X, Kokiri Forest; this PR and main @ aa95d35f on one dev_packs base; the comment of 18:33) are slower, not faster: descriptors 3.44 / 3.95 / 3.69 ms a frame against 2.78 / 2.52 / 2.89 on main in the fps A/B, and 1.79 against 1.25 ms (the whole replay 7.82 against 7.68) with RX_VK_BENCH=2600,100. Under investigation; this PR stays WIP until that is understood. **Turning it off:** `RX_VK_DESC_KEEP=0` **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 stage's descriptors are kept across draws in a recording part while what they were resolved from stands
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 4s
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
1f00672f8e
Descriptors were rebuilt whenever anything at all was bound (the global bind generation), and the game binds a uniform buffer
for nearly every draw: build_descriptors resolved every uniform buffer, storage buffer and texture of every stage again, about
1.3 ms a frame in Kokiri Forest on the serial replay thread, while bind_descriptors then found most writes unchanged.

Each program and stage now keeps, per thread and for one recording part (desc_kept_part, counted by draw_begin_recording), what
its last build resolved, and uses it while its inputs stand:
- textures, for a vertex to fragment stage whose every handle is a word of its own c2 (no handle program): the same state, the
  texture destroy, sampler registration and texture epochs, and every slot its handles named at the same version; when the
  stage's texture bindings moved, the handles are read again and the kept ones used if they are the same;
- storage buffers, for a stage whose buffers are all read only and named in c0 or c1: the stage's storage bindings and the
  pools' bind stamps, every buffer bound and below 1 TB;
- uniform buffers, each element: c1 for the part; a user buffer bound where it is, with no stream update over it, while its
  range, the updates and the bind stamp stand.
Written storage buffers, storage images, texel buffers, handles from c3+ or handle programs, and compute, task and mesh stages
are resolved at every build as before. The kept textures are put in draw_sampled and the reuse fingerprint as a build would.
RX_VK_DESC_KEEP=0 (or RX_VK_DESC_REUSE=never, or a descriptor diagnostic): every build resolves everything, as before.
RX_VK_PROF_FINE=1 counts the kept uniform buffers, storage stages and texture stages.

tests/test_desc_keep.py compiles the keys' rules and checks each invalidation, the side effects a kept stage repeats, what is
never kept, the part's count and the switch.

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.
Author
Member

First in-game numbers (RTX 3080, Ryzen 9 3900X, Windows 11; dev_packs runtimes of this PR and main @ aa95d35f on one base; Kokiri Forest from the repo save kokiri-start): this is slower, not faster, as it stands. The descriptors' time went up in every run:

  • the fps A/B with the camera turning (3 interleaved runs each, the renderer's 'a frame's replay time' line): descriptors 3.44 / 3.95 / 3.69 ms a frame here against 2.78 / 2.52 / 2.89 on main;
  • RX_VK_BENCH=2600,100 (the same frame replayed 100 times, one run each): descriptors 1.79 ms against 1.25 on main, the whole replay 7.82 against 7.68 ms.

Likely cause, not yet confirmed with RX_VK_PROF_FINE's kept counters: few kept stages are used (keys that move often: the texture destroy and epoch generations, ub_gen with every uniform update), while every build now also pays for the handles read twice, the slot versions and refilling the kept vectors. Next: the fine counters' hit rates, then either keys that hold or this closed. Stays WIP.

First in-game numbers (RTX 3080, Ryzen 9 3900X, Windows 11; dev_packs runtimes of this PR and main @ aa95d35f on one base; Kokiri Forest from the repo save kokiri-start): **this is slower, not faster, as it stands.** The descriptors' time went up in every run: - the fps A/B with the camera turning (3 interleaved runs each, the renderer's 'a frame's replay time' line): descriptors 3.44 / 3.95 / 3.69 ms a frame here against 2.78 / 2.52 / 2.89 on main; - RX_VK_BENCH=2600,100 (the same frame replayed 100 times, one run each): descriptors 1.79 ms against 1.25 on main, the whole replay 7.82 against 7.68 ms. Likely cause, not yet confirmed with RX_VK_PROF_FINE's kept counters: few kept stages are used (keys that move often: the texture destroy and epoch generations, ub_gen with every uniform update), while every build now also pays for the handles read twice, the slot versions and refilling the kept vectors. Next: the fine counters' hit rates, then either keys that hold or this closed. Stays WIP.
dyst force-pushed perf/vk-descriptor-cache from 1f00672f8e
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 4s
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 86389b3583
Some checks failed
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
CI / Python source checks (ubuntu-24.04, part 1/2) (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
PR description / Description follows the template (pull_request_target) Successful in 4s
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) Has been cancelled
2026-11-15 18:53:10 +00:00
Compare
dyst force-pushed perf/vk-descriptor-cache from 86389b3583
Some checks failed
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
CI / Python source checks (ubuntu-24.04, part 1/2) (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
PR description / Description follows the template (pull_request_target) Successful in 4s
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) Has been cancelled
to 6352d91c8c
Some checks are pending
PR description / Description follows the template (pull_request_target) Waiting to run
CI / Conventional Commit title (pull_request) Waiting to run
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
CI / Workflow lint (actionlint) (pull_request) Waiting to run
Guide screenshots / Guide images and local UI source review (pull_request) Waiting to run
Third-party parts / As their notes say (pull_request) Waiting to run
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
2026-11-15 19:27:56 +00:00
Compare
Author
Member

Rebased onto main @ b9e95173 (now 6352d91c; stands alone on main, not stacked); the 230 checks that read this diff, not the whole suite: 221 passed, 9 failed, each failure also failing on main @ b9e95173 in the same checks. Stays WIP: measurements pending from the RX_VK_BENCH A/B. Risks now name the measured descriptor regression of the 18:33 comment.

Rebased onto main @ b9e95173 (now 6352d91c; stands alone on main, not stacked); the 230 checks that read this diff, not the whole suite: 221 passed, 9 failed, each failure also failing on main @ b9e95173 in the same checks. Stays WIP: measurements pending from the RX_VK_BENCH A/B. Risks now name the measured descriptor regression of the 18:33 comment.
Some checks are pending
PR description / Description follows the template (pull_request_target) Waiting to run
CI / Conventional Commit title (pull_request) Waiting to run
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
CI / Workflow lint (actionlint) (pull_request) Waiting to run
Guide screenshots / Guide images and local UI source review (pull_request) Waiting to run
Third-party parts / As their notes say (pull_request) Waiting to run
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'perf/vk-descriptor-cache:dyst-perf/vk-descriptor-cache'
git switch 'dyst-perf/vk-descriptor-cache'
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!794
No description provided.