Skip to content
Game 1 Unit 2 of 128 1 hr learning time

Bitplane Display

One bitplane turns colour bands into a real landscape. Where bits are 0, sky shows through. Where bits are 1, terrain appears. The bitmap is the world.

2% of Exodus

In Unit 1, the Copper painted colour bands. Now those bands become a real landscape — sky above, earth below. One bitplane turns the entire lower portion of the screen into solid terrain.

The Amiga uses bitplanes for graphics. Each bitplane is a block of memory where every bit maps to one pixel. With one bitplane, each pixel is either 0 (background — COLOR00) or 1 (foreground — COLOR01). Where the terrain bits are 1, the earthy brown of COLOR01 appears. Where they’re 0, the sky gradient of COLOR00 shows through.

This bitmap is the world. In later units, creatures will walk on it, and the Blitter will dig through it. Every pixel of terrain is a bit in memory.

Exodus Unit 2

Sky gradient above. Solid earth below. The Copper still paints the sky — five colour changes from navy to cyan. But now the transition from sky to terrain is real: at row 60 of the bitplane, the bits flip from 0 to 1. The display switches from sky to earth.

Bitplane Basics

The Amiga’s low-res mode is 320 pixels wide and 256 lines tall (PAL). Each pixel in a single bitplane is one bit:

  • 320 pixels = 40 bytes per row (320 ÷ 8)
  • 256 rows × 40 bytes = 10,240 bytes per bitplane

All of this must live in Chip RAM — the first 512KB of memory that the custom chips can access via DMA. The CPU writes to this memory; Agnus reads it automatically every frame.

With 1 bitplane, each pixel indexes into colours 0 or 1:

  • Bit = 0 → COLOR00 (the background — our sky gradient)
  • Bit = 1 → COLOR01 (the foreground — our terrain brown)

More bitplanes give more colours: 2 planes = 4 colours, 3 = 8, up to 6 planes = 64 colours (HAM mode goes further). For now, 1 plane and 2 colours is all we need.

Display Setup

The Copper list gained new registers. These tell the display hardware where the visible area is and when to fetch bitplane data:

copperlist:
            ; --- Display window (standard PAL low-res) ---
            dc.w    DIWSTRT,$2c81       ; Window start: line $2C, column $81
            dc.w    DIWSTOP,$2cc1       ; Window stop: line $12C, column $C1
            dc.w    DDFSTRT,$0038       ; Data fetch start
            dc.w    DDFSTOP,$00d0       ; Data fetch stop

            ; --- Bitplane configuration ---
            dc.w    BPLCON0,$1200       ; 1 bitplane + colour burst
            dc.w    BPLCON1,$0000       ; No scroll
            dc.w    BPLCON2,$0000       ; Default priority
            dc.w    BPL1MOD,$0000       ; No modulo

            ; --- Bitplane pointer (patched by CPU at startup) ---
            dc.w    BPL1PTH
bpl1pth_val:
            dc.w    $0000               ; High word of bitplane address
            dc.w    BPL1PTL
bpl1ptl_val:
            dc.w    $0000               ; Low word of bitplane address

            ; --- Colours ---
            dc.w    COLOR00,COLOUR_SKY_DEEP     ; Background: sky
            dc.w    COLOR01,COLOUR_TERRAIN      ; Foreground: terrain

DIWSTRT and DIWSTOP define the display window — the rectangle of screen where graphics appear. $2C81 means start at line $2C, column $81. $2CC1 means stop at line $12C, column $C1. These are the standard PAL values for a 320×256 display.

DDFSTRT and DDFSTOP control data fetch timing — when Agnus reads bitplane data from memory on each line. $0038 to $00D0 is the standard low-res fetch window, matching 320 pixels.

BPLCON0 changed from $0200 (zero bitplanes) to $1200 (one bitplane). Bits 14-12 hold the bitplane count: 001 = 1 plane. The colour burst bit stays on.

BPL1PTH/BPL1PTL tell Agnus where the bitplane data lives. These are patched by the CPU at startup — the actual address isn’t known until the program loads:

            lea     bitplane,a0
            move.l  a0,d0
            swap    d0                  ; High word first
            lea     bpl1pth_val,a1
            move.w  d0,(a1)
            swap    d0                  ; Low word
            lea     bpl1ptl_val,a1
            move.w  d0,(a1)

The swap instruction exchanges the high and low words of D0. The Amiga stores 32-bit addresses as two 16-bit writes: high word to BPL1PTH, low word to BPL1PTL.

Filling the Terrain

The bitplane starts as 10,240 bytes of zeros — all sky. The fill loop writes $FFFFFFFF (all bits set) from row 60 downward:

            ; --- Fill terrain into bitplane ---
            ; Sky area is already 0 (transparent — shows COLOR00)
            ; Fill from TERRAIN_START to bottom with $FF (solid — shows COLOR01)
            lea     bitplane,a0
            add.l   #TERRAIN_START*BYTES_PER_ROW,a0
            move.w  #(SCREEN_HEIGHT-TERRAIN_START)*BYTES_PER_ROW/4-1,d0
.fill:
            move.l  #$ffffffff,(a0)+
            dbra    d0,.fill

LEA bitplane,a0 loads the bitplane address into A0. ADD.L advances the pointer to the start of the terrain area: row 60 × 40 bytes per row = 2,400 bytes in.

MOVE.W #count,d0 loads the loop counter. DBRA (Decrement and Branch) subtracts 1 from D0 and branches if the result isn’t -1. This means a count of N-1 loops N times. The fill writes 1,960 longwords (7,840 bytes) — 196 rows of solid terrain.

(A0)+ is auto-increment addressing. After each MOVE.L, A0 advances by 4 bytes. The CPU writes, the pointer moves, DBRA loops. No separate increment instruction needed.

DMA Changes

Unit 1 enabled only Copper DMA ($8280). Now bitplane DMA is needed too:

            move.w  #$8380,DMACON(a5)   ; SET + DMAEN + COPEN + BPLEN

Bit 8 = BPLEN (bitplane DMA enable). Without it, Agnus doesn’t read the bitplane — the screen shows only COLOR00 as if no bitmap exists.

Experiment: Move the Terrain Line

Change TERRAIN_START at the top of the file:

TERRAIN_START       equ 60          ; Try 20, 60, 120, 200
  • 20 — terrain nearly fills the screen (thin sky)
  • 120 — terrain in the lower half (wide sky)
  • 200 — thin strip of terrain at the bottom

The number is the bitplane row where terrain begins. Rows above are 0 (sky). Rows below are filled with 1s (terrain).

Experiment: Change the Terrain Colour

COLOUR_TERRAIN      equ $0741       ; Try $0F00, $00F0, $0FFF
  • $0F00 — red terrain (volcanic)
  • $00F0 — green terrain (lush)
  • $0FFF — white terrain (snow)

The entire terrain mass changes with one number because every set bit in the bitplane references COLOR01.

The Complete Code

;──────────────────────────────────────────────────────────────
; EXODUS - A terrain puzzle for the Commodore Amiga
; Unit 2: Bitplane Display
;
; One bitplane turns colour bands into a real landscape.
; Where bits are 0: sky shows through (COLOR00).
; Where bits are 1: terrain appears (COLOR01).
;──────────────────────────────────────────────────────────────

;══════════════════════════════════════════════════════════════
; TWEAKABLE VALUES
;══════════════════════════════════════════════════════════════

; Colours ($0RGB)
COLOUR_SKY_DEEP     equ $0016       ; Deep navy
COLOUR_SKY_UPPER    equ $0038       ; Dark blue
COLOUR_SKY_MID      equ $005B       ; Medium blue
COLOUR_SKY_LOWER    equ $007D       ; Light blue
COLOUR_SKY_HORIZON  equ $009E       ; Pale horizon

COLOUR_TERRAIN      equ $0741       ; Earth brown (where bits = 1)

; Terrain starts at this bitplane row (try 40-120)
TERRAIN_START       equ 60

;══════════════════════════════════════════════════════════════
; DISPLAY CONSTANTS
;══════════════════════════════════════════════════════════════

SCREEN_WIDTH    equ 320
SCREEN_HEIGHT   equ 256
BYTES_PER_ROW   equ SCREEN_WIDTH/8      ; 40
BITPLANE_SIZE   equ BYTES_PER_ROW*SCREEN_HEIGHT  ; 10240

;══════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════

CUSTOM      equ $dff000

DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COP1LC      equ $080
COPJMP1     equ $088
BPLCON0     equ $100
BPLCON1     equ $102
BPLCON2     equ $104
BPL1MOD     equ $108
DIWSTRT     equ $08e
DIWSTOP     equ $090
DDFSTRT     equ $092
DDFSTOP     equ $094
BPL1PTH     equ $0e0
BPL1PTL     equ $0e2
COLOR00     equ $180
COLOR01     equ $182
VPOSR       equ $004

;══════════════════════════════════════════════════════════════
; CODE (Chip RAM)
;══════════════════════════════════════════════════════════════

            section code,code_c

start:
            lea     CUSTOM,a5

            ; --- Take over the machine ---
            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            ; --- Fill terrain into bitplane ---
            ; Sky area is already 0 (transparent — shows COLOR00)
            ; Fill from TERRAIN_START to bottom with $FF (solid — shows COLOR01)
            lea     bitplane,a0
            add.l   #TERRAIN_START*BYTES_PER_ROW,a0
            move.w  #(SCREEN_HEIGHT-TERRAIN_START)*BYTES_PER_ROW/4-1,d0
.fill:
            move.l  #$ffffffff,(a0)+
            dbra    d0,.fill

            ; --- Patch bitplane pointer into Copper list ---
            lea     bitplane,a0
            move.l  a0,d0
            swap    d0                  ; High word first
            lea     bpl1pth_val,a1
            move.w  d0,(a1)
            swap    d0                  ; Low word
            lea     bpl1ptl_val,a1
            move.w  d0,(a1)

            ; --- Install Copper list ---
            lea     copperlist,a0
            move.l  a0,COP1LC(a5)
            move.w  d0,COPJMP1(a5)

            ; --- Enable DMA ---
            move.w  #$8380,DMACON(a5)   ; SET + DMAEN + COPEN + BPLEN

            ; === Main Loop ===
mainloop:
            move.l  #$1ff00,d1
.vbwait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .vbwait

            btst    #6,$bfe001
            bne.s   mainloop

.halt:
            bra.s   .halt

;══════════════════════════════════════════════════════════════
; COPPER LIST
;══════════════════════════════════════════════════════════════

copperlist:
            ; --- Display window (standard PAL low-res) ---
            dc.w    DIWSTRT,$2c81       ; Window start: line $2C, column $81
            dc.w    DIWSTOP,$2cc1       ; Window stop: line $12C, column $C1
            dc.w    DDFSTRT,$0038       ; Data fetch start
            dc.w    DDFSTOP,$00d0       ; Data fetch stop

            ; --- Bitplane configuration ---
            dc.w    BPLCON0,$1200       ; 1 bitplane + colour burst
            dc.w    BPLCON1,$0000       ; No scroll
            dc.w    BPLCON2,$0000       ; Default priority
            dc.w    BPL1MOD,$0000       ; No modulo

            ; --- Bitplane pointer (patched by CPU at startup) ---
            dc.w    BPL1PTH
bpl1pth_val:
            dc.w    $0000               ; High word of bitplane address
            dc.w    BPL1PTL
bpl1ptl_val:
            dc.w    $0000               ; Low word of bitplane address

            ; --- Colours ---
            dc.w    COLOR00,COLOUR_SKY_DEEP     ; Background: sky
            dc.w    COLOR01,COLOUR_TERRAIN      ; Foreground: terrain

            ; --- SKY GRADIENT ---
            dc.w    $3401,$fffe
            dc.w    COLOR00,COLOUR_SKY_UPPER

            dc.w    $4401,$fffe
            dc.w    COLOR00,COLOUR_SKY_MID

            dc.w    $5401,$fffe
            dc.w    COLOR00,COLOUR_SKY_LOWER

            dc.w    $6001,$fffe
            dc.w    COLOR00,COLOUR_SKY_HORIZON

            ; Below the sky, set background to black
            ; (visible in overscan border where bitplane doesn't reach)
            dc.w    $6801,$fffe
            dc.w    COLOR00,$0000

            ; --- END ---
            dc.w    $ffff,$fffe

;══════════════════════════════════════════════════════════════
; BITPLANE DATA (10,240 bytes — Chip RAM)
;══════════════════════════════════════════════════════════════

            even
bitplane:
            dcb.b   BITPLANE_SIZE,0

If It Doesn’t Work

  • No terrain visible? Check BPLCON0 is $1200 (not $0200). The bitplane count must be 1. Also check DMA: $8380 (not $8280) — bit 8 enables bitplane DMA.
  • Screen is all one colour? The bitplane pointer might not be patched correctly. The bpl1pth_val and bpl1ptl_val labels must be placed immediately after their register words in the Copper list — they hold the address values that the CPU patches.
  • Garbage on screen? The bitplane data must be in Chip RAM. The section code,code_c directive (note the _c suffix) ensures this. Without _c, the data could end up in Fast RAM where Agnus can’t read it.
  • Fill doesn’t appear? Check the ADD.L offset. TERRAIN_START*BYTES_PER_ROW must equal the byte offset into the bitplane. If TERRAIN_START is 60 and BYTES_PER_ROW is 40, the offset is 2,400.
  • Terrain is in the wrong position? Remember that bitplane row 0 corresponds to the top of the display window (line $2C), not line 0 of the video beam.

What You’ve Learnt

  • Bitplanes — memory blocks where each bit is one pixel. The Amiga can display 1-6 bitplanes, giving 2-64 colours. One bitplane gives 2 colours: COLOR00 and COLOR01.
  • Display registers — DIWSTRT/DIWSTOP define the visible area, DDFSTRT/DDFSTOP control data fetch timing, BPLCON0 sets the bitplane count.
  • Pointer patching — the CPU writes the bitplane address into the Copper list at startup. SWAP splits a 32-bit address into two 16-bit words.
  • DBRA loops — Decrement and Branch. Count-1 in the register, the instruction subtracts 1 and branches until -1. Combined with (A0)+ auto-increment, it fills memory efficiently.
  • Chip RAM — all DMA-accessible data (bitplanes, Copper lists, sprites, audio) must live in Chip RAM. The _c section suffix ensures this.

What’s Next

The terrain is a solid block — a flat horizon with no features. In Unit 3, the CPU draws a terrain shape directly into the bitplane: hills, gaps, and ledges. Direct memory writes turn a flat bitmap into a landscape that creatures can walk across.