Repository navigation
Vulkan Performance
This page is a cost map for the Vulkan backend. It identifies where work occurs, what each measurement can prove, and which deeper pages explain the corresponding architecture.
It is intentionally not a chronological PR log.
flowchart TD
A[Guest draw / GPU method] --> B[PGRAPH state resolution]
B --> C[Surface / texture preparation]
C --> D[Shader binding]
D --> E[Descriptor / uniform preparation]
E --> F[Vertex / index synchronization]
F --> G[Graphics pipeline lookup]
G --> H[Render-pass / command recording]
H --> I[Queue submission]
I --> J[GPU execution]
J --> K[Fence / report / presentation dependency]
A slow guest interval can involve several of these regions. Optimize only after identifying which region is on the dependency path for the measured workload.
Before Vulkan-specific work begins, shared NV2A/PGRAPH code determines the effective guest state.
Questions to answer:
- Is the cost in shared guest-state decoding or Vulkan-specific translation?
- Did the effective state actually change?
- Is a dirty flag causing broad recomputation when a narrower identity would suffice?
Read: NV2A Graphics Architecture.
pgraph_vk_bind_shaders() can include much more than compilation.
Possible work includes:
- Build/compare
ShaderState. - Shader-binding cache lookup.
- Per-stage module lookup.
- GLSL generation on module miss.
- Persistent SPIR-V lookup.
- glslang compilation on artifact miss.
- Vulkan module creation.
- Reflection/layout processing.
- Uniform/descriptor implications.
Do not equate a binding miss, module miss, pipeline miss and compiler invocation.
Read: Shader Binding & Compilation.
Shared NV2A shader state is converted into generated GLSL. Generation can be cheaper than compilation but still matter when performed thousands of times or when an identity check unnecessarily regenerates source.
Measure generation separately from glslang so source-generation work is not hidden inside a broad “shader compile” bucket.
A genuinely unseen stage source can synchronously compile to SPIR-V on current main when no compatible persisted artifact exists.
Current main's persistent SPIR-V reuse from PR #70 removes glslang for qualified warm artifacts, but not for truly new source.
For unseen specialization research, see Ubershader & Hybrid Specialization.
Creating a VkShaderModule from existing SPIR-V is a distinct Vulkan object operation. It can be measured separately and should not be counted as glslang work.
xemu needs information about shader resources/uniform layout after compilation/artifact reuse. Reflection is a separate cost and ownership layer.
A persistent compiled artifact is therefore not automatically a ready complete shader binding.
A graphics pipeline combines shaders with substantial raster/depth/blend/vertex/render-pass state.
stage modules ready
↓
complete PipelineKey
↓
pipeline cache lookup
├─ hit → bind/reuse
└─ miss → vkCreateGraphicsPipelines
Pipeline creation can remain expensive even when shader compilation is completely absent.
Measure:
- Pipeline-key construction/hash.
- xemu pipeline-cache hit/miss.
-
vkCreateGraphicsPipelines()wall/CPU time. - Driver pipeline-cache behavior where available.
Do not “fix shader stutter” by only counting pipeline misses; first prove which cache layer missed.
Descriptors connect shader resources to textures/buffers. Uniform staging carries runtime values that should not always require new shader or pipeline objects.
Potential costs:
- Descriptor-set allocation/rotation.
- Descriptor writes.
- Uniform staging copies.
- Capacity-triggered submission/finish.
- Rebinding unchanged state.
A capacity increase is only a real performance improvement if total completion/wait work decreases. PR #83 is an example where removing one finish reason did not establish a net throughput win.
Read: Vulkan Renderer and Performance Methodology.
Texture work can include:
- Guest DMA/source identity.
- Dirty/freshness checks.
- Surface-overlap resolution.
- Host texture creation/update.
- Format conversion.
- Sampler/binding state.
Merged PR #80 provides guarded warm texture-binding reuse on current main. The guard matters: a cache hit is valid only if the effective guest source and freshness still match the cached host object.
Measure cache hit rate and bytes/conversions avoided, but also verify output and DMA-remap cases.
A large synchronization cost can originate from deciding that GPU-authored surface data must be downloaded before a CPU/vertex/texture consumer uses guest memory.
overlap detected
↓
is GPU surface newer?
├─ no → no readback required
└─ yes → synchronize/download before guest-memory consumer
Two opposite failure modes exist:
- False positive overlap/freshness → unnecessary expensive readback.
- False negative overlap/freshness → stale bytes / incorrect rendering.
Performance work here must carry a correctness oracle. PR #85 is active work around this boundary.
The Vulkan backend mirrors guest vertex RAM. Updating that mirror is cheap only when command-buffer ordering remains correct.
Potential paths:
- Direct mapped host write before an active command buffer depends on the bytes.
- Direct mapped write proven fresh relative to all earlier recorded draws.
- Ordered staging copy with barriers when earlier recorded reads exist.
- Submission/finish when staging capacity or another lifetime boundary requires it.
Read: Vertex Uploads & Render-Pass Lifetime.
Render passes group compatible draws. Copies or other non-draw commands can require the current pass to end.
Investigate:
- Number of passes.
- Why each pass ended.
- Amount of useful draw work per pass.
- State replay after pass restart.
- Copies/transitions adjacent to pass boundaries.
- Whether a versioning/reordering design can legally keep more work inside the pass.
Do not interpret “73.84% of GPU time inside render passes” as “73.84% removable overhead.” That span primarily contains useful rendering.
PR #87 measured one Morrowind trace where render-pass spans were the majority of main-GPU time while ordered staged copies were another substantial region. The useful conclusion was to inspect pass/copy interaction, not to subtract all pass time.
After a render-pass restart, Vulkan state may need to be re-established before subsequent draws.
Potential costs include repeated:
- Pipeline binds.
- Descriptor binds.
- Viewport/scissor/dynamic state.
- Vertex/index buffer binds.
PR #88 investigates reuse across restarts. Its current mixed/adverse results are important: reducing command replay locally does not guarantee improved guest progress if the changed lifetime/synchronization pattern costs more elsewhere.
A submission can be triggered by many reasons:
- Explicit guest synchronization.
- Resource capacity.
- Surface/report dependency.
- Buffer growth/reuse.
- Flush/sync request.
- Frame/presentation boundary.
Always record the finish/submission reason. Counting submissions without reason attribution makes it difficult to tell whether a patch removed a dependency or merely shifted it to another category.
Fence waits are often where accumulated GPU work becomes visible as a CPU stall.
A wait duration answers:
How much completion time remained when the CPU reached this dependency?
It does not by itself identify which preceding draw/copy caused that GPU time.
Combine:
- CPU wait timing.
- Submission identity/reason.
- GPU timestamp spans.
- Guest progress marker.
This prevents optimizing the waiter while leaving the producer cost untouched.
NV2A report/query operations can require GPU completion before a guest-visible result may be written.
A report-heavy title can therefore turn earlier command-buffer work into a later CPU-visible fence wait.
When a change makes a draw function look faster, verify report/query retirement did not simply absorb the same delay.
GPU time should be divided into meaningful spans rather than one frame total when diagnosing a backend path.
Useful regions can include:
- Render-pass/draw spans.
- Ordered copies/transfers.
- Clears.
- Readbacks.
- Presentation/copy work.
- Work outside classified regions.
Timestamp instrumentation is itself a diagnostic tool. Keep the diagnostic build separate from final product A/B acceptance where possible.
Current main can present Vulkan output through either:
- Shared external memory to the OpenGL presentation context.
- Host-copy fallback for incompatible/cross-adapter combinations.
A Vulkan performance result should record:
- Actual rendering GPU.
- Presentation transport.
- Resolution scale.
- VSync/present policy.
Merged PRs #75 and #76 make these identities explicit enough to diagnose mixed-GPU setups.
Vulkan makes host-side implementation costs unusually visible. Pipeline creation, descriptor rotation, render-pass boundaries, command-buffer submission and fence waits are not native NV2A objects.
flowchart TD
A[Guest NV2A work] --> B[Required semantic work]
B --> C[Host translation/bookkeeping]
C --> D[Vulkan driver/API work]
D --> E[Host GPU execution]
E --> F[Guest-visible completion]
A valid optimization can remove or reorganize the middle layers even though no corresponding Xbox-hardware operation is removed. The invariant is the same guest-visible state, ordering and result.
This also means GPU timestamp regions are host-GPU regions. They are excellent for finding Vulkan inefficiency but should not be described as literal NV2A pipeline utilization.
| Change | Mainline role |
|---|---|
| PR #70 | Persistent SPIR-V reuse; removes glslang from qualified warm stage-module misses |
| PR #75 | Exact Vulkan physical-device selection/reporting |
| PR #76 | Host-copy presentation fallback when direct external-memory sharing is incompatible |
| PR #80 | Guarded reuse of unchanged warm texture bindings |
These are current architecture, not active experiments.
| Investigation | Main question | Status |
|---|---|---|
| PR #71 | Can admitted unseen fragment state draw through a runtime fallback while specialization happens off the draw dependency? | Draft / HOLD |
| PR #83 | Can descriptor-capacity pressure be reduced without merely moving the submission/wait boundary? | Draft / HOLD |
| PR #85 | Is guest vertex memory fresh relative to GPU-authored overlapping surfaces? | Draft / HOLD |
| PR #87 | Can fresh vertex pages be written directly without violating earlier recorded draws? | Draft / HOLD |
| PR #88 | Can command state survive render-pass restarts with less CPU replay and no adverse side effects? | Draft / HOLD |
| PR #89 | Can bounded immutable vertex versions reduce ordered-copy pressure while preserving draw-time bytes? | Draft |
See Development for details and current PR links.
| Hot region | First measurement | Causal evidence | Acceptance metric |
|---|---|---|---|
| Shader binding | CPU samples + per-substage timers | exact binding/module/source/cache identities | guest interval tails + total binding CPU |
| Pipeline creation | create counts/time | exact PipelineKey miss identity |
guest tails + pipeline preparation time |
| Descriptors | write/rotation/finish counts | finish reason + capacity state | total guest progress + waits |
| Textures | bind/cache/update counts | DMA/source/freshness identity | output correctness + CPU/GPU cost |
| Surface readback | bytes/readbacks/waits | exact overlap/freshness reason | output correctness + total stalls |
| Vertex upload | direct/staged pages/bytes | read-page/version identity | guest progress + copy/pass cost |
| Render passes | count + GPU spans | pass-end reason | total GPU/guest progress, not pass count alone |
| Fence waits | CPU wait durations | submission and preceding GPU work | end-to-end progress |
| Presentation | transport/device identity | shared-memory versus copy path | valid presented-FPS/latency capture |
A profiler region often contains useful work. Render-pass GPU time and generated guest CPU code are obvious examples.
Only if the missed layer actually requires compilation. Shader binding, stage module, persisted SPIR-V and graphics pipeline are separate layers.
Not if a later submission becomes larger and the same fence dependency still blocks guest progress.
Not until the progress marker is proven equivalent relative to host GPU completion and presentation.
Instrumentation can change scheduling and timing. Reproduce the change in a normal product build.
Profile broad CPU/GPU region
↓
classify exact cache/sync/lifetime decision
↓
add focused correctness oracle
↓
make one minimal behavioral change
↓
run exact normal A/B pair
↓
check p95/p99/max/stalls
↓
check total CPU + GPU + wait work
↓
run broad XISO / retail / lifecycle gates
For the full measurement rules, see Performance Methodology.
- Vulkan Renderer — base backend architecture.
- Shader Binding & Compilation — shader/module/pipeline cache layers and PR #68 evidence.
- Ubershader & Hybrid Specialization — PR #71 fallback/specialization research.
- Vertex Uploads & Render-Pass Lifetime — PR #87 synchronization/lifetime research.
- OpenGL vs Vulkan — backend comparison and measurement caveats.
Debugging Lag & Hot Paths contains the detailed Release profiling workflow and worked examples for shader compilation/pipeline misses, render-pass versus ordered-copy attribution, and cold-tail investigation.
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