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

173 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ЦИАН фаза (персонажи + передний слой) — анализ и оптимизация
Рабочий документ: живёт между сессиями. Внизу **журнал правок** — каждая
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
[`perf_l13_room23.md`](perf_l13_room23.md); там же ответ про 8-битные
габариты. Парная фаза — [`perf_green_phase.md`](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`](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` |