libbgi: спрайты — кэш адреса кадра, DDA+asm тик, Y-сортировка со слоями
Оптимизации 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>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* _spr_ysort — ПЕРСИСТЕНТНАЯ таблица порядка отрисовки спрайтов для
|
||||
* блит-прохода sprite_update (gfx_sprite_ysort): 4-байтные записи
|
||||
* {key16, ptr16}, отсортированные вставками по возрастанию ключа.
|
||||
* Возвращает адрес таблицы.
|
||||
*
|
||||
* КЛЮЧ = {layer, y}: старший байт — слой спрайта (слой 1 всегда поверх
|
||||
* слоя 0 — ходячие/летающие/курсор), младший — clamp(y, 0, 255)
|
||||
* (идея пользователя: экран 256 строк — вся видимая зона сортируется
|
||||
* точно, за краями порядок неважен, перекрытие невидимо). Компоненты
|
||||
* отключаемы независимо (YSORT_Y/YSORT_LAYER): маски _spr_ysort_kmask
|
||||
* затирают выключенный байт ключа — без ветвлений в горячем цикле.
|
||||
*
|
||||
* ПЕРСИСТЕНТНОСТЬ (идея пользователя): таблица живёт МЕЖДУ кадрами.
|
||||
* Каждый вызов — «resort»: для каждой записи ключ пересчитывается по
|
||||
* её спрайту и запись вставляется в отсортированный префикс. Спрайты
|
||||
* двигаются на пиксели за кадр → порядок прошлого кадра почти актуален
|
||||
* → вставки вырождаются в одно сравнение с соседом (почти линейно).
|
||||
* Смена сцены (arr/count != прошлых, см. _spr_ysort_last/_lastn) —
|
||||
* rebuild: заполнить ptr-поля из массива и прогнать тот же resort
|
||||
* (первый кадр — полная сортировка, разово дороже). МАССИВ ПРИЛОЖЕНИЯ
|
||||
* НЕ ТРОГАЕТСЯ — сортируются только внутренние записи-указатели.
|
||||
*
|
||||
* Стабильность: равные ключи сохраняют порядок ПРОШЛОГО КАДРА (спрайты
|
||||
* на одной высоте не мерцают взаимным порядком, пока реально не
|
||||
* разойдутся).
|
||||
*
|
||||
* C-эквивалент:
|
||||
* if (arr != last || count != lastn) { // новая сцена
|
||||
* for (i = 0; i < count; i++) tab[i].ptr = &arr[i];
|
||||
* last = arr; lastn = count;
|
||||
* }
|
||||
* for (i = 0; i < count; i++) { // resort
|
||||
* p = tab[i].ptr;
|
||||
* key = (p->layer << 8) | clamp8(p->y);
|
||||
* for (pos = i; pos && tab[pos-1].key > key; pos--)
|
||||
* tab[pos] = tab[pos-1];
|
||||
* tab[pos] = {key, p};
|
||||
* }
|
||||
* return tab;
|
||||
*
|
||||
* Офсеты: y = +8/9, layer = +56 в sprite_t, sizeof(sprite_t) = 57 —
|
||||
* сверять пробником при правке структуры (рецепт в шапке tick_move).
|
||||
* Вход __sdcccall(1): arr → HL, count → DE (используется E; count
|
||||
* ограничен _GFX_YSORT_MAX вызывающим); ВОЗВРАТ — адрес таблицы (DE).
|
||||
* IX callee-saved.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
/* Данные модуля (без вызова gfx_sprite_ysort не линкуются вместе с
|
||||
* кодом). _spr_ysort_last обнуляет сеттер при включении — форсирует
|
||||
* rebuild. НЕ инициализировать (crt0 зануляет). */
|
||||
uint8_t _gfx_ysort_tab[4u * _GFX_YSORT_MAX];
|
||||
void *_spr_ysort_last; /* arr прошлого кадра */
|
||||
uint8_t _spr_ysort_lastn; /* count прошлого кадра */
|
||||
uint8_t _spr_ysort_kmask[2]; /* маски ключа (сеттер): *
|
||||
* [0] & y, [1] & layer */
|
||||
|
||||
uint8_t *_spr_ysort(void *arr, uint16_t count) __naked
|
||||
{
|
||||
(void)arr; (void)count;
|
||||
__asm
|
||||
push ix
|
||||
ld a, e
|
||||
or a, a
|
||||
jr NZ, ys_have_sprites
|
||||
;; count == 0: вернуть таблицу как есть (сцену не запоминаем)
|
||||
ld de, #__gfx_ysort_tab
|
||||
pop ix
|
||||
ret
|
||||
|
||||
ys_have_sprites:
|
||||
;; ---- та же сцена, что кадр назад? (arr и count совпадают) ----
|
||||
ld a, (__spr_ysort_lastn)
|
||||
cp a, e
|
||||
jr NZ, ys_rebuild
|
||||
ld bc, (__spr_ysort_last)
|
||||
ld a, c
|
||||
cp a, l
|
||||
jr NZ, ys_rebuild
|
||||
ld a, b
|
||||
cp a, h
|
||||
jr Z, ys_resort_entry ; сцена та же — только resort
|
||||
|
||||
ys_rebuild:
|
||||
;; ---- новая сцена: запомнить и заполнить ptr-поля записей ----
|
||||
ld (__spr_ysort_last), hl
|
||||
ld a, e
|
||||
ld (__spr_ysort_lastn), a
|
||||
ld b, a ; B = count
|
||||
ld de, #__gfx_ysort_tab + 2 ; DE = &tab[0].ptr
|
||||
ys_fill:
|
||||
ld a, l
|
||||
ld (de), a
|
||||
inc de
|
||||
ld a, h
|
||||
ld (de), a
|
||||
inc de
|
||||
inc de
|
||||
inc de ; DE = &tab[i+1].ptr
|
||||
ld a, l ; cur += sizeof(sprite_t) = 57
|
||||
add a, #57
|
||||
ld l, a
|
||||
jr NC, ys_fill_nc
|
||||
inc h
|
||||
ys_fill_nc:
|
||||
djnz ys_fill
|
||||
|
||||
ys_resort_entry:
|
||||
;; ---- resort: обновить ключи и пересортировать вставками ----
|
||||
ld a, (__spr_ysort_lastn)
|
||||
ld b, a ; B = счётчик записей
|
||||
ld hl, #__gfx_ysort_tab ; HL = R (текущая запись)
|
||||
|
||||
ys_next_rec:
|
||||
push bc ; [счётчик]
|
||||
push hl ; [R]
|
||||
;; BC = P (спрайт записи)
|
||||
inc hl
|
||||
inc hl
|
||||
ld c, (hl)
|
||||
inc hl
|
||||
ld b, (hl)
|
||||
;; ---- ключ: E = clamp8(P->y), D = P->layer ----
|
||||
ld hl, #8
|
||||
add hl, bc ; HL = &P->y
|
||||
ld e, (hl)
|
||||
inc hl
|
||||
ld a, (hl) ; y hi
|
||||
or a, a
|
||||
jr Z, ys_key_y_done ; y в 0..255 — точный ключ
|
||||
ld e, #0
|
||||
bit 7, a
|
||||
jr NZ, ys_key_y_done ; y < 0 → 0
|
||||
ld e, #255 ; y > 255 → 255
|
||||
ys_key_y_done:
|
||||
ld hl, #56
|
||||
add hl, bc ; HL = &P->layer
|
||||
ld d, (hl) ; key = layer:8 | y:8 (DE)
|
||||
;; маски компонентов (YSORT_Y/YSORT_LAYER): выключенный — в 0
|
||||
ld a, (__spr_ysort_kmask + 0)
|
||||
and a, e
|
||||
ld e, a
|
||||
ld a, (__spr_ysort_kmask + 1)
|
||||
and a, d
|
||||
ld d, a
|
||||
;; стек к скану: [счётчик][R][P], HL = pos = R
|
||||
pop hl ; R
|
||||
push hl
|
||||
push bc ; P
|
||||
|
||||
ys_scan:
|
||||
;; pos == tab? → место в голове
|
||||
ld a, l
|
||||
cp a, #<(__gfx_ysort_tab)
|
||||
jr NZ, ys_cmp_prev
|
||||
ld a, h
|
||||
cp a, #>(__gfx_ysort_tab)
|
||||
jr Z, ys_place
|
||||
ys_cmp_prev:
|
||||
;; key < prev.key (prev = pos-4)? — сдвиг prev вправо и шаг
|
||||
;; влево; key >= prev.key (включая равные — стабильность) —
|
||||
;; место найдено.
|
||||
push hl
|
||||
ld a, l
|
||||
sub a, #4
|
||||
ld l, a
|
||||
jr NC, ys_prev_nc
|
||||
dec h
|
||||
ys_prev_nc: ; HL = prev
|
||||
ld a, e
|
||||
sub a, (hl) ; key.lo - prev.lo
|
||||
inc hl
|
||||
ld a, d
|
||||
sbc a, (hl) ; CF = key < prev.key (16-бит)
|
||||
pop hl ; HL = pos
|
||||
jr NC, ys_place
|
||||
;; сдвиг: копировать prev → pos (4 байта), pos = prev
|
||||
push de ; key
|
||||
ld d, h
|
||||
ld e, l ; DE = pos (dst)
|
||||
ld a, l
|
||||
sub a, #4
|
||||
ld l, a
|
||||
jr NC, ys_shift_nc
|
||||
dec h
|
||||
ys_shift_nc: ; HL = prev (src)
|
||||
push hl ; prev — станет новым pos
|
||||
ld bc, #4
|
||||
ldir ; prev → pos, 4 байта
|
||||
pop hl ; pos = prev
|
||||
pop de ; key
|
||||
jr ys_scan
|
||||
|
||||
ys_place:
|
||||
;; записать {key, P} в pos (HL)
|
||||
ld (hl), e
|
||||
inc hl
|
||||
ld (hl), d
|
||||
inc hl
|
||||
pop bc ; P
|
||||
ld (hl), c
|
||||
inc hl
|
||||
ld (hl), b
|
||||
;; продвинуть R и счётчик
|
||||
pop hl ; R
|
||||
ld a, l
|
||||
add a, #4
|
||||
ld l, a
|
||||
jr NC, ys_adv_nc
|
||||
inc h
|
||||
ys_adv_nc:
|
||||
pop bc ; счётчик
|
||||
dec b
|
||||
jp NZ, ys_next_rec
|
||||
|
||||
ld de, #__gfx_ysort_tab ; вернуть адрес таблицы (ptr в DE)
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
Reference in New Issue
Block a user