Files
Sprinter-SDCC/applications/PoP/docs/perf_cyan_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

11 KiB
Raw Blame History

ЦИАН фаза (персонажи + передний слой) — анализ и оптимизация

Рабочий документ: живёт между сессиями. Внизу журнал правок — каждая запись с замером до/после. Сцена, рецепт воспроизведения и зонды — perf_l13_room23.md; там же ответ про 8-битные габариты. Парная фаза — perf_green_phase.md.

Границы фазы в roomtest.c: от PROF(6) (строка 620) до PROF(0) (строка 677). Содержимое: pop_check_mirror, pop_loose_mob_draw, соперник, pop_char_draw(KID), pop_loose_mob_draw_over, pop_fore_needed, pop_hp_draw, pop_char_fore(KID), pop_cd_clear, pop_room_clip_borders.

Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).

состояние было (2026-08-17, c312e4a) стало (af189a1)
покой, Кид пропущен 20 550 20 550
Кид перерисовывается, кусков нет 193 000 192 000
пик каскада (6 кусков + Кид) 631 800 270 510 ✔

ЦЕЛЬ ФАЗЫ ВЫПОЛНЕНА (270 510 при бюджете 400 000, запас 32 %).


1. Что решило дело

C1. Пометки «фон трогали» в mob_render подавлены — −55 000

pop_loose_mob_tick помечает весь коридор куска одним вызовом, а три блита внутри mob_render метили подмножества того же прямоугольника по 4 502 такта каждый. Механизм — pop_cd_mute()/pop_cd_unmute()pop_tile.c; отдельное значение того же флага pop_cd_batch, чтобы у pop_cd_touch на общем пути осталась ОДНА проверка).

Добавлена пометка в mob_spawn_copy: кусок, рождённый ВНУТРИ тика (loose_fall сбил плиту), получает слот с начала таблицы, то есть уже пройденный циклом, — своей пометки в этом кадре он бы не получил, а нарисован был бы. Без этого пропущенная пометка = стёртый и не перерисованный персонаж.

C4. Кусок клипуется САМ, вместо чистки бортов после — −138 000

Самая крупная и самая неожиданная статья. В mob_render стоял pop_clip_sprite, то есть кусок рисовался в борт целиком и взводил border_dirty; pop_room_clip_borders потом стирал ДВЕ полосы во всю ширину экрана (320×28 и 320×28) — 150 978 тактов в КАЖДОМ кадре, пока хоть один кусок торчит выше поля. А гряда 13-го уровня рождается ровно у потолка (y = 2), то есть почти весь каскад. Стало 1 722.

Теперь окно клипа (pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H)) ставится ТОЛЬКО когда кусок реально задевает борт: внутри поля блиты идут быстрым путём.

Композит куска: три блита → один — −163 000

Части env 70 / 74 / 72 складываются в ОДИН getimage-блоб при загрузке тайлсета (mob_spr_build в pop_room.c). Мотив прямо из blit_cost_model: у блита ~8 800 такта постоянных накладных против ~5 000 на пиксели, а шесть кусков в воздухе давали 18 вызовов = 258 708 такта, больше половины фазы.

Тонкости, которые пришлось соблюсти:

  • части перекрываются (74 и 70 обе идут от mob_x), поэтому композит собирается попиксельно с пропуском 0xFF — ровно как три прозрачных блита друг поверх друга;
  • габариты частей читаются, а не берутся константами: у тайлсетов правая часть разная (26 px в подземелье, 25 во дворце);
  • блоб лежит в обычной памяти (W2), поэтому блит идёт мимо atlas_image и gfx_w0_map/unmap — ещё ~1 350 такта на вызов. Новый резидентный лист pop_mem_b (pop_tile.c);
  • страйд блоба = его ширина; сначала считается точный габарит, потом копирование. Промежуточная версия объявляла блоб шириной 63 при фактических 58 и переносила пять прозрачных колонок на каждом кадре;
  • собирается на КАЖДУЮ смену тайлсета; резервный путь на три блита остался (mob_spr_ok).

Общие правки, попавшие и в эту фазу

  • blit_b_clip: байтовый габарит + file-scope вместо локалей (кадр 22 → 12 Б, обращений (ix) 211 → 51). Подробности — в perf_green_phase.md §G3.
  • pop_blit_b: аргументы в file-scope (76 → 11 обращений (ix)).

2. Раскладка на 2026-08-17 (до правок) — для истории

Подфазы (три готовых PROF(6): 0x4BCE / 0x4C26 / 0x4C93), пик:

участок покой пик
pop_check_mirror + pop_loose_mob_draw + соперник 23 250 154 512
pop_char_draw(KID) + pop_loose_mob_draw_over + pop_fore_needed + HP 3 726 445 284
pop_char_fore(KID) + pop_cd_clear + чистка бортов 1 722 150 978

Разбор одного pop_blit_b (157 замеров быстрого пути, зонды b1..b5):

участок такты доля
atlas_image + gfx_w0_map + чтение габарита 1 086 7 %
ядро блита (libbgi, gfx_blit_noclip) 10 422 64 %
pop_cd_touch — пометка «фон тронут» 4 502 28 %
gfx_w0_unmap + возврат 264 2 %
ИТОГО 16 273

Клипованный путь тогда же: ядро blit_b_clip 14 088, итого 16 409. После правок: клипованный блит 11 848, быстрый ~13 900 (у него больше пикселей).


3. Что осталось в запасе (если понадобится ещё)

Фаза в бюджете, поэтому это задел, а не план.

C5. Один gfx_w0_map/unmap на группу блитов

1 086 + 264 на вызов. Для композита куска уже не нужно (он в обычной памяти), но остаётся для тайлов фона: куски одного тайла часто лежат в одной странице атласа. Мешает то, что pop_blit_b — общий лист для всех вызывающих; нужна форма «открыть страницу, N блитов, закрыть».

C6. Размеры ленты — из каталога атласа

fw/fh уже лежат в записи каталога (8 байт: offset u16, fw u8, fh u8, nx u8, ny u8, резерв u16), и у всех фоновых лент nx = ny = 1, то есть они равны w/h из шапки. atlas_image их читает и выбрасывает.

C7. objtable вместо отдельного fore-прохода на персонажа

Позиции 5 и 6 старого perf_backlog.md — большой рефакторинг. Оригинал кладёт персонажей и куски в objtable и рисует их при обходе тайлов (draw_objtable_items_at_tile), порядок окклюзии получается сам; у нас отдельный fore-проход НА КАЖДОГО персонажа.

Снять временную оснастку

Шесть вызовов pop_dbg_b1..b6 внутри pop_blit_b — ~400 такта на блит (call + ret × 6), плюс pop_dbg_kind/m16 в pop_redraw_needed. Снимать ПОСЛЕ того, как оптимизация закончена: без них не мерить.


4. Что НЕ делать

  • Не ставить W3-скобку из кода с --w3 — белый экран (memory gfx_blit_noclip_fast).
  • Не ускорять передачу пикселей — предел железа (memory blit_cost_model).
  • Не возвращать клип куска по clip.right = 40 оригинала (add_mob_to_objtable, seg007:1161): единицы этого поля не выяснены, буквальные 40 экранных пикселей срезают правый задний угол плиты (прогон 2026-08-13).
  • Не сужать коридор heal «по палаццовому следу» — габариты частей у тайлсетов разные; брать высоту СОБРАННОГО композита (так и сделано).

5. Журнал правок

дата что сделано циан: покой / Кид / пик коммит
2026-08-17 базовый замер 20 550 / 193 000 / 631 800 c312e4a
2026-08-17 C1 подавление пометок в mob_render + пометка в mob_spawn_copy — / — / 577 050 a3c473d
2026-08-17 C4 кусок клипуется сам вместо чистки бортов; blit_b_clip байты+file-scope — / — / 438 546 a3c473d
2026-08-17 pop_blit_b аргументы в file-scope — / — / 435 180 b2da0b8
2026-08-17 композит куска: один блит вместо трёх — / — / 273 078 b2da0b8
2026-08-17 точный габарит композита (было 63 при 58) 20 550 / 192 000 / 270 510 ✔ af189a1