libc: сплит «1 функция = 1 модуль» — вся библиотека, wildcard-сборка

- bios/conio/env/errno/gfx/io/mem/mouse/stdio/stdlib/string/sys/time/
  video разложены по модулям: общие статики и helpers — в internal
  _-модулях (_conio.h/_mouse.h/_gfx.h/_palette.h/_atexit.h/_time.h)
- lib/Makefile: LIBC_C = wildcard libc/*/*.c — гранулярность файлов
  = гранулярность DCE линкера
- эффект _CODE: gfx_text 6986→2568 Б, gfx_mous −1745, gfx_demo/d16
  −542; ранее timedir −3270, ls −3098, stattest −2995
- комментарии оставшихся модулей переведены на русский; puts: убран
  мёртвый pchars; videomode_raw разложен на get/set
- docs/libc-split-asm-cases.md — правила asm-связок между модулями;
  docs/libc-roadmap.md — план этапа
- восстановлен examples/mdview/SAMPLE.MD (нужен make floppy)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 16:08:58 +03:00
parent 46553f4e07
commit 4a081501d8
237 changed files with 4830 additions and 3933 deletions
+109
View File
@@ -0,0 +1,109 @@
/*
* _gfx.h — внутренности графической подсистемы (НЕ публичный заголовок).
*
* Общее состояние лежит в data-модулях (_gfx_state.c, _gfx_w3_state.c,
* _gfx_acc256.c, _gfx_g16_state.c, _gfx_font_state.c), helpers — в
* отдельных internal-модулях; см. docs/libc-split-asm-cases.md.
*/
#ifndef _GFX_INTERNAL_H
#define _GFX_INTERNAL_H
#include <gfx.h>
/* ---- Общее состояние (_gfx_state.c) ------------------------------ */
/* Кэш SELPAGE: видимая и рисуемая страницы (0/1). */
extern uint8_t _gfx_visible_page;
extern uint8_t _gfx_draw_page;
/* Байт страницы W3 (0x50..0x5F) — какой видеобанк мапится при записи. */
extern uint8_t _gfx_bank;
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
extern uint16_t _gfx_addr_base;
/* ---- Низкоуровневый видеорежим (video/videomode_raw.c) ----------- */
uint8_t _videomode_raw_get(void);
int _videomode_raw_set(uint8_t mode);
/* ---- W3-скобки (_gfx_w3_video_begin/end.c, _gfx_w3_state.c) ------ *
* begin: DI + сохранить текущий W3 и замапить _gfx_bank;
* end: восстановить W3 + EI. Не реентерабельно (сейв в статике). */
extern uint8_t _gfx_saved_w3;
void _gfx_w3_video_begin(void);
void _gfx_w3_video_end(void);
/* ---- Raw-примитивы 256 цветов (mode 0x81) ------------------------- *
* "Raw" = W3-naive: вызывающий обязан обернуть последовательность в
* одну пару _gfx_w3_video_begin/_gfx_w3_video_end. */
void _gfx_putpixel256_raw(int x, int y, uint8_t color);
void _gfx_hline256_raw (int x, int y, int len, uint8_t color);
void _gfx_vline256_raw (int x, int y, int len, uint8_t color);
void _gfx_clear256_raw (uint8_t color);
/* Скретч акселератора (_gfx_acc256.c) — общий для hline/vline/clear;
* однопоточно, IRQ выключены между begin/end. */
extern uint8_t _gfx_acc_color;
extern uint8_t _gfx_acc_y;
extern uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
extern uint16_t _gfx_acc_addr;
/* Горизонтальный Fill-burst: _gfx_acc_len байт с _gfx_acc_addr на
* строке _gfx_acc_y (W3 замаплен, DI активен). */
void _gfx_hfill256_chunk(void);
/* ---- Raw-примитивы 16 цветов (mode 0x82) -------------------------- */
void _gfx_putpixel16_raw(int x, int y, uint8_t color);
void _gfx_hline16_raw (int x, int y, int len, uint8_t color);
void _gfx_vline16_raw (int x, int y, int len, uint8_t color);
void _gfx_clear16_raw (uint8_t color);
/* Скретч 16-цветных примитивов (_gfx_g16_state.c). */
extern uint8_t _gfx_g16_y;
extern uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) */
extern uint8_t _gfx_g16_nibble; /* цвет в нужной половине байта */
extern uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
extern uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
extern uint16_t _gfx_g16_addr;
/* Горизонтальный Fill-burst байтами _gfx_g16_byte (_gfx_hfill16.c). */
void _gfx_hfill16_chunk(void);
/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (_gfx_rmw16.c). */
void _gfx_rmw16_pixel(void);
/* ---- Шрифт (_gfx_font_state.c, _gfx_font_ensure.c) ---------------- *
* Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char;
* биты MSB-first. См. memory/sprinter_font_format. */
#define _GFX_FONT_BYTES 2048
extern uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
extern const uint8_t *_gfx_font_ptr;
extern uint8_t _gfx_font_loaded;
/* Ленивая инициализация: грузит системный шрифт при первом использовании. */
void _gfx_font_ensure(void);
/* ---- Текст 16 цветов: общая машинерия ----------------------------- *
* Данные в _gfx_t16_state.c, рендер ряда в _gfx_render_row16.c,
* pair-таблица в _gfx_t16_pair_build.c. */
extern uint8_t _gfx_t16_y;
extern uint8_t _gfx_t16_row; /* битовый паттерн ряда глифа */
extern uint16_t _gfx_t16_addr; /* VRAM-адрес ряда */
extern uint8_t _gfx_t16_pair[4]; /* LEFT*2+RIGHT → (LEFT<<4)|RIGHT */
/* Отрисовать один 8-пиксельный ряд по pair-таблице (4 записи байт). */
void _gfx_render_row16(void);
/* Заполнить pair-таблицу для пары цветов fg/bg. */
void _gfx_t16_pair_build(uint8_t fg, uint8_t bg);
#endif
+12
View File
@@ -0,0 +1,12 @@
/*
* _gfx_acc256 — скретч акселератора для 256-цветных raw-примитивов
* (модуль только с данными). Общий для hline/vline/clear; однопоточно —
* GFX работает с выключенными прерываниями между W3-begin/end.
*/
#include "_gfx.h"
uint8_t _gfx_acc_color;
uint8_t _gfx_acc_y;
uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
uint16_t _gfx_acc_addr;
+20
View File
@@ -0,0 +1,20 @@
/*
* _gfx_clear16_raw — залить всё поле 640×256 цветом color, mode 0x82.
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта). W3-naive.
*/
#include "_gfx.h"
void _gfx_clear16_raw(uint8_t color)
{
_gfx_g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4));
for (int y = 0; y < GFX_HEIGHT_16; y++) {
_gfx_g16_y = (uint8_t)y;
_gfx_g16_addr = _gfx_addr_base;
_gfx_g16_len = 0;
_gfx_hfill16_chunk();
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + 0x100);
_gfx_g16_len = 64;
_gfx_hfill16_chunk();
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
* _gfx_clear256_raw — залить всё поле 320×256 цветом color, mode 0x81.
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта) = 512.
* W3-naive: вызывающий обрамляет begin/end.
*/
#include "_gfx.h"
void _gfx_clear256_raw(uint8_t color)
{
_gfx_acc_color = color;
for (int y = 0; y < GFX_HEIGHT; y++) {
_gfx_acc_y = (uint8_t)y;
_gfx_acc_addr = _gfx_addr_base; /* burst 256 байт */
_gfx_acc_len = 0;
_gfx_hfill256_chunk();
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + 256); /* burst 64 байта */
_gfx_acc_len = 64;
_gfx_hfill256_chunk();
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
* _gfx_font_ensure — ленивая инициализация шрифта: текстовые рендеры
* зовут её при каждом входе, системный шрифт грузится при первом
* использовании (чисто графическая программа BIOS-вызов не платит).
*/
#include "_gfx.h"
void _gfx_font_ensure(void)
{
if (!_gfx_font_loaded) gfx_load_default_font();
}
+16
View File
@@ -0,0 +1,16 @@
/*
* _gfx_font_state — буфер шрифта, указатель и флаг загрузки (данные).
*
* Формат (ZX-Spectrum-совместимый): 256 глифов × 8 рядов × 1 байт = 2 КБ,
* INTERLEAVED — offset = row*256 + char (ряд 0 всех глифов занимает
* 0x000..0x0FF и т.д.), биты MSB-first (бит 7 = левый пиксель).
*
* _gfx_font_ptr всегда указывает на валидные данные после
* _gfx_font_ensure(); программы могут подменить шрифт gfx_set_font().
*/
#include "_gfx.h"
uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
const uint8_t *_gfx_font_ptr = _gfx_font_buf;
uint8_t _gfx_font_loaded;
+14
View File
@@ -0,0 +1,14 @@
/*
* _gfx_g16_state — скретч 16-цветных raw-примитивов (только данные).
* Общий для putpixel/hline/vline/clear; однопоточно, IRQ выключены
* между W3-begin/end.
*/
#include "_gfx.h"
uint8_t _gfx_g16_y;
uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) — цвет в обе половины */
uint8_t _gfx_g16_nibble; /* цвет в нужной половине (low или high) */
uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
uint16_t _gfx_g16_addr;
+38
View File
@@ -0,0 +1,38 @@
/*
* _gfx_hfill16_chunk — горизонтальный Fill-burst акселератора для
* mode 0x82: _gfx_g16_len байт значения _gfx_g16_byte с _gfx_g16_addr
* на строке _gfx_g16_y. Один burst красит до 256 байт = 512 пикселей.
* Pre: W3 замаплен, DI активен.
*
* SMC только у байта длины. Цвет предзагружается в C и уходит через
* `ld a, c` (0x79) — вставить ещё один `ld a,#n` между LD C,C и
* стреляющим LD (HL),A нельзя: FSM акселератора перечитает immediate
* как новый размер блока.
*/
#include "_gfx.h"
void _gfx_hfill16_chunk(void) __naked
{
__asm
ld a, (__gfx_g16_len)
ld (_hfill16_len_imm), a
ld a, (__gfx_g16_byte)
ld c, a
ld a, (__gfx_g16_y)
out (#0x89), a
ld hl, (__gfx_g16_addr)
ld d, d ; 0x52 режим размера блока
ld a, #0 ; 0x3E nn длина (патчится)
_hfill16_len_imm = . - 1
ld c, c ; 0x49 горизонтальный Fill
ld a, c ; 0x79 A = байт цвета
ld (hl), a ; выстрел
ld b, b ; 0x40 выключить
ret
__endasm;
}
+45
View File
@@ -0,0 +1,45 @@
/*
* _gfx_hfill256_chunk — горизонтальный Fill-burst акселератора:
* _gfx_acc_len байт цвета _gfx_acc_color с адреса _gfx_acc_addr на
* строке _gfx_acc_y. Pre: W3 замаплен на видеобанк, DI активен.
*
* Опкоды акселератора (docs/converted/accel_r.txt):
* LD D,D (0x52) режим «задать размер блока»; СЛЕДУЮЩИЙ байт — длина,
* ОБЯЗАН быть immediate-операндом LD A,n (0x3E)
* LD C,C (0x49) горизонтальный Fill (LD (HL),A заливает n байт)
* LD B,B (0x40) выключить акселератор
*
* Байт длины патчится SMC — HOME после загрузки DSS это RAM, патчить
* собственный .EXE безопасно. Вставлять другой `ld a,#n` между LD C,C
* и стреляющим LD (HL),A нельзя — FSM акселератора перечитает immediate
* как новый размер блока.
*/
#include "_gfx.h"
void _gfx_hfill256_chunk(void) __naked
{
__asm
;; Пропатчить immediate LD A,#n байтом длины.
ld a, (__gfx_acc_len)
ld (_hfill256_len_imm), a
ld a, (__gfx_acc_y)
out (#0x89), a ; Port_Y = y
;; Цвет в C и адрес в HL готовим ДО взведения акселератора.
ld a, (__gfx_acc_color)
ld c, a
ld hl, (__gfx_acc_addr)
;; --- ПОСЛЕДОВАТЕЛЬНОСТЬ АКСЕЛЕРАТОРА ---
ld d, d ; 0x52 режим размера блока
ld a, #0 ; 0x3E nn размер (nn патчится выше)
_hfill256_len_imm = . - 1
ld c, c ; 0x49 горизонтальный Fill
ld a, c ; 0x79 A = цвет (НЕ ещё один ld a,#n)
ld (hl), a ; выстрел; заливает _gfx_acc_len байт
ld b, b ; 0x40 выключить
ret
__endasm;
}
+50
View File
@@ -0,0 +1,50 @@
/*
* _gfx_hline16_raw — горизонтальная линия len пикселей от (x,y),
* mode 0x82: невыровненные крайние ниблы через RMW, середина —
* Fill-burst'ами акселератора по целым байтам. W3-naive.
*/
#include "_gfx.h"
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;
_gfx_g16_y = (uint8_t)y;
uint8_t cnib = color & 0x0F;
_gfx_g16_byte = (uint8_t)(cnib | (cnib << 4));
/* Ведущий невыровненный пиксель: x нечётный — ПРАВАЯ половина
* крайнего левого байта. */
if (x & 1) {
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
_gfx_g16_nibble = cnib;
_gfx_g16_mask = 0xF0;
_gfx_rmw16_pixel();
x++;
len--;
if (len <= 0) return;
}
/* x чётный; len/2 целых байтов через accel hfill. */
int full = len >> 1;
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
while (full > 0) {
int chunk = full > 256 ? 256 : full;
_gfx_g16_len = (chunk == 256) ? 0 : (uint8_t)chunk;
_gfx_hfill16_chunk();
full -= chunk;
_gfx_g16_addr += chunk;
}
/* Хвостовой пиксель нечётной длины: ЛЕВАЯ половина след. байта. */
if (len & 1) {
_gfx_g16_nibble = (uint8_t)(cnib << 4);
_gfx_g16_mask = 0x0F;
_gfx_rmw16_pixel();
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* _gfx_hline256_raw — горизонтальная линия len пикселей от (x,y),
* mode 0x81, через Fill-burst'ы акселератора (до 256 байт за выстрел).
* W3-naive: вызывающий обрамляет begin/end.
*/
#include "_gfx.h"
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;
_gfx_acc_color = color;
_gfx_acc_y = (uint8_t)y;
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
while (len > 0) {
int chunk = len > 256 ? 256 : len;
_gfx_acc_len = (chunk == 256) ? 0 : (uint8_t)chunk;
_gfx_hfill256_chunk();
len -= chunk;
_gfx_acc_addr += chunk;
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* _gfx_putpixel16_raw — пиксель (x,y) цветом 0..15, mode 0x82.
* Байт по адресу _gfx_addr_base + x/2 держит два пикселя: high nibble =
* ЛЕВЫЙ (чётный x), low = ПРАВЫЙ (нечётный). W3-naive.
*/
#include "_gfx.h"
void _gfx_putpixel16_raw(int x, int y, uint8_t color)
{
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
_gfx_g16_y = (uint8_t)y;
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
if (x & 1) {
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
_gfx_g16_mask = 0xF0;
} else {
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
_gfx_g16_mask = 0x0F;
}
_gfx_rmw16_pixel();
}
+30
View File
@@ -0,0 +1,30 @@
/*
* _gfx_putpixel256_raw — пиксель (x,y) цветом color, mode 0x81.
*
* "Raw" = W3-naive: вызывающий уже сделал _gfx_w3_video_begin (W3 на
* видеобанке, DI). Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y.
*
* Скретч отдельный от _gfx_acc_* — чтобы putpixel внутри цикла
* Брезенхэма не топтал внешнее состояние акселератора.
*/
#include "_gfx.h"
static uint8_t _gfx_pp_y;
static uint16_t _gfx_pp_addr;
static uint8_t _gfx_pp_color;
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;
}
+53
View File
@@ -0,0 +1,53 @@
/*
* _gfx_render_row16 — один 8-пиксельный ряд глифа в mode 0x82
* (640×256×16): 4 выходных байта, каждый пакует два соседних пикселя
* как (LEFT<<4) | RIGHT через прекомпилированную _gfx_t16_pair —
* 2-битный паттерн источника (LEFT*2 + RIGHT) сразу даёт выходной
* байт, без mask/OR на пиксель. Сам делает DI / W3 save+restore —
* вызывать можно из любого контекста без скобок.
*/
#include "_gfx.h"
void _gfx_render_row16(void) __naked
{
__asm
di
in a, (#0xE2)
push af
ld a, #0x50
out (#0xE2), a
ld a, (__gfx_t16_y)
out (#0x89), a
ld hl, (__gfx_t16_addr)
ld a, (__gfx_t16_row)
ld c, a ; C = сдвигаемые биты источника
ld b, #4 ; 4 выходных байта
rr16_loop:
;; Два верхних бита C в индекс A = LEFT*2 + RIGHT.
sla c ; CY = бит LEFT; C <<= 1
ld a, #0
adc a, a ; A = LEFT
sla c ; CY = бит RIGHT; C <<= 1
adc a, a ; A = LEFT*2 + RIGHT (0..3)
;; A = _gfx_t16_pair[A]; HL сохраняем через стек.
push hl
ld e, a
ld d, #0
ld hl, #__gfx_t16_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;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* _gfx_rmw16_pixel — RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y):
* прочитать байт, стереть целевой нибл маской _gfx_g16_mask, вписать
* _gfx_g16_nibble. ~10 инструкций на пиксель — медленно, но байт
* покрывает два горизонтальных пикселя, иначе никак.
* Pre: W3 замаплен, DI активен.
*/
#include "_gfx.h"
void _gfx_rmw16_pixel(void) __naked
{
__asm
ld a, (__gfx_g16_y)
out (#0x89), a
ld hl, (__gfx_g16_addr)
ld a, (__gfx_g16_mask)
ld b, a ; B = маска сохранения
ld a, (__gfx_g16_nibble)
ld c, a ; C = новый нибл (в нужной половине)
ld a, (hl)
and a, b ; стереть целевой нибл
or a, c ; вписать новый
ld (hl), a
ret
__endasm;
}
+20
View File
@@ -0,0 +1,20 @@
/*
* _gfx_state — общее состояние графики (модуль только с данными).
*
* Пишут gfx_init / gfx_set_visible_page / gfx_set_draw_page /
* gfx_set_bank; читают все mode-специфичные примитивы.
*/
#include "_gfx.h"
uint8_t _gfx_visible_page;
uint8_t _gfx_draw_page;
/* См. memory/sprinter_vram_transparency.md про биты 0x50..0x5F
* (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба). */
uint8_t _gfx_bank = 0x50;
/* Каждая VRAM-строка — 1024 байта: page 0 занимает байты 0..319
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
* режима/палитра, их не трогаем. */
uint16_t _gfx_addr_base = 0xC000;
+17
View File
@@ -0,0 +1,17 @@
/*
* _gfx_t16_pair_build — заполнить pair-таблицу текстового рендера
* mode 0x82 для пары цветов fg/bg.
*/
#include "_gfx.h"
void _gfx_t16_pair_build(uint8_t fg, uint8_t bg)
{
uint8_t f = fg & 0x0F;
uint8_t b = bg & 0x0F;
/* HIGH nibble = LEFT, LOW = RIGHT (конвенция sprinter_graphics). */
_gfx_t16_pair[0] = (uint8_t)((b << 4) | b); /* 00 BG BG */
_gfx_t16_pair[1] = (uint8_t)((b << 4) | f); /* 01 BG FG */
_gfx_t16_pair[2] = (uint8_t)((f << 4) | b); /* 10 FG BG */
_gfx_t16_pair[3] = (uint8_t)((f << 4) | f); /* 11 FG FG */
}
+14
View File
@@ -0,0 +1,14 @@
/*
* _gfx_t16_state — скретч текстового рендера mode 0x82 (только данные).
* Пишут gfx_putchar16/gfx_text16 и _gfx_t16_pair_build, читает
* _gfx_render_row16.
*/
#include "_gfx.h"
uint8_t _gfx_t16_y;
uint8_t _gfx_t16_row;
uint16_t _gfx_t16_addr;
/* index = LEFT*2 + RIGHT, value = (LEFT<<4)|RIGHT. */
uint8_t _gfx_t16_pair[4];
+30
View File
@@ -0,0 +1,30 @@
/*
* _gfx_vline16_raw — вертикальная линия len пикселей от (x,y),
* mode 0x82. Вертикального accel-пути нет: байт покрывает два
* горизонтальных пикселя, Fill затёр бы соседний нибл — поэтому
* RMW на каждую строку. W3-naive.
*/
#include "_gfx.h"
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;
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
if (x & 1) {
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
_gfx_g16_mask = 0xF0;
} else {
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
_gfx_g16_mask = 0x0F;
}
for (int i = 0; i < len; i++) {
_gfx_g16_y = (uint8_t)(y + i);
_gfx_rmw16_pixel();
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* _gfx_vline256_raw — вертикальная линия len пикселей от (x,y),
* mode 0x81. Вертикальный Fill акселератора сам инкрементирует Port_Y,
* так что вся колонка (высота 256) — один burst. W3-naive.
*/
#include "_gfx.h"
/* Вертикальный Fill: _gfx_acc_len пикселей в колонке _gfx_acc_addr,
* верхняя строка _gfx_acc_y. Про SMC и правила FSM акселератора —
* см. _gfx_hfill256.c. */
static void vfill_chunk(void) __naked
{
__asm
ld a, (__gfx_acc_len)
ld (_vfill256_len_imm), a
ld a, (__gfx_acc_y)
out (#0x89), a ; стартовый Y
ld a, (__gfx_acc_color)
ld c, a
ld hl, (__gfx_acc_addr)
ld d, d ; 0x52 режим размера блока
ld a, #0 ; immediate-длина (патчится)
_vfill256_len_imm = . - 1
ld e, e ; 0x5B вертикальный Fill
ld a, c ; A = цвет
ld (hl), a ; выстрел
ld b, b ; 0x40 выключить
ret
__endasm;
}
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, полная колонка — один accel-burst. */
_gfx_acc_color = color;
_gfx_acc_y = (uint8_t)y;
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
_gfx_acc_len = (len == 256) ? 0 : (uint8_t)len;
vfill_chunk();
}
+10
View File
@@ -0,0 +1,10 @@
/*
* _gfx_w3_state — сохранённый байт W3 между begin и end (данные).
*
* НЕ реентерабельно — безопасно только потому, что GFX держит
* прерывания выключенными между _gfx_w3_video_begin и _end.
*/
#include "_gfx.h"
uint8_t _gfx_saved_w3;
+20
View File
@@ -0,0 +1,20 @@
/*
* _gfx_w3_video_begin — DI, сохранить текущую страницу W3 и замапить
* _gfx_bank (видеобанк 0x50..0x5F). Композитные примитивы обрамляют
* одной парой begin/end весь внутренний цикл — W3-танец оплачивается
* один раз на операцию, а не на пиксель.
*/
#include "_gfx.h"
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;
}
+15
View File
@@ -0,0 +1,15 @@
/*
* _gfx_w3_video_end — восстановить сохранённую страницу W3 и EI.
*/
#include "_gfx.h"
void _gfx_w3_video_end(void) __naked
{
__asm
ld a, (__gfx_saved_w3)
out (#0xE2), a
ei
ret
__endasm;
}
-106
View File
@@ -1,106 +0,0 @@
/*
* 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();
}
-118
View File
@@ -1,118 +0,0 @@
/*
* 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();
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_clear16 — залить экран цветом (mode 0x82); одна W3-скобка.
*/
#include "_gfx.h"
void gfx_clear16(uint8_t color)
{
_gfx_w3_video_begin();
_gfx_clear16_raw(color);
_gfx_w3_video_end();
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_clear256 — залить экран цветом (mode 0x81); одна W3-скобка.
*/
#include "_gfx.h"
void gfx_clear256(uint8_t color)
{
_gfx_w3_video_begin();
_gfx_clear256_raw(color);
_gfx_w3_video_end();
}
-141
View File
@@ -1,141 +0,0 @@
/*
* 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
* (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c)
*
* 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 wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get /
* gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also
* libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */
+10
View File
@@ -0,0 +1,10 @@
/*
* gfx_done — восстановить ранее сохранённый видеорежим (из gfx_init).
*/
#include "_gfx.h"
void gfx_done(uint8_t mode)
{
_videomode_raw_set(mode);
}
+16
View File
@@ -0,0 +1,16 @@
/*
* gfx_fill_rect16 — залитый прямоугольник w×h от (x,y) (mode 0x82).
* Поколоночные vlines (per-row RMW в каждой колонке); внешняя
* begin/end-скобка мапит W3 один раз на весь прямоугольник.
*/
#include "_gfx.h"
void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color)
{
if (w <= 0 || h <= 0) return;
_gfx_w3_video_begin();
for (int xx = 0; xx < w; xx++)
_gfx_vline16_raw(x + xx, y, h, color);
_gfx_w3_video_end();
}
+28
View File
@@ -0,0 +1,28 @@
/*
* gfx_fill_rect256 — залитый прямоугольник w×h от (x,y) (mode 0x81).
*/
#include "_gfx.h"
void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color)
{
if (w <= 0 || h <= 0) return;
/* Ориентация с меньшим числом accel-burst'ов. Один burst красит
* до 256 смежных байт (горизонталь) или до 256 пикселей колонки.
* построчно (hlines): h × ceil(w/256) burst'ов
* поколоночно (vlines): w × ceil(h/256) — при h ≤ 256 это w
* Вертикаль выигрывает на высоких узких, горизонталь — на низких
* широких. */
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();
}
-63
View File
@@ -1,63 +0,0 @@
/*
* 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();
}
+10
View File
@@ -0,0 +1,10 @@
/*
* gfx_get_bank — текущий байт страницы W3 (0x50..0x5F).
*/
#include "_gfx.h"
uint8_t gfx_get_bank(void)
{
return _gfx_bank;
}
+10
View File
@@ -0,0 +1,10 @@
/*
* gfx_get_draw_page — кэшированный номер рисуемой страницы (0/1).
*/
#include "_gfx.h"
uint8_t gfx_get_draw_page(void)
{
return _gfx_draw_page;
}
+10
View File
@@ -0,0 +1,10 @@
/*
* gfx_get_visible_page — кэшированный номер видимой страницы (0/1).
*/
#include "_gfx.h"
uint8_t gfx_get_visible_page(void)
{
return _gfx_visible_page;
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_hline16 — горизонтальная линия (mode 0x82) в W3-скобке.
*/
#include "_gfx.h"
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();
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_hline256 — горизонтальная линия (mode 0x81) в W3-скобке.
*/
#include "_gfx.h"
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();
}
+19
View File
@@ -0,0 +1,19 @@
/*
* gfx_init — переключиться в графический режим `mode`, вернуть прежний.
*
* `page` (0/1) — стартовая страница: и видимая, и рисуемая; банк W3
* сбрасывается в 0x50 (канонический видимый видеобанк). Идёт через
* _videomode_raw_set — минуя text-only валидацию conio.
*/
#include "_gfx.h"
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;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* gfx_line16 — линия (x0,y0)-(x1,y1) (mode 0x82). Ортогональные —
* через hline/vline; наклонные — Брезенхэм с одной W3-скобкой.
*/
#include "_gfx.h"
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;
}
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();
}
+37
View File
@@ -0,0 +1,37 @@
/*
* gfx_line256 — линия (x0,y0)-(x1,y1) (mode 0x81). Ортогональные
* уходят на акселератор; наклонные — Брезенхэм с одной W3-скобкой
* вокруг всего цикла.
*/
#include "_gfx.h"
void gfx_line256(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_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;
}
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();
}
+28
View File
@@ -0,0 +1,28 @@
/*
* gfx_load_default_font — забрать активный системный знакогенератор
* через BIOS WIN_GET_ZG (fn 0xB8) в _gfx_font_buf и включить его.
*/
#include "_gfx.h"
/* BIOS WIN_GET_ZG (0xB8): DE = приёмник, возвращает 2 КБ. */
static void bios_get_zg(uint8_t *dest) __naked
{
(void)dest;
__asm
push ix ; BIOS клобберит IX
ex de, hl ; DE = dest (аргумент пришёл в HL)
xor a ; шрифт 0 (системный)
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;
}
+13
View File
@@ -0,0 +1,13 @@
/*
* gfx_pal_get — прочитать блок записей графической палитры;
* обёртка над pal_get (libc/video) — не на горячем пути.
*/
#include "_gfx.h"
#include <palette.h>
void gfx_pal_get(uint8_t pal_num, uint8_t start, uint8_t count,
uint8_t *data)
{
pal_get(pal_num, start, count, data);
}
+13
View File
@@ -0,0 +1,13 @@
/*
* gfx_pal_get_color — прочитать одну запись графической палитры в
* R/G/B (любой указатель может быть NULL); обёртка над pal_get_color.
*/
#include "_gfx.h"
#include <palette.h>
void gfx_pal_get_color(uint8_t pal_num, uint8_t idx,
uint8_t *r, uint8_t *g, uint8_t *b)
{
pal_get_color(pal_num, idx, r, g, b);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* gfx_pal_load — загрузить блок записей графической палитры
* (BIOS $A4 PIC_SET_PAL): pal_num 0..3, start — первый слот,
* count слотов (0 = 256), data — записи по 4 байта (B, G, R, 0).
*
* Собственный inline-asm вместо тонкой обёртки над pal_load — тонкая
* обёртка через __sdcccall(1)-передачу эмпирически ломала текстовую
* палитру (следующая печать в текстовом режиме вешалась).
*/
#include "_gfx.h"
#include <palette.h>
static uint8_t gpl_num_;
static uint8_t gpl_start_;
static uint8_t gpl_count_;
static uint16_t gpl_data_;
void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count,
const uint8_t *data)
{
gpl_num_ = pal_num;
gpl_start_ = start;
gpl_count_ = count;
gpl_data_ = (uint16_t)(uintptr_t)data;
__asm
push ix
ld a, (_gpl_start_)
ld e, a ; E = start
ld a, (_gpl_count_)
ld d, a ; D = count (0 = 256)
ld hl, (_gpl_data_) ; HL = data
ld b, #0xFF ; маска
ld a, (_gpl_num_) ; A = номер палитры
ld c, #0xA4 ; BIOS PIC_SET_PAL
rst #0x08
pop ix
__endasm;
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_pal_reset — восстановить системную графическую палитру
* (BIOS $A6, type = PAL_GRAPH).
*/
#include "_gfx.h"
#include <palette.h>
void gfx_pal_reset(void)
{
pal_reset(PAL_GRAPH);
}
+17
View File
@@ -0,0 +1,17 @@
/*
* gfx_pal_set — одна запись графической палитры из RGB: собирает
* четвёрку (B, G, R, 0) и зовёт gfx_pal_load(pal_num, idx, 1, ...).
*/
#include "_gfx.h"
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);
}
-74
View File
@@ -1,74 +0,0 @@
/*
* gfx_palette.c — graphics-side palette wrappers.
*
* The "legacy" entry points (gfx_pal_load / gfx_pal_set) keep their own
* inline-asm implementation rather than thunking through pal_*; this
* avoids depending on SDCC's __sdcccall(1) stack-shuffling for nested
* calls, which empirically misbehaves here (the trampoline corrupts the
* text palette so the next text-mode print hangs). Functionally these
* are identical to the old gfx_core.c versions.
*
* The newer get / reset helpers are simple wrappers — they're either
* not on hot paths or are first introduced here, so the thunk overhead
* doesn't matter and we get to share the asm with libc/video/palette.c.
*/
#include <stdint.h>
#include <gfx.h>
#include <palette.h>
static uint8_t gpl_num_;
static uint8_t gpl_start_;
static uint8_t gpl_count_;
static uint16_t gpl_data_;
void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count,
const uint8_t *data)
{
gpl_num_ = pal_num;
gpl_start_ = start;
gpl_count_ = count;
gpl_data_ = (uint16_t)(uintptr_t)data;
__asm
push ix
ld a, (_gpl_start_)
ld e, a ; E = start
ld a, (_gpl_count_)
ld d, a ; D = count (0 256)
ld hl, (_gpl_data_) ; HL = data
ld b, #0xFF ; mask
ld a, (_gpl_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);
}
void gfx_pal_get(uint8_t pal_num, uint8_t start, uint8_t count,
uint8_t *data)
{
pal_get(pal_num, start, count, data);
}
void gfx_pal_get_color(uint8_t pal_num, uint8_t idx,
uint8_t *r, uint8_t *g, uint8_t *b)
{
pal_get_color(pal_num, idx, r, g, b);
}
void gfx_pal_reset(void)
{
pal_reset(PAL_GRAPH);
}
+27
View File
@@ -0,0 +1,27 @@
/*
* gfx_putchar16 — символ шрифтом 8×8 в (x,y), mode 0x82.
* x должен быть ЧЁТНЫМ (байтовое выравнивание); нечётный молча
* игнорируется — текстовые сетки обычно выровнены.
*/
#include "_gfx.h"
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;
_gfx_t16_pair_build(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;
_gfx_t16_y = (uint8_t)yy;
_gfx_t16_addr = base;
_gfx_t16_row = _gfx_font_ptr[r * 256 + cc];
_gfx_render_row16();
}
}
@@ -1,28 +1,19 @@
/*
* 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.
* gfx_putchar256 символ шрифтом 8×8 в (x,y), mode 0x81 (байт на
* пиксель). На ряд глифа (8 пикселей) пишем 8 байт: FG где бит 1,
* BG где 0. Рендер ряда сам делает DI / W3 save+restore вызывать
* можно из любого контекста без скобок.
*/
#include <gfx.h>
#include <stdint.h>
#include "_gfx.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_row; /* битовый паттерн текущего ряда глифа */
static uint8_t txt_fg;
static uint8_t txt_bg;
static uint16_t txt_addr; /* VRAM address for this row */
static uint16_t txt_addr; /* VRAM-адрес ряда */
/* 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
@@ -40,9 +31,9 @@ static void render_row_256(void) __naked
ld a, (_txt_bg)
ld e, a
ld a, (_txt_row)
ld b, #8 ; 8 pixels in this row
ld b, #8 ; 8 пикселей в ряду
rr256_loop:
rla ; CY = MSB, A <<= 1
rla ; CY = старший бит, A <<= 1
jr nc, rr256_bg
ld (hl), d ; FG
jr rr256_next
@@ -74,17 +65,8 @@ void gfx_putchar256(int x, int y, char c, uint8_t fg, uint8_t bg)
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]. */
/* Interleaved-раскладка: ряд r глифа cc — 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;
}
}
+14
View File
@@ -0,0 +1,14 @@
/*
* gfx_putpixel16 — пиксель (x,y) цветом 0..15 (mode 0x82);
* одиночный вызов в собственной W3-скобке.
*/
#include "_gfx.h"
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();
}
+14
View File
@@ -0,0 +1,14 @@
/*
* gfx_putpixel256 — пиксель (x,y) цветом color (mode 0x81);
* одиночный вызов в собственной W3-скобке.
*/
#include "_gfx.h"
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();
}
-186
View File
@@ -1,186 +0,0 @@
/*
* 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();
}
}
-175
View File
@@ -1,175 +0,0 @@
/*
* 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();
}
}
-44
View File
@@ -1,44 +0,0 @@
/*
* 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;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* gfx_rect16 — рамка w×h от (x,y) (mode 0x82); все четыре стороны
* в одной W3-скобке.
*/
#include "_gfx.h"
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();
}
+19
View File
@@ -0,0 +1,19 @@
/*
* gfx_rect256 — рамка w×h от (x,y) (mode 0x81); все четыре стороны
* в одной W3-скобке.
*/
#include "_gfx.h"
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();
}
+10
View File
@@ -0,0 +1,10 @@
/*
* gfx_set_bank — задать байт страницы W3 (0x50..0x5F) для gfx-записей.
*/
#include "_gfx.h"
void gfx_set_bank(uint8_t bank)
{
_gfx_bank = bank;
}
+14
View File
@@ -0,0 +1,14 @@
/*
* gfx_set_draw_page — выбрать страницу для рисования (0/1); обновляет
* _gfx_addr_base, который читают все примитивы.
*/
#include "_gfx.h"
void gfx_set_draw_page(uint8_t page)
{
_gfx_draw_page = page & 1;
/* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3
* инструкции Z80 — SDCC не сворачивает сложение констант. */
_gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000;
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_set_font — подменить шрифт своим (указатель удерживается —
* storage должен жить, пока шрифт активен). Формат см. _gfx.h.
*/
#include "_gfx.h"
void gfx_set_font(const uint8_t *font)
{
_gfx_font_ptr = font;
_gfx_font_loaded = 1;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* gfx_set_visible_page — выбрать видимую страницу (ESTEX $54 SELPAGE).
*
* Прямой OUT в порт 0xC9 бит 0 переключил бы регистр, но не уведомил
* бы display-учёт DSS — экран остаётся в рассогласованном состоянии
* (один из свопов показывается чёрным). Через syscall DSS доволен.
*/
#include "_gfx.h"
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 = страница 0/1
ld c, #0x54 ; ESTEX SELPAGE
rst #0x10
pop ix
__endasm;
}
+29
View File
@@ -0,0 +1,29 @@
/*
* gfx_text16 — строка шрифтом 8×8 от (x,y), mode 0x82; x чётный,
* растёт на 8 на символ. Pair-таблица строится один раз на всю
* строку (FG/BG не меняются) — поэтому не через gfx_putchar16.
*/
#include "_gfx.h"
void gfx_text16(int x, int y, const char *s, uint8_t fg, uint8_t bg)
{
_gfx_font_ensure();
if (x & 1) return;
_gfx_t16_pair_build(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;
_gfx_t16_y = (uint8_t)yy;
_gfx_t16_addr = base;
_gfx_t16_row = _gfx_font_ptr[r * 256 + cc];
_gfx_render_row16();
}
x += 8;
}
}
+15
View File
@@ -0,0 +1,15 @@
/*
* gfx_text256 — строка шрифтом 8×8 от (x,y), mode 0x81; x растёт
* на 8 на символ.
*/
#include "_gfx.h"
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;
}
}
-124
View File
@@ -1,124 +0,0 @@
/*
* 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;
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_vline16 — вертикальная линия (mode 0x82) в W3-скобке.
*/
#include "_gfx.h"
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();
}
+12
View File
@@ -0,0 +1,12 @@
/*
* gfx_vline256 — вертикальная линия (mode 0x81) в W3-скобке.
*/
#include "_gfx.h"
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();
}
+18
View File
@@ -0,0 +1,18 @@
/*
* gfx_wait_vsync — ждать следующий кадровый IM2-прерыв (50 Гц, DSS).
*
* HALT усыпляет Z80 до ближайшего IRQ; DSS обрабатывает его и
* возвращается на инструкцию после HALT — это начало обратного хода
* луча, идеальный момент для свопа страниц без разрывов.
*/
#include "_gfx.h"
void gfx_wait_vsync(void) __naked
{
__asm
ei
halt
ret
__endasm;
}