Skip to content

GPU pipeline

A program draws by writing a list of commands into memory and telling the GPU where the list is. The emulator reads that list, collects the settings for each draw, rewrites its Ampere machine code for Vulkan, and draws on the PC's graphics card. For example, a triangle takes a handful of commands: where the shaders are, where the vertices are, the viewport, then "draw".

How does a draw reach Vulkan?

sequenceDiagram
    participant RAM as Emulated RAM
    participant P as Pusher (reads command lists)
    participant C as 3D class (drawing registers)
    participant E as Engine
    participant T as Translator (SASS to SPIR-V)
    participant V as Vulkan (PC graphics card)
    E->>P: Queue of command lists
    P->>RAM: Fetch command words
    P->>C: Register writes
    C-->>P: Frozen draw settings
    P-->>E: Events, in command order
    E->>RAM: Read shader header and code
    E->>T: NVIDIA shader code
    T-->>E: Vulkan shader
    E->>RAM: Copy the buffers the draw reads
    E->>V: Build pipeline, draw
    V-->>E: Pixels
    E->>RAM: Write pixels back
    Note over E,RAM: The display reads the finished image from emulated RAM

One draw, from the program's command list to pixels back in emulated memory.

The words on this path:

  • A pushbuffer is one command list in emulated RAM, and the GPFIFO is the queue of pointers to those lists.
  • A method is one command: a write to a GPU engine's register.
  • The 3D class is the drawing engine's register set.
  • SASS is NVIDIA's shader machine code, and SPIR-V is the shader format Vulkan accepts.
  • A Vulkan pipeline bundles the shaders with the settings Vulkan must know before drawing.

What happens at each step?

Step Code Output
Read queue entries and command headers fifo.rs Pusher::submit Register writes, each aimed at one engine
Bind engines to slots, run channel commands fifo.rs channel Semaphore events, the GPU's "I got this far" signals
Keep the drawing registers, freeze them at each draw threed.rs ThreeD::write Event::Draw(DrawSnapshot)
Read the shader header (SPH) and code, translate engine.rs module, sm86 translate_program SPIR-V, cached by a hash of header and code
Copy the buffers the draw reads engine.rs mirror PC-side buffers, keyed by where they sit in emulated RAM
Run compute launches engine.rs dispatch Emulated RAM pages the program changed
Draw and read back graphics.rs VkState::draw RGBA8 pixels
Write back and show engine.rs draw, present Emulated RAM, then display::present

Why does submit wait for every draw?

Gpu::submit returns only after every draw has landed in emulated RAM. So any later CPU read sees the result, with no extra waiting. See ADR-0006.

Why freeze the settings at each draw?

A draw must see the registers as they were when it was issued. The frozen copy, the snapshot, keeps later commands from changing a draw that is still running.

Does compute work take the same road?

Yes, through the compute class 0xC7C0. A QMD, the block in emulated RAM that describes one compute launch, becomes a dispatch snapshot (see command processor). The engine translates the program, runs it over the mapped memory window, writes back changed pages and releases the launch's semaphores.

Verified by: the engine tests in core/src/tests/gpu/engine.rs on Intel and lavapipe (Mesa's CPU-based Vulkan driver), with the Vulkan validation layer reporting no errors. They cover the pushbuffer triangle with a vertex remap and a QMD compute launch. They also cover indexed, instanced, depth-tested, blended and textured draws, render to texture, clears, reports, acquires and both y-flips.

Sources: core/src/gpu/{fifo,threed,engine,graphics}.rs, core/src/tests/gpu/engine.rs.