/* * gfx_raw_16.c — 16-colour (mode 0x82) raw primitives. * * Pixel format (verified empirically 2026-05-31): each byte at * 0xC000+xb holds two horizontal pixels — * high nibble (bits 7-4) = pixel at x = 2*xb (LEFT, even) * low nibble (bits 3-0) = pixel at x = 2*xb + 1 (RIGHT, odd) * * Row addressing matches mode 0x81: Port_Y (0x89) = y, 320 bytes/row, * 320 × 2 = 640 pixels. * * Accelerator: byte-wise — one Fill burst paints up to 256 bytes = * 512 pixels. For a solid colour the byte value is the nibble in * both halves: b = c | (c<<4). * * RMW pattern for unaligned edges & vertical lines: * read byte at (HL); mask out target nibble; OR in new nibble; write. * ~10 instructions per pixel — slow but unavoidable since a byte * spans two horizontal pixels. * * "Raw" = W3-naive: caller wraps a sequence with one * _gfx_w3_video_begin / _gfx_w3_video_end pair. */ #include #include extern uint16_t _gfx_addr_base; /* ---- Scratch shared across asm helpers ---------------------------- */ static uint8_t g16_y; static uint8_t g16_byte; /* combined byte: nibble | (nibble<<4) */ static uint8_t g16_nibble; /* color in correct half (low or high) */ static uint8_t g16_mask; /* mask preserving the OTHER half */ static uint8_t g16_len; /* accel block size (0 = 256) */ static uint16_t g16_addr; /* ---- Accel horizontal Fill burst ---------------------------------- * * * Caller has W3 mapped and DI active. Only the block-size byte uses * SMC. Colour is preloaded into C and shipped via `ld a, c` (0x79). * Inserting another `ld a, #n` between LD C,C and the firing LD (HL),A * breaks the burst — the accel FSM re-reads the immediate as a fresh * block size. */ static void g16_hfill_chunk(void) __naked { __asm ld a, (_g16_len) ld (_g16_h_len_imm), a ld a, (_g16_byte) ld c, a ld a, (_g16_y) out (#0x89), a ld hl, (_g16_addr) ld d, d ; 0x52 — set block size ld a, #0 ; 0x3E nn — length (patched) _g16_h_len_imm = . - 1 ld c, c ; 0x49 — horizontal Fill ld a, c ; 0x79 — A = colour byte ld (hl), a ; fires accel ld b, b ; 0x40 — disable ret __endasm; } /* ---- RMW one nibble at (g16_addr, g16_y) ------------------------- */ static void g16_rmw_pixel(void) __naked { __asm ld a, (_g16_y) out (#0x89), a ld hl, (_g16_addr) ld a, (_g16_mask) ld b, a ; B = preserve mask ld a, (_g16_nibble) ld c, a ; C = new nibble (in correct half) ld a, (hl) and a, b ; clear target nibble or a, c ; OR in new ld (hl), a ret __endasm; } /* ---- Raw primitives (W3-naive, composable) ----------------------- */ void _gfx_putpixel16_raw(int x, int y, uint8_t color) { if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; g16_y = (uint8_t)y; g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); if (x & 1) { g16_nibble = (uint8_t)(color & 0x0F); g16_mask = 0xF0; } else { g16_nibble = (uint8_t)((color & 0x0F) << 4); g16_mask = 0x0F; } g16_rmw_pixel(); } void _gfx_hline16_raw(int x, int y, int len, uint8_t color) { if ((unsigned)y >= GFX_HEIGHT_16) return; if (x < 0) { len += x; x = 0; } if (x >= GFX_WIDTH_16) return; if (x + len > GFX_WIDTH_16) len = GFX_WIDTH_16 - x; if (len <= 0) return; g16_y = (uint8_t)y; uint8_t cnib = color & 0x0F; g16_byte = (uint8_t)(cnib | (cnib << 4)); /* Leading unaligned pixel: x odd → RIGHT half of left-most byte. */ if (x & 1) { g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); g16_nibble = cnib; g16_mask = 0xF0; g16_rmw_pixel(); x++; len--; if (len <= 0) return; } /* Even x; emit (len/2) full bytes via accel hfill. */ int full = len >> 1; g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); while (full > 0) { int chunk = full > 256 ? 256 : full; g16_len = (chunk == 256) ? 0 : (uint8_t)chunk; g16_hfill_chunk(); full -= chunk; g16_addr += chunk; } /* Trailing odd-length pixel: LEFT half of the next byte. */ if (len & 1) { g16_nibble = (uint8_t)(cnib << 4); g16_mask = 0x0F; g16_rmw_pixel(); } } void _gfx_vline16_raw(int x, int y, int len, uint8_t color) { if ((unsigned)x >= GFX_WIDTH_16) return; if (y < 0) { len += y; y = 0; } if (y >= GFX_HEIGHT_16) return; if (y + len > GFX_HEIGHT_16) len = GFX_HEIGHT_16 - y; if (len <= 0) return; g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); if (x & 1) { g16_nibble = (uint8_t)(color & 0x0F); g16_mask = 0xF0; } else { g16_nibble = (uint8_t)((color & 0x0F) << 4); g16_mask = 0x0F; } for (int i = 0; i < len; i++) { g16_y = (uint8_t)(y + i); g16_rmw_pixel(); } } /* Clear the whole 640×256 area with `color`. Row-major hfill — * 256 rows × 2 bursts each (256+64 bytes). */ void _gfx_clear16_raw(uint8_t color) { g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4)); for (int y = 0; y < GFX_HEIGHT_16; y++) { g16_y = (uint8_t)y; g16_addr = _gfx_addr_base; g16_len = 0; g16_hfill_chunk(); g16_addr = (uint16_t)(_gfx_addr_base + 0x100); g16_len = 64; g16_hfill_chunk(); } }