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:
2026-07-12 21:47:37 +03:00
parent 78161561e7
commit 72ce66275e
42 changed files with 2835 additions and 32 deletions
+131
View File
@@ -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 (;;) { }
}