Skip to main content

Drawing Basics

The PALETTE Register#

WASM-4 can only display 4 colors on screen at a time. This palette's RGB values are stored in the PALETTE memory register, and can be modified.

For example, to change the palette to Ice Cream GB:

(i32.store (global.get $PALETTE0) (i32.const 0xfff6d3))(i32.store (global.get $PALETTE1) (i32.const 0xf9a875))(i32.store (global.get $PALETTE2) (i32.const 0xeb6b6f))(i32.store (global.get $PALETTE3) (i32.const 0x7c3f58))

The palette colors are considered to be numbered 1-4, even though they may be accessed with indices 0-3.

The default Gameboy-ish palette looks like this:

Color 1
Color 2
Color 3
Color 4

The first color in the palette register is used as the screen background color.

The DRAW_COLORS Register#

DRAW_COLORS is a set of 4 indexes into PALETTE. Drawing functions use these indexes to decide which colors to use, and what to use them for.

DRAW_COLORS is a 16 bit value that holds 4 indexes. Bits 0-3 (the least significant bits) hold the first draw color, bits 4-7 hold the second draw color, and so on.

Setting a draw color to 1 means use PALETTE color 1 for that draw color. The same applies when setting a draw color to 2, 3, or 4.

For example, rect() uses the first draw color for the fill color, and the second draw color as the outline color. To draw a light-green (palette color 2) rectangle with a black (palette color 4) outline:

;; Set DRAW_COLORS to 0x42.(i32.store16 (global.get $DRAW_COLORS) (i32.const 0x42))
;; Draw a rectangle at (10, 10) with size (32, 32).(call $rect (i32.const 10) (i32.const 10) (i32.const 32) (i32.const 32))

However, setting a draw color to 0 will make it transparent. For example, to draw a black outlined rectangle with no fill, set DRAW_COLORS to 0x40.

Other Shapes#

For info on other shape drawing functions like line() and oval(), see the Functions reference.

Direct Framebuffer Access#

The FRAMEBUFFER memory region contains the framebuffer, with each byte containing 4 pixels (2 bits per pixel). In the framebuffer, the palette colors 1-4 are represented numerically as 0-3.

For example, to clear the entire screen to palette color 4, we write 3 into each position:

;; i = 0;(local $i i32)
(loop $loop  ;; FRAMEBUFFER[i] = 0xff;  (i32.store8 offset=0xa0 (local.get $i) (i32.const 0xff))
  ;; i = i + 1;  (local.set $i (i32.add (local.get $i) (i32.const 1)))
  ;; loop while i < 160*160/4  (br_if $loop (i32.lt_u (local.get $i) (i32.const 6400))))

Advanced users can implement their own drawing functions by carefully manipulating the framebuffer. For example, to implement a pixel() function that draws a single pixel:

(func $pixel (param $x i32) (param $y i32)  (local $idx i32)  (local $shift i32)  (local $mask i32)  (local $color i32)
  ;; The byte index into the framebuffer that contains (x, y)  ;;  idx = (y*160 + x) >> 2;  (local.set $idx    (i32.shr_u      (i32.add        (i32.mul          (local.get $y)          (i32.const 160))        (local.get $x))      (i32.const 2)))
  ;; Calculate the bits within the byte that corresponds to our position  ;; shift = (x & 0b11) << 1;  (local.set $shift    (i32.mul      (i32.and        (local.get $x)        (i32.const 3))      (i32.const 2)))
  ;; mask = 0b11 << shift;  (local.set $mask    (i32.shl      (i32.const 3)      (local.get $shift)))
  ;; Use the first DRAW_COLOR as the pixel color  ;; color = *DRAW_COLORS & 0b1111;  (local.set $color    (i32.and      (i32.load16_u (global.get $DRAW_COLORS))      (i32.const 15)))  ;; return if $color is zero, then subtract 1 and mask.  (if (i32.eqz (local.get $color)) (then (return)))  (local.set $color (i32.and                      (i32.const 3)                      (i32.sub (local.get $color) (i32.const 1))))
  ;; Write to the framebuffer:  ;; FRAMEBUFFER[idx] = (color << shift) | (FRAMEBUFFER[idx] & ~mask);  ;;  ;; Note that WebAssembly doesn't have a bitwise not instruction, so  ;; `~n` becomes `n ^ ~0` (where -1 is used for ~0 below).  (i32.store8 offset=0xa0    (local.get $idx)    (i32.or      (i32.shl        (local.get $color)        (local.get $shift))      (i32.and        (i32.load8_u offset=0xa0 (local.get $idx))        (i32.xor          (local.get $mask)          (i32.const -1))))))