Files
Sprinter-SDCC/tests/gfxbanks/gfxbanks.c
T
snark13 72ce66275e 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>
2026-07-12 21:51:52 +03:00

260 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 (;;) { }
}