SprPoP: автономное приложение, выделенное из roomtest
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* pop_ui.c — текст, палитровый fade и full-screen примитивы (FG5, банк 9).
|
||||
*
|
||||
* Шрифты извлечены из SDLPoP в FONT\FONT.ATL. Внутри map/unmap разрешены
|
||||
* лишь обращения к EMM/W0: всё, что ждёт кадр или трогает BIOS-палитру,
|
||||
* выполняется строго после unmap. Это общий контракт для menu/title/PV.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <kbd_raw.h>
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — граница игрового поля */
|
||||
#include "pop_font.h"
|
||||
#include "pop_arc.h"
|
||||
#include "pop_ui.h"
|
||||
#include "pop_status.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t base;
|
||||
uint8_t ascent;
|
||||
uint8_t space;
|
||||
uint8_t first; /* первый код, который есть в наборе */
|
||||
uint8_t last;
|
||||
} pop_font_desc_t;
|
||||
|
||||
/* Тёмный набор — тот же мелкий шрифт другим цветом; глифов в нём меньше
|
||||
* (см. pop_font.h), поэтому диапазон кодов хранится у каждого набора. */
|
||||
static const pop_font_desc_t font_desc[] = {
|
||||
{ POP_FONT_SMALL_BASE, POP_FONT_SMALL_ASCENT, POP_FONT_SMALL_SPACE,
|
||||
POP_FONT_FIRST, POP_FONT_LAST },
|
||||
{ POP_FONT_BIG_BASE, POP_FONT_BIG_ASCENT, POP_FONT_BIG_SPACE,
|
||||
POP_FONT_FIRST, POP_FONT_LAST },
|
||||
{ POP_FONT_DARK_BASE, POP_FONT_BIG_ASCENT, POP_FONT_BIG_SPACE,
|
||||
POP_FONT_DARK_FIRST, POP_FONT_DARK_LAST }
|
||||
};
|
||||
|
||||
static atlas_t font_at;
|
||||
static uint8_t font_ready;
|
||||
|
||||
/* БУФЕР ПАЛИТРЫ ПО ФИКСИРОВАННОМУ АДРЕСУ 0x4000. DSS занимает эти 256
|
||||
* байт только при загрузке программы, дальше они наши (код начинается с
|
||||
* 0x4100), и держать буфер здесь дешевле, чем класть 256 байт на стек в
|
||||
* каждом вызове — идея пользователя, 2026-08-25. */
|
||||
__at (0x4000) uint8_t pop_pal_buf[256];
|
||||
|
||||
static uint8_t text_index(const pop_font_desc_t *font, uint8_t ch)
|
||||
{
|
||||
if (ch < font->first || ch > font->last) ch = '?';
|
||||
return (uint8_t)(font->base + ch - font->first);
|
||||
}
|
||||
|
||||
int pop_ui_init(void) __banked
|
||||
{
|
||||
if (font_ready) return 0;
|
||||
if (atlas_load(&font_at, "FONT\\font.atl") != 0) return -1;
|
||||
font_ready = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pop_ui_free(void) __banked
|
||||
{
|
||||
if (!font_ready) return;
|
||||
atlas_free(&font_at);
|
||||
font_ready = 0;
|
||||
}
|
||||
|
||||
uint8_t pop_ui_ready(void)
|
||||
{
|
||||
return font_ready;
|
||||
}
|
||||
|
||||
void pop_text_map(void)
|
||||
{
|
||||
gfx_w0_map(font_at.page);
|
||||
}
|
||||
|
||||
void pop_text_unmap(void)
|
||||
{
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
uint8_t pop_text_width_mapped(pop_text_font_t font, const char *s)
|
||||
{
|
||||
const pop_font_desc_t *desc = &font_desc[font];
|
||||
uint8_t width = 0;
|
||||
|
||||
while (*s) {
|
||||
const uint8_t *img = (const uint8_t *)atlas_image(&font_at,
|
||||
text_index(desc, (uint8_t)*s++));
|
||||
width = (uint8_t)(width + img[0] + desc->space);
|
||||
}
|
||||
return width ? (uint8_t)(width - desc->space) : 0;
|
||||
}
|
||||
|
||||
void pop_text_draw_mapped(pop_text_font_t font, int x, int baseline,
|
||||
const char *s)
|
||||
{
|
||||
const pop_font_desc_t *desc = &font_desc[font];
|
||||
|
||||
while (*s) {
|
||||
const uint8_t *img = (const uint8_t *)atlas_image(&font_at,
|
||||
text_index(desc, (uint8_t)*s++));
|
||||
/* Пробел не рисует НИ ОДНОГО пикселя: его глиф целиком 0xFF, а
|
||||
* текст всегда идёт прозрачным банком. Блит при этом стоит те же
|
||||
* ~4,6 тыс. тактов, что и буква, поэтому в строке из полусотни
|
||||
* символов пропуск пробелов — самая дешёвая экономия, какая тут
|
||||
* есть (важно для статус-строк, они рисуются в игровом кадре).
|
||||
* У крупного пробела вдобавок высота 0: для accel это 256 строк,
|
||||
* и нулевой блит нельзя отдавать ядру даже noclip-вариантом. */
|
||||
if (img[2] && s[-1] != ' ') gfx_blit_noclip(x, baseline - desc->ascent, img);
|
||||
x += img[0] + desc->space;
|
||||
}
|
||||
}
|
||||
|
||||
void pop_text_center_mapped(pop_text_font_t font, int center, int baseline,
|
||||
const char *s)
|
||||
{
|
||||
pop_text_draw_mapped(font, center - pop_text_width_mapped(font, s) / 2,
|
||||
baseline, s);
|
||||
}
|
||||
|
||||
void pop_text_right_mapped(pop_text_font_t font, int right, int baseline,
|
||||
const char *s)
|
||||
{
|
||||
pop_text_draw_mapped(font, right - pop_text_width_mapped(font, s),
|
||||
baseline, s);
|
||||
}
|
||||
|
||||
/* W0 свободно: snapshot кладём в зарезервированный хвост страницы шрифта.
|
||||
* 4 × 256 Б = ровно 1024 Б, [0x3C00,0x4000), и не пересекаются с атласом. */
|
||||
/* Снимок палитры лежит в странице шрифта (в W2 килобайта нет). Копируем
|
||||
* memcpy, а не циклом: у SDCC это LDIR — 256 байт за ~5 400 тактов против
|
||||
* примерно ста тысяч у побайтового цикла на C. Именно этот цикл и делал
|
||||
* пересчёт палитры шестикадровым (замер 2026-08-26). */
|
||||
static void snapshot_chunk(uint8_t chunk, uint8_t *buf, uint8_t store)
|
||||
{
|
||||
uint8_t *slot;
|
||||
|
||||
gfx_w0_map(font_at.page);
|
||||
slot = (uint8_t *)(POP_FONT_SNAPSHOT_OFF + (uint16_t)chunk * 256u);
|
||||
if (store) memcpy(slot, buf, 256);
|
||||
else memcpy(buf, slot, 256);
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
/* Снимок палитры целиком: дробить на мелкие порции больше не нужно —
|
||||
* причина скрежета была не в длительности BIOS-вызова, а в том, какое окно
|
||||
* он под себя забирал (см. pop_pal_buf выше). */
|
||||
|
||||
void pop_ui_palette_snapshot(void) __banked
|
||||
{
|
||||
uint8_t chunk;
|
||||
|
||||
if (!font_ready) return;
|
||||
for (chunk = 0; chunk < 4; chunk++) {
|
||||
gfx_pal_get(0, (uint8_t)(chunk << 6), 64, pop_pal_buf);
|
||||
snapshot_chunk(chunk, pop_pal_buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Из одного исходного снимка приготовить обе экранные палитры. Умножения
|
||||
* нет: шкала 1/4, 1/2, 3/4 на 8-битном значении не должна тащить делитель
|
||||
* в постоянно вызываемый (во время fade) путь. */
|
||||
/* ВОСЕМЬ СТУПЕНЕЙ, а не четыре. Fade у оригинала идёт 64 шагами по два
|
||||
* тика — 2,13 с плавного затухания; наши четыре ступени укладывались в
|
||||
* доли секунды и выглядели как моргание, а растянуть их по времени значило
|
||||
* получить четыре заметных скачка яркости.
|
||||
*
|
||||
* Яркость ступени — простое умножение: (v * (8 - step)) >> 3. Через
|
||||
* switch с парами сдвигов выходило дороже: 768 каналов на вызов, и цепочка
|
||||
* сравнений SDCC съедала больше, чем стоит само умножение.
|
||||
*
|
||||
* dim_step: 0 — исходная, 8 — чёрная. */
|
||||
void pop_ui_palette_dim(uint8_t dim_step, uint8_t keep_ui) __banked
|
||||
{
|
||||
/* ТАБЛИЦА ЯРКОСТИ — НА СТЕКЕ, и это главное здесь по времени.
|
||||
*
|
||||
* Замер в MAME (2026-08-26): вызов стоил 2,44 млн тактов, то есть почти
|
||||
* шесть кадров, из которых на BIOS-заливку палитры уходило всего 136
|
||||
* тысяч (8 x 17 000). Остальное съедал пересчёт: 768 умножений
|
||||
* uint16 в кадровом цикле — на Z80 это вызов библиотечной функции на
|
||||
* каждый канал. Со ста вызовами подряд (fade кадр за кадром) набегало
|
||||
* больше десяти секунд — ровно то «очень долгое затемнение», которое
|
||||
* поймал пользователь.
|
||||
*
|
||||
* Теперь таблица строится один раз на ступень (256 сложений, без
|
||||
* единого умножения), а каналы её просто читают. 256 байт стека —
|
||||
* дешёвая плата: в этой точке израсходована едва сотня байт из 1279.
|
||||
*
|
||||
* Вместе с memcpy для снимка это дало 487 тысяч тактов на вызов против
|
||||
* 2,44 млн (замер в MAME 2026-08-26). */
|
||||
uint8_t lut[256];
|
||||
uint8_t chunk, i, start;
|
||||
uint8_t *buf = pop_pal_buf;
|
||||
|
||||
if (!font_ready) return;
|
||||
if (dim_step > POP_PAL_DIM_BLACK) dim_step = POP_PAL_DIM_BLACK;
|
||||
if (dim_step) {
|
||||
uint8_t mul = (uint8_t)(POP_PAL_DIM_BLACK - dim_step);
|
||||
uint16_t v = 0;
|
||||
|
||||
/* Без умножения вовсе: значение растёт на mul/32 с каждым шагом,
|
||||
* дробная часть копится в младших пяти битах аккумулятора. */
|
||||
for (i = 0; ; i++) {
|
||||
lut[i] = (uint8_t)(v >> 5);
|
||||
v += mul;
|
||||
if (i == 255) break;
|
||||
}
|
||||
}
|
||||
for (chunk = 0; chunk < 4; chunk++) {
|
||||
start = (uint8_t)(chunk << 6);
|
||||
snapshot_chunk(chunk, buf, 0);
|
||||
if (dim_step) {
|
||||
uint8_t *p = buf;
|
||||
|
||||
for (i = 0; i < 64; i++, p += 4) {
|
||||
uint8_t idx = (uint8_t)(start + i);
|
||||
|
||||
if (keep_ui && idx >= 0x37 && idx <= 0x3F) continue;
|
||||
p[0] = lut[p[0]];
|
||||
p[1] = lut[p[1]];
|
||||
p[2] = lut[p[2]];
|
||||
}
|
||||
}
|
||||
gfx_pal_load(0, start, 64, buf);
|
||||
gfx_pal_load(1, start, 64, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void pop_ui_palette_restore(void) __banked
|
||||
{
|
||||
pop_ui_palette_dim(0, 0);
|
||||
}
|
||||
|
||||
void pop_ui_palette_black(void) __banked
|
||||
{
|
||||
uint8_t chunk;
|
||||
uint16_t i;
|
||||
uint8_t *black = pop_pal_buf;
|
||||
|
||||
/* BIOS принимает максимум 64 записи за вызов; одним буфером покрываем
|
||||
* все четыре четверти палитры, не занимая постоянную память. */
|
||||
for (i = 0; i < sizeof(black); i++) black[i] = 0;
|
||||
for (chunk = 0; chunk < 4; chunk++) {
|
||||
uint8_t start = (uint8_t)(chunk << 6);
|
||||
gfx_pal_load(0, start, 64, black);
|
||||
gfx_pal_load(1, start, 64, black);
|
||||
}
|
||||
}
|
||||
|
||||
/* ДЛИТЕЛЬНОСТЬ ЗАДАЁТСЯ КАДРАМИ, А ПАЛИТРА ТРОГАЕТСЯ ВОСЕМЬ РАЗ.
|
||||
*
|
||||
* frames — сколько кадров луча длится затухание целиком (у оригинала это
|
||||
* 2,13 с). Наивный цикл «ступень на каждый кадр» звал pop_ui_palette_dim
|
||||
* сотню раз, а он не бесплатный: 4 чтения снимка по 256 байт, 768 каналов
|
||||
* арифметики и 8 вызовов BIOS на палитру — около 9 мс, то есть половина
|
||||
* кадра сверху на каждой итерации. Fade растягивался втрое против
|
||||
* заказанного (поймано пользователем: «один fade in секунд восемь»).
|
||||
* Теперь ступень пересчитывается только когда она СМЕНИЛАСЬ. */
|
||||
/* Возвращает 1, если прерван нажатием (только при skip=1).
|
||||
*
|
||||
* ЗАЧЕМ ПРЕРЫВАНИЕ. Fade — это 2,13 с, и на каждом стыке экранов заставки
|
||||
* их два. Пока цикл был глухим, эти секунды нельзя было пропустить ничем:
|
||||
* пропуск работал в сцене, но не между сценами, и заставка ощущалась
|
||||
* невыключаемой.
|
||||
*
|
||||
* Кромка нажатия считается ОТ ВХОДА, как в сценах (intro_skip_requested):
|
||||
* клавиша, которой закончили предыдущий экран, в момент входа ещё зажата, и
|
||||
* принимать её за «пропустить» нельзя — иначе fade схлопывался бы сам.
|
||||
*
|
||||
* Прерванный fade всё равно доводит палитру до конечного состояния: экран
|
||||
* не должен остаться на промежуточной ступени. */
|
||||
static uint8_t fade_run(uint8_t frames, uint8_t up, uint8_t skip)
|
||||
{
|
||||
uint8_t i, shown = 0xFF, armed = 0, hit = 0;
|
||||
|
||||
if (!frames) {
|
||||
pop_ui_palette_dim(up ? 0 : POP_PAL_DIM_BLACK, 0);
|
||||
return 0;
|
||||
}
|
||||
if (skip) {
|
||||
kbd_raw_sync();
|
||||
armed = (uint8_t)!kbd_raw_any_down();
|
||||
}
|
||||
for (i = 1; i <= frames; i++) {
|
||||
uint8_t step = (uint8_t)((uint16_t)i * POP_PAL_DIM_BLACK / frames);
|
||||
|
||||
if (up) step = (uint8_t)(POP_PAL_DIM_BLACK - step);
|
||||
if (step != shown) {
|
||||
pop_ui_palette_dim(step, 0);
|
||||
shown = step;
|
||||
}
|
||||
gfx_wait_vsync();
|
||||
if (skip) {
|
||||
kbd_raw_sync();
|
||||
if (!kbd_raw_any_down()) armed = 1;
|
||||
else if (armed) { hit = 1; break; }
|
||||
}
|
||||
}
|
||||
/* Довести до края: целочисленное деление могло не дойти до 8 (или 0),
|
||||
* а при прерывании мы вообще ушли с середины. */
|
||||
if (hit || shown != (up ? 0 : POP_PAL_DIM_BLACK))
|
||||
pop_ui_palette_dim(up ? 0 : POP_PAL_DIM_BLACK, 0);
|
||||
return hit;
|
||||
}
|
||||
|
||||
void pop_ui_fade_out(uint8_t steps) __banked
|
||||
{
|
||||
pop_ui_palette_snapshot();
|
||||
(void)fade_run(steps, 0, 0);
|
||||
}
|
||||
|
||||
void pop_ui_fade_in(uint8_t steps) __banked
|
||||
{
|
||||
if (!steps) { pop_ui_palette_restore(); return; }
|
||||
(void)fade_run(steps, 1, 0);
|
||||
}
|
||||
|
||||
/* Прерываемые варианты — для заставки и катсцен. Отдельными функциями, а не
|
||||
* флагом в прежних: в меню и на переходе уровня прерывать нечего, и молча
|
||||
* менять там поведение не следует. */
|
||||
uint8_t pop_ui_fade_out_skip(uint8_t steps) __banked
|
||||
{
|
||||
pop_ui_palette_snapshot();
|
||||
return fade_run(steps, 0, 1);
|
||||
}
|
||||
|
||||
uint8_t pop_ui_fade_in_skip(uint8_t steps) __banked
|
||||
{
|
||||
if (!steps) { pop_ui_palette_restore(); return 0; }
|
||||
return fade_run(steps, 1, 1);
|
||||
}
|
||||
|
||||
uint8_t pop_screen_begin(void) __banked
|
||||
{
|
||||
uint8_t page = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
gfx_set_draw_page(page);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
return page;
|
||||
}
|
||||
|
||||
void pop_screen_fill(uint8_t color) __banked
|
||||
{
|
||||
setfillstyle(SOLID_FILL, color);
|
||||
bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
|
||||
}
|
||||
|
||||
/* Экран под ИГРУ: вне поля — цвет POP_COL_OUTSIDE, внутри — цвет 0.
|
||||
*
|
||||
* Зачем два разных чёрных. Вспышки (молния Джафара, урон, зелья) сделаны
|
||||
* подменой ЦВЕТА 0 всей палитры — как в оригинале. Пока борта и полоса HP
|
||||
* тоже были нулём, мигал весь экран целиком, вместе с рамкой и жизнями.
|
||||
* Красим всё, что вне поля 320x192, отдельным индексом, а в палитре держим
|
||||
* его чёрным (pop_pal_outside_apply): картинка та же, но вспышка достаёт
|
||||
* только игровую область. Идея пользователя, 2026-08-25.
|
||||
*
|
||||
* Индексы 1..15 у нас свободны: атласы кладут либо 0xFF (прозрачность),
|
||||
* либо PAL_BASE+v (0x40 и выше), шрифт — 0x3F. */
|
||||
void pop_screen_fill_game(void) __banked
|
||||
{
|
||||
setfillstyle(SOLID_FILL, POP_COL_OUTSIDE);
|
||||
bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, POP_YOFF, GFX_WIDTH - 1, POP_CLIP_BOTTOM - 1);
|
||||
/* Заливка съела и обе служебные полосы — они ВНЕ игрового поля.
|
||||
* Инвалидируем здесь, а не у вызывающих: точек заливки несколько
|
||||
* (старт игры, смена уровня), и пропустить одну из них — ровно тот
|
||||
* баг, из-за которого строка исчезала при переходе между комнатами. */
|
||||
pop_status_invalidate();
|
||||
}
|
||||
|
||||
/* Только ИГРОВОЕ ПОЛЕ, без бортов. Вход в комнату перерисовывает лишь
|
||||
* его: борта от комнаты к комнате не меняются, а всё, что на них живёт —
|
||||
* полоса HP, статус-строка, отладочная строка — обязано вход пережить.
|
||||
* Заодно это на четверть меньше заливки (192 строки вместо 256). */
|
||||
void pop_screen_fill_field(void) __banked
|
||||
{
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, POP_YOFF, GFX_WIDTH - 1, POP_CLIP_BOTTOM - 1);
|
||||
}
|
||||
|
||||
void pop_screen_present(uint8_t page) __banked
|
||||
{
|
||||
gfx_wait_vsync();
|
||||
gfx_set_visible_page(page);
|
||||
}
|
||||
|
||||
/* transition_ltr() SDLPoP: подготовленная СКРЫТАЯ страница переползает на
|
||||
* видимую узкими вертикальными полосами слева направо. У оригинала
|
||||
* 2px/120 FPS; здесь 4px/60 FPS даёт ту же длительность (~1,3 с), не
|
||||
* удваивая число полных копий 320x256.
|
||||
*
|
||||
* Страницы при этом НЕ меняются местами: копируем в видимую, как оригинал
|
||||
* копирует offscreen в onscreen. Поэтому у вызывающего после перехода на
|
||||
* ЭКРАНЕ и в скрытой странице лежит одно и то же. */
|
||||
void pop_screen_present_ltr(void) __banked
|
||||
{
|
||||
gfx_rect_t stripe;
|
||||
uint8_t visible = gfx_get_visible_page();
|
||||
int x;
|
||||
|
||||
gfx_set_draw_page(visible); /* source gfx_copy_page = target */
|
||||
stripe.y = 0;
|
||||
stripe.h = GFX_HEIGHT;
|
||||
for (x = 0; x < GFX_WIDTH; x += 4) {
|
||||
if (kbd_raw_any_down()) {
|
||||
/* Прервали — дорисовать остаток разом, а не бросить полукадр. */
|
||||
stripe.x = 0; stripe.w = GFX_WIDTH;
|
||||
gfx_copy_page(&stripe, GFX_COPY_DIRECT);
|
||||
return;
|
||||
}
|
||||
stripe.x = x;
|
||||
stripe.w = (uint16_t)((GFX_WIDTH - x < 4) ? GFX_WIDTH - x : 4);
|
||||
gfx_copy_page(&stripe, GFX_COPY_DIRECT);
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
}
|
||||
|
||||
/* Нарисовать одну полосу и отпустить её страницу. Общая часть обоих
|
||||
* загрузчиков ниже: они отличаются только тем, откуда взялся атлас. */
|
||||
static int strip_blit(atlas_t *at, int x, int *y)
|
||||
{
|
||||
const uint8_t *img;
|
||||
|
||||
gfx_w0_map(at->page);
|
||||
img = (const uint8_t *)atlas_image(at, 0);
|
||||
/* gfx_blit_noclip ограничен шириной uint8 и обрезает 320px. */
|
||||
gfx_blit(x, *y, img);
|
||||
*y += img[2];
|
||||
gfx_w0_unmap();
|
||||
atlas_free(at);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Полноэкранные изображения не помещаются одним getimage в EMM-страницу.
|
||||
* Общий загрузчик полос держит лишь одну страницу и не трогает page flip:
|
||||
* caller уже собрал скрытую страницу и сам решает, когда её показать. */
|
||||
int pop_screen_blit_atlas_strips(const char * const *parts,
|
||||
uint8_t count, int x, int y) __banked
|
||||
{
|
||||
uint8_t part;
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
for (part = 0; part < count; part++) {
|
||||
atlas_t at;
|
||||
|
||||
if (atlas_load(&at, parts[part]) != 0) return -1;
|
||||
strip_blit(&at, x, &y);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* То же самое, но полосы лежат подряд в АРХИВЕ (заставка, story, PV): один
|
||||
* `open` на весь экран вместо пяти — при цене открытия 51,4 мс против
|
||||
* 32,6 мс на чтение это больше половины времени показа. */
|
||||
int pop_screen_blit_atlas_arc(pop_arc_t *arc, const uint8_t *tbl,
|
||||
uint8_t base, uint8_t count,
|
||||
int x, int y) __banked
|
||||
{
|
||||
uint8_t part;
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
for (part = 0; part < count; part++) {
|
||||
atlas_t at;
|
||||
|
||||
if (pop_arc_atlas(arc, tbl, (uint8_t)(base + part), &at) != 0)
|
||||
return -1;
|
||||
strip_blit(&at, x, &y);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user