Skip to content
RetrogamingDeep Dive Published Updated 7 min readViews unavailable

Atari Jaguar Object Processor: Display Lists, Line Buffers, and Bus Priority

Explain Jaguar TOM object headers, linked display lists, bitmap expansion, dual line buffers, bus priority, and repeatable per-scanline diagnostics.

The Atari Jaguar’s TOM Object Processor (OP) is best understood as a display-list engine that constructs scanlines, not as a conventional sprite unit or a modern framebuffer compositor. Software describes objects in memory; the OP follows the list, fetches bitmap phrases, expands pixels, and writes a line buffer while the other line buffer is being displayed. Branch and stop objects control list flow, and interrupt objects can coordinate work with the GPU. The result combines elements of a sprite pipeline and a frame store, but neither analogy alone captures its timing or memory behavior.

This walkthrough follows the video-side object list. It does not cover the Jaguar PVR2, Nintendo 64 video interface, or 3DO cel engine; those systems use different mechanisms and should not inherit Jaguar assumptions.

A scanline is built from an executable list

An object list is a sequence of typed headers and associated data. Bitmap objects describe source image dimensions, pixel depth, position, scaling, and where their data can be fetched. Branch objects redirect list traversal; stop objects end the list; interrupt objects provide a handoff to the GPU. The OP processes objects in list order and modifies the current line buffer, so layering is a consequence of execution order and object flags rather than a later general-purpose depth sort.

Object headers have a hardware-defined phrase layout. A phrase is 64 bits, and scaled and unscaled object forms use different header sizes. Do not overlay a C structure on arbitrary host pointers and assume alignment will match: define the bit fields using explicit masks and shifts, read the exact number of phrases, and validate address alignment. When an object has a transparent color or padding pixels, those details are part of the fetch/expand path and can affect phrase boundaries.

The OP can source pixels at several depths and convert palette-based values into physical line-buffer pixels. Data is fetched as phrases while prior data is expanded, allowing fetch and write work to overlap. That overlap is not unlimited. Resolution, scaling, read-modify-write behavior, DRAM row changes, refresh, and bus-master activity all influence whether peak pixel rates can be sustained. A benchmark that counts only output pixels and ignores memory access patterns is therefore incomplete.

Two line buffers decouple drawing from video output

The hardware has two line buffers. One is read by video output while the OP prepares another; they can be switched at the start and, in some configurations, within a display line. Each is described in the Atari reference as 360 by 32-bit RAM holding physical pixels in the selected color modes. The line-buffer address and current output timing are distinct from the source bitmap address in main memory.

This architecture makes line timing important. If an object list overruns its available time, the visible failure may be a partial line, stale pixels, or a corrupt line-buffer handoff rather than a simple missed frame. If a branch skips an object or a stop is not encountered at the intended point, later list bytes can be misread as a header. Trace object boundaries, source phrase reads, destination pixel positions, and buffer swaps by scanline.

Read-modify-write can composite a new object with existing line-buffer content, enabling effects such as shadows or translucency-like treatments. It changes the throughput budget because the prior destination must be read as well as written. Do not model it as a free alpha blend performed by the host GPU after the line has been completed; that can change ordering and can no longer reproduce interactions with other objects.

Bus priority is part of the visible contract

The OP competes with the Jaguar’s CPU, GPU, DSP, blitter, refresh logic, and other bus masters. The Atari software reference warns that the GPU and blitter must not use high bus priority while the OP runs. In particular, GPU DMAEN and blitter BUSHI settings can violate the required priority constraints. If a higher-priority master steals the bus between critical object-header phrases, the line-buffer address can be corrupted and horizontal black stripes can result.

That documented failure is why “the object list is valid” does not prove the picture is correct. The schedule must honor the relative priorities and the OP’s protected fetch windows. A software emulator can implement arbitration by scheduling request/grant events and accounting for bus cycles. It should not randomize a few pixels or add a generic penalty to every object. Deterministic arbitration yields repeatable traces and makes collision with GPU or blitter DMA diagnosable.

When a title uses interrupt objects, preserve the ordering between object processing and the GPU handler. The interrupt does not imply that the whole frame has completed. A handler may update a later part of a list, compute transformed data, or prepare work for a future line. A host thread that defers the handler until after the entire frame can miss the exact list mutation deadline.

Inspect headers without losing phrase alignment

An engineering dumper should log one record per object: scanline, object type, header address, raw header phrases, source address, width, height, depth, scaling mode, flags, branch target, and estimated bus demand. Keep raw values beside decoded fields. Decode branch destinations only after applying the hardware’s documented addressing rules. Validate every address against mapped memory before reading; malformed or uninitialized display lists should produce a controlled diagnostic rather than an out-of-bounds host access.

An abstract record schema is useful for an emulator trace:

{
  "line": 72,
  "type": "bitmap",
  "header_address": "0x00123400",
  "source_address": "0x00128000",
  "pixel_depth": 8,
  "scaled": false,
  "raw_phrases": ["0x0000000000000000", "0x0000000000000000"]
}

This is diagnostic JSON, not an encoded Jaguar object header. Keeping the distinction explicit prevents a trace format from being copied into game code as if it were the hardware layout.

Build tests around list traversal and bandwidth

First validate an unscaled object in each documented pixel depth, then a scaled object with known source dimensions, a branch, a stop, and an interrupt. Test phrase padding, transparent pixels, clipping at both horizontal edges, line-buffer swap points, read-modify-write, and a branch that crosses a memory page. Add controlled GPU/blitter bus requests at legal and illegal priority so the expected effect is verified rather than guessed.

Run a scanline-level differential test with the same memory image and register state in a trusted emulator or hardware trace. Compare the list cursor, phrase reads, pixel writes, arbitration grants, and buffer selection before comparing the final frame. Save-state tests should serialize both line buffers, object-list cursor, partial phrase/expansion state, priority state, and pending interrupt events. A save taken midway through a line must resume from that point, not rebuild the line from the first object.

The Object Processor’s design rewards accurate boundaries: typed display-list control, phrase-aligned data, per-line buffers, and bus-priority rules. Once these are explicit, rendering errors can be localized to list decoding, memory traffic, scheduling, or color conversion instead of being hidden by a broad “Jaguar graphics” label.

The graphics processor and blitter are collaborators, not substitutes for the OP. A GPU routine can generate transformed vertices or construct the next display list, while the OP remains responsible for line construction. A blitter can prepare or move source data, but its bus priority still has to respect display deadlines. For an emulator debugger, expose the producer of each list region and whether the bytes were complete when the OP fetched them. A race where the GPU writes the next header while the OP is following the current list can look like a random parser failure unless write and fetch cycles are correlated. Stress this with a list split across adjacent memory regions, a branch target updated just before traversal, and a low-priority bus request competing with object fetches. Use fixed deterministic event order so repeating the same input yields the same line. If a stripe appears only under load, inspect arbitration and list publication before changing pixel expansion or the final color converter.

Related:

Sources:

Comments