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>
176 lines
5.8 KiB
C
176 lines
5.8 KiB
C
/*
|
||
* gfx_raw_256.c — 256-colour (mode 0x81) raw primitives.
|
||
*
|
||
* "Raw" = W3-naive: caller must have W3 mapped to the video bank and
|
||
* interrupts disabled (typically via _gfx_w3_video_begin/end from
|
||
* gfx_raw_common.c). Composite primitives in gfx_256.c wrap one
|
||
* begin/end around many raw calls so the W3 dance is amortised across
|
||
* the whole drawing operation.
|
||
*
|
||
* Addressing (mode 0x81): pixel (x, y) lives at CPU 0xC000 + x with
|
||
* Port_Y (0x89) = y. _gfx_addr_base is 0xC000 for draw page 0 and
|
||
* 0xC140 for draw page 1 — see gfx_core.c.
|
||
*
|
||
* Accelerator opcodes (docs/converted/accel_r.txt):
|
||
* LD D,D (0x52) enter "set block size" mode; the NEXT byte is the
|
||
* length operand and MUST follow LD A immediate (0x3E).
|
||
* LD C,C (0x49) horizontal Fill mode (LD (HL),A fills n bytes)
|
||
* LD E,E (0x5B) vertical Fill mode (auto-increments Port_Y)
|
||
* LD B,B (0x40) disable accelerator
|
||
*
|
||
* The block-length byte uses SMC — we patch the immediate at runtime,
|
||
* then run the accel sequence. HOME is RAM after DSS loads us, so
|
||
* SMC inside our own .EXE is safe.
|
||
*/
|
||
|
||
#include <gfx.h>
|
||
#include <stdint.h>
|
||
|
||
extern uint16_t _gfx_addr_base;
|
||
|
||
/* ---- Scratch shared across asm helpers --------------------------- *
|
||
* Single-threaded — no reentrancy. GFX runs with interrupts off
|
||
* between _gfx_w3_video_begin and _gfx_w3_video_end. */
|
||
static uint8_t acc_color;
|
||
static uint8_t acc_y;
|
||
static uint8_t acc_len; /* 0 means 256 — the accel convention */
|
||
static uint16_t acc_addr;
|
||
|
||
/* Putpixel scratch — separate from acc_* so a putpixel inside a
|
||
* Bresenham loop doesn't trample on outer accel state. */
|
||
static uint8_t _gfx_pp_y;
|
||
static uint16_t _gfx_pp_addr;
|
||
static uint8_t _gfx_pp_color;
|
||
|
||
/* ---- inner accel bursts ------------------------------------------ *
|
||
*
|
||
* Pre: W3 mapped to the video bank, DI active. Caller wraps a sequence
|
||
* of bursts in a single begin/end pair. */
|
||
|
||
/* Horizontal fill: acc_len bytes at acc_addr on row acc_y. */
|
||
static void hfill_chunk(void) __naked
|
||
{
|
||
__asm
|
||
;; Patch the LD A,#n immediate (operand byte) with acc_len.
|
||
ld a, (_acc_len)
|
||
ld (_hfill_len_imm), a
|
||
|
||
ld a, (_acc_y)
|
||
out (#0x89), a ; Port_Y = y
|
||
|
||
;; Pre-load colour into C and dest into HL before arming accel.
|
||
ld a, (_acc_color)
|
||
ld c, a
|
||
ld hl, (_acc_addr)
|
||
|
||
;; --- ACCEL SEQUENCE ---
|
||
ld d, d ; 0x52 — set block size mode
|
||
ld a, #0 ; 0x3E nn — block size (nn patched above)
|
||
_hfill_len_imm = . - 1
|
||
ld c, c ; 0x49 — horizontal Fill
|
||
ld a, c ; 0x79 — A = colour (NOT another ld a,#n)
|
||
ld (hl), a ; fires accel; fills acc_len bytes
|
||
ld b, b ; 0x40 — disable
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* Vertical fill: acc_len pixels at column acc_addr, top row acc_y.
|
||
* The accel auto-increments Port_Y as it paints down the column. */
|
||
static void vfill_chunk(void) __naked
|
||
{
|
||
__asm
|
||
ld a, (_acc_len)
|
||
ld (_vfill_len_imm), a
|
||
|
||
ld a, (_acc_y)
|
||
out (#0x89), a ; starting Y
|
||
|
||
ld a, (_acc_color)
|
||
ld c, a
|
||
ld hl, (_acc_addr)
|
||
|
||
ld d, d ; 0x52 — set block size
|
||
ld a, #0 ; immediate length (patched)
|
||
_vfill_len_imm = . - 1
|
||
ld e, e ; 0x5B — vertical Fill
|
||
ld a, c ; A = colour
|
||
ld (hl), a ; fires accel
|
||
ld b, b ; 0x40 — disable
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* ---- Raw primitives (W3-naive, composable) ----------------------- *
|
||
*
|
||
* These do NO DI/W3 setup — caller wraps a sequence with one
|
||
* _gfx_w3_video_begin / _gfx_w3_video_end pair. */
|
||
|
||
void _gfx_putpixel256_raw(int x, int y, uint8_t color)
|
||
{
|
||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||
_gfx_pp_y = (uint8_t)y;
|
||
_gfx_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||
_gfx_pp_color = color;
|
||
__asm
|
||
ld a, (__gfx_pp_y)
|
||
out (#0x89), a
|
||
ld hl, (__gfx_pp_addr)
|
||
ld a, (__gfx_pp_color)
|
||
ld (hl), a
|
||
__endasm;
|
||
}
|
||
|
||
void _gfx_hline256_raw(int x, int y, int len, uint8_t color)
|
||
{
|
||
if ((unsigned)y >= GFX_HEIGHT) return;
|
||
if (x < 0) { len += x; x = 0; }
|
||
if (x >= GFX_WIDTH) return;
|
||
if (x + len > GFX_WIDTH) len = GFX_WIDTH - x;
|
||
if (len <= 0) return;
|
||
|
||
acc_color = color;
|
||
acc_y = (uint8_t)y;
|
||
acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||
|
||
while (len > 0) {
|
||
int chunk = len > 256 ? 256 : len;
|
||
acc_len = (chunk == 256) ? 0 : (uint8_t)chunk;
|
||
hfill_chunk();
|
||
len -= chunk;
|
||
acc_addr += chunk;
|
||
}
|
||
}
|
||
|
||
void _gfx_vline256_raw(int x, int y, int len, uint8_t color)
|
||
{
|
||
if ((unsigned)x >= GFX_WIDTH) return;
|
||
if (y < 0) { len += y; y = 0; }
|
||
if (y >= GFX_HEIGHT) return;
|
||
if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y;
|
||
if (len <= 0) return;
|
||
|
||
/* GFX_HEIGHT = 256 so a full column is a single accel burst. */
|
||
acc_color = color;
|
||
acc_y = (uint8_t)y;
|
||
acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||
acc_len = (len == 256) ? 0 : (uint8_t)len;
|
||
vfill_chunk();
|
||
}
|
||
|
||
/* Clear the whole 320×256 area with `color`. Row-major hfill:
|
||
* 256 rows × 2 bursts each (256-byte + 64-byte) = 512 bursts. */
|
||
void _gfx_clear256_raw(uint8_t color)
|
||
{
|
||
acc_color = color;
|
||
for (int y = 0; y < GFX_HEIGHT; y++) {
|
||
acc_y = (uint8_t)y;
|
||
acc_addr = _gfx_addr_base; /* 256-byte burst */
|
||
acc_len = 0;
|
||
hfill_chunk();
|
||
acc_addr = (uint16_t)(_gfx_addr_base + 256); /* 64-byte burst */
|
||
acc_len = 64;
|
||
hfill_chunk();
|
||
}
|
||
}
|