Skip to content

Graphics Backend Comparison

OpenAI Codex edited this page Nov 1, 2026 · 3 revisions

OpenGL vs Vulkan

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.

Shared path

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]
Loading

Everything above the renderer boundary should represent the same guest hardware semantics.

Core differences

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

Shader processing

Both backends begin from shared guest shader state but diverge after source generation.

OpenGL

NV2A state
    ↓
shared GLSL generation
    ↓
GL shader compile / program link or cache hit
    ↓
glUseProgram + uniforms/state
    ↓
draw

Vulkan

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.

State translation

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.

Synchronization model

OpenGL

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.

Vulkan

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.

Command timing

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.

Surfaces and readback

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.

Vertex updates

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.

Reports and queries

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.

Caches

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.

Presentation

OpenGL is the natural presentation context in xemu's current graphics design. Vulkan can render on a selected physical device and present through:

  1. Compatible shared external memory into the OpenGL presentation context.
  2. 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.

Current feature and platform scope

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.

Debugging strategy

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.

Neither backend is a literal NV2A pipeline

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
Loading

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.

Performance comparison rules

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.

See Performance Methodology.

Which backend page should I read?

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

Clone this wiki locally