Files
Sprinter-SDCC/applications/PoP/roomtest/pop_tile.h
T
snark13 a3c473d910 Оптимизация каскада плит: -27% работы за кадр (пять правок с замерами)
Сцена — ур.13 комната 23, шесть плит с потолка (docs/perf_l13_room23.md).
Все замеры сняты одним и тем же прогоном «ESC -> зонды -> roomtest».

                       было       стало
  работа за кадр    1 437 150   1 051 332   -27%
  зелёная (фон)       805 000     575 730   -28%
  циан (спрайты)      631 800     438 546   -31%
  синяя                142 830     142 830   в бюджете

Цена одной перерисовки:
  RDA_CEIL_GONE (запечь колодец)  251 335 -> 137 975   -45%
  RD_FLOOR (щебень на посадке)    198 805 -> 182 124    -8%
  RDA_CEIL (дрожащая плита)        48 785 ->  43 543   -11%

C1. Пометки «фон трогали» в mob_render ПОДАВЛЕНЫ (pop_cd_mute): коридор
куска уже помечен одним вызовом в pop_loose_mob_tick, а три блита метили
подмножества того же прямоугольника по 4 502 такта.  Плюс пометка в
mob_spawn_copy — кусок, рождённый внутри тика, свою мог не получить
(слот выдаётся с начала таблицы, то есть уже пройденный).  -55 тыс.

G2. Контекст тайла — file-scope, а не локали draw_tile (порт
load_curr_and_left_tile, seg008:0339).  В функции 57 вызовов, и каждое
живое через вызов значение спиливалось: 26 байт кадра и 513 обращений
`-N(ix)`.  Стало 33 обращения, кадра нет, банк 7 -703 Б.  -55 тыс.

G1. Окно клипа для точечной перерисовки (pop_t_win_set/clear).  В
pop_ceil_bake_empty куски ряда 0 высотой 63 px рисовались целиком, хотя
восстановить надо девять строк: блит стоил 21 447, стал 7 619.  -90 тыс.
В pop_floor_bake окно попробовано и ОТКАЧЕНО (там нет клипа, блиты шли
быстрым путём, а окно уводило их в blit_b_clip и не отсеивало ничего:
187 266 -> 198 279) — вернуться после G3, запись в коде.

G3. blit_b_clip: байтовый габарит + file-scope вместо локалей.  Одного
байтового габарита НЕ ХВАТИЛО (кадр остался, 211 -> 173 обращений) —
значений, живых через шесть вызовов ядер libbgi, больше, чем регистров.
С file-scope: 51 обращение, кадр 22 -> 12 Б.  Клипованный блит подешевел.

C4. Падающий кусок КЛИПУЕТСЯ сам, вместо чистки бортов после.  Раньше он
рисовался в борт целиком и взводил border_dirty, а pop_room_clip_borders
стирал две полосы 320x28 — 150 978 тактов в каждом кадре, пока хоть один
кусок торчит выше поля (гряда 13-го рождается ровно у потолка, y=2), то
есть почти весь каскад.  Стало 1 722.  -138 тыс.

Заодно blit_b_oversize больше не ходит через blit_b_clip (у того габарит
теперь байтовый) — рисует напрямую gfx_blit_part.  Это путь под
полноэкранные подложки интро/финала (320x200, docs/perf_l13_room23.md §5).

Проверено: 8 наборов tests-host зелёные; в MAME каскад рисуется корректно
(кадр с летящими плитами, чистые борта, итоговая картинка как до правок).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 13:39:57 +03:00

173 lines
13 KiB
C
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.
/*
* pop_tile.h — ОБЩИЕ ЛИСТЬЯ отрисовки фона: блит куска атласа, чтение тайла
* комнаты, таблица тайлов. Внутренний заголовок слоя фона, НЕ публичный
* API (публичный — pop_bg.h).
*
* ПОЧЕМУ ОТДЕЛЬНЫЙ РЕЗИДЕНТНЫЙ МОДУЛЬ (pop_tile.c в _CODE / W1), а не часть
* банка 2. Слой фона режется надвое: горячая половина (fore-проход поверх
* персонажей, каждый кадр) и холодная (полная отрисовка комнаты и точечные
* перерисовки тайлов, раз на комнату/событие). Обе половины зовут одни и те
* же листья — blit_b, tile_code, tile_table. Разложить их по банкам нельзя:
*
* - код в чужом банке зовётся ТОЛЬКО через трамплин (по `__banked` в
* объявлении), и пометка бьёт по ВСЕМ вызывающим, включая горячих;
* - `const`-таблицы банка лежат в его СТРАНИЦЕ (0xC000+) и видны, лишь
* пока банк замаплен — из другого банка их не прочитать вовсе;
* - дублировать листья в оба банка — лишние килобайты и два места правки.
*
* Резидент W1 замаплен ВСЕГДА, поэтому и горячая, и холодная половина зовут
* их обычным `call` без трамплина, а таблицы читают напрямую. Тот же приём,
* что у pop_heal_fast (pop_draw.c) и pop_sword_draw (pop_kid.c).
*
* ПИСУЧИЕ глобалы (атласы, указатели на комнату, окно клипа) лежат в _DATA
* (W2) и видны отовсюду независимо от банка — они здесь ради одного места
* определения, а не ради доступности.
*/
#ifndef POP_TILE_H
#define POP_TILE_H
#include <stdint.h>
#include <sprite.h>
#include "pop_geom.h" /* pop_rnd_t — сид кладки комнаты */
/* ---- Атласы фона (прямая адресация id) ------------------------------ *
* env[id>>POP_ENV_SHIFT] idx id&POP_ENV_MASK — chtab_6 environment;
* wall — chtab_7; fore — передний слой; pot — chtab_1 (зелья, меч).
* Раскладка и число страниц — из pop_bg_atlas.h (генерит упаковщик).
* Тайлсетов ДВА (подземелье/дворец), но раскладка id у них общая:
* подменяются только файлы и 32 записи палитры (см. pop_bg_set_tileset). */
#include "pop_bg_atlas.h"
extern atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at;
/* Загруженный тайлсет: 0 подземелье, 1 дворец. Ставит pop_bg_load. По нему
* расходится с десяток веток отрисовки (seg008) и модификатор пустой клетки
* от упавшей плиты (seg007:0EB8). */
extern uint8_t pop_palace;
/* ---- Текущая отрисованная комната ----------------------------------- *
* fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО
* соседа (кромка); below — ряд 0 комнаты снизу (11 байт, см. pop_bg.h);
* above/abovemod — ряд 2 комнаты сверху (полоса кладки у потолка). */
extern const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
extern const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
extern uint8_t pop_t_room; /* номер комнаты — зерно раскладки стен */
extern pop_rnd_t pop_t_seed; /* сид кладки (seg009 PRNG) */
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра выше границы не
* рисуются. Нужен полосе кладки у потолка (ряд −1): высокие куски не должны
* лезть в верхний борт. 0 — без клипа. */
extern int pop_t_clip_top;
/* ОКНО FORE-СЛОЯ (экранные координаты, полуинтервал). Пока включено,
* blit_b рисует только ту часть куска, что попадает в окно.
*
* Зачем: fore-слой существует РОВНО для того, чтобы вернуть куски тайлов
* поверх нарисованного персонажа — за пределами его прямоугольника в
* видеопамяти и так лежит правильный фон. А рисовал он тайлы ЦЕЛИКОМ:
* стена (код 20) — блок 64×63 = 4032 пикселя, и таких кусков за проход до
* девяти. Замер в MAME 2026-07-29 (комната 3, Kid + страж): проход поверх
* Kid — 402 816 тактов, поверх стража — 431 964, вместе 78 % всей работы
* кадра при бюджете 430 000. С окном большинство кусков отсекается целиком,
* остальные режутся до пересечения. */
extern int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1;
/* Та же рамка в ЛОГИЧЕСКИХ координатах (без переворота). Всё, что решает
* «задевает ли ТАЙЛ окно», обязано сравнивать по ней: позиция тайла в
* комнате от переворота не зависит. Экранная пара — только для клипа
* блита, который режет уже перевёрнутый прямоугольник. */
extern int pop_t_fclip_ly0, pop_t_fclip_ly1;
extern uint8_t pop_t_fclip_on;
/* Поставить то же окно под ТОЧЕЧНУЮ ПЕРЕРИСОВКУ / ЗАПЕЧКУ. Прямоугольник —
* тот, который восстанавливаем (heal или бар-в-чёрный), в ЛОГИЧЕСКИХ
* экранных координатах (с POP_YOFF, но до переворота); экранную пару
* функция считает сама.
*
* Зачем: точечная перерисовка зовёт draw_tile, а тот честно рисует ВЕСЬ тайл.
* У полосы кладки у потолка это особенно дорого: куски ряда 0 высотой 63 px
* рисуются целиком, хотя восстановить надо девять верхних строк (замер
* 2026-08-17: блит в pop_ceil_bake_empty стоит 21 447 против 14 384 в
* pop_floor_bake — ровно разница в высоте). Окно и отсеивает куски, вовсе не
* попавшие в прямоугольник (предфильтр pop_blit_b — ДО atlas_image и
* gfx_w0_map), и режет остальные по нему.
*
* ВАЖНО: пока окно стоит, pop_blit_b НЕ ставит пометку «фон трогали» (признак
* fore-прохода) — вызывающий обязан пометить прямоугольник САМ (pop_cd_touch
* или pop_bar_black, который метит сам). */
void pop_t_win_set(int x, int ytop, int w, int h);
void pop_t_win_clear(void);
/* ---- Таблица тайлов (piece, seg008.c:27) ---------------------------- */
typedef struct {
uint8_t base_id, floor_left; int8_t base_y;
uint8_t right_id, floor_right; int8_t right_y;
uint8_t stripe_id, topright_id, bottom_id, fore_id, fore_x; int8_t fore_y;
} piece;
extern const piece pop_tile_table[31];
/* Таблицы, нужные ОБЕИМ половинам слоя фона (односторонние живут по месту). */
extern const uint8_t POP_COL_XH[10]; /* x тайла = COL_XH[col]*8 */
/* Деление координаты на ширину тайла таблицей (оригинал: tile_div_tbl /
* tile_mod_tbl, seg006:702). Индекс — координата 0..255 как есть, смещение
* -58 уже внутри: POP_TILE_DIV[x] = floor((x-58)/14), POP_TILE_MOD[x] —
* остаток. Резидент: нужны и карте (банк 3), и слою фона (банк 2). */
extern const int8_t POP_TILE_DIV[256];
extern const uint8_t POP_TILE_MOD[256];
extern const uint8_t POP_WALL_FRAM_BOTTOM[4]; /* нижняя грань стены по bg */
extern const uint8_t POP_WALL_FRAM_MAIN[4]; /* основная грань стены */
extern const uint8_t POP_SPIKES_FRAM_LEFT[10]; /* кадр пик в своей ячейке */
extern const uint8_t POP_LOOSE_FRAM_LEFT[12]; /* верх дрожащей плиты */
extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей плиты */
extern const uint8_t POP_CHOMP_FRAM_FOR[5]; /* чомпер: перед (pop_fore!) */
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Фон обязан быть
* состоянием ПОКОЯ; анимация каждый кадр кладётся поверх (банк SPRITE). */
extern uint8_t pop_t_bake_rest;
/* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */
uint8_t pop_loose_frame(uint8_t modif);
/* Номер позы чомпера по его модификатору (seg008 chomper_fram1): 0..4 —
* индекс во ВСЕ три набора кусков (низ/верх/перед) и в кровь (+114/+119). */
uint8_t pop_chomp_pose(uint8_t modif);
/* ---- Блит куска атласа ---------------------------------------------- *
* НИЗ спрайта на ybottom (room-space, POP_YOFF добавляется внутри), левый
* край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в
* W0). Режется окном fore-клипа и pop_t_clip_top. */
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom);
void pop_env_b(uint8_t id, int x, int yb);
void pop_wall_b(uint8_t id, int x, int yb);
void pop_fore_b(uint8_t id, int x, int yb);
void pop_pot_b(uint8_t id, int x, int yb);
/* heal в room-space (со сдвигом POP_YOFF) — все heal слоя фона/loose. */
void pop_heal_off(int x, int y, int w, int h);
/* Чёрная плита по ЭКРАННЫМ координатам, с учётом переворота (POP_FLIP_TOP).
* Пишет банком 0x50 — и в видео-ОЗУ, и в ОЗУ-копию, иначе heal вернёт
* стёртое. ВСЕ стирания слоя фона обязаны идти через неё, а не через голый
* bar: иначе при перевороте плита ложится в неотражённое место (так
* ломались решётка и плита-потолок). */
void pop_bar_black(int x, int top, int w, int h);
/* ---- Чтение тайла отрисованной комнаты ------------------------------ */
uint8_t pop_tile_code(int row, int col);
uint8_t pop_tile_mod(int row, int col);
/* Тип «как он РИСУЕТСЯ» — подстановка нажатой кнопки (get_tile_to_draw,
* seg008:240): opener → floor, closer → stuck при таймере связи > 1. */
uint8_t pop_tile_code_drawn(int row, int col);
/* seg008.c:1255: сосед — стена, если его тип == 20. 0 SWS / 1 SWW / 2 WWS
* / 3 WWW. На кромке col0 сосед по умолчанию = стена (как render_room). */
uint8_t pop_wall_modifier(int row, int col);
/* Склянка зелья (draw_tile_fore, chtab_1): нужна и полной отрисовке тайла, и
* fore-проходу поверх персонажа. */
void pop_potion_flask(int row, int col, uint8_t modif);
#endif