Files
Sprinter-SDCC/applications/PoP/docs/perf_cyan_phase.md
T
snark13 f44663040c Регресс тактов на 23/13 после обхода уровней 1-2: изменений нет
Замер 418 кадров, зонды A/C/D/E (база модуля roomtest 0x42AD — совпала с
прошлой сборкой, фикс ушёл в банк).

                работа     синяя   зелёная      циан
af189a1        916 458   142 830   546 900   270 510
40f0d46        873 930   158 874   417 630   379 482
ec1f384        878 550   158 880   419 526   379 488

Фиксы второго уровня на бюджет не повлияли: +4 620 работы и +1 896 зелёной
— шум прогона.  Период: 3 растра в 392 кадрах, 4 в 24, 5 в одном, то есть за
бюджет вылезает только сам каскад.

Синяя и циан в бюджете 400 000; зелёная 419 526 — 1,05x цели и ниже
растрового кадра 430 000.  Остаток на потом: раскол draw_tile на узкие части
и идея G8 (инвалидация соседнего тайла полосой 28 px вместо целых 60).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 22:49:02 +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
2026-08-17 замер после фиксов уровня 1 — / — / 379 482 40f0d46
2026-08-17 регресс после фиксов уровня 2 — без изменений — / — / 379 488 ✔ ec1f384