Files
Sprinter-SDCC/applications/PoP/docs/perf_green_phase.md
T
snark13 14e183108d Документы по фазам: результаты дня и оставшийся план
Зелёная и циан — раскладка ПОСЛЕ правок, журнал правок с коммитами,
отрицательные результаты с объяснением причины (окно клипа в
pop_floor_bake — три попытки), и что осталось: раскол draw_tile на девять
узких частей по образцу seg008:01C7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 14:23:38 +03:00

15 KiB
Raw Blame History

ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация

Рабочий документ: живёт между сессиями. Внизу журнал правок — каждая запись с замером до/после. Сцена, рецепт воспроизведения и зонды — 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_tile55 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

Два шага, и важен порядок наблюдений:

  1. Байтового габарита ОДНОГО НЕ ХВАТИЛО. sx/sy/dw/dhuint8_t (корректно: кадры атласов ≤ 56×63) дало 211 → 173 обращения, а 22-байтовый кадр остался: значений, живых через шесть вызовов ядер libbgi, всё равно больше, чем регистров у Z80.
  2. Решило вынесение из локалей (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 на dby1). То есть плита живёт в yb+47 .. yb+65, а бар начинается с yb+2621 лишняя строка сверху.

Проверять осторожно: бар заодно стирает и то, что рисует ДРУГИЕ куски тайла (орнаментная лента stripe_id на dmy27 = 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