Skip to content

gl

the graphics language: the machine's hardware 3D/2D engine

NAME
    gl - the graphics language: the machine's hardware 3D/2D engine

SYNOPSIS
    gl <command line>            one ASCII GL line from the shell
    gl <FILE.GL>                 stream a .gl scene file to the card
    POKE the stream at GLDATA ($FF50) from BASIC or C

DESCRIPTION
    Since stage 10d the card speaks ASCII: `gl DRAW3 90,-90,300` at the
    shell just works (the command wraps the line in CA ... CX, so the
    card is back in hex mode after), and `gl /SCENE.GL` streams a scene
    file verbatim -- the file carries its own CA/CX, so it may be text,
    raw hex bytes, or both. Keywords come in long and short forms
    (DRAW3 / D3, the PG-640A's own abbreviations), any case; numbers
    are decimal int16, '-' negates; delimiters are space, tab, comma,
    semicolon and newline. Card-queued errors are reported after the
    stream ("?GL: unknown command", "?GL: bad parameter", ...).

    A PGC-style graphics processor (modelled on the Matrox PG-640A) in
    the FPGA fabric: it holds the modelling and viewing matrices, the
    window, viewport and clip planes, and draws 2D and 3D primitives
    through the display engine -- all driven by a byte COMMAND STREAM,
    not registers. Anything that can poke a byte can render: C, BASIC,
    even the monitor. Commands are an opcode byte plus little-endian
    16-bit parameters (hex mode; an ASCII mode arrives at stage 10d).

    The port ($FF50-$FF57):
      GLDATA $FF50  write: push one command byte into the FIFO
      GLSTAT $FF51  read: bit7 FIFO full (wait before pushing),
                          bit6 busy, bit1 error pending, bit0 readback
      GLRB   $FF52  read: pop one read-back byte (stage 10e; GLSTAT
                          bit0 says one is waiting; low byte first)
      GLERR  $FF53  read: pop one error byte (0 = none): 1 unknown
                          opcode, 2 bad parameter, 3 mode not fitted,
                          4 FIFO overflow
      GLID   $FF54  read: 'G' ($47) -- the presence probe

    The verbs (opcode / parameters):
      01 NOOP        02 FLIP        03 PGSYNC      04 RESETF
      05 WAIT n      06 COLOR r g b (bytes)        07 FLOOD r g b
      08 POINT       09 POINT3      0F CLEARS r g b (BOTH pages)
      10 MOVE x y    11 MOVER dx dy 12 MOVE3 x y z 13 MOVER3
      28 DRAW x y    29 DRAWR       2A DRAW3 x y z 2B DRAWR3
      30 POLY n xy.. 31 POLYR       32 POLY3 n xyz.. 33 POLYR3
      34 RECT x y    35 RECTR       38 CIRCLE r    39 ELIPSE rx ry
      43 "CA "/"CX " (mode)
      70 CLBEG n     71 CLEND       72 CLRUN n     73 CLOOP n cnt
      74 CLDEL n     79 CLAPP n (P8X: append to list n)
      80 TEXT n ch.. 81 TSIZE s (8.8) 82 TANGLE deg  83 TEXTP n ch..
      84 TDEFIN c    85 TJUST h v (1..3, 1..3)
      61 FLAGRD n    62 MATXRD 1|2  76 CLRD n     (state -> read-back)
      63 PIXRD x y   (one pixel's colour -> read-back; P8X's own)
      64 BLIT x y w h + 2*w*h RAW RGB565 bytes (P8X's own; hex only)
      90 MDIDEN      91 MDORG x y z 92 MDSCAL sx sy sz (8.8)
      93 MDROTX deg  94 MDROTY deg  95 MDROTZ deg  96 MDTRAN x y z
      97 MDMATX 12w  A0 VWIDEN      A1 VWRPT x y z A3 VWROTX deg
      A4 VWROTY deg  A5 VWROTZ deg  A7 VWMATX 9w   A8 DISTH d
      A9 DISTY d     AA CLIPH f     AB CLIPY f     AF CONVRT
      B0 PROJCT ang  B1 DISTAN d    B2 VWPORT x1 x2 y1 y2
      B3 WINDOW x1 x2 y1 y2         C0 AREA
      C1 AREABC r g b               E0 PRMFIL f
      EA LINPAT p                   EB LINFUN m

    3D pipeline: modelling matrix (MD*, composed in command order about
    the MDORG pivot), viewing matrix (VW*, orbiting the VWRPT reference
    point, DISTAN backing the viewer off), projection (PROJCT angle;
    0 = orthographic; power-up = the classic focal-256 camera), hither/
    yon clipping (DISTH/DISTY from the reference point, enabled by
    CLIPH/CLIPY), window clip, viewport map. 2D primitives live in
    window space: no matrix, just clip and map. PRMFIL 1 fills closed
    primitives (POLY3 3 ... is the filled triangle). WINDOW and VWPORT
    take PGC parameter order: x1 x2 y1 y2.

    LINFUN m (stage 10f) sets the pixel-write mode for lines, points
    and outlines: 0 replace (power-up), 1 complement (dest inverted,
    pen ignored), 2 OR, 3 AND, 4 XOR. XOR twice restores the ground --
    the rubber-band idiom. FILLS ALWAYS REPLACE (the burst filler is
    not modal). Applies between primitives, never mid-draw; RESETF
    returns the mode to replace. BASIC LINE/PIXELW honour it too --
    the mode is engine state that every drawing path shares.

    CURVES (stage 10i) draw at the 2D current point and move nothing:
    CIRCLE r and ELIPSE rx ry map their radii through the window->
    viewport scale and rasterize on the device (radii clamp at 255
    device pixels; PRMFIL 1 fills; they clip to the SCREEN, not the
    window -- the one curve/window divergence). A negative radius is
    error 2. (The PGC's ARC and SECTOR were removed 2026-08-30 for
    card placement headroom; opcodes 3C/3D are error 1. Draw partial
    arcs as short DRAW chains -- 4-degree steps match what the card
    did.)

    PATTERNS (stage 10j): LINPAT p sets a 16-bit line pattern in the
    DEVICE (like the LINFUN mode): every line from every door consults
    it, MSB first, restarting each primitive -- glyph strokes
    included. LINPAT -1 restores solid. AREA forces BOTH replace mode
    and a solid line pattern (its visited-mark invariant). RESETF
    restores everything. (The PGC's AREAPT fill mask was removed
    2026-08-30 for card placement headroom; opcode E7 is error 1.)

    TJUST h v (stage 10k) justifies TEXT about the current point:
    h 1/2/3 = left/centre/right, v 1/2/3 = bottom/middle/top. The
    offset is applied in MODEL units before drawing, so TSIZE and
    TANGLE scale and rotate the justification with the string. BOTH
    are compose ALIASES of MDSCAL/MDROTZ: they COMPOUND per issue
    (TSIZE 512 twice = 4x; TSIZE 256 = a no-op, not a restore --
    MDIDEN resets), transform the anchor too, and pivot about MDORG.
    The PGC's TSIZE was absolute; this divergence is deliberate. TEXTP
    is the SAME engine as TEXT (on the PGC, TEXT was the fixed
    character generator and TEXTP the programmable stroke text -- P8X
    text IS stroke text). TEXT records into command lists and replays
    since 10k; TDEFIN still may not.

    READ-BACK (stage 10e + the single-interface migration): FLAGRD n
    streams state (1 PRMFIL, 2 COLOR, 3 PROJCT, 4 DISTAN, 5 WINDOW,
    6 VWPORT, 9 near/far), MATXRD 1|2 the composed modeling/viewing
    matrices, CLRD n a stored command list (length halfword then the
    bytes -- clsave's path), all as int16 words through GLRB. (CLMOD,
    the one-byte in-place list patch, was removed 2026-09-01 -- its
    342 LUT4 funded BLIT; CLRD out and re-record instead.) PIXRD
    x y (2026-08-31, the first single-interface verb) reads ONE
    pixel: window coordinates through the SAME map as every 2D verb
    -- it transforms under WINDOW/VWPORT, so PIXELW-then-PIXRD
    round-trips in any window -- answering one RGB565 word (0
    off-screen, the device rule). All of them record into command
    lists and perform their reads at replay time. The PGC had no
    read-backs; these are P8X's.

    BLIT x y w h (the single-interface DMA verb, 2026-09-01): after
    the header, exactly 2*w*h RAW RGB565 bytes follow in the SAME
    stream -- rows top-down, little-endian, the P8I file layout
    verbatim. (x,y) is the image's BOTTOM-LEFT in window coordinates,
    mapped through the current window like every verb; w,h are DEVICE
    pixels, unscaled (the CIRCLE-radius precedent), each written as a
    real PIXELW: GMODE applies, patterns do not, off-screen clips by
    the device rule. HEX MODE ONLY (no ASCII keyword -- a decimal
    payload would be absurd). w or h of 0 owes no payload; w or h
    over 512 (or negative) is error 2 with NO payload owed -- the
    sender must not stream one. Inside CLBEG it is error 2, the
    header is not recorded, and the payload is consumed and DISCARDED
    so the stream stays in sync; a replayed header (none should
    exist) is error 2 with no payload owed. BASIC's IMAGE is this
    verb, one BLIT per row.

    AREA (stage 10g) seed-fills from the 2D current point (set it with
    MOVE) using the pen, bounded by pen-coloured pixels: outline a
    shape, MOVE inside it, AREA. AREABC r g b bounds on a stated
    colour instead, so the outline and the fill can differ. The fill
    is a scanline flood walking actual framebuffer pixels: a seed
    outside the window is error 2, a seed ON the boundary (or on
    already-pen pixels) is a silent no-op, and a fill too convoluted
    for the 16384-seed stack stops at error 8 with a deterministic
    partial fill. AREA forces the write mode to replace (a fill under
    XOR would erase its own boundary test).

    The OS streams /FONT.GL to this port at BOOT (since 2026-09-01,
    when PGC TEXT replaced BASIC's GTEXT outright), so TEXT works out
    of the box everywhere; the glyph bank survives RESETF -- a font
    is INSTALLED, not drawn -- and dies only at power-off.

    TEXT (stage 10h) draws vector text: a glyph is a COMMAND LIST of
    relative strokes (MOVER3/DRAWR3 ending in a pen-up advance) in a
    second 64-slot bank, one slot per ASCII 32..95 (lowercase folds to
    upper). TDEFIN c records one exactly like CLBEG...CLEND records a
    list; `gl /FONT.GL` loads the shipped font. TEXT draws each char
    at the current 3D point, so place with MOVE3 x y 0 -- and use the
    ORTHO camera (PROJCT 0) for window-space text, since z=0 sits
    behind the perspective camera's near plane. TSIZE s (8.8: 256 =
    design size, 512 = double) and TANGLE deg are compose ALIASES of
    MDSCAL s s s and MDROTZ deg: they scale and rotate strokes AND
    baseline together, they compose like every matrix verb (MDIDEN
    resets), and scaling happens about MDORG -- anchor big or tilted
    text with MDORG at the same point as the MOVE3. In ASCII mode the
    string is quoted: TEXT "HELLO" (one line; the translator emits one
    single-char command per char). Chars without a glyph skip
    silently; TEXT/TDEFIN inside a command list is refused (error 5,
    deferred); RESETF does NOT clear the font -- it is an installed
    resource, though power loss does (SDRAM).

    COMMAND LISTS (stage 10c): CLBEG n records everything up to CLEND
    into list n (0-255, 4KB each, stored on the card) instead of
    drawing it; CLRUN n replays the list, CLOOP n cnt replays it cnt
    times -- and matrix verbs inside a looped list are DELTAS that
    accumulate per pass, so a list that nudges MDROTY, draws, FLIPs and
    WAITs is a self-spinning scene with the CPU idle. CLAPP appends to
    an existing list; CLDEL forgets one. The shell convention: LIST 0
    IS THE SCENE -- tri records/appends it, rotate and camera replay
    it. Errors: 5 nesting (a list op inside recording/replay), 6 list
    undefined, 7 list full (recording aborted).

    From BASIC, a poke loop is the whole driver:
      10 REM white filled triangle, house frame
      20 POKE 65360,179 : REM WINDOW ... (see the opcode table)

EXAMPLES
    gl WINDOW -120 120 -120 120
    gl COLOR 31 0 0 PRMFIL 1 POLY3 3 -80 -80 300 80 -80 300 0 40 420
    gl MDY 30 CLRUN 0             spin the scene 30 degrees, redraw
    gl /DEMO.GL                   play a scene file (uppercase names)
    POKE 65360,2                  flip the pages from BASIC (FLIP)

SEE ALSO
    tri, page, image, g3d, gfx; man basic (the verbs are also native
    BASIC statements: WINDOW, DRAW3, MDROTY, CLBEG ...);
    STAGE10-DESIGN.md and the PG-640A manual
    (docs/reference/pg640a.pdf) in the repo

See also: tri, page, image, g3d, man, basic

The text of man gl on P8X (os/man/gl in the repository). All commands