Оптимизация каскада плит: -27% работы за кадр (пять правок с замерами)
Сцена — ур.13 комната 23, шесть плит с потолка (docs/perf_l13_room23.md).
Все замеры сняты одним и тем же прогоном «ESC -> зонды -> roomtest».
было стало
работа за кадр 1 437 150 1 051 332 -27%
зелёная (фон) 805 000 575 730 -28%
циан (спрайты) 631 800 438 546 -31%
синяя 142 830 142 830 в бюджете
Цена одной перерисовки:
RDA_CEIL_GONE (запечь колодец) 251 335 -> 137 975 -45%
RD_FLOOR (щебень на посадке) 198 805 -> 182 124 -8%
RDA_CEIL (дрожащая плита) 48 785 -> 43 543 -11%
C1. Пометки «фон трогали» в mob_render ПОДАВЛЕНЫ (pop_cd_mute): коридор
куска уже помечен одним вызовом в pop_loose_mob_tick, а три блита метили
подмножества того же прямоугольника по 4 502 такта. Плюс пометка в
mob_spawn_copy — кусок, рождённый внутри тика, свою мог не получить
(слот выдаётся с начала таблицы, то есть уже пройденный). -55 тыс.
G2. Контекст тайла — file-scope, а не локали draw_tile (порт
load_curr_and_left_tile, seg008:0339). В функции 57 вызовов, и каждое
живое через вызов значение спиливалось: 26 байт кадра и 513 обращений
`-N(ix)`. Стало 33 обращения, кадра нет, банк 7 -703 Б. -55 тыс.
G1. Окно клипа для точечной перерисовки (pop_t_win_set/clear). В
pop_ceil_bake_empty куски ряда 0 высотой 63 px рисовались целиком, хотя
восстановить надо девять строк: блит стоил 21 447, стал 7 619. -90 тыс.
В pop_floor_bake окно попробовано и ОТКАЧЕНО (там нет клипа, блиты шли
быстрым путём, а окно уводило их в blit_b_clip и не отсеивало ничего:
187 266 -> 198 279) — вернуться после G3, запись в коде.
G3. blit_b_clip: байтовый габарит + file-scope вместо локалей. Одного
байтового габарита НЕ ХВАТИЛО (кадр остался, 211 -> 173 обращений) —
значений, живых через шесть вызовов ядер libbgi, больше, чем регистров.
С file-scope: 51 обращение, кадр 22 -> 12 Б. Клипованный блит подешевел.
C4. Падающий кусок КЛИПУЕТСЯ сам, вместо чистки бортов после. Раньше он
рисовался в борт целиком и взводил border_dirty, а pop_room_clip_borders
стирал две полосы 320x28 — 150 978 тактов в каждом кадре, пока хоть один
кусок торчит выше поля (гряда 13-го рождается ровно у потолка, y=2), то
есть почти весь каскад. Стало 1 722. -138 тыс.
Заодно blit_b_oversize больше не ходит через blit_b_clip (у того габарит
теперь байтовый) — рисует напрямую gfx_blit_part. Это путь под
полноэкранные подложки интро/финала (320x200, docs/perf_l13_room23.md §5).
Проверено: 8 наборов tests-host зелёные; в MAME каскад рисуется корректно
(кадр с летящими плитами, чистые борта, итоговая картинка как до правок).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -246,11 +246,20 @@ void pop_cd_batch_end(void)
|
||||
pop_cd_touch(cdb_x0, cdb_y0, cdb_x1 - cdb_x0 + 1, cdb_y1 - cdb_y0 + 1);
|
||||
}
|
||||
|
||||
/* Подавление на серию блитов — см. шапку в pop_cdraw.h. Отдельным значением
|
||||
* того же флага, а не вторым флагом: у pop_cd_touch тогда остаётся ОДНА
|
||||
* проверка на общем пути. */
|
||||
#define CD_BATCH_MUTE 2
|
||||
|
||||
void pop_cd_mute(void) { pop_cd_batch = CD_BATCH_MUTE; }
|
||||
void pop_cd_unmute(void) { pop_cd_batch = 0; }
|
||||
|
||||
void pop_cd_touch(int x, int y, int w, int h)
|
||||
{
|
||||
uint16_t cols;
|
||||
uint8_t r0, r1;
|
||||
if (pop_cd_batch) { /* копим, не разбирая на колонки/ряды */
|
||||
if (pop_cd_batch == CD_BATCH_MUTE) return; /* область помечена вызывающим */
|
||||
int x1 = x + w - 1, y1 = y + h - 1;
|
||||
if (x < cdb_x0) cdb_x0 = x;
|
||||
if (y < cdb_y0) cdb_y0 = y;
|
||||
@@ -310,12 +319,65 @@ void pop_cd_clear(uint8_t p)
|
||||
* перерисовка тайлов — идёт с pop_t_fclip_on == 0. */
|
||||
#define FLIP_TOP(top, h) POP_FLIP_TOP(top, h) /* формула — в pop_bg.h */
|
||||
|
||||
static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
|
||||
/* Окно под точечную перерисовку/запечку — контракт в шапке pop_tile.h.
|
||||
* Логическая пара нужна для отсева тайлов (он считается из ряда комнаты),
|
||||
* экранная — для клипа самого блита; при перевороте они разные. */
|
||||
void pop_t_win_set(int x, int ytop, int w, int h)
|
||||
{
|
||||
int dx = x, dy = top, dw = w, dh = h, sx = 0, sy = 0;
|
||||
pop_t_fclip_x0 = x;
|
||||
pop_t_fclip_x1 = x + w;
|
||||
pop_t_fclip_ly0 = ytop;
|
||||
pop_t_fclip_ly1 = ytop + h;
|
||||
if (pop_upside) {
|
||||
int t = FLIP_TOP(ytop, h);
|
||||
pop_t_fclip_y0 = t;
|
||||
pop_t_fclip_y1 = t + h;
|
||||
} else {
|
||||
pop_t_fclip_y0 = ytop;
|
||||
pop_t_fclip_y1 = ytop + h;
|
||||
}
|
||||
pop_t_fclip_on = 1;
|
||||
}
|
||||
|
||||
void pop_t_win_clear(void) { pop_t_fclip_on = 0; }
|
||||
|
||||
/* Клипованный путь блита. Габарит и смещения внутри куска — БАЙТЫ, а вся
|
||||
* рабочая переменная — file-scope, НЕ локали.
|
||||
*
|
||||
* Корректность байтового габарита по построению: кадры наших атласов не
|
||||
* крупнее 56x63 (просканированы все 109 .atl, docs/perf_l13_room23.md §5), а
|
||||
* кадр больше 255 в pop_blit_b уходит в blit_b_oversize и сюда не попадает.
|
||||
*
|
||||
* Зачем file-scope: с локалями SDCC заводил 22-байтовый стековый кадр и 211
|
||||
* обращений `-N(ix)` — при ~46 замеренных тактах на обращение это ~9 700
|
||||
* такта из 14 088 цены клипованного блита (замер 2026-08-17,
|
||||
* docs/perf_green_phase.md §1.6). Одного байтового габарита НЕ ХВАТИЛО:
|
||||
* кадр остался, обращений стало 173 — значений, живых через шесть вызовов
|
||||
* ядер libbgi, всё равно больше, чем регистров у Z80. С file-scope
|
||||
* спиливать нечего, а адресация абсолютная (13 тактов против 19).
|
||||
* Тот же приём и по той же причине применён к draw_tile (pop_room.c).
|
||||
*
|
||||
* Реентерабельности не было и не требуется: единственный вызывающий —
|
||||
* pop_blit_b, и он себя не зовёт.
|
||||
*
|
||||
* Арифметика клипа считается в int16 (bc_d), в байт уходит только результат —
|
||||
* иначе вычитание ушло бы в перенос по модулю 256. */
|
||||
static const uint8_t *bc_img;
|
||||
static int bc_dx, bc_dy;
|
||||
static int16_t bc_d;
|
||||
static uint8_t bc_w, bc_h, bc_dw, bc_dh, bc_sx, bc_sy;
|
||||
|
||||
static void blit_b_clip(const uint8_t *img, int x, int top, uint8_t w, uint8_t h)
|
||||
{
|
||||
bc_img = img; bc_w = w; bc_h = h;
|
||||
bc_dx = x; bc_dy = top;
|
||||
bc_dw = w; bc_dh = h; bc_sx = 0; bc_sy = 0;
|
||||
if (!pop_upside) {
|
||||
if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */
|
||||
sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy;
|
||||
if (pop_t_clip_top && bc_dy < pop_t_clip_top) { /* полоса у потолка */
|
||||
bc_d = (int16_t)(pop_t_clip_top - bc_dy);
|
||||
if (bc_d >= (int16_t)bc_dh) return;
|
||||
bc_sy = (uint8_t)bc_d; bc_dy = pop_t_clip_top;
|
||||
bc_dh = (uint8_t)(bc_dh - bc_sy);
|
||||
}
|
||||
} else {
|
||||
/* ПЕРЕВОРОТ: то, что в обычном виде торчало ВЫШЕ поля (полоса кладки
|
||||
@@ -323,56 +385,95 @@ static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
|
||||
* где живёт полоса HP (баг «мусор ниже 192», найден пользователем).
|
||||
* Режем по границам поля ВСЕГДА, а не по pop_t_clip_top: зеркальный
|
||||
* спрайт может вылезти и там, где в обычном виде клип не ставили. */
|
||||
if (dy < POP_YOFF) { sy = POP_YOFF - dy; dy = POP_YOFF; dh -= sy; }
|
||||
if (dy + dh > POP_CLIP_BOTTOM) dh = POP_CLIP_BOTTOM - dy;
|
||||
if (bc_dy < POP_YOFF) {
|
||||
bc_d = (int16_t)(POP_YOFF - bc_dy);
|
||||
if (bc_d >= (int16_t)bc_dh) return;
|
||||
bc_sy = (uint8_t)bc_d; bc_dy = POP_YOFF;
|
||||
bc_dh = (uint8_t)(bc_dh - bc_sy);
|
||||
}
|
||||
if (bc_dy + bc_dh > POP_CLIP_BOTTOM) {
|
||||
bc_d = (int16_t)(POP_CLIP_BOTTOM - bc_dy);
|
||||
if (bc_d <= 0) return;
|
||||
bc_dh = (uint8_t)bc_d;
|
||||
}
|
||||
}
|
||||
if (pop_t_fclip_on) { /* окно fore-слоя */
|
||||
if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; }
|
||||
if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; }
|
||||
if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx;
|
||||
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
|
||||
if (pop_t_fclip_on) { /* окно fore-слоя / перерисовки */
|
||||
if (bc_dx < pop_t_fclip_x0) {
|
||||
bc_d = (int16_t)(pop_t_fclip_x0 - bc_dx);
|
||||
if (bc_d >= (int16_t)bc_dw) return;
|
||||
bc_sx = (uint8_t)(bc_sx + (uint8_t)bc_d);
|
||||
bc_dx += bc_d; bc_dw = (uint8_t)(bc_dw - (uint8_t)bc_d);
|
||||
}
|
||||
if (bc_dy < pop_t_fclip_y0) {
|
||||
bc_d = (int16_t)(pop_t_fclip_y0 - bc_dy);
|
||||
if (bc_d >= (int16_t)bc_dh) return;
|
||||
bc_sy = (uint8_t)(bc_sy + (uint8_t)bc_d);
|
||||
bc_dy += bc_d; bc_dh = (uint8_t)(bc_dh - (uint8_t)bc_d);
|
||||
}
|
||||
if (bc_dx + bc_dw > pop_t_fclip_x1) {
|
||||
bc_d = (int16_t)(pop_t_fclip_x1 - bc_dx);
|
||||
if (bc_d <= 0) return;
|
||||
bc_dw = (uint8_t)bc_d;
|
||||
}
|
||||
if (bc_dy + bc_dh > pop_t_fclip_y1) {
|
||||
bc_d = (int16_t)(pop_t_fclip_y1 - bc_dy);
|
||||
if (bc_d <= 0) return;
|
||||
bc_dh = (uint8_t)bc_d;
|
||||
}
|
||||
}
|
||||
if (dw <= 0 || dh <= 0) return;
|
||||
if (!bc_dw || !bc_dh) return;
|
||||
/* ПЕРЕВОРОТ: экранная строка (dy+i) берёт источник (h-1-i), поэтому
|
||||
* обрезка СВЕРХУ экрана съедает НИЖНИЕ строки источника. В терминах
|
||||
* куска источника это sy' = h - sy - dh (при обрезке только сверху даёт
|
||||
* 0, при обрезке только снизу — верхний край). */
|
||||
/* Проверки `dw < 256 && dh < 256 && w < 256` убраны как тождественные:
|
||||
* это уже байты. */
|
||||
if (pop_upside) {
|
||||
int fsy = h - sy - dh;
|
||||
if (fsy < 0) fsy = 0;
|
||||
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||
dx + dw <= 320 && dy + dh <= 256 && w < 256)
|
||||
gfx_blit_part_noclip_vflip(dx, dy, img, (uint8_t)sx, (uint8_t)fsy,
|
||||
(uint8_t)dw, (uint8_t)dh);
|
||||
bc_d = (int16_t)bc_h - (int16_t)bc_sy - (int16_t)bc_dh;
|
||||
if (bc_d < 0) bc_d = 0;
|
||||
if (bc_dx >= 0 && bc_dy >= 0 &&
|
||||
bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256)
|
||||
gfx_blit_part_noclip_vflip(bc_dx, bc_dy, bc_img, bc_sx,
|
||||
(uint8_t)bc_d, bc_dw, bc_dh);
|
||||
else
|
||||
gfx_blit_part_vflip(dx, dy, img, sx, fsy, dw, dh);
|
||||
gfx_blit_part_vflip(bc_dx, bc_dy, bc_img, bc_sx,
|
||||
(uint8_t)bc_d, bc_dw, bc_dh);
|
||||
} else
|
||||
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
|
||||
if (sx == 0 && sy == 0 && dw == w && dh == h)
|
||||
gfx_blit_noclip(dx, dy, img);
|
||||
if (bc_dx >= 0 && bc_dy >= 0 &&
|
||||
bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256) {
|
||||
if (bc_sx == 0 && bc_sy == 0 && bc_dw == bc_w && bc_dh == bc_h)
|
||||
gfx_blit_noclip(bc_dx, bc_dy, bc_img);
|
||||
else
|
||||
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
|
||||
(uint8_t)dw, (uint8_t)dh);
|
||||
gfx_blit_part_noclip(bc_dx, bc_dy, bc_img, bc_sx, bc_sy,
|
||||
bc_dw, bc_dh);
|
||||
} else {
|
||||
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана */
|
||||
gfx_blit_part(bc_dx, bc_dy, bc_img, bc_sx, bc_sy, bc_dw, bc_dh);
|
||||
}
|
||||
/* Кусок ФОНА лёг туда, где может лежать уже нарисованный персонаж —
|
||||
* пометить область (DRAW-COST, pop_cdraw.h). Признак «это не fore-проход
|
||||
* поверх персонажа» — окно клипа: его ставит только он. */
|
||||
if (!pop_t_fclip_on) pop_cd_touch(dx, dy, dw, dh);
|
||||
if (!pop_t_fclip_on) pop_cd_touch(bc_dx, bc_dy, bc_dw, bc_dh);
|
||||
}
|
||||
|
||||
/* Кадр шире или выше 255 — общий (медленный) путь. Вынесен отдельно, чтобы
|
||||
* 16-битная арифметика габарита не жила в горячем pop_blit_b: у нас таких
|
||||
* кадров нет вовсе (максимум 32x63), но контракт формата .atl их допускает. */
|
||||
* 16-битная арифметика габарита не жила ни в горячем pop_blit_b, ни в
|
||||
* blit_b_clip (у того габарит теперь БАЙТОВЫЙ, см. его шапку). У нас таких
|
||||
* кадров в игровом кадре нет вовсе (максимум 56x63), но контракт формата
|
||||
* .atl их допускает, а интро/финал такие подложки принесут (320x200) —
|
||||
* см. docs/perf_l13_room23.md §5.
|
||||
*
|
||||
* Клипа полосы у потолка и окна перерисовки здесь нет намеренно: такой кадр
|
||||
* — полноэкранная подложка, ей ни то, ни другое не адресовано; за клип по
|
||||
* экрану отвечает само ядро gfx_blit_part. */
|
||||
static void blit_b_oversize(const uint8_t *img, int x, int ybottom)
|
||||
{
|
||||
uint16_t w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
uint16_t h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
int top = ybottom - (int)h + 1 + POP_YOFF;
|
||||
if (pop_upside) top = FLIP_TOP(top, h);
|
||||
if (w && h) blit_b_clip(img, x, top, (int)w, (int)h);
|
||||
if (!w || !h) return;
|
||||
gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
||||
if (!pop_t_fclip_on) pop_cd_touch(x, top, (int)w, (int)h);
|
||||
}
|
||||
|
||||
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
@@ -429,7 +530,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
/* В перевёрнутом виде быстрый путь годится, только если спрайт
|
||||
* целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */
|
||||
if (pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) {
|
||||
blit_b_clip(img, x, top, (int)w, (int)h);
|
||||
blit_b_clip(img, x, top, w, h);
|
||||
gfx_w0_unmap();
|
||||
return;
|
||||
}
|
||||
@@ -447,7 +548,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
pop_cd_touch(x, top, (int)w, (int)h);
|
||||
pop_dbg_b4(); /* ВРЕМЕННО */
|
||||
} else {
|
||||
blit_b_clip(img, x, top, (int)w, (int)h);
|
||||
blit_b_clip(img, x, top, w, h);
|
||||
pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user