Зелёная и циан — раскладка ПОСЛЕ правок, журнал правок с коммитами, отрицательные результаты с объяснением причины (окно клипа в pop_floor_bake — три попытки), и что осталось: раскол draw_tile на девять узких частей по образцу seg008:01C7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15 KiB
ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация
Рабочий документ: живёт между сессиями. Внизу журнал правок — каждая
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
perf_l13_room23.md; там же ответ про 8-битные
габариты. Парная фаза — perf_cyan_phase.md (её цель
достигнута).
Границы фазы в roomtest.c: от PROF(4) (строка 450) до PROF(6)
(строка 620). Содержимое: pop_loose_tick, pop_process_trobs,
pop_redraw_needed, шов, смена уровня, сигналы провалов, вспышка.
Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).
| состояние | было (c312e4a) |
стало (af189a1) |
|---|---|---|
| покой в комнате 23 | 35 760 | 35 760 |
| дрожат 6 плит-потолков | 366 000 | 335 400 |
| пик каскада | 805 000 | 546 900 |
ЦЕЛЬ ФАЗЫ НЕ ДОСТИГНУТА: 546 900 против 400 000 (1,37×). Что осталось
сделать и почему это именно раскол draw_tile — §3.
1. Раскладка ПОСЛЕ правок (замер af189a1)
Пик — кадры 24-28 (посадки плит), больше не кадры провалов.
| участок | покой | дрожь | пик |
|---|---|---|---|
pop_loose_tick |
36 234 | 36 234 | 156 762 |
pop_process_trobs |
1 050 | 1 050 | 1 050 |
pop_redraw_needed |
924 | 288 800 | 380 568 |
| хвост (шов, смена уровня, сигналы, вспышка) | 9 336 | 9 336 | 10 776 |
pop_loose_tick изнутри на пике: два цикла по тайлам 9 852,
pop_loose_mob_tick 154 074 (heal шести летящих кусков), остальное мелочь.
Цена одной перерисовки: было → стало
| вид | было | стало | чем |
|---|---|---|---|
RDA_CEIL — дрожащая плита-потолок |
48 785 | 43 536 | G2 + G3 |
RDA_CEIL_GONE — запечь колодец |
251 335 | 138 318 | G1 (клип полосы) + G2 + G3 |
RD_FLOOR — щебень на месте посадки |
198 805 | 179 914 | G2 + G3 + снятая двойная пометка |
Штук за кадр: RDA_CEIL до 6, RDA_CEIL_GONE до 2, RD_FLOOR до 2.
Детальный профиль RD_FLOOR (179 914) — главная оставшаяся статья
Снят зондами по каждому блиту (pop_dbg_b1/b5) и по рамкам
(pop_bar_black, pop_cd_batch_begin/end):
| участок | такты |
|---|---|
вход pop_floor_bake + gfx_set_bank |
2 382 |
pop_bar_black 60×39 (включая pop_cd_touch 4 502) |
~15 500 |
контекст draw_tile #1 (5 чтений тайлов, 63*row, индексация таблицы) |
13 584 |
| 4 блита тайла #1 | 50 718 |
диспетчер между блитами #1 (все if (code == …)) |
11 388 |
контекст draw_tile #2 |
13 584 |
| 4 блита тайла #2 | ~54 000 |
| диспетчер между блитами #2 | 11 388 |
| хвост | 6 474 |
Итого: 105 500 — сами блиты (реальные пиксели), 74 400 — накладные, из
которых 27 168 контекст двух draw_tile и 22 776 их диспетчер.
2. Что сделано (с чем сравнивать)
G1. Окно клипа для точечной перерисовки — −90 000
pop_t_win_set(x, ytop, w, h) / pop_t_win_clear() в pop_tile.c: ставит
уже существующее окно pop_t_fclip_* на прямоугольник, который перерисовка
восстанавливает. Работает в обе стороны — и предфильтр pop_blit_b
отсеивает куски мимо окна ДО atlas_image/gfx_w0_map, и blit_b_clip
режет остальные по нему.
Где сработало: pop_ceil_bake_empty — куски ряда 0 высотой 63 px рисовались
целиком, хотя восстановить надо девять строк полосы. Блит 21 447 → 7 619,
вся перерисовка 251 335 → 138 318.
Где НЕ сработало — см. §4, отрицательные результаты.
Побочно: пока окно стоит, pop_blit_b не ставит пометку «фон трогали»
(признак fore-прохода), поэтому вызывающий обязан пометить прямоугольник сам.
В pop_ceil_shake_draw добавлен явный pop_cd_touch на область heal'а; в
pop_ceil_bake_empty и pop_floor_bake метит pop_bar_black, а лишний
второй вызов на ту же область снят.
G2. Контекст тайла — file-scope, а не локали draw_tile — −55 000
Порт load_curr_and_left_tile (seg008:0339): у оригинала это
curr_tile/curr_modifier/draw_xh/draw_main_y/draw_bottom_y —
переменные модуля, а не локали.
Причина в кодогене: в draw_tile 57 вызовов, и каждое живое через вызов
значение SDCC спиливал в стековый кадр — 26 байт кадра и 513 обращений
-N(ix) (при ~46 замеренных тактах на обращение это ~23 600, что и
намерено). Стало 33 обращения, кадра нет, банк 7 −703 Б.
G3. blit_b_clip — байтовый габарит + file-scope
Два шага, и важен порядок наблюдений:
- Байтового габарита ОДНОГО НЕ ХВАТИЛО.
sx/sy/dw/dh→uint8_t(корректно: кадры атласов ≤ 56×63) дало 211 → 173 обращения, а 22-байтовый кадр остался: значений, живых через шесть вызовов ядер libbgi, всё равно больше, чем регистров у Z80. - Решило вынесение из локалей (
bc_*): 51 обращение, кадр 22 → 12 Б.
Клипованный блит 14 088 → 11 848. Заодно blit_b_oversize больше не ходит
через blit_b_clip (там теперь байтовый габарит) — рисует напрямую
gfx_blit_part; это путь под полноэкранные подложки интро/финала.
G4. Мелочи
pop_blit_b: аргументы в file-scope (третий и дальше SDCC передаёт стеком, каждое чтение шло через-N(ix)) — 76 → 11 обращений.pop_loose_mob_tick: пометки всех кусков ОДНИМ пакетом (pop_cd_batch_begin/end) — было по 4 502 такта на кусок. 176 772 → 168 600.- Коридор heal куска — по фактической высоте СОБРАННОГО композита (было 24 строки константой, стало 20). 168 600 → 156 762.
3. Что осталось: раскол draw_tile (позиция G5)
Оставшийся разрыв: −147 000. Он весь в двух местах.
G5. Расколоть draw_tile на узкие части, как в оригинале
Ожидание: −50 000 … −60 000.
У оригинала draw_tile (seg008:01C7) — это девять независимых вызовов:
draw_tile_floorright, draw_tile_anim_topright, draw_tile_right,
draw_tile_anim_right, draw_tile_bottom, draw_loose, draw_tile_base,
draw_tile_anim, draw_tile_fore. Для ряда −1 он зовёт шесть из них
(draw_tile_aboveroom, seg008:01F2), для полосы у потолка — те же шесть плюс
draw_tile_wipe(3) (redraw_needed_above, seg008:02C1).
У нас всё это — ветки if (row >= 0) ВНУТРИ одной функции, то есть контекст
(13 584) и диспетчер (11 388) оплачиваются целиком всегда. Расколов, каждая
точечная перерисовка сможет звать только нужные части:
pop_floor_bake: вместо второго полногоdraw_tile(row, col+1)— только его правую грань и базу;pop_ceil_shake_draw/pop_ceil_bake_empty: дословныйdraw_tile_aboveroom;pop_loose_shake_draw,pop_spike_redraw,pop_gate_redraw— то же.
Риск средний: у draw_tile собрано много инвариантов (BUG-LOOSE-3,
BUG-LATTICE-DOORTOP, BUG-SEAM-WEDGE-1), проверять придётся прогонами всех
уровней. Поэтому делать отдельным заходом, а не хвостом другой правки.
G6. Меньше блитов в RD_FLOOR
Ожидание: неизвестно, надо мерить. 105 500 из 179 914 — это 7,6 блита,
и они рисуют настоящие пиксели. Сократить можно только сократив то, что
восстанавливается: бар сейчас 60×39 от yb+26, а плита занимает по вертикали
меньше (её куски: левая грань POP_LOOSE_FRAM_LEFT 32×13 на dmy = yb+62,
низ POP_LOOSE_FRAM_BOTTOM 32×3 на dby = yb+65, правая грань в соседе
26×16 на dby−1). То есть плита живёт в yb+47 .. yb+65, а бар начинается с
yb+26 — 21 лишняя строка сверху.
Проверять осторожно: бар заодно стирает и то, что рисует ДРУГИЕ куски тайла
(орнаментная лента stripe_id на dmy−27 = yb+35 попадает как раз в
«лишнюю» часть). Сузишь бар — надо убедиться, что ничего не осталось.
G7. heal летящих кусков — 154 074 (28 % фазы)
Шесть кусков × ~25 700: сам heal 64×20 (по модели ~19 700) + пакетная
пометка + накладные mob_tick_one (16-байтовый кадр, 99 обращений (ix)).
Сам heal у предела железа — это 1 280 пикселей на кусок, оптимизировать
нечего, кроме площади. Площадь уже подрезана до габарита композита.
Остаётся mob_tick_one (~4 500 на кусок = 27 000 на кадр) — то же лечение
file-scope, что у draw_tile.
G8. Снять временную оснастку
Шесть pop_dbg_b1..b6 внутри pop_blit_b — ~400 такта на блит; при 15
блитах зелёной это 6 000 на кадр. Плюс pop_dbg_kind/m16 (2 вызова на
перерисовку) и pop_dbg_m5..m15. Снимать ПОСЛЕ окончания оптимизации: без
них не мерить.
4. Отрицательные результаты — НЕ повторять
Окно клипа в pop_floor_bake — проверено ТРИ раза, каждый раз хуже
| попытка | было | стало |
|---|---|---|
| до G3 | 187 266 | 198 279 |
| после G3 | 182 124 | 188 460 |
после pop_blit_b file-scope, с детальным зондом |
179 914 | 188 417 |
Третья попытка объяснила причину: клипованный путь стоит +1 500 такта на КАЖДОМ из 7,6 блитов (+11 400), а режет он только редкие высокие куски — в трассе такие нашлись (34 878 → 25 872 и 28 818 → 24 090, всего −13 700), но в среднем по 12 перерисовкам их нет. Запись стоит в коде.
Прочее (проверено раньше)
- Не откладывать запекание на другой кадр — запечка пишет ОЗУ-копию, из которой восстанавливает heal; отложенная даёт призрак плиты на месте дыры.
- Не батчить смежные колонки в
pop_ceil_shake_draw— плиты стартуют со случайными задержками, в кадре дрожат разрозненные колонки, пробег почти всегда длиной в одну. - Не ускорять передачу пикселей — предел железа (3+3 такта на байт,
memory
blit_cost_model). В пиковом кадре «железный» минимум всех блитов ≈150 000 из 916 458. LOOSE-SHAKE-RUNS(пометки по сменам кадра): наивный вариант выигрыша НЕ даёт — пять смен × две страницы = те же десять перерисовок. Работает только версия «пары и тройки», ~20 % и только на дрожащих плитах; оценка 2026-08-13, не перемерена. Подробности —roomtest/TASKS_OPEN.md#loose-shake-runs.
5. Журнал правок
| дата | что сделано | зелёная: покой / дрожь / пик | коммит |
|---|---|---|---|
| 2026-08-17 | базовый замер | 35 760 / 366 000 / 805 000 | c312e4a |
| 2026-08-17 | G2 контекст тайла в file-scope | — / — / 747 954 | a3c473d |
| 2026-08-17 | G1 окно клипа в pop_ceil_bake_empty |
— / — / 663 250 | a3c473d |
| 2026-08-17 | G3 blit_b_clip байты + file-scope |
— / 335 400 / 575 730 | a3c473d |
| 2026-08-17 | pop_blit_b file-scope; пакетная пометка кусков |
— / — / 557 706 | b2da0b8 |
| 2026-08-17 | коридор heal по высоте композита | 35 760 / 335 400 / 546 900 | 9a50ab2 |