Оптимизация каскада плит: -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:
@@ -255,47 +255,64 @@ static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
||||
}
|
||||
|
||||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||||
/* ---- КОНТЕКСТ ТАЙЛА (порт load_curr_and_left_tile, seg008:0339) -------
|
||||
* Не локали draw_tile, а file-scope переменные — ровно как curr_tile /
|
||||
* curr_modifier / draw_xh / draw_main_y / draw_bottom_y у оригинала.
|
||||
*
|
||||
* Зачем: в draw_tile 57 вызовов, и КАЖДОЕ значение, живое через вызов, SDCC
|
||||
* спиливал в стековый кадр — получались 26 байт кадра и 513 обращений
|
||||
* `-N(ix)` (замер 2026-08-17: 23 700 тактов накладных на вызов при одном
|
||||
* блите на тайл, docs/perf_green_phase.md §1.6). У file-scope переменных
|
||||
* абсолютная адресация (`ld a,(nn)` — 13 тактов против 19) и спиливать
|
||||
* нечего. Функция от этого не становится реентерабельной, но и не была:
|
||||
* draw_tile ниоткуда себя не зовёт. */
|
||||
static int dt_row, dt_col;
|
||||
static uint8_t dt_code, dt_lcode, dt_lmod;
|
||||
static int dt_xh, dt_x, dt_dby, dt_dmy;
|
||||
static const piece *dt_t, *dt_lt;
|
||||
static uint8_t dt_rbl, dt_rblm, dt_wmod, dt_base_id;
|
||||
static int dt_base_yb;
|
||||
|
||||
static void draw_tile(int row, int col)
|
||||
{
|
||||
uint8_t code = pop_tile_code(row, col);
|
||||
uint8_t lcode = pop_tile_code(row, col - 1);
|
||||
uint8_t lmod = pop_tile_mod(row, col - 1);
|
||||
dt_row = row; dt_col = col;
|
||||
dt_code = pop_tile_code(dt_row, dt_col);
|
||||
dt_lcode = pop_tile_code(dt_row, dt_col - 1);
|
||||
dt_lmod = pop_tile_mod(dt_row, dt_col - 1);
|
||||
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
|
||||
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
|
||||
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
|
||||
* соседа (его правая грань). */
|
||||
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||
if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } }
|
||||
else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; }
|
||||
int xh = POP_COL_XH[col];
|
||||
int x = xh * 8;
|
||||
int dby = 63 * row + 65;
|
||||
int dmy = dby - 3;
|
||||
const piece *t = &pop_tile_table[code];
|
||||
const piece *lt = &pop_tile_table[lcode];
|
||||
uint8_t rbl_code, rbl_mod;
|
||||
uint8_t wmod;
|
||||
if (dt_code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 1; }
|
||||
else if (dt_code == 0x06) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 5; }
|
||||
if (dt_lcode == 0x0F) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) { dt_lcode = 1; dt_lmod = 0; } }
|
||||
else if (dt_lcode == 0x06) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) dt_lcode = 5; }
|
||||
dt_xh = POP_COL_XH[dt_col];
|
||||
dt_x = dt_xh * 8;
|
||||
dt_dby = 63 * dt_row + 65;
|
||||
dt_dmy = dt_dby - 3;
|
||||
dt_t = &pop_tile_table[dt_code];
|
||||
dt_lt = &pop_tile_table[dt_lcode];
|
||||
|
||||
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
|
||||
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] =
|
||||
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
|
||||
if (row + 1 > 2) {
|
||||
int lc = col - 1;
|
||||
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[dt_col] =
|
||||
* room_B(0, dt_col-1); dt_col-1<0 → левая комната снизу = стена уровня. */
|
||||
if (dt_row + 1 > 2) {
|
||||
int lc = dt_col - 1;
|
||||
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
||||
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
||||
* «стена» там стоит, только если такой комнаты нет — как в
|
||||
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
||||
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
||||
else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 0;
|
||||
if (lc < 0) dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
||||
else dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 0;
|
||||
/* Модификатора ряда снизу у нас нет (pop_t_below — только fg), а он
|
||||
* нужен паласной панели над воротами. Ставим 0 = «панели нет»:
|
||||
* случай узкий (ворота в комнате СНИЗУ, по диагонали от пустого
|
||||
* тайла нижнего ряда), но расхождение с оригиналом честное. */
|
||||
rbl_mod = 0;
|
||||
dt_rblm = 0;
|
||||
} else {
|
||||
rbl_code = pop_tile_code(row + 1, col - 1);
|
||||
rbl_mod = pop_tile_mod(row + 1, col - 1);
|
||||
dt_rbl = pop_tile_code(dt_row + 1, dt_col - 1);
|
||||
dt_rblm = pop_tile_mod(dt_row + 1, dt_col - 1);
|
||||
}
|
||||
|
||||
/* Пометка «фон трогали» — ОДНА на весь тайл, а не на каждый его кусок
|
||||
@@ -306,72 +323,72 @@ static void draw_tile(int row, int col)
|
||||
/* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева.
|
||||
* Условие дословно: свой тайл пуст / верх большой колонны / верх двери,
|
||||
* а снизу-слева — ворота. --- */
|
||||
if ((code == 0 || code == 9 || code == 12) && rbl_code == 4) {
|
||||
uint8_t om = (rbl_mod > 188) ? 188 : rbl_mod;
|
||||
pop_env_b(GATE_TOP_MASK, x, dby); /* маска (mono, чёрным) */
|
||||
pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], x, dby);
|
||||
if ((dt_code == 0 || dt_code == 9 || dt_code == 12) && dt_rbl == 4) {
|
||||
uint8_t om = (dt_rblm > 188) ? 188 : dt_rblm;
|
||||
pop_env_b(GATE_TOP_MASK, dt_x, dt_dby); /* маска (mono, чёрным) */
|
||||
pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], dt_x, dt_dby);
|
||||
}
|
||||
|
||||
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
||||
if (code == 0 || code == 9 || code == 12 || code == 26) {
|
||||
draw_topright(rbl_code, rbl_mod, x, dby);
|
||||
if (dt_code == 0 || dt_code == 9 || dt_code == 12 || dt_code == 26) {
|
||||
draw_topright(dt_rbl, dt_rblm, dt_x, dt_dby);
|
||||
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
|
||||
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
|
||||
* floor_right: lt->floor_right. */
|
||||
* floor_right: dt_lt->floor_right. */
|
||||
}
|
||||
|
||||
/* --- draw_tile_right (seg008.c:456) --- */
|
||||
if (code != 20) {
|
||||
if (lcode == 20) {
|
||||
if (dt_code != 20) {
|
||||
if (dt_lcode == 20) {
|
||||
/* Полоска на стене (seg008:510) — только палас и только если у
|
||||
* стены не взведён старший бит модификатора. */
|
||||
if (PALACE && (lmod & 0x80) == 0)
|
||||
pop_env_b(84, x + 3 * 8, dmy - 27);
|
||||
pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
|
||||
} else if (lcode == 0) {
|
||||
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
|
||||
pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
|
||||
} else if (lcode == 1) {
|
||||
if (PALACE && (dt_lmod & 0x80) == 0)
|
||||
pop_env_b(84, dt_x + 3 * 8, dt_dmy - 27);
|
||||
pop_wall_b(1, dt_x, pop_tile_table[20].right_y + dt_dmy); /* грань стены */
|
||||
} else if (dt_lcode == 0) {
|
||||
if (dt_lmod <= 3 && BLUELINE_FRAM1[dt_lmod])
|
||||
pop_env_b(BLUELINE_FRAM1[dt_lmod], dt_x, BLUELINE_FRAM_Y[dt_lmod] + dt_dmy);
|
||||
} else if (dt_lcode == 1) {
|
||||
uint8_t num;
|
||||
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
|
||||
num = (lmod <= 3) ? lmod : 0;
|
||||
pop_env_b(42, dt_x, pop_tile_table[1].right_y + dt_dmy); /* правый треуг. пола */
|
||||
num = (dt_lmod <= 3) ? dt_lmod : 0;
|
||||
/* seg008:501 — `if (num == !!level_type) return;`: в подземелье
|
||||
* пропускается num==0, в паласе num==1. */
|
||||
if (num != (PALACE ? 1 : 0))
|
||||
pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
|
||||
} else if (lcode == 7 || lcode == 12) {
|
||||
pop_env_b(BLUELINE_FRAM3[num], dt_x, dt_dmy - 20); /* силуэт кладки */
|
||||
} else if (dt_lcode == 7 || dt_lcode == 12) {
|
||||
/* Низ декоративной панели над воротами (seg008:506) — только
|
||||
* палас; в подземелье у doortop right_id нулевой и ветка пустая. */
|
||||
if (PALACE && DOORTOP_FRAM_BOT[lmod & 3])
|
||||
pop_env_b(DOORTOP_FRAM_BOT[lmod & 3], x, lt->right_y + dmy);
|
||||
if (PALACE && DOORTOP_FRAM_BOT[dt_lmod & 3])
|
||||
pop_env_b(DOORTOP_FRAM_BOT[dt_lmod & 3], dt_x, dt_lt->right_y + dt_dmy);
|
||||
} else {
|
||||
if (lt->right_id)
|
||||
pop_env_b(lt->right_id, x, lt->right_y + dmy);
|
||||
if (dt_lt->right_id)
|
||||
pop_env_b(dt_lt->right_id, dt_x, dt_lt->right_y + dt_dmy);
|
||||
/* Орнаментная лента соседа (seg008:486) — только палас. Она и
|
||||
* даёт непрерывную полосу под окнами: stripe_id = 145 у пола,
|
||||
* кнопок, зелья, loose, чомпера и меча. */
|
||||
if (PALACE && lt->stripe_id)
|
||||
pop_env_b(lt->stripe_id, x, dmy - 27);
|
||||
if (PALACE && dt_lt->stripe_id)
|
||||
pop_env_b(dt_lt->stripe_id, dt_x, dt_dmy - 27);
|
||||
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
||||
if (lcode == 19 || lcode == 30)
|
||||
pop_env_b(146, x, dby - 28);
|
||||
if (dt_lcode == 19 || dt_lcode == 30)
|
||||
pop_env_b(146, dt_x, dt_dby - 28);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||||
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||||
* верхней полосы (dt_row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||||
* отрицательного индекса в pop_loose_modif. --- */
|
||||
if (row >= 0) {
|
||||
if (lcode == 4) { /* gate: решётка */
|
||||
pop_gate_back_b(lmod, xh, dby, dmy);
|
||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
|
||||
} else if (lcode == 2) { /* spike: статич. кадр */
|
||||
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
||||
if (dt_row >= 0) {
|
||||
if (dt_lcode == 4) { /* gate: решётка */
|
||||
pop_gate_back_b(dt_lmod, dt_xh, dt_dby, dt_dmy);
|
||||
} else if (dt_lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[dt_row * 10 + (dt_col - 1)])], dt_x, dt_dby - 1);
|
||||
} else if (dt_lcode == 2) { /* spike: статич. кадр */
|
||||
uint8_t sf = (dt_lmod & 0x80) ? 5 : dt_lmod;
|
||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||
pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||
} else if (lcode == 16) { /* дверь уровня */
|
||||
draw_leveldoor(lmod, xh, dmy);
|
||||
pop_env_b(SPIKES_FRAM_RIGHT[sf], dt_x, dt_dmy - 7);
|
||||
} else if (dt_lcode == 16) { /* дверь уровня */
|
||||
draw_leveldoor(dt_lmod, dt_xh, dt_dmy);
|
||||
}
|
||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) здесь, в статичной
|
||||
* отрисовке тайла, не рисуем — его ведёт pop_torch_draw из
|
||||
@@ -381,101 +398,101 @@ static void draw_tile(int row, int col)
|
||||
}
|
||||
|
||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||
if (code == 20) {
|
||||
wmod = pop_wall_modifier(row, col);
|
||||
if (dt_code == 20) {
|
||||
dt_wmod = pop_wall_modifier(dt_row, dt_col);
|
||||
if (!PALACE) /* во дворце тело стены даёт */
|
||||
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* wall_pattern */
|
||||
pop_wall_b(POP_WALL_FRAM_BOTTOM[dt_wmod], dt_x, dt_dby); /* wall_pattern */
|
||||
/* заливками (seg008:576) */
|
||||
pop_wall_pattern_b(row, col, 0);
|
||||
} else if (t->bottom_id) {
|
||||
pop_env_b(t->bottom_id, x, dby);
|
||||
pop_wall_pattern_b(dt_row, dt_col, 0);
|
||||
} else if (dt_t->bottom_id) {
|
||||
pop_env_b(dt_t->bottom_id, dt_x, dt_dby);
|
||||
}
|
||||
|
||||
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
|
||||
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
|
||||
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
|
||||
if (code == 11)
|
||||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col]
|
||||
: pop_ceil_modif[col])], x, dby);
|
||||
* верхней полосе (dt_row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
|
||||
* её состояние живёт отдельно, в pop_ceil_modif[dt_col] (pop_map). --- */
|
||||
if (dt_code == 11)
|
||||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(dt_row >= 0 ? pop_loose_modif[dt_row * 10 + dt_col]
|
||||
: pop_ceil_modif[dt_col])], dt_x, dt_dby);
|
||||
|
||||
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
|
||||
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (dt_row<0) базу НЕ
|
||||
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
|
||||
if (row >= 0) {
|
||||
uint8_t base_id = t->base_id;
|
||||
int base_yb = dmy;
|
||||
if (lcode == 26 && code == 12) {
|
||||
if (dt_row >= 0) {
|
||||
dt_base_id = dt_t->base_id;
|
||||
dt_base_yb = dt_dmy;
|
||||
if (dt_lcode == 26 && dt_code == 12) {
|
||||
/* «Lattice + door A» (seg008:622): у doortop своей базы нет
|
||||
* (base_id 0), и когда слева от него стоит арка (lattice_down),
|
||||
* (dt_base_id 0), и когда слева от него стоит арка (lattice_down),
|
||||
* пара рисуется ОДНИМ спрайтом 6, опущенным на 3 px. Без этой
|
||||
* ветки на месте тайла оставалась чёрная дыра — уровень 5,
|
||||
* комната 12, тайл (0,9) (BUG-LATTICE-DOORTOP). */
|
||||
base_id = 6;
|
||||
base_yb += 3;
|
||||
dt_base_id = 6;
|
||||
dt_base_yb += 3;
|
||||
}
|
||||
else if (code == 11) /* loose: левая грань (анимир.) */
|
||||
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
|
||||
else if (code == 0x0F && lcode == 0 && !PALACE) /* opener, слева пусто */
|
||||
base_id = 148; /* левая половина без пола — только */
|
||||
else if (dt_code == 11) /* loose: левая грань (анимир.) */
|
||||
dt_base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[dt_row * 10 + dt_col])];
|
||||
else if (dt_code == 0x0F && dt_lcode == 0 && !PALACE) /* opener, слева пусто */
|
||||
dt_base_id = 148; /* левая половина без пола — только */
|
||||
/* подземелье (seg008:628) */
|
||||
if (base_id)
|
||||
pop_env_b(base_id, x, t->base_y + base_yb);
|
||||
if (dt_base_id)
|
||||
pop_env_b(dt_base_id, dt_x, dt_t->base_y + dt_base_yb);
|
||||
}
|
||||
|
||||
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
||||
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
||||
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||||
* пустая. */
|
||||
if (row >= 0 && code == 2) {
|
||||
uint8_t sm = pop_tile_mod(row, col);
|
||||
if (dt_row >= 0 && dt_code == 2) {
|
||||
uint8_t sm = pop_tile_mod(dt_row, dt_col);
|
||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], dt_x, dt_dmy - 2);
|
||||
}
|
||||
/* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть
|
||||
* на draw_main_y, поверх неё кровь (если кого-то перемололо), затем
|
||||
* верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД
|
||||
* персонажем; передний слой рисует fore-проход. */
|
||||
if (row >= 0 && code == 0x12) {
|
||||
uint8_t cm = pop_tile_mod(row, col);
|
||||
if (dt_row >= 0 && dt_code == 0x12) {
|
||||
uint8_t cm = pop_tile_mod(dt_row, dt_col);
|
||||
uint8_t pose = pop_chomp_pose(cm);
|
||||
pop_env_b(CHOMP_FRAM_BOT[pose], x, dmy);
|
||||
pop_env_b(CHOMP_FRAM_BOT[pose], dt_x, dt_dmy);
|
||||
if (cm & 0x80)
|
||||
pop_env_b((uint8_t)(pose + 114), x + 8, dmy - 6);
|
||||
pop_env_b((uint8_t)(pose + 114), dt_x + 8, dt_dmy - 6);
|
||||
if (CHOMP_FRAM_TOP[pose])
|
||||
pop_env_b(CHOMP_FRAM_TOP[pose], x, dmy - CHOMP_FRAM_Y[pose]);
|
||||
pop_env_b(CHOMP_FRAM_TOP[pose], dt_x, dt_dmy - CHOMP_FRAM_Y[pose]);
|
||||
}
|
||||
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
||||
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
||||
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
||||
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
||||
if (row >= 0 && code == 22)
|
||||
pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3);
|
||||
if (dt_row >= 0 && dt_code == 22)
|
||||
pop_pot_b((uint8_t)((pop_tile_mod(dt_row, dt_col) == 1) + 10), dt_x, dt_dmy - 3);
|
||||
|
||||
/* --- draw_tile_fore (seg008.c:690) --- */
|
||||
if (code == 20) {
|
||||
wmod = pop_wall_modifier(row, col);
|
||||
if (dt_code == 20) {
|
||||
dt_wmod = pop_wall_modifier(dt_row, dt_col);
|
||||
if (!PALACE) /* seg008:711 */
|
||||
pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
|
||||
pop_wall_pattern_b(row, col, 1);
|
||||
pop_wall_b(POP_WALL_FRAM_MAIN[dt_wmod], dt_x, dt_dmy); /* основная грань */
|
||||
pop_wall_pattern_b(dt_row, dt_col, 1);
|
||||
}
|
||||
if (code == 0x12) { /* чомпер: передние зубья + кровь.
|
||||
if (dt_code == 0x12) { /* чомпер: передние зубья + кровь.
|
||||
* Отдельной веткой, как в оригинале:
|
||||
* у записи 0x12 fore_id == 0, и через
|
||||
* таблицу этот слой не идёт. */
|
||||
uint8_t cm = pop_tile_mod(row, col);
|
||||
uint8_t cm = pop_tile_mod(dt_row, dt_col);
|
||||
uint8_t pose = pop_chomp_pose(cm);
|
||||
/* Передние куски чомпера и его кровь лежат в pop_fore.atl
|
||||
* (FORE_ENV_IDS в pop_pack_bg.py), а НЕ в env-страницах: блит
|
||||
* обязан быть pop_fore_b, иначе id уходит в чужую страницу. */
|
||||
pop_fore_b(POP_CHOMP_FRAM_FOR[pose], x, dmy);
|
||||
pop_fore_b(POP_CHOMP_FRAM_FOR[pose], dt_x, dt_dmy);
|
||||
if (cm & 0x80)
|
||||
pop_fore_b((uint8_t)(pose + 119), x + 8, dmy - 6);
|
||||
pop_fore_b((uint8_t)(pose + 119), dt_x + 8, dt_dmy - 6);
|
||||
}
|
||||
if (code == 10) { /* зелье: chtab_1, не env fore_id.
|
||||
if (dt_code == 10) { /* зелье: chtab_1, не env fore_id.
|
||||
* Пузырёк — динамика (pop_potion_draw). */
|
||||
pop_potion_flask(row, col, pop_tile_mod(row, col));
|
||||
} else if (t->fore_id) {
|
||||
pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
||||
pop_potion_flask(dt_row, dt_col, pop_tile_mod(dt_row, dt_col));
|
||||
} else if (dt_t->fore_id) {
|
||||
pop_fore_b(dt_t->fore_id, dt_t->fore_x * 8 + dt_x, dt_t->fore_y + dt_dmy);
|
||||
}
|
||||
pop_cd_batch_end(); /* одна пометка на весь тайл */
|
||||
}
|
||||
@@ -595,9 +612,15 @@ void pop_ceil_shake_draw(int col) __banked
|
||||
pop_dbg_m13(); /* ЗАМЕР: вход pop_ceil_shake_draw */
|
||||
pop_heal_off(x, 0, 64, CEIL_BAND_H);
|
||||
pop_dbg_m14(); /* ЗАМЕР: heal сделан */
|
||||
/* Пометка «фон трогали» — ОДНА на восстановленный прямоугольник: пока
|
||||
* стоит окно (ниже), pop_blit_b пометок не ставит. Область та же, что у
|
||||
* heal'а, — он её и стирает. */
|
||||
pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pop_t_clip_top = POP_YOFF;
|
||||
pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H); /* восстанавливаем ровно полосу */
|
||||
draw_tile(-1, col);
|
||||
pop_t_win_clear();
|
||||
pop_t_clip_top = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_dbg_m15(); /* ЗАМЕР: draw_tile сделан */
|
||||
@@ -606,11 +629,17 @@ void pop_ceil_shake_draw(int col) __banked
|
||||
void pop_ceil_bake_empty(int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* bar мимо pop_blit_b */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* Отдельного pop_cd_touch тут не надо: pop_bar_black метит сам, и ровно
|
||||
* этот же прямоугольник (было два вызова на одну область). */
|
||||
pop_bar_black(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* стереть плиту в ОЗУ-копии */
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
pop_t_clip_top = POP_YOFF;
|
||||
/* Окно = ровно стёртая полоса. Главный выигрыш здесь не в отсеве, а в
|
||||
* КЛИПЕ: куски ряда 0 высотой 63 px рисовались целиком (клип был только
|
||||
* сверху, по pop_t_clip_top), хотя восстановить надо девять строк —
|
||||
* отсюда 21 447 такта на блит против 14 384 в pop_floor_bake. */
|
||||
pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H + 1);
|
||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||||
@@ -629,6 +658,7 @@ void pop_ceil_bake_empty(int col) __banked
|
||||
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
||||
}
|
||||
pop_t_bake_rest = 0;
|
||||
pop_t_win_clear();
|
||||
pop_t_clip_top = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
@@ -734,14 +764,22 @@ void pop_torch_wipe(int row, int col) __banked
|
||||
void pop_floor_bake(int row, int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
pop_cd_touch(x, POP_YOFF + 63 * row + 26, 60, 39); /* bar мимо pop_blit_b */
|
||||
int yb = POP_YOFF + 63 * row;
|
||||
/* Пометку ставит сам pop_bar_black — по тому же прямоугольнику; отдельный
|
||||
* pop_cd_touch тут был вторым вызовом на одну область (4 502 такта). */
|
||||
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
||||
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
||||
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_bar_black(x, yb + 26, 60, 39);
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
/* ОКНА КЛИПА ЗДЕСЬ НЕТ ОСОЗНАННО (замер 2026-08-17). Оно попробовано и
|
||||
* откачено: у этого тайла клипа нет вовсе, то есть блиты идут БЫСТРЫМ
|
||||
* путём (gfx_blit_noclip, 10 422), а окно уводит их в blit_b_clip
|
||||
* (14 088) — и не отсеивает ни одного куска (7,6 блита до и после).
|
||||
* Итог был 187 266 -> 198 279, то есть хуже. Вернуться к этой идее
|
||||
* после того, как blit_b_clip перестанет платить за 22-байтовый
|
||||
* стековый кадр (позиция G3 в docs/perf_green_phase.md). */
|
||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||
draw_tile(row, col);
|
||||
if (col + 1 < 10) draw_tile(row, col + 1);
|
||||
@@ -886,6 +924,29 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
||||
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
||||
#define MOB_Y_NONE 0x7FFF
|
||||
|
||||
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
|
||||
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
|
||||
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
|
||||
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
|
||||
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
|
||||
#define MOB_X0(mx) ((mx) - 4)
|
||||
/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом».
|
||||
* След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение
|
||||
* трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y).
|
||||
*
|
||||
* Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля.
|
||||
* А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой
|
||||
* операции кадра, помноженной на число падающих плит; сужена до 24.
|
||||
*
|
||||
* ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не
|
||||
* подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в
|
||||
* memory про акселератор таких требований нет. Единственный источник —
|
||||
* наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить
|
||||
* ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */
|
||||
#define MOB_W 64 /* mob_x-4 .. mob_x+60 */
|
||||
#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */
|
||||
#define MOB_HEAL_H 24 /* .. mob_y + 4 */
|
||||
|
||||
/* Свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
||||
* который ещё не стёрт со второй страницы). Общий для обычного отрыва и
|
||||
* для копии из loose_fall — раньше цикл был написан дважды. */
|
||||
@@ -949,6 +1010,13 @@ static void mob_spawn_copy(const mob_t *src)
|
||||
m->clean = 0;
|
||||
m->defer = 0;
|
||||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||||
/* Кусок рождается ВНУТРИ mob_tick_one (loose_fall сбил плиту), и слот ему
|
||||
* mob_alloc выдаёт с начала таблицы — то есть, скорее всего, УЖЕ
|
||||
* пройденный циклом pop_loose_mob_tick. Своей пометки «фон трогали» этот
|
||||
* кусок в текущем кадре не получит, а нарисован будет; помечаем коридор
|
||||
* здесь — иначе персонаж под ним стирается и не просит перерисовки
|
||||
* (см. pop_cd_mute в mob_render). */
|
||||
pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64);
|
||||
}
|
||||
|
||||
/* Отрыв куска в ЧУЖОЙ комнате: плита, которую Кид расшатал и убежал, всё
|
||||
@@ -960,28 +1028,6 @@ void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked
|
||||
mob_spawn(room, row, col);
|
||||
}
|
||||
|
||||
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
|
||||
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
|
||||
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
|
||||
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
|
||||
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
|
||||
#define MOB_X0(mx) ((mx) - 4)
|
||||
/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом».
|
||||
* След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение
|
||||
* трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y).
|
||||
*
|
||||
* Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля.
|
||||
* А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой
|
||||
* операции кадра, помноженной на число падающих плит; сужена до 24.
|
||||
*
|
||||
* ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не
|
||||
* подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в
|
||||
* memory про акселератор таких требований нет. Единственный источник —
|
||||
* наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить
|
||||
* ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */
|
||||
#define MOB_W 64 /* mob_x-4 .. mob_x+60 */
|
||||
#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */
|
||||
#define MOB_HEAL_H 24 /* .. mob_y + 4 */
|
||||
|
||||
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
|
||||
* должно утечь в новую комнату). */
|
||||
@@ -1306,13 +1352,38 @@ static void mob_render(mob_t *m, uint8_t pg)
|
||||
* Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и
|
||||
* «цельная ровная плита» вместо двух частей со сдвигом правой половины
|
||||
* на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). */
|
||||
pop_env_b(74, m->x, m->y);
|
||||
pop_env_b(70, m->x, m->y - 3);
|
||||
pop_env_b(72, m->x + 32, m->y - 1);
|
||||
/* Пометки «фон трогали» на время трёх блитов ПОДАВЛЕНЫ: коридор куска
|
||||
* (MOB_X0 .. +MOB_W, y−27 .. +37) уже помечен ОДНИМ вызовом — из
|
||||
* pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска,
|
||||
* рождённого внутри тика. Спрайт целиком внутри коридора: он занимает
|
||||
* x m->x .. m->x+57 (правая часть 72 идёт с x+32, ширина 26) при
|
||||
* коридоре m->x−4 .. m->x+59, и y m->y−15 .. m->y при коридоре
|
||||
* m->y−27 .. m->y+37 с запасом на ускорение (speed ≤ 31 за кадр).
|
||||
* Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на
|
||||
* кусок, то есть 81 000 в кадре при шести кусках. */
|
||||
pop_cd_mute();
|
||||
/* ВЫХОД ЗА ПОЛЕ РЕЖЕМ САМИ, а не чистим борта после. Раньше здесь стоял
|
||||
* pop_clip_sprite, то есть кусок рисовался в борт целиком, взводил
|
||||
* border_dirty, и pop_room_clip_borders потом стирал ДВЕ полосы во всю
|
||||
* ширину экрана — 320x28 и 320x28. Замер 2026-08-17: 150 978 тактов в
|
||||
* КАЖДОМ кадре, пока хоть один кусок торчит выше поля, то есть почти весь
|
||||
* каскад (гряда на 13-м рождается ровно у потолка, y = 2). Это была
|
||||
* четверть цианового блока.
|
||||
*
|
||||
* Окно ставим только когда кусок реально задевает борт: внутри поля блиты
|
||||
* идут быстрым путём (10 422 против 14 088). */
|
||||
{
|
||||
int ytop = m->y - 16 + POP_YOFF, ybot = m->y + POP_YOFF;
|
||||
uint8_t clipped = (uint8_t)(ytop < POP_YOFF || ybot >= POP_CLIP_BOTTOM);
|
||||
if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H);
|
||||
pop_env_b(74, m->x, m->y);
|
||||
pop_env_b(70, m->x, m->y - 3);
|
||||
pop_env_b(72, m->x + 32, m->y - 1);
|
||||
if (clipped) pop_t_win_clear();
|
||||
}
|
||||
pop_cd_unmute();
|
||||
m->prev_y[pg] = m->y;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
|
||||
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);
|
||||
}
|
||||
|
||||
/* Отрисовка кусков, идущих ПОД Kid. Звать ПОСЛЕ pop_redraw_needed(): фон
|
||||
|
||||
Reference in New Issue
Block a user