diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index dbd0afb..cbb437e 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -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(); diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index 16d4b7c..a643753 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -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); } diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index de6dd71..aede14a 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -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);