Композит падающего куска: циан в бюджете (632 000 -> 273 078)

Три части падающей плиты (env 70/74/72) складываются в ОДИН getimage-блоб
при загрузке тайлсета, и кусок рисуется одним блитом вместо трёх.  Блоб
лежит в обычной памяти (W2), поэтому вызов идёт без atlas_image и без
gfx_w0_map/unmap — ещё ~1 350 такта.  Прозрачность соблюдена: части
ПЕРЕКРЫВАЮТСЯ (74 и 70 обе от mob_x), поэтому композит собирается
попиксельно с пропуском 0xFF, то есть точно как три прозрачных блита.

Мотив: у блита ~8 800 такта постоянных накладных против ~5 000 на пиксели.
Шесть кусков в воздухе = 18 вызовов = 258 708 такта, больше половины
цианового блока.  Резервный путь на три блита оставлен (mob_spr_ok).

  циан пик   435 180 -> 273 078   (цель 400 000 — ВЫПОЛНЕНА)
  работа     1 037 250 -> 931 782
  зелёная      558 498 -> 557 706 (не затронута, ею занимаемся дальше)

Заодно НАЙДЕН И ПОФИКШЕН БАГ КОРИДОРА heal.  Снятые из каталогов реальные
габариты частей оказались другими, чем в комментарии, И РАЗНЫМИ у тайлсетов:

    подземелье  70 = 32x16   74 = 26x15   72 = 26x16
    дворец      70 = 32x13   74 = 25x15   72 = 31x13

То есть во ДВОРЦЕ (уровни 4-6, 10, 11, 13, 14) кусок достаёт до mob_x+62, а
коридор heal был mob_x-4 .. mob_x+59 — правые три пикселя не стирались
никогда.  Четыре пикселя слева при этом чистились впустую: левее mob_x
кусок не рисует ничего.  Коридор стал mob_x .. mob_x+63, площадь та же.
Высоту (24 строки при следе 19) НЕ сужаем: 24 — это след подземелья плюс
две строки поля с каждой стороны, «сужение по палаццовому следу» сломало бы
подземелье.

Ещё три правки того же захода:

- pop_blit_b: аргументы в file-scope.  Третий и дальше SDCC передаёт стеком,
  и каждое чтение шло через `-N(ix)` — 76 обращений.  Стало 11.
- pop_loose_mob_tick: пометки всех кусков одним пакетом (было по 4 502 такта
  на кусок).  mobTk 176 772 -> 168 600.
- pop_floor_bake: окно клипа проверено ВТОРОЙ раз (после того как
  blit_b_clip подешевел) и снова хуже — 182 124 -> 188 460.  Причина
  записана в коде: у этого тайла клипа нет, блиты идут быстрым путём, а окно
  их уводит в клипованный и при этом не отсеивает ни одного куска и не
  режет ни одной строки.  Не пробовать в третий раз.

Новый лист pop_mem_b (резидент): блит блоба из обычной памяти с теми же
клипом полосы у потолка и окном перерисовки, что у pop_blit_b.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 14:01:48 +03:00
parent a3c473d910
commit b2da0b85b5
3 changed files with 236 additions and 70 deletions
+69 -31
View File
@@ -476,51 +476,61 @@ static void blit_b_oversize(const uint8_t *img, int x, int ybottom)
if (!pop_t_fclip_on) pop_cd_touch(x, top, (int)w, (int)h);
}
/* Рабочие переменные pop_blit_b — file-scope, не локали и не стековые
* аргументы (см. пролог функции). Реентерабельности нет и не требуется:
* pop_blit_b себя не зовёт, а blit_b_clip держит СВОЙ набор (bc_*). */
static atlas_t *pb_a;
static uint8_t pb_idx, pb_w, pb_h;
static int pb_x, pb_yb, pb_top;
static const uint8_t *pb_img;
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{
const uint8_t *img;
uint8_t w, h; /* БАЙТЫ, а не uint16_t — см. ниже */
int top;
if (idx >= a->count)
/* Аргументы сразу в file-scope: третий и дальше SDCC передаёт СТЕКОМ, и
* дальше каждое их чтение — `-N(ix)` (замер 2026-08-17: 76 таких
* обращений в теле, ~3 500 такта из 14 500 цены блита). То же лечение,
* что у draw_tile и blit_b_clip. */
pb_a = a; pb_idx = idx; pb_x = x; pb_yb = ybottom;
if (pb_idx >= pb_a->count)
return;
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
/* Грубый отсев ДО маппинга страницы: реальные pb_w/pb_h лежат в заголовке
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
* atlas_image, ни за map/unmap. */
if (pop_t_fclip_on &&
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1))
(pb_x >= pop_t_fclip_x1 || pb_x + 64 <= pop_t_fclip_x0 ||
pb_yb + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
pb_yb + POP_YOFF - 64 >= pop_t_fclip_ly1))
return;
pop_dbg_b1(); /* ВРЕМЕННО */
img = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page);
pb_img = (const uint8_t *)atlas_image(pb_a, pb_idx);
gfx_w0_map(pb_a->page);
pop_dbg_b2(); /* ВРЕМЕННО */
/* Габарит читаем БАЙТАМИ. С `uint16_t w = img[0] | (img[1] << 8)` SDCC
/* Габарит читаем БАЙТАМИ. С `uint16_t pb_w = pb_img[0] | (pb_img[1] << 8)` SDCC
* разворачивал каждую такую строку в два десятка IX-относительных
* пересылок: 16-битные w/h не влезали в регистры, и функция целиком
* пересылок: 16-битные pb_w/pb_h не влезали в регистры, и функция целиком
* уезжала в стековый кадр (пролог `ld iy,#-14 / add iy,sp / ld sp,iy`,
* дальше всё через -N(ix) по 19 тактов). Замер 2026-08-13: до вызова
* ядра блита уходило ~6 700 тактов при том, что работы там — прочитать
* четыре байта заголовка и сравнить границы.
* Байтовый габарит корректен по построению: обе ветки ниже И ТАК требуют
* w < 256 && h < 256 (иначе ядро не примет), а кадры наших атласов не
* pb_w < 256 && pb_h < 256 (иначе ядро не примет), а кадры наших атласов не
* крупнее 32x63. Формат .atl допускает больше — такой кадр уходит на
* общий путь выше. */
if (img[1] | img[3]) { /* кадр больше 255 — редкий путь */
blit_b_oversize(img, x, ybottom);
if (pb_img[1] | pb_img[3]) { /* кадр больше 255 — редкий путь */
blit_b_oversize(pb_img, pb_x, pb_yb);
gfx_w0_unmap();
return;
}
w = img[0];
h = img[2];
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
pb_w = pb_img[0];
pb_h = pb_img[2];
pb_top = pb_yb - (int)pb_h + 1 + POP_YOFF; /* +YOFF: центрирование */
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в
* экранных координатах результата. */
if (pop_upside) top = FLIP_TOP(top, h);
if (w && h) {
if (pop_upside) pb_top = FLIP_TOP(pb_top, pb_h);
if (pb_w && pb_h) {
if (!pop_t_fclip_on && !pop_t_clip_top) {
/* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне
* fore-прохода. Линейное ядро без клипа — ~2.9× быстрее общего
@@ -529,26 +539,26 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
/* В перевёрнутом виде быстрый путь годится, только если спрайт
* целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */
if (pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) {
blit_b_clip(img, x, top, w, h);
if (pop_upside && (pb_top < POP_YOFF || pb_top + (int)pb_h > POP_CLIP_BOTTOM)) {
blit_b_clip(pb_img, pb_x, pb_top, pb_w, pb_h);
gfx_w0_unmap();
return;
}
/* w<256 && h<256 больше не проверяем — гарантировано типом. */
if (x >= 0 && top >= 0 &&
x + (int)w <= 320 && top + (int)h <= 256) {
/* pb_w<256 && pb_h<256 больше не проверяем — гарантировано типом. */
if (pb_x >= 0 && pb_top >= 0 &&
pb_x + (int)pb_w <= 320 && pb_top + (int)pb_h <= 256) {
pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
else gfx_blit_noclip(x, top, img);
if (pop_upside) gfx_blit_noclip_vflip(pb_x, pb_top, pb_img);
else gfx_blit_noclip(pb_x, pb_top, pb_img);
} else {
if (pop_upside) gfx_blit_part_vflip(x, top, img, 0, 0, (int)w, (int)h);
else gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
if (pop_upside) gfx_blit_part_vflip(pb_x, pb_top, pb_img, 0, 0, (int)pb_w, (int)pb_h);
else gfx_blit_part(pb_x, pb_top, pb_img, 0, 0, (int)pb_w, (int)pb_h);
}
pop_dbg_b3(); /* ВРЕМЕННО */
pop_cd_touch(x, top, (int)w, (int)h);
pop_cd_touch(pb_x, pb_top, (int)pb_w, (int)pb_h);
pop_dbg_b4(); /* ВРЕМЕННО */
} else {
blit_b_clip(img, x, top, w, h);
blit_b_clip(pb_img, pb_x, pb_top, pb_w, pb_h);
pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */
}
}
@@ -556,6 +566,34 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
pop_dbg_b5(); /* ВРЕМЕННО */
}
/* Блит getimage-блоба из ОБЫЧНОЙ памяти (W2), а не из EMM-страницы атласа:
* те же клип полосы у потолка и окно перерисовки, что у pop_blit_b, но без
* atlas_image и без gfx_w0_map/unmap (это ещё ~1 350 такта). Заведено под
* композитный спрайт падающего куска (pop_room.c: три части сложены в один
* блоб, один вызов блита вместо трёх). */
void pop_mem_b(const uint8_t *img, int x, int ybottom)
{
uint8_t w = img[0], h = img[2];
int top;
if (pop_t_fclip_on &&
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1))
return;
if (!w || !h) return;
top = ybottom - (int)h + 1 + POP_YOFF;
if (pop_upside) top = FLIP_TOP(top, h);
if (!pop_t_fclip_on && !pop_t_clip_top &&
!(pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) &&
x >= 0 && top >= 0 && x + (int)w <= 320 && top + (int)h <= 256) {
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
else gfx_blit_noclip(x, top, img);
pop_cd_touch(x, top, (int)w, (int)h);
} else {
blit_b_clip(img, x, top, w, h);
}
}
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> POP_ENV_SHIFT], (uint8_t)(id & POP_ENV_MASK), x, yb); }
void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); }
void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }