c71e249a4e
First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.
What's in here:
bin/sprinter-cc — driver script invoking SDCC + linker + mkexe
libc/ — Sprinter-specific libc layer over ESTEX/BIOS
(conio, gfx, io, mem, stdio + headers)
runtime/ — crt0 variants (default/small/banked/minimal)
+ heap + bank trampolines
toolchain/ — mkexe (SprintEXE packer, C + tests)
examples/ — 30 demo programs (gfx, file I/O, env, time, …)
lib/Makefile — builds the libc archive (sprinter.lib)
docs/ — converted Sprinter manuals + asm reference samples
third_party/ — solid-c reference compiler dump + sdcc setup script
release_docs/ — packaging / release notes
gitignore overhaul:
• Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
on macOS APFS). Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
• Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
INPUT fixtures, not build outputs.
• Collapse the duplicated SDCC/C/Sdcc sections into one section per
concern (build outputs / vendored / OS-junk).
• Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
179 lines
5.5 KiB
C
179 lines
5.5 KiB
C
/*
|
|
* 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 <gfx.h>
|
|
#include <stdint.h>
|
|
|
|
/* 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);
|
|
}
|