Зеркальные кадры персонажей — готовыми файлами, а не разворотом в рантайме
Замеры в MAME (такты Z80, кадр = 430 000) решили вопрос однозначно: загрузка 28 атласов Кида с HDD — 27 160 092 такта (1.26 с), 970 003 на страницу; разворот ОДНОЙ страницы на месте — 12 645 582 такта (0.59 с), т.е. 771 такт/байт. Даже идеальный вариант (копия accel'ом ~0.25 кадра + asm-реверс ~2.2 кадра) дал бы ~1.05 млн на страницу — то есть в лучшем случае сравнялся бы с чтением готового файла. На 34 страницы: 1.5 с загрузки против 20 с разворота. * Упаковщики пишут второй набор: vflip_cols/vflip_page в pop_pack_kid.py -> kid0_v.atl…kid27_v.atl, sword_v.atl; pop_pack_guard.py -> g0_v.atl…g4_v.atl ТОЛЬКО для обычного стража (на уровне 9 tbl_guard_type = 0; тень рисуется атласами Кида, скелет/визирь с переворотом не встречаются). * pop_vflip.c схлопнулся до таблицы «страница -> зеркальная пара»; весь побайтовый разворот и аллокация EMM удалены. * pop_vflip_load_all (банк 8) грузит набор ОДНИМ вызовом и откуда угодно — задел под загрузку ресурсов во время интро. Зовётся при старте и при смене уровня, если это POP_UPSIDE_LEVEL; чит U на других уровнях поднимает набор лениво (страховка в главном цикле). * Стартовый уровень стал параметром сборки: make LEVEL=N (-DFIRST_LEVEL). Проверено в MAME на уровне 9: переворот мгновенный, pop_flip_screen = 897 263 такта (2.1 кадра) — это сама копия экрана, загрузки в кадре больше нет. tests-host 6/6.
This commit is contained in:
@@ -1,111 +1,55 @@
|
||||
/*
|
||||
* pop_vflip.c — ЗЕРКАЛЬНЫЕ ПО ВЕРТИКАЛИ копии страниц атласов персонажей
|
||||
* pop_vflip.c — таблица «страница атласа -> её ВЕРТИКАЛЬНО ЗЕРКАЛЬНАЯ пара»
|
||||
* (зелье инверсии, уровень 9). См. pop_vflip.h.
|
||||
*
|
||||
* Зачем отдельный набор страниц. Спрайты персонажей хранятся COLUMN-MAJOR
|
||||
* (out[col*h + row], transpose_cols в pop_pack_kid.py) — ради БЕСПЛАТНОГО
|
||||
* горизонтального флипа: accel копирует колонки, и порядок колонок задаётся
|
||||
* знаком страйда. А вертикальное зеркало у такой раскладки — это разворот
|
||||
* байтов ВНУТРИ колонки, чего accel не умеет: блок он копирует только
|
||||
* вперёд. Значит перевёрнутые кадры надо готовить заранее.
|
||||
* Сами зеркальные ленты делает упаковщик (`vflip_cols` в pop_pack_kid.py) и
|
||||
* кладёт рядом с обычными: kid0_v.atl, sword_v.atl, g0_v.atl… Здесь остаётся
|
||||
* только связать пары: зеркальный файл грузится обычным atlas_load, а его
|
||||
* раскладка байт-в-байт совпадает с оригиналом — значит движку достаточно
|
||||
* подменить НОМЕР СТРАНИЦЫ, которую он мапит в W0.
|
||||
*
|
||||
* Устройство. Копия — ПОСТРАНИЧНАЯ и побайтово совпадает с оригиналом по
|
||||
* раскладке: тот же заголовок .atl, тот же каталог, ленты на тех же
|
||||
* смещениях; развёрнуты только сами пиксели. Поэтому вызывающему не нужно
|
||||
* менять НИЧЕГО, кроме номера страницы, которую он мапит в W0 — все
|
||||
* atlas_image-смещения и размеры кадров остаются прежними.
|
||||
* Почему не считаем в рантайме (замеры MAME 2026-08-12, такты Z80):
|
||||
* загрузка одной 16-КБ страницы с HDD — 970 003 такта (0.045 с);
|
||||
* разворот той же страницы на месте — 12 645 582 такта (0.59 с).
|
||||
* Даже идеальная конвертация (accel-копия + asm-реверс) даёт ~1.05 млн —
|
||||
* то есть в лучшем случае сравнивается с чтением готового файла, а стоит
|
||||
* asm-модуля. На все 34 страницы: 1.5 с загрузки против 20 с разворота.
|
||||
*
|
||||
* Кэш ЛЕНИВЫЙ: страница зеркалится при первом обращении к ней в
|
||||
* перевёрнутом виде (~16 КБ копирования = заметно, но однократно на кадр
|
||||
* анимации), дальше отдаётся из таблицы. Держится до конца уровня;
|
||||
* pop_vflip_reset освобождает всё (смена уровня / зелье выпито обратно —
|
||||
* см. pop_vflip.h про то, почему НЕ освобождаем при каждом перевороте).
|
||||
*
|
||||
* РЕЗИДЕНТ, а не банк: функция маппит окно W3 (приёмник копии), а банковый
|
||||
* код в нём же и исполняется — правило из _pop_level.h.
|
||||
* РЕЗИДЕНТ: pop_vflip_page зовёт отрисовка персонажей (банк 4) на каждый
|
||||
* спрайт — трамплин тут ни к чему, а кода всего ничего.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_vflip.h"
|
||||
|
||||
/* Раскладка .atl (sprite.h §Атласы): 0x100-байтовый заголовок, каталог с
|
||||
* 0x68 — по 8 байт на ленту: [0..1] смещение ленты, дальше размеры. */
|
||||
#define ATL_DIR_OFF 0x68u
|
||||
#define ATL_COUNT_OFF 0x04u /* число лент в заголовке */
|
||||
|
||||
/* Таблица кэша: страница-источник -> страница-копия. Персонажных атласов
|
||||
* у нас 28 (Кид) + 5 (страж) + 1 (меч) = 34; берём с запасом. */
|
||||
/* Персонажных страниц: 28 (Кид) + 5 (страж) + 1 (меч) = 34; с запасом. */
|
||||
#define VF_MAX 40
|
||||
static uint8_t vf_src[VF_MAX], vf_dst[VF_MAX], vf_blk[VF_MAX], vf_n;
|
||||
static uint8_t vf_src[VF_MAX]; /* страница оригинала */
|
||||
static atlas_t vf_atl[VF_MAX]; /* зеркальный атлас (его page) */
|
||||
static uint8_t vf_n;
|
||||
|
||||
/* Развернуть ленту на месте В СТРАНИЦЕ-ПРИЁМНИКЕ (она замаплена в W3).
|
||||
* off — смещение ленты от начала страницы; формат ленты: w, _, h, _, пиксели
|
||||
* колонками по h байт. */
|
||||
static void flip_strip(uint16_t off)
|
||||
int pop_vflip_add(const atlas_t *orig, const char *path)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)(0xC000u + off);
|
||||
uint8_t w = p[0], h = p[2];
|
||||
uint8_t *col = p + 4;
|
||||
uint8_t c;
|
||||
|
||||
if (!w || !h) return;
|
||||
for (c = 0; c < w; c++) {
|
||||
uint8_t *a = col, *b = col + h - 1;
|
||||
while (a < b) { uint8_t t = *a; *a++ = *b; *b-- = t; }
|
||||
col += h;
|
||||
}
|
||||
}
|
||||
|
||||
/* Сделать зеркальную копию страницы src. Возврат номера копии или 0. */
|
||||
static uint8_t make_flipped(uint8_t src)
|
||||
{
|
||||
uint8_t blk, dst, saved, count, i;
|
||||
uint16_t k;
|
||||
const uint8_t *s;
|
||||
uint8_t *d;
|
||||
|
||||
blk = mem_alloc_pages(1);
|
||||
if (!blk) return 0;
|
||||
dst = mem_get_page(blk, 0);
|
||||
|
||||
/* Побайтовая копия страницы: источник в W0 (0x0000-0x3FFF), приёмник в
|
||||
* W3 (0xC000). Оба окна свободны для резидентного кода W1/W2. */
|
||||
saved = _io_page_w3;
|
||||
gfx_w0_map(src);
|
||||
sprinter_page_w3(dst);
|
||||
s = (const uint8_t *)0x0000;
|
||||
d = (uint8_t *)0xC000;
|
||||
for (k = 0; k < 16384u; k++) d[k] = s[k];
|
||||
gfx_w0_unmap();
|
||||
|
||||
/* Развернуть пиксели каждой ленты — уже в приёмнике (он в W3). */
|
||||
count = ((const uint8_t *)0xC000)[ATL_COUNT_OFF];
|
||||
for (i = 0; i < count; i++) {
|
||||
const uint8_t *e = (const uint8_t *)(0xC000u + ATL_DIR_OFF) + (uint16_t)i * 8;
|
||||
uint16_t off = (uint16_t)(e[0] | ((uint16_t)e[1] << 8));
|
||||
if (off >= 0x100u && off < 0x4000u) flip_strip(off);
|
||||
}
|
||||
sprinter_page_w3(saved);
|
||||
|
||||
if (vf_n < VF_MAX) {
|
||||
vf_src[vf_n] = src; vf_dst[vf_n] = dst; vf_blk[vf_n] = blk; vf_n++;
|
||||
}
|
||||
return dst;
|
||||
if (!orig || vf_n >= VF_MAX) return -1;
|
||||
if (atlas_load(&vf_atl[vf_n], path) != 0) return -1;
|
||||
vf_src[vf_n] = orig->page;
|
||||
vf_n++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t pop_vflip_page(uint8_t src)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < vf_n; i++)
|
||||
if (vf_src[i] == src) return vf_dst[i];
|
||||
return make_flipped(src);
|
||||
if (vf_src[i] == src) return vf_atl[i].page;
|
||||
return 0; /* пары нет — рисуем обычным атласом */
|
||||
}
|
||||
|
||||
uint8_t pop_vflip_ready(void) { return vf_n != 0; }
|
||||
|
||||
void pop_vflip_reset(void)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < vf_n; i++) mem_free_block(vf_blk[i]);
|
||||
for (i = 0; i < vf_n; i++) atlas_free(&vf_atl[i]);
|
||||
vf_n = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user