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:
+109
@@ -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
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* _gfx_font_ensure — ленивая инициализация шрифта: текстовые рендеры
|
||||
* зовут её при каждом входе, системный шрифт грузится при первом
|
||||
* использовании (чисто графическая программа BIOS-вызов не платит).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_font_ensure(void)
|
||||
{
|
||||
if (!_gfx_font_loaded) gfx_load_default_font();
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 */
|
||||
}
|
||||
@@ -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];
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* _gfx_w3_state — сохранённый байт W3 между begin и end (данные).
|
||||
*
|
||||
* НЕ реентерабельно — безопасно только потому, что GFX держит
|
||||
* прерывания выключенными между _gfx_w3_video_begin и _end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_saved_w3;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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. */
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_done — восстановить ранее сохранённый видеорежим (из gfx_init).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_done(uint8_t mode)
|
||||
{
|
||||
_videomode_raw_set(mode);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_get_bank — текущий байт страницы W3 (0x50..0x5F).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t gfx_get_bank(void)
|
||||
{
|
||||
return _gfx_bank;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_get_draw_page — кэшированный номер рисуемой страницы (0/1).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t gfx_get_draw_page(void)
|
||||
{
|
||||
return _gfx_draw_page;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_get_visible_page — кэшированный номер видимой страницы (0/1).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t gfx_get_visible_page(void)
|
||||
{
|
||||
return _gfx_visible_page;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_set_bank — задать байт страницы W3 (0x50..0x5F) для gfx-записей.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_set_bank(uint8_t bank)
|
||||
{
|
||||
_gfx_bank = bank;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user