Fade: пересчёт палитры в пять раз дешевле, шестнадцать ступеней
Затемнение длилось около восьми секунд вместо заказанных двух. Причин две, и обе измерены в MAME. Первая: пересчёт палитры звался на КАЖДОМ кадре — сто с лишним раз за fade, хотя ступеней всего восемь. Теперь пересчёт идёт только при смене ступени, остальное время цикл просто ждёт луча. Вторая: сам пересчёт стоил 2,44 млн тактов — почти шесть кадров. Разложение показало, что заливка палитры через BIOS тут ни при чём (136 тысяч на восемь вызовов). Съедали два цикла на C: 768 умножений uint16 на канал и побайтовое копирование снимка из страницы шрифта. Умножения заменены таблицей яркости на стеке (256 сложений, без единого умножения), копирование — memcpy, то есть LDIR. Итог: 487 тысяч тактов, в пять раз меньше. Освободившийся запас потрачен на плавность: ступеней теперь шестнадцать вместо восьми. Длительность fade считает POP_FADE_FRAMES — из 106 кадров (2,13 с оригинала) вычитается то, что съедает сам пересчёт. Host-тесты: 15 наборов. Проверено в MAME: заставка проходит цепочку кадров штатно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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) он не помещается даже теоретически. Ему нужен
|
||||
КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те,
|
||||
|
||||
@@ -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): створка ворот
|
||||
* закрывается, затем открывается дверь покоев, из которой входит Джафар. */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* выполняется строго после unmap. Это общий контракт для menu/title/PV.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user