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 := blitperf
|
||||
EXTRA_FLAGS ?= --gfx 256 --memory small
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* blitperf — микробенчмарк спрайтового пути: операций в секунду (RTC)
|
||||
* для каждого слоя цепочки putsprite/gfx_heal. Диагностика разрыва
|
||||
* с asm-референсом (examples/balls ~1.5 мс/шар при теории ~0.3 мс):
|
||||
* отделяет стоимость leaf'ов от C-обвязки (клип/банк/заголовок) и
|
||||
* даёт опорные точки (bar — давно оптимизированный accel-путь).
|
||||
*
|
||||
* Методика: измеряется число вызовов за одну RTC-секунду (старт по
|
||||
* смене секунды; время проверяется раз в 8 операций). Строка «empty»
|
||||
* — цена самого цикла (вызов пустышки + getdatetime/8) — вычитать из
|
||||
* остальных: t_op = 1/n_op - 1/n_empty.
|
||||
*
|
||||
* ВНИМАНИЕ: использует ВНУТРЕННИЕ символы libbgi (прототипы
|
||||
* продублированы ниже — сверять с libbgi/_bgi.h при изменениях ABI).
|
||||
*
|
||||
* Запуск:
|
||||
* python3 toolchain/mame_interactive.py tests/blitperf/blitperf.exe \
|
||||
* --snap 22 --timeout 24
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <time.h>
|
||||
|
||||
/* --- внутренности libbgi (копия из _bgi.h, НЕ публичный API) ------- */
|
||||
extern uint16_t _gfx_addr_base;
|
||||
void _bgi_begin(void);
|
||||
void _bgi_end(void);
|
||||
void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h);
|
||||
void _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int dstride, int sstride, uint8_t y0);
|
||||
/* ------------------------------------------------------------------- */
|
||||
|
||||
#define BW 16
|
||||
static uint8_t spr[4 + BW * BW];
|
||||
|
||||
static void build_sprite(void)
|
||||
{
|
||||
uint8_t *p = spr;
|
||||
int i;
|
||||
|
||||
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||||
for (i = 0; i < BW * BW; i++)
|
||||
*p++ = (uint8_t)(LIGHTRED + ((i & 7) == 0)); /* без 0xFF */
|
||||
}
|
||||
|
||||
/* --- операции ------------------------------------------------------ */
|
||||
|
||||
static void op_empty(void) { }
|
||||
|
||||
static void op_leaf_heal(void) /* leaf + W3-скобка, без C-обвязки */
|
||||
{
|
||||
_bgi_begin();
|
||||
_bgi_heal_rows_raw((uint8_t *)(_gfx_addr_base + 100), 100, BW, BW);
|
||||
_bgi_end();
|
||||
}
|
||||
|
||||
static void op_leaf_blit(void) /* copy-leaf + скобка, без обвязки */
|
||||
{
|
||||
_bgi_begin();
|
||||
_bgi_copy_rows_raw(spr + 4, (uint8_t *)(_gfx_addr_base + 100),
|
||||
BW, BW, 0, BW, 100);
|
||||
_bgi_end();
|
||||
}
|
||||
|
||||
static void op_gfx_heal(void) { gfx_heal(100, 100, BW, BW); }
|
||||
static void op_blit_part(void) { gfx_blit_part(100, 100, spr, 0, 0, BW, BW); }
|
||||
static void op_putsprite(void) { putsprite(100, 100, spr); }
|
||||
static void op_bar(void) /* опорная точка: accel-fill 16×16 */
|
||||
{
|
||||
bar(100, 100, 100 + BW - 1, 100 + BW - 1);
|
||||
}
|
||||
|
||||
/* --- измеритель ---------------------------------------------------- */
|
||||
|
||||
static uint16_t bench(void (*op)(void))
|
||||
{
|
||||
datetime_t dt;
|
||||
uint8_t s0, k;
|
||||
uint16_t n = 0;
|
||||
|
||||
getdatetime(&dt);
|
||||
s0 = dt.second;
|
||||
do { getdatetime(&dt); } while (dt.second == s0); /* синхр. к границе */
|
||||
s0 = dt.second;
|
||||
for (;;) {
|
||||
for (k = 0; k < 8; k++)
|
||||
op();
|
||||
n += 8;
|
||||
getdatetime(&dt);
|
||||
if (dt.second != s0)
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
static void say_num(int y, const char *tag, uint16_t v)
|
||||
{
|
||||
char buf[16];
|
||||
uint8_t i;
|
||||
uint16_t d = 10000;
|
||||
|
||||
for (i = 0; tag[i] && i < 9; i++)
|
||||
buf[i] = tag[i];
|
||||
while (i < 10) buf[i++] = ' ';
|
||||
for (; d; d /= 10)
|
||||
buf[i++] = (char)('0' + (v / d) % 10);
|
||||
buf[i] = 0;
|
||||
setcolor(WHITE);
|
||||
outtextxy(4, y, buf);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
build_sprite();
|
||||
initgraph();
|
||||
cleardevice();
|
||||
setcolor(WHITE);
|
||||
outtextxy(4, 4, "BLITPERF ops/sec (16x16)");
|
||||
|
||||
say_num(20, "empty", bench(op_empty));
|
||||
say_num(30, "leafheal", bench(op_leaf_heal));
|
||||
say_num(40, "leafblit", bench(op_leaf_blit));
|
||||
say_num(50, "gfx_heal", bench(op_gfx_heal));
|
||||
say_num(60, "blitpart", bench(op_blit_part));
|
||||
say_num(70, "putsprite", bench(op_putsprite));
|
||||
say_num(80, "bar", bench(op_bar));
|
||||
outtextxy(4, 96, "done");
|
||||
|
||||
for (;;) { }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := blitw
|
||||
EXTRA_FLAGS ?= --gfx 256 --memory small
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* blitw — программный регресс ядра блиттинга gfx_blit_part по ширинам.
|
||||
*
|
||||
* Репро артефакта tests/sprites (клипнутые шары w=12 рисуются со
|
||||
* сдвигом): блитим паттерн-образ 24×24 разными ширинами банком 0x50
|
||||
* (запись идёт и в ОЗУ-копию → getpixel читает ровно то, что записано)
|
||||
* и побайтно сверяем прямоугольник + рамку 1px вокруг (должна остаться
|
||||
* фоном). Вердикт по каждой ширине печатается на экране:
|
||||
* w=NN PASS | w=NN FAIL @x,y eXX gYY (exp/got первого расхождения)
|
||||
*
|
||||
* Запуск:
|
||||
* python3 toolchain/mame_interactive.py tests/blitw/blitw.exe \
|
||||
* --snap 14 --timeout 16
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
|
||||
#define IW 24
|
||||
#define IH 24
|
||||
#define TX 200 /* позиция тестового прямоугольника (правее текста) */
|
||||
#define TY 30
|
||||
|
||||
static uint8_t img[4 + IW * IH];
|
||||
static char msg[40];
|
||||
|
||||
static uint8_t pat(int x, int y)
|
||||
{
|
||||
return (uint8_t)(((x + 1) * (y + 3)) & 0x7F); /* != 0xFF, != 0 кроме редких */
|
||||
}
|
||||
|
||||
static void build_img(void)
|
||||
{
|
||||
uint8_t *p = img;
|
||||
int x, y;
|
||||
|
||||
*p++ = IW; *p++ = 0; *p++ = IH; *p++ = 0;
|
||||
for (y = 0; y < IH; y++)
|
||||
for (x = 0; x < IW; x++)
|
||||
*p++ = pat(x, y);
|
||||
}
|
||||
|
||||
static char hexd(uint8_t v)
|
||||
{
|
||||
v &= 15;
|
||||
return v < 10 ? '0' + v : 'A' + v - 10;
|
||||
}
|
||||
|
||||
/* Проверить блит шириной w в (tx,TY): весь rect = pat, рамка = 0. */
|
||||
static void check_w(int w, int tx, int ty, int row)
|
||||
{
|
||||
int x, y;
|
||||
uint8_t exp, got;
|
||||
uint8_t fail = 0;
|
||||
|
||||
/* фон под областью + рамкой */
|
||||
setfillstyle(SOLID_FILL, 0);
|
||||
bar(tx - 2, ty - 2, tx + w + 1, ty + IH + 1);
|
||||
|
||||
gfx_blit_part(tx, ty, img, 0, 0, w, IH);
|
||||
|
||||
for (y = -1; y <= IH && !fail; y++) {
|
||||
for (x = -1; x <= w && !fail; x++) {
|
||||
exp = (y >= 0 && y < IH && x >= 0 && x < w) ? pat(x, y) : 0;
|
||||
got = (uint8_t)getpixel(tx + x, ty + y);
|
||||
if (got != exp) {
|
||||
fail = 1;
|
||||
msg[0] = 'w'; msg[1] = '0' + w / 10; msg[2] = '0' + w % 10;
|
||||
msg[3] = ' '; msg[4] = 'F'; msg[5] = 'A'; msg[6] = 'I';
|
||||
msg[7] = 'L'; msg[8] = ' ';
|
||||
msg[9] = 'x'; msg[10] = (char)('0' + (x + 1) / 10);
|
||||
msg[11] = (char)('0' + (x + 1) % 10);
|
||||
msg[12] = 'y'; msg[13] = (char)('0' + (y + 1) / 10);
|
||||
msg[14] = (char)('0' + (y + 1) % 10);
|
||||
msg[15] = ' '; msg[16] = 'e'; msg[17] = hexd(exp >> 4);
|
||||
msg[18] = hexd(exp);
|
||||
msg[19] = ' '; msg[20] = 'g'; msg[21] = hexd(got >> 4);
|
||||
msg[22] = hexd(got);
|
||||
msg[23] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fail) {
|
||||
msg[0] = 'w'; msg[1] = '0' + w / 10; msg[2] = '0' + w % 10;
|
||||
msg[3] = ' '; msg[4] = 'P'; msg[5] = 'A'; msg[6] = 'S';
|
||||
msg[7] = 'S'; msg[8] = 0;
|
||||
}
|
||||
outtextxy(4, 40 + row * 10, msg);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const uint8_t widths[] = { 24, 16, 12, 8, 4, 2, 1 };
|
||||
uint8_t i;
|
||||
|
||||
build_img();
|
||||
initgraph();
|
||||
cleardevice();
|
||||
setcolor(WHITE);
|
||||
outtextxy(4, 4, "BLITW: gfx_blit_part width sweep");
|
||||
|
||||
for (i = 0; i < sizeof(widths); i++)
|
||||
check_w(widths[i], TX, TY, i);
|
||||
|
||||
/* и правый край, как в sprites: dst у конца строки page 0 */
|
||||
check_w(12, 308 - 12, TY, 7); /* dst близко к краю, но без клипа */
|
||||
|
||||
/* Регресс утечки CPU-байта write-триггера (см. шапку
|
||||
* _bgi_copy_rows_raw). Программно (getpixel = ОЗУ) утечка на MAME
|
||||
* 0.283 НЕ ловится: ОЗУ-ветка 0x58 пишет все байты, и burst
|
||||
* перезаписывает утечку в ОЗУ; жертвой остаётся только VRAM
|
||||
* (FF-скип). Поэтому проверка ВИЗУАЛЬНАЯ: блит образа с col0=0xFF
|
||||
* и последним столбцом YELLOW через 0x5C поверх ЗЕЛЁНОЙ плашки.
|
||||
* col0 (x=TX..) обязан остаться ЗЕЛЁНЫМ; до фикса он был YELLOW
|
||||
* (CPU-байт = последний байт burst-чтения). */
|
||||
{
|
||||
static uint8_t timg[4 + 16 * 8];
|
||||
uint8_t *p = timg;
|
||||
int x, y;
|
||||
|
||||
*p++ = 16; *p++ = 0; *p++ = 8; *p++ = 0;
|
||||
for (y = 0; y < 8; y++)
|
||||
for (x = 0; x < 16; x++)
|
||||
*p++ = (x == 0) ? 0xFF : (x == 15 ? YELLOW : LIGHTBLUE);
|
||||
|
||||
setfillstyle(SOLID_FILL, GREEN);
|
||||
bar(TX - 4, 150 - 4, TX + 19, 150 + 11);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit(TX, 150, timg);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
outtextxy(4, 40 + 8 * 10 + 4, "trig leak: col0 @200,150 green?");
|
||||
}
|
||||
|
||||
outtextxy(4, 40 + 9 * 10 + 6, "done");
|
||||
for (;;) { }
|
||||
}
|
||||
@@ -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 (;;) { }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := spriteclip
|
||||
EXTRA_FLAGS ?= --gfx 256 --memory small
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* spriteclip — ЭКСПЕРИМЕНТ (гипотеза пользователя 2026-07-12): режет ли
|
||||
* ЖЕЛЕЗО спрайт, выведенный за ПРАВЫЙ/НИЖНИЙ край экрана, БЕЗ
|
||||
* программного клипа? Или запись за x>=320 / y>=256 портит соседнюю
|
||||
* память (следующую строку / страницу 1 / верх экрана)?
|
||||
*
|
||||
* Метод: блит 16×16 НЕПРОЗРАЧНОГО белого спрайта мимо клипа — прямым
|
||||
* вызовом leaf'а _bgi_copy_rows_raw (в обход _gfx_blit_clip). Фон —
|
||||
* синий; зелёные линии-маркеры на x=319 и y=255 (граница экрана);
|
||||
* левый край (0..7) и верх (0..7) залиты красным-маркером, чтобы
|
||||
* поймать «заворот» переполнения именно туда.
|
||||
*
|
||||
* Читать скриншот:
|
||||
* - ПРАВЫЙ (спрайт в x=312): если видно только 8 левых колонок белого
|
||||
* (x=312..319), а правых 8 нет И красная левая полоса цела →
|
||||
* железо режет по правому краю. Если правые 8 колонок «завернулись»
|
||||
* белым в x=0..7 (поверх красного) или появился мусор — НЕ режет,
|
||||
* клип обязателен.
|
||||
* - НИЖНИЙ (спрайт в y=250): если видно 6 строк белого (y=250..255),
|
||||
* а строк за 256 нет И красная верхняя полоса цела → железо режет по
|
||||
* нижнему. Если белое всплыло сверху (y=0..) — Port_Y завернулся.
|
||||
* - УГОЛ (x=312,y=250): комбинация.
|
||||
*
|
||||
* НЕ для CI-регресса — разовый hardware-probe (и в MAME, и на железе).
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <conio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Внутренние символы libbgi (глобальны в bgi256.lib) — тянем напрямую
|
||||
* ради no-clip блита. Обычному коду так делать НЕ надо (есть gfx_blit). */
|
||||
extern void _bgi_begin(void);
|
||||
extern void _bgi_end(void);
|
||||
extern void _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int dstride, int sstride, uint8_t y0);
|
||||
extern uint16_t _gfx_addr_base;
|
||||
|
||||
#define BW 16
|
||||
static uint8_t sprite[4 + BW * BW];
|
||||
|
||||
static void build_sprite(void)
|
||||
{
|
||||
uint8_t *p = sprite;
|
||||
int i;
|
||||
|
||||
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||||
for (i = 0; i < BW * BW; i++)
|
||||
*p++ = WHITE; /* непрозрачный: каждый байт пишется */
|
||||
}
|
||||
|
||||
/* Блит БЕЗ клипа: адрес = base + x, Port_Y стартует с y, строки шагают
|
||||
* Port_Y'ем (dstride 0). x/y могут выходить за экран — в этом и суть. */
|
||||
static void blit_noclip(int x, int y)
|
||||
{
|
||||
uint8_t *dst = (uint8_t *)(_gfx_addr_base + x);
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
_bgi_begin();
|
||||
_bgi_copy_rows_raw(sprite + 4, dst, BW, BW, 0, BW, (uint8_t)y);
|
||||
_bgi_end();
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
build_sprite();
|
||||
initgraph();
|
||||
|
||||
/* Фон синий. */
|
||||
setfillstyle(SOLID_FILL, BLUE);
|
||||
bar(0, 0, 319, 255);
|
||||
|
||||
/* Красные маркер-полосы: левые 8 колонок и верхние 8 строк —
|
||||
* сюда «завернётся» переполнение вправо/вниз, если железо не режет. */
|
||||
setfillstyle(SOLID_FILL, RED);
|
||||
bar(0, 0, 7, 255);
|
||||
bar(0, 0, 319, 7);
|
||||
|
||||
/* Зелёные маркеры границы экрана. */
|
||||
setcolor(GREEN);
|
||||
line(319, 0, 319, 255);
|
||||
line(0, 255, 319, 255);
|
||||
|
||||
/* Sanity: клипнутый putsprite тем же спрайтом в безопасном месте. */
|
||||
putsprite(140, 120, sprite);
|
||||
|
||||
/* Эксперимент — три no-clip блита за края: */
|
||||
blit_noclip(312, 100); /* правый: x+16=328, 8 колонок за 320 */
|
||||
blit_noclip(100, 250); /* нижний: y+16=266, 10 строк за 256 */
|
||||
blit_noclip(312, 250); /* угол: и вправо, и вниз */
|
||||
|
||||
getch();
|
||||
closegraph();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := sprites
|
||||
EXTRA_FLAGS ?= --gfx 256 --memory small
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* sprites — тест Фазы B спрайтового API (docs/sprite-api-design.md §3):
|
||||
* putsprite / movesprite / gfx_heal / gfx_blit_part поверх accel-ядра.
|
||||
*
|
||||
* Сцена: полосатый фон (вертикальные полосы 8px RED/LIGHTGRAY, банк
|
||||
* 0x50 → фон и в VRAM, и в ОЗУ-копии). Спрайт — шар 24×24 (YELLOW с
|
||||
* RED-ободом), углы = 0xFF (GFX_TRANSPARENT).
|
||||
*
|
||||
* Стадии (паузы по RTC, скриншот после каждой):
|
||||
* S1 фон + разметка.
|
||||
* S2 putsprite:
|
||||
* - в центре зоны (C) — прозрачные углы: полосы сквозь них;
|
||||
* - 4 клип-позиции: слева x=-12, справа x=308, сверху y=-10,
|
||||
* снизу y=244 — рисуется только видимая часть, соседняя
|
||||
* память не портится;
|
||||
* - heal-проба: putsprite в H=(60,130) и тут же gfx_heal того же
|
||||
* rect — шара БЫТЬ НЕ ДОЛЖНО (первая проверка heal src==dst
|
||||
* через акселератор — риск §10.4 дизайна);
|
||||
* - атлас: gfx_blit_part кадра 1 (MAGENTA-шар) из ленты 2×24×24
|
||||
* в (270,130) банком GFX_BANK_SPRITE.
|
||||
* Проверка A (по ОЗУ, getpixel = ОЗУ-копия): полосы под C целы.
|
||||
* S3 movesprite: шар из (16,60) 12 шагов по (+16,+8) до (208,156) —
|
||||
* след обязан быть чистым (heal старой позиции на каждом шаге).
|
||||
* Проверка B: ОЗУ вдоль пути — полосы.
|
||||
*
|
||||
* VRAM программно не читается (write-only) — корректность экрана
|
||||
* подтверждают скриншоты; программные проверки ловят порчу ОЗУ-копии.
|
||||
*
|
||||
* Запуск:
|
||||
* python3 toolchain/mame_interactive.py tests/sprites/sprites.exe \
|
||||
* --snap 13,17,21 --timeout 23
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <time.h>
|
||||
|
||||
#define BW 24 /* сторона спрайта */
|
||||
#define BG_Y0 40
|
||||
#define BG_Y1 199
|
||||
|
||||
#define CX 150 /* центральный putsprite */
|
||||
#define CY 100
|
||||
|
||||
static uint8_t ball[4 + BW * BW]; /* YELLOW шар */
|
||||
static uint8_t strip[4 + 2 * BW * BW]; /* лента: кадр0 YELLOW, кадр1 MAGENTA */
|
||||
|
||||
static uint8_t stripe_color(int x)
|
||||
{
|
||||
return ((x >> 3) & 1) ? LIGHTGRAY : RED;
|
||||
}
|
||||
|
||||
/* Шар: r<=9 — fill, r 9..11 — ring, дальше прозрачно (0xFF). */
|
||||
static uint8_t ball_pixel(int x, int y, uint8_t fill, uint8_t ring)
|
||||
{
|
||||
int dx = x - BW / 2, dy = y - BW / 2;
|
||||
int d2 = dx * dx + dy * dy;
|
||||
|
||||
if (((x == 0 || x == BW - 1) && ((y >=0 && y <= 3) || (y >=BW - 4 && y <= BW - 1))) ||
|
||||
((y == 0 || y == BW - 1) && ((x >=0 && x <= 3) || (x >=BW - 4 && x <= BW - 1)))) return GREEN;
|
||||
if (x==0) return GFX_TRANSPARENT;
|
||||
if (d2 <= 81) return fill;
|
||||
if (d2 <= 121) return ring;
|
||||
return GFX_TRANSPARENT;
|
||||
}
|
||||
|
||||
static void build_images(void)
|
||||
{
|
||||
uint8_t *p;
|
||||
int x, y, f;
|
||||
|
||||
p = ball;
|
||||
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||||
for (y = 0; y < BW; y++)
|
||||
for (x = 0; x < BW; x++)
|
||||
*p++ = ball_pixel(x, y, YELLOW, RED);
|
||||
|
||||
/* Лента 48×24: кадры лежат рядом по x (кадр N: sx = N*BW). */
|
||||
p = strip;
|
||||
*p++ = 2 * BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||||
for (y = 0; y < BW; y++)
|
||||
for (f = 0; f < 2; f++)
|
||||
for (x = 0; x < BW; x++)
|
||||
*p++ = ball_pixel(x, y, f ? LIGHTMAGENTA : YELLOW,
|
||||
f ? BLUE : RED);
|
||||
}
|
||||
|
||||
/* ОЗУ-копия (getpixel) в трёх точках зоны 24×24 — нетронутые полосы? */
|
||||
static uint8_t ram_is_stripes(int zx, int zy)
|
||||
{
|
||||
static const uint8_t off[3][2] = { {12, 12}, {8, 16}, {16, 6} };
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
int x = zx + off[i][0];
|
||||
if (getpixel(x, zy + off[i][1]) != stripe_color(x))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void say(int y, const char *s)
|
||||
{
|
||||
outtextxy(4, y, s);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint8_t chkA, chkB;
|
||||
int x, i;
|
||||
|
||||
build_images();
|
||||
initgraph();
|
||||
cleardevice();
|
||||
gfx_sprite_clip(1);
|
||||
setcolor(WHITE);
|
||||
|
||||
/* ---- S1: фон -------------------------------------------------- */
|
||||
say(4, "SPRITES: putsprite/movesprite/heal");
|
||||
for (x = 0; x < 320; x += 8) {
|
||||
setfillstyle(SOLID_FILL, stripe_color(x));
|
||||
bar(x, BG_Y0, x + 7, BG_Y1);
|
||||
}
|
||||
say(208, "S1 bg");
|
||||
(void)getchar();
|
||||
|
||||
/* ---- S2: putsprite + клипы + heal-проба + атлас ---------------- */
|
||||
putsprite(CX, CY, ball); /* центр: прозрачные углы */
|
||||
(void)getchar();
|
||||
putsprite(-12, 52, ball); /* клип слева */
|
||||
(void)getchar();
|
||||
putsprite(308, 82, ball); /* клип справа */
|
||||
(void)getchar();
|
||||
putsprite(297, 162, ball); /* клип справа */
|
||||
(void)getchar();
|
||||
putsprite(60, -10, ball); /* клип сверху */
|
||||
(void)getchar();
|
||||
putsprite(290, 244, ball); /* клип снизу */
|
||||
(void)getchar();
|
||||
putsprite(60, 130, ball); /* heal-проба: нарисовать... */
|
||||
(void)getchar();
|
||||
gfx_heal(60, 130, BW, BW); /* ...и стереть (шара НЕТ) */
|
||||
(void)getchar();
|
||||
gfx_set_bank(GFX_BANK_SPRITE); /* атлас: кадр 1 (MAGENTA) */
|
||||
(void)getchar();
|
||||
gfx_blit_part(270, 130, strip, BW, 0, BW, BW);
|
||||
(void)getchar();
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
(void)getchar();
|
||||
|
||||
chkA = ram_is_stripes(CX, CY);
|
||||
say(218, "S2 putsprite+clip, heal@60,130");
|
||||
say(228, chkA ? "A RAM under sprite: PASS"
|
||||
: "A RAM under sprite: FAIL");
|
||||
pause_sec(4);
|
||||
|
||||
/* ---- S3: movesprite — след обязан быть чистым ------------------ */
|
||||
x = 16;
|
||||
putsprite(x, 60, ball);
|
||||
(void)getchar();
|
||||
for (i = 0; i < 12; i++) {
|
||||
movesprite(x, 60 + (x - 16) / 2, x + 16, 60 + (x - 16 + 16) / 2,
|
||||
ball);
|
||||
x += 16;
|
||||
(void)getchar();
|
||||
}
|
||||
chkB = ram_is_stripes(16, 60) && ram_is_stripes(112, 108);
|
||||
say(238, chkB ? "B RAM along path: PASS"
|
||||
: "B RAM along path: FAIL");
|
||||
say(248, "S3 moved 16,60 -> 208,156 done");
|
||||
|
||||
(void)getchar();
|
||||
}
|
||||
Reference in New Issue
Block a user