Skip to content
RetrogamingDeep Dive Published Updated 7 min readViews unavailable

Nintendo DS 3D Geometry FIFO: Command Packing, GXSTAT, and the Geometry Pipe

Trace Nintendo DS geometry commands through packed FIFO writes, the internal pipe, GXSTAT, DMA thresholds, matrix state, and polygon completion.

The Nintendo DS separates its 3D command processor from the rasterizer and from the ARM9/ARM7 communication FIFO. The geometry engine accepts commands through its own FIFO, transforms vertices and polygons, and eventually supplies primitives to the 3D renderer. That path has command packing, parameter counts, internal buffering, matrix stacks, status, and DMA request behavior. It is not the same FIFO as the IPC mechanism used to message the two CPUs.

The distinction is operationally important. A homebrew engine may submit a correct sequence but still overflow or starve the command path. An emulator may render a plausible scene while misreporting busy state or geometry FIFO levels, then fail when the game polls GXSTAT or starts DMA based on a threshold.

GXFIFO carries command words, not arbitrary geometry blobs

The ARM9 writes command and parameter words to the geometry FIFO register region beginning at 0x04000400. The command set includes matrix operations, vertex submission, polygon attributes, texture/palette state, viewport setup, and synchronization or swap operations. Some commands consume one parameter; others consume several. A packed command stream places multiple command identifiers into one command word and follows it with parameters in the documented sequence. The parser must know the parameter count for each opcode before it can interpret the next word.

The geometry engine contains both a FIFO and a smaller processing pipe. Documentation describes 256 FIFO entries plus a four-entry pipe, so a FIFO count of zero does not necessarily mean all previously submitted commands have finished. This explains why “queue empty” and “geometry idle” must remain distinct. GXSTAT provides status and FIFO level information; it also exposes conditions relevant to busy state, stack errors, and DMA service. Read the specific bit definitions for the target hardware revision rather than deriving status from a host list length.

Parameter encoding is command-specific. Vertex commands may use fixed-point or compact coordinate forms, while matrix commands change which stack or matrix is active. Treating every word as a signed coordinate corrupts command boundaries. A robust decoder keeps a table of opcode, parameter width/count, and state effect, and processes a command only when all of its required words are available.

Matrices and stack state are observable

The geometry engine maintains distinct matrix state for projection, position, position/vector, and texture transformations. Push and pop behavior is bounded; overflow and underflow conditions are not interchangeable with silently wrapping an array index. Matrix mode selects the target stack for subsequent operations. The command sequence, not a host graphics API’s current matrix, defines the state.

Vertex commands feed a pipeline that transforms coordinates and associates polygon state. A swap command is a synchronization boundary between geometry production and display. If the engine is still processing, the exact completion and swap behavior matters. A frontend may present frames on a host display later, but that host timing must not change when the DS’s geometry state becomes visible to software.

The engine also retains polygon and vertex capacity limits. A faithful model should track clipping, sorting, and output availability according to the documented hardware limits. If a title reaches a limit, it may intentionally rely on what is discarded. An emulator that allocates an unlimited host vector and renders every polygon can produce a cleaner image than the console and conceal a missing resource counter.

FIFO thresholds and DMA service

Software can poll FIFO level or request DMA service when the command queue reaches a configured condition. A DMA transfer is a producer operation: it writes words into the same command protocol and therefore must preserve ordering and parameter boundaries. The FIFO request signal is a flow-control condition, not a signal that a complete frame has rendered.

Design the emulated scheduler around explicit states such as command waiting for parameters, command executing, pipe occupied, FIFO below/above threshold, and geometry pipeline busy. A CPU write may complete even though the geometry engine has not consumed the word. DMA may be granted while there is capacity but must not overrun that capacity. The transition that changes GXSTAT or raises a geometry interrupt should occur at the correct emulated event, not as an immediate side effect of appending to a host container.

An illustrative decoder test can state the invariant without pretending to encode a specific opcode stream:

for each command word:
    append command identifiers to the pending command queue
    while the next command and all required parameters are present:
        consume exactly that command's parameter count
        update geometry state in order
        schedule execution completion if the command is not immediate

In production tests, replace this outline with a table generated from the official opcode definitions. Assert that truncation leaves unconsumed words queued, unknown commands set the documented error state, and a parameter word whose bit pattern resembles an opcode is still treated as a parameter.

Keep this FIFO separate from IPC and display memory

The IPC FIFO and IPCSYNC registers are for ARM7/ARM9 communication and have their own send/receive flags and interrupts. They do not submit 3D commands. Likewise, the 3D framebuffer and texture memory are downstream resources rather than the geometry FIFO. Separate device models avoid accidental coupling where an IPC reset clears geometry commands or a renderer flush changes CPU-visible command status.

There are useful interactions through bus arbitration and DMA, but interactions should be modeled at the shared resource boundary. If geometry FIFO DMA competes with another transfer for bus time, reflect the documented arbiter. Do not emulate contention by randomly adding frame delays. Preserve provenance for each queue write so a trace can say whether a word came from a CPU store or a DMA channel.

Validation strategy

Begin with isolated FIFO tests: one command with one parameter, a packed command word containing multiple opcodes, a multi-parameter command split across writes, exact FIFO-full behavior, DMA threshold transitions, and FIFO-empty while the internal pipe is still busy. Then test matrix push/pop boundaries and confirm stack error behavior. Use diagnostic homebrew programs or verified command traces rather than guessing based on commercial-game visuals.

For integration, capture the command stream, GXSTAT reads, DMA events, geometry completion, and display swaps for scenes with several matrix modes, texture changes, polygons near capacity, and mid-frame synchronization. Compare state transitions before pixel output. Save-state tests should serialize partially parsed packed command words, queued parameters, matrix stacks, FIFO/pipe occupancy, DMA state, and pending geometry work. Restoring only matrices and framebuffer loses the very temporal state that causes difficult bugs.

The geometry engine is a queue-driven coprocessor with a documented command language and visible backpressure. Model that language and its flow control first; use a host 3D API only as a later rendering implementation detail.

Geometry completion and raster output can diverge in ways that matter to game code. Clipping, polygon sorting, per-vertex attributes, texture lookup, and edge coverage happen after command submission; a host renderer may normally combine these stages into one API call. Keep a hardware-facing geometry result representation that records transformed coordinates, polygon attributes, and ordering before converting it to host primitives. That makes it possible to compare geometry output independently of rasterizer differences. Test near-plane and viewport clipping, degenerate polygons, and state changes between adjacent primitives. Include display-swap commands while geometry is busy and verify that the visible buffer changes only when the documented condition is met. For a save state, include partially completed geometry work and pending swap state, not only submitted command words. These checks catch “looks right on my GPU” discrepancies caused by host clipping conventions or a renderer that eagerly flushes all pending work at frame end.

Related:

Sources:

Comments