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:
+51
-1
@@ -46,9 +46,59 @@ void gfx_set_visible_page(uint8_t page); /* 0 or 1 */
|
||||
inline uint8_t gfx_get_visible_page(void) { return _gfx_visible_page; }
|
||||
void gfx_set_draw_page(uint8_t page); /* 0 or 1 */
|
||||
inline uint8_t gfx_get_draw_page(void) { return _gfx_draw_page; }
|
||||
inline void gfx_set_bank(uint8_t bank) { _gfx_bank = bank; } /* 0x50..0x5F */
|
||||
|
||||
/* Видеобанк W3 (0x50..0x5F): биты 2/3 номера страницы — аппаратные
|
||||
* подрежимы ЗАПИСИ (Иван Мак §4.3; docs/sprite-api-design.md §1),
|
||||
* действуют на ВСЕ примитивы рисования до следующего gfx_set_bank.
|
||||
* Биты 0/1 всегда 0 (резерв конфигураций). */
|
||||
#define GFX_BANK_NORMAL 0x50 /* запись в видео-ОЗУ + ОЗУ-копию */
|
||||
#define GFX_BANK_NOSHADOW 0x54 /* только видео-ОЗУ (врем. вывод) */
|
||||
#define GFX_BANK_TRANSPARENT 0x58 /* байт 0xFF не записывается */
|
||||
#define GFX_BANK_SPRITE 0x5C /* NOSHADOW + TRANSPARENT */
|
||||
|
||||
/* Прозрачный цвет аппаратной прозрачности (256-режим: пиксель;
|
||||
* 16-режим: ПАРА пикселей цвета 15). Цвет 255 нельзя нарисовать
|
||||
* через банк с битом 3 — он и есть прозрачный. */
|
||||
#define GFX_TRANSPARENT 0xFF
|
||||
|
||||
inline void gfx_set_bank(uint8_t bank) { _gfx_bank = bank; } /* GFX_BANK_* */
|
||||
inline uint8_t gfx_get_bank(void) { return _gfx_bank; }
|
||||
|
||||
/* Клип спрайтов по экрану (putsprite/movesprite/спрайтовый движок).
|
||||
* on=1 (дефолт) — клипить (спрайты могут уходить за края); on=0 —
|
||||
* ОТКЛЮЧИТЬ (приложение ГАРАНТИРУЕТ, что спрайты целиком на экране —
|
||||
* иначе запись за край портит соседнюю память, см. tests/spriteclip).
|
||||
* Выигрыш ~+19% на анимации: движок зовёт noclip-ядра без клип-кода
|
||||
* (не просто скип проверки — вся клип-математика физически отсутствует,
|
||||
* SDCC тесней раскладывает регистры). Флаг читается раз на спрайт.
|
||||
* НЕ влияет на putimage/gfx_blit (у них клип всегда). */
|
||||
extern uint8_t _gfx_sprite_clip;
|
||||
inline void gfx_sprite_clip(uint8_t on) { _gfx_sprite_clip = on; }
|
||||
inline uint8_t gfx_get_sprite_clip(void) { return _gfx_sprite_clip; }
|
||||
|
||||
/* ---- Блиттинг (ядро спрайтов; Фаза B sprite-api-design) ----------- *
|
||||
* Формат картинки — getimage: uint16 w, uint16 h, пиксели построчно
|
||||
* (1 байт/пиксель в 256-режиме). Буфер обязан лежать ВНЕ W3
|
||||
* (< 0xC000) — на время операции W3 замаплен на видеобанк.
|
||||
* Блит через accel block-copy (до 256 байт/burst), клиппинг по экрану
|
||||
* есть всегда; рисует ТЕКУЩИМ банком (gfx_set_bank). */
|
||||
|
||||
/* Блит целой картинки левым-верхним углом в (x,y). */
|
||||
void gfx_blit(int x, int y, const void *img);
|
||||
|
||||
/* Блит под-прямоугольника (sx,sy,w,h) картинки (атлас кадров:
|
||||
* кадр N ленты — sx = N*FRAME_W). Под-прямоугольник обязан лежать
|
||||
* внутри img; по экрану — клиппится. */
|
||||
void gfx_blit_part(int x, int y, const void *img,
|
||||
int sx, int sy, int w, int h);
|
||||
|
||||
/* Восстановить прямоугольник экрана из ОЗУ-копии (стирание спрайта/
|
||||
* оверлея, нарисованного банком с битом 2: 0x54/0x5C). Всегда
|
||||
* работает банком GFX_BANK_NORMAL независимо от gfx_set_bank;
|
||||
* клиппится по экрану. Буфер сохранения не нужен: ОЗУ-копия и есть
|
||||
* бэкап фона (чтение из #50..#5F всегда возвращает её). */
|
||||
void gfx_heal(int x, int y, int w, int h);
|
||||
|
||||
/* Block until the next frame (50 Hz). Типичный double-buffer:
|
||||
* gfx_set_draw_page(hidden); draw_frame(); gfx_wait_vsync();
|
||||
* gfx_set_visible_page(hidden); // tear-free flip */
|
||||
|
||||
@@ -194,9 +194,30 @@ unsigned imagesize(int left, int top, int right, int bottom);
|
||||
void getimage(int left, int top, int right, int bottom, void *bitmap);
|
||||
|
||||
/* Вывести образ левым-верхним углом в (left,top) операцией op
|
||||
* (COPY/XOR/OR/AND/NOT_PUT). */
|
||||
* (COPY/XOR/OR/AND/NOT_PUT). COPY_PUT — через акселератор, с
|
||||
* клиппингом по экрану и текущим банком gfx_set_bank (см. <gfx.h>);
|
||||
* XOR/OR/AND/NOT — по-пиксельно, без клиппинга. */
|
||||
void putimage(int left, int top, const void *bitmap, int op);
|
||||
|
||||
/* ---- Спрайты (аппаратная прозрачность; docs/sprite-api-design.md) - *
|
||||
* Спрайт — тот же буфер getimage-формата; прозрачные точки в данных =
|
||||
* байты 0xFF (GFX_TRANSPARENT). Прозрачность и сохранность фона
|
||||
* обеспечивает железо (банк GFX_BANK_SPRITE = 0x5C): прозрачные байты
|
||||
* не записываются, ОЗУ-копия фона не трогается — стирание без
|
||||
* save-буфера через gfx_heal (см. <gfx.h>).
|
||||
* ВАЖНО: фон/сцену рисовать ОБЫЧНЫМ банком (0x50) — то, что нарисовано
|
||||
* при активном бите 2 (0x54/0x5C), в ОЗУ-копию не попадает и heal
|
||||
* вернёт то, что лежало под ним. */
|
||||
|
||||
/* Вывести спрайт в (x,y): банк на время вызова — GFX_BANK_SPRITE,
|
||||
* затем прежний. Клиппится по экрану. */
|
||||
void putsprite(int x, int y, const void *img);
|
||||
|
||||
/* Переместить спрайт: восстановить фон под (oldx,oldy) (gfx_heal по
|
||||
* размерам img) и вывести спрайт в (x,y). Перекрытие старой и новой
|
||||
* позиций безопасно (heal читает фон из ОЗУ-копии). */
|
||||
void movesprite(int oldx, int oldy, int x, int y, const void *img);
|
||||
|
||||
/* ---- Текст ------------------------------------------------------- *
|
||||
* Шрифт 8×8 (системный). Рисуется текущим цветом на фоновом. */
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* sprite.h — managed-движок спрайтов (retained-модель) поверх ядра
|
||||
* gfx_blit/gfx_heal. Дизайн: docs/sprite-api-design.md §9.1.
|
||||
*
|
||||
* Мотивация — profile examples/balls vs asm-референса docs/samples/balls
|
||||
* (2026-07-12): ~2× разрыв не в W3-скобке, а в per-call C-обвязке
|
||||
* (клип/парс заголовка/save-restore банка/SDCC-фрейм), повторяемой 2×N
|
||||
* раз за кадр. Движок амортизирует её: sprite_update держит ОДНУ
|
||||
* W3-скобку и ОДИН банк на весь ПРОХОД, вызывая bracket-free ядра
|
||||
* (_gfx_heal_full/_gfx_blit_full) в цикле по спрайтам. DI остаётся
|
||||
* гранулярным (по строке в leaf) — под CBL/IM2-аудио длинный DI
|
||||
* недопустим.
|
||||
*
|
||||
* Retained-модель: sprite_move/frame/show/hide/touch меняют ТОЛЬКО
|
||||
* состояние (O(1), сколько угодно раз за кадр); всё рисование —
|
||||
* один раз в sprite_update, двухпроходно «heal ВСЕ → блит ВСЕ» (слияние
|
||||
* per-sprite выкусывает соседей на перекрытии — проверено). Учёт
|
||||
* «где спрайт реально нарисован на КАЖДОЙ из двух страниц» (drawn[2])
|
||||
* ведёт движок, не приложение.
|
||||
*
|
||||
* Формат картинки — getimage (uint16 w, uint16 h, пиксели построчно,
|
||||
* 1 байт/пиксель в 256-режиме); 0xFF = прозрачно (банк 0x5C). Буфер
|
||||
* обязан лежать ВНЕ W3 (< 0xC000). Размер КАДРА спрайта ≤ 64×64
|
||||
* (требование движка): кэш w,h — uint8_t, и блит идёт лин-ядром без
|
||||
* split'а на полосы >256 (спрайт заведомо ≤256 в строке). Для больших
|
||||
* картинок — gfx_blit/putimage напрямую (там общий путь со split'ом).
|
||||
* ЛЕНТА-АТЛАС может быть шире 64 (img_w — шаг), но КАДР ≤ 64.
|
||||
*
|
||||
* z-order = индекс в массиве (последний рисуется сверху). Массив
|
||||
* владеет приложением: без malloc, без лимитов; пересортировка z =
|
||||
* перестановка элементов (drawn-состояние едет со структурой).
|
||||
*/
|
||||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Публичные флаги sprite_t.flags. */
|
||||
#define SPR_VISIBLE 0x01u /* спрайт рисуется в sprite_update */
|
||||
#define SPR_ALWAYS 0x02u /* перерисовывать КАЖДЫЙ кадр (статик, над */
|
||||
/* которым постоянно ходят активные спрайты) */
|
||||
|
||||
/* Внутренние dirty-биты (владеет движок): «страница p требует обновления
|
||||
* этого спрайта». Приложению не трогать — только через sprite_*. */
|
||||
#define _SPR_DIRTY0 0x04u
|
||||
#define _SPR_DIRTY1 0x08u
|
||||
#define _SPR_DIRTY (_SPR_DIRTY0 | _SPR_DIRTY1)
|
||||
|
||||
typedef struct {
|
||||
const void *img; /* getimage-формат / атлас-лента (вне W3) */
|
||||
int x, y; /* ЛОГИЧЕСКАЯ позиция левого-верхнего угла кадра */
|
||||
int sx, sy; /* смещение кадра внутри img (атлас; 0 = целая) */
|
||||
uint8_t w, h; /* размер кадра (кэш заголовка), 1..255 */
|
||||
uint8_t flags; /* SPR_* | внутренние dirty */
|
||||
/* --- внутреннее (владеет движок) --- */
|
||||
struct {
|
||||
int x, y; /* где нарисован на странице p */
|
||||
uint8_t on; /* нарисован ли на странице p */
|
||||
} drawn[2];
|
||||
} sprite_t;
|
||||
|
||||
/* Инициализация: w,h из заголовка img, невидим, ничего не нарисовано.
|
||||
* Для АТЛАСА (кадр — под-прямоугольник) после init выставить s->w/s->h
|
||||
* в размер кадра и s->sx/s->sy — на нужный кадр (или sprite_frame). */
|
||||
void sprite_init(sprite_t *s, const void *img);
|
||||
|
||||
/* Кадр всей сцены: на ТЕКУЩЕЙ draw-странице (gfx_get_draw_page)
|
||||
* heal ВСЕХ старых позиций → блит ВСЕХ видимых (двухпроходно, одна
|
||||
* скобка/банк на проход). Спрайты без изменений с прошлого визита этой
|
||||
* страницы (и без SPR_ALWAYS) не трогаются. Для дабл-буфера звать после
|
||||
* gfx_set_draw_page(hidden); для одиночной страницы/курсора — прямо на
|
||||
* видимой. */
|
||||
void sprite_update(sprite_t *arr, uint8_t count);
|
||||
|
||||
/* Сахар канонического дабл-буфер-кадра: draw-страница = скрытая →
|
||||
* sprite_update → gfx_wait_vsync → сделать скрытую видимой (tear-free).
|
||||
* Динамический фон/HUD рисовать банком 0x50 на скрытой странице ДО
|
||||
* sprite_flip — тогда heal учтёт его через ОЗУ-копию. */
|
||||
void sprite_flip(sprite_t *arr, uint8_t count);
|
||||
|
||||
/* --- O(1) изменения состояния (inline: без call-оверхеда в физике) --- */
|
||||
|
||||
/* Переместить: меняет только логическую позицию + грязнит обе страницы. */
|
||||
inline void sprite_move(sprite_t *s, int x, int y) {
|
||||
if (s->x != x || s->y != y) { s->x = x; s->y = y; s->flags |= _SPR_DIRTY; }
|
||||
}
|
||||
|
||||
/* Сменить кадр атласа (под-прямоугольник img). */
|
||||
inline void sprite_frame(sprite_t *s, int sx, int sy) {
|
||||
if (s->sx != sx || s->sy != sy) { s->sx = sx; s->sy = sy; s->flags |= _SPR_DIRTY; }
|
||||
}
|
||||
|
||||
/* Показать (если был скрыт — грязнит обе страницы). */
|
||||
inline void sprite_show(sprite_t *s) {
|
||||
if (!(s->flags & SPR_VISIBLE)) s->flags |= (SPR_VISIBLE | _SPR_DIRTY);
|
||||
}
|
||||
|
||||
/* Скрыть (если был виден — грязнит: sprite_update сотрёт heal'ом). */
|
||||
inline void sprite_hide(sprite_t *s) {
|
||||
if (s->flags & SPR_VISIBLE) { s->flags &= ~SPR_VISIBLE; s->flags |= _SPR_DIRTY; }
|
||||
}
|
||||
|
||||
/* Принудительная перерисовка на ОБЕИХ страницах (напр. после того как
|
||||
* активный спрайт прошёл над статиком — приложение помечает статик). */
|
||||
inline void sprite_touch(sprite_t *s) { s->flags |= _SPR_DIRTY; }
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user