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. Thedraw_spriteimplementation usesfor (row, &byte) in sprite.iter().enumerate()— see Destructuring. See The Display for the XOR drawing logic and Font Sprites for howFX29provides the sprite address.