--- name: graphics-vic2 description: > Use this skill for all VIC-II graphics programming on the C64. Covers text modes, bitmap modes, multicolor modes, sprites, raster interrupts, smooth scrolling, color RAM, and the VIC-II register set. Sources: The Anatomy of the C64, COMPUTE!'s Mapping the C64, The Advanced ML Book. --- # VIC-II Graphics Programming ## VIC-II Chip Overview The **MOS 6567** (NTSC) / **MOS 6569** (PAL) Video Interface Chip II is the C64's graphics processor. It resides at addresses **$D000–$D3FF** (53248–54271) when I/O is banked in. Key capabilities: - 40×25 character text display (standard or multicolor) - 320×200 high-resolution bitmap (or 160×200 multicolor bitmap) - 8 independent hardware sprites (24×21 each, or 12×21 multicolor) - 16 colors - Programmable raster interrupt - Horizontal and vertical fine scrolling (8 pixels each) - Background color in multiple modes (up to 4 simultaneously) --- ## VIC-II Register Map ($D000–$D02E) ### Sprite Position Registers | Address | Decimal | Register | Description | |---------|---------|----------|-------------| | $D000 | 53248 | SP0X | Sprite 0 X position | | $D001 | 53249 | SP0Y | Sprite 0 Y position | | $D002 | 53250 | SP1X | Sprite 1 X position | | $D003 | 53251 | SP1Y | Sprite 1 Y position | | $D004 | 53252 | SP2X | Sprite 2 X position | | $D005 | 53253 | SP2Y | Sprite 2 Y position | | $D006 | 53254 | SP3X | Sprite 3 X position | | $D007 | 53255 | SP3Y | Sprite 3 Y position | | $D008 | 53256 | SP4X | Sprite 4 X position | | $D009 | 53257 | SP4Y | Sprite 4 Y position | | $D00A | 53258 | SP5X | Sprite 5 X position | | $D00B | 53259 | SP5Y | Sprite 5 Y position | | $D00C | 53260 | SP6X | Sprite 6 X position | | $D00D | 53261 | SP6Y | Sprite 6 Y position | | $D00E | 53262 | SP7X | Sprite 7 X position | | $D00F | 53263 | SP7Y | Sprite 7 Y position | | $D010 | 53264 | MSIGX | Most significant bits of sprites' X positions (bit N = sprite N) | ### Control Registers | Address | Decimal | Register | Description | |---------|---------|----------|-------------| | $D011 | 53265 | SCROLY | Vertical control register | | $D012 | 53266 | RASTER | Raster counter / raster IRQ compare | | $D013 | 53267 | LPENX | Light pen X (read-only) | | $D014 | 53268 | LPENY | Light pen Y (read-only) | | $D015 | 53269 | SPENA | Sprite enable (bit N = sprite N) | | $D016 | 53270 | SCROLX | Horizontal control register | | $D017 | 53271 | YXPAND | Sprite Y-expansion (bit N = sprite N) | | $D018 | 53272 | VMCSB | VIC-II memory control | | $D019 | 53273 | VICIRQ | Interrupt register (read/clear) | | $D01A | 53274 | IRQMSK | Interrupt enable mask | | $D01B | 53275 | SPBGPR | Sprite-background priority | | $D01C | 53276 | SPMC | Sprite multicolor enable | | $D01D | 53277 | XXPAND | Sprite X-expansion | | $D01E | 53278 | SPSPCL | Sprite-sprite collision (clears on read) | | $D01F | 53279 | SPBGCL | Sprite-background collision (clears on read) | | $D020 | 53280 | EXTCOL | Border color | | $D021 | 53281 | BGCOL0 | Background color 0 | | $D022 | 53282 | BGCOL1 | Background color 1 (extended/multicolor) | | $D023 | 53283 | BGCOL2 | Background color 2 (extended/multicolor) | | $D024 | 53284 | BGCOL3 | Background color 3 (extended color text only) | | $D025 | 53285 | SPMC0 | Sprite multicolor register 0 | | $D026 | 53286 | SPMC1 | Sprite multicolor register 1 | | $D027 | 53287 | SP0COL | Sprite 0 color | | $D028 | 53288 | SP1COL | Sprite 1 color | | $D029 | 53289 | SP2COL | Sprite 2 color | | $D02A | 53290 | SP3COL | Sprite 3 color | | $D02B | 53291 | SP4COL | Sprite 4 color | | $D02C | 53292 | SP5COL | Sprite 5 color | | $D02D | 53293 | SP6COL | Sprite 6 color | | $D02E | 53294 | SP7COL | Sprite 7 color | --- ## $D011 — SCROLY: Vertical Control Register ``` Bit 7 RST8 — Bit 8 of raster compare (use with $D012 for lines > 255) Bit 6 ECM — Extended Color Mode (1=on) Bit 5 BMM — Bitmap Mode (1=on, 0=text mode) Bit 4 DEN — Display Enable (1=display on) Bit 3 RSEL — Row Select: 1=25 rows, 0=24 rows (borders shown) Bit 2-0 YSCL — Fine vertical scroll (0-7) ``` Default value: `$1B` = %00011011 (display on, 25 rows, scroll=3) ## $D016 — SCROLX: Horizontal Control Register ``` Bit 7 (unused) Bit 6 (unused) Bit 5 RES — Always set to 1 for normal operation Bit 4 MCM — Multicolor Mode (1=on) Bit 3 CSEL — Column Select: 1=40 columns, 0=38 columns Bit 2-0 XSCL — Fine horizontal scroll (0-7) ``` Default value: `$C8` = %11001000 (40 cols, scroll=0) ## $D018 — VMCSB: VIC-II Memory Control ``` Bits 7-4 VM — Video matrix base address (within current VIC-II bank) Bits 3-1 CB — Character base address (within current VIC-II bank) Bit 0 (unused) ``` Video matrix address = bits[7:4] × $0400 within the VIC-II bank Character base = bits[3:1] × $0800 within the VIC-II bank | $D018 Value | Screen RAM | Character Set | |-------------|------------|---------------| | $10 (default) | $0400 | $1000 (ROM chars via mirror) | | $14 | $0400 | $1800 (ROM chars, inverted) | | $18 | $0400 | $2000 (user char at $2000) | | $15 | $0400 | $1400 (user chars at $1400 in Bank 0) | --- ## Display Modes ### 1. Standard Text Mode (default) - 40×25 characters, 8×8 pixels each = 320×200 pixels - **Screen RAM** at $0400: each byte = character code (0-255) - **Color RAM** at $D800: each byte = foreground color (bits 0-3) - Character set at $D000 (character ROM, 256 chars × 8 bytes = 2KB) ### 2. Multicolor Text Mode ($D016 bit 4 = 1) - Characters with color RAM value ≥ 8 use 4 colors, 2 pixels per bit - Characters with color RAM value 0-7 use standard 2-color mode - Colors: BG0 ($D021), BG1 ($D022), BG2 ($D023), color RAM value AND 7 ### 3. Extended Background Color Mode ($D011 bit 6 = 1) - Only 64 character codes available (bits 6-7 select BG color) - Four background colors: $D021–$D024 - Cannot be combined with bitmap or multicolor modes ### 4. Standard Bitmap Mode ($D011 bit 5 = 1) - 320×200 pixels, 8000 bytes of bitmap data - Each 8×8 cell has its own 2-color pair stored in screen RAM - Bitmap data location: bit 3 of $D018 × $2000 within VIC-II bank - $D018 = $18 → bitmap at $2000 (if screen at $0400) - $D018 = $1A → bitmap at $4000 in Bank 0? No — bitmap bit is bit3 of high nybble - Screen RAM holds color pairs: high nybble = "0" pixel color, low nybble = "1" pixel color ### 5. Multicolor Bitmap Mode ($D011 bit 5 = 1, $D016 bit 4 = 1) - 160×200 pixels, 4 colors per 4×8 cell - Colors: $D021 (global), $D022 (global), $D023 (global), color RAM per cell --- ## Sprite Programming ### Sprite Specifications - **Size**: 24×21 pixels (hi-res) or 12×21 pixels (multicolor) - **Data**: 63 bytes per sprite (3 bytes × 21 rows), stored in 64-byte blocks - **Maximum 8 sprites** on screen simultaneously - Hardware **X expansion** (2× wide) and **Y expansion** (2× tall) per sprite ### Setting Up a Sprite **Step 1** — Define sprite shape data (63 bytes in a 64-byte block): ```basic ' Write sprite data to block 13 ($0340-$037F) FOR B = 0 TO 62 : POKE 832+B, 0 : NEXT ' clear first ' Each row is 3 bytes (24 bits = 24 horizontal pixels) POKE 832, 255 : POKE 833, 255 : POKE 834, 255 ' row 0: solid line ``` **Step 2** — Point sprite pointer to the block: ```basic POKE 2040, 13 ' sprite 0 → block 13 ($0340) ' Sprite pointer addresses: 2040-2047 = sprites 0-7 ' (at default screen RAM $0400 + $03F8-$03FF = $07F8-$07FF) ``` **Step 3** — Set sprite position: ```basic POKE 53248, 160 ' sprite 0 X position POKE 53249, 100 ' sprite 0 Y position ``` **Step 4** — Set color and enable: ```basic POKE 53287, 1 ' sprite 0 color = white (1) POKE 53269, PEEK(53269) OR 1 ' enable sprite 0 (bit 0) ``` ### Sprite Screen Coordinates - Valid X range: 0-335 (use $D010 for X > 255) - Valid Y range: 0-247 - Sprite appears at top-left of screen when X≈24, Y≈50 (NTSC) - Top-left of visible area: approximately X=24, Y=50 ### Sprite X > 255 (using $D010) ```basic ' Set sprite 0 X to 300 (= 256 + 44) POKE 53248, 44 ' low 8 bits POKE 53264, PEEK(53264) OR 1 ' set MSB for sprite 0 ``` ### Sprite Priority ($D01B) ```basic POKE 53275, 1 ' sprite 0 behind background characters POKE 53275, 0 ' sprite 0 in front of background (default) ``` ### Collision Detection ```basic ' Read and clear sprite-sprite collisions C = PEEK(53278) ' bit N set if sprite N collided with another sprite ' Read and clear sprite-background collisions C = PEEK(53279) ' bit N set if sprite N hit a background pixel ``` --- ## Colors | Code | Color | Code | Color | |------|-------|------|-------| | 0 | Black | 8 | Orange | | 1 | White | 9 | Brown | | 2 | Red | 10 | Light Red | | 3 | Cyan | 11 | Dark Grey | | 4 | Purple | 12 | Grey | | 5 | Green | 13 | Light Green | | 6 | Blue | 14 | Light Blue | | 7 | Yellow | 15 | Light Grey | --- ## Raster Interrupts The VIC-II generates an interrupt when the raster beam reaches the line stored in $D012 (plus bit 8 in $D011 bit 7). NTSC: 262 lines (0-261), PAL: 312 lines (0-311). Visible screen starts around line 50. ### Setting Up a Raster IRQ ```asm SEI LDA #RasterIRQ STA $0315 ; IRQ vector high LDA #50 ; trigger at line 50 STA $D012 LDA $D011 AND #$7F ; clear bit 8 of raster STA $D011 LDA #$01 ; enable raster IRQ STA $D01A CLI RasterIRQ: LDA $D019 ; read IRQ status STA $D019 ; acknowledge (write-back clears) ; ... your raster effect code ... JMP $EA31 ; chain to normal IRQ handler ``` ### Double-Buffer Raster Trick (for stable raster) ```asm ; After acknowledging the interrupt, wait for the specific raster line WAIT LDA $D012 CMP #100 ; target line BNE WAIT ; Now change colors etc. ``` --- ## Smooth Scrolling ### Vertical Scroll (software scroll) 1. Change `$D011` bits 0-2 (YSCL) from 7 down to 0 each frame 2. When YSCL reaches 0, physically shift screen RAM up one row, reset YSCL=7 ### Horizontal Scroll (software scroll) 1. Change `$D016` bits 0-2 (XSCL) from 7 down to 0 each frame 2. When XSCL reaches 0, shift all characters in each screen row left one, reset XSCL=7 ```basic ' Smooth vertical scroll - change scroll register each frame FOR S = 7 TO 0 STEP -1 POKE 53265, (PEEK(53265) AND 248) OR S FOR W = 1 TO 100 : NEXT ' wait NEXT S ' Then scroll screen RAM and reset to 7 ```