From b27b31318a00e62198aa2a8516362c0d98c3ddb3 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 13 Aug 2026 23:36:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=20draw=5Ft?= =?UTF-8?q?ile:=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D1=86=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B1=D0=BB=D0=B8=D1=82=D0=B0=20+=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=BA=D0=B5=D1=82=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=BE=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BA=D0=B0=20pop=5Fcd=5Ftouch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Замер (MAME, комната 23 ур.13) разложил цену одного gfx_blit_noclip регрессией по 339 блитам, высоты 3..63: такты = 8791 + 198.2 * h + 5.96 * (w * h) Модель ложится на весь диапазон (32x3 -> 9 891, 32x13 -> 13 887, 32x60 -> 32 073), разброс внутри размерной группы — ТРИ такта. Выводы: - 5.96 на байт — предел железа: байт идёт через акселератор дважды (burst src->память акселератора, burst ->экран), по 3 такта на проход при системном клоке 21 МГц. Ускорять передачу нечем. - 198 на строку — цикл _bgi_blit_rows_raw; при w=32 это половина построчной цены. - 8791 на ВЫЗОВ — крупнейшая статья, НЕ объяснена. Проверено, что это не W3-скобка (в обеих половинах по 5 инструкций) и не прерывания (разброс 3 такта). Один тайл = 5-6 спрайтов ~ 107 000 тактов, из них ~50 000 — постоянные накладные вызовов. Это и есть главный резерв. Заодно: pop_cd_touch собирается в ПАКЕТ на тайл (pop_cd_batch_begin/end, скобка в draw_tile) вместо вызова на каждый кусок — раньше 2 866 тактов на спрайт, 15% цены блита. ВНИМАНИЕ: выигрыш замером НЕ подтверждён — в захваченных кадрах скобка не сработала (блиты шли из холодной отрисовки комнаты, мимо draw_tile). Правка безопасна по построению: объединение прямоугольников может пометку только расширить, не сузить. Снятые сегодня неверные утверждения (чтобы не всплыли): - «клипованный блит дороже полного» — артефакт сравнения разных выборок спрайтов; 32x60 с клипом до полосы стоит 7 236, то есть клип работает; - «блиты идут программным циклом со скоростью ldir» — нет, ядро на акселераторе, см. модель выше; - «отложить запекание на кадр» — НЕЛЬЗЯ: запекание пишет ОЗУ-копию фона, из которой heal восстанавливает; отложенное даёт призрак плиты. Все 8 наборов tests-host проходят. Оснастка замера временная. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/pop_cdraw.h | 8 +++++ applications/PoP/roomtest/pop_room.c | 6 ++++ applications/PoP/roomtest/pop_state.c | 16 +++++++++ applications/PoP/roomtest/pop_state.h | 3 ++ applications/PoP/roomtest/pop_tile.c | 47 ++++++++++++++++++++++++++- 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/applications/PoP/roomtest/pop_cdraw.h b/applications/PoP/roomtest/pop_cdraw.h index f48db99..c6ace88 100644 --- a/applications/PoP/roomtest/pop_cdraw.h +++ b/applications/PoP/roomtest/pop_cdraw.h @@ -129,6 +129,14 @@ void pop_cd_clear(uint8_t p); /* снять метку стр void pop_cd_touch(int x, int y, int w, int h); #define POP_CD_TOUCH_ALL() pop_cd_touch(0, 0, 320, 256) +/* Пакетная пометка: между begin и end блиты КОПЯТ общий прямоугольник, и + * настоящая пометка ставится одна (на весь тайл), а не на каждый кусок. + * Скобку ставит draw_tile — выигрыш ~15% цены блита фона. Вкладывать + * скобки нельзя: аккумулятор один. */ +extern uint8_t pop_cd_batch; +void pop_cd_batch_begin(void); +void pop_cd_batch_end(void); + /* Кого в этом кадре можно НЕ перерисовывать: бит 0 — Кид, бит 1 — соперник. * Звать ДВАЖДЫ: перед heal (стирать нечего) и перед отрисовкой (тик мог * персонажа всё-таки сдвинуть — тогда бит снимется сам, а pop_char_draw diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 0b69c2e..08166ad 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -298,6 +298,11 @@ static void draw_tile(int row, int col) rbl_mod = pop_tile_mod(row + 1, col - 1); } + /* Пометка «фон трогали» — ОДНА на весь тайл, а не на каждый его кусок + * (pop_cd_batch_begin/end). Досрочных выходов у draw_tile нет, так что + * скобка гарантированно закрывается. */ + pop_cd_batch_begin(); + /* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева. * Условие дословно: свой тайл пуст / верх большой колонны / верх двери, * а снизу-слева — ворота. --- */ @@ -472,6 +477,7 @@ static void draw_tile(int row, int col) } else if (t->fore_id) { pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy); } + pop_cd_batch_end(); /* одна пометка на весь тайл */ } diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c index 5d98df6..7a445c3 100644 --- a/applications/PoP/roomtest/pop_state.c +++ b/applications/PoP/roomtest/pop_state.c @@ -146,3 +146,19 @@ void pop_dbg_m15(void) { } * его вместе с тактами — так видно цену КАЖДОГО вида, а не число пометок. */ void pop_dbg_kind(uint8_t k) { (void)k; } void pop_dbg_m16(void) { } + +/* ВРЕМЕННО (разбор pop_blit_b 2026-08-13): сколько в одном блите занимают + * САМИ пиксели, а сколько пооспрайтовые накладные (map/unmap страницы + * атласа, чтение заголовка, пометка грязного прямоугольника). */ +void pop_dbg_b1(void) { } /* вход (спрайт прошёл отсев) */ +void pop_dbg_b2(void) { } /* atlas_image + gfx_w0_map */ +void pop_dbg_b3(void) { } /* сам блит */ +void pop_dbg_b4(void) { } /* pop_cd_touch */ +void pop_dbg_b5(void) { } /* gfx_w0_unmap (конец) */ +void pop_dbg_b6(void) { } /* взведён = пошли в gfx_blit_noclip, а не в _part */ + +/* ВРЕМЕННО (регрессия цены блита 2026-08-13): w и h упакованы в один + * 16-битный аргумент (arg1 -> HL при __sdcccall(1)), брейкпоинт логирует + * HL — дальше цена раскладывается как a + b*h + c*w*h, где b и есть + * построчные накладные ядра. */ +void pop_dbg_wh(uint16_t wh) { (void)wh; } diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h index 841be8a..d10b78f 100644 --- a/applications/PoP/roomtest/pop_state.h +++ b/applications/PoP/roomtest/pop_state.h @@ -93,3 +93,6 @@ void pop_dbg_m11(void); void pop_dbg_m12(void); void pop_dbg_m13(void); void pop_dbg_m14(void); void pop_dbg_m15(void); void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void); +void pop_dbg_b1(void); void pop_dbg_b2(void); void pop_dbg_b3(void); +void pop_dbg_b4(void); void pop_dbg_b5(void); void pop_dbg_b6(void); +void pop_dbg_wh(uint16_t wh); diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index a22c6dc..b50d5de 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -218,10 +218,47 @@ static uint16_t cd_cols_of(int x, int x1) /* Обе страницы помечаются сразу (персонаж чинится на каждой в свой кадр), * поэтому цикл развёрнут: индекс-переменная заставляла SDCC считать адрес * каждой ячейки сложением. Зовётся с КАЖДОГО блита фона. */ +/* Пакетная пометка (pop_cd_batch_begin/end). Один тайл рисуется пятью-шестью + * спрайтами, и каждый звал pop_cd_touch со своими четырьмя 16-битными + * аргументами — 2 866 тактов на спрайт, 15% всей цены блита (замер + * 2026-08-13). В пакете спрайты копят ОБЩИЙ прямоугольник, а настоящая + * пометка ставится одна на тайл. + * + * Объединение ЗАГРУБЛЯЕТ прямоугольник (накрывает и то, чего между кусками + * не касались), и это безопасно ровно в одну сторону: лишняя пометка стоит + * лишней перерисовки персонажа, пропущенная — стёртого и не восстановленного + * персонажа. Практически загрубление почти нулевое: все куски одного тайла + * и так лежат в тех же 1-2 колонках сетки 32px и том же ряду. */ +uint8_t pop_cd_batch; +static int cdb_x0, cdb_y0, cdb_x1, cdb_y1; + +void pop_cd_batch_begin(void) +{ + pop_cd_batch = 1; + cdb_x0 = cdb_y0 = 32767; + cdb_x1 = cdb_y1 = -32768; +} + +void pop_cd_batch_end(void) +{ + pop_cd_batch = 0; /* СНАЧАЛА снять: ниже настоящий вызов */ + if (cdb_x1 >= cdb_x0 && cdb_y1 >= cdb_y0) + pop_cd_touch(cdb_x0, cdb_y0, cdb_x1 - cdb_x0 + 1, cdb_y1 - cdb_y0 + 1); +} + void pop_cd_touch(int x, int y, int w, int h) { - uint16_t cols = cd_cols_of(x, x + w - 1); + uint16_t cols; uint8_t r0, r1; + if (pop_cd_batch) { /* копим, не разбирая на колонки/ряды */ + int x1 = x + w - 1, y1 = y + h - 1; + if (x < cdb_x0) cdb_x0 = x; + if (y < cdb_y0) cdb_y0 = y; + if (x1 > cdb_x1) cdb_x1 = x1; + if (y1 > cdb_y1) cdb_y1 = y1; + return; + } + cols = cd_cols_of(x, x + w - 1); if (!cols) return; r0 = cd_row_of(y); r1 = cd_row_of(y + h - 1); @@ -343,8 +380,11 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 || ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1)) return; + pop_dbg_b1(); /* ВРЕМЕННО */ img = (const uint8_t *)atlas_image(a, idx); gfx_w0_map(a->page); + pop_dbg_b2(); /* ВРЕМЕННО */ + pop_dbg_wh((uint16_t)(((uint16_t)img[0] << 8) | img[2])); /* ВРЕМЕННО: w,h */ w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */ @@ -368,18 +408,23 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) } if (x >= 0 && top >= 0 && w < 256 && h < 256 && x + (int)w <= 320 && top + (int)h <= 256) { + pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */ if (pop_upside) gfx_blit_noclip_vflip(x, top, img); else gfx_blit_noclip(x, top, 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); } + pop_dbg_b3(); /* ВРЕМЕННО */ pop_cd_touch(x, top, (int)w, (int)h); + pop_dbg_b4(); /* ВРЕМЕННО */ } else { blit_b_clip(img, x, top, (int)w, (int)h); + pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */ } } gfx_w0_unmap(); + pop_dbg_b5(); /* ВРЕМЕННО */ } 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); }