Skip to content

NV2A Graphics Architecture

Mainkill1 edited this page Sep 27, 2026 · 3 revisions

NV2A Graphics Architecture

xemu's graphics implementation is layered. Guest software talks to the NV2A, not to OpenGL or Vulkan. PFIFO and PGRAPH model the guest-facing GPU behavior, while a PGRAPHRenderer backend translates that state into a host graphics API.

This shared layer is the correct place to start before reading backend-specific code.

End-to-end command flow

flowchart TD
    A[Guest CPU] --> B[MMIO / pushbuffer writes]
    B --> C[Guest RAM / DMA objects]
    C --> D[PFIFO pusher]
    D --> E[PFIFO channel / method stream]
    E --> F[PFIFO puller]
    F --> G[PGRAPH method handling]
    G --> H[PGRAPHState]
    H --> I{PGRAPHRenderer}
    I --> J[OpenGL backend]
    I --> K[Vulkan backend]
    J --> L[Host GPU]
    K --> L
Loading

The guest command stream is therefore separated from host API submission by several layers of validation, state decoding, and lifetime management.

PFIFO

PFIFO is the NV2A command front end. Guest software builds pushbuffers and channel state in guest memory. PFIFO consumes that representation and turns it into a stream of methods and data for the graphics engine.

At a high level PFIFO is responsible for work such as:

  • Channel/pushbuffer state.
  • DMA-backed command fetch.
  • Pusher parsing.
  • Puller/method delivery.
  • Coordinating pending renderer work when command processing reaches synchronization points.

The important performance distinction is that a guest draw is not represented by a single host API call at this level. It first appears as a sequence of NV2A methods that update registers, resources, and draw state.

PGRAPH

PGRAPH owns the shared guest graphics state. PGRAPHState contains the state required to interpret NV2A methods and prepare rendering independent of which host backend is active.

Examples of shared responsibilities include:

  • NV2A register state.
  • Vertex attributes and draw state.
  • Texture state.
  • Shader/combiner inputs.
  • Surface state.
  • Report/query state.
  • Dirty/change tracking used to decide what the backend must update.
  • Renderer selection and renderer-owned state pointers.

This division is important: an OpenGL optimization and a Vulkan optimization should preserve the same PGRAPH-visible behavior even if their host implementations differ completely.

Renderer interface

hw/xbox/nv2a/pgraph/pgraph.h defines the renderer abstraction. The active PGRAPHRenderer identifies a backend and provides operations for lifecycle, drawing, surfaces, reports, display, synchronization, and pending work.

Representative operations include:

Renderer operation Architectural purpose
init / finalize Create and destroy backend-owned host resources
early_context_init Establish backend/context prerequisites needed before normal initialization
draw_begin / draw_end Enter/complete a draw around shared PGRAPH state
flush_draw Flush pending draw work where guest semantics require it
clear_surface Implement guest surface clear semantics
surface_update Reconcile guest surface state with backend resources
image_blit Execute guest-visible blit/copy behavior
get_report Retrieve or queue guest GPU report/query results
process_pending Run backend work that must be serviced outside immediate method dispatch
pre_savevm_trigger / pre_savevm_wait Make guest-visible graphics state safe for snapshot serialization
pre_shutdown_trigger / pre_shutdown_wait Quiesce asynchronous/backend work before destruction
get_framebuffer_surface Expose the rendered framebuffer to presentation/UI code
get_gpu_properties Report backend/adapter capabilities to the rest of xemu

The exact backend implementation is different, but the guest-visible contract is shared.

Shared GLSL translation

Both rendering backends consume common NV2A shader/combiner semantics. Shared code under hw/xbox/nv2a/pgraph/glsl translates Xbox GPU state into GLSL-oriented representations used by the host backends.

Conceptually:

NV2A vertex / combiner state
        ↓
shared decoded shader state
        ↓
GLSL generation
        ↓
backend-specific compile / object / pipeline path

The last stage differs substantially:

  • OpenGL compiles/links GL shader/program objects and relies on GL driver state validation.
  • Vulkan compiles GLSL to SPIR-V, builds VkShaderModule state and prepares explicit graphics pipelines.

See OpenGL Renderer and Vulkan Renderer.

Draw preparation

A simplified draw can involve all of the following before the final host draw command:

flowchart TD
    A[NV2A draw method] --> B[Resolve active surfaces]
    B --> C[Resolve textures]
    C --> D[Resolve shader state]
    D --> E[Resolve vertex / index data]
    E --> F[Translate guest render state]
    F --> G[Backend synchronization / resource updates]
    G --> H[Host draw command]
    H --> I[Mark resulting surfaces / reports / state]
Loading

Some of these stages are conditional. Dirty tracking and caches exist to avoid rebuilding state that has not changed.

Guest memory, VRAM, and host resources

Graphics state crosses several representations.

Representation Why it exists
Guest RAM / VRAM Authoritative memory visible to Xbox software
Guest DMA mapping NV2A addressing and resource selection
Backend mirror Efficient host-visible copy of frequently consumed guest data
Staging buffer Ordered transfer source/destination for host GPU operations
Host texture/image/buffer GPU-native resource used for rendering
Host surface/readback buffer Mechanism for returning GPU-written data to guest memory

The newest copy is not always guest RAM. A rendered surface can be newer on the host GPU; a CPU write can make guest memory newer; a later draw may require either an upload or a readback before the resource is coherent.

Surface coherency

Surface coherency is one of the most important graphics correctness problems.

Guest memory says page changed
    ↓
Does page overlap a GPU-authored surface?
    ├─ no  → guest memory can be uploaded directly
    └─ yes → determine whether host surface is newer
                 ↓
             read back if required
                 ↓
             upload/consume correct bytes

Using an overly broad overlap range can create unnecessary GPU readbacks. Using an overly narrow range can let the CPU or vertex path consume stale guest memory. Current development around PRs #85/#87/#89 is focused on this boundary.

Vertex data

Vertex data can be inline, indexed, or backed by guest memory. The backend must preserve the version of bytes that each recorded draw is supposed to consume.

The Vulkan backend makes this especially visible because commands may be recorded and executed later. Rewriting a buffer range after an earlier recorded draw references it can change ordering unless the backend uses an ordered copy, a separate immutable version, or proves the range has not yet been consumed by the active batch.

See Vertex Uploads & Render-Pass Lifetime.

Textures

Texture preparation includes guest DMA source identity, format/layout conversion, surface overlap/freshness, palette/state handling, and host texture caching.

A “texture cache hit” is only safe if the cached host resource still corresponds to the same effective guest source and state. Merged PR #80 is an example of why clean-state reuse needs DMA/source checks rather than only a dirty bit.

Reports and queries

NV2A reports expose GPU-derived results back to guest memory. Their lifecycle can involve:

Guest requests report
    ↓
PGRAPH queues report/query work
    ↓
Backend ensures relevant GPU work is complete
    ↓
Result is serialized to guest-selected memory
    ↓
Guest CPU reads completion/result

Publishing too early breaks correctness; waiting more broadly than required can create stalls. The OpenGL and Vulkan backends can therefore show different host synchronization behavior even for the same guest report command.

Display and presentation

Rendering and presentation are related but separate.

PGRAPH produces the Xbox framebuffer/surface. The backend then makes that output available to xemu's presentation path. Vulkan can use shared external memory when the selected Vulkan device is compatible with the OpenGL presentation context; current main also has a host-copy fallback for incompatible/cross-adapter presentation.

This means “Vulkan rendering on GPU A” and “window presentation uses GPU B/context B” can be separate facts.

Thread and lock boundaries

A practical model is:

Domain Responsibility
vCPU thread Guest x86 execution and guest writes that produce GPU-visible state/commands
PFIFO/PGRAPH processing Consume command stream, mutate guest GPU state, coordinate renderer operations
Renderer backend Own host GL/Vulkan objects according to the renderer's thread/context rules
UI/presentation Consume the framebuffer/presentation result and display it

PFIFO and PGRAPH locks protect shared command/graphics state. The renderer also exposes process_pending and lifecycle hooks because some work must be coordinated outside the immediate guest method call.

Do not move a Vulkan or OpenGL object mutation to a worker thread merely because the work is expensive. First define whether that worker can legally own the host API object and how its result is published back to renderer-owned state.

Snapshot boundary

Host graphics resources are derived state. A snapshot must preserve what the guest can observe, not opaque GL/Vulkan handles.

Before save, the renderer may need to make GPU-authored state visible in guest memory. After restore, backend caches and host resources must be treated as derived from restored guest/device state and revalidated or rebuilt as needed.

This is a major reason live renderer-reset experiments require careful framebuffer/UI coordination rather than simply destroying every host object at arbitrary times.

Backend selection

The renderer interface lets xemu expose the same guest-facing NV2A model through OpenGL or Vulkan.

flowchart LR
    A[PGRAPHState] --> B{Selected renderer}
    B --> C[OpenGL renderer]
    B --> D[Vulkan renderer]
    C --> E[Host GL driver]
    D --> F[Host Vulkan driver]
Loading

Backend choice does not change what the guest is supposed to observe. It changes how xemu realizes the work on the host.

Fidelity boundary: NV2A is not Vulkan or OpenGL

The original NV2A executes its own fixed-function, combiner, vertex-program, memory and raster pipeline. xemu models the guest-visible NV2A command/state contract and translates it into a modern host graphics API; it does not simulate every NV2A pipeline clock or transistor stage.

flowchart TD
    A[Xbox NV2A method/state] --> B[PFIFO/PGRAPH semantic model]
    B --> C[Shared decoded draw/resource state]
    C --> D{Host realization}
    D --> E[OpenGL programs/state]
    D --> F[Vulkan SPIR-V/pipelines/descriptors]
    E --> G[Host GPU architecture]
    F --> G
Loading

Host concepts such as VkRenderPass, descriptor pools, SPIR-V modules, OpenGL program links and driver pipeline caches are emulator implementation artifacts, not hardware blocks inside the Xbox GPU.

Fast paths are guest-state proofs

Texture reuse, shader/module caches, surface caches, direct vertex writes and command-state reuse are safe only when xemu proves that all guest-visible inputs relevant to that host object are unchanged. A cache-key reduction is therefore a hardware-semantics change unless it proves the removed field cannot affect the NV2A result.

Known fidelity-sensitive boundaries

  • GPU-authored surface data can be newer than guest RAM, so CPU/vertex consumers may require readback.
  • Report/query publication must correspond to the guest-visible completion semantics even though GL/Vulkan completion primitives differ.
  • Renderer reset/save/shutdown must reconcile host-only resources back into guest-observable state.
  • Host GPU scheduling/pipeline compilation can add stalls that have no one-to-one NV2A hardware equivalent.

See Hardware Fidelity & Emulation Boundaries.

Source map

Source area Responsibility
hw/xbox/nv2a/pfifo.c Command submission/pushbuffer processing
hw/xbox/nv2a/pgraph/pgraph.c Shared PGRAPH method/state behavior
hw/xbox/nv2a/pgraph/pgraph.h PGRAPHState, renderer interface and shared declarations
hw/xbox/nv2a/pgraph/glsl/* Shared shader/combiner translation infrastructure
hw/xbox/nv2a/pgraph/gl/* OpenGL backend
hw/xbox/nv2a/pgraph/vk/* Vulkan backend
hw/xbox/nv2a/ptimer.c NV2A PTIMER; separate from draw rendering but part of the same device
hw/xbox/nv2a/pcrtc.c Display/CRTC-facing NV2A behavior

Related Development

Current research is deliberately kept separate from this architecture description:

  • PR #80 is merged and forms part of current Vulkan texture-binding behavior.
  • PR #85 investigates/repairs vertex/surface freshness.
  • PR #87 investigates fresh-page direct vertex mirror writes.
  • PR #89 investigates immutable bounded vertex versions.
  • PR #92 recorded a negative live renderer-reinitialization experiment and exposed a framebuffer ownership wait.

See Development for current status.

Related pages

Clone this wiki locally