babd40bc84
Контрольный замер перед оптимизацией (задача OPT-BLIT требовала начать с
него, а не с правок). Сцена: старт уровня 13, комната 23, шесть плит
сыплются с потолка вразнобой.
Пик логического кадра — 1 437 150 тактов при растровом кадре 430 000, то
есть 5-6 растровых кадров вместо трёх. По секциям (бюджет 400 000):
синяя 142 830 (в норме), зелёная до 805 000, циан до 632 000.
Что нашлось (всё подтверждено зондами, не гипотезы):
RDA_CEIL дрожащая плита-потолок 48 658 x до 6 = 292 000
RDA_CEIL_GONE запечь колодец 251 023 x до 2 = 619 000
RD_FLOOR щебень на месте посадки 198 259 x до 2 = 397 000
Внутри RDA_CEIL: heal 8 491 + draw_tile 39 029, а блитов в этом draw_tile
всего 1,17 на тайл (15-16 тыс.) — то есть ~23 700 тактов чистых накладных.
Подсчёт asm сходится до процентов: у draw_tile 26-байтовый стековый кадр и
513 обращений (ix), при ~46 замеренных тактах на обращение это те же
~23 600. У blit_b_clip — 22 Б кадра и 211 (ix).
Разбор одного pop_blit_b (157 замеров быстрого пути): atlas_image+w0_map
1 086, ядро libbgi 10 422, pop_cd_touch 4 502 (28%!), unmap+возврат 264.
В mob_render эти три pop_cd_touch к тому же ИЗБЫТОЧНЫ — коридор куска уже
помечен одним вызовом в pop_loose_mob_tick: 81 000 тактов в кадре впустую.
Итог по пиковому кадру: блиты 488 100 (34%), из них «железный» минимум
пикселей ~150 000; остальные ~1 150 000 (80% кадра) — наши накладные.
Узкое место не передача пикселей, а 16-битная арифметика в IX-кадрах.
Ответ на вопрос про uint8_t: просканированы все 109 .atl и наборы
TITLE/PV — максимум по игровому кадру 56x63 (kid3), фон 48x63, спрайты
комнаты принцессы 49x60. Шире 255 только восемь полноэкранных подложек
титров/сюжета (три 320x200 и пять 256..272), и все рисуются раз на экран.
Значит горячий путь переводится на байтовые габариты целиком, а подложкам —
отдельный банк / прямой libbgi (решение пользователя).
Документы (решение пользователя — вести работу по фазам, чтобы результаты
не теряись между сессиями):
docs/perf_l13_room23.md сцена, рецепт воспроизведения, зонды, канал clog,
сводка по кадрам, габариты спрайтов
docs/perf_green_phase.md зелёная фаза: раскладка, позиции G1..G6, журнал
docs/perf_cyan_phase.md циан: раскладка, позиции C1..C7, журнал
Отдельно записан рецепт воспроизведения: сцена повторяется ТОЛЬКО
перезапуском программы (ESC → зонды → roomtest). Возврат в комнату не
годится — упавшая плита уходит в страницу уровня насовсем; рестарт уровня
не годится — гряда доваливается заочно через trob комнаты 17, пока телепорт
уносит Кида; запись отладчиком на бегущей машине затирается тем же trob.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
220 lines
13 KiB
Markdown
220 lines
13 KiB
Markdown
# ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация
|
||
|
||
Рабочий документ: живёт между сессиями. Внизу **журнал правок** — каждая
|
||
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
|
||
[`perf_l13_room23.md`](perf_l13_room23.md); там же ответ про 8-битные
|
||
габариты. Парная фаза — [`perf_cyan_phase.md`](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).**
|
||
|
||
| состояние | зелёная |
|
||
|---|---:|
|
||
| покой в комнате 23 | 35 760 |
|
||
| дрожат 6 плит-потолков | 366 000 |
|
||
| **пик каскада (провалы + посадки)** | **805 000 (2,0× бюджета)** |
|
||
|
||
---
|
||
|
||
## 1. Раскладка (замер 2026-08-17, `c312e4a`, зонды m5/m6/m7 + kind/m16)
|
||
|
||
### 1.1 Верхний уровень
|
||
|
||
| участок | покой | дрожь | пик |
|
||
|---|---:|---:|---:|
|
||
| `pop_loose_tick` | 25 602 | 35 646 | **196 152** |
|
||
| `pop_process_trobs` | 1 050 | 1 050 | 1 050 |
|
||
| **`pop_redraw_needed`** | 924 | **319 944** | **737 322** |
|
||
| хвост (шов, смена уровня, сигналы, вспышка) | 9 336 | 9 336 | 11 964 |
|
||
|
||
**`pop_redraw_needed` — 91 % фазы на пике.**
|
||
|
||
### 1.2 `pop_loose_tick` изнутри (зонды m9..m12)
|
||
|
||
| участок | покой | пик |
|
||
|---|---:|---:|
|
||
| два цикла по тайлам (30 + 10 позиций) | 11 004 | 9 852 |
|
||
| **`pop_loose_mob_tick`** (heal летящих кусков) | 7 416 | **178 494** |
|
||
| `check_loose_fall_on_kid` | 6 120 | 3 480 |
|
||
| хвост | 324 | 3 252 |
|
||
|
||
`pop_loose_mob_tick` при шести кусках в воздухе — **≈29 700 такта на кусок**.
|
||
Это heal прошлой позиции (коридор `MOB_W × 64`) плюс `pop_cd_touch` на
|
||
коридор плюс `mob_tick_one`.
|
||
|
||
### 1.3 Цена ОДНОЙ перерисовки по видам (66 + 12 + 12 замеров)
|
||
|
||
| вид | штук замерено | средняя цена | сколько за кадр | Σ за кадр |
|
||
|---|---:|---:|---:|---:|
|
||
| `RDA_CEIL` — дрожащая плита-потолок | 66 | **48 658** | до 6 | 292 000 |
|
||
| `RDA_CEIL_GONE` — запечь колодец | 12 | **251 023** | до 2 | 619 000 |
|
||
| `RD_FLOOR` — щебень на месте посадки | 12 | **198 259** | до 2 | 397 000 |
|
||
|
||
Для масштаба: ОДНА запечка колодца = 58 % растрового кадра, две = 144 %.
|
||
`pop_dbg_rdmax` подтверждает состав пика: шесть пометок, все `RDA_CEIL`.
|
||
|
||
### 1.4 `RDA_CEIL` = 48 658 изнутри (зонды m13/m14/m15)
|
||
|
||
| участок | такты |
|
||
|---|---:|
|
||
| `pop_heal_off(x, 0, 64, CEIL_BAND_H=8)` | 8 491 |
|
||
| `draw_tile(-1, col)` | **39 029** |
|
||
|
||
Блитов внутри этого `draw_tile` — **1,17 на тайл** (7 вызовов `pop_blit_b`
|
||
на 6 тайлов), то есть 15–16 тыс. тактов.
|
||
**Остальные ~23 700 такта — накладные `draw_tile`, ни одного пикселя.**
|
||
|
||
### 1.5 `RDA_CEIL_GONE` = 251 023 и `RD_FLOOR` = 198 259
|
||
|
||
- `pop_ceil_bake_empty(col)` — бар + **шесть** `draw_tile`
|
||
(ряд −1 и ряд 0, колонки col−1..col+1): 6 × 39 000 ≈ 234 000.
|
||
- `pop_floor_bake(row,col)` — бар 60×39 от `yb+26` + **два** `draw_tile`
|
||
НИЖНЕГО ряда; там стена, то есть `wall_pattern` — ≈**99 000 на тайл**.
|
||
|
||
### 1.6 Почему `draw_tile` стоит 39 000 при одном блите
|
||
|
||
Кодоген. Подсчёт в `bank7_pop_room.asm` и совпадение с замером до процентов
|
||
(одно `-N(ix)` ≈ 19 номинальных тактов ≈ 46 замеренных):
|
||
|
||
| функция | стековый кадр | обращений `(ix)` | ожидаемо | замерено |
|
||
|---|---:|---:|---:|---:|
|
||
| `draw_tile` | **26 Б** | **513** | ~23 600 | **~23 700** |
|
||
| `pop_ceil_bake_empty` | — | 8 | ~370 | |
|
||
| `pop_floor_bake` | — | 12 | ~550 | |
|
||
| `mob_tick_one` | 16 Б | 99 | ~4 550 | из ~29 700/кусок |
|
||
| `mob_draw_pass` | 18 Б | 20 | ~900 | |
|
||
|
||
У `draw_tile` пятнадцать локалей, объявленных `int`
|
||
(`code`/`lcode`/`lmod`/`xh`/`x`/`dby`/`dmy`/`t`/`lt`/`rbl_code`/`rbl_mod`/
|
||
`wmod`/`base_id`/`base_yb`), — в регистры они не влезли, и функция целиком
|
||
уехала в стековый кадр. Байтовые по природе из них все, кроме `x`/`dby`/`dmy`
|
||
(экранные координаты, бывают отрицательными).
|
||
|
||
---
|
||
|
||
## 2. Способы ускорения
|
||
|
||
По убыванию отдачи. Оценки — от замеров выше; каждая позиция сверена с
|
||
`SDLPoP/src/seg008.c` (правило проекта: механику сверять с оригиналом ДО
|
||
кодинга).
|
||
|
||
### G1. Окно клипа для ЗАПЕЧКИ — самый крупный кусок
|
||
|
||
**Отдача: −250 000 … −400 000 на пиковый кадр.**
|
||
|
||
`pop_ceil_bake_empty` и `pop_floor_bake` восстанавливают ИЗВЕСТНЫЙ
|
||
прямоугольник (тот, который сами же залили баром), а зовут полный
|
||
`draw_tile`, который честно рисует и то, что в этот прямоугольник не попадает.
|
||
У `pop_floor_bake` бар — 60×39 от `yb+26`, а тело дворцовой кладки рисуется
|
||
НИЖЕ `yb+64`, то есть **вся стена рисуется мимо цели**.
|
||
|
||
В `pop_blit_b` уже есть дешёвый предфильтр по окну `pop_t_fclip_*` — он режет
|
||
кусок ДО `atlas_image` и `gfx_w0_map`, то есть почти даром. Достаточно
|
||
выставлять это окно на время запечки.
|
||
|
||
Грабли, которые надо обойти: `pop_t_fclip_on` в `pop_blit_b` заодно подавляет
|
||
`pop_cd_touch` (признак «это fore-проход поверх персонажа»). Для запечки это
|
||
как раз правильно — она помечает область сама, — но проверить обязательно;
|
||
если понадобится, ввести отдельный флаг «окно только для отсева».
|
||
|
||
Сверка с оригиналом: у него та же идея, только через `wipetable` —
|
||
`draw_tile_wipe(height)` кладёт прямоугольник в таблицу, а `draw_table`
|
||
рисует уже с известным клипом (seg008:1368, 1373).
|
||
|
||
### G2. Расколоть `draw_tile` + отдельный лист для ряда −1
|
||
|
||
**Отдача: 39 029 → 18 000–20 000 на тайл полосы, то есть −140 000 на кадр
|
||
дрожания; и она умножается на G1 (шесть вызовов в запечке).**
|
||
|
||
Два шага:
|
||
|
||
1. **Отдельный `draw_tile_aboveroom(col)`** — дословный порт seg008:01F2.
|
||
Оригинал для ряда −1 делает РОВНО шесть вещей: `draw_tile_floorright`,
|
||
`draw_tile_anim_topright`, `draw_tile_right`, `draw_tile_bottom(1)`,
|
||
`draw_loose(1)`, `draw_tile_fore`. Ни базы, ни `draw_tile_anim`, ни
|
||
`draw_tile_anim_right`, ни `wall_pattern`, ни вычисления `rbl_*` через
|
||
`row+1 > 2`. У нас всё это отсекается ветками `if (row >= 0)` уже ВНУТРИ
|
||
общего `draw_tile` — то есть кадр IX, пролог и вся арифметика оплачиваются
|
||
всегда.
|
||
2. **Расколоть общий `draw_tile` по образцу оригинала** (seg008:01C7 —
|
||
девять узких листьев). Каждый лист получает `xh`/`dby`/`dmy` параметрами
|
||
(в регистрах), локалей у него единицы, кадра IX нет.
|
||
|
||
Заодно проверить `redraw_needed_above` (seg008:02C1): оригинал делает
|
||
`draw_tile_wipe(3)` — стирает **32×3**, а мы `pop_heal_off(x,0,64,8)` =
|
||
64×8. Площадь в 5 раз больше нужной (8 491 такта), хотя основная цена там —
|
||
накладные вызова.
|
||
|
||
### G3. `blit_b_clip` — 22 Б кадра, 211 `(ix)`
|
||
|
||
**Отдача: 14 088 → ~10 500 на блит. В зелёной фазе ВСЕ блиты клипованные
|
||
(полоса у потолка выставляет `pop_t_clip_top`) — при 7–17 блитах это
|
||
−25 000 … −60 000; в циане и fore-проходе больше.**
|
||
|
||
`dx/dy/dw/dh/sx/sy` объявлены `int`. По построению `sx/sy/dw/dh ≤ 255`
|
||
(габарит любого спрайта ≤ 56×63, см. `perf_l13_room23.md` §5) — их можно
|
||
сделать `uint8_t`, оставив 16-битными только экранные `dx/dy`.
|
||
Прецедент: тот же приём в `pop_blit_b` (коммит `c312e4a`) дал −19 % на
|
||
клипованном пути и −16 % на быстром, `_CODE` −42 Б.
|
||
|
||
Проверка по правилу OPT-BLIT: после правки смотреть пролог в
|
||
`.sprinter-cc-roomtest/pop_tile.asm` — исчез ли `ld iy,#-22`, сколько
|
||
осталось `-N(ix)`.
|
||
|
||
### G4. `mob_tick_one` / `pop_loose_mob_tick` — 29 700 на кусок
|
||
|
||
**Отдача: −30 000 … −48 000 на кадр при шести кусках.**
|
||
|
||
16-байтовый кадр, 99 `(ix)`. Байтовыми могут быть габарит heal-коридора и
|
||
разность `prev_y`; `x`/`y` — 16-битные. Плюс `pop_cd_touch` на коридор — один
|
||
на кусок, 4 502 такта (см. `perf_cyan_phase.md` §1.3 про его цену).
|
||
|
||
### G5. `LOOSE-SHAKE-RUNS` — пометки по сменам кадра
|
||
|
||
**Отдача: ~20 % работы фазы, и только на дрожащих плитах. Оценка старая
|
||
(2026-08-13), не перемерена.**
|
||
|
||
Подробности и арифметика — в `roomtest/TASKS_OPEN.md#loose-shake-runs`.
|
||
Коротко: наивный вариант «помечать только на смене кадра» выигрыша НЕ даёт
|
||
(пять смен × две страницы = те же десять перерисовок); работает только версия
|
||
«пары и тройки» — две пометки с `pages = 2` на фазах 5 и 8. Тот же приём
|
||
применим к плитам-ПОТОЛКАМ (`pop_set_redraw_above`), где сейчас идёт пометка
|
||
каждый кадр отсчёта.
|
||
|
||
Вопрос открытый: стоит ли неочевидный код такого узкого выигрыша. Браться
|
||
после G1–G4.
|
||
|
||
### G6. `BG-ONCE` — heal вместо чёрного бара
|
||
|
||
Предложение пользователя, см. `roomtest/TASKS_OPEN.md#bg-once`. Сейчас
|
||
запечка = «залить чёрным + перерисовать фон», то есть двойная работа; heal по
|
||
координатам бара делает то же за один проход. Прямо дополняет G1.
|
||
|
||
---
|
||
|
||
## 3. Что НЕ делать (проверено, отрицательный результат)
|
||
|
||
- **Не ускорять передачу пикселей** — она на пределе железа (3+3 такта на
|
||
байт через акселератор), memory `blit_cost_model`. В пиковом кадре
|
||
«железный» минимум всех блитов ≈150 000 из 1 437 150.
|
||
- **Не откладывать запекание на другой кадр** — запечка пишет ОЗУ-копию, из
|
||
которой восстанавливает heal; отложенная даёт призрак плиты на месте дыры
|
||
(проверено 2026-08-13).
|
||
- **Не батчить смежные колонки в `pop_ceil_shake_draw`** — пробовалось и
|
||
убрано: плиты стартуют со случайными задержками, в кадре дрожат разрозненные
|
||
колонки, пробег почти всегда длиной в одну.
|
||
- **Не искать проблему в W3-скобке** (`_bgi_begin`/`_bgi_end` — пять
|
||
инструкций) и не списывать разброс на прерывания (внутри размерной группы
|
||
разброс три такта).
|
||
|
||
---
|
||
|
||
## 4. Журнал правок
|
||
|
||
| дата | что сделано | зелёная: покой / дрожь / пик | коммит |
|
||
|---|---|---|---|
|
||
| 2026-08-17 | базовый замер (до правок этой серии) | 35 760 / 366 000 / **805 000** | `c312e4a` |
|