libbgi: спрайтовый движок v2 + accel-блит/heal leaf'ы + noclip-путь
Спрайтовая графика поверх accel block-copy (docs/sprite-api-design.md): - Ядро блиттинга: leaf'ы _bgi_blit_rows_raw (dst фикс, только src-страйд) / _bgi_copy_rows_raw (getimage) / _bgi_heal_rows_raw (src==dst). DI один на спрайт (санкция: малый спрайт под одним DI аудио не рвёт); src[0]-фикс снят (точная MAME подавляет CPU-байт триггера записи — на железе перепроверить; для heal был избыточен и снят безусловно). - Общие bracket-free ядра _gfx_blit_full/_gfx_heal_full (полная ширина: клип по экрану + split >256 для putimage) + лин _gfx_blit_sprite/ _gfx_heal_sprite (кадр ≤64, без split, 8-бит w/h) + noclip-варианты (клип-кода нет → полный codegen-win). Имя *_full (не *_clip) — «clip» двусмысленно (sprite-ядра тоже клипуют; различитель — ширина/split). - Движок retained-модели <sprite.h>: sprite_init/update/flip + inline move/frame/show/hide/touch; drawn[2] per-page внутри структуры; кадр — двухпроходно heal ВСЕ -> блит ВСЕ под одной W3-скобкой/банком на проход. - Флаг gfx_sprite_clip(on/off): приложение, само следящее за границами, отключает клип (~+19% на анимации; диспетч пока через if — funcptr далее). - putsprite/movesprite/gfx_blit/putimage(COPY)/getimage переведены на ядро. Тесты: examples/balls (движок, дабл-буфер, boundary-тест клипа), tests/sprites (RAM PASS, клип 4 края, атлас), tests/blitw (trig-leak), tests/spriteclip (hardware-probe: железо НЕ режет за краем -> клип нужен), tests/blitperf, tests/gfxbanks. size-baseline обновлён (53 программы). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfxbanks
|
||||
EXTRA_FLAGS ?= --gfx 256 --memory small
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* gfxbanks — Фаза 0 спрайтового дизайна: верификация подрежимов
|
||||
* видеостраниц #50..#5F (docs/sprite-api-design.md, «Фаза 0»).
|
||||
*
|
||||
* Семантика по доку (Иван Мак §4.3):
|
||||
* bit3 (0x58/0x5C) — запись байта 0xFF НЕ производится (прозрачность);
|
||||
* bit2 (0x54/0x5C) — запись только в видео-ОЗУ, ОЗУ-копия не трогается;
|
||||
* чтение из #50..#5F всегда возвращает ОЗУ-копию (getpixel = ОЗУ).
|
||||
*
|
||||
* ВНИМАНИЕ: вердикт MAME НЕ финален — тест прогоняется и в MAME, и на
|
||||
* железе. Известное расхождение: в MAME 0.283 (наш v306) FF-скип bit3
|
||||
* гасит ТОЛЬКО запись в VRAM, а теневое ОЗУ при bit2=0 всё равно
|
||||
* получает FF (ram_w: `if (BIT(~page,2)) ram[...]=data;
|
||||
* if (!(BIT(page,3) && data==0xff)) vram_w(...)`); в master-драйвере
|
||||
* уже полное подавление (`return`) — по доку. Через 0x5C расхождение
|
||||
* не проявляется (bit2 гасит ОЗУ-ветку) — поэтому putsprite планово
|
||||
* работает через 0x5C. На банке 0x58 зона SKIP это расхождение
|
||||
* ловит: chkC = FAIL на 0.283, PASS по доку/master.
|
||||
*
|
||||
* Сценарий (4 стадии, скриншот после каждой; палитра[255] = синий;
|
||||
* паузы — по RTC getdatetime, т.к. delayms в --memory small неработосп.
|
||||
* (irq_install → EINVAL), а скорость CPU в паузах не участвует):
|
||||
* S1 фон: вертикальные полосы RED/LIGHTGRAY через 0x50 (VRAM+ОЗУ).
|
||||
* S2 «спрайты» (после Фазы A putimage(COPY) идёт через акселератор,
|
||||
* поэтому CPU-путь в ZL — честный цикл putpixel):
|
||||
* ZL (CPU-путь): банк 0x5C, putpixel-ями круг GREEN с полями
|
||||
* 0xFF — ожидание: круг поверх полос, поля прозрачны;
|
||||
* ZR (accel-путь): банк 0x5C, bar 0xFF 64×64 (accel-fill:
|
||||
* ожидание no-op) + putimage того же круга (accel-COPY:
|
||||
* прозрачность полей на burst-пути);
|
||||
* ZB (bit3-skip): банк 0x58, putimage 0xFF (accel-copy, верх) +
|
||||
* bar 0xFF (accel-fill, низ) — ожидание по доку: no-op
|
||||
* экрана и ОЗУ.
|
||||
* Проверки по ОЗУ (getpixel): A = ОЗУ под ZL/ZR не тронуто (bit2);
|
||||
* C = ОЗУ под ZB не тронуто (полное подавление FF-записи).
|
||||
* Hex-пробы ОЗУ печатаются рядом (ожидание — цвета полос 04/07).
|
||||
* S3 КЛЮЧЕВОЙ ШАГ — 0xFF реально в VRAM: банк 0x54 (скипа нет),
|
||||
* квадрат 0xFF 32×32 поверх круга (CPU putpixel-ями в ZL,
|
||||
* accel-copy putimage'ем в ZR).
|
||||
* Экран: СИНИЙ квадрат → FF отображается как цвет 255
|
||||
* (семантика дока полная, стирание = heal);
|
||||
* ПОЛОСЫ → видеокарта подставляет ОЗУ-копию
|
||||
* (дешёвое стирание FF-заливкой работает);
|
||||
* круг/зелёный → 0x54 не эмулируется.
|
||||
* Проверка B = ОЗУ всё ещё полосы (bit2 у 0x54).
|
||||
* S4 heal: чтение+запись через 0x50 ЛЕВЫХ половин всех зон —
|
||||
* левые половины возвращаются к фону ИЗ ОЗУ-КОПИИ, правые
|
||||
* сохраняют артефакты S2/S3. NB: если ОЗУ под зоной испорчено
|
||||
* (chkC FAIL на 0.283), heal честно перенесёт порчу в VRAM —
|
||||
* зона SKIP посинеет на этой стадии.
|
||||
*
|
||||
* Запуск:
|
||||
* python3 toolchain/mame_interactive.py tests/gfxbanks/gfxbanks.exe \
|
||||
* --snap 13,17,21,25 --timeout 27
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <time.h>
|
||||
|
||||
#define ZY 56 /* верх зон */
|
||||
#define ZH 64 /* зоны 64×64 */
|
||||
#define ZLX 16 /* 0x5C, CPU-путь */
|
||||
#define ZRX 120 /* 0x5C, accel-путь */
|
||||
#define ZBX 224 /* 0x58, bit3-skip */
|
||||
|
||||
#define STRIPE_Y0 48
|
||||
#define STRIPE_Y1 135
|
||||
|
||||
/* Точки-пробы внутри области, куда S2 писал НЕ-FF байты (круг r=28 и
|
||||
* квадрат +16..+47 содержат все три), — общие для проверок и hex-дампа. */
|
||||
static const uint8_t probe[3][2] = { {32, 32}, {20, 28}, {40, 36} };
|
||||
|
||||
/* Полосы шириной 8: цвет по абсолютному x */
|
||||
static uint8_t stripe_color(int x)
|
||||
{
|
||||
return ((x >> 3) & 1) ? LIGHTGRAY : RED;
|
||||
}
|
||||
|
||||
/* Спрайт 64×64: круг GREEN r=28, вокруг — прозрачный 0xFF */
|
||||
static uint8_t spr[4 + 64 * 64];
|
||||
/* Квадрат 32×32 сплошного 0xFF */
|
||||
static uint8_t ffsq[4 + 32 * 32];
|
||||
|
||||
static void build_images(void)
|
||||
{
|
||||
uint8_t *p = spr;
|
||||
int x, y;
|
||||
|
||||
*p++ = 64; *p++ = 0; *p++ = 64; *p++ = 0;
|
||||
for (y = 0; y < 64; y++)
|
||||
for (x = 0; x < 64; x++) {
|
||||
int dx = x - 32, dy = y - 32;
|
||||
*p++ = (dx * dx + dy * dy <= 28 * 28) ? GREEN : 0xFF;
|
||||
}
|
||||
|
||||
p = ffsq;
|
||||
*p++ = 32; *p++ = 0; *p++ = 32; *p++ = 0;
|
||||
for (x = 0; x < 32 * 32; x++)
|
||||
*p++ = 0xFF;
|
||||
}
|
||||
|
||||
/* ОЗУ-копия зоны — нетронутые полосы? */
|
||||
static uint8_t ram_is_stripes(int zx)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
int x = zx + probe[i][0];
|
||||
if (getpixel(x, ZY + probe[i][1]) != stripe_color(x))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char hexd(uint8_t v)
|
||||
{
|
||||
v &= 15;
|
||||
return v < 10 ? '0' + v : 'A' + v - 10;
|
||||
}
|
||||
|
||||
/* Hex-дамп трёх ОЗУ-проб зоны: "ZX 04 04 07" в колонке x=216 */
|
||||
static void show_probes(const char *tag, int zx, int ty)
|
||||
{
|
||||
char buf[12];
|
||||
uint8_t i, v;
|
||||
|
||||
buf[0] = tag[0];
|
||||
buf[1] = tag[1];
|
||||
for (i = 0; i < 3; i++) {
|
||||
v = (uint8_t)getpixel(zx + probe[i][0], ZY + probe[i][1]);
|
||||
buf[2 + i * 3] = ' ';
|
||||
buf[3 + i * 3] = hexd(v >> 4);
|
||||
buf[4 + i * 3] = hexd(v);
|
||||
}
|
||||
buf[11] = 0;
|
||||
outtextxy(216, ty, buf);
|
||||
}
|
||||
|
||||
/* Пауза n секунд по RTC (гранулярность 1 с, не зависит от CPU/режима) */
|
||||
static void pause_sec(uint8_t n)
|
||||
{
|
||||
datetime_t dt;
|
||||
uint8_t last;
|
||||
|
||||
getdatetime(&dt);
|
||||
last = dt.second;
|
||||
while (n) {
|
||||
getdatetime(&dt);
|
||||
if (dt.second != last) {
|
||||
last = dt.second;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CPU-путь: вывести rect w×h из img (getimage-формат) в (x0,y0)
|
||||
* по-пиксельно через putpixel (LD (HL),A — НЕ акселератор). */
|
||||
static void blit_cpu(int x0, int y0, const void *img, int w, int h)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img + 4;
|
||||
int x, y;
|
||||
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = 0; x < w; x++)
|
||||
putpixel(x0 + x, y0 + y, *p++);
|
||||
}
|
||||
|
||||
/* CPU-путь: залить rect цветом по-пиксельно. */
|
||||
static void fill_cpu(int x0, int y0, int w, int h, uint8_t color)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = 0; x < w; x++)
|
||||
putpixel(x0 + x, y0 + y, color);
|
||||
}
|
||||
|
||||
/* heal: прочитать (вернёт ОЗУ-копию) и записать (уйдёт в VRAM+ОЗУ)
|
||||
* каждый пиксель прямоугольника банком 0x50. Прообраз gfx_heal. */
|
||||
static void heal(int x0, int y0, int w, int h)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
for (y = y0; y < y0 + h; y++)
|
||||
for (x = x0; x < x0 + w; x++)
|
||||
putpixel(x, y, getpixel(x, y));
|
||||
}
|
||||
|
||||
static void say(int y, const char *s)
|
||||
{
|
||||
outtextxy(4, y, s);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint8_t chkA, chkB, chkC;
|
||||
int x;
|
||||
|
||||
build_images();
|
||||
initgraph();
|
||||
gfx_pal_set(0, 255, 0, 0, 255); /* цвет 255 = ярко-синий */
|
||||
cleardevice();
|
||||
setcolor(WHITE);
|
||||
|
||||
/* ---- S1: фон ------------------------------------------------ */
|
||||
say(4, "GFXBANKS: page submodes 50/54/58/5C");
|
||||
for (x = 0; x < 320; x += 8) {
|
||||
setfillstyle(SOLID_FILL, stripe_color(x));
|
||||
bar(x, STRIPE_Y0, x + 7, STRIPE_Y1);
|
||||
}
|
||||
outtextxy(ZLX, 44, "5C CPU");
|
||||
outtextxy(ZRX, 44, "5C ACC");
|
||||
outtextxy(ZBX, 44, "58 SKIP");
|
||||
say(148, "S1 stripes via 50");
|
||||
pause_sec(4);
|
||||
|
||||
/* ---- S2: спрайты через 0x5C / no-op через 0x58 --------------- */
|
||||
gfx_set_bank(0x5C);
|
||||
blit_cpu(ZLX, ZY, spr, 64, 64); /* CPU: круг + FF-поля */
|
||||
setfillstyle(SOLID_FILL, 255);
|
||||
bar(ZRX, ZY, ZRX + 63, ZY + 63); /* accel-fill FF — ждём no-op */
|
||||
putimage(ZRX, ZY, spr, COPY_PUT); /* accel-COPY: круг + FF-поля */
|
||||
gfx_set_bank(0x58);
|
||||
putimage(ZBX, ZY, ffsq, COPY_PUT); /* accel-copy FF, верх зоны */
|
||||
setfillstyle(SOLID_FILL, 255);
|
||||
bar(ZBX, ZY + 32, ZBX + 31, ZY + 63); /* accel-fill FF, низ зоны */
|
||||
gfx_set_bank(0x50);
|
||||
|
||||
chkA = ram_is_stripes(ZLX) && ram_is_stripes(ZRX);
|
||||
chkC = ram_is_stripes(ZBX);
|
||||
say(158, "S2 sprites 5C, ff-skip 58");
|
||||
say(168, chkA ? "A bit2 RAM intact: PASS" : "A bit2 RAM intact: FAIL");
|
||||
show_probes("ZL", ZLX, 168);
|
||||
say(178, chkC ? "C bit3 skip (RAM): PASS" : "C bit3 skip (RAM): FAIL");
|
||||
show_probes("ZB", ZBX, 178);
|
||||
pause_sec(4);
|
||||
|
||||
/* ---- S3: 0xFF реально в VRAM (банк 0x54, скипа нет) ---------- */
|
||||
gfx_set_bank(0x54);
|
||||
fill_cpu(ZLX + 16, ZY + 16, 32, 32, 0xFF); /* CPU-путь */
|
||||
putimage(ZRX + 16, ZY + 16, ffsq, COPY_PUT); /* accel-copy путь */
|
||||
gfx_set_bank(0x50);
|
||||
|
||||
chkB = ram_is_stripes(ZLX) && ram_is_stripes(ZRX);
|
||||
say(188, "S3 KEY: ff into vram via 54");
|
||||
say(198, chkB ? "B 54 RAM intact: PASS" : "B 54 RAM intact: FAIL");
|
||||
show_probes("ZR", ZRX, 198);
|
||||
say(208, "S3 look: BLUE=doc STRIPES=subst");
|
||||
pause_sec(4);
|
||||
|
||||
/* ---- S4: heal левых половин зон ------------------------------ */
|
||||
heal(ZLX, ZY, 32, ZH);
|
||||
heal(ZRX, ZY, 32, ZH);
|
||||
heal(ZBX, ZY, 32, ZH);
|
||||
say(218, "S4 healed left halves, done");
|
||||
|
||||
for (;;) { }
|
||||
}
|
||||
Reference in New Issue
Block a user