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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user