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>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* gfx_16.c — 16-colour (mode 0x82) public drawing API.
|
||||
*
|
||||
* Raw primitives live in gfx_raw_16.c. Single-shot wrappers do one
|
||||
* W3-begin / one W3-end around their raw call; composites wrap many
|
||||
* raw calls in a single begin/end pair so the W3 dance is amortised.
|
||||
*
|
||||
* No vertical-accelerator path: in 16-colour mode each byte spans two
|
||||
* horizontal pixels, so a vertical Fill burst would also affect the
|
||||
* other column's nibble — vline16 falls back to per-row RMW.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern void _gfx_w3_video_begin(void);
|
||||
extern void _gfx_w3_video_end(void);
|
||||
|
||||
extern void _gfx_putpixel16_raw(int x, int y, uint8_t color);
|
||||
extern void _gfx_hline16_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_vline16_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_clear16_raw (uint8_t color);
|
||||
|
||||
void gfx_clear16(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear16_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_putpixel16(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_putpixel16_raw(x, y, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_hline16(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline16_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_vline16(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_vline16_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_rect16(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline16_raw(x, y, w, color);
|
||||
_gfx_hline16_raw(x, y + h - 1, w, color);
|
||||
if (h > 2) {
|
||||
_gfx_vline16_raw(x, y + 1, h - 2, color);
|
||||
_gfx_vline16_raw(x + w - 1, y + 1, h - 2, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
/* Column-major vlines — user's earlier request: 16-color rect_fill
|
||||
* via vertical lines. Each column does its own per-row RMW; the
|
||||
* outer begin/end wraps the whole rect so W3 is mapped once. */
|
||||
_gfx_w3_video_begin();
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
_gfx_vline16_raw(x + xx, y, h, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_line16(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
if (y0 == y1) {
|
||||
int x = x0 <= x1 ? x0 : x1;
|
||||
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
|
||||
gfx_hline16(x, y0, w, color);
|
||||
return;
|
||||
}
|
||||
if (x0 == x1) {
|
||||
int y = y0 <= y1 ? y0 : y1;
|
||||
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
|
||||
gfx_vline16(x0, y, h, color);
|
||||
return;
|
||||
}
|
||||
/* Bresenham — single W3 setup around the whole loop. */
|
||||
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
|
||||
int err = (dx > dy ? dx : -dy) / 2;
|
||||
int x = x0, y = y0;
|
||||
_gfx_w3_video_begin();
|
||||
for (;;) {
|
||||
_gfx_putpixel16_raw(x, y, color);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x += sx; }
|
||||
if (e2 < dy) { err += dx; y += sy; }
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* gfx_256.c — 256-colour (mode 0x81) public drawing API.
|
||||
*
|
||||
* Single-shot wrappers do one W3-begin / one W3-end around their raw
|
||||
* call. Composites (rect, fill_rect, line) wrap a single begin/end
|
||||
* around many raw calls so the W3 dance is paid once per operation,
|
||||
* not once per pixel/byte.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* From gfx_raw_common.c — DI + map _gfx_bank into W3, save previous. */
|
||||
extern void _gfx_w3_video_begin(void);
|
||||
extern void _gfx_w3_video_end(void);
|
||||
|
||||
/* From gfx_raw_256.c — W3-naive primitives. */
|
||||
extern void _gfx_putpixel256_raw(int x, int y, uint8_t color);
|
||||
extern void _gfx_hline256_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_vline256_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_clear256_raw (uint8_t color);
|
||||
|
||||
void gfx_clear256(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear256_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_putpixel256(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_putpixel256_raw(x, y, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_hline256(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline256_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_vline256(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_vline256_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_rect256(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline256_raw(x, y, w, color);
|
||||
_gfx_hline256_raw(x, y + h - 1, w, color);
|
||||
if (h > 2) {
|
||||
_gfx_vline256_raw(x, y + 1, h - 2, color);
|
||||
_gfx_vline256_raw(x + w - 1, y + 1, h - 2, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
/* Pick the orientation with fewer accelerator bursts. Each burst
|
||||
* paints up to 256 contiguous bytes (horizontal) or up to 256
|
||||
* vertical pixels in one column.
|
||||
* row-major (hlines): h × ceil(w/256) bursts
|
||||
* col-major (vlines): w × ceil(h/256) bursts — for h ≤ 256 = w
|
||||
* Vertical wins for tall-narrow rects; horizontal for short-wide. */
|
||||
int h_bursts = h * ((w + 255) >> 8);
|
||||
int v_bursts = w * ((h + 255) >> 8);
|
||||
_gfx_w3_video_begin();
|
||||
if (h_bursts <= v_bursts) {
|
||||
for (int yy = 0; yy < h; yy++)
|
||||
_gfx_hline256_raw(x, y + yy, w, color);
|
||||
} else {
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
_gfx_vline256_raw(x + xx, y, h, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_line256(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
/* Orthogonal lines route to the accelerator. */
|
||||
if (y0 == y1) {
|
||||
int x = x0 <= x1 ? x0 : x1;
|
||||
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
|
||||
gfx_hline256(x, y0, w, color);
|
||||
return;
|
||||
}
|
||||
if (x0 == x1) {
|
||||
int y = y0 <= y1 ? y0 : y1;
|
||||
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
|
||||
gfx_vline256(x0, y, h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Bresenham — single W3-setup around the whole loop. */
|
||||
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
|
||||
int err = (dx > dy ? dx : -dy) / 2;
|
||||
int x = x0, y = y0;
|
||||
_gfx_w3_video_begin();
|
||||
for (;;) {
|
||||
_gfx_putpixel256_raw(x, y, color);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x += sx; }
|
||||
if (e2 < dy) { err += dx; y += sy; }
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* gfx_font.c — bitmap-font management shared by the 256- and 16-colour
|
||||
* text renderers.
|
||||
*
|
||||
* Format (ZX-Spectrum-compatible): 256 glyphs × 8 rows × 1 byte = 2 KB,
|
||||
* INTERLEAVED row-major —
|
||||
* offset = row * 256 + char_code
|
||||
* so row 0 of every glyph occupies bytes 0x000..0x0FF, row 1 occupies
|
||||
* 0x100..0x1FF, etc. Each row byte is MSB-first (bit 7 = leftmost px).
|
||||
*
|
||||
* The default source is BIOS WIN_GET_ZG (fn 0xB8) — the active system
|
||||
* character generator. Programs may override with gfx_set_font().
|
||||
*
|
||||
* Lazy initialisation: gfx_text_256.c / gfx_text_16.c call _gfx_font_ensure
|
||||
* on first use so a pure-graphics program doesn't pay the BIOS call.
|
||||
*
|
||||
* The font pointer and the buffer are exported to the two text renderers
|
||||
* via _gfx_font_ptr (extern), which always points at valid data once
|
||||
* _gfx_font_ensure has been called.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define FONT_BYTES 2048
|
||||
|
||||
static uint8_t _gfx_font_buf[FONT_BYTES];
|
||||
const uint8_t *_gfx_font_ptr = _gfx_font_buf;
|
||||
static uint8_t _gfx_font_loaded = 0;
|
||||
|
||||
/* BIOS WIN_GET_ZG (0xB8): DE = destination, returns 2 KB. */
|
||||
static void bios_get_zg(uint8_t *dest) __naked
|
||||
{
|
||||
(void)dest;
|
||||
__asm
|
||||
push ix ; BIOS clobbers IX
|
||||
ex de, hl ; DE = dest (HL was arg)
|
||||
xor a ; font 0 (default)
|
||||
ld c, #0xB8
|
||||
rst #0x08
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void gfx_load_default_font(void)
|
||||
{
|
||||
bios_get_zg(_gfx_font_buf);
|
||||
_gfx_font_ptr = _gfx_font_buf;
|
||||
_gfx_font_loaded = 1;
|
||||
}
|
||||
|
||||
void gfx_set_font(const uint8_t *font)
|
||||
{
|
||||
_gfx_font_ptr = font;
|
||||
_gfx_font_loaded = 1;
|
||||
}
|
||||
|
||||
/* Called from text renderers — loads the default font on first use. */
|
||||
void _gfx_font_ensure(void)
|
||||
{
|
||||
if (!_gfx_font_loaded) gfx_load_default_font();
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* gfx_raw_common.c — W3 page mapping primitives shared by every gfx mode.
|
||||
*
|
||||
* Composite primitives (gfx_line / gfx_rect / gfx_fill_rect / ...) bracket
|
||||
* their inner loop with one `_gfx_w3_video_begin()` / `_gfx_w3_video_end()`
|
||||
* pair and call the `*_raw` variants inside, so the W3 dance is paid once
|
||||
* per drawing operation instead of once per pixel. Single-shot wrappers
|
||||
* (gfx_putpixel, gfx_hline, ...) wrap the same way for their single call.
|
||||
*
|
||||
* Begin disables interrupts, saves the current W3 page byte, then maps
|
||||
* `_gfx_bank` (the current video bank, 0x50..0x5F). End restores the
|
||||
* saved page and re-enables interrupts.
|
||||
*
|
||||
* NOT re-entrant — saving the previous W3 byte in a static is safe only
|
||||
* because GFX runs with interrupts off between begin and end.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint8_t _gfx_bank; /* current W3 video bank (gfx_core.c) */
|
||||
|
||||
static uint8_t _gfx_saved_w3;
|
||||
|
||||
void _gfx_w3_video_begin(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
ld (__gfx_saved_w3), a
|
||||
ld a, (__gfx_bank)
|
||||
out (#0xE2), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void _gfx_w3_video_end(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_saved_w3)
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* gfx_text_16.c — bitmap-font text rendering for mode 0x82
|
||||
* (640×256×16, 4 bits per pixel).
|
||||
*
|
||||
* Per glyph row (8 pixels): emit 4 bytes — each byte packs two
|
||||
* adjacent pixels as (LEFT<<4) | RIGHT. A precomputed 4-entry pair
|
||||
* table maps the 2-bit source pattern (LEFT*2 + RIGHT) directly to
|
||||
* the output byte, avoiding per-pixel mask/OR.
|
||||
*
|
||||
* Currently requires byte-aligned x (x must be even); odd x is
|
||||
* silently ignored. Text grids are typically aligned.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint16_t _gfx_addr_base;
|
||||
extern const uint8_t *_gfx_font_ptr;
|
||||
extern void _gfx_font_ensure(void);
|
||||
|
||||
static uint8_t txt_y;
|
||||
static uint8_t txt_row;
|
||||
static uint16_t txt_addr;
|
||||
|
||||
/* 4-byte lookup: index = LEFT*2 + RIGHT, value = (LEFT<<4)|RIGHT. */
|
||||
static uint8_t txt_pair[4];
|
||||
|
||||
/* Render one 8-pixel row using the pair table — 4 byte writes. */
|
||||
static void render_row_16(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
push af
|
||||
ld a, #0x50
|
||||
out (#0xE2), a
|
||||
ld a, (_txt_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (_txt_addr)
|
||||
ld a, (_txt_row)
|
||||
ld c, a ; C = rotating source bits
|
||||
ld b, #4 ; 4 output bytes
|
||||
rr16_loop:
|
||||
;; Extract two top bits of C as index A = LEFT*2 + RIGHT.
|
||||
sla c ; CY = LEFT bit; C <<= 1
|
||||
ld a, #0
|
||||
adc a, a ; A = LEFT
|
||||
sla c ; CY = RIGHT bit; C <<= 1
|
||||
adc a, a ; A = LEFT*2 + RIGHT (0..3)
|
||||
|
||||
;; A = txt_pair[A]; preserve HL across the lookup.
|
||||
push hl
|
||||
ld e, a
|
||||
ld d, #0
|
||||
ld hl, #_txt_pair
|
||||
add hl, de
|
||||
ld a, (hl)
|
||||
pop hl
|
||||
|
||||
ld (hl), a
|
||||
inc hl
|
||||
djnz rr16_loop
|
||||
|
||||
pop af
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
static void build_pair_table(uint8_t fg, uint8_t bg)
|
||||
{
|
||||
uint8_t f = fg & 0x0F;
|
||||
uint8_t b = bg & 0x0F;
|
||||
/* HIGH nibble = LEFT, LOW nibble = RIGHT (sprinter_graphics convention). */
|
||||
txt_pair[0] = (uint8_t)((b << 4) | b); /* 00 BG BG */
|
||||
txt_pair[1] = (uint8_t)((b << 4) | f); /* 01 BG FG */
|
||||
txt_pair[2] = (uint8_t)((f << 4) | b); /* 10 FG BG */
|
||||
txt_pair[3] = (uint8_t)((f << 4) | f); /* 11 FG FG */
|
||||
}
|
||||
|
||||
void gfx_putchar16(int x, int y, char c, uint8_t fg, uint8_t bg)
|
||||
{
|
||||
_gfx_font_ensure();
|
||||
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
|
||||
if (x & 1) return;
|
||||
|
||||
build_pair_table(fg, bg);
|
||||
uint8_t cc = (uint8_t)c;
|
||||
uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
|
||||
for (int r = 0; r < 8; r++) {
|
||||
int yy = y + r;
|
||||
if ((unsigned)yy >= GFX_HEIGHT_16) break;
|
||||
txt_y = (uint8_t)yy;
|
||||
txt_addr = base;
|
||||
txt_row = _gfx_font_ptr[r * 256 + cc];
|
||||
render_row_16();
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_text16(int x, int y, const char *s, uint8_t fg, uint8_t bg)
|
||||
{
|
||||
/* Build pair table once for the whole string (FG/BG don't change). */
|
||||
_gfx_font_ensure();
|
||||
if (x & 1) return;
|
||||
build_pair_table(fg, bg);
|
||||
for (; *s; s++) {
|
||||
if (x >= GFX_WIDTH_16) break;
|
||||
if ((unsigned)y >= GFX_HEIGHT_16) break;
|
||||
uint8_t cc = (uint8_t)*s;
|
||||
uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
for (int r = 0; r < 8; r++) {
|
||||
int yy = y + r;
|
||||
if ((unsigned)yy >= GFX_HEIGHT_16) break;
|
||||
txt_y = (uint8_t)yy;
|
||||
txt_addr = base;
|
||||
txt_row = _gfx_font_ptr[r * 256 + cc];
|
||||
render_row_16();
|
||||
}
|
||||
x += 8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* gfx_text_256.c — bitmap-font text rendering for mode 0x81
|
||||
* (320×256×256, one byte per pixel).
|
||||
*
|
||||
* Per glyph row (8 pixels): emit 8 bytes — FG where the source bit is
|
||||
* 1, BG where 0. Port_Y is set once per row; the row writer wraps its
|
||||
* own DI / W3 save+restore so callers don't need to.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint16_t _gfx_addr_base;
|
||||
extern const uint8_t *_gfx_font_ptr;
|
||||
extern void _gfx_font_ensure(void);
|
||||
|
||||
/* ---- Scratch shared with the asm row writer ---------------------- */
|
||||
static uint8_t txt_y;
|
||||
static uint8_t txt_row; /* current glyph-row bit pattern */
|
||||
static uint8_t txt_fg;
|
||||
static uint8_t txt_bg;
|
||||
static uint16_t txt_addr; /* VRAM address for this row */
|
||||
|
||||
/* Render one 8-pixel row. Self-contained DI / W3 save+restore so a
|
||||
* gfx_text() can be called from any context without extra ceremony. */
|
||||
static void render_row_256(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
push af
|
||||
ld a, #0x50
|
||||
out (#0xE2), a
|
||||
ld a, (_txt_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (_txt_addr)
|
||||
ld a, (_txt_fg)
|
||||
ld d, a
|
||||
ld a, (_txt_bg)
|
||||
ld e, a
|
||||
ld a, (_txt_row)
|
||||
ld b, #8 ; 8 pixels in this row
|
||||
rr256_loop:
|
||||
rla ; CY = MSB, A <<= 1
|
||||
jr nc, rr256_bg
|
||||
ld (hl), d ; FG
|
||||
jr rr256_next
|
||||
rr256_bg:
|
||||
ld (hl), e ; BG
|
||||
rr256_next:
|
||||
inc hl
|
||||
djnz rr256_loop
|
||||
|
||||
pop af
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void gfx_putchar256(int x, int y, char c, uint8_t fg, uint8_t bg)
|
||||
{
|
||||
_gfx_font_ensure();
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
|
||||
txt_fg = fg;
|
||||
txt_bg = bg;
|
||||
uint8_t cc = (uint8_t)c;
|
||||
uint16_t base = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
|
||||
for (int r = 0; r < 8; r++) {
|
||||
int yy = y + r;
|
||||
if ((unsigned)yy >= GFX_HEIGHT) break;
|
||||
txt_y = (uint8_t)yy;
|
||||
txt_addr = base;
|
||||
/* Interleaved layout: row r of char cc lives at font[r*256 + cc]. */
|
||||
txt_row = _gfx_font_ptr[r * 256 + cc];
|
||||
render_row_256();
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_text256(int x, int y, const char *s, uint8_t fg, uint8_t bg)
|
||||
{
|
||||
for (; *s; s++) {
|
||||
if (x >= GFX_WIDTH) break;
|
||||
gfx_putchar256(x, y, *s, fg, bg);
|
||||
x += 8;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user