Repository navigation
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.
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
The guest command stream is therefore separated from host API submission by several layers of validation, state decoding, and lifetime management.
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 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.
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.
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
VkShaderModulestate and prepares explicit graphics pipelines.
See OpenGL Renderer and Vulkan Renderer.
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]
Some of these stages are conditional. Dirty tracking and caches exist to avoid rebuilding state that has not changed.
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 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 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.
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.
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.
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.
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.
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.
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]
Backend choice does not change what the guest is supposed to observe. It changes how xemu realizes the work on the host.
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
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.
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.
- 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 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 |
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.
- Hardware Oddities — GPU address domains, resource aliasing, raw shader state, precision, PFIFO, semaphores, and display scanout. The linked pages label Xbox observations separately from NVIDIA-family test targets.
- OpenGL Renderer
- Vulkan Renderer
- OpenGL vs Vulkan
- System Architecture
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