Разбор draw_tile: модель цены блита + пакетная пометка pop_cd_touch
Замер (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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(); /* одна пометка на весь тайл */
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user