Skip to content

Graphics Backend Comparison

github-actions[bot] edited this page Sep 13, 2026 · 3 revisions

OpenGL vs Vulkan

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.

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 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.

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.

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.

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

Clone this wiki locally