Repository navigation
Graphics Backend Comparison
OpenGL and Vulkan are two host implementations of the same guest-facing NV2A/PGRAPH model. They should agree on what the Xbox observes, but they reach that result through very different host APIs.
This page is an architectural comparison, not a claim that one backend is universally faster.
flowchart TD
A[Guest NV2A methods] --> B[PFIFO]
B --> C[PGRAPHState]
C --> D[Shared shader / texture / surface semantics]
D --> E{Renderer}
E --> F[OpenGL]
E --> G[Vulkan]
Everything above the renderer boundary should represent the same guest hardware semantics.
| Area | OpenGL | Vulkan |
|---|---|---|
| Host API model | Stateful context | Explicit objects and command recording |
| Device selection | Largely context/OS/driver controlled | Explicit physical-device selection and capability query |
| Shader executable | GL shader/program objects | SPIR-V → VkShaderModule + pipeline stages |
| Complete pipeline | Mostly implicit driver validation around current GL state | Explicit VkPipeline object |
| Resource binding | GL bindings/state | Descriptor sets, bindings, layouts and offsets |
| Draw submission | Immediate-looking GL calls queued by driver | Commands explicitly recorded into command buffers then submitted |
| Memory ordering | Many dependencies implicit in API/driver | Explicit barriers, access masks, stages and submission order |
| Completion |
glFinish, queries, driver fences/implicit rules |
Fences, command-buffer completion, explicit queue submissions |
| Render-target lifetime | Framebuffer/context state | Images/views + explicit render-pass/attachment compatibility |
| Pipeline compilation visibility | Often inside GL driver/program/state transitions |
vkCreateGraphicsPipelines() visible to xemu |
| Validation/debugging | GL errors/debug output depending context | Vulkan validation layers/VUIDs plus xemu telemetry |
| Cross-GPU presentation | GL context owns presentation side | Vulkan render device may share with or copy to GL presenter |
Both backends begin from shared guest shader state but diverge after source generation.
NV2A state
↓
shared GLSL generation
↓
GL shader compile / program link or cache hit
↓
glUseProgram + uniforms/state
↓
draw
NV2A state
↓
shared GLSL generation
↓
glslang → SPIR-V or persisted SPIR-V hit
↓
VkShaderModule + reflection
↓
complete graphics pipeline lookup/create
↓
record draw
A first-seen Vulkan pipeline is therefore a different cache layer from a first-seen Vulkan stage module. OpenGL drivers may perform analogous internal specialization, but much of that work is hidden behind GL APIs.
OpenGL naturally maps many guest state changes to stateful API calls:
NV2A blend/depth/raster state
↓
GL state calls
↓
current GL context state
Vulkan generally needs those same effective choices captured in pipeline state, dynamic state, descriptors, or command-buffer commands:
NV2A blend/depth/raster state
↓
PipelineKey + dynamic state
↓
VkPipeline / vkCmdSet* state
This creates different optimization opportunities. Removing a redundant GL state call may be cheap and local; removing a Vulkan pipeline-key field can change pipeline identity and must be proven against pipeline creation/output semantics.
The driver tracks many hazards internally. xemu still issues explicit strong synchronization such as glFinish() when guest semantics require completed work.
Advantages:
- Less synchronization bookkeeping in xemu for ordinary API use.
- Simpler call sequence for many operations.
Tradeoffs:
- Driver may synchronize or compile at points that are difficult to predict from xemu code.
- A CPU profile can move into driver DLL/shared-library code without identifying the exact guest dependency.
xemu explicitly tracks hazards with barriers, command buffers, queue submissions, and fences.
Advantages:
- Ordering and object lifetimes are visible and can be measured precisely.
- Resource transitions can potentially be narrowed instead of relying on a global finish.
Tradeoffs:
- Incorrect lifetime/barrier logic becomes an emulator correctness bug.
- More CPU-side bookkeeping is required.
- A small guest update can break a render pass or force submission if the resource dependency is not structured carefully.
The phrase “draw call” means something different on the two backends.
| Phase | OpenGL | Vulkan |
|---|---|---|
| CPU issue | GL command enters driver | Command is encoded into xemu-owned command buffer |
| GPU execution | Driver schedules/executes later | Occurs after explicit queue submission |
| Completion point | Driver/API sync mechanism | Fence/query/report completion tied to submitted command buffer |
Therefore a guest progress marker can occur at a different distance from actual host GPU completion in each backend. This is especially important when comparing report/query or display-write cadence.
Both backends must answer the same guest-level question:
Is guest memory or the GPU-rendered surface the newest copy of this range?
The host mechanism differs:
- OpenGL uses GL framebuffer/texture/readback synchronization.
- Vulkan uses explicit image/buffer transfers, layouts, barriers, submissions, and fences.
A correctness test can fail on one backend and pass on the other because the backend coherency implementation differs even though the required guest result is identical.
OpenGL driver ordering can make some update patterns simpler to express, but the backend still must not consume stale surface/VRAM data.
Vulkan exposes an additional command-recording lifetime problem: a draw can already be recorded while the host buffer it references remains writable. Directly updating that buffer must therefore prove the earlier recorded draw cannot observe the new bytes incorrectly.
See Vertex Uploads & Render-Pass Lifetime.
Both backends eventually serialize NV2A report/query results into guest memory, but host completion behavior differs.
A benchmark using guest display-write or report cadence must document whether that event means:
- Guest submitted work.
- Host API command was issued.
- Host GPU completed a batch.
- Report/query result became visible.
- Frame reached presentation.
These are not interchangeable milestones.
| Cache concept | OpenGL | Vulkan |
|---|---|---|
| Guest shader-state reuse | Yes | Yes |
| Host shader executable reuse | GL program/shader cache | Stage-module/SPIR-V cache |
| Persistent compiled shader artifact | Driver-dependent; xemu architecture differs | xemu persistent SPIR-V cache in current main |
| Complete graphics pipeline cache | Mostly driver/API internal state model | Explicit xemu pipeline LRU + Vulkan driver cache object |
| Texture reuse | Backend texture cache | Backend texture cache |
| Surface reuse | Backend surface cache/state | Backend surface/image cache/state |
Do not compare cache hit counts between backends as if they refer to the same object type.
OpenGL is the natural presentation context in xemu's current graphics design. Vulkan can render on a selected physical device and present through:
- Compatible shared external memory into the OpenGL presentation context.
- A host-copy path when direct sharing is not compatible.
This can produce mixed-adapter configurations. A Vulkan benchmark should record both render-device identity and presentation transport.
When a bug occurs in both backends, start in shared PGRAPH/device logic.
When only one backend fails, compare at the renderer boundary:
same guest input/state?
↓
yes
↓
same shared decoded PGRAPH state?
↓
yes
↓
backend-specific translation/resource/sync difference
Useful differential checks include:
- Same XISO guest outcome.
- Same framebuffer/hash where cross-driver determinism is valid.
- Same guest memory/report values.
- Same state transitions before renderer dispatch.
- Backend-specific validation/debug output.
A fair OpenGL/Vulkan comparison should control:
- Exact executable/source.
- Game/snapshot seed.
- Renderer settings.
- Resolution scale.
- VSync/presentation settings.
- GPU identity.
- Cache condition (cold/warm).
- Measurement milestone.
- Run order.
Most importantly, guest progress is not automatically host-presented FPS. A backend can advance a guest-visible marker earlier while still completing GPU work later.
| Question | Page |
|---|---|
| How is guest GPU state shared before backend selection? | [[NV2A Graphics Architecture |
How do GL context, programs, surfaces and glFinish fit together? |
[[OpenGL Renderer |
| How do command buffers, pipelines, descriptors and barriers fit together? | [[Vulkan Renderer |
| Why are Vulkan shader misses expensive? | [[Shader Binding & Compilation |
| How is first-seen Vulkan specialization being researched? | [[Ubershader & Hybrid Specialization |
| Why can a vertex upload end a Vulkan render pass? | [[Vertex Uploads & Render-Pass Lifetime |
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