diff --git a/docs/size_baseline.tsv b/docs/size_baseline.tsv index 2602d5f..1f8aece 100644 --- a/docs/size_baseline.tsv +++ b/docs/size_baseline.tsv @@ -39,12 +39,13 @@ mdview2 27031 mem_test 4555 mouse 4382 openenv 6124 +palfile 5406 ptime 5744 rt_test 4892 seek 4188 simple 955 solidt 11531 -space 10956 +space 10976 spranim 8752 spriteclip 4119 sprites 6791 diff --git a/examples/space/space.c b/examples/space/space.c index 4d60ea1..98c3f82 100644 --- a/examples/space/space.c +++ b/examples/space/space.c @@ -104,13 +104,7 @@ int main(void) initgraph(); gfx_sprite_clip(0); /* цели в пределах */ - /* Палитра страницы 1 = палитре страницы 0 — иначе флип на page 1 - * показывает сцену ЧЁРНОЙ (у каждой страницы своя палитра). */ - { - static uint8_t egapal[16 * 4]; - gfx_pal_get(0, 0, 16, egapal); - gfx_pal_load(1, 0, 16, egapal); - } + gfx_pal_sync(); /* обязательный шаг дабл-буфера (см. gfx.h) */ /* Спрайты: астероиды (LOOP + цель), взрывы (скрыты), маяк (PINGPONG). */ for (i = 0; i < NAST; i++) { diff --git a/libbgi/_bgi.h b/libbgi/_bgi.h index 3f7eb05..1416e2e 100644 --- a/libbgi/_bgi.h +++ b/libbgi/_bgi.h @@ -155,6 +155,12 @@ void _gfx_w0_isr(void); #define _ATL_DIR_OFF 0x68u /* каталог: count × 8 Б (макс 19) */ #define _ATL_DATA_OFF 0x100u /* ленты; = резерв ISR-стаба */ +/* ---- Палитровые хелперы (common/_gfx_pal_buf.c) ------------------- * + * Общий буфер чанковых операций (sync/fload/fsave): 64 записи × 4 Б; + * в W2 (BIOS $A4 требует буфер в #4000-#BFFF). */ +#define _GFX_PAL_CHUNK 64u +extern uint8_t _gfx_pal_buf[_GFX_PAL_CHUNK * 4]; + /* ---- Авто-анимация спрайтов (§9г) --------------------------------- * * Тикер подключается указателем (DCE: программа без sprite_anim/ * sprite_moveto не тянет его код): дефолт NULL (_BSS), ставят diff --git a/libbgi/common/_gfx_pal_buf.c b/libbgi/common/_gfx_pal_buf.c new file mode 100644 index 0000000..576572a --- /dev/null +++ b/libbgi/common/_gfx_pal_buf.c @@ -0,0 +1,9 @@ +/* + * _gfx_pal_buf — общий строковый буфер палитровых хелперов + * (gfx_pal_sync / gfx_pal_fload / gfx_pal_fsave): 64 записи × 4 байта. + * Статик в W2 — BIOS $A4 требует буфер в #4000-#BFFF. Отдельный + * data-модуль: линкуется только с самими хелперами (DCE). + */ +#include "../_bgi.h" + +uint8_t _gfx_pal_buf[_GFX_PAL_CHUNK * 4]; diff --git a/libbgi/common/gfx_pal_fload.c b/libbgi/common/gfx_pal_fload.c new file mode 100644 index 0000000..69c39ef --- /dev/null +++ b/libbgi/common/gfx_pal_fload.c @@ -0,0 +1,40 @@ +/* + * gfx_pal_fload — загрузить палитру pal_num (0..3) из файла формата + * gfx_pal_fsave (записи по 4 байта: B, G, R, 0). Файл может быть + * УСЕЧЁН: грузится столько полных записей, сколько есть (палитра + * 16-цветной картинки — файл 64 байта), остальные слоты не трогаются. + * Возврат: число загруженных записей (0..256) или -1 + errno. + * + * NB: заливает только палитру pal_num — для дабл-буфера после загрузки + * в 0 звать gfx_pal_sync(). + */ +#include +#include +#include "../_bgi.h" + +int gfx_pal_fload(uint8_t pal_num, const char *path) +{ + int fd, n, total = 0; + uint8_t start = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + + while (total < 256) { + uint8_t entries; + + n = read(fd, _gfx_pal_buf, _GFX_PAL_CHUNK * 4); + if (n < 0) { close(fd); return -1; } + entries = (uint8_t)(n / 4); + if (!entries) + break; + gfx_pal_load(pal_num, start, entries, _gfx_pal_buf); + start += entries; + total += entries; + if (n < (int)(_GFX_PAL_CHUNK * 4)) + break; /* конец файла */ + } + close(fd); + return total; +} diff --git a/libbgi/common/gfx_pal_fsave.c b/libbgi/common/gfx_pal_fsave.c new file mode 100644 index 0000000..1fdd5db --- /dev/null +++ b/libbgi/common/gfx_pal_fsave.c @@ -0,0 +1,30 @@ +/* + * gfx_pal_fsave — сохранить палитру pal_num (0..3) в файл: 256 записей + * по 4 байта (B, G, R, 0 — родной формат BIOS $A4), итого 1024 байта. + * Пишется чанками по 64 записи через общий буфер. 0 — OK; -1 + errno. + */ +#include +#include +#include "../_bgi.h" + +int gfx_pal_fsave(uint8_t pal_num, const char *path) +{ + uint8_t k, start = 0; + int fd, rc = 0; + + fd = creat(path, 0); + if (fd < 0) + return -1; + + for (k = 0; k < 4; k++) { + gfx_pal_get(pal_num, start, _GFX_PAL_CHUNK, _gfx_pal_buf); + if (write(fd, _gfx_pal_buf, _GFX_PAL_CHUNK * 4) + != _GFX_PAL_CHUNK * 4) { + rc = -1; + break; + } + start += _GFX_PAL_CHUNK; + } + close(fd); + return rc; +} diff --git a/libbgi/common/gfx_pal_sync.c b/libbgi/common/gfx_pal_sync.c new file mode 100644 index 0000000..e21774e --- /dev/null +++ b/libbgi/common/gfx_pal_sync.c @@ -0,0 +1,20 @@ +/* + * gfx_pal_sync — палитра страницы 1 := палитре страницы 0 (все 256 + * записей, чанками по 64 через общий буфер). Обязательный шаг + * seamless-дабл-буфера: у каждой графической страницы СВОЯ палитра, и + * без синхронизации флип на страницу 1 показывает сцену чёрной + * (грабли examples/space 2026-07-13). Звать после initgraph и после + * любых правок палитры 0 (gfx_pal_set/load/reset). + */ +#include "../_bgi.h" + +void gfx_pal_sync(void) +{ + uint8_t k, start = 0; + + for (k = 0; k < 4; k++) { + gfx_pal_get(0, start, _GFX_PAL_CHUNK, _gfx_pal_buf); + gfx_pal_load(1, start, _GFX_PAL_CHUNK, _gfx_pal_buf); + start += _GFX_PAL_CHUNK; + } +} diff --git a/libbgi/include/gfx.h b/libbgi/include/gfx.h index 76950a9..8c85b11 100644 --- a/libbgi/include/gfx.h +++ b/libbgi/include/gfx.h @@ -126,4 +126,17 @@ void gfx_pal_get_color(uint8_t pal_num, uint8_t idx, uint8_t *r, uint8_t *g, uint8_t *b); void gfx_pal_reset(void); +/* Палитра страницы 1 := палитре страницы 0 (все 256 записей). + * ОБЯЗАТЕЛЬНЫЙ шаг дабл-буфера: без него флип на страницу 1 показывает + * сцену чёрной (у каждой страницы своя палитра). Звать после initgraph + * и после правок палитры 0. */ +void gfx_pal_sync(void); + +/* Палитра ↔ файл. Формат: записи по 4 байта (B, G, R, 0 — родной + * BIOS $A4), полная палитра = 1024 байта. fload принимает и усечённый + * файл (напр. 64 Б = 16 цветов) — остальные слоты не трогает; возврат: + * число загруженных записей или -1. fsave пишет все 256; 0 / -1. */ +int gfx_pal_fsave(uint8_t pal_num, const char *path); +int gfx_pal_fload(uint8_t pal_num, const char *path); + #endif diff --git a/tests/palfile/Makefile b/tests/palfile/Makefile new file mode 100644 index 0000000..b3cb47d --- /dev/null +++ b/tests/palfile/Makefile @@ -0,0 +1,4 @@ +PROJ_ROOT := $(abspath $(CURDIR)/../..) +EXAMPLE := palfile +EXTRA_FLAGS ?= --gfx 256 --memory tiny +include $(PROJ_ROOT)/app.mk diff --git a/tests/palfile/palfile.c b/tests/palfile/palfile.c new file mode 100644 index 0000000..478d7b0 --- /dev/null +++ b/tests/palfile/palfile.c @@ -0,0 +1,99 @@ +/* + * palfile — тест палитровых хелперов: gfx_pal_sync / gfx_pal_fsave / + * gfx_pal_fload. Программные проверки (gfx_pal_get_color), MAME — + * только хост. + * + * P1 fsave: сохранить дефолтную палитру 0 → PAL0.PAL (1024 Б). + * P2 fload: испортить слоты 1 и 200 → fload(0) вернул 256 и ОБА + * слота восстановились (полный файл). + * P3 усечённый файл: записать 2 записи (8 Б) → испортить слоты 0,1,2 + * → fload вернул 2, слоты 0,1 восстановлены, слот 2 НЕ тронут. + * P4 sync: испортить слот 5 палитры 1 → gfx_pal_sync() → слот 5 + * палитры 1 == слоту 5 палитры 0. + */ +#include +#include +#include +#include +#include + +static uint8_t eq(uint8_t p, uint8_t i, uint8_t r, uint8_t g, uint8_t b) +{ + uint8_t rr, gg, bb; + + gfx_pal_get_color(p, i, &rr, &gg, &bb); + return rr == r && gg == g && bb == b; +} + +static void get3(uint8_t p, uint8_t i, uint8_t *rgb) +{ + gfx_pal_get_color(p, i, rgb, rgb + 1, rgb + 2); +} + +int main(void) +{ + uint8_t a[3], c[3], ok; + int n; + + puts("PALFILE: pal sync/fsave/fload"); + initgraph(); + + /* ---- P1: сохранить дефолт ---------------------------------------- */ + ok = (gfx_pal_fsave(0, "pal0.pal") == 0); + closegraph(); + printf("P1 fsave: %s\n", ok ? "PASS" : "FAIL"); + if (!ok) return 1; + + initgraph(); + /* ---- P2: полный fload восстанавливает испорченное ----------------- */ + get3(0, 1, a); /* эталоны */ + get3(0, 200, c); + gfx_pal_set(0, 1, 63, 0, 63); /* испортить */ + gfx_pal_set(0, 200, 1, 2, 3); + n = gfx_pal_fload(0, "pal0.pal"); + ok = (n == 256) && eq(0, 1, a[0], a[1], a[2]) + && eq(0, 200, c[0], c[1], c[2]); + closegraph(); + printf("P2 fload: %s (n=%d)\n", ok ? "PASS" : "FAIL", n); + + initgraph(); + /* ---- P3: усечённый файл ------------------------------------------- */ + { + int fd = creat("pal2.pal", 0); + uint8_t two[8]; + uint8_t i; + + /* первые 2 записи из pal0.pal */ + for (i = 0; i < 8; i++) two[i] = 0; + { + int f0 = open("pal0.pal", O_RDONLY); + read(f0, two, 8); + close(f0); + } + write(fd, two, 8); + close(fd); + } + get3(0, 0, a); + get3(0, 1, c); + gfx_pal_set(0, 0, 63, 63, 0); + gfx_pal_set(0, 1, 0, 63, 63); + gfx_pal_set(0, 2, 7, 7, 7); /* слот 2 портим НАВСЕГДА */ + n = gfx_pal_fload(0, "pal2.pal"); + ok = (n == 2) && eq(0, 0, a[0], a[1], a[2]) + && eq(0, 1, c[0], c[1], c[2]) + && eq(0, 2, 7, 7, 7); /* не тронут fload'ом */ + closegraph(); + printf("P3 trunc: %s (n=%d)\n", ok ? "PASS" : "FAIL", n); + + initgraph(); + /* ---- P4: sync ------------------------------------------------------ */ + get3(0, 5, a); + gfx_pal_set(1, 5, 1, 1, 1); /* испортить у страницы 1 */ + gfx_pal_sync(); + ok = eq(1, 5, a[0], a[1], a[2]); + closegraph(); + printf("P4 sync: %s\n", ok ? "PASS" : "FAIL"); + + puts("done"); + return 0; +}