b9ddce8d34
Оптимизации A+B (профиль rpgwalk-15: активная часть кадра 410К → 307К
тактов из 430080; лимит спрайтов 16×16 на стабильные 48.8 fps: 14 → ~21):
- (B) sprite_t.src/stride — готовый адрес кадра: считают только
sprite_frame (теперь функция, одно умножение на СМЕНУ кадра) и тикер
(±an_step БАЙТ инкрементально); блит-ядра принимают src+stride,
img/sx/sy из сигнатуры ушли. Блит 177К → 146К на кадр.
- (A) тик 100К → 37.7К: tween переформулирован Брезенхэм → беззнаковый
DDA (mv_rem/mv_acc, «приехали» = rem==0 — без знаковых 16-бит
сравнений), tick_move и tick_anim — ручной asm (SDCC спиллит такие
функции в IX-фрейм ~100 обращений; C-реструктуризации не помогали —
проверено кодогеном). Биты an_flags переименованы по категориям
(_SPR_STRIP_HORZ, _SPR_PP_BACK).
Y-сортировка (gfx_sprite_ysort, идеи пользователя — 8-бит ключ,
персистентность):
- painter's algorithm по ключу {layer:8, clamp_y:8}; поле
sprite_t.layer (в КОНЦЕ структуры — asm-офсеты не сдвигает): слои
сцены в одном массиве/одном sprite_update;
- ПЕРСИСТЕНТНАЯ asm-таблица {key16, ptr16}: resort порядка прошлого
кадра (почти линейно), rebuild при смене arr/count; массив
приложения не трогается; ~28К/15 спрайтов (с нуля было 44К);
- компоненты YSORT_Y/YSORT_LAYER отключаемы независимо масками ключа
(без ветвлений в сортировщике); ВНИМАНИЕ: mode=1 значит Y-only,
полный порядок = YSORT_Y|YSORT_LAYER;
- funcptr-DCE: выключено = код и таблица не линкуются (rpgwalk −190 Б);
- ПРАВИЛО в sprite.h: два sprite_update на страницу запрещены (heal
второй группы стирает спрайты первой — ОЗУ-копия чистая).
Попутные фиксы:
- libbgi/Makefile: .rel зависят от заголовков (HDRS) — stale .rel со
старой раскладкой sprite_t молча ломал рантайм;
- rpgprof: --memory small (перерос tiny: BSS вылезал за W2 → мгновенный
«Unexpected application termination»; mkexe это пока не ловит);
- tests/spranim: проверки переведены на кэш src, добавлены T6 (reframe
после тикера) и T7 (Y-сортировка: порядок, слои, персистентный
resort, LAYER-only) — 7/7 PASS в MAME.
Доки: §9д — новый бюджет (19.5К/спрайт), §9е — ПЛАН FPS-делителя
(frame pacing, gfx_set_fps_div); TODO — дизайн цепочки irq-обработчиков.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
312 lines
24 KiB
C
312 lines
24 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)
|
||
|
||
/* Статус-биты sprite_t.an_flags (чтение — sprite_anim_status()):
|
||
* *_ON — анимация активна (тикается в sprite_update); *_DONE — дошла
|
||
* до конца сама (one-shot до last / перемещение до цели) или была
|
||
* добита sprite_move_stop(at_target=1). DONE сбрасывается новым
|
||
* запуском соответствующей анимации. */
|
||
#define SPR_ANIM_ON 0x01u /* кадровая анимация идёт */
|
||
#define SPR_ANIM_DONE 0x02u /* one-shot дошёл до last */
|
||
#define SPR_MOVE_ON 0x04u /* tween-перемещение идёт */
|
||
#define SPR_MOVE_DONE 0x08u /* перемещение достигло цели */
|
||
|
||
/* Внутренние биты an_flags (владеет движок) — три РАЗНЫЕ категории:
|
||
* режим анимации (зеркало параметра mode), свойство ЛЕНТЫ (ориентация
|
||
* данных в атласе) и runtime-состояние (меняется само в процессе). */
|
||
#define _SPR_PP_BACK 0x10u /* runtime: маятник сейчас идёт назад */
|
||
#define _SPR_STRIP_HORZ 0x20u /* лента: кадры вдоль X (двигается sx) */
|
||
#define _SPR_ANIM_PP 0x40u /* режим: маятник */
|
||
#define _SPR_ANIM_ONCE 0x80u /* режим: one-shot */
|
||
|
||
/* Режимы sprite_anim(). */
|
||
#define ANIM_LOOP 0u /* 0,1,..,last,first,.. (цикл) */
|
||
#define ANIM_PINGPONG 1u /* 0,1,..,last,last-1,..,first,1,.. */
|
||
#define ANIM_ONCE 2u /* first..last, замереть на last + DONE */
|
||
#define ANIM_HORIZ 0x80u /* ИЛИ-флаг: кадры вдоль X (сетка nx>1) */
|
||
|
||
/* ВНИМАНИЕ: порядок полей ЗАШИТ в asm-ядро тикера (офсеты полей в
|
||
* common/_sprite_tick.c, tick_move) — при изменении порядка/типов
|
||
* пересчитать офсеты там (offsetof-пробником, рецепт в его шапке). */
|
||
typedef struct {
|
||
const void *img; /* getimage-формат / атлас-лента (вне W3) */
|
||
/* --- кэш адреса кадра (владеет движок; менять ТОЛЬКО через
|
||
* sprite_frame) ---
|
||
* src — ГОТОВЫЙ адрес первого пикселя ТЕКУЩЕГО кадра (img + 4 +
|
||
* sx + sy*stride). Блит берёт его как есть: ни чтения заголовка,
|
||
* ни умножения в кадровом цикле. Тикер анимации двигает src
|
||
* инкрементально (±an_step байт); sx/sy при этом НЕ обновляются —
|
||
* они «последняя позиция, установленная sprite_frame», и снова
|
||
* становятся истиной при следующем sprite_frame/sprite_anim
|
||
* (те ставят ось абсолютно, а не относительно). */
|
||
const uint8_t *src; /* адрес пикселей текущего кадра */
|
||
uint16_t stride; /* полная ширина img в байтах (шаг строки) */
|
||
int x, y; /* ЛОГИЧЕСКАЯ позиция левого-верхнего угла кадра */
|
||
int sx, sy; /* смещение кадра, заданное sprite_frame (см. src)*/
|
||
uint8_t w, h; /* размер кадра (кэш заголовка), 1..255 */
|
||
uint8_t flags; /* SPR_* | внутренние dirty */
|
||
uint8_t page; /* 0 = img в обычной памяти; иначе физ. страница *
|
||
* W0-атласа (img — адрес 0x0100-0x3FFF, движок *
|
||
* сам мапит страницу в W0 на время блита) */
|
||
/* --- авто-анимация (владеет тикер; настраивать ТОЛЬКО через
|
||
* sprite_anim../sprite_moveto.., читать — inline-аксессорами) --- */
|
||
uint8_t an_flags; /* статус: SPR_ANIM_.. | SPR_MOVE_.. */
|
||
uint8_t an_frame; /* ТЕКУЩИЙ индекс кадра (0-based по ленте) */
|
||
uint8_t an_first, an_last; /* границы анимации (индексы) */
|
||
uint8_t an_speed, an_timer; /* кадров между сменами / счётчик */
|
||
uint16_t an_step; /* шаг кадра в БАЙТАХ src: fw (гориз. лента) *
|
||
* или fh*stride (верт.) — считает sprite_anim */
|
||
const uint8_t *an_src0; /* src кадра first (для wrap цикла) */
|
||
uint8_t mv_speed, mv_timer; /* кадров между шагами / счётчик */
|
||
uint8_t mv_step; /* макс. пикселей вдоль большей оси за шаг */
|
||
int mv_tx, mv_ty; /* цель (для sprite_move_stop) */
|
||
/* DDA по главной оси (беззнаковый — на Z80 16-бит беззнаковые
|
||
* сравнения в разы дешевле знаковых Брезенхэма, 2026-07-14):
|
||
* главная ось шагает каждую итерацию, минорная подтягивается
|
||
* аккумулятором; «приехали» = mv_rem == 0 (сравнений координат нет). */
|
||
uint16_t mv_rem; /* осталось шагов главной оси до цели */
|
||
uint16_t mv_acc; /* аккумулятор минорной оси (старт dmaj/2) */
|
||
uint16_t mv_dmaj, mv_dmin; /* |дельты| главной / минорной осей */
|
||
uint8_t mv_ymaj; /* 1 = главная ось Y (иначе X) */
|
||
int8_t mv_sgnmaj, mv_sgnmin;/* знаки шага главной/минорной осей */
|
||
/* --- внутреннее (владеет движок) --- */
|
||
struct {
|
||
int x, y; /* где нарисован на странице p */
|
||
uint8_t on; /* нарисован ли на странице p */
|
||
} drawn[2];
|
||
/* Слой для Y-сортировки (см. gfx_sprite_ysort): при включённой
|
||
* сортировке спрайты упорядочиваются по {layer, y} — слой старше:
|
||
* слой 1 ВСЕГДА поверх слоя 0 (ходячие/летающие/курсор...), внутри
|
||
* слоя — painter по y. 0 после sprite_init; без сортировки не
|
||
* используется (порядок = индекс массива). Поле В КОНЦЕ структуры:
|
||
* офсеты полей выше зашиты в asm тикера/сортировщика. */
|
||
uint8_t layer;
|
||
} sprite_t;
|
||
|
||
/* Инициализация: w,h из заголовка img, кадр (0,0), невидим, ничего не
|
||
* нарисовано. Для АТЛАСА (кадр — под-прямоугольник) после init
|
||
* выставить s->w/s->h в размер кадра и перейти на нужный кадр ТОЛЬКО
|
||
* через sprite_frame (он ведёт кэш src; голые s->sx/s->sy кэш не
|
||
* обновят). */
|
||
void sprite_init(sprite_t *s, const void *img);
|
||
|
||
/* Кадр всей сцены: на ТЕКУЩЕЙ draw-странице (gfx_get_draw_page)
|
||
* heal ВСЕХ старых позиций → блит ВСЕХ видимых (двухпроходно, одна
|
||
* скобка/банк на проход). Спрайты без изменений с прошлого визита этой
|
||
* страницы (и без SPR_ALWAYS) не трогаются. Для дабл-буфера звать после
|
||
* gfx_set_draw_page(hidden); для одиночной страницы/курсора — прямо на
|
||
* видимой.
|
||
*
|
||
* ВСЯ СЦЕНА СТРАНИЦЫ — ОДИН ВЫЗОВ! Два sprite_update на одну страницу
|
||
* (например «сначала ходячие, потом летающие») ЛОМАЮТ перекрытия МЕЖДУ
|
||
* группами: heal второй группы берёт из ОЗУ-копии чистый ФОН (спрайты
|
||
* в копию не пишутся — банк 0x5C) и стирает нарисованных первой
|
||
* группой; двухпроходная дисциплина «heal все → блит все» покрывает
|
||
* перекрытия только ВНУТРИ одного вызова. Логические группы держать
|
||
* диапазонами индексов одного массива (z-порядок групп = порядок
|
||
* диапазонов; слоение при Y-сортировке — полем layer). */
|
||
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);
|
||
|
||
/* Режимы gfx_sprite_ysort (битовая маска компонентов ключа). */
|
||
#define YSORT_OFF 0u /* сортировки нет: z-order = индекс массива */
|
||
#define YSORT_Y 0x01u /* painter по y (нижние поверх верхних) */
|
||
#define YSORT_LAYER 0x02u /* слои: layer 1 поверх layer 0 */
|
||
|
||
/* Y-сортировка блит-прохода (painter's algorithm): mode — какие
|
||
* компоненты ключа участвуют в порядке отрисовки:
|
||
* YSORT_Y | YSORT_LAYER — полный порядок {layer, y}: сначала слои
|
||
* (спрайт слоя 1 ВСЕГДА поверх слоя 0 — ходячие/летающие/
|
||
* курсор...), внутри слоя — по y (нижние по экрану поверх
|
||
* верхних, «ближе к зрителю»);
|
||
* YSORT_Y — только painter по y, слои игнорируются;
|
||
* YSORT_LAYER — только слои; внутри слоя порядок стабилен
|
||
* (наследуется от прошлого кадра / порядка массива);
|
||
* YSORT_OFF (0, дефолт) — z-order = индекс массива.
|
||
* Выключенный компонент маскируется в ключе (2 AND на спрайт, ~1К на
|
||
* 15 спрайтов) — сортировщик не замедляется ветвлениями.
|
||
*
|
||
* ПЕРСИСТЕНТНОСТЬ: таблица порядка живёт МЕЖДУ кадрами — каждый кадр
|
||
* ключи обновляются и порядок пересортировывается вставками; спрайты
|
||
* двигаются на пиксели за кадр, порядок почти актуален → почти
|
||
* линейно. Цена (замер rpgwalk-15, 2026-07-14): ~28К тактов на 15
|
||
* спрайтов (~2К/спрайт, ~6% бюджета кадра; построение с нуля каждый
|
||
* кадр стоило 44К). Массив приложения при этом НЕ трогается (sp[2]
|
||
* всегда персонаж 2) — сортируются внутренние записи-указатели. При
|
||
* смене arr/count (другая сцена) таблица перестраивается сама; первый
|
||
* кадр после включения/смены — полная сортировка (разово дороже).
|
||
*
|
||
* Сортировка СТАБИЛЬНА относительно ПРОШЛОГО КАДРА: спрайты с равным
|
||
* ключом сохраняют взаимный порядок прошлого кадра (не мерцают), а не
|
||
* порядок массива. Лимит: до 32 спрайтов на вызов sprite_update;
|
||
* больше — кадр рисуется без сортировки (безопасный фолбэк).
|
||
* Выключено = 0 тактов И 0 байт: код сортировщика и таблица не
|
||
* линкуются (funcptr-DCE). heal-проход порядка не требует и остаётся
|
||
* линейным.
|
||
*
|
||
* Замечания: (1) перекрывающиеся спрайты обязаны быть dirty ОБА, чтобы
|
||
* смена их взаимного порядка отрисовалась — движущиеся грязнятся сами,
|
||
* статики помечать sprite_touch (правило то же, что и без сортировки);
|
||
* (2) сортировка перемешивает порядок W0-атласов — смен страницы W0
|
||
* может стать больше (один дешёвый OUT на смену). */
|
||
void gfx_sprite_ysort(uint8_t on);
|
||
|
||
/* --- 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: пересчитывает
|
||
* кэш src (одно 16-бит умножение sy*stride) — при АКТИВНОЙ кадровой
|
||
* анимации не звать (тикер ведёт src сам). */
|
||
void sprite_frame(sprite_t *s, int sx, int sy);
|
||
|
||
/* Показать (если был скрыт — грязнит обе страницы). */
|
||
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; }
|
||
|
||
/* ==== Авто-анимация (§9г дизайна) ====================================
|
||
* Тикается САМА в начале каждого sprite_update (раз на кадр), код
|
||
* тикера подключается через funcptr — программа без анимаций его не
|
||
* линкует. Кадровая анимация и tween-перемещение НЕЗАВИСИМЫ и могут
|
||
* идти одновременно. Пока кадровая активна — не трогать sprite_frame
|
||
* вручную (тикер ведёт ось сам); границы кадров должны лежать вдоль
|
||
* ОДНОЙ оси ленты (вертикальная по умолчанию, ANIM_HORIZ — вдоль X). */
|
||
|
||
/* Запустить кадровую анимацию: кадры first..last (индексы вдоль оси
|
||
* ленты), смена раз в speed кадров (1 = каждый кадр), mode = ANIM_LOOP/
|
||
* ANIM_PINGPONG/ANIM_ONCE (| ANIM_HORIZ для горизонтальной ленты).
|
||
* Старт с first; сбрасывает SPR_ANIM_DONE. */
|
||
void sprite_anim(sprite_t *s, uint8_t first, uint8_t last,
|
||
uint8_t speed, uint8_t mode);
|
||
|
||
/* Остановить кадровую анимацию: frame = индекс кадра, который останется
|
||
* на экране, или -1 — замереть на текущем. */
|
||
void sprite_anim_stop(sprite_t *s, int8_t frame);
|
||
|
||
/* Запустить перемещение к (tx,ty): раз в interval кадров — шаг до
|
||
* max_step пикселей вдоль БОЛЬШЕЙ оси (меньшая тянется Брезенхэмом,
|
||
* без деления). Сбрасывает SPR_MOVE_DONE. Пример: (0,0)→(100,50),
|
||
* max_step 5, interval 4 → 20 шагов × 4 кадра = 80 кадров (1.6 с). */
|
||
void sprite_moveto(sprite_t *s, int tx, int ty,
|
||
uint8_t max_step, uint8_t interval);
|
||
|
||
/* Остановить перемещение: at_target = 0 — замереть где есть;
|
||
* 1 — прыгнуть в цель (и взвести SPR_MOVE_DONE). */
|
||
void sprite_move_stop(sprite_t *s, uint8_t at_target);
|
||
|
||
/* Статус анимаций: битовое поле SPR_ANIM_ON/DONE | SPR_MOVE_ON/DONE. */
|
||
inline uint8_t sprite_anim_status(const sprite_t *s) { return s->an_flags; }
|
||
|
||
/* Текущий индекс кадра ленты (ведёт тикер; валиден и после stop). */
|
||
inline uint8_t sprite_anim_frame(const sprite_t *s) { return s->an_frame; }
|
||
|
||
/* Перемещение ещё идёт? */
|
||
inline uint8_t sprite_moving(const sprite_t *s) {
|
||
return s->an_flags & SPR_MOVE_ON;
|
||
}
|
||
|
||
/* ==== Атласы в 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
|