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,57 @@
|
||||
/*
|
||||
* _gfx_blit_full — bracket-free ядро блита под-прямоугольника картинки
|
||||
* getimage-формата (см. gfx_blit_part). Клип по экрану + нарезка на
|
||||
* полосы ≤256 + вызов accel-leaf'а _bgi_copy_rows_raw. БЕЗ _bgi_begin/
|
||||
* _bgi_end и БЕЗ смены банка — вызывающий обязан замапить W3 (_bgi_begin)
|
||||
* и выставить нужный _gfx_bank ДО вызова.
|
||||
*
|
||||
* Вынесено из gfx_blit_part (2026-07-12) ради batch-пасса спрайтового
|
||||
* движка v2: sprite_update держит одну W3-скобку и один банк на ВЕСЬ
|
||||
* проход, вызывая это ядро в цикле по спрайтам (амортизация обвязки —
|
||||
* см. docs/sprite-api-design.md §9.1). gfx_blit_part теперь = _bgi_begin
|
||||
* + это ядро + _bgi_end.
|
||||
*
|
||||
* Clip fast-path: если прямоугольник целиком на экране — 4 сравнения
|
||||
* вместо полного пути коррекции краёв (TODO «клип fast-path»).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_blit_full(int x, int y, const void *img,
|
||||
int sx, int sy, int w, int h)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
const uint8_t *src;
|
||||
uint16_t dst;
|
||||
int img_w;
|
||||
|
||||
/* img_w — ПОЛНАЯ ширина картинки/ленты из заголовка getimage
|
||||
* (p[0]=lo, p[1]=hi); это шаг между строками источника (sstride).
|
||||
* sx/sy — смещение кадра внутри картинки (атлас). Подробный разбор
|
||||
* формата и sx/sy — в common/_gfx_blit_sprite.c. */
|
||||
img_w = p[0] | (p[1] << 8);
|
||||
|
||||
/* Клип по экрану — только если хоть один край вылезает. */
|
||||
if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) {
|
||||
if (x < 0) { sx -= x; w += x; x = 0; }
|
||||
if (y < 0) { sy -= y; h += y; y = 0; }
|
||||
if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x;
|
||||
if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
}
|
||||
|
||||
/* src = img + 4 + sy*img_w + sx — без __mulint (обычно sy == 0). */
|
||||
src = p + 4 + sx;
|
||||
while (sy--)
|
||||
src += img_w;
|
||||
|
||||
dst = _gfx_addr_base + x;
|
||||
while (w > 256) {
|
||||
_bgi_blit_rows_raw(src, (uint8_t *)dst, 0 /* =256 */, (uint8_t)h,
|
||||
img_w, (uint8_t)y);
|
||||
src += 256;
|
||||
dst += 256;
|
||||
w -= 256;
|
||||
}
|
||||
_bgi_blit_rows_raw(src, (uint8_t *)dst, (uint8_t)w, (uint8_t)h,
|
||||
img_w, (uint8_t)y);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* _gfx_blit_sprite — лин-ядро блита СПРАЙТА (кадр ≤ 64×64, см. sprite.h):
|
||||
* то же, что _gfx_blit_full, но БЕЗ split'а на полосы >256 (спрайт
|
||||
* заведомо ≤256 байт в строке) и с 8-битными w/h (дешевле передача
|
||||
* аргументов). Один вызов _bgi_blit_rows_raw. БЕЗ _bgi_begin/_bgi_end
|
||||
* и смены банка — вызывающий (sprite_update/putsprite) держит скобку и
|
||||
* банк 0x5C. Проверок размера НЕТ (контракт ≤64 — в доке, не в коде).
|
||||
*
|
||||
* Клип по ЭКРАНУ остаётся (это функциональность — спрайты штатно уходят
|
||||
* за края): fast-path «целиком на экране» = 4 сравнения.
|
||||
*
|
||||
* ФОРМАТ getimage и параметры атласа
|
||||
* ----------------------------------
|
||||
* img указывает на getimage-буфер: 4-байтовый заголовок + пиксели.
|
||||
* Заголовок = uint16 width, uint16 height (little-endian):
|
||||
* p[0]=width lo, p[1]=width hi, p[2]=height lo, p[3]=height hi.
|
||||
* Дальше (p+4) — пиксели ПОСТРОЧНО, по width байт на строку.
|
||||
*
|
||||
* int img_w = p[0] | (p[1] << 8);
|
||||
* — это ПОЛНАЯ ширина картинки/ленты-атласа (склейка двух байтов
|
||||
* заголовка в 16-бит). Используется как ШАГ между строками источника
|
||||
* (sstride): чтобы спуститься на строку ниже в буфере, надо прибавить к
|
||||
* адресу целую ширину картинки. Для одиночного спрайта img_w == ширине
|
||||
* кадра; для ЛЕНТЫ-АТЛАСА (много кадров в ряд) img_w — ширина всей ленты
|
||||
* (> ширины кадра w), поэтому img_w остаётся int (лента может быть шире
|
||||
* 64, хотя КАДР ≤ 64).
|
||||
*
|
||||
* (sx, sy) — смещение КАДРА (под-прямоугольника) ВНУТРИ картинки:
|
||||
* левый-верхний угол вырезаемого кадра в пикселях. Для целого спрайта
|
||||
* sx = sy = 0. Для кадра N горизонтальной ленты: sx = N*FRAME_W, sy = 0
|
||||
* (вертикальные атласы — sy). Отсюда адрес первого пикселя кадра:
|
||||
* src = p + 4 (пропустить заголовок)
|
||||
* + sx (сместиться на sx пикселей вправо в первой строке)
|
||||
* + sy*img_w (спуститься на sy строк — цикл сложений ниже, без
|
||||
* умножения: обычно sy == 0).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void _gfx_blit_sprite(int x, int y, const void *img,
|
||||
int sx, int sy, uint8_t w, uint8_t h)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
const uint8_t *src;
|
||||
uint16_t dst;
|
||||
int img_w = p[0] | (p[1] << 8); /* ширина картинки/ленты = sstride */
|
||||
int cw = w, ch = h; /* рабочие w,h кадра (для клипа) */
|
||||
|
||||
if (x < 0 || y < 0 || x + cw > _bgi_maxx + 1 || y + ch > _bgi_maxy + 1) {
|
||||
if (x < 0) { sx -= x; cw += x; x = 0; }
|
||||
if (y < 0) { sy -= y; ch += y; y = 0; }
|
||||
if (x + cw > _bgi_maxx + 1) cw = _bgi_maxx + 1 - x;
|
||||
if (y + ch > _bgi_maxy + 1) ch = _bgi_maxy + 1 - y;
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
}
|
||||
|
||||
|
||||
/* Адрес первого пикселя кадра: заголовок(4) + sx вправо, затем sy
|
||||
* строк вниз (по img_w на строку) — циклом сложений, без __mulint
|
||||
* (обычно sy == 0: целый спрайт или горизонтальная лента). */
|
||||
src = p + 4 + sx;
|
||||
while (sy--)
|
||||
src += img_w;
|
||||
|
||||
dst = _gfx_addr_base + x;
|
||||
|
||||
_bgi_blit_rows_raw(src, (uint8_t *)dst, (uint8_t)cw, (uint8_t)ch,
|
||||
img_w, (uint8_t)y);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* _gfx_blit_sprite_noclip — блит спрайта БЕЗ клипа по экрану. Как
|
||||
* _gfx_blit_sprite, но клип-математики нет ВООБЩЕ (не скип проверки —
|
||||
* кода нет физически): приложение ГАРАНТИРУЕТ, что спрайт целиком на
|
||||
* экране (флаг gfx_sprite_clip(0)). Отдельная функция, а не ветка в
|
||||
* _gfx_blit_sprite: без клип-кода SDCC тесней раскладывает регистры
|
||||
* (меньше спиллов) — отсюда полный выигрыш ~+19% на анимации (замер
|
||||
* A/B на examples/balls 2026-07-12), а не только скип сравнений.
|
||||
*
|
||||
* ВНИМАНИЕ: запись за край экрана портит соседнюю память (Port_Y за 256
|
||||
* заворачивается наверх, x за 320 — в соседнюю строку/страницу; см.
|
||||
* tests/spriteclip). Вызывать только когда спрайт заведомо в пределах.
|
||||
*
|
||||
* Формат img/img_w/sx/sy — как в _gfx_blit_sprite (getimage-заголовок +
|
||||
* лента-атлас). БЕЗ _bgi_begin/_bgi_end и смены банка (держит вызывающий).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_blit_sprite_noclip(int x, int y, const void *img,
|
||||
int sx, int sy, uint8_t w, uint8_t h)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
const uint8_t *src;
|
||||
int img_w = p[0] | (p[1] << 8); /* ширина картинки/ленты = sstride */
|
||||
|
||||
src = p + 4 + sx;
|
||||
while (sy--)
|
||||
src += img_w;
|
||||
|
||||
_bgi_blit_rows_raw(src, (uint8_t *)(_gfx_addr_base + x),
|
||||
w, h, img_w, (uint8_t)y);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* _gfx_heal_full — bracket-free ядро heal прямоугольника экрана из
|
||||
* ОЗУ-копии (см. gfx_heal). Клип по экрану + нарезка на полосы ≤256 +
|
||||
* вызов accel-leaf'а _bgi_heal_rows_raw (src == dst, strides 0). БЕЗ
|
||||
* _bgi_begin/_bgi_end и БЕЗ смены банка — вызывающий обязан замапить W3
|
||||
* и выставить банк GFX_BANK_NORMAL (0x50) ДО вызова.
|
||||
*
|
||||
* Вынесено из gfx_heal (2026-07-12) ради batch-пасса движка v2:
|
||||
* sprite_update лечит ВСЕ старые позиции под одной W3-скобкой и одним
|
||||
* банком 0x50 (docs/sprite-api-design.md §9.1). gfx_heal теперь =
|
||||
* save-банк + set 0x50 + _bgi_begin + это ядро + _bgi_end + restore.
|
||||
*
|
||||
* Clip fast-path: целиком на экране → 4 сравнения вместо полного пути.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_heal_full(int x, int y, int w, int h)
|
||||
{
|
||||
uint8_t *scr;
|
||||
|
||||
if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) {
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x;
|
||||
if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
}
|
||||
|
||||
scr = (uint8_t *)(_gfx_addr_base + x);
|
||||
while (w > 256) {
|
||||
_bgi_heal_rows_raw(scr, y, 0 /* =256 */, (uint8_t)h);
|
||||
scr += 256;
|
||||
w -= 256;
|
||||
}
|
||||
_bgi_heal_rows_raw(scr, y, (uint8_t)w, (uint8_t)h);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* _gfx_heal_sprite — лин-ядро heal СПРАЙТА (≤ 64×64, см. sprite.h): как
|
||||
* _gfx_heal_full, но БЕЗ split'а на полосы >256 и с 8-битными w/h. Один
|
||||
* вызов _bgi_heal_rows_raw. БЕЗ _bgi_begin/_bgi_end и смены банка —
|
||||
* вызывающий (sprite_update) держит скобку и банк 0x50. Проверок
|
||||
* размера НЕТ (контракт ≤64 — в доке). Клип по экрану остаётся.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_heal_sprite(int x, int y, uint8_t w, uint8_t h)
|
||||
{
|
||||
uint8_t *scr;
|
||||
int cw = w, ch = h;
|
||||
|
||||
if (x < 0 || y < 0 || x + cw > _bgi_maxx + 1 || y + ch > _bgi_maxy + 1) {
|
||||
if (x < 0) { cw += x; x = 0; }
|
||||
if (y < 0) { ch += y; y = 0; }
|
||||
if (x + cw > _bgi_maxx + 1) cw = _bgi_maxx + 1 - x;
|
||||
if (y + ch > _bgi_maxy + 1) ch = _bgi_maxy + 1 - y;
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
}
|
||||
|
||||
scr = (uint8_t *)(_gfx_addr_base + x);
|
||||
_bgi_heal_rows_raw(scr, y, (uint8_t)cw, (uint8_t)ch);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* _gfx_heal_sprite_noclip — heal спрайта БЕЗ клипа по экрану. Как
|
||||
* _gfx_heal_sprite, но клип-математики нет (приложение гарантирует
|
||||
* пределы, флаг gfx_sprite_clip(0)). Отдельная функция ради полного
|
||||
* выигрыша (тесней codegen без клип-кода). БЕЗ скобки/смены банка.
|
||||
* ВНИМАНИЕ про запись за край — см. _gfx_blit_sprite_noclip.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _gfx_heal_sprite_noclip(int x, int y, uint8_t w, uint8_t h)
|
||||
{
|
||||
_bgi_heal_rows_raw((uint8_t *)(_gfx_addr_base + x), y, w, h);
|
||||
}
|
||||
@@ -18,3 +18,10 @@ uint8_t _gfx_bank = 0x50;
|
||||
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
|
||||
* режима/палитра, их не трогаем. */
|
||||
uint16_t _gfx_addr_base = 0xC000;
|
||||
|
||||
/* Клип спрайтов по экрану: 1 = включён (безопасно, дефолт), 0 = выключен
|
||||
* (приложение само гарантирует, что спрайты не выходят за края — даёт
|
||||
* ~+19% на анимации, см. docs/sprite-api-design.md). Читает движок/
|
||||
* putsprite/movesprite: при 1 зовут clip-ядра, при 0 — noclip-ядра.
|
||||
* Ненулевой дефолт → в _INITIALIZED (как _gfx_bank). */
|
||||
uint8_t _gfx_sprite_clip = 1;
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
/*
|
||||
* getimage — сохранить прямоугольник экрана в буфер bitmap.
|
||||
* Формат: uint16 width, uint16 height, затем w*h байт (по строкам).
|
||||
* Чтение raw в одной W3-скобке.
|
||||
*
|
||||
* Через accel-ядро: _bgi_copy_rows_raw строками экран→буфер (Фаза A
|
||||
* спрайтового дизайна). Чтение экрана возвращает ОЗУ-копию (видео-ОЗУ
|
||||
* write-only) — после рисования в temp-банке (0x54/0x5C) getimage
|
||||
* вернёт ФОН, а не то, что на экране (это и есть механизм heal).
|
||||
*
|
||||
* Клиппинга нет (контракт BGI: rect валиден); safe-версия отсекает
|
||||
* выход за экран целиком. Буфер bitmap обязан лежать вне W3
|
||||
* (< 0xC000) — на время операции W3 замаплен на видеобанк.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void getimage(int left, int top, int right, int bottom, void *bitmap)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)bitmap;
|
||||
const uint8_t *src;
|
||||
int w = right - left + 1;
|
||||
int h = bottom - top + 1;
|
||||
int x, y;
|
||||
int rem;
|
||||
|
||||
if (w <= 0 || h <= 0) return;
|
||||
#ifndef GFX_NOCHECK
|
||||
if (left < 0 || top < 0 || right > _bgi_maxx || bottom > _bgi_maxy)
|
||||
return;
|
||||
#endif
|
||||
|
||||
*p++ = (uint8_t)w;
|
||||
*p++ = (uint8_t)(w >> 8);
|
||||
*p++ = (uint8_t)h;
|
||||
*p++ = (uint8_t)(h >> 8);
|
||||
|
||||
/* Полосы <= 256 байт (лимит блока акселератора); шаг строки
|
||||
* буфера — полная ширина w у ОБЕИХ полос (полосы одной строки
|
||||
* лежат в буфере подряд). */
|
||||
src = (const uint8_t *)(_gfx_addr_base + left);
|
||||
rem = w;
|
||||
_bgi_begin();
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = 0; x < w; x++)
|
||||
*p++ = (uint8_t)_bgi_read_raw(left + x, top + y);
|
||||
while (rem > 256) {
|
||||
_bgi_copy_rows_raw(src, p, 0 /* =256 */, (uint8_t)h,
|
||||
w, 0, (uint8_t)top);
|
||||
src += 256;
|
||||
p += 256;
|
||||
rem -= 256;
|
||||
}
|
||||
_bgi_copy_rows_raw(src, p, (uint8_t)rem, (uint8_t)h,
|
||||
w, 0, (uint8_t)top);
|
||||
_bgi_end();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* gfx_blit — блит ЦЕЛОЙ картинки getimage-формата в (x,y) текущим
|
||||
* банком (gfx_set_bank). Читает w,h из заголовка и делегирует ядру
|
||||
* gfx_blit_part (клиппинг/полосы/accel — там).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_blit(int x, int y, const void *img)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
|
||||
gfx_blit_part(x, y, img, 0, 0,
|
||||
p[0] | (p[1] << 8), p[2] | (p[3] << 8));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* gfx_blit_part — блит под-прямоугольника картинки getimage-формата
|
||||
* (uint16 w, uint16 h, пиксели построчно) в (x,y) ТЕКУЩИМ банком
|
||||
* (gfx_set_bank): 0x50 обычный, 0x58 прозрачный (0xFF не пишется),
|
||||
* 0x54/0x5C временный (ОЗУ-копия не трогается). Ядро блиттинга поверх
|
||||
* accel-leaf'а _bgi_copy_rows_raw (docs/sprite-api-design.md §4.2):
|
||||
* putimage(COPY_PUT) — тонкая обёртка над этой функцией.
|
||||
*
|
||||
* Под-прямоугольник (sx,sy,w,h) ОБЯЗАН лежать внутри картинки (для
|
||||
* атласов кадров); по экрану клиппится здесь — всегда, в т.ч. в fast
|
||||
* (спрайты штатно уходят за края; цена — один расчёт на блит).
|
||||
*
|
||||
* Требование: img вне W3 (< 0xC000) — на время блита W3 замаплен на
|
||||
* видеобанк.
|
||||
*
|
||||
* Фаза A: объявление в _bgi.h (internal); Фаза B переносит прототип в
|
||||
* публичный gfx.h — символ уже итоговый. 256-режим: пиксель = байт;
|
||||
* при добавлении 16-цветного режима (Фаза 2 libbgi) адресную
|
||||
* арифметику по x/сдвигам придётся спустить в mode-specific leaf.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_blit_part(int x, int y, const void *img,
|
||||
int sx, int sy, int w, int h)
|
||||
{
|
||||
/* Разовый вызов: своя W3-скобка вокруг bracket-free ядра. Клип,
|
||||
* нарезку на полосы и accel-копию делает _gfx_blit_full; batch-путь
|
||||
* спрайтового движка зовёт то же ядро под ОДНОЙ скобкой на кадр. */
|
||||
_bgi_begin();
|
||||
_gfx_blit_full(x, y, img, sx, sy, w, h);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* gfx_heal — восстановить прямоугольник экрана из ОЗУ-копии (стирание
|
||||
* спрайта/оверлея, нарисованного банком с битом 2: 0x54/0x5C).
|
||||
*
|
||||
* Механизм (docs/sprite-api-design.md §2): чтение из #50..#5F всегда
|
||||
* возвращает ОЗУ-копию (видео-ОЗУ write-only), поэтому accel-копия
|
||||
* src == dst == адрес экрана в банке 0x50 читает ФОН из ОЗУ и пишет
|
||||
* его в видео-ОЗУ (+ в ОЗУ то же самое — безвредно). Оба stride = 0:
|
||||
* строку и чтения и записи выбирает один Port_Y. Save-буфер не нужен.
|
||||
*
|
||||
* Банк на время операции — ВСЕГДА GFX_BANK_NORMAL (независимо от
|
||||
* gfx_set_bank: heal через 0x54/0x5C не имел бы смысла — запись не
|
||||
* дошла бы до ОЗУ, а через 0x58 дырявила бы FF-байты фона);
|
||||
* пользовательский банк сохраняется и восстанавливается. Клип, полосы
|
||||
* и accel-heal — в bracket-free ядре _gfx_heal_full (общем с batch-путём
|
||||
* спрайтового движка); здесь — только банк-дисциплина и W3-скобка.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_heal(int x, int y, int w, int h)
|
||||
{
|
||||
uint8_t saved = gfx_get_bank();
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
_bgi_begin();
|
||||
_gfx_heal_full(x, y, w, h);
|
||||
_bgi_end();
|
||||
gfx_set_bank(saved);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* movesprite — переместить спрайт (кадр ≤ 64×64): восстановить фон под
|
||||
* старой позицией (heal по размерам img из заголовка) и вывести спрайт в
|
||||
* новой (putsprite). Порядок heal→draw делает перекрытие старой и новой
|
||||
* позиций безопасным: heal читает фон из ОЗУ-копии, которую спрайт
|
||||
* (банк 0x5C) никогда не трогал.
|
||||
*
|
||||
* Идёт через лин-ядро _gfx_heal_sprite (без split'а, 8-бит w/h) — банк
|
||||
* на время heal форсится 0x50 (иначе heal бессмыслен), затем putsprite.
|
||||
* Спрайтовая программа не тянет общие *_full.
|
||||
*
|
||||
* Канонический цикл анимации — docs/sprite-api-design.md §6:
|
||||
* putsprite(x, y, hero); ...
|
||||
* gfx_wait_vsync(); movesprite(x, y, nx, ny, hero);
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void movesprite(int oldx, int oldy, int x, int y, const void *img)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
uint8_t saved = gfx_get_bank();
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
_bgi_begin();
|
||||
if (_gfx_sprite_clip) _gfx_heal_sprite(oldx, oldy, p[0], p[2]);
|
||||
else _gfx_heal_sprite_noclip(oldx, oldy, p[0], p[2]);
|
||||
_bgi_end();
|
||||
gfx_set_bank(saved);
|
||||
|
||||
putsprite(x, y, img);
|
||||
}
|
||||
@@ -2,8 +2,20 @@
|
||||
* putimage — вывести образ bitmap левым-верхним углом в (left,top)
|
||||
* операцией op. Заголовок буфера: uint16 width, uint16 height.
|
||||
*
|
||||
* COPY — пишем src; XOR/OR/AND — комбинируем с экраном (нужно raw-
|
||||
* чтение); NOT — пишем ~src. Всё в одной W3-скобке (raw-плот/чтение).
|
||||
* COPY_PUT — через accel-ядро gfx_blit_part (block-copy строками, до
|
||||
* 256 байт за burst; Фаза A спрайтового дизайна) — С КЛИППИНГОМ по
|
||||
* экрану (superset поведения Turbo C, где выход за экран — UB) и
|
||||
* с учётом текущего банка (gfx_set_bank: 0x58 даёт аппаратную
|
||||
* прозрачность 0xFF, 0x54/0x5C — временный вывод).
|
||||
*
|
||||
* XOR/OR/AND — комбинируем с экраном по-пиксельно (raw-чтение +
|
||||
* raw-плот в одной W3-скобке); NOT — пишем ~src. Ускорение этих op
|
||||
* блочными режимами акселератора — «следующая версия» (см.
|
||||
* docs/sprite-api-design.md §9). Клиппинга на этом пути НЕТ
|
||||
* (per-pixel bounds-check остаётся только в safe-версии).
|
||||
*
|
||||
* Буфер bitmap обязан лежать вне W3 (< 0xC000) — на время операции W3
|
||||
* замаплен на видеобанк.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
@@ -15,9 +27,14 @@ void putimage(int left, int top, const void *bitmap, int op)
|
||||
|
||||
w = p[0] | (p[1] << 8);
|
||||
h = p[2] | (p[3] << 8);
|
||||
p += 4;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
if (op == COPY_PUT) {
|
||||
gfx_blit_part(left, top, bitmap, 0, 0, w, h);
|
||||
return;
|
||||
}
|
||||
|
||||
p += 4;
|
||||
_bgi_begin();
|
||||
for (y = 0; y < h; y++) {
|
||||
for (x = 0; x < w; x++) {
|
||||
@@ -26,9 +43,8 @@ void putimage(int left, int top, const void *bitmap, int op)
|
||||
case XOR_PUT: dst = (uint8_t)_bgi_read_raw(left + x, top + y) ^ src; break;
|
||||
case OR_PUT: dst = (uint8_t)_bgi_read_raw(left + x, top + y) | src; break;
|
||||
case AND_PUT: dst = (uint8_t)_bgi_read_raw(left + x, top + y) & src; break;
|
||||
case NOT_PUT: dst = (uint8_t)~src; break;
|
||||
case COPY_PUT:
|
||||
default: dst = src; break;
|
||||
case NOT_PUT:
|
||||
default: dst = (uint8_t)~src; break;
|
||||
}
|
||||
_bgi_plot_raw(left + x, top + y, dst);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* putsprite — вывести спрайт getimage-формата (0xFF = прозрачно, кадр
|
||||
* ≤ 64×64) в (x,y). Банк на время вызова — GFX_BANK_SPRITE (0x5C):
|
||||
* прозрачные байты (bit3) не пишутся, ОЗУ-копия фона (bit2) цела →
|
||||
* спрайт стирается gfx_heal/movesprite без save-буфера. Пользовательский
|
||||
* банк сохраняется/восстанавливается (уважаем вложенный temp-режим).
|
||||
*
|
||||
* Идёт через лин-ядро _gfx_blit_sprite (без split'а >256, 8-бит w/h) —
|
||||
* спрайтовая программа не тянет общий _gfx_blit_full. Клип по экрану —
|
||||
* в ядре. w,h — младшие байты заголовка (кадр ≤ 64).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void putsprite(int x, int y, const void *img)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
uint8_t saved = gfx_get_bank();
|
||||
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
_bgi_begin();
|
||||
if (_gfx_sprite_clip) _gfx_blit_sprite(x, y, img, 0, 0, p[0], p[2]);
|
||||
else _gfx_blit_sprite_noclip(x, y, img, 0, 0, p[0], p[2]);
|
||||
_bgi_end();
|
||||
gfx_set_bank(saved);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* sprite_flip — сахар канонического дабл-буфер-кадра поверх
|
||||
* sprite_update (docs/sprite-api-design.md §9.1):
|
||||
* draw-страница = скрытая → sprite_update → ждать кадр → показать её.
|
||||
* Флип по vsync делает переход tear-free. Приложение, рисующее свой
|
||||
* динамический фон/HUD, делает это банком 0x50 на СКРЫТОЙ странице ДО
|
||||
* sprite_flip (тогда heal учтёт его через ОЗУ-копию) — либо зовёт
|
||||
* составные части (gfx_set_draw_page/sprite_update/gfx_wait_vsync/
|
||||
* gfx_set_visible_page) сам.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
#include <sprite.h>
|
||||
|
||||
void sprite_flip(sprite_t *arr, uint8_t count)
|
||||
{
|
||||
uint8_t hidden = gfx_get_visible_page() ^ 1;
|
||||
|
||||
gfx_set_draw_page(hidden);
|
||||
sprite_update(arr, count);
|
||||
gfx_wait_vsync();
|
||||
gfx_set_visible_page(hidden);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* sprite_init — инициализировать спрайт: img + размер кадра из заголовка
|
||||
* getimage (uint16 w, uint16 h). Спрайт невидим (flags = 0) и нигде не
|
||||
* нарисован (drawn[*].on = 0) — первый sprite_show + sprite_update его
|
||||
* выведет. Для атласа после init перезадать s->w/s->h (размер кадра) и
|
||||
* s->sx/s->sy. См. docs/sprite-api-design.md §9.1.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
#include <sprite.h>
|
||||
|
||||
void sprite_init(sprite_t *s, const void *img)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
|
||||
s->img = img;
|
||||
s->x = 0;
|
||||
s->y = 0;
|
||||
s->sx = 0;
|
||||
s->sy = 0;
|
||||
/* Размер кадра ≤ 255 (см. sprite.h) → младший байт uint16-заголовка;
|
||||
* старший байт для спрайта-кадра всегда 0. */
|
||||
s->w = p[0];
|
||||
s->h = p[2];
|
||||
s->flags = 0;
|
||||
s->drawn[0].on = 0;
|
||||
s->drawn[1].on = 0;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* sprite_update — отрисовать кадр сцены на ТЕКУЩЕЙ draw-странице p
|
||||
* (gfx_get_draw_page). Ядро batch-пасса движка v2 (см.
|
||||
* docs/sprite-api-design.md §9.1): вся работа кадра — здесь, двухпроходно
|
||||
* «heal ВСЕ → блит ВСЕ», под ОДНОЙ W3-скобкой и ОДНИМ банком на проход.
|
||||
*
|
||||
* Дисциплина двух проходов ОБЯЗАТЕЛЬНА: слияние heal+блит per-sprite не
|
||||
* работает на перекрытиях (heal соседа выкусывает только что
|
||||
* нарисованный спрайт — проверено на examples/balls 2026-07-11).
|
||||
*
|
||||
* Что перерисовывается: спрайт, у которого страница p ГРЯЗНАЯ (dbit —
|
||||
* взведён sprite_move/frame/show/hide/touch) ИЛИ флаг SPR_ALWAYS.
|
||||
* Неизменившиеся с прошлого визита этой страницы (и без SPR_ALWAYS) не
|
||||
* трогаются — суть retained-модели.
|
||||
*
|
||||
* Проход 1 (банк 0x50): heal старой позиции drawn[p] у тех, кто нарисован
|
||||
* (drawn[p].on) и требует обновления → снять drawn[p].on.
|
||||
* Проход 2 (банк 0x5C): блит видимых, требующих обновления, в (x,y) кадром
|
||||
* (sx,sy) → записать drawn[p] = (x,y), on = 1; снять dbit (страница p
|
||||
* актуальна для этого спрайта). z-order = порядок массива.
|
||||
*
|
||||
* DI живёт внутри leaf'ов (по строке) — скобка _bgi_begin/_bgi_end
|
||||
* прерывания НЕ трогает (только мапит W3), поэтому длина прохода не
|
||||
* влияет на аудио-ISR. w,h кадра ≤ 255 (см. sprite.h).
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
#include <sprite.h>
|
||||
|
||||
void sprite_update(sprite_t *arr, uint8_t count)
|
||||
{
|
||||
uint8_t p = gfx_get_draw_page(); /* 0 / 1 */
|
||||
uint8_t dbit = p ? _SPR_DIRTY1 : _SPR_DIRTY0;
|
||||
uint8_t saved = gfx_get_bank();
|
||||
uint8_t clip = _gfx_sprite_clip; /* раз на кадр: clip- или noclip-ядро */
|
||||
uint8_t i;
|
||||
sprite_t *s;
|
||||
|
||||
/* Проход 1: heal ВСЕХ старых позиций, требующих обновления. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
_bgi_begin();
|
||||
for (i = 0, s = arr; i < count; i++, s++) {
|
||||
if (s->drawn[p].on && (s->flags & (dbit | SPR_ALWAYS))) {
|
||||
if (clip) _gfx_heal_sprite(s->drawn[p].x, s->drawn[p].y, s->w, s->h);
|
||||
else _gfx_heal_sprite_noclip(s->drawn[p].x, s->drawn[p].y, s->w, s->h);
|
||||
s->drawn[p].on = 0;
|
||||
}
|
||||
}
|
||||
_bgi_end();
|
||||
|
||||
/* Проход 2: блит ВСЕХ видимых, требующих обновления. */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
_bgi_begin();
|
||||
for (i = 0, s = arr; i < count; i++, s++) {
|
||||
if (s->flags & (dbit | SPR_ALWAYS)) {
|
||||
if (s->flags & SPR_VISIBLE) {
|
||||
if (clip) _gfx_blit_sprite(s->x, s->y, s->img, s->sx, s->sy, s->w, s->h);
|
||||
else _gfx_blit_sprite_noclip(s->x, s->y, s->img, s->sx, s->sy, s->w, s->h);
|
||||
s->drawn[p].x = s->x;
|
||||
s->drawn[p].y = s->y;
|
||||
s->drawn[p].on = 1;
|
||||
}
|
||||
s->flags &= ~dbit; /* страница p обновлена для спрайта */
|
||||
}
|
||||
}
|
||||
_bgi_end();
|
||||
|
||||
gfx_set_bank(saved);
|
||||
}
|
||||
Reference in New Issue
Block a user