Repository navigation
Graphics Backend Comparison
Reviewed: November 1, 2026 · Source: main
b7140848. Release availability: Both backends and hybrid/cache/vertex foundations are in v0.8.136-0-g458730bf5373 (September 30, 2026). Later #256/#298 OpenGL fixes, #335/#336 Vulkan scheduling and #282–#289 depth foundations are reviewed-main only.
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.
The fresh renderer default is OpenGL, with internal scale 1× and VSync enabled. Vulkan is selectable; the best backend depends on title, GPU/driver and workload. This architectural comparison does not establish a universal performance ranking.
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 | xemu GL program-binary store, validated against xemu/GL identity | xemu exact-source SPIR-V store |
| Learned fallback-family history | No Vulkan-family mechanism | Bounded recipes for launch prewarm; not executable pipelines |
| Persistent driver pipeline data | Different GL driver/program contract | Device-compatible Vulkan cache bytes, vendor/device/UUID checked |
| 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.
| Mechanism | Backend / current scope |
|---|---|
| Hybrid Off/Fallback/Prewarm/Always | Vulkan only; Prewarm fresh default, explicit saved choice preserved, full process restart to apply mode changes |
| Three persistent cache layers | Vulkan SPIR-V, family recipes and driver pipeline bytes; OpenGL program binaries are separate |
| Vertex freshness #85/#125 | Checked ownership/consumed spans; #125 also repairs OpenGL CPU decode and shared draw-scope lifecycle |
| Unread-page/private-version copy avoidance #89 | Vulkan only; eligibility and ordered fallback preserve old recorded reads |
| Bordered cubemap mip fix #256 / DMA revalidation #298 | OpenGL main-only fixes; limited to the documented layout/source cases |
| DOT_ZW depth replacement #264 / workaround removal #265 | Shared rendering semantics; separate from diagnostic NV20 vertex arithmetic |
| PTIMER/control scheduling #267/#271 | Shared guest-device progress/locking, not faster guest clocks; retained Def Jam native evidence is Windows Vulkan |
| Read-only depth/alias foundations #282–#289 | Vulkan main only; #283 direct unscaled upload is Linux-specific |
| GPU-resident alias consumer #290 / small color uploads #291 | Open/HOLD, absent from main; delivered foundations do not imply the full consumer shipped |
Vulkan adapter UUID selection and cross-GPU host-copy transport (#75/#76/#101) are operational capabilities, not automatic speedups. Available host features and memory-sharing extensions determine the transport. Windows waiting shortcuts are platform-specific; do not infer every setting or native result applies to Linux, macOS or another GPU.
#228 prepares missing prewarm stages asynchronously. #335 replaces pending-job polling with completion wakes, while #336 learns new optional families for future launches and admits speculative work from launch-loaded history. Their retained limits include #335's accepted 2.40% texture-control tail increase and #336's unresolved seeded-history black/stopped-flip run. See Hybrid Specialization for exact historical identities and evidence scope.
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.
OpenGL and Vulkan are alternative host realizations, not two models of different Xbox GPUs.
flowchart TD
A[Same NV2A guest contract] --> B[OpenGL realization]
A --> C[Vulkan realization]
B --> D[Implicit driver state / synchronization]
C --> E[Explicit xemu state / synchronization]
D --> F[Equivalent guest-visible result]
E --> F
If one backend performs fewer host operations, that can be entirely legitimate. The comparison target is the guest-visible NV2A result and ordering, not identical host call counts.
Likewise, a backend-specific stall does not prove the original NV2A would stall at the same point. It may be translation overhead, driver compilation, explicit host synchronization, or a conservative coherency rule introduced by the emulator.
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 does implemented Vulkan fallback and specialization work? | 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