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 занимает с ними около семи
|
тика, 128 тиков = 2,13 с; сцена перед уровнем 2 занимает с ними около семи
|
||||||
секунд. Наши четыре ступени укладывались в восемь сотых секунды, и сцена
|
секунд. Наши четыре ступени укладывались в восемь сотых секунды, и сцена
|
||||||
выходила втрое короче. Теперь `INTRO_FADE = POP_T60(128)`, а ступеней в
|
выходила втрое короче. Теперь `INTRO_FADE = POP_T60(128)`, а ступеней в
|
||||||
`pop_ui_palette_dim` восемь вместо четырёх (7/8, 3/4, 5/8, 1/2, 3/8, 1/4,
|
`pop_ui_palette_dim` шестнадцать вместо четырёх: на четырёх растянутых
|
||||||
1/8, 0 — каждая парой сдвигов, без умножения): на четырёх растянутых
|
|
||||||
ступенях затемнение выглядело бы скачками.
|
ступенях затемнение выглядело бы скачками.
|
||||||
|
|
||||||
|
**Цена ступени** (замеры в 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 —
|
**Чего пока нет.** Финальный `won` (56): 115 с, 1,2 МБ, 78 страниц EMM —
|
||||||
в `POP_MUS_PAGES` (20) он не помещается даже теоретически. Ему нужен
|
в `POP_MUS_PAGES` (20) он не помещается даже теоретически. Ему нужен
|
||||||
КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те,
|
КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те,
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
* 128 тиков, то есть 2,13 с (fade_in_1/fade_out_1 -> fade_*_frame ->
|
* 128 тиков, то есть 2,13 с (fade_in_1/fade_out_1 -> fade_*_frame ->
|
||||||
* do_simple_wait(1) при wait_time = 2). Наши четыре кадра укладывались в
|
* do_simple_wait(1) при wait_time = 2). Наши четыре кадра укладывались в
|
||||||
* восемь сотых секунды, и вся сцена выходила втрое короче оригинальной.
|
* восемь сотых секунды, и вся сцена выходила втрое короче оригинальной.
|
||||||
* Число здесь — кадры луча, ступени яркости внутри распределяет
|
* Точное число кадров считает POP_FADE_FRAMES (pop_ui.h): из
|
||||||
* pop_ui_fade_*. */
|
* заказанных 2,13 с вычитается то, что съедает сам пересчёт палитры. */
|
||||||
#define INTRO_FADE POP_T60(128)
|
#define INTRO_FADE POP_FADE_FRAMES
|
||||||
|
|
||||||
/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422): створка ворот
|
/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422): створка ворот
|
||||||
* закрывается, затем открывается дверь покоев, из которой входит Джафар. */
|
* закрывается, затем открывается дверь покоев, из которой входит Джафар. */
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
#define TITLE_H 200
|
#define TITLE_H 200
|
||||||
#define TITLE_Y POP_YOFF
|
#define TITLE_Y POP_YOFF
|
||||||
#define TITLE_PARTS 5
|
#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 {
|
enum {
|
||||||
TITLE_MAIN = 0,
|
TITLE_MAIN = 0,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
* выполняется строго после unmap. Это общий контракт для menu/title/PV.
|
* выполняется строго после unmap. Это общий контракт для menu/title/PV.
|
||||||
*/
|
*/
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
#include <graphics.h>
|
#include <graphics.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
@@ -120,17 +121,18 @@ void pop_text_right_mapped(pop_text_font_t font, int right, int baseline,
|
|||||||
|
|
||||||
/* W0 свободно: snapshot кладём в зарезервированный хвост страницы шрифта.
|
/* W0 свободно: snapshot кладём в зарезервированный хвост страницы шрифта.
|
||||||
* 4 × 256 Б = ровно 1024 Б, [0x3C00,0x4000), и не пересекаются с атласом. */
|
* 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)
|
static void snapshot_chunk(uint8_t chunk, uint8_t *buf, uint8_t store)
|
||||||
{
|
{
|
||||||
uint16_t i;
|
|
||||||
uint8_t *slot;
|
uint8_t *slot;
|
||||||
|
|
||||||
gfx_w0_map(font_at.page);
|
gfx_w0_map(font_at.page);
|
||||||
slot = (uint8_t *)(POP_FONT_SNAPSHOT_OFF + (uint16_t)chunk * 256u);
|
slot = (uint8_t *)(POP_FONT_SNAPSHOT_OFF + (uint16_t)chunk * 256u);
|
||||||
for (i = 0; i < 256; i++) {
|
if (store) memcpy(slot, buf, 256);
|
||||||
if (store) slot[i] = buf[i];
|
else memcpy(buf, slot, 256);
|
||||||
else buf[i] = slot[i];
|
|
||||||
}
|
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,43 +157,62 @@ void pop_ui_palette_snapshot(void) __banked
|
|||||||
/* ВОСЕМЬ СТУПЕНЕЙ, а не четыре. Fade у оригинала идёт 64 шагами по два
|
/* ВОСЕМЬ СТУПЕНЕЙ, а не четыре. Fade у оригинала идёт 64 шагами по два
|
||||||
* тика — 2,13 с плавного затухания; наши четыре ступени укладывались в
|
* тика — 2,13 с плавного затухания; наши четыре ступени укладывались в
|
||||||
* доли секунды и выглядели как моргание, а растянуть их по времени значило
|
* доли секунды и выглядели как моргание, а растянуть их по времени значило
|
||||||
* получить четыре заметных скачка яркости. Восемь ступеней собираются
|
* получить четыре заметных скачка яркости.
|
||||||
* пáрой сдвигов каждая (умножения на Z80 не нужно), а по времени их
|
*
|
||||||
* распределяет вызывающий (pop_ui_fade_*).
|
* Яркость ступени — простое умножение: (v * (8 - step)) >> 3. Через
|
||||||
|
* switch с парами сдвигов выходило дороже: 768 каналов на вызов, и цепочка
|
||||||
|
* сравнений SDCC съедала больше, чем стоит само умножение.
|
||||||
*
|
*
|
||||||
* dim_step: 0 — исходная, 8 — чёрная. */
|
* 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
|
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 chunk, i, start;
|
||||||
uint8_t *buf = pop_pal_buf;
|
uint8_t *buf = pop_pal_buf;
|
||||||
|
|
||||||
if (!font_ready) return;
|
if (!font_ready) return;
|
||||||
if (dim_step > POP_PAL_DIM_BLACK) dim_step = POP_PAL_DIM_BLACK;
|
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++) {
|
for (chunk = 0; chunk < 4; chunk++) {
|
||||||
start = (uint8_t)(chunk << 6);
|
start = (uint8_t)(chunk << 6);
|
||||||
snapshot_chunk(chunk, buf, 0);
|
snapshot_chunk(chunk, buf, 0);
|
||||||
if (dim_step) {
|
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);
|
uint8_t idx = (uint8_t)(start + i);
|
||||||
uint16_t p = (uint16_t)i * 4u;
|
|
||||||
if (keep_ui && idx >= 0x37 && idx <= 0x3F) continue;
|
if (keep_ui && idx >= 0x37 && idx <= 0x3F) continue;
|
||||||
buf[p] = dim_value(buf[p], dim_step);
|
p[0] = lut[p[0]];
|
||||||
buf[p + 1] = dim_value(buf[p + 1], dim_step);
|
p[1] = lut[p[1]];
|
||||||
buf[p + 2] = dim_value(buf[p + 2], dim_step);
|
p[2] = lut[p[2]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gfx_pal_load(0, start, 64, buf);
|
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;
|
uint8_t i, shown = 0xFF;
|
||||||
if (!steps) return;
|
|
||||||
pop_ui_palette_snapshot();
|
if (!frames) {
|
||||||
for (i = 1; i <= steps; i++) {
|
pop_ui_palette_dim(up ? 0 : POP_PAL_DIM_BLACK, 0);
|
||||||
uint8_t step = (uint8_t)((uint16_t)i * POP_PAL_DIM_BLACK / steps);
|
return;
|
||||||
pop_ui_palette_dim(step, 0);
|
}
|
||||||
|
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();
|
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
|
void pop_ui_fade_in(uint8_t steps) __banked
|
||||||
{
|
{
|
||||||
uint8_t i;
|
|
||||||
if (!steps) { pop_ui_palette_restore(); return; }
|
if (!steps) { pop_ui_palette_restore(); return; }
|
||||||
for (i = steps; i > 0; i--) {
|
fade_run(steps, 1);
|
||||||
uint8_t step = (uint8_t)((uint16_t)(i - 1) * POP_PAL_DIM_BLACK / steps);
|
|
||||||
pop_ui_palette_dim(step, 0);
|
|
||||||
gfx_wait_vsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pop_screen_begin(void) __banked
|
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 — чёрная; между ними
|
* FONT.ATL. dim_step: 0 — исходная, POP_PAL_DIM_BLACK — чёрная; между ними
|
||||||
* восемь равных ступеней (7/8, 3/4, 5/8, 1/2, 3/8, 1/4, 1/8). Восемь, а не
|
* равные ступени яркости (step/16). Шестнадцать, а не четыре, потому что
|
||||||
* четыре, потому что fade оригинала длится 2,13 с: на четырёх ступенях это
|
* fade оригинала длится 2,13 с: на четырёх ступенях это четыре заметных
|
||||||
* четыре заметных скачка яркости.
|
* скачка, а не затухание.
|
||||||
* keep_ui сохраняет диапазон 0x37..0x3F ярким для текста/рамки меню. */
|
* keep_ui сохраняет диапазон 0x37..0x3F ярким для текста/рамки меню. */
|
||||||
void pop_ui_palette_snapshot(void) __banked;
|
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_dim(uint8_t dim_step, uint8_t keep_ui) __banked;
|
||||||
void pop_ui_palette_restore(void) __banked;
|
void pop_ui_palette_restore(void) __banked;
|
||||||
|
|||||||
Reference in New Issue
Block a user