Композит падающего куска: циан в бюджете (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:
@@ -84,6 +84,100 @@ static int bg_load_tile_pal(const char *name)
|
||||
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 дворец). Повторный вызов с тем же
|
||||
* набором ничего не делает; со сменой — сначала выгружает старый, иначе
|
||||
* 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;
|
||||
bg_set = set;
|
||||
pop_palace = (uint8_t)(set == 1);
|
||||
mob_spr_build(); /* композит падающего куска — свой на набор */
|
||||
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
|
||||
* прежнего набора и картинка хотя бы не разъезжается. */
|
||||
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);
|
||||
pop_bar_black(x, yb + 26, 60, 39);
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
/* ОКНА КЛИПА ЗДЕСЬ НЕТ ОСОЗНАННО (замер 2026-08-17). Оно попробовано и
|
||||
* откачено: у этого тайла клипа нет вовсе, то есть блиты идут БЫСТРЫМ
|
||||
* путём (gfx_blit_noclip, 10 422), а окно уводит их в blit_b_clip
|
||||
* (14 088) — и не отсеивает ни одного куска (7,6 блита до и после).
|
||||
* Итог был 187 266 -> 198 279, то есть хуже. Вернуться к этой идее
|
||||
* после того, как blit_b_clip перестанет платить за 22-байтовый
|
||||
* стековый кадр (позиция G3 в docs/perf_green_phase.md). */
|
||||
/* ОКНА КЛИПА ЗДЕСЬ НЕТ — проверено ДВАЖДЫ, оба раза хуже (2026-08-17):
|
||||
* до G3 187 266 -> 198 279
|
||||
* после G3 182 124 -> 188 460 (блит 14 530 -> 15 593)
|
||||
* Причина: у этого тайла клипа нет, блиты идут БЫСТРЫМ путём
|
||||
* (gfx_blit_noclip), окно уводит их в blit_b_clip и при этом НЕ отсеивает
|
||||
* ни одного куска и НЕ режет ни одной строки — все куски щебня и так
|
||||
* лежат внутри бара yb+26..yb+64. Не пробовать в третий раз. */
|
||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||
draw_tile(row, col);
|
||||
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};
|
||||
#define MOB_Y_NONE 0x7FFF
|
||||
|
||||
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
|
||||
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
|
||||
* ширина 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).
|
||||
/* Габарит падающего куска. Части и их места (порт draw_mob, seg007:13E5;
|
||||
* индексы loose_fram_*[10], seg008:518/596/608):
|
||||
*
|
||||
* Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля.
|
||||
* А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой
|
||||
* операции кадра, помноженной на число падающих плит; сужена до 24.
|
||||
* env 70 левая часть при mob_y-3
|
||||
* env 74 низ при mob_y
|
||||
* env 72 правая часть при mob_x+32, mob_y-1
|
||||
*
|
||||
* ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не
|
||||
* подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в
|
||||
* memory про акселератор таких требований нет. Единственный источник —
|
||||
* наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить
|
||||
* ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */
|
||||
#define MOB_W 64 /* mob_x-4 .. mob_x+60 */
|
||||
* РАЗМЕРЫ У ТАЙЛСЕТОВ РАЗНЫЕ (снято из каталогов атласов 2026-08-17; прежний
|
||||
* комментарий «41=32x13, 42=26x15, 43=32x3» был неверен вдвойне — и id, и
|
||||
* размеры):
|
||||
*
|
||||
* подземелье 70 = 32x16 74 = 26x15 72 = 26x16
|
||||
* дворец 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_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 оставлен: функция
|
||||
* зовётся и из других мест. */
|
||||
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++) {
|
||||
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
|
||||
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
|
||||
@@ -1323,6 +1436,7 @@ void pop_loose_mob_tick(void) __banked
|
||||
mob_tick_one(m, pg);
|
||||
if (m->active) live++;
|
||||
}
|
||||
pop_cd_batch_end();
|
||||
mobs_live = live;
|
||||
}
|
||||
|
||||
@@ -1346,19 +1460,24 @@ static void mob_render(mob_t *m, uint8_t pg)
|
||||
/* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable
|
||||
* obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из
|
||||
* таблиц по этому индексу (seg008:518/596/608):
|
||||
* loose_fram_left[10] = 70 (32x13, y-3)
|
||||
* loose_fram_bottom[10] = 74 (32x3, y)
|
||||
* loose_fram_right[10] = 72 (26x16, x+32, y-1)
|
||||
* loose_fram_left[10] = 70 при (x, y-3)
|
||||
* loose_fram_bottom[10] = 74 при (x, y)
|
||||
* loose_fram_right[10] = 72 при (x+32, y-1)
|
||||
* Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и
|
||||
* «цельная ровная плита» вместо двух частей со сдвигом правой половины
|
||||
* на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). */
|
||||
/* Пометки «фон трогали» на время трёх блитов ПОДАВЛЕНЫ: коридор куска
|
||||
* на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом).
|
||||
* Габариты частей — в блоке MOB_X0 выше (у тайлсетов они РАЗНЫЕ).
|
||||
*
|
||||
* Рисуем ОДНИМ блитом по композиту (mob_spr_build): три вызова блита
|
||||
* стоили 258 708 такта в кадре при шести кусках — больше половины
|
||||
* цианового блока. Три отдельных блита остались резервным путём на
|
||||
* случай, если композит не собрался. */
|
||||
/* Пометки «фон трогали» на время блитов ПОДАВЛЕНЫ: коридор куска
|
||||
* (MOB_X0 .. +MOB_W, y−27 .. +37) уже помечен ОДНИМ вызовом — из
|
||||
* pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска,
|
||||
* рождённого внутри тика. Спрайт целиком внутри коридора: он занимает
|
||||
* x m->x .. m->x+57 (правая часть 72 идёт с x+32, ширина 26) при
|
||||
* коридоре m->x−4 .. m->x+59, и y m->y−15 .. m->y при коридоре
|
||||
* m->y−27 .. m->y+37 с запасом на ускорение (speed ≤ 31 за кадр).
|
||||
* рождённого внутри тика. Спрайт целиком внутри коридора: по x он
|
||||
* занимает m->x .. m->x+62, по y m->y−18 .. m->y, коридор — m->x ..
|
||||
* m->x+63 и m->y−27 .. m->y+37 (запас на ускорение, speed ≤ 31 за кадр).
|
||||
* Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на
|
||||
* кусок, то есть 81 000 в кадре при шести кусках. */
|
||||
pop_cd_mute();
|
||||
@@ -1373,12 +1492,16 @@ static void mob_render(mob_t *m, uint8_t pg)
|
||||
* Окно ставим только когда кусок реально задевает борт: внутри поля блиты
|
||||
* идут быстрым путём (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);
|
||||
if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H);
|
||||
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 (mob_spr_ok) {
|
||||
pop_mem_b(mob_spr, m->x, m->y);
|
||||
} 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();
|
||||
}
|
||||
pop_cd_unmute();
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -140,6 +140,11 @@ uint8_t pop_chomp_pose(uint8_t modif);
|
||||
* край на 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);
|
||||
/* Блит 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_wall_b(uint8_t id, int x, int yb);
|
||||
void pop_fore_b(uint8_t id, int x, int yb);
|
||||
|
||||
Reference in New Issue
Block a user