Repository navigation
Hot Path Vulkan Unchanged Shader Work
Lifecycle: Performance - Implemented · Type: Early-out shortcut
State: Present in currentmain.
| 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 |
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.
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.
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.
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.
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.
| 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 |
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]
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.
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]
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.
| 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.
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.
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.
| 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.
- 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.
- Pure predicate tests for every blocker and successful reuse.
- Register dirty-map and program-data invalidation tests.
- Non-register state change tests.
- Specialized/ubershader route transition and epoch tests.
- Pipeline/render-pass/blend/depth/raster dirty tests.
- Dynamic uniform, descriptor, and ubershader-control change tests while executable state is unchanged.
- Worker completion between consecutive draws.
- On/Off same-binary XISO with exact output hashes and zero new VUIDs.
- High-small-draw representative workload with route counters and parent-region CPU.
- PGR2/Morrowind p50/p95/p99/max and host CPU/GPU.
- 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.
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.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues