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>
187 lines
5.7 KiB
C
187 lines
5.7 KiB
C
/*
|
||
* 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 <gfx.h>
|
||
#include <stdint.h>
|
||
|
||
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();
|
||
}
|
||
}
|