pop_blit_b: быстрый путь без клипа + backlog отложенной оптимизации

Клипованный путь вынесен в отдельную функцию blit_b_clip: под его девять
16-битных локалей SDCC заводит кадр IX, и за этот кадр платили ВСЕ блиты
фона, включая те, где клипа нет вовсе (весь фон вне fore-прохода — факелы,
зелья, перерисовка тайлов, у них pop_t_fclip_on == 0).  Быстрый путь идёт
сразу в gfx_blit_noclip.

Замер: pop_torch_draw 41 778 -> 31 218 тактов на факел (часть разницы —
прошлая правка pop_cd_touch; чистый вклад этой ~6 300 на блит).  Поведение
не изменилось: клипованная ветка перенесена дословно.

docs/perf_backlog.md — отложенные идеи с измеренной ценой (футпринт из
физики 11 574, размеры ленты из каталога атласа, один map/unmap на группу
блитов, единый проход по тайлам как redraw_needed_tiles, objtable,
отложенные таблицы back/mid/fore) плюс раздел «как мерить»: wait-state'ы
дают 2,4x к справочным тактам, кадр 430 000, адреса символов меняются
после каждой пересборки, сцена между сессиями не воспроизводится.
Там же — что уже проверено и НЕ сработало.

tests-host: 5/5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 11:05:42 +03:00
parent a9f4521ffd
commit c8fe0bd37a
4 changed files with 211 additions and 29 deletions
+48 -29
View File
@@ -186,11 +186,45 @@ void pop_cd_touch(int x, int y, int w, int h)
}
/* ---- Блит куска атласа ---------------------------------------------- */
/* Клипованный путь — ОТДЕЛЬНОЙ функцией. У него девять 16-битных локалей,
* и SDCC заводит под них кадр IX; пока он жил в общем теле, за этот кадр
* платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46).
* А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья,
* перерисовка тайлов — идёт с pop_t_fclip_on == 0. */
static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
{
int dx = x, dy = top, dw = w, dh = h, sx = 0, sy = 0;
if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */
sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy;
}
if (pop_t_fclip_on) { /* окно fore-слоя */
if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; }
if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; }
if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx;
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
}
if (dw <= 0 || dh <= 0) return;
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
if (sx == 0 && sy == 0 && dw == w && dh == h)
gfx_blit_noclip(dx, dy, img);
else
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
(uint8_t)dw, (uint8_t)dh);
} else {
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана */
}
/* Кусок ФОНА лёг туда, где может лежать уже нарисованный персонаж —
* пометить область (DRAW-COST, pop_cdraw.h). Признак «это не fore-проход
* поверх персонажа» — окно клипа: его ставит только он. */
if (!pop_t_fclip_on) pop_cd_touch(dx, dy, dw, dh);
}
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{
const uint8_t *img;
uint16_t w, h;
int top, dx, dy, dw, dh, sx = 0, sy = 0;
int top;
if (idx >= a->count)
return;
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
@@ -208,37 +242,22 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
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: центрирование */
dx = x; dy = top; dw = (int)w; dh = (int)h;
if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */
sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy;
}
if (pop_t_fclip_on) { /* окно fore-слоя */
if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; }
if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; }
if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx;
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
}
if (w && h && dw > 0 && dh > 0) {
/* Целиком на экране — линейное ядро без клипа: ~2.9× быстрее
* (замер: 4617 против 13288 тактов на спрайт 32×3; общее ядро
* gfx_blit платит за клип/16-бит/split, а не за пиксели).
* W3-скобку ставит сама libbgi: из кода, собранного с --w3, её
* вызывать нельзя (окно W3 уходит под видеобанк). */
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
if (sx == 0 && sy == 0 && dw == (int)w && dh == (int)h)
gfx_blit_noclip(dx, dy, img);
if (w && h) {
if (!pop_t_fclip_on && !pop_t_clip_top) {
/* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне
* fore-прохода. Линейное ядро без клипа — ~2.9× быстрее общего
* (замер: 4617 против 13288 тактов на спрайт 32×3; gfx_blit
* платит за клип/16-бит/split, а не за пиксели). W3-скобку
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
x + (int)w <= 320 && top + (int)h <= 256)
gfx_blit_noclip(x, top, img);
else
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
(uint8_t)dw, (uint8_t)dh);
gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
pop_cd_touch(x, top, (int)w, (int)h);
} else {
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
blit_b_clip(img, x, top, (int)w, (int)h);
}
/* Кусок ФОНА лёг туда, где может лежать уже нарисованный персонаж —
* пометить область, чтобы его перерисовали (DRAW-COST, pop_cdraw.h).
* Признак «это не fore-проход поверх персонажа» — окно клипа: его
* ставит только он. */
if (!pop_t_fclip_on) pop_cd_touch(dx, dy, dw, dh);
}
gfx_w0_unmap();
}