Skip to content
RetrogamingDeep Dive Published Updated 8 min readViews unavailable

PlayStation GPU Drawing Environment: Clip Areas, Offsets, and Mask Bits

Trace PlayStation GP0 drawing-area, coordinate-offset, and mask-bit state to reproduce clipping, framebuffer protection, command ordering, and rendering tests.

The original PlayStation GPU is easier to emulate when its rendering environment is treated as persistent command state rather than as attributes attached only to one polygon. Three GP0 commands are especially important for that state: E3h and E4h configure a drawing-area rectangle, E5h adds a signed coordinate offset, and E6h controls the mask bit in framebuffer pixels. A textured triangle can have correct vertices and colors yet still draw in the wrong place, overwrite protected pixels, or escape the intended viewport if one of these commands is decoded incorrectly.

This article focuses on those drawing-environment controls. It does not attempt to document the full GP0 polygon format, texture-page selection, semi-transparency blending, or video scanout. Keeping the boundary narrow makes it easier to determine whether a rendering defect comes from clipping and framebuffer policy or from another GPU stage.

Drawing area: define where rasterized pixels may land

GP0(E3h) sets the top-left drawing-area coordinate (X1, Y1), and GP0(E4h) sets the bottom-right coordinate (X2, Y2). The GPU clips rendering commands so pixels outside the configured area are not drawn. This is an output constraint on rasterized pixels; it is not the same as clipping source vertices on the CPU or changing the coordinates sent by a game.

The coordinates are fields in the command word. The documented X field is 10 bits, while Y is 9 bits on the standard 1 MiB PlayStation GPU. Documentation also describes a taller range for a 2 MiB GPU variant used in some non-console hardware, so do not import that larger limit into a normal PS1 model without evidence. Keep the target GPU revision and VRAM geometry explicit in the emulator rather than inferring them from the host display size.

The endpoints describe the drawing window, but edge coverage must be verified against the intended GPU behavior. Tests should draw a rectangle that crosses each boundary by one pixel and inspect both the pixels inside and the untouched pixels outside. Also test reversed, empty, or wrapped-looking coordinates according to the hardware specification or a trusted conformance suite. Do not “repair” odd guest values by silently sorting the corners unless that behavior is established for the target.

The drawing area applies to rendering commands, not to every operation that writes VRAM. GPU fill and transfer commands have their own coordinate and extent semantics. For example, a quick VRAM fill is a separate command with its own alignment and mask rules; it should not be implemented by sending a normal rectangle through the polygon rasterizer and assuming all environment state applies identically.

Drawing offset: change the origin used for primitives

GP0(E5h) carries signed 11-bit X and Y offsets, each in the range -1024 through +1023. The GPU adds this offset to primitive coordinates before rendering. A game can choose an origin aligned with its scene convention: for example, placing coordinate (0,0) at the upper-left of a drawing area or at a center point used by geometry calculations.

An emulator should sign-extend each offset field before adding it. Masking the field as an unsigned value turns a negative adjustment into a large positive displacement. Keep the offset distinct from the drawing-area bounds: the offset changes where primitive coordinates are interpreted, while the drawing area limits the final renderable region. When debugging a misplaced object, capture the raw vertex, current offset, transformed coordinate, and clip result separately.

The drawing offset is persistent environment state until another E5h command changes it. It is not part of every primitive packet. That makes command ordering observable: a later environment command can affect subsequent primitives even when those primitives’ command words are identical to earlier ones. A GPU trace that records only polygon data but omits the active draw area and offset is not sufficient to reproduce a scene.

The mask bit is not simply an alpha channel

In 15-bit framebuffer storage, each pixel uses bits 0–14 for RGB components and bit 15 as a mask flag. The meaning of that same top bit depends on the operation. In a textured source color, bit 15 can be associated with semi-transparency behavior; in the destination framebuffer, the mask setting can use it to protect a pixel from later writes. Treating the bit as a universal alpha value loses this distinction.

GP0(E6h) selects two behaviors:

E6 bit Name in PSX-SPX Effect
0 PBW Preserve the incoming texture mask bit, or force bit 15 on for written pixels
1 PBC Draw regardless of the old mask bit, or reject writes where destination bit 15 is already set

When PBC is enabled, a destination pixel whose mask bit is set is protected from rendering writes. When PBW is enabled, the GPU sets bit 15 on pixels produced by drawing. If PBW is disabled, the mask bit follows the documented source-pixel rule; for untextured polygons it is cleared. That lets software mark newly written pixels as protected while preserving per-pixel texture semantics when requested.

The controls also affect GPU transfer operations in ways that should be modeled deliberately. PSX-SPX notes that the mask setting applies to rendering and CPU-to-VRAM or VRAM-to-VRAM transfers, but not to the quick Fill-VRAM command. Do not route all write paths through one generic “write pixel” helper unless that helper accepts an explicit operation class and reproduces each command’s mask behavior.

Command order and state lifetime

GP0 commands are submitted as 32-bit words, and some commands consume parameter words. The drawing environment therefore belongs in the GPU command processor’s state machine, not in a renderer-side preference that can be changed asynchronously. The emulator should decode the environment command, update its state at the correct point in the stream, and ensure later primitives observe that state in order.

There is an ordering caveat in the PSX-SPX reference: it notes that E3h through E5h do not consume space in the command FIFO and says they are “probably” executed immediately, warning that pending render commands might then see newly updated drawing-area settings. Because the reference itself presents immediate execution as probable, treat this as a testable hardware observation, not as settled primary documentation. A careful implementation should compare traces from real hardware or an established conformance test before changing FIFO semantics based on that note alone.

This is exactly why a single end-of-frame software renderer can be misleading. If it records triangles and reads the final clip rectangle only when rasterizing the frame, it may apply a later environment state retroactively to commands that were submitted earlier. Conversely, eagerly capturing every setting into a draw call can be wrong if the hardware updates or samples that state at a different point. Establish the ordering rule, encode it once in the command processor, and test it with a stream that changes E3h, E4h, or E5h between deliberately simple primitives.

A practical emulator state model

The following is illustrative state organization, not hardware register layout:

GpuDrawState:
    clip_left, clip_top, clip_right, clip_bottom
    offset_x_signed, offset_y_signed
    force_mask_bit
    check_destination_mask

for each pixel produced by a rendering command:
    x = primitive_x + offset_x_signed
    y = primitive_y + offset_y_signed
    if outside_clip_area(x, y): continue
    if check_destination_mask and (vram[y][x] & 0x8000): continue
    pixel = shade_and_texture_sample(...)
    vram[y][x] = apply_mask_rule(pixel, force_mask_bit, ...)

The pseudocode shows the conceptual separation between offset, clipping, destination protection, and pixel generation. A production core still needs the correct edge coverage, coordinate wrapping, command timing, texture-bit handling, and distinct transfer behavior. In particular, apply_mask_rule must know whether the pixel came from a textured primitive, untextured primitive, CPU transfer, VRAM copy, or quick fill.

Regression tests and failure signatures

Start with a cleared VRAM image and one primitive at a time. Draw shapes fully inside the clip area, crossing one edge, and lying fully outside it. Then move the drawing offset by a small positive value and by a negative value; check that the geometry moves while the clip rectangle remains fixed. Test minimum and maximum representable signed offsets to catch zero-extension errors.

For mask behavior, write a destination pixel with bit 15 set and one with it clear. With PBC off, verify that both are eligible for drawing. With PBC on, verify the marked destination is preserved while the clear one can be replaced. Toggle PBW independently and inspect bit 15 in output pixels from textured and untextured primitives. Finally, repeat equivalent writes through the documented transfer and fill commands to confirm that each path follows its own rules.

Record the complete command stream, current environment state, and VRAM coordinates in a regression fixture. If a game shows geometry shifted uniformly, inspect E5h; if objects leak beyond a viewport, inspect E3h/E4h and edge coverage; if decals or layered surfaces disappear, inspect destination bit 15 and PBC/PBW before blaming texture sampling. A deterministic fixture reduces the temptation to hide state errors with host-side clipping or a shader.

The drawing environment is small but has far-reaching effects because it is persistent state shared by many primitives. Model the clip window, signed origin adjustment, and framebuffer mask independently, preserve their command order, and test every VRAM write path. That produces a renderer whose behavior can be explained from the guest’s GP0 stream instead of tuned until one screenshot happens to look right.

Related:

Sources:

Comments