diff --git a/applications/PoP/docs/sound_plan.md b/applications/PoP/docs/sound_plan.md index 78f122e..5e98bb2 100644 --- a/applications/PoP/docs/sound_plan.md +++ b/applications/PoP/docs/sound_plan.md @@ -899,10 +899,25 @@ load_intro у оригинала и так начинается с ожидан тика, 128 тиков = 2,13 с; сцена перед уровнем 2 занимает с ними около семи секунд. Наши четыре ступени укладывались в восемь сотых секунды, и сцена выходила втрое короче. Теперь `INTRO_FADE = POP_T60(128)`, а ступеней в -`pop_ui_palette_dim` восемь вместо четырёх (7/8, 3/4, 5/8, 1/2, 3/8, 1/4, -1/8, 0 — каждая парой сдвигов, без умножения): на четырёх растянутых +`pop_ui_palette_dim` шестнадцать вместо четырёх: на четырёх растянутых ступенях затемнение выглядело бы скачками. +**Цена ступени** (замеры в MAME 2026-08-26, такты 21 МГц; кадр 430 000): + +| версия | такты | что изменилось | +|--------|-------|----------------| +| исходная | 2 440 000 | снимок копировался побайтовым циклом на C | +| + таблица яркости на стеке | 1 250 000 | 768 умножений uint16 заменены 256 сложениями | +| + memcpy для снимка | 487 000 | LDIR вместо цикла — главный выигрыш | + +Из оставшихся 487 тысяч 136 тысяч — заливка палитры через BIOS (8 вызовов +`gfx_pal_load` по 17 000). Дальше можно было бы хранить готовые таблицы +яркости файлом, но при 1,2 мс на построение это уже незаметно. + +ВАЖНО: ступень пересчитывается только когда она СМЕНИЛАСЬ. Наивный цикл +«ступень на каждый кадр» звал пересчёт сто раз и растягивал fade до +десяти с лишним секунд. + **Чего пока нет.** Финальный `won` (56): 115 с, 1,2 МБ, 78 страниц EMM — в `POP_MUS_PAGES` (20) он не помещается даже теоретически. Ему нужен КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те, diff --git a/applications/PoP/roomtest/pop_intro.c b/applications/PoP/roomtest/pop_intro.c index 8c24b1a..8c1c0de 100644 --- a/applications/PoP/roomtest/pop_intro.c +++ b/applications/PoP/roomtest/pop_intro.c @@ -31,9 +31,9 @@ * 128 тиков, то есть 2,13 с (fade_in_1/fade_out_1 -> fade_*_frame -> * do_simple_wait(1) при wait_time = 2). Наши четыре кадра укладывались в * восемь сотых секунды, и вся сцена выходила втрое короче оригинальной. - * Число здесь — кадры луча, ступени яркости внутри распределяет - * pop_ui_fade_*. */ -#define INTRO_FADE POP_T60(128) + * Точное число кадров считает POP_FADE_FRAMES (pop_ui.h): из + * заказанных 2,13 с вычитается то, что съедает сам пересчёт палитры. */ +#define INTRO_FADE POP_FADE_FRAMES /* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422): створка ворот * закрывается, затем открывается дверь покоев, из которой входит Джафар. */ diff --git a/applications/PoP/roomtest/pop_title.c b/applications/PoP/roomtest/pop_title.c index 5ca7b39..061b56c 100644 --- a/applications/PoP/roomtest/pop_title.c +++ b/applications/PoP/roomtest/pop_title.c @@ -26,7 +26,7 @@ #define TITLE_H 200 #define TITLE_Y POP_YOFF #define TITLE_PARTS 5 -#define TITLE_FADE POP_T60(128) /* 64 шага по 2 тика, как fade_*_1 */ +#define TITLE_FADE POP_FADE_FRAMES /* 2,13 с, как fade_*_1 оригинала */ enum { TITLE_MAIN = 0, diff --git a/applications/PoP/roomtest/pop_ui.c b/applications/PoP/roomtest/pop_ui.c index 0b2ea24..9a84bc6 100644 --- a/applications/PoP/roomtest/pop_ui.c +++ b/applications/PoP/roomtest/pop_ui.c @@ -6,6 +6,7 @@ * выполняется строго после unmap. Это общий контракт для menu/title/PV. */ #include +#include #include #include #include @@ -120,17 +121,18 @@ void pop_text_right_mapped(pop_text_font_t font, int right, int baseline, /* 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) { - uint16_t i; uint8_t *slot; gfx_w0_map(font_at.page); slot = (uint8_t *)(POP_FONT_SNAPSHOT_OFF + (uint16_t)chunk * 256u); - for (i = 0; i < 256; i++) { - if (store) slot[i] = buf[i]; - else buf[i] = slot[i]; - } + if (store) memcpy(slot, buf, 256); + else memcpy(buf, slot, 256); gfx_w0_unmap(); } @@ -155,43 +157,62 @@ void pop_ui_palette_snapshot(void) __banked /* ВОСЕМЬ СТУПЕНЕЙ, а не четыре. Fade у оригинала идёт 64 шагами по два * тика — 2,13 с плавного затухания; наши четыре ступени укладывались в * доли секунды и выглядели как моргание, а растянуть их по времени значило - * получить четыре заметных скачка яркости. Восемь ступеней собираются - * пáрой сдвигов каждая (умножения на Z80 не нужно), а по времени их - * распределяет вызывающий (pop_ui_fade_*). + * получить четыре заметных скачка яркости. + * + * Яркость ступени — простое умножение: (v * (8 - step)) >> 3. Через + * switch с парами сдвигов выходило дороже: 768 каналов на вызов, и цепочка + * сравнений SDCC съедала больше, чем стоит само умножение. * * dim_step: 0 — исходная, 8 — чёрная. */ -static uint8_t dim_value(uint8_t v, uint8_t step) -{ - switch (step) { - case 1: return (uint8_t)(v - (v >> 3)); /* 7/8 */ - case 2: return (uint8_t)((v >> 1) + (v >> 2)); /* 3/4 */ - case 3: return (uint8_t)((v >> 1) + (v >> 3)); /* 5/8 */ - case 4: return (uint8_t)(v >> 1); /* 1/2 */ - case 5: return (uint8_t)((v >> 2) + (v >> 3)); /* 3/8 */ - case 6: return (uint8_t)(v >> 2); /* 1/4 */ - case 7: return (uint8_t)(v >> 3); /* 1/8 */ - default: return 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/16 с каждым шагом, + * дробная часть копится в младших четырёх битах аккумулятора. */ + for (i = 0; ; i++) { + lut[i] = (uint8_t)(v >> 4); + 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) { - for (i = 0; i < 64; i++) { + uint8_t *p = buf; + + for (i = 0; i < 64; i++, p += 4) { uint8_t idx = (uint8_t)(start + i); - uint16_t p = (uint16_t)i * 4u; + if (keep_ui && idx >= 0x37 && idx <= 0x3F) continue; - buf[p] = dim_value(buf[p], dim_step); - buf[p + 1] = dim_value(buf[p + 1], dim_step); - buf[p + 2] = dim_value(buf[p + 2], dim_step); + p[0] = lut[p[0]]; + p[1] = lut[p[1]]; + p[2] = lut[p[2]]; } } gfx_pal_load(0, start, 64, buf); @@ -220,27 +241,48 @@ void pop_ui_palette_black(void) __banked } } -void pop_ui_fade_out(uint8_t steps) __banked +/* ДЛИТЕЛЬНОСТЬ ЗАДАЁТСЯ КАДРАМИ, А ПАЛИТРА ТРОГАЕТСЯ ВОСЕМЬ РАЗ. + * + * frames — сколько кадров луча длится затухание целиком (у оригинала это + * 2,13 с). Наивный цикл «ступень на каждый кадр» звал pop_ui_palette_dim + * сотню раз, а он не бесплатный: 4 чтения снимка по 256 байт, 768 каналов + * арифметики и 8 вызовов BIOS на палитру — около 9 мс, то есть половина + * кадра сверху на каждой итерации. Fade растягивался втрое против + * заказанного (поймано пользователем: «один fade in секунд восемь»). + * Теперь ступень пересчитывается только когда она СМЕНИЛАСЬ. */ +static void fade_run(uint8_t frames, uint8_t up) { - uint8_t i; - if (!steps) return; - pop_ui_palette_snapshot(); - for (i = 1; i <= steps; i++) { - uint8_t step = (uint8_t)((uint16_t)i * POP_PAL_DIM_BLACK / steps); - pop_ui_palette_dim(step, 0); + uint8_t i, shown = 0xFF; + + if (!frames) { + pop_ui_palette_dim(up ? 0 : POP_PAL_DIM_BLACK, 0); + return; + } + 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(); } + /* Довести до края: целочисленное деление могло не дойти до 8 (или 0). */ + if (shown != (up ? 0 : POP_PAL_DIM_BLACK)) + pop_ui_palette_dim(up ? 0 : POP_PAL_DIM_BLACK, 0); +} + +void pop_ui_fade_out(uint8_t steps) __banked +{ + pop_ui_palette_snapshot(); + fade_run(steps, 0); } void pop_ui_fade_in(uint8_t steps) __banked { - uint8_t i; if (!steps) { pop_ui_palette_restore(); return; } - for (i = steps; i > 0; i--) { - uint8_t step = (uint8_t)((uint16_t)(i - 1) * POP_PAL_DIM_BLACK / steps); - pop_ui_palette_dim(step, 0); - gfx_wait_vsync(); - } + fade_run(steps, 1); } uint8_t pop_screen_begin(void) __banked diff --git a/applications/PoP/roomtest/pop_ui.h b/applications/PoP/roomtest/pop_ui.h index ab99e8f..6c2d3ed 100644 --- a/applications/PoP/roomtest/pop_ui.h +++ b/applications/PoP/roomtest/pop_ui.h @@ -35,12 +35,24 @@ void pop_text_right_mapped(pop_text_font_t font, int right, int baseline, /* Снимок обеих (идентичных по контракту) палитр хранится в свободном хвосте * FONT.ATL. dim_step: 0 — исходная, POP_PAL_DIM_BLACK — чёрная; между ними - * восемь равных ступеней (7/8, 3/4, 5/8, 1/2, 3/8, 1/4, 1/8). Восемь, а не - * четыре, потому что fade оригинала длится 2,13 с: на четырёх ступенях это - * четыре заметных скачка яркости. + * равные ступени яркости (step/16). Шестнадцать, а не четыре, потому что + * fade оригинала длится 2,13 с: на четырёх ступенях это четыре заметных + * скачка, а не затухание. * keep_ui сохраняет диапазон 0x37..0x3F ярким для текста/рамки меню. */ void pop_ui_palette_snapshot(void) __banked; -#define POP_PAL_DIM_BLACK 8 +#define POP_PAL_DIM_BLACK 16 + +/* СКОЛЬКО КАДРОВ ЖДАТЬ, чтобы fade длился столько же, сколько у оригинала + * (fade_in_1/fade_out_1 — 64 шага по 2 тика = 128 тиков = 2,13 с, то есть + * 106 кадров луча). + * + * Ждать все 106 нельзя: пересчёт палитры не бесплатен — 487 тысяч тактов + * на ступень, то есть чуть больше кадра (замер в MAME 2026-08-26; из них + * 136 тысяч уходит на заливку через BIOS). Семнадцать ступеней съедают + * около двадцати кадров, и без вычитания fade растянулся бы длиннее + * заказанного. */ +#define POP_FADE_STEP_FRAMES 1 /* цена одного пересчёта, кадров луча */ +#define POP_FADE_FRAMES (106 - (POP_PAL_DIM_BLACK + 1) * POP_FADE_STEP_FRAMES) void pop_ui_palette_dim(uint8_t dim_step, uint8_t keep_ui) __banked; void pop_ui_palette_restore(void) __banked;