DRW — Drawing Sprites

DXYN is the only drawing opcode. It reads N bytes from memory starting at address I and draws them as a sprite at position (Vx, Vy). It sets VF to 1 if any pixel was erased — which is how games detect collisions.

Encoding

DXYN
│││└─ N: number of sprite rows (1–15)
││└── Y: register holding y coordinate
│└─── X: register holding x coordinate
└──── opcode category D

Example: 0xD125 — draw a 5-row sprite at (V1, V2), reading from address I.

Decode and execute

#![allow(unused)]
fn main() {
0xD => {
    let x = self.v[((opcode & 0x0F00) >> 8) as usize] as usize;
    let y = self.v[((opcode & 0x00F0) >> 4) as usize] as usize;
    let n = (opcode & 0x000F) as usize;

    let sprite: Vec<u8> = (0..n)
        .map(|row| self.memory.read(self.i + row as u16))
        .collect();

    let collision = self.display.draw_sprite(x, y, &sprite);
    self.v[0xF] = collision as u8;
}
}

Note that X and Y come from the values of Vx and Vy, not the nibbles directly. The nibbles are register indices — the coordinates are whatever those registers contain at runtime.

Reading the sprite

#![allow(unused)]
fn main() {
let sprite: Vec<u8> = (0..n)
    .map(|row| self.memory.read(self.i + row as u16))
    .collect();
}

(0..n) is a range from 0 to N-1. .map() reads each byte from memory at address I + row. .collect() gathers everything into a Vec<u8>. The sprite size is only known at runtime (it comes from the opcode), so a dynamic Vec is necessary.

Sprite format

Each byte is one row of 8 pixels. Bit 7 (MSB) is the leftmost pixel:

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

The digit '0' is 5 bytes:
0xF0  ████░░░░
0x90  █░░█░░░░
0x90  █░░█░░░░
0x90  █░░█░░░░
0xF0  ████░░░░

Collision

draw_sprite uses XOR. If any lit pixel gets drawn over another lit pixel, it turns off. When that happens, draw_sprite returns true. The CPU stores this in VF:

#![allow(unused)]
fn main() {
self.v[0xF] = collision as u8;  // 1 if collision, 0 if not
}

Games use VF to detect when a ball hits a brick, when a player sprite overlaps an enemy, etc.

In the test program

#![allow(unused)]
fn main() {
0xF0, 0x29,  // LD F, V0 — I = address of font sprite for digit V0
0xD1, 0x25,  // DRW V1, V2, 5 — draw 5-row sprite at (V1, V2)
}

Each loop iteration: set I to the font sprite address, then draw it. The x position (V1) advances by 5 each time, drawing all 16 hex digits across the screen.


Rust concepts used here: Vec<u8> as a dynamically-sized collection is Vec<T>. The (0..n).map(...).collect() iterator chain is Iterators. The draw_sprite implementation uses for (row, &byte) in sprite.iter().enumerate() — see Destructuring. See The Display for the XOR drawing logic and Font Sprites for how FX29 provides the sprite address.