The Display

The CHIP-8 display is a 64×32 monochrome screen. Each pixel is either on or off — no color, no grayscale, just two states.

(0,0)                                           (63,0)
  ┌──────────────────────────────────────────────┐
  │                                              │  32 rows
  │                                              │
  └──────────────────────────────────────────────┘
(0,31)                                          (63,31)

The framebuffer

#![allow(unused)]
fn main() {
pub const WIDTH:  usize = 64;
pub const HEIGHT: usize = 32;

pub struct Display {
    framebuffer: [[bool; WIDTH]; HEIGHT],  // [[bool; 64]; 32]
}
}

A 2D array of booleans. Indexed as framebuffer[y][x] — row first, then column. The array is private; the outside world reads pixels via pixel(x, y) and draws via draw_sprite.

clear()

#![allow(unused)]
fn main() {
pub fn clear(&mut self) {
    self.framebuffer = [[false; WIDTH]; HEIGHT];
}
}

Sets every pixel to off. Called by the 00E0 CLS opcode.

draw_sprite()

A sprite is a sequence of bytes where each byte represents one 8-pixel row. Bit 7 (MSB) is the leftmost pixel:

byte 0xF0 = 0b11110000:
  ████░░░░

For each bit in the sprite that is 1, the corresponding pixel on screen is XORed:

screen OFF + sprite 1 = pixel ON    (no collision)
screen ON  + sprite 1 = pixel OFF   (collision!)
screen ON  + sprite 0 = pixel ON    (unchanged)

XOR means drawing the same sprite twice returns the screen to its original state. If any pixel goes from ON to OFF during a draw, there was a collision — draw_sprite returns true, and the CPU stores that in VF.

Wrapping

Sprites that extend beyond screen edges wrap around:

#![allow(unused)]
fn main() {
let px = (x + col) % WIDTH;   // horizontal wrap
let py = (y + row) % HEIGHT;  // vertical wrap
}

A sprite drawn at x=62 will have its rightmost pixels appear at x=0 and x=1.

Display is owned by the CPU

#![allow(unused)]
fn main() {
pub struct Cpu {
    // ...
    pub display: Display,
}
}

The DRW opcode calls self.display.draw_sprite(...). The CLS opcode calls self.display.clear(). The main loop reads pixels via cpu.display.pixel(x, y) to build the render buffer.


Rust concepts used here: The 2D boolean array ([[bool; WIDTH]; HEIGHT]) uses the fixed-size array syntax from Primitive Types and Arrays. Iterating over sprite bytes uses Iterators and Destructuring. See CLS and DRW for the opcodes that drive this module.