Files
snark13 c71e249a4e Add full compiler toolchain, libc, examples and reference docs
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>
2026-06-03 16:13:21 +03:00

187 lines
5.7 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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();
}
}