0e935343d9
Реализация §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>
151 lines
9.8 KiB
C
151 lines
9.8 KiB
C
/*
|
||
* 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
|