Skip to content

Hot Path Vulkan Uniform Update Plans

github-actions[bot] edited this page Sep 14, 2026 · 4 revisions

Vulkan Uniform Dirty Summaries and Prepared Copy Plans

Lifecycle: Performance - Unproven · Type: Prepared-state optimization
State: Source-backed; runtime value is not established.

Status: Source-backed narrow follow-up candidate; not implemented as described and not benchmarked.

Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579

Scope: Uniform dirty-row discovery and repeated interpretation of fixed shader-layout metadata.

Current main already contains substantial shader/uniform avoidance: stage-specific source epochs, dirty rows, layout-change tracking, complete-binding reuse and the optional unchanged-shader fast path. This candidate does not claim xemu rebuilds all uniforms or shaders every draw.

The remaining opportunity is narrower: avoid scanning clean dirty-flag arrays and prepare a compact copy plan when a shader layout becomes known.

Why this is applicable

Current Vulkan shader code includes:

  • a helper that scans dirty-flag arrays to determine whether a stage needs work;
  • row-update logic that scans rows again during a partial update;
  • generic uniform metadata traversal/copy dispatch;
  • stage/layout state that often remains stable across many draws.

The existing Skip Unchanged Shader Work can bypass broader shader/pipeline preparation only when its complete guard set passes. Uniform updates still need a precise and efficient route when dynamic rows actually change or when the broad shortcut is ineligible.

Why it may help

Potentially avoided work includes:

  • scanning all vertex/light/context dirty booleans to discover that none are set;
  • rescanning every row when only a few changed;
  • revisiting inactive uniform metadata;
  • recomputing source/destination offsets for a stable layout;
  • element-by-element compare/copy where one contiguous compatible region is valid.

The candidate targets CPU preparation and staging writes, not shader compilation or graphics-pipeline creation.

Current conceptual flow

flowchart TD
    A[Prepare draw uniforms] --> B[Scan dirty arrays for any changed row]
    B --> C{Stage update required?}
    C -->|No| D[Reuse current stage data]
    C -->|Yes| E[Walk rows and dirty flags again]
    E --> F[Interpret active uniform metadata]
    F --> G[Generic compare/copy into staging allocation]
    G --> H[Clear dirty state and publish offsets]
Loading

Candidate flow

flowchart TD
    A[Uniform writer marks row] --> B[Set row bit and stage summary bit]
    C[Prepare draw uniforms] --> D{Stage summary empty?}
    D -->|Yes| E[Skip row scan]
    D -->|No| F[Iterate only set bits]
    F --> G[Execute PreparedUniformCopyPlan entries]
    G --> H[Use specialized contiguous or strided copy operation]
    H --> I[Clear processed bits and summary exactly]
Loading

Dirty bitmap and summary

A safe design keeps:

  • a bitmap for each row family/stage;
  • a cheap any_dirty summary derived and maintained by writers/clearers;
  • explicit full-update state for layout/source changes;
  • existing source epochs and layout-change flags.

The summary is not authoritative on its own. It accelerates the empty case; the bitmap identifies exact rows.

All writer and clearing paths must update both consistently, including:

  • guest register/control writes;
  • full uniform rebuild;
  • rows with no active destination;
  • shader/layout change;
  • reset and snapshot restore;
  • error/retry paths.

Prepared copy plan

When ShaderUniformLayout becomes stable, prepare entries containing only active destinations:

source family / base
source row or field index
source stride
staging destination offset
copy size / element count
destination padding/stride rule
specialized copy kind

The plan must be invalidated when layout, source representation, push-constant/UBO policy or any offset/stride contract changes.

A copy plan does not make source values immutable. It prepares where and how to copy live values.

Contiguous-copy specialization

Some active locations may form contiguous compatible regions. Those can use one compare/copy operation. Padded arrays, transposed data, component conversion or strided layouts retain dedicated routes.

Do not combine this work with claims about uniform_index() string lookup unless profiling proves that lookup occurs in the hot path. The source audit supports dirty scans and layout interpretation; it does not establish a per-draw string-search bottleneck.

Seen in the wild

General renderer precedent: engines track constant/uniform updates with dirty bitsets and precomputed binding/copy layouts, visiting only changed ranges while retaining a full rebuild path after layout changes.

Internal xemu precedent: current stage dirty flags, source epochs and unchanged-binding shortcuts already use dependency summaries. This candidate extends that model to row discovery and copy execution rather than inventing a new global cache.

Code and ownership map

Area Role
vk/shaders.c::get_uniform_stage_update_needs() Determines stage update requirements
Row update helpers Current per-row dirty processing
Uniform metadata/copy helpers Interpret layout and move values
ShaderUniformLayout Stable layout information suitable for prepared plan
PGRAPH/Vulkan render thread Owns dirty consumption and staging publication
Guest state writers Must maintain dirty bitmap/summary contract

Evidence and counters

uniform_prepare_calls
uniform_stage_summary_empty_hits
uniform_dirty_rows_marked
uniform_dirty_rows_visited
uniform_rows_scanned
uniform_copy_plan_builds
uniform_copy_plan_executes
uniform_copy_entries_executed
uniform_bytes_compared
uniform_bytes_copied
full_uniform_updates
partial_uniform_updates
uniform_prepare_cpu_ns

Expected useful signature:

stable shader/layout workload
    -> plan builds remain low
    -> many prepare calls find empty summary
    -> partial updates visit far fewer rows than total rows
    -> staging bytes and output remain equivalent

Risks

  • A writer changes a row without setting the bitmap/summary.
  • Clearing one row incorrectly clears the stage summary while other bits remain.
  • Full-update/layout-change path leaves stale partial bits.
  • Snapshot restore reconstructs values but not matching dirty/plan state.
  • Prepared offsets outlive a layout or staging-policy change.
  • Contiguous specialization crosses padding/stride boundaries.
  • Added summary/bitmap writes cost more than scans in highly dynamic workloads.
  • Broader unchanged-shader fast path and this plan disagree about what remains dynamic.

Qualification

  1. Add pure bitmap/summary state-machine tests.
  2. Instrument current row scans before changing representation.
  3. Split plan construction from plan execution with reference output comparison.
  4. Exercise no-change, one-row, sparse-row, all-row and full-layout-change cases.
  5. Exercise absent locations, push-constant/UBO policy changes and ubershader controls.
  6. Exercise reset, shader switch, command-buffer rollover, snapshot save/restore and teardown.
  7. Compare staging bytes, descriptor state and framebuffer hashes.
  8. Profile Release builds with counters disabled for final timing.
  9. Keep dirty-summary and copy-plan changes independently bisectable.

Disposition

Applicable as a focused refinement, not a replacement for existing shader fast paths. Add counters first because current main is already partially optimized and the remaining scan cost may be small.

Related pages

Full technical reference

Primary reference: This subject page is the design and research reference for Uniform Update Plans. Pull requests are implementation/test history only.
Lifecycle: Performance - Unproven · Mechanism: Dirty-summary and prepared-copy optimization
Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579 · Acceptance class: intended exact or behavior-preserving for its admitted scope

1. Scope

Maintain compact dirty summaries and a shader-layout-specific copy plan so clean arrays are not rescanned and inactive uniform metadata is not reinterpreted on every update.

This entry remains Unproven: the source path and opportunity are concrete, but no accepted xemu runtime result has established that the candidate improves representative workloads. The page defines what must be bypassed, what remains mandatory, and how the hypothesis can be rejected.

2. Source seams

File / function Role
hw/xbox/nv2a/pgraph/vk/shaders.c get_uniform_stage_update_needs(), update_uniform_rows(), apply_uniform_updates()
hw/xbox/nv2a/pgraph/vk/glsl.h uniform layout metadata

3. Current execution path

flowchart TD
    N0["Check several dirty bool arrays"]
    N1["Decide stage update"]
    N0 --> N1
    N2["Scan rows again"]
    N1 --> N2
    N3["Walk generic uniform metadata"]
    N2 --> N3
    N4["Compare/copy active values"]
    N3 --> N4
    N5["Clear dirty state"]
    N4 --> N5
Loading

4. Candidate execution path

flowchart TD
    N0["Writers maintain dirty bitmap and nonzero summary"]
    N1["No dirty words: skip row scan"]
    N0 --> N1
    N2["Visit only set bits"]
    N1 --> N2
    N3["Execute prepared active-copy plan"]
    N2 --> N3
    N4["Clear bits and summary exactly"]
    N3 --> N4
Loading

5. Why it may help

Current main already has stage epochs and partial dirty tracking; the remaining target is narrower. The candidate should not claim that all uniforms are rebuilt every draw. It removes repeated scans and layout interpretation when a known layout receives sparse updates.

6. Work removed

  • Scanning clean dirty arrays
  • Scanning clean rows during partial update
  • Repeated traversal of inactive uniform entries
  • Repeated offset/stride interpretation

7. Work that remains mandatory

  • Authoritative dirty marking
  • Value comparisons/copies
  • Full-update route
  • Texture/polygon/inline inputs
  • Descriptor/upload work after data preparation

8. Implementation sketch

if (dirty_summary == 0 && !layout_changed && !source_changed) {
    stats.uniform_stage_skips++;
    return;
}

for_each_set_bit(word, dirty_bitmap) {
    copy_uniform_row(plan->row[word]);
    clear_bit(word, dirty_bitmap);
}
dirty_summary = recompute_or_clear_summary(dirty_bitmap);

The sketch defines the intended boundary; it is not a complete patch and does not replace source review, ownership design, or focused tests.

9. Correctness contract

  • Every writer and full-clear/full-update path maintains the bitmap/summary consistently.
  • The prepared plan is keyed by the exact active shader uniform layout.
  • Padding/strided arrays use correct copy operations; contiguous memcpy is only admitted where layout proves it.
  • Absent locations and reset/restore state are handled.

10. State, ownership, invalidation, and fallback

Dirty metadata is renderer/state-owned. Copy plans belong to shader module/layout lifetime and are invalidated with module/layout replacement. Snapshot/reset can rebuild summaries from canonical dirty arrays or force a full update.

The maintained reference path remains the fallback whenever admission, capability, lifetime, or validation is uncertain. A fast result with an unproven dependency is not acceptable as an exact optimization.

11. Advantages

  • Reduces common clean/sparse-update CPU work
  • Builds on existing layout objects
  • Does not change shader semantics
  • Counters can attribute scans and copies

12. Costs and failure modes

  • Missed dirty writer creates stale uniforms
  • Bitmap maintenance can add writer cost
  • Copy-plan complexity for padding/strides
  • Benefit may be small if full updates dominate

13. Evidence counters

Counters must be cheap, subsystem-owned, and snapshotted outside the hot loop. Record at least:

  • stage_update_checks
  • dirty_array_elements_scanned
  • dirty_words_nonzero
  • rows_copied
  • full_updates
  • plan_entries_executed
  • uniform_prepare_cpu_us
  • descriptor updates

A local reduction is not enough. Evidence must connect route reachability and removed work to fixed guest progress, CPU/GPU time where applicable, and p95/p99 behavior.

14. Qualification matrix

  • Mutate every uniform source independently
  • Sparse and dense dirty patterns
  • Full update and absent locations
  • Layout/module switch
  • Padded/strided arrays
  • Reset/snapshot
  • Compare allocation bytes and output
  • Release A/B on constant-heavy scenes

Correctness runs may use validation layers, detailed comparisons, and fault injection. Performance acceptance must use optimized Release builds with matching symbols and minimal instrumentation.

15. Acceptance rule

Promote when mutation coverage proves summary correctness and sparse/clean workloads show meaningful preparation savings.

16. Rejection rule

Reject or simplify if writers are too numerous to maintain safely, full updates dominate, or plan execution does not beat current scans.

17. Related architecture and history

Vulkan-Shader-Binding-and-Compilation · Performance-Unproven · Vulkan-Dynamic-Uniform-Descriptors · Hot-Path-Vulkan-Unchanged-Shader-Work

PR pages may be linked as supporting evidence, but they are not substitutes for this reference.

Clone this wiki locally