Files
Sprinter-SDCC/libbgi/include/sprite.h
T
snark13 0e935343d9 libbgi: W0-атласы спрайтов — загрузчик, движок, упаковщик (все проверки PASS)
Реализация §3.1/§9в: атлас = один .atl-файл = одна EMM-страница,
подключаемая в W0 на время блита.

- atlas_load: read() файла целиком в страницу через W3 + патч ISR-стаба
  (0x38: JP _gfx_w0_isr; 0x66: RETN); atlas_free/atlas_image/
  atlas_sprite_init; gfx_w0_map/unmap для ручных вызовов.
- _gfx_w0_isr (стаб из tests/w0page): свап на ядро DSS → честный 0x38 →
  restore спрайт-страницы; покрывает IM1 и IM2-чейн.
- sprite_t.page (0 = обычная память); sprite_update в блит-проходе
  мапит страницу по смене (один OUT на атлас), эпилог возвращает DSS
  (+52 Б на движок — цена фичи).
- toolchain/mkatlas.py: PNG (indexed) / raw → .atl; заголовок 0x100,
  каталог 0x68 (19 лент), файл-офсет == офсет страницы == W0-адрес.
- Сплит _gfx_sprite_fns → _gfx_blit_fn.c + _gfx_heal_fn.c (1 указатель
  = 1 модуль): putsprite-only программа не тянет heal-ядро (spriteclip
  ловил +292 Б; теперь −68 Б от эталона).

tests/atlas (MAME dev, PASS): загрузка (count/страница), каталог и
данные по W0-адресам (заголовок ленты через gfx_w0_map), 3 спрайта из
двух лент через движок — пиксели проверены read_vram побайтно
(0x10/0x12/0x21, прозрачный угол = фон).

docs: §9г — предложение авто-анимации (кадровая ±step без умножений,
tween-Брезенхэм порциями, тикер через funcptr) — ОБСУЖДАЕТСЯ.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:52:00 +03:00

151 lines
9.8 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.
/*
* 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 */
uint8_t page; /* 0 = img в обычной памяти; иначе физ. страница *
* W0-атласа (img — адрес 0x0100-0x3FFF, движок *
* сам мапит страницу в W0 на время блита) */
/* --- внутреннее (владеет движок) --- */
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; }
/* ==== Атласы в EMM-страницах (W0) ====================================
* Файл .atl (docs/sprite-api-design.md §3.1, вариант II): 0x100-байтовый
* заголовок (магия 'SPA1', count; каталог с 0x68) + getimage-ленты;
* файл-офсет == офсет в странице == адрес в W0. Загрузчик читает файл
* ЦЕЛИКОМ в EMM-страницу через W3 и патчит ISR-стаб (0x38/0x66) —
* страница безопасна в W0 при включённых прерываниях (§9в).
*
* ВАЖНО: указатели лент — адреса 0x0100-0x3FFF, разыменовывать их можно
* ТОЛЬКО при подключенной странице (sprite_update мапит сам; для ручного
* putsprite/gfx_blit_part — обернуть в gfx_w0_map/gfx_w0_unmap).
* Пока страница в W0 — НИКАКИХ ESTEX/BIOS-вызовов. */
typedef struct {
uint8_t page; /* физ. страница (маппить в W0/W3) */
uint8_t blk; /* EMM-блок — для atlas_free */
uint8_t count; /* число лент в атласе */
} atlas_t;
/* Загрузить .atl в свежую EMM-страницу (read в W3 + патч стаба).
* 0 — OK; -1 + errno (файл/память/формат). */
int atlas_load(atlas_t *a, const char *path);
/* Освободить EMM-страницу атласа (все sprite_t с его лентами должны
* быть скрыты/переинициализированы ДО вызова). */
void atlas_free(atlas_t *a);
/* Указатель-лента idx (W0-адрес, см. ВАЖНО выше) — для gfx_blit_part
* и ручных вызовов; кадр (i,j) ленты: sx = i*fw, sy = j*fh. */
const void *atlas_image(const atlas_t *a, uint8_t idx);
/* Инициализировать спрайт лентой idx атласа: img/w/h (размер КАДРА из
* каталога) /page; невидим, кадр (0,0). Аналог sprite_init для W0. */
void atlas_sprite_init(sprite_t *s, const atlas_t *a, uint8_t idx);
/* Ручное подключение страницы атласа в W0 (для putsprite/gfx_blit_part
* вне движка). map ставит и _gfx_w0_cur (ISR-стаб восстановит страницу
* после прерывания), unmap возвращает страницу ядра DSS. */
void gfx_w0_map(uint8_t page);
void gfx_w0_unmap(void);
#endif