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:
@@ -68,6 +68,30 @@ void _bgi_hspan_raw(int x, int y, int len, uint8_t color) __preserves_regs(d
|
||||
void _bgi_vspan_raw(int x, int y, int len, uint8_t color) __preserves_regs(d,e); /* V-отрезок, без скобки */
|
||||
void _bgi_clear_raw(uint8_t color); /* заливка экрана, без скобки (клоббер всех) */
|
||||
|
||||
/* Копирование h строк по w байт через accel block-copy (LD L,L) —
|
||||
* ядро блиттинга (putimage, getimage, gfx_blit, gfx_heal). Без скобки и
|
||||
* клиппинга; w,h: 0 = 256; stride 0 = адрес на месте (сторона экрана,
|
||||
* строку выбирает Port_Y); y0 — стартовый Port_Y. Направление задают
|
||||
* указатели (буфер обязан быть вне W3). Контракт/схема — в
|
||||
* bgi256/_bgi_copy_rows_raw.c. */
|
||||
void _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int dstride, int sstride, uint8_t y0);
|
||||
|
||||
/* Специализация для БЛИТА: dst фиксирован (dstride 0), src двигается на
|
||||
* sstride (SMC-константа); dst-цепочка выкинута (~24Т/строку). Ядро
|
||||
* gfx_blit_part/putsprite/спрайтового движка. Без скобки/клиппинга; банк
|
||||
* ставит вызывающий (0x5C прозрачный). bgi256/_bgi_blit_rows_raw.c. */
|
||||
void _bgi_blit_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int sstride, uint8_t y0);
|
||||
|
||||
/* Специализация для heal: src == dst == адрес экрана, оба страйда 0
|
||||
* (строку выбирает Port_Y) — адресной арифметики в цикле нет, строка
|
||||
* 77Т + burst'ы. y0 — стартовый Port_Y; w,h: 0 = 256. Без скобки и
|
||||
* клиппинга; банк ставит вызывающий (gfx_heal форсит 0x50). */
|
||||
void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h);
|
||||
|
||||
/* Self-bracket версии (begin + raw + end) — для разовых вызовов из
|
||||
* математики, которой не нужен пакетный режим (floodfill, cleardevice).
|
||||
* Разовое чтение пикселя — публичный getpixel (та же композиция;
|
||||
@@ -75,6 +99,48 @@ void _bgi_clear_raw(uint8_t color); /* заливка экр
|
||||
void _bgi_hspan (int x, int y, int len, uint8_t color);
|
||||
void _bgi_clearall(uint8_t color);
|
||||
|
||||
/* Ядро блиттинга gfx_blit_part/gfx_blit/gfx_heal (Фаза B) — публичные,
|
||||
* прототипы в <gfx.h>; спрайтовые обёртки putsprite/movesprite — в
|
||||
* <graphics.h>. Реализации: common/gfx_blit_part.c и соседи. */
|
||||
|
||||
/* Bracket-free общие ядра (полная ширина) (common/_gfx_blit_full.c, _gfx_heal_full.c):
|
||||
* клип по экрану + нарезка полос ≤256 + accel-leaf, БЕЗ _bgi_begin/
|
||||
* _bgi_end и БЕЗ смены банка. Вызывающий обязан замапить W3 (_bgi_begin)
|
||||
* и выставить нужный банк ДО вызова. Общее ядро для разовых обёрток
|
||||
* (gfx_blit_part/gfx_heal) и batch-пасса спрайтового движка v2, который
|
||||
* держит одну скобку + один банк на весь кадр (docs/sprite-api-design.md
|
||||
* §9.1). blit: банк 0x5C для прозрачности; heal: банк 0x50. */
|
||||
void _gfx_blit_full(int x, int y, const void *img,
|
||||
int sx, int sy, int w, int h);
|
||||
void _gfx_heal_full(int x, int y, int w, int h);
|
||||
|
||||
/* Лин-ядра для СПРАЙТОВ (кадр ≤ 64×64, sprite.h): как *_full, но без
|
||||
* split'а >256 и с 8-битными w/h (движок/putsprite — горячий путь;
|
||||
* программа только со спрайтами не тянет общие *_full со split'ом).
|
||||
* common/_gfx_blit_sprite.c, _gfx_heal_sprite.c. */
|
||||
void _gfx_blit_sprite(int x, int y, const void *img,
|
||||
int sx, int sy, uint8_t w, uint8_t h);
|
||||
void _gfx_heal_sprite(int x, int y, uint8_t w, uint8_t h);
|
||||
|
||||
/* NOCLIP-ядра: клип-кода нет вообще (полный выигрыш от codegen, не только
|
||||
* скип проверок). Зовутся при gfx_sprite_clip(0) — приложение
|
||||
* гарантирует пределы. common/_gfx_blit_sprite_noclip.c, _gfx_heal_… */
|
||||
void _gfx_blit_sprite_noclip(int x, int y, const void *img,
|
||||
int sx, int sy, uint8_t w, uint8_t h);
|
||||
void _gfx_heal_sprite_noclip(int x, int y, uint8_t w, uint8_t h);
|
||||
|
||||
/* Диспетчеризация clip/noclip через УКАЗАТЕЛИ (не ветка в горячем цикле):
|
||||
* gfx_sprite_clip() перенаправляет их на clip- или noclip-ядро ОДИН раз,
|
||||
* а sprite_update/putsprite/movesprite зовут через указатель — один
|
||||
* call-site, без if в цикле → тесный codegen (полный выигрыш noclip,
|
||||
* см. docs/sprite-api-design.md). Установка по умолчанию (clip on) — в
|
||||
* initgraph. */
|
||||
typedef void (*_gfx_blit_fp)(int, int, const void *, int, int,
|
||||
uint8_t, uint8_t);
|
||||
typedef void (*_gfx_heal_fp)(int, int, uint8_t, uint8_t);
|
||||
extern _gfx_blit_fp _gfx_blit_fn;
|
||||
extern _gfx_heal_fp _gfx_heal_fn;
|
||||
|
||||
/* ---- Fill-burst'ы акселератора 256 (register-ABI, asm-only) ------ *
|
||||
* Вызываются ТОЛЬКО из asm raw-примитивов; аргументы в регистрах, без
|
||||
* глобального скретча в _DATA:
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* _bgi_blit_rows_raw — специализация accel block-copy для БЛИТА
|
||||
* (буфер → экран): dst ФИКСИРОВАН (адрес экрана base+x; строку выбирает
|
||||
* Port_Y, dstride == 0 по определению), src двигается на sstride (ширину
|
||||
* картинки) на строку. Выделен из _bgi_copy_rows_raw (2026-07-12,
|
||||
* профиль examples/balls): у блита dst-цепочка (ld a,e; add; ld e,a;
|
||||
* ld a,d; adc; ld d,a ≈ 24Т/строку) — мёртвый груз, т.к. dst не меняется;
|
||||
* здесь её нет вообще. src-страйд SMC-патчится в 8-битную adc-цепочку
|
||||
* (константа на вызов). Универсальный _bgi_copy_rows_raw остаётся для
|
||||
* getimage (grab: dst двигается, src фикс — другая специализация).
|
||||
*
|
||||
* ВХОД (__sdcccall(1)): src→HL, dst→DE (фикс); стек: w 4(ix), h 5(ix),
|
||||
* sstride 6/7(ix), y0 8(ix). w — байт/строку за burst (1..256, 0=256);
|
||||
* h — строк (1..256, 0=256); sstride — шаг адреса src между строками;
|
||||
* y0 — стартовый Port_Y. Callee-pop 5 байт.
|
||||
*
|
||||
* Raw: клиппинга НЕТ (вызывающий), W3 замаплен (_bgi_begin/_bgi_end
|
||||
* снаружи), банк выставлен вызывающим (0x5C прозрачный / 0x50 обычный).
|
||||
* Буфер src обязан быть ВНЕ W3 (< 0xC000). Полосы > 256 режет вызывающий.
|
||||
*
|
||||
* ОДИН DI на весь блит (arm + все строки): убирает per-row EI-окно, где
|
||||
* CBL-ISR мог пере-армировать акселератор своим размером блока, и тесней
|
||||
* loop (санкция пользователя 2026-07-12: малый спрайт под одним DI аудио
|
||||
* не рвёт). ВНИМАНИЕ: h ограничен малыми спрайтами — большие блиты
|
||||
* (putimage 256 строк) вызывающий обязан резать на чанки ≤16 строк.
|
||||
*
|
||||
* Квирк CPU-байта write-триггера (см. _bgi_copy_rows_raw): src[0]
|
||||
* предчитывается и подставляется в A через EX AF,AF' — CPU-цикл
|
||||
* LD (DE),A кладёт корректный src[0] при любом банке.
|
||||
*
|
||||
* Клоббер: AF/AF'/BC/DE/HL; IX сохраняется.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_blit_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int sstride, uint8_t y0) __naked
|
||||
{
|
||||
(void)src; (void)dst; (void)w; (void)h; (void)sstride; (void)y0;
|
||||
__asm
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
;; SMC: размер блока <- w (immediate у ld a,#n — требование FSM)
|
||||
ld a, 4 (ix)
|
||||
ld (br_len_imm), a
|
||||
;; SMC: src-страйд — константа на вызов (8-битная adc-цепочка)
|
||||
ld a, 6 (ix)
|
||||
ld (br_slo_imm), a
|
||||
ld a, 7 (ix)
|
||||
ld (br_shi_imm), a
|
||||
ld b, 5 (ix) ; B = счётчик строк (0 => 256: djnz)
|
||||
ld c, 8 (ix) ; C = y
|
||||
di ; один DI на весь блит
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0
|
||||
br_len_imm = . - 1
|
||||
ld b, b ; 0x40 — стоп (B=счётчик не тронут)
|
||||
br_row:
|
||||
ld a, c
|
||||
out (#0x89), a ; Port_Y = y
|
||||
inc c
|
||||
;; ld a, (hl) ; A = src[0] (accel выключен)
|
||||
;; ex af, af' ; спрятать src[0]
|
||||
ld l, l ; 0x6D — армировать копию строки
|
||||
ld a, (hl) ; burst: src -> память акселератора
|
||||
;; ex af, af' ; A = src[0] — CPU-байт триггера записи
|
||||
ld (de), a ; burst: память акселератора -> dst (фикс)
|
||||
ld b, b ; 0x40 — стоп
|
||||
;; src += sstride (dst НЕ двигается — dstride 0)
|
||||
ld a, l
|
||||
add a, #0
|
||||
br_slo_imm = . - 1
|
||||
ld l, a
|
||||
ld a, h
|
||||
adc a, #0
|
||||
br_shi_imm = . - 1
|
||||
ld h, a
|
||||
djnz br_row
|
||||
ei ; закрыть DI-окно блита
|
||||
pop ix
|
||||
;; callee-pop 5 байт (w,h,sstride:2,y0)
|
||||
pop hl ; ret-адрес
|
||||
pop af
|
||||
pop af
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* _bgi_copy_rows_raw — копирование h строк по w байт через block-copy
|
||||
* акселератора (LD L,L), mode 0x81. Ядро блиттинга: putimage(COPY),
|
||||
* getimage, gfx_blit*, gfx_heal — все операции блиттинга это один этот
|
||||
* leaf с разными src/dst (docs/sprite-api-design.md §2, §4.1):
|
||||
*
|
||||
* блит src=буфер, sstride=w картинки; dst=экран, dstride=0
|
||||
* grab src=экран, sstride=0; dst=буфер, dstride=w
|
||||
* Для heal (src == dst == экран, оба страйда 0) — специализированный
|
||||
* _bgi_heal_rows_raw (без адресной арифметики в цикле вообще).
|
||||
* Страйды здесь — константы на весь вызов и патчатся SMC при входе
|
||||
* (8-битные цепочки add/adc, 73Т/строку вместо 140Т у прежнего
|
||||
* варианта с перечитыванием через IX; профиль examples/balls).
|
||||
*
|
||||
* ВХОД (__sdcccall(1)): src→HL, dst→DE; стек: w 4(ix), h 5(ix),
|
||||
* dstride 6/7(ix), sstride 8/9(ix), y0 10(ix).
|
||||
* w — байт в строке за burst, 1..256 (0 = 256 — конвенция акселератора);
|
||||
* h — строк, 1..256 (0 = 256);
|
||||
* dstride/sstride — шаг адреса между строками (0 = адрес на месте,
|
||||
* строку выбирает Port_Y — сторона, живущая в видеоокне W3);
|
||||
* y0 — стартовый Port_Y, инкремент на строку.
|
||||
*
|
||||
* Raw: клиппинга НЕТ (весь клип делает вызывающий), W3 уже замаплен
|
||||
* (_bgi_begin/_bgi_end снаружи). Буферная сторона обязана быть ВНЕ W3
|
||||
* (< 0xC000) — на время операции W3 занят видеобанком. Полосы > 256
|
||||
* байт режет вызывающий (см. gfx_blit_part).
|
||||
*
|
||||
* Схема (референс Draw_Restangle_Data, accelerator_doc.txt): размер
|
||||
* блока армируется ОДИН раз (SMC у LD A,#n — операнд ОБЯЗАН быть
|
||||
* immediate, memory/sprinter_accelerator; размер переживает LD B,B —
|
||||
* tests/accfill), далее на строку: OUT Port_Y, DI, LD L,L (армировать
|
||||
* копию), LD A,(HL) (burst-чтение src в память акселератора),
|
||||
* LD (DE),A (burst-запись из памяти акселератора в dst), LD B,B, EI.
|
||||
* Между армированием и триггерами — только регистровые M1-опкоды.
|
||||
*
|
||||
* КВИРК ТРИГГЕРА ЗАПИСИ (найден tests/sprites — клипнутые спрайты):
|
||||
* LD (DE),A — реальная CPU-инструкция: её собственный цикл записи
|
||||
* кладёт байт A в dst[0], и лишь затем burst FSM пишет src[0..w-1]
|
||||
* поверх. После burst-чтения в A остаётся ПОСЛЕДНИЙ байт строки —
|
||||
* при банке 0x50 утечка невидима (src[0] тут же перезапишет), но при
|
||||
* 0x58/0x5C, если src[0] == 0xFF, перезапись скипается и в dst[0]
|
||||
* остаётся вертикальная полоса цвета последних байтов строк.
|
||||
* Нейтрализация: src[0] предчитывается ДО армирования и подставляется
|
||||
* в A через EX AF,AF' (регистровый M1-опкод — под armed разрешён)
|
||||
* прямо перед триггером — CPU-байт становится src[0], что корректно
|
||||
* при ЛЮБОМ банке и любой семантике первого цикла записи (в MAME
|
||||
* 0.283 подтверждено tests/blitw+sprites; на железе перепроверить).
|
||||
*
|
||||
* Клоббер: AF/AF'/BC/DE/HL; IX сохраняется (callee-saved).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
|
||||
uint8_t w, uint8_t h,
|
||||
int dstride, int sstride, uint8_t y0) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
;; SMC: размер блока <- w (immediate у ld a,#n — требование FSM)
|
||||
ld a, 4 (ix)
|
||||
ld (cr_len_imm), a
|
||||
;; SMC: страйды — константы на весь вызов; патчим immediate
|
||||
;; 8-битных цепочек сложения вместо перечитывания из стека
|
||||
;; через IX на каждой строке (профиль examples/balls
|
||||
;; 2026-07-12: было 140Т/строку, стало 73Т — и BC не нужен,
|
||||
;; push/pop и ex de,hl ушли).
|
||||
ld a, 8 (ix)
|
||||
ld (cr_slo_imm), a
|
||||
ld a, 9 (ix)
|
||||
ld (cr_shi_imm), a
|
||||
ld a, 6 (ix)
|
||||
ld (cr_dlo_imm), a
|
||||
ld a, 7 (ix)
|
||||
ld (cr_dhi_imm), a
|
||||
ld b, 5 (ix) ; B = счётчик строк (0 => 256: djnz)
|
||||
ld c, 10 (ix) ; C = y
|
||||
;; ОДИН DI на весь блит (arm + все строки): убирает per-row
|
||||
;; EI-окно (CBL-ISR не пере-армирует акселератор посреди спрайта)
|
||||
;; и тесней loop. Санкция пользователя 2026-07-12: малый спрайт
|
||||
;; под одним DI аудио не рвёт. ВНИМАНИЕ: для больших h (putimage
|
||||
;; 256 строк) вызывающий обязан резать на чанки ≤16 строк.
|
||||
di
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0
|
||||
cr_len_imm = . - 1
|
||||
ld b, b ; 0x40 — стоп (B=счётчик не тронут)
|
||||
cr_row:
|
||||
ld a, c
|
||||
out (#0x89), a ; Port_Y = y
|
||||
inc c
|
||||
;; ld a, (hl) ; A = src[0] (accel выключен)
|
||||
;; ex af, af' ; спрятать src[0] в A'
|
||||
ld l, l ; 0x6D — армировать копию строки
|
||||
ld a, (hl) ; burst: src -> память акселератора
|
||||
;; ex af, af' ; A = src[0] — CPU-байт триггера записи
|
||||
ld (de), a ; burst: память акселератора -> dst
|
||||
ld b, b ; 0x40 — стоп (no-op по регистрам)
|
||||
;; src += sstride (SMC-immediates, 16-бит через adc)
|
||||
ld a, l
|
||||
add a, #0
|
||||
cr_slo_imm = . - 1
|
||||
ld l, a
|
||||
ld a, h
|
||||
adc a, #0
|
||||
cr_shi_imm = . - 1
|
||||
ld h, a
|
||||
;; dst += dstride
|
||||
ld a, e
|
||||
add a, #0
|
||||
cr_dlo_imm = . - 1
|
||||
ld e, a
|
||||
ld a, d
|
||||
adc a, #0
|
||||
cr_dhi_imm = . - 1
|
||||
ld d, a
|
||||
djnz cr_row
|
||||
ei ; закрыть DI-окно блита
|
||||
pop ix
|
||||
;; callee-pop 7 байт стековых аргументов
|
||||
pop hl ; ret-адрес
|
||||
pop af
|
||||
pop af
|
||||
pop af
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* _bgi_heal_rows_raw — специализация accel-копии для heal (src == dst ==
|
||||
* адрес экрана): h строк по w байт «прочитать ОЗУ-копию → записать в
|
||||
* VRAM+ОЗУ» на одном месте. Выделен из _bgi_copy_rows_raw по профилю
|
||||
* examples/balls (2026-07-12): у heal ОБА страйда нулевые по
|
||||
* определению (строку выбирает Port_Y, CPU-адрес не меняется), поэтому
|
||||
* адресной арифметики в цикле нет вообще — строка 77Т + burst'ы против
|
||||
* 204Т у универсального leaf'а до оптимизации.
|
||||
*
|
||||
* ВХОД (__sdcccall(1)): scr→HL (base + x), y0→DE (E — стартовый
|
||||
* Port_Y); стек: w 4(ix), h 5(ix) — 1..256, 0 = 256 (конвенция
|
||||
* акселератора). Callee-pops 2 байта.
|
||||
*
|
||||
* Raw: клиппинга нет, W3 замаплен вызывающим; банк выбирает
|
||||
* вызывающий (gfx_heal форсит 0x50 — иначе heal бессмыслен).
|
||||
* Полосы > 256 байт режет вызывающий.
|
||||
*
|
||||
* Квирк CPU-байта write-триггера (см. _bgi_copy_rows_raw): для heal
|
||||
* предчитанный src[0] — ровно тот байт, что burst запишет в dst[0],
|
||||
* так что фикс EX AF,AF' здесь одновременно и нейтрализация утечки,
|
||||
* и просто корректное значение.
|
||||
*
|
||||
* Клоббер: AF/AF'/BC/DE/HL; IX сохраняется.
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
;; SMC: размер блока <- w (immediate — требование FSM)
|
||||
ld a, 4 (ix)
|
||||
ld (hr_len_imm), a
|
||||
ld c, e ; C = y (взять ДО затирания DE)
|
||||
ld b, 5 (ix) ; B = счётчик строк (0 => 256)
|
||||
ld d, h
|
||||
ld e, l ; DE = HL = scr (src == dst)
|
||||
;; ОДИН DI на весь спрайт (arm + все строки): убирает per-row
|
||||
;; EI-окно, где CBL-ISR мог пере-армировать акселератор своим
|
||||
;; размером блока (латентная реентерабельность), и тесней loop.
|
||||
;; Санкция пользователя 2026-07-12: 16×16 = 256 байт под одним DI
|
||||
;; (как 1×256 fill) на аудио не влияет. ВНИМАНИЕ: h ограничен
|
||||
;; малыми спрайтами — для больших блитов (putimage 256 строк)
|
||||
;; вызывающий обязан резать на чанки ≤16 строк (иначе долгий DI).
|
||||
di
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0
|
||||
hr_len_imm = . - 1
|
||||
ld b, b ; 0x40 — стоп (B=счётчик не тронут)
|
||||
hr_row:
|
||||
ld a, c
|
||||
out (#0x89), a ; Port_Y = y
|
||||
inc c
|
||||
;; ld a, (hl) ; предчитать ОЗУ[0] (accel выключен)
|
||||
;; ex af, af'
|
||||
ld l, l ; 0x6D — армировать копию строки
|
||||
ld a, (hl) ; burst: ОЗУ-копия -> память акселератора
|
||||
;; ex af, af' ; A = ОЗУ[0] — CPU-байт триггера корректен
|
||||
ld (de), a ; burst: память акселератора -> VRAM+ОЗУ
|
||||
ld b, b ; 0x40 — стоп
|
||||
djnz hr_row ; адреса не меняются — только Port_Y
|
||||
ei ; закрыть DI-окно спрайта
|
||||
pop ix
|
||||
pop hl ; ret-адрес; callee-pop 2 байта (w,h)
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+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