Dark Mode

/*

picotron_gfx_pipeline.txt
 
author:  zep
updated: 2022-12-31 (WIP!)
 
picotron.net

*/

▨ Draw State

Picotron API functions for drawing sprites (spr, map, tline) and shapes (circ, rect, line) all observe a draw state that is used to decide what the video out value is for each pixel:

Colour Tables:    64x64 lookup tables indexed by source and destination colour values
Fill Pattern:     A 64-bit (8x8) repeating pattern
Read Mask:        An 8-bit mask applied to the draw colour
Write Mask:       An 8-bit mask that determines which bits in the target pixel should be altered
Target Mask:      Applied to the target pixel value during colour table selection and lookup

Picotron's video output format is 6-bit colour index per pixel. When a frame has been sent to video out, it is displayed using an rgb "display palette" definable at 0x5000. There are 4 64-colour display palettes that can be selected per-scanline using bits at 0x5400 (2 bits per scanline, LSB first).

Bitmaps in Picotron are 2d 8-bit blocks of userdata (b = userdata("u8", width, height)). The low 6 bits form the colour index (0..63), and the high 2 bits are used to switch between colour tables ("colour table selection bits", or "stencil bits" for short, as this is a common use for them).

▨ Colour Tables

A colour table is conceptually a 64x64 lookup table that is indexed both by the draw colour (or the colour of each pixel in the case of sprites), and the colour of the target pixel that is to be written. For example, drawing red (colour 8) over a blue pixel might produce a different result than drawing over a green pixel, depending on the state of the colour table.

Unlike PICO-8, there is no draw palette or per-colour transparency bit. Instead, all colour mapping, transparency, and other effects like stencil masking and colour blending, are implemented using colour tables.

The default colour table is set up so that 0 is transparent, and all other colours map to themselves. The 4096 (64x64) bytes look like this:

0,1,2,3, ... 63,   -- colour 0 maps to whatever the target colour value is
1,1,1,1, ... 1,    -- colour 1 overwrites any target value with 1
2,2,2,2, ... 2,    -- colour 2 overwrites any target value with 2
..
63,63,63 ... 63

Functions pal() and palt() only alter the colour table values, and can be used to efficiently make a colour opaque (and mapped to another colour) or transparent (mapping each target value to itself).

▨ Masks

Each of the three masks (@[email protected]) are used to control which bits in the colour index (0x3f) are read/written, and also which stencil bits (0x40, 0x80) are observed during colour table selection.

For example, setting the write mask (0x5509) to 0x3 means that only the lowest two bits can be altered in the target bitmap for any given gfx operation. cls() is an exception, and performs a raw memset.

▨ Colour Table Selection

There are 4 colour tables defined at 0x8000. Which colour table is applied for each pixel depends on 3 things:

  1. The stencil bits (0xc0) in the draw colour
  2. The stencil bits (0xc0) in the target colour
  3. The fill pattern (used only for sprites)

The colour table index (0..3) is derived from:

  ( ((draw_col & read_mask) | (target_col & target_mask)) >> 6 ) | fillp_bit
   
  // fillp_bit is 0x0 or 0x2, and only used when drawing sprites / textures

By default, the read mask (@0x5508) is 0x3f and the fill pattern is 0, so only the first colour table (at 0x8000) is used unless there are high bits set in the target pixels.

The default colour table at 0x9000 is an identity colour table (has no effect), so setting bit 0x40 in the pixels of the target bitmap will function as a stencil; once set, no gfx operations will alter that pixel value (assuming the default mask values).

To disable colour table selection, unset bits 0xc0 in both the read mask (@0x5508) and the target mask (@0x550a)

▨ Fill Patterns

The global fill pattern is a 8x8 1-bit pattern defined with 8 bytes starting at 0x5500.

It is used differently by shape (circfill, line..) and sprite (spr, map..) functions:

For shape functions, the draw colour (normally passed as the last parameter) is taken to be 2 separate colours. The second colour is stored in the high 8 bits (0xff00) and is used when the fill pattern bit is set for that pixel.

For sprite functions, the draw colour (taken from the sprite's pixel) is taken to be a single colour. Instead, the fill pattern bit is used to control which colour table is used. When the fill pattern bit is set, colour table 2 or 3 is used (according to the normal colour table selection rules for stencil bit 0x40).

▨ tline3d

Graphics functions are similar to PICO-8, with the exception of tline3d:

tline3d(src, x0, y0, x1, y1, u0, v0, u1, v1, [w0, w1])

src can be either a bitmap or a map x,y are screen pixels (ints) u,v are texture coordinates in pixels w is 1/z, useful for perspective-correct texture mapping u,v should be given as u/z and v/z when w0 and w1 are both 1 (the default), tline3d is linear

▨ sspr

sspr is also a little different from PICO-8; the first parameter is the source sprite:

sspr(src, x0,y0,w0,h0, x1,y1,[w1,h1])

▨ CPU costs

// Provisional goals -- Picotron Playground currently has more conservative placeholder costs for gfx operations.

A full screen sprite or rectfill (480x270) can be drawn 8 times a frame at 60fps
A full screen worth of tline3d calls can be called 3 times a frame at 60fps

▨ Memory Map

0x5000  rgb display palettes (1k)
0x5400  per-scanline rgb display palette selection (256 bytes)
0x5500  picotron draw state (64 bytes)
  0x5500  fill pattern (8 bytes)
  0x5508  read mask
  0x5509  write mask
  0x550a  target mask
  [..]
0x5540  reserved (64 bytes)
0x5580  controller input state (128 bytes)
0x5600  P8SCII custom font (2k)
0X5e00  persistent cart data (256 bytes)
0x5f00  legacy draw state (reserved)
0x5f80  legacy gpio state (reserved)
0x6000  legacy video memory (8k)
0x8000  colour tables (16k)
0xc000  unused (16k)
0x10000 video memory (128k)