Dreamcast PVR2 Tile Accelerator: Region Arrays, Object Lists, and Frame Submission
Trace Dreamcast PowerVR2 geometry submission through tile binning, per-tile object lists, texture-memory budgets, and the handoff to rasterization.
The Dreamcast’s PowerVR2 does not behave like a conventional immediate-mode renderer that rasterizes each submitted polygon straight into a full-screen framebuffer. Its Tile Accelerator (TA) first consumes scene data and organizes polygon references by screen region. A later rendering stage walks those per-tile lists, rasterizes the relevant primitives, and writes the completed image. That deferred organization is central to both the console’s memory behavior and the way an emulator must model frame progress.
Sega’s Dreamcast/Dev.Box System Architecture describes the display-list data as a Region Array, Object Lists, and ISP/TSP parameters. The TA consumes control, global, and vertex parameters, then generates the object-list and polygon-parameter data used by the rendering core. The CPU prepares the Region Array that associates screen tiles with their relevant list entries. This terminology is more useful than the loose description “the GPU sorts triangles”: it names the buffers and ownership transitions a debugger can actually inspect.
The article separates hardware concepts documented by Sega from API behavior documented by KallistiOS. KallistiOS is a homebrew SDK, not the Dreamcast’s universal hardware specification. Its scene API is a practical example of how software can manage the device, but a rule specific to that API should not automatically be attributed to every commercial SDK.
Two passes change what “drawing a frame” means
In the first pass, the SH-4 program submits primitive headers and vertex data to the TA. The vertex stream includes state such as list type and polygon parameters along with the vertices. The TA uses the submitted geometry to determine which screen tiles a primitive can affect and constructs the structures needed by the rendering core. The resulting object list is not a copy of an ordinary CPU-side triangle array: it is a device-oriented set of references and parameters laid out in PowerVR texture memory.
In the second pass, the rendering core consumes the per-tile data and produces pixels. Sega’s architecture manual describes a 32-by-32-pixel tile organization. Processing a tile locally allows the renderer to resolve visibility and color for that tile without requiring every primitive to round-trip through a large external framebuffer for each intermediate operation. The final output still lives in framebuffer memory; “tile based” does not mean a Dreamcast game has no framebuffer or that the whole image exists only in a transient tile cache.
This ordering is why a frame can be partially complete in one stage but not another. The CPU may have finished submitting geometry while the TA is still building lists; the TA may have completed its work while rasterization is still pending; a rendered buffer may exist before it is the one currently shown. A host renderer that turns every CPU submission into an immediate draw call can accidentally erase these distinctions.
Region Array, Object Lists, and polygon parameters
The Region Array describes the screen’s tile layout and points each tile at the appropriate Object List data. The Object Lists identify which polygon parameter records participate in a tile. ISP/TSP parameters hold data needed by the rendering core, including the polygon and shading information that must be evaluated while rasterizing. Sega’s architecture document describes these as distinct data structures with distinct roles; an emulator should keep them distinct even if its internal representation is optimized.
The Region Array is generally determined by the active screen geometry and tile-buffer configuration, not by the identity of every polygon in a particular scene. Sega’s manual explains that it can often be reused until the display configuration or its backing structures change. That makes it a useful diagnostic baseline: if an unchanged mode produces a different Region Array unexpectedly, check the tile dimensions, buffer base, and address calculation before investigating game geometry.
For a 640-by-480 active area using 32-pixel tiles, the nominal grid is 20 columns by 15 rows. That arithmetic is only a starting point. Real setup must honor the exact mode, clipping limits, hardware encoding, and memory alignment rather than assuming that active pixels always divide evenly into full tiles. A generic calculation can help size a software-side table, but it is not a substitute for packing fields according to the hardware manual:
static unsigned tiles_for_extent(unsigned pixels, unsigned tile_size) {
return (pixels + tile_size - 1u) / tile_size;
}
unsigned columns = tiles_for_extent(active_width, 32u);
unsigned rows = tiles_for_extent(active_height, 32u);
size_t tile_count = (size_t)columns * rows;
The multiplication is widened before evaluation to avoid overflowing a 32-bit intermediate in host tools. Production code should also validate nonzero dimensions, the chosen video mode, and all target-specific address and field limits before allocating or encoding a hardware structure.
Texture memory is a shared scene budget
The TA’s output structures, polygon/vertex data, textures, and other PowerVR allocations compete for a finite texture-memory pool. KallistiOS explicitly warns that its bins and vertex buffers come from that pool and recommends allocating only what the application needs. If a scene appears correct when small but corrupts as object count grows, inspect the TA’s vertex and object-pointer capacity, per-tile occupancy, growth/overflow allocation, and texture residency together. Increasing only a polygon count limit can move the collision rather than fix it.
The words “one buffer per tile” can also be misleading. A tile needs the list information required by the active rendering lists, and the storage model includes list pointers and parameter data, not simply a fixed array of triangle IDs with a universal capacity. Sega’s architecture describes the relationships; KallistiOS’s internal implementation illustrates one concrete strategy with vertex buffers, object pointer buffers, a tile matrix, and overflow space. Those internal KallistiOS structures are implementation details and may change, so they should be used to understand the model - not copied as a hardware ABI.
Budget scenes by worst-case occupancy, not only by total polygon count. A single large polygon can touch many tiles; many tiny polygons may overload a smaller region. A useful profiler records total submitted vertices, TA transfer size, object-list growth, overflow events, occupancy histograms by tile, and texture-memory high-water marks. Report the largest tile separately from the average: averages can conceal a hot region that exhausts its allocation.
Primitive lists are a submission contract
The Dreamcast PVR distinguishes primitive-list types, including opaque and translucent polygon classes as well as modifier and punch-through classes. Software must submit the right header and vertices for the selected list. Mixing list types as if each triangle carried an arbitrary host blend mode is not equivalent to respecting the device’s submission protocol.
KallistiOS documents a practical constraint for its normal TA submission path: primitive types are submitted grouped by list, with a list delimiter between types. Its pvr_list_begin() and pvr_list_finish() calls express those groups. After a list is closed, that list cannot be reopened during the same frame through this API. An opened but empty list is still closed in a way that satisfies the hardware. KallistiOS also offers buffered submission, but that queueing behavior belongs to the SDK; do not infer that the underlying hardware accepts arbitrary interleaving simply because the API can buffer calls from the application.
The following is a control-flow sketch, not a complete buildable example: it omits polygon construction, initialization, error cleanup, and the exact vertex data. It illustrates the grouping boundary that a KallistiOS caller must preserve:
if (pvr_wait_ready() < 0)
return -1;
pvr_scene_begin();
if (pvr_list_begin(PVR_LIST_OP_POLY) < 0)
return -1;
/* Submit all opaque polygon primitives for this scene. */
if (pvr_list_finish() < 0)
return -1;
if (pvr_list_begin(PVR_LIST_TR_POLY) < 0)
return -1;
/* Submit all translucent polygon primitives for this scene. */
if (pvr_list_finish() < 0)
return -1;
return pvr_scene_finish();
In direct pvr_prim() submission mode, KallistiOS documents that the source data must be 32-byte aligned and the size must be a multiple of 32 bytes. Its buffered pvr_list_prim() also requires a size that is a multiple of 32 and requires a vertex buffer for that list. These checks are straightforward to enforce at the API boundary. A wrong list type, misaligned pointer, or incorrect packet length can corrupt a scene long before texture sampling is involved.
Transparency and ordering need explicit tests
Opaque visibility and translucent composition are not interchangeable cases. The PVR has separate list categories and supports an automatic/presort choice for translucent polygons exposed by KallistiOS. The SDK documents this as a mode selection for scene submission, not a guarantee that arbitrary host blending in any draw order will reproduce the console. When porting or emulating a scene, preserve the list classification and configured sorting behavior, then test overlapping translucent surfaces with known depth and coverage.
Avoid statements such as “PowerVR always draws every polygon in perfect depth order.” The result depends on primitive class, depth state, sort mode, and the exact device rules. If an image differs only where translucent surfaces overlap, capture the submitted list, sort setting, depth values, and final per-pixel composition. Sorting the entire host draw list as a generic workaround can fix one title while breaking another.
Frame ownership and synchronization
KallistiOS presents scene construction as pvr_scene_begin(), grouped list submission, and pvr_scene_finish(). The API says that once pvr_scene_finish() is called, more data cannot be submitted until a new scene begins. pvr_wait_ready() blocks until the PVR is ready for another frame; its documentation notes that activation of the new scene occurs at vertical blank. The API also exposes a separate wait for rendering completion, useful when software must know that the device no longer uses a resource before modifying it.
This yields several distinct events worth tracing in an emulator or homebrew renderer:
- The CPU begins a scene and writes or queues geometry.
- The TA consumes the stream and completes its per-tile organization.
- The rendering core rasterizes the prepared scene into an output buffer.
- The display system makes the completed image visible at the appropriate boundary.
Do not collapse all four into “frame complete” unless the chosen API intentionally hides those states and no CPU-visible behavior depends on them. KallistiOS’s internal source shows separate RAM submission, TA, render, and view targets, and documents a pipelined schedule. Those details are evidence of that implementation’s model, not a promise that every game or SDK schedules work identically.
Emulator instrumentation and regression tests
Capture the inputs and outputs at the stage boundaries. A useful trace includes scene/frame identifier, list type, command or vertex address, packet length, polygon state, TA completion, tile coordinates touched, object-list pointer and growth, overflow allocation, render target, and display-visible buffer. Keep console emulated addresses in the trace rather than exposing only host pointers. If the rendered image is wrong, dump both the generated tile/list data and the final framebuffer so the defect can be placed before or after binning.
Build regression scenes that isolate the architecture:
- Submit one triangle fully inside a tile, then one crossing a horizontal and vertical tile boundary.
- Render a large polygon across most of the screen and compare per-tile list membership.
- Stress one tile with many small primitives while keeping total scene size modest; separately stress many tiles with sparse geometry.
- Reuse a Region Array across frames with an unchanged mode, then change mode or clipping and verify that dependent structures are rebuilt.
- Submit opaque, punch-through, modifier, and translucent lists independently before combining them.
- Exercise opaque/translucent overlap under each supported sort configuration.
- Finish a scene while prior rendering is active, then verify that buffer ownership and vertical-blank transitions remain deterministic.
- Save state at submission, TA completion, and rendering completion; restore and compare both intermediate structures and visible output.
Track a memory high-water mark and a per-tile occupancy histogram in stress tests. A renderer that produces a plausible final screenshot but never exercises overflow, frame overlap, or list boundaries has not established robust compatibility. Make the first invalid address, allocation expansion, or illegal state transition fail loudly in development builds.
Practical mental model
Think of the PVR2 as a geometry-registration phase followed by tile-local rendering, with the Region Array connecting the screen grid to per-tile object data. That mental model explains why list grouping, texture-memory allocation, and frame ownership are part of graphics correctness rather than peripheral optimization concerns. It also gives a disciplined debugging order: validate the submitted packets; inspect TA output and tile membership; check object-list capacity and addresses; then inspect rasterization, framebuffer writes, and display handoff.
The central engineering lesson is not merely that the Dreamcast used tiles. It is that the machine exposes meaningful boundaries between scene submission, binning, rasterization, and visibility. Preserving those boundaries produces an emulator that can explain a failure, not just cover it with a host-GPU workaround.
Related:
- Nintendo 64 RDP: From Microcode Display Lists to Filtered Pixels
- Saturn VDP1 Command Tables: Drawing Commands and Framebuffer Handoffs
Sources:
- Sega, Dreamcast/Dev.Box System Architecture (1999), mirrored resource page with original document attribution
- KallistiOS PowerVR scene-submission documentation
- KallistiOS PowerVR polygon-list API documentation
- KallistiOS PowerVR initialization and buffer defaults
- KallistiOS internal PVR pipeline notes and buffer model