/* * gfx_core.c — Sprinter graphics: common state + setup/teardown API. * * This module owns the variables that select which graphics page is * visible vs drawn into, which W3 bank is mapped during gfx writes, * and the row-base CPU address. All mode-specific primitives (256- * color in gfx_raw_256.c / gfx_256.c, 16-color in gfx_raw_16.c / * gfx_16.c, text in gfx_text_*.c) read this state via extern. * * Public API: * gfx_init / gfx_done — switch video mode, restore previous * gfx_set_visible_page / get — ESTEX $54 SELPAGE wrapper + cached value * gfx_set_draw_page / get — updates _gfx_addr_base for the new page * gfx_set_bank / get — sets the W3 page byte (0x50..0x5F) * gfx_wait_vsync — EI; HALT until next frame interrupt * gfx_pal_load / gfx_pal_set — BIOS $A4 PIC_SET_PAL wrappers * * Shared state (extern from this file): * _gfx_addr_base — 0xC000 for page 0, 0xC140 for page 1. Every * mode-specific primitive uses this instead of a * hard-coded 0xC000 so the same code targets the * currently-selected draw page. * _gfx_bank — read by _gfx_w3_video_begin in gfx_raw_common.c * to map the right W3 page. */ #include #include /* From conio's videomode_raw.c — bypasses set_videotextmode's text-only * validation so gfx_init can move INTO graphics modes. */ extern uint8_t _videomode_raw_get(void); extern int _videomode_raw_set(uint8_t mode); /* ---- Shared graphics state --------------------------------------- */ /* Cached values of the SELPAGE state. Page numbers are 0 or 1. */ static uint8_t _gfx_visible_page = 0; static uint8_t _gfx_draw_page = 0; /* The W3 page byte (0x50..0x5F) — see memory/sprinter_vram_transparency.md * for the bit-meanings (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C * both). Read by _gfx_w3_video_begin in gfx_raw_common.c. */ uint8_t _gfx_bank = 0x50; /* CPU address of column 0 in the current draw page. Each VRAM row is * 1024 bytes wide — page 0 occupies bytes 0..319 (CPU 0xC000+), page 1 * occupies 320..639 (CPU 0xC140+), and the remaining 384 bytes hold * mode descriptors / palette data we don't touch. Updated by * gfx_set_draw_page; read by every primitive's raw helper. */ uint16_t _gfx_addr_base = 0xC000; /* ---- gfx_init / gfx_done ----------------------------------------- */ uint8_t gfx_init(uint8_t mode, uint8_t page) { uint8_t prev = _videomode_raw_get(); _videomode_raw_set(mode); _gfx_bank = 0x50; gfx_set_visible_page(page); gfx_set_draw_page(page); return prev; } void gfx_done(uint8_t mode) { _videomode_raw_set(mode); } /* ---- Visible page (ESTEX $54 SELPAGE) ---------------------------- * * * Direct OUT to port 0xC9 bit 0 toggles the "screen mode page" register * but doesn't notify DSS's display bookkeeping, leaving the screen in * an inconsistent state (one of the swaps shows as black). Going * through the syscall keeps DSS happy. */ void gfx_set_visible_page(uint8_t page) { _gfx_visible_page = page & 1; __asm push ix ld a, (__gfx_visible_page) ld b, a ; B = page 0/1 ld c, #0x54 ; ESTEX SELPAGE rst #0x10 pop ix __endasm; } uint8_t gfx_get_visible_page(void) { return _gfx_visible_page; } /* ---- Draw page --------------------------------------------------- */ void gfx_set_draw_page(uint8_t page) { _gfx_draw_page = page & 1; /* Direct constants beat (0xC000 + (cond ? 0x140 : 0)) by 3 Z80 * instructions — SDCC doesn't fold the constant addition. */ _gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000; } uint8_t gfx_get_draw_page(void) { return _gfx_draw_page; } /* ---- W3 bank (0x50..0x5F) ---------------------------------------- */ void gfx_set_bank(uint8_t bank) { _gfx_bank = bank; } uint8_t gfx_get_bank(void) { return _gfx_bank; } /* ---- Frame sync -------------------------------------------------- * * * Block until the next IM2 frame interrupt (50 Hz, programmed by DSS * for keyboard / cursor maintenance). The Z80's HALT instruction * sleeps the CPU until the next IRQ, which DSS handles and returns * to the instruction after HALT — that's the start of the vertical * retrace window, ideal for a tear-free page swap. */ void gfx_wait_vsync(void) __naked { __asm ei halt ret __endasm; } /* ---- Palette (BIOS $A4 PIC_SET_PAL) ----------------------------- */ static uint8_t pal_num_; static uint8_t pal_start_; static uint8_t pal_count_; static uint16_t pal_data_; void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count, const uint8_t *data) { pal_num_ = pal_num; pal_start_ = start; pal_count_ = count; pal_data_ = (uint16_t)(uintptr_t)data; __asm push ix ld a, (_pal_start_) ld e, a ; E = start ld a, (_pal_count_) ld d, a ; D = count (0 → 256) ld hl, (_pal_data_) ; HL = data ld b, #0xFF ; mask ld a, (_pal_num_) ; A = palette number ld c, #0xA4 ; BIOS PIC_SET_PAL rst #0x08 pop ix __endasm; } void gfx_pal_set(uint8_t pal_num, uint8_t idx, uint8_t r, uint8_t g, uint8_t b) { uint8_t entry[4]; entry[0] = b; entry[1] = g; entry[2] = r; entry[3] = 0; gfx_pal_load(pal_num, idx, 1, entry); }