Композит падающего куска: циан в бюджете (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
+162 -39
View File
@@ -84,6 +84,100 @@ static int bg_load_tile_pal(const char *name)
return 0; return 0;
} }
/* ---- Композитный спрайт ПАДАЮЩЕГО КУСКА ------------------------------ *
* Три части (env 70 / 74 / 72, см. блок MOB_X0 ниже) складываются в ОДИН
* getimage-блоб в обычной памяти, и кусок рисуется одним блитом вместо трёх.
*
* Зачем: у блита ~8 800 такта постоянных накладных против ~5 000 на пиксели
* (memory blit_cost_model). Шесть кусков в воздухе давали 18 вызовов и
* 258 708 такта — БОЛЬШЕ ПОЛОВИНЫ цианового блока (замер 2026-08-17,
* docs/perf_cyan_phase.md). Плюс блоб в обычной памяти не требует
* atlas_image и gfx_w0_map/unmap — ещё ~1 350 такта на вызов.
*
* Прозрачность соблюдена: части ПЕРЕКРЫВАЮТСЯ (74 и 70 обе идут от mob_x),
* поэтому копируются попиксельно с пропуском 0xFF — ровно как три блита с
* прозрачностью друг поверх друга.
*
* Размер буфера — по худшему из тайлсетов (дворец даёт 63 в ширину,
* подземелье 19 в высоту), 1 201 Б статики W2. Куча roomtest не
* используется (malloc'ов в приложении нет), так что это её и не задевает.
*
* Собирается на КАЖДУЮ смену тайлсета: размеры частей у наборов разные. */
#define MOBS_MAXW 63
#define MOBS_MAXH 19
static uint8_t mob_spr[4 + MOBS_MAXW * MOBS_MAXH];
static uint8_t mob_spr_ok; /* 0 — не собрался, рисуем тремя блитами */
/* Одна часть в композит. xo — смещение по x, ybo — смещение НИЗА части
* относительно низа композита (0 = вплотную к низу, отрицательное = выше).
* top_off — смещение верхней строки композита относительно mob_y. */
static uint8_t mob_spr_put(uint8_t id, uint8_t xo, int8_t ybo, int8_t top_off)
{
atlas_t *a = &pop_env[id >> POP_ENV_SHIFT];
uint8_t idx = (uint8_t)(id & POP_ENV_MASK);
const uint8_t *src;
uint8_t w, h, r, c, v;
int row0;
if (idx >= a->count) return 0;
src = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page);
w = src[0]; h = src[2];
if (src[1] || src[3] || !w || !h ||
(uint16_t)xo + w > MOBS_MAXW) { gfx_w0_unmap(); return 0; }
row0 = (int)ybo - (int)h + 1 - (int)top_off; /* верхняя строка части */
if (row0 < 0 || row0 + h > MOBS_MAXH) { gfx_w0_unmap(); return 0; }
src += 4;
for (r = 0; r < h; r++) {
const uint8_t *sp = src + (uint16_t)r * w;
uint8_t *dp = mob_spr + 4 + (uint16_t)(row0 + r) * MOBS_MAXW + xo;
for (c = 0; c < w; c++) { v = sp[c]; if (v != 0xFF) dp[c] = v; }
}
gfx_w0_unmap();
return 1;
}
/* Высота композита: самый верхний пиксель даёт часть 70 (ybo −3), у неё же
* самая большая высота. Считаем по фактическим габаритам, а не по константам
* — они у тайлсетов разные. */
static int8_t mob_spr_top_off(void)
{
static const uint8_t ids[3] = {74, 70, 72};
static const int8_t ybo[3] = {0, -3, -1};
int8_t best = 0;
uint8_t k;
for (k = 0; k < 3; k++) {
atlas_t *a = &pop_env[ids[k] >> POP_ENV_SHIFT];
uint8_t idx = (uint8_t)(ids[k] & POP_ENV_MASK);
const uint8_t *src;
int t;
if (idx >= a->count) continue;
src = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page);
t = (int)ybo[k] - (int)src[2] + 1;
gfx_w0_unmap();
if (t < best) best = (int8_t)t;
}
return best;
}
static void mob_spr_build(void)
{
uint16_t i;
int8_t top_off;
mob_spr_ok = 0;
top_off = mob_spr_top_off();
if (top_off <= -MOBS_MAXH) return; /* не влезло — общий путь */
mob_spr[0] = MOBS_MAXW; mob_spr[1] = 0;
mob_spr[2] = (uint8_t)(1 - top_off); /* низ композита = mob_y */
mob_spr[3] = 0;
for (i = 0; i < (uint16_t)MOBS_MAXW * MOBS_MAXH; i++) mob_spr[4 + i] = 0xFF;
/* Порядок как в mob_render: низ, левая часть, правая часть. */
if (!mob_spr_put(74, 0, 0, top_off)) return;
if (!mob_spr_put(70, 0, -3, top_off)) return;
if (!mob_spr_put(72, 32, -1, top_off)) return;
mob_spr_ok = 1;
}
/* Загрузить набор set (0 подземелье, 1 дворец). Повторный вызов с тем же /* Загрузить набор set (0 подземелье, 1 дворец). Повторный вызов с тем же
* набором ничего не делает; со сменой — сначала выгружает старый, иначе * набором ничего не делает; со сменой — сначала выгружает старый, иначе
* EMM-страницы утекут (их по 12 на набор). */ * EMM-страницы утекут (их по 12 на набор). */
@@ -105,6 +199,7 @@ int pop_bg_load(uint8_t set) __banked
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1; atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
bg_set = set; bg_set = set;
pop_palace = (uint8_t)(set == 1); pop_palace = (uint8_t)(set == 1);
mob_spr_build(); /* композит падающего куска — свой на набор */
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от /* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
* прежнего набора и картинка хотя бы не разъезжается. */ * прежнего набора и картинка хотя бы не разъезжается. */
return bg_load_tile_pal(pop_tile_pal[set]); return bg_load_tile_pal(pop_tile_pal[set]);
@@ -773,13 +868,13 @@ void pop_floor_bake(int row, int col) __banked
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
pop_bar_black(x, yb + 26, 60, 39); pop_bar_black(x, yb + 26, 60, 39);
gfx_set_bank(GFX_BANK_TRANSPARENT); gfx_set_bank(GFX_BANK_TRANSPARENT);
/* ОКНА КЛИПА ЗДЕСЬ НЕТ ОСОЗНАННО (замер 2026-08-17). Оно попробовано и /* ОКНА КЛИПА ЗДЕСЬ НЕТ — проверено ДВАЖДЫ, оба раза хуже (2026-08-17):
* откачено: у этого тайла клипа нет вовсе, то есть блиты идут БЫСТРЫМ * до G3 187 266 -> 198 279
* путём (gfx_blit_noclip, 10 422), а окно уводит их в blit_b_clip * после G3 182 124 -> 188 460 (блит 14 530 -> 15 593)
* (14 088) — и не отсеивает ни одного куска (7,6 блита до и после). * Причина: у этого тайла клипа нет, блиты идут БЫСТРЫМ путём
* Итог был 187 266 -> 198 279, то есть хуже. Вернуться к этой идее * (gfx_blit_noclip), окно уводит их в blit_b_clip и при этом НЕ отсеивает
* после того, как blit_b_clip перестанет платить за 22-байтовый * ни одного куска и НЕ режет ни одной строки — все куски щебня и так
* стековый кадр (позиция G3 в docs/perf_green_phase.md). */ * лежат внутри бара yb+26..yb+64. Не пробовать в третий раз. */
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */ pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
draw_tile(row, col); draw_tile(row, col);
if (col + 1 < 10) draw_tile(row, col + 1); if (col + 1 < 10) draw_tile(row, col + 1);
@@ -924,28 +1019,36 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25}; static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
#define MOB_Y_NONE 0x7FFF #define MOB_Y_NONE 0x7FFF
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26) /* Габарит падающего куска. Части и их места (порт draw_mob, seg007:13E5;
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46: * индексы loose_fram_*[10], seg008:518/596/608):
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
#define MOB_X0(mx) ((mx) - 4)
/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом».
* След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение
* трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y).
* *
* Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля. * env 70 левая часть при mob_y-3
* А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой * env 74 низ при mob_y
* операции кадра, помноженной на число падающих плит; сужена до 24. * env 72 правая часть при mob_x+32, mob_y-1
* *
* ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не * РАЗМЕРЫ У ТАЙЛСЕТОВ РАЗНЫЕ (снято из каталогов атласов 2026-08-17; прежний
* подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в * комментарий «41=32x13, 42=26x15, 43=32x3» был неверен вдвойне — и id, и
* memory про акселератор таких требований нет. Единственный источник — * размеры):
* наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить *
* ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */ * подземелье 70 = 32x16 74 = 26x15 72 = 26x16
#define MOB_W 64 /* mob_x-4 .. mob_x+60 */ * дворец 70 = 32x13 74 = 25x15 72 = 31x13
*
* Отсюда фактический след: по x mob_x .. mob_x+62 (дворец: 32+31),
* по y mob_y-18 .. mob_y (подземелье: 3+16-1).
*
* КОРИДОР ИСПРАВЛЕН 2026-08-17: было MOB_X0 = mob_x-4 при MOB_W = 64, то есть
* mob_x-4 .. mob_x+59 — на ДВОРЦОВЫХ уровнях (10, 11, 13, 14) правые три
* пикселя куска 72 не попадали в heal и оставались висеть на странице.
* Четыре пикселя слева при этом чистились впустую: левее mob_x кусок не
* рисует ничего. */
#define MOB_X0(mx) (mx)
#define MOB_W 64 /* mob_x .. mob_x+63 */
/* Коридор heal по высоте: mob_y-20 .. mob_y+3 при следе mob_y-18 .. mob_y.
* Уже НЕ сужать: 24 строки — это след подземелья (19) плюс две строки поля с
* каждой стороны. Сужение до 18 «по палаццовому следу» сломало бы
* подземелье. */
#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */ #define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */
#define MOB_HEAL_H 24 /* .. mob_y + 4 */ #define MOB_HEAL_H 24 /* .. mob_y + 3 */
/* Свободный слот = не летит И дочистка завершена (иначе затрём хвост, /* Свободный слот = не летит И дочистка завершена (иначе затрём хвост,
* который ещё не стёрт со второй страницы). Общий для обычного отрыва и * который ещё не стёрт со второй страницы). Общий для обычного отрыва и
@@ -1304,6 +1407,16 @@ void pop_loose_mob_tick(void) __banked
* (замер 2026-08-13). Гард в самом mob_tick_one оставлен: функция * (замер 2026-08-13). Гард в самом mob_tick_one оставлен: функция
* зовётся и из других мест. */ * зовётся и из других мест. */
mob_t *m = mobs; mob_t *m = mobs;
/* Пометки всех кусков — ОДНИМ пакетом: настоящий pop_cd_touch стоит 4 502
* такта (замер 2026-08-17), а кусков в кадре каскада шесть. В пакете они
* копят общий прямоугольник четырьмя сравнениями, и настоящая пометка одна.
*
* Загрубление осознанное: объединение шести коридоров накрывает и ряды
* между ними. Безопасно в одну сторону — лишняя пометка стоит лишней
* перерисовки персонажа, пропущенная стоит ИСЧЕЗНУВШЕГО персонажа.
* Практически гряда идёт по колонкам 2..7, а Кид от неё убегает — на
* замерах разницы в числе перерисовок нет. */
pop_cd_batch_begin();
for (i = 0; i < MOB_MAX; i++, m++) { for (i = 0; i < MOB_MAX; i++, m++) {
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом, /* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя * мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
@@ -1323,6 +1436,7 @@ void pop_loose_mob_tick(void) __banked
mob_tick_one(m, pg); mob_tick_one(m, pg);
if (m->active) live++; if (m->active) live++;
} }
pop_cd_batch_end();
mobs_live = live; mobs_live = live;
} }
@@ -1346,19 +1460,24 @@ static void mob_render(mob_t *m, uint8_t pg)
/* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable /* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable
* obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из * obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из
* таблиц по этому индексу (seg008:518/596/608): * таблиц по этому индексу (seg008:518/596/608):
* loose_fram_left[10] = 70 (32x13, y-3) * loose_fram_left[10] = 70 при (x, y-3)
* loose_fram_bottom[10] = 74 (32x3, y) * loose_fram_bottom[10] = 74 при (x, y)
* loose_fram_right[10] = 72 (26x16, x+32, y-1) * loose_fram_right[10] = 72 при (x+32, y-1)
* Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и * Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и
* «цельная ровная плита» вместо двух частей со сдвигом правой половины * «цельная ровная плита» вместо двух частей со сдвигом правой половины
* на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). */ * на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом).
/* Пометки «фон трогали» на время трёх блитов ПОДАВЛЕНЫ: коридор куска * Габариты частей — в блоке MOB_X0 выше (у тайлсетов они РАЗНЫЕ).
*
* Рисуем ОДНИМ блитом по композиту (mob_spr_build): три вызова блита
* стоили 258 708 такта в кадре при шести кусках — больше половины
* цианового блока. Три отдельных блита остались резервным путём на
* случай, если композит не собрался. */
/* Пометки «фон трогали» на время блитов ПОДАВЛЕНЫ: коридор куска
* (MOB_X0 .. +MOB_W, y27 .. +37) уже помечен ОДНИМ вызовом — из * (MOB_X0 .. +MOB_W, y27 .. +37) уже помечен ОДНИМ вызовом — из
* pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска, * pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска,
* рождённого внутри тика. Спрайт целиком внутри коридора: он занимает * рождённого внутри тика. Спрайт целиком внутри коридора: по x он
* x m->x .. m->x+57 (правая часть 72 идёт с x+32, ширина 26) при * занимает m->x .. m->x+62, по y m->y18 .. m->y, коридор — m->x ..
* коридоре m->x4 .. m->x+59, и y m->y15 .. m->y при коридоре * m->x+63 и m->y27 .. m->y+37 (запас на ускорение, speed ≤ 31 за кадр).
* m->y27 .. m->y+37 с запасом на ускорение (speed ≤ 31 за кадр).
* Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на * Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на
* кусок, то есть 81 000 в кадре при шести кусках. */ * кусок, то есть 81 000 в кадре при шести кусках. */
pop_cd_mute(); pop_cd_mute();
@@ -1373,12 +1492,16 @@ static void mob_render(mob_t *m, uint8_t pg)
* Окно ставим только когда кусок реально задевает борт: внутри поля блиты * Окно ставим только когда кусок реально задевает борт: внутри поля блиты
* идут быстрым путём (10 422 против 14 088). */ * идут быстрым путём (10 422 против 14 088). */
{ {
int ytop = m->y - 16 + POP_YOFF, ybot = m->y + POP_YOFF; int ytop = m->y - 18 + POP_YOFF, ybot = m->y + POP_YOFF;
uint8_t clipped = (uint8_t)(ytop < POP_YOFF || ybot >= POP_CLIP_BOTTOM); uint8_t clipped = (uint8_t)(ytop < POP_YOFF || ybot >= POP_CLIP_BOTTOM);
if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H); if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H);
pop_env_b(74, m->x, m->y); if (mob_spr_ok) {
pop_env_b(70, m->x, m->y - 3); pop_mem_b(mob_spr, m->x, m->y);
pop_env_b(72, m->x + 32, m->y - 1); } else {
pop_env_b(74, m->x, m->y);
pop_env_b(70, m->x, m->y - 3);
pop_env_b(72, m->x + 32, m->y - 1);
}
if (clipped) pop_t_win_clear(); if (clipped) pop_t_win_clear();
} }
pop_cd_unmute(); pop_cd_unmute();
+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); 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) void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{ {
const uint8_t *img; /* Аргументы сразу в file-scope: третий и дальше SDCC передаёт СТЕКОМ, и
uint8_t w, h; /* БАЙТЫ, а не uint16_t — см. ниже */ * дальше каждое их чтение — `-N(ix)` (замер 2026-08-17: 76 таких
int top; * обращений в теле, ~3 500 такта из 14 500 цены блита). То же лечение,
if (idx >= a->count) * что у draw_tile и blit_b_clip. */
pb_a = a; pb_idx = idx; pb_x = x; pb_yb = ybottom;
if (pb_idx >= pb_a->count)
return; return;
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке /* Грубый отсев ДО маппинга страницы: реальные pb_w/pb_h лежат в заголовке
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать * ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее * gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
* 64×64) и, если он не задевает окно, уходим не заплатив ни за * 64×64) и, если он не задевает окно, уходим не заплатив ни за
* atlas_image, ни за map/unmap. */ * atlas_image, ни за map/unmap. */
if (pop_t_fclip_on && if (pop_t_fclip_on &&
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 || (pb_x >= pop_t_fclip_x1 || pb_x + 64 <= pop_t_fclip_x0 ||
ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 || pb_yb + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1)) pb_yb + POP_YOFF - 64 >= pop_t_fclip_ly1))
return; return;
pop_dbg_b1(); /* ВРЕМЕННО */ pop_dbg_b1(); /* ВРЕМЕННО */
img = (const uint8_t *)atlas_image(a, idx); pb_img = (const uint8_t *)atlas_image(pb_a, pb_idx);
gfx_w0_map(a->page); gfx_w0_map(pb_a->page);
pop_dbg_b2(); /* ВРЕМЕННО */ 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-относительных * разворачивал каждую такую строку в два десятка IX-относительных
* пересылок: 16-битные w/h не влезали в регистры, и функция целиком * пересылок: 16-битные pb_w/pb_h не влезали в регистры, и функция целиком
* уезжала в стековый кадр (пролог `ld iy,#-14 / add iy,sp / ld sp,iy`, * уезжала в стековый кадр (пролог `ld iy,#-14 / add iy,sp / ld sp,iy`,
* дальше всё через -N(ix) по 19 тактов). Замер 2026-08-13: до вызова * дальше всё через -N(ix) по 19 тактов). Замер 2026-08-13: до вызова
* ядра блита уходило ~6 700 тактов при том, что работы там — прочитать * ядра блита уходило ~6 700 тактов при том, что работы там — прочитать
* четыре байта заголовка и сравнить границы. * четыре байта заголовка и сравнить границы.
* Байтовый габарит корректен по построению: обе ветки ниже И ТАК требуют * Байтовый габарит корректен по построению: обе ветки ниже И ТАК требуют
* w < 256 && h < 256 (иначе ядро не примет), а кадры наших атласов не * pb_w < 256 && pb_h < 256 (иначе ядро не примет), а кадры наших атласов не
* крупнее 32x63. Формат .atl допускает больше — такой кадр уходит на * крупнее 32x63. Формат .atl допускает больше — такой кадр уходит на
* общий путь выше. */ * общий путь выше. */
if (img[1] | img[3]) { /* кадр больше 255 — редкий путь */ if (pb_img[1] | pb_img[3]) { /* кадр больше 255 — редкий путь */
blit_b_oversize(img, x, ybottom); blit_b_oversize(pb_img, pb_x, pb_yb);
gfx_w0_unmap(); gfx_w0_unmap();
return; return;
} }
w = img[0]; pb_w = pb_img[0];
h = img[2]; pb_h = pb_img[2];
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */ pb_top = pb_yb - (int)pb_h + 1 + POP_YOFF; /* +YOFF: центрирование */
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно /* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в * fore-слоя, полоса у потолка, отсев по экрану) считается уже в
* экранных координатах результата. */ * экранных координатах результата. */
if (pop_upside) top = FLIP_TOP(top, h); if (pop_upside) pb_top = FLIP_TOP(pb_top, pb_h);
if (w && h) { if (pb_w && pb_h) {
if (!pop_t_fclip_on && !pop_t_clip_top) { if (!pop_t_fclip_on && !pop_t_clip_top) {
/* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне /* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне
* fore-прохода. Линейное ядро без клипа — ~2.9× быстрее общего * fore-прохода. Линейное ядро без клипа — ~2.9× быстрее общего
@@ -529,26 +539,26 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */ * ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
/* В перевёрнутом виде быстрый путь годится, только если спрайт /* В перевёрнутом виде быстрый путь годится, только если спрайт
* целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */ * целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */
if (pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) { if (pop_upside && (pb_top < POP_YOFF || pb_top + (int)pb_h > POP_CLIP_BOTTOM)) {
blit_b_clip(img, x, top, w, h); blit_b_clip(pb_img, pb_x, pb_top, pb_w, pb_h);
gfx_w0_unmap(); gfx_w0_unmap();
return; return;
} }
/* w<256 && h<256 больше не проверяем — гарантировано типом. */ /* pb_w<256 && pb_h<256 больше не проверяем — гарантировано типом. */
if (x >= 0 && top >= 0 && if (pb_x >= 0 && pb_top >= 0 &&
x + (int)w <= 320 && top + (int)h <= 256) { pb_x + (int)pb_w <= 320 && pb_top + (int)pb_h <= 256) {
pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */ pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */
if (pop_upside) gfx_blit_noclip_vflip(x, top, img); if (pop_upside) gfx_blit_noclip_vflip(pb_x, pb_top, pb_img);
else gfx_blit_noclip(x, top, img); else gfx_blit_noclip(pb_x, pb_top, pb_img);
} else { } else {
if (pop_upside) gfx_blit_part_vflip(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(x, top, img, 0, 0, (int)w, (int)h); else gfx_blit_part(pb_x, pb_top, pb_img, 0, 0, (int)pb_w, (int)pb_h);
} }
pop_dbg_b3(); /* ВРЕМЕННО */ 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(); /* ВРЕМЕННО */ pop_dbg_b4(); /* ВРЕМЕННО */
} else { } 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 */ 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(); /* ВРЕМЕННО */ 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_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_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); } void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }
+5
View File
@@ -140,6 +140,11 @@ uint8_t pop_chomp_pose(uint8_t modif);
* край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в * край на x, прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в
* W0). Режется окном fore-клипа и pop_t_clip_top. */ * W0). Режется окном fore-клипа и pop_t_clip_top. */
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom); void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom);
/* Блит getimage-блоба из обычной памяти (W2) — без atlas_image и без
* маппинга страницы; клип полосы у потолка и окно перерисовки те же, что у
* pop_blit_b. Для композитных спрайтов, собранных в рантайме. */
void pop_mem_b(const uint8_t *img, int x, int ybottom);
void pop_env_b(uint8_t id, int x, int yb); void pop_env_b(uint8_t id, int x, int yb);
void pop_wall_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_fore_b(uint8_t id, int x, int yb);