Skip to content

Hot Path Vulkan Unchanged Shader Work

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

Vulkan Skip Unchanged Shader Work

Lifecycle: Performance - Implemented · Type: Early-out shortcut
State: Present in current main.

Status

Field State at reviewed main
Underlying optimization Implemented, merged with PR #71
Advanced control Implemented, Skip unchanged shader work
Default Off
Apply boundary Live; next draw preparation
Reference route Full shader-state/key/route/pipeline preparation
Evidence counters Hybrid trace/diagnostics exist; shared #94 summary absent
Qualification Focused correctness present; local improvement observed, whole-workload benefit not established
Reviewed source 322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14
Primary history PR #71; PR #68 supplies parent bottleneck context

Summary

A draw can enter Vulkan shader preparation even when no shader-affecting state changed since the previous draw and the complete currently bound shader/pipeline remains usable. The shortcut recognizes that conservative case and keeps the current executable instead of rebuilding requested shader state, probing route/module caches, and reselecting a pipeline.

It does not freeze dynamic data. Uniform stages, ubershader control packets, descriptors, and other genuinely dynamic inputs still update. It also does not skip when a worker completed a new specialized result that changes route selection.

Why It May Help

The saving scales with:

consecutive draws with unchanged effective shader state
× repeated state/key/route/cache work avoided

It targets CPU overhead on the common steady-state draw path, not first-seen compilation. It is most useful when titles issue many small draws with the same executable shader state but changing dynamic values, vertices, textures, or surfaces.

It will not materially help when every draw changes shader registers/program data, pipeline state is dirty, hybrid completions alter the selected route, or another stage dominates draw preparation.

Observed in xemu

PR #71 diagnostics reported a large reduction in route probes and route-selection CPU when this control was enabled. However, total Hybrid-On shader-binding time remained around the same order because other preparation, uniform, descriptor, pipeline, or renderer work dominated. One warm XISO comparison moved favorably relative to Hybrid-On shortcut Off but did not establish a broad current-main speedup.

This is the central lesson: a heavily reduced local subpath can be real while the parent bottleneck remains effectively unchanged.

External Precedent

Related emulator pattern: Dolphin retains shader and pipeline objects keyed by guest GPU state so unchanged states can reuse existing host objects: Dolphin ShaderCache.cpp.

General graphics pattern: Khronos notes that state is not automatically reused across command buffers and that excessive small command/state work can add CPU overhead: Command buffer usage.

These are related patterns. xemu's shortcut is narrower: it retains the exact currently active renderer objects only after proving the complete effective NV2A shader state and route remain valid.

Emulation Boundary

Guest draw and NV2A state
    ↓
xemu determines effective shader semantics
    ↓
current host executable still represents those semantics?
    ├─ yes: reuse executable, update dynamic inputs
    └─ no: perform full shader/pipeline selection

The Xbox does not have xemu's GLSL keys, SPIR-V modules, Vulkan pipelines, hybrid epochs, or descriptor state. Skipping host recomputation is valid only while the guest-visible shader result remains unchanged.

Code and Ownership Map

Location Role Owner
hw/xbox/nv2a/pgraph/vk/draw.c Conservative fast-path predicate and draw preparation Renderer/PFIFO thread
vk/shaders.c Shader binding state, route controls, dynamic uniforms Renderer + compiler workers
vk/hybrid-ready.h / policy helpers Detect completed/changed hybrid selection Renderer
vk/renderer.h Current shader/pipeline binding, epochs, dirty state Renderer
pgraph dirty maps/program flags Guest register/program change evidence PGRAPH state owner
ui/xemu-tweaks.[ch] Live effective bit UI publishes; renderer reads

Actual Current Flow

flowchart TD
    A[Draw preparation] --> B[Process any completed hybrid work]
    B --> C{Shortcut enabled?}
    C -->|No| R[Full shader preparation]
    C -->|Yes| D{No registers written and no program data dirty?}
    D -->|No| R
    D -->|Yes| E{Current shader binding and complete pipeline exist?}
    E -->|No| R
    E -->|Yes| F{Pipeline is not clear and route still matches?}
    F -->|No| R
    F -->|Yes| G{Hybrid selection epoch unchanged?}
    G -->|No| R
    G -->|Yes| H{Non-register shader state unchanged?}
    H -->|No| R
    H -->|Yes| I{Pipeline state remains clean?}
    I -->|No| R
    I -->|Yes| J{Ubershader controls refresh if required?}
    J -->|No| R
    J -->|Yes| K[Reactivate current shader/pipeline]
    K --> L[Update dynamic uniforms, controls and descriptors]
    R --> M[Build requested state, choose route, lookup/create modules and pipeline]
    M --> L
    L --> N[Draw]
Loading

The shortcut predicate intentionally contains multiple proofs. “No registers written” alone is insufficient because non-register state and asynchronous hybrid completion can alter the effective executable.

Maintained Reference Flow

flowchart TD
    A[Every draw] --> B[Build requested shader state]
    B --> C[Resolve specialized or ubershader route]
    C --> D[Lookup/create stage modules]
    D --> E[Build/check pipeline key]
    E --> F[Lookup/create complete pipeline]
    F --> G[Activate binding]
    G --> H[Update dynamic inputs]
    H --> I[Draw]
Loading

Shortcut Off runs this route even when it resolves back to the same binding. That makes it a useful same-binary reference for state-reuse bugs.

Eligibility and Protected Invariants

Invariant Why required Failure if removed
No shader-affecting register writes Register state participates in generated semantics Reuse wrong shader
Program data clean Vertex/fragment program changed Stale executable
Shader binding exists Nothing complete to reuse Null/partial state
Pipeline exists and is non-clear Draw requires a complete compatible pipeline Invalid bind
Bound route matches shader route Specialized and ubershader layouts differ Wrong pipeline/layout
Hybrid selection epoch unchanged Worker completion may make exact route ready Ignore intended promotion
Non-register shader state unchanged Surface/primitive/other inputs affect generation Semantically stale shader
Pipeline key remains clean Blend/depth/raster/render-pass state changed Incompatible pipeline
Ubershader controls refresh successfully Dynamic interpreter state must match draw Wrong fragment output
Dynamic uniforms/descriptors still update Not every input is part of executable identity Frozen current values

The control must remain after all these proofs, not replace them.

State, Lifetime, Invalidation, and Fallback

The shortcut reuses renderer-owned shader_binding and pipeline_binding. Their lifetime is constrained by LRU/cache ownership and active command-buffer use. An active pipeline cannot be evicted while recorded draws still reference it.

Invalidators include:

  • guest shader/program register writes;
  • program-data changes;
  • non-register state changes;
  • dirty pipeline state;
  • binding/pipeline destruction or cache replacement;
  • hybrid selection epoch changes;
  • route change between specialized and ubershader;
  • failed control refresh.

Any invalidator falls through to full preparation. The setting is live because the next draw reads the active bit.

Expected Profile Signature

A suitable workload should show:

same draw count and output
    + high unchanged-state eligibility
    + fewer route/key/module/pipeline probes
    + lower shader-preparation CPU
    + unchanged dynamic uniform/control updates

Disproof or limitation:

  • eligible draws are rare;
  • route probes fall but total bind/draw/frame time does not;
  • uniform/descriptor/pipeline work becomes dominant;
  • worker completions frequently invalidate reuse;
  • p99 worsens despite lower mean local CPU.

Measure the parent draw-preparation region, not only the predicate/helper.

Counters and Evidence

Counter Unit Meaning
vk.shader_fastpath.attempts draws Draws entering the predicate
vk.shader_fastpath.hits draws Current executable reused
vk.shader_fastpath.reference draws Control disabled on otherwise eligible state
vk.shader_fastpath.blocked_registers draws Shader-affecting registers written
blocked_program/nonregister/pipeline/epoch draws Specific invalidators
vk.shader_fastpath.control_refresh_failures draws Ubershader controls required full route
route/module/pipeline probes operations Local work avoided
shader-preparation CPU ns/us Parent-region impact

Counters should be renderer-owned and reset per evidence session. Do not turn the eligibility expression into a materially slower diagnostic branch ladder in production.

Risks and Known Bugs

  • Dirty summaries can miss a source if a new shader-affecting input is added without invalidation wiring.
  • Route equality is not enough when pipeline or layout state changed.
  • Worker completion can make a specialized pipeline ready without a guest register write.
  • Reusing an ubershader requires refreshed dynamic controls.
  • A local route-probe win can have no end-to-end effect; PR #71 demonstrated this risk.
  • Logging/timing every draw can erase the shortcut's cost or alter race timing.
  • Enabling this together with Hybrid Ubershader changes two variables unless the experiment explicitly isolates both.

See PR #71 and Pull Request Lessons.

Qualification

  1. Pure predicate tests for every blocker and successful reuse.
  2. Register dirty-map and program-data invalidation tests.
  3. Non-register state change tests.
  4. Specialized/ubershader route transition and epoch tests.
  5. Pipeline/render-pass/blend/depth/raster dirty tests.
  6. Dynamic uniform, descriptor, and ubershader-control change tests while executable state is unchanged.
  7. Worker completion between consecutive draws.
  8. On/Off same-binary XISO with exact output hashes and zero new VUIDs.
  9. High-small-draw representative workload with route counters and parent-region CPU.
  10. PGR2/Morrowind p50/p95/p99/max and host CPU/GPU.
  11. Cache pressure, command-buffer rollover, teardown, and renderer restart.

Default-on promotion requires repeatable parent-path and end-to-end benefit, not merely fewer route probes.

More aggressive generation-trust research

Shader Selection Epoch Trust documents a stronger proposed variant that would replace several current-state guards with one complete dependency generation. It is not implemented and must not be confused with the current conservative shortcut.

Clone this wiki locally