PoP roomtest: fore-слой — окно клипа, кэш кладки; кадр 2.5 -> 0.78 периода
Жалоба: стойка неподвижных Кида и стража занимала больше полутора
кадровых периодов. Гипотеза «виноват __banked» ЗАМЕРОМ НЕ
ПОДТВЕРДИЛАСЬ: весь банковый вызов (трамплин + смена страницы + тело
pop_guard_tick + возврат) стоит 654 такта при бюджете кадра 430 000.
Как мерил (выборка PC бесполезна — мост MAME отвечает из фреймового
колбэка, все сэмплы падают в обработчик прерывания): breakpoint'ы MAME с
действием {printf totalcycles; g} на входах фаз главного цикла, разности
соседних меток = стоимость фазы. Плюс профилирование полосами бордюра
(make PROF=1, макрос PROF() в roomtest.c) для быстрого взгляда.
Замер комнаты 3 (Kid + страж), такты, кадр = 430 000:
fore поверх стража 431 964
fore поверх Kid 402 816 -> 78 % всей работы кадра
остальное 241 956
ИТОГО 1 076 736 = 2.5 кадра
Две причины, обе устранены:
1. Fore-слой рисовал ЦЕЛЫЕ тайлы, хотя существует ровно для того, чтобы
вернуть куски поверх спрайта — за его прямоугольником в видеопамяти и
так правильный фон. Введено ОКНО клипа (pop_fore_set_clip): спрайт
сообщает свой итоговый габарит (у Kid — с клинком, брызгами и
обрезкой clip_char), fore-проход режет по нему. Отсев трёхступенчатый:
тайл целиком (tile_in_fclip, до обращения к атласу), кусок по грубому
габариту (до gfx_w0_map — w/h лежат в EMM-странице), и точный клип в
blit_b. Для последнего добавлен libbgi-примитив
gfx_blit_part_noclip — пара к gfx_blit_noclip, но под-прямоугольник.
2. Оставшиеся 341 К после клипа оказались НЕ пикселями: 18 вызовов
pop_prandom за проход, ~10 700 тактов каждый (32-битный LCG:
__mullong ~8 000 + __moduint). Раскладка кладки тайла — чистая
функция (комната, ряд, колонка), то есть константа комнаты, а
wall_pattern пересчитывал её каждый кадр. Теперь кэшируются готовые
РЕШЕНИЯ (что рисовать и с каким смещением), 3 байта на тайл, сброс в
pop_room_draw. Порядок вызовов prandom воспроизведён один в один,
включая то, что значение метки берётся только при сработавшем условии.
Итог того же замера: fore поверх стража 37 464, поверх Kid 46 464,
кадр целиком 334 716 = 0.78 периода (было 2.5). Ускорение 3.2x, сами
fore-проходы — 10x.
Проверка отсутствия регрессии: попиксельная разность скриншотов комнат
1/2/3 до и после — отличаются ТОЛЬКО языки пламени факелов (анимация),
кладка и метки совпадают байт в байт.
Побочно: sprinter-cc научился пробрасывать -DNAME в sdcc.
Память: куча W2 1245 -> 996 Б (кэш кладки 120 Б), резидент W3 12 819 ->
14 512 (свободно 1872 Б — становится тесно), банк 1 236/16384.
ВНИМАНИЕ: make size-check показывает рост 7 программ, но эталон
docs/size_baseline.tsv отстал (последний раз принят в 484b18d, libbgi
менялась в 95c22be/c127a4b/64ce633) — к этой правке рост отношения не
имеет: новый модуль библиотеки в чужие программы не линкуется.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,11 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
||||
# оставаться достижимым из __banked-модулей (guards.c): из банка
|
||||
# резидентная страница W3 не видна — ни напрямую, ни транзитивно.
|
||||
# См. docs/layout_plan_v2.md §2 (R2), §4.
|
||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --w3 pop_gdraw.c --bank 1=guards.c
|
||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --w3 pop_gdraw.c --bank 1=guards.c $(PROF_FLAGS)
|
||||
# make PROF=1 — профилирование полосами бордюра (см. PROF() в roomtest.c):
|
||||
# каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||
# MAME = время фазы в растровых строках.
|
||||
PROF_FLAGS := $(if $(PROF),-DPROF_BORDER)
|
||||
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
|
||||
@@ -62,33 +62,73 @@ static int g_clip_top;
|
||||
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
|
||||
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0).
|
||||
* При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */
|
||||
/* ОКНО FORE-СЛОЯ (экранные координаты, полуинтервал). Пока оно включено,
|
||||
* blit_b рисует только ту часть куска, что попадает в окно.
|
||||
*
|
||||
* Зачем: fore-слой существует РОВНО для того, чтобы вернуть куски тайлов
|
||||
* поверх нарисованного персонажа — за пределами его прямоугольника в
|
||||
* видеопамяти и так лежит правильный фон. А рисовал он тайлы ЦЕЛИКОМ:
|
||||
* стена (код 20) — блок 64×63 = 4032 пикселя, и таких кусков за проход
|
||||
* до девяти. Замер в MAME 2026-07-29 (комната 3, Kid + страж): проход
|
||||
* поверх Kid — 402 816 тактов, поверх стража — 431 964, вместе 78 % всей
|
||||
* работы кадра при бюджете 430 000 тактов на кадр. С окном большинство
|
||||
* кусков отсекается целиком, остальные режутся до пересечения. */
|
||||
static int fclip_x0, fclip_y0, fclip_x1, fclip_y1;
|
||||
static uint8_t fclip_on;
|
||||
|
||||
void pop_fore_set_clip(int x, int y, int w, int h)
|
||||
{
|
||||
fclip_x0 = x; fclip_y0 = y;
|
||||
fclip_x1 = x + w; fclip_y1 = y + h;
|
||||
}
|
||||
|
||||
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
{
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
int top;
|
||||
int top, dx, dy, dw, dh, sx = 0, sy = 0;
|
||||
if (idx >= a->count)
|
||||
return;
|
||||
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
|
||||
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
|
||||
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
|
||||
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
|
||||
* atlas_image, ни за map/unmap. */
|
||||
if (fclip_on &&
|
||||
(x >= fclip_x1 || x + 64 <= fclip_x0 ||
|
||||
ybottom + POP_YOFF + 1 <= fclip_y0 ||
|
||||
ybottom + POP_YOFF - 64 >= fclip_y1))
|
||||
return;
|
||||
img = (const uint8_t *)atlas_image(a, idx);
|
||||
gfx_w0_map(a->page);
|
||||
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: центрирование */
|
||||
if (w && h) {
|
||||
if (g_clip_top && top < g_clip_top) {
|
||||
int sy = g_clip_top - top; /* пропустить верхние строки */
|
||||
if (sy < (int)h)
|
||||
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy);
|
||||
} else if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
||||
x + (int)w <= 320 && top + (int)h <= 256) {
|
||||
/* Спрайт целиком на экране — линейное ядро без клипа: ~2.9×
|
||||
* быстрее (замер: 4617 против 13288 тактов на спрайт 32×3;
|
||||
* общее ядро gfx_blit платит за клип/16-бит/split, а не за
|
||||
* пиксели). W3-скобку ставит сама libbgi: из кода, собранного
|
||||
* с --w3, её вызывать нельзя (окно W3 уходит под видеобанк). */
|
||||
gfx_blit_noclip(x, top, img);
|
||||
dx = x; dy = top; dw = (int)w; dh = (int)h;
|
||||
if (g_clip_top && dy < g_clip_top) { /* полоса у потолка */
|
||||
sy = g_clip_top - dy; dy = g_clip_top; dh -= sy;
|
||||
}
|
||||
if (fclip_on) { /* окно fore-слоя */
|
||||
if (dx < fclip_x0) { int d = fclip_x0 - dx; sx += d; dx += d; dw -= d; }
|
||||
if (dy < fclip_y0) { int d = fclip_y0 - dy; sy += d; dy += d; dh -= d; }
|
||||
if (dx + dw > fclip_x1) dw = fclip_x1 - dx;
|
||||
if (dy + dh > fclip_y1) dh = 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);
|
||||
else
|
||||
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
|
||||
(uint8_t)dw, (uint8_t)dh);
|
||||
} else {
|
||||
gfx_blit(x, top, img); /* край экрана — с клипом */
|
||||
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
@@ -339,64 +379,134 @@ static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
|
||||
}
|
||||
|
||||
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
||||
static void wall_pattern(int row, int col, int which_part)
|
||||
{
|
||||
int xh = COL_XH[col];
|
||||
int dby = 63 * row + 65;
|
||||
unsigned long saved = rnd_seed;
|
||||
uint16_t mid_div, mid_off, bot_div, bot_off;
|
||||
uint8_t bg;
|
||||
/* КЭШ РАСКЛАДКИ КЛАДКИ. Серия prandom для тайла зависит только от
|
||||
* (комната, ряд, колонка) — пока мы в комнате, это КОНСТАНТА. А
|
||||
* wall_pattern зовётся из fore-слоя КАЖДЫЙ кадр, и на Z80 это самая
|
||||
* дорогая вещь в кадре: замер в MAME 2026-07-29 (комната 3) — 18 вызовов
|
||||
* pop_prandom за один fore-проход, ~10 700 тактов каждый (32-битный LCG:
|
||||
* __mullong ~8 000 + __moduint), то есть 193 К из 341 К всего прохода при
|
||||
* бюджете кадра ~430 К. Кэшируем не сид, а ГОТОВЫЕ РЕШЕНИЯ (что рисовать
|
||||
* и с каким смещением) — тогда в кадре не остаётся ни одного prandom.
|
||||
*
|
||||
* Порядок вызовов prandom воспроизведён один в один, включая то, что
|
||||
* значение метки берётся ТОЛЬКО когда сработало её условие: иначе
|
||||
* последовательность разъедется и кладка станет другой. which_part=0
|
||||
* использует префикс той же серии, поэтому кэш общий.
|
||||
*
|
||||
* Упаковка: wp_a = mid_div | mid_off<<1 | bot_div<<4 | bot_off<<5;
|
||||
* wp_b = rndblk | right<<1 | right_val<<2 | left<<4 | left_val<<5.
|
||||
* Индекс — (row+1)*10+col, ряды −1..2. */
|
||||
#define WP_N 40
|
||||
static uint8_t wp_a[WP_N], wp_b[WP_N], wp_ok[WP_N];
|
||||
|
||||
static void wall_pattern_reset(void) /* смена комнаты — сид другой */
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < WP_N; i++) wp_ok[i] = 0;
|
||||
}
|
||||
|
||||
/* Выходы wall_rnd — в файловых статиках: возвращать девять значений
|
||||
* через указатели на Z80 дороже самой функции. */
|
||||
static uint8_t wr_mid_div, wr_mid_off, wr_bot_div, wr_bot_off;
|
||||
static uint8_t wr_rndblk, wr_right, wr_rightv, wr_left, wr_leftv;
|
||||
|
||||
static void wall_rnd(int row, int col)
|
||||
{
|
||||
/* Кэшируем только штатную сетку: колонка вне 0..9 дала бы индекс
|
||||
* соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */
|
||||
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
|
||||
uint8_t a, b, bg;
|
||||
unsigned long saved;
|
||||
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
|
||||
a = wp_a[idx]; b = wp_b[idx];
|
||||
wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7);
|
||||
wr_bot_div = (uint8_t)((a >> 4) & 1); wr_bot_off = (uint8_t)((a >> 5) & 7);
|
||||
wr_rndblk = (uint8_t)(b & 1);
|
||||
wr_right = (uint8_t)((b >> 1) & 1); wr_rightv = (uint8_t)((b >> 2) & 3);
|
||||
wr_left = (uint8_t)((b >> 4) & 1); wr_leftv = (uint8_t)((b >> 5) & 7);
|
||||
return;
|
||||
}
|
||||
saved = rnd_seed;
|
||||
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
|
||||
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
|
||||
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||||
prandom(1); /* сброс */
|
||||
mid_div = prandom(1);
|
||||
mid_off = prandom(4);
|
||||
bot_div = prandom(1);
|
||||
bot_off = prandom(4);
|
||||
wr_mid_div = (uint8_t)prandom(1);
|
||||
wr_mid_off = (uint8_t)prandom(4);
|
||||
wr_bot_div = (uint8_t)prandom(1);
|
||||
wr_bot_off = (uint8_t)prandom(4);
|
||||
wr_rndblk = wr_right = wr_left = 0;
|
||||
wr_rightv = wr_leftv = 0;
|
||||
bg = wall_modifier(row, col);
|
||||
if (bg == 3) { /* WWW */
|
||||
wr_rndblk = (uint8_t)(prandom(4) == 0);
|
||||
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
|
||||
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
|
||||
} else if (bg == 0) { /* SWS */
|
||||
if (prandom(6) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(1); }
|
||||
} else if (bg == 1) { /* SWW */
|
||||
wr_rndblk = (uint8_t)(prandom(4) == 0);
|
||||
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
|
||||
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(3); }
|
||||
} else { /* WWS (bg==2) */
|
||||
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)(prandom(1) + 2); }
|
||||
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
|
||||
}
|
||||
rnd_seed = saved;
|
||||
if (idx >= 0 && idx < WP_N) {
|
||||
wp_a[idx] = (uint8_t)(wr_mid_div | (wr_mid_off << 1) |
|
||||
(wr_bot_div << 4) | (wr_bot_off << 5));
|
||||
wp_b[idx] = (uint8_t)(wr_rndblk | (wr_right << 1) | (wr_rightv << 2) |
|
||||
(wr_left << 4) | (wr_leftv << 5));
|
||||
wp_ok[idx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
||||
static void wall_pattern(int row, int col, int which_part)
|
||||
{
|
||||
int xh = COL_XH[col];
|
||||
int dby = 63 * row + 65;
|
||||
uint8_t bg = wall_modifier(row, col);
|
||||
|
||||
wall_rnd(row, col);
|
||||
if (bg == 3) { /* WWW */
|
||||
if (which_part) {
|
||||
if (prandom(4) == 0)
|
||||
if (wr_rndblk)
|
||||
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||
}
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
|
||||
if (which_part) {
|
||||
if (prandom(4) == 0)
|
||||
draw_right_mark(prandom(3), mid_off, row, col);
|
||||
if (prandom(4) == 0)
|
||||
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
|
||||
if (wr_right)
|
||||
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||
if (wr_left)
|
||||
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||
}
|
||||
} else if (bg == 0) { /* SWS */
|
||||
if (which_part) {
|
||||
if (prandom(6) == 0)
|
||||
draw_left_mark(prandom(1), mid_off - mid_div, bot_off - bot_div, row, col);
|
||||
}
|
||||
if (which_part && wr_left)
|
||||
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||
} else if (bg == 1) { /* SWW */
|
||||
if (which_part) {
|
||||
if (prandom(4) == 0)
|
||||
if (wr_rndblk)
|
||||
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
||||
if (prandom(4) == 0)
|
||||
draw_right_mark(prandom(3), mid_off, row, col);
|
||||
if (prandom(4) == 0)
|
||||
draw_left_mark(prandom(3), mid_off - mid_div, bot_off - bot_div, row, col);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||
if (wr_right)
|
||||
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||
if (wr_left)
|
||||
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||
}
|
||||
} else { /* WWS (bg==2) */
|
||||
if (which_part)
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
|
||||
if (which_part) {
|
||||
if (prandom(4) == 0)
|
||||
draw_right_mark((uint16_t)(prandom(1) + 2), mid_off, row, col);
|
||||
if (prandom(4) == 0)
|
||||
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
|
||||
if (wr_right)
|
||||
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||
if (wr_left)
|
||||
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||
}
|
||||
}
|
||||
rnd_seed = saved;
|
||||
}
|
||||
|
||||
/* topright тайла снизу-слева (seg008.c:410). */
|
||||
@@ -1088,6 +1198,7 @@ void pop_room_draw(uint8_t room_num,
|
||||
{
|
||||
int row, col;
|
||||
g_room = room_num;
|
||||
wall_pattern_reset(); /* кэш кладки привязан к номеру комнаты */
|
||||
g_fg = fg; g_bg = bg;
|
||||
g_lfg = lcol_fg; g_lbg = lcol_bg;
|
||||
|
||||
@@ -1177,8 +1288,25 @@ static void ceil_over_kid_tile(int col)
|
||||
fore_only_tile(-1, col); /* draw_tile_fore */
|
||||
}
|
||||
|
||||
/* Пересекается ли габарит тайла с окном fore-слоя. Шаг тайла — 32 px
|
||||
* (COL_XH[col]*8 = col*32), куски выходят вправо максимум на +8 (дивайдеры
|
||||
* и метки рисуются в ячейке правого соседа) и вверх на 42+высота кадра.
|
||||
* Даёт отсев ЦЕЛОГО тайла ДО обращения к атласу: спрайт 27..40 px шириной
|
||||
* задевает не все тайлы футпринта, а платили за каждый (atlas_image +
|
||||
* gfx_w0_map + разбор заголовка на КАЖДЫЙ кусок). */
|
||||
static uint8_t tile_in_fclip(int row, int col)
|
||||
{
|
||||
int x0, y0;
|
||||
if (!fclip_on) return 1;
|
||||
x0 = COL_XH[col] * 8;
|
||||
y0 = POP_YOFF + 63 * row;
|
||||
return (uint8_t)(x0 < fclip_x1 && x0 + 40 > fclip_x0 &&
|
||||
y0 - 8 < fclip_y1 && y0 + 70 > fclip_y0);
|
||||
}
|
||||
|
||||
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
||||
{
|
||||
if (!tile_in_fclip(row, col)) return;
|
||||
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
|
||||
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
|
||||
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
|
||||
@@ -1211,7 +1339,9 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx)
|
||||
{
|
||||
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
|
||||
* Kid спускается/подтягивается, закрашивается целым тайлом */
|
||||
uint8_t code = tile_code_drawn(row, col);
|
||||
uint8_t code;
|
||||
if (!tile_in_fclip(row, col)) return;
|
||||
code = tile_code_drawn(row, col);
|
||||
int x, dmy, dby;
|
||||
const piece *t;
|
||||
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
|
||||
@@ -1301,8 +1431,9 @@ static void overlay_mid_tile(int row, int col)
|
||||
* сразу), поэтому порядок эмулируется явной проверкой. */
|
||||
static void other_overlay_tile(int row, int col)
|
||||
{
|
||||
uint8_t code = tile_code(row, col);
|
||||
uint8_t left;
|
||||
uint8_t code, left;
|
||||
if (!tile_in_fclip(row, col)) return;
|
||||
code = tile_code(row, col);
|
||||
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
|
||||
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
|
||||
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и
|
||||
@@ -1374,6 +1505,7 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir, uint8_t frame, uint8_t action)
|
||||
{
|
||||
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
|
||||
fclip_on = 1; /* окно = прямоугольник спрайта */
|
||||
char_footprint(obj_x, obj_y, w, h, dir);
|
||||
cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw;
|
||||
rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw;
|
||||
@@ -1444,6 +1576,7 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
g_clip_top = 0;
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
fclip_on = 0;
|
||||
}
|
||||
|
||||
/* Передние грани тайлов поверх ПРОИЗВОЛЬНОГО персонажа (страж, дальше —
|
||||
@@ -1460,6 +1593,7 @@ void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir)
|
||||
{
|
||||
int8_t r, c;
|
||||
fclip_on = 1; /* окно = прямоугольник спрайта */
|
||||
char_footprint(obj_x, obj_y, w, h, dir);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
for (r = fp_rT; r <= fp_rB; r++)
|
||||
@@ -1474,4 +1608,5 @@ void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
g_clip_top = 0;
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
fclip_on = 0;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,14 @@ void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_
|
||||
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir, uint8_t frame, uint8_t action);
|
||||
|
||||
/* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с
|
||||
* POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит
|
||||
* тот, кто нарисовал спрайт (kid_draw / pop_guard_draw) — он один знает
|
||||
* итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне
|
||||
* fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и
|
||||
* замер — в pop_bg.c у fclip_on. */
|
||||
void pop_fore_set_clip(int x, int y, int w, int h);
|
||||
|
||||
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
|
||||
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
|
||||
* (до ×8/7), как у kid_fp_obj_x(). */
|
||||
|
||||
@@ -59,7 +59,7 @@ void pop_guard_heal(void)
|
||||
|
||||
void pop_guard_draw(void)
|
||||
{
|
||||
int obj_x, obj_y, top, fwd, fp_x;
|
||||
int obj_x, obj_y, top, fwd, fp_x, bx0 = 0, top0 = 0;
|
||||
uint8_t page, idx, p, flip;
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
@@ -85,6 +85,7 @@ void pop_guard_draw(void)
|
||||
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
|
||||
if (flip) bx -= (int)w;
|
||||
top = obj_y - (int)h + 1;
|
||||
bx0 = bx; top0 = top;
|
||||
p = gfx_get_draw_page() & 1;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
@@ -92,6 +93,9 @@ void pop_guard_draw(void)
|
||||
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
/* Окно fore-слоя = прямоугольник стража (см. pop_bg.c, fclip_on):
|
||||
* без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов на кадр. */
|
||||
pop_fore_set_clip(bx0, top0 + POP_YOFF, (int)w, (int)h);
|
||||
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
|
||||
* midtable, а foretable рисуется после всех midtable-записей, поэтому
|
||||
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
|
||||
|
||||
@@ -367,6 +367,9 @@ void kid_draw_splash(void)
|
||||
if (top + (int)h > y1) y1 = top + (int)h;
|
||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
||||
kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0);
|
||||
/* Окно fore-слоя расширяем тем же объединением: брызги
|
||||
* тоже должны уходить под передние грани тайлов. */
|
||||
pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,6 +393,10 @@ void kid_draw(void)
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
|
||||
/* Окно fore-слоя по умолчанию пустое: на любом раннем выходе (нет
|
||||
* кадра / страница не загружена) перекрывать нечего, и fore-проход
|
||||
* не должен трогать экран. */
|
||||
pop_fore_set_clip(0, 0, 0, 0);
|
||||
if (fr.image == 255)
|
||||
return; /* пустой кадр */
|
||||
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
|
||||
@@ -488,4 +495,17 @@ void kid_draw(void)
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
/* Окно fore-слоя = итоговый прямоугольник спрайта (с клинком и
|
||||
* обрезкой clip_char): fore-проход вернёт куски тайлов ТОЛЬКО поверх
|
||||
* него, а не целыми тайлами — см. pop_bg.c, fclip_on. Ставим и когда
|
||||
* рисовать нечего (kid_lvalid=0): нулевое окно = fore-проход не
|
||||
* трогает экран, что и правильно — перекрывать нечего. */
|
||||
{
|
||||
uint8_t dp = gfx_get_draw_page() & 1;
|
||||
if (kid_lvalid[dp])
|
||||
pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF,
|
||||
(int)kid_lw[dp], (int)kid_lh[dp]);
|
||||
else
|
||||
pop_fore_set_clip(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,26 @@
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
|
||||
/* ПРОФИЛИРОВАНИЕ ПОЛОСАМИ БОРДЮРА (make PROF=1). Цвет бордюра держится,
|
||||
* пока фаза считает, поэтому высота полосы на скриншоте = время фазы в
|
||||
* растровых строках (1 строка ≈ 224 такта Z80 при 3.5 МГц). Это
|
||||
* единственный способ мерить здесь: мост MAME отвечает из фреймового
|
||||
* колбэка, поэтому выборка PC вырождается (100 % сэмплов в обработчике
|
||||
* прерывания). Фазы:
|
||||
* 0 чёрный — ожидание vsync (запас кадра)
|
||||
* 1 синий — ввод + heal (стереть прошлые спрайты)
|
||||
* 2 красный — логика (kid_tick/физика/страж в банке)
|
||||
* 3 лиловый — слой фона (loose/trobs/redraw_needed/шов)
|
||||
* 4 зелёный — kid_draw
|
||||
* 5 циан — guard_draw + fore поверх стража
|
||||
* 6 жёлтый — fore поверх Kid + чистка бортов
|
||||
* 7 белый — флип */
|
||||
#ifdef PROF_BORDER
|
||||
#define PROF(n) io_border = (n)
|
||||
#else
|
||||
#define PROF(n) ((void)0)
|
||||
#endif
|
||||
|
||||
/* Сколько банков грузит crt0_banked (читает его стартовый код). */
|
||||
const uint8_t n_banks = 1;
|
||||
|
||||
@@ -267,7 +287,7 @@ int main(void)
|
||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||
* переключ). Выключение возвращает показ на страницу 0. */
|
||||
|
||||
io_border = 2; /* синий - начало */
|
||||
PROF(1); /* ввод + heal */
|
||||
|
||||
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
||||
if (sp && !sp_prev) {
|
||||
@@ -291,7 +311,7 @@ int main(void)
|
||||
cheat_prev = k;
|
||||
}
|
||||
if (frozen) {
|
||||
io_border = 0; /* черный: ждём vsync */
|
||||
PROF(0); /* чёрный: ждём vsync */
|
||||
gfx_wait_vsync();
|
||||
continue;
|
||||
}
|
||||
@@ -335,6 +355,7 @@ int main(void)
|
||||
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
||||
pop_guard_heal(); /* и прошлый кадр стража */
|
||||
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
|
||||
PROF(2); /* логика */
|
||||
kid_tick(); /* play_seq: следующий кадр */
|
||||
pop_phys_tick(); /* K3: падение/приземление/стена */
|
||||
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
|
||||
@@ -360,16 +381,14 @@ int main(void)
|
||||
}
|
||||
}
|
||||
|
||||
io_border = 4; /* зеленый */
|
||||
PROF(3); /* слой фона */
|
||||
|
||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
||||
io_border = 6; /* голубой */
|
||||
pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
|
||||
/* process_trobs — рисования внутри нет) */
|
||||
pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
|
||||
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
|
||||
io_border = 4; /* синий */
|
||||
{ /* ворота соседа слева (room8) видны в col0 — */
|
||||
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||
if (lft) {
|
||||
@@ -386,7 +405,6 @@ int main(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
io_border = 2; /* зеленый */
|
||||
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
||||
dead_frames++;
|
||||
if (dead_frames > RESPAWN_SETTLE &&
|
||||
@@ -399,13 +417,11 @@ int main(void)
|
||||
pop_kid_hp_reset();
|
||||
}
|
||||
}
|
||||
io_border = 6; /* голубой */
|
||||
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
||||
uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
|
||||
uint8_t fcol = (uint8_t)(fpos % 10);
|
||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||
pop_loose_fell = 0;
|
||||
io_border = 2; /* синий */
|
||||
add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
|
||||
if (down) {
|
||||
int8_t lrow = pop_room_col_landing(down, fcol);
|
||||
@@ -449,16 +465,17 @@ int main(void)
|
||||
flash_bg(255, 85, 85); /* color_12_brightred */
|
||||
flash_on = 1;
|
||||
}
|
||||
io_border = 4; /* зеленый */
|
||||
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
||||
PROF(5); /* страж: спрайт + fore поверх него */
|
||||
pop_guard_draw(); /* страж — ДО Kid (порядок как в оригинале) */
|
||||
PROF(4); /* kid_draw */
|
||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
||||
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
|
||||
kid_draw_splash();
|
||||
pop_kid_hurt = 0;
|
||||
}
|
||||
io_border = 0; /* черный */
|
||||
PROF(6); /* fore поверх Kid + борта */
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
kid_fp_width(), kid_fp_height(),
|
||||
Kid.direction, Kid.frame, Kid.action);
|
||||
@@ -468,7 +485,7 @@ int main(void)
|
||||
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
||||
* set_visible_page переключает во время бланка (gfx.h паттерн). */
|
||||
|
||||
io_border = 0; /* черный: ждём vsync */
|
||||
PROF(0); /* чёрный: ждём vsync */
|
||||
|
||||
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
|
||||
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
||||
|
||||
+4
-1
@@ -23,6 +23,7 @@
|
||||
# CODE=W1|W2 DATA=W1|W2|SAME BANKED=W1|W3
|
||||
# Example: --memory-manual CODE=W2,DATA=W2,BANKED=W3
|
||||
# -I PATH additional include path (repeatable)
|
||||
# -DNAME[=VAL] preprocessor define, passed to sdcc as is (repeatable)
|
||||
# -L 0xADDR code load address (default: derived from --memory mode)
|
||||
# -E 0xADDR entry address (default: same as -L)
|
||||
# -S 0xADDR stack address (default: 0xBFFE)
|
||||
@@ -106,6 +107,7 @@ LD_EXTRA=()
|
||||
MKEXE_EXTRA=()
|
||||
BANK_SPECS=() # entries like "1=engine.c"
|
||||
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
||||
USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть
|
||||
W3_RELS=() # заполняется при компиляции W3-модулей
|
||||
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
||||
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
||||
@@ -139,6 +141,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--w3) W3_SPECS+=("$2"); shift 2;;
|
||||
--stack-size) STACK_SIZE="$2"; shift 2;;
|
||||
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
||||
-D*) USER_DEFS+=("$1"); shift;;
|
||||
--bank) BANK_SPECS+=("$2"); shift 2;;
|
||||
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
||||
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
||||
@@ -366,7 +369,7 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
|
||||
|
||||
# 2. user sources → .rel (HOME)
|
||||
USER_RELS=()
|
||||
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}")
|
||||
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}" "${USER_DEFS[@]}")
|
||||
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
|
||||
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
|
||||
for src in "${SOURCES[@]}"; do
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* gfx_blit_part_noclip — блит ПОД-ПРЯМОУГОЛЬНИКА картинки getimage-формата
|
||||
* БЕЗ клипа по экрану, в ТЕКУЩЕМ банке (gfx_set_bank). Пара к
|
||||
* gfx_blit_noclip ровно так же, как gfx_blit_part — к gfx_blit.
|
||||
*
|
||||
* Зачем: тайловому движку часто нужно положить поверх спрайта не весь
|
||||
* тайл, а только ту его часть, что перекрывает спрайт. Через
|
||||
* gfx_blit_part это идёт общим ядром _gfx_blit_full (16-битная
|
||||
* арифметика, клип, нарезка полос) — ~13 К тактов накладных на вызов
|
||||
* независимо от размера; здесь тот же под-прямоугольник рисуется линейным
|
||||
* спрайтовым ядром (~4.6 К на 32×3). Замер PoP roomtest 2026-07-29:
|
||||
* fore-слой поверх персонажа рисовал 9 целых тайлов за 432 К тактов
|
||||
* (стена = блок 64×63); с обрезкой по прямоугольнику спрайта тех же
|
||||
* пикселей остаётся втрое меньше.
|
||||
*
|
||||
* Требования (проверяет ВЫЗЫВАЮЩИЙ, кода проверок здесь нет):
|
||||
* - под-прямоугольник (sx,sy,w,h) лежит внутри картинки;
|
||||
* - прямоугольник назначения целиком на экране;
|
||||
* - w,h <= 255, y <= 255.
|
||||
* Не выполняется — зовите gfx_blit_part.
|
||||
*
|
||||
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): из приложения,
|
||||
* собранного с --w3, её вызывать нельзя — после _bgi_begin окно W3 занято
|
||||
* видеобанком и код вызывающего исчезает из адресного пространства.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_blit_part_noclip(int x, int y, const void *img,
|
||||
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
|
||||
const uint8_t *src = p + 4 + (uint16_t)sy * stride + sx;
|
||||
|
||||
_bgi_begin();
|
||||
_gfx_blit_sprite_noclip(x, y, src, stride, w, h);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -105,6 +105,14 @@ void gfx_heal_noclip(int x, int y, uint8_t w, uint8_t h);
|
||||
void gfx_blit_part(int x, int y, const void *img,
|
||||
int sx, int sy, int w, int h);
|
||||
|
||||
/* Тот же под-прямоугольник БЕЗ клипа по экрану — линейным спрайтовым
|
||||
* ядром (пара к gfx_blit_noclip). Вызывающий гарантирует: под-
|
||||
* прямоугольник внутри img, назначение целиком на экране, w,h,y <= 255.
|
||||
* Для тайловых движков, кладущих поверх спрайта только перекрывающую его
|
||||
* часть тайла (PoP: fore-слой). */
|
||||
void gfx_blit_part_noclip(int x, int y, const void *img,
|
||||
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h);
|
||||
|
||||
/* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel-
|
||||
* проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без
|
||||
* CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot-
|
||||
|
||||
Reference in New Issue
Block a user