DRAW-COST шаг 1: пропуск неизменившегося персонажа — 210% -> 116% кадра
Персонаж, у которого с прошлой отрисовки ЭТОЙ страницы дабл-буфера не изменился ни один вход отрисовки, а фон в его прямоугольнике не трогали, уже нарисован правильно: heal, блит и fore-проход пропускаются целиком. Не спецкейс «мёртвый страж», а общее правило — покрывает и труп, и стоящего Кида, и ждущего стража. Механизм: снимок входов по страницам (pop_cdraw.c, cd_sig/cd_quiet) + позиционная метка «фон трогали вот здесь» (pop_cd_touch в резидентном pop_tile.c, зовёт сам pop_blit_b). Решение перепроверяется перед отрисовкой, а pop_char_draw страхуется собственным heal — если тик всё-таки сдвинул персонажа, прошлый кадр стирается там. Слоты рядом (32 px) — перерисовываем оба, иначе heal соседа выест кусок из «тихого». Метка обязана быть ПОЗИЦИОННОЙ: с флагом «фон трогали хоть где-то» выигрыш был ровно нулевым — факелы анимируются каждый кадр и гасили пропуск для всех сразу (597 684 такта, как без оптимизации). Замеры (MAME, брейкпоинты по totalcycles, бюджет кадра 430 000): комн. 1.3, труп стража, Кид стоит: 210 % -> 116 % (500 772 такта), ноль вызовов pop_heal_fast за кадр, весь фон — 2 блита (44 136); комн. 1.1, Кид стоит вдали от факелов: 404 112 (94 %), цикл 4 -> 3 кадра. Узкое место сместилось на ЛОГИКУ: 60 % кадра уходит на тик персонажей, которые СТОЯТ, ещё 28 % — на loose_tick + process_trobs в комнате без единой ловушки. Разбивка и план — TASKS_OPEN.md#draw-cost.
This commit is contained in:
@@ -156,8 +156,12 @@ static void cd_clip_add(pop_cdraw_t *s, int x, int y, uint16_t w, uint16_t h)
|
||||
/* Стирание прошлого кадра персонажа и его накладных. pop_heal_fast, а не
|
||||
* голый gfx_heal: стираем ровно то, что рисовали, а рисовали в подавляющем
|
||||
* большинстве кадров noclip-ядром; клипающий путь остаётся для straddle и
|
||||
* выхода за поле. */
|
||||
void pop_char_heal(uint8_t who) __banked
|
||||
* выхода за поле.
|
||||
*
|
||||
* Тело — static: pop_char_draw зовёт его же «страховкой» (см. ниже), а
|
||||
* трамплин выбирается ОБЪЯВЛЕНИЕМ, и вызов через `__banked`-имя стоил бы
|
||||
* 654 такта внутри собственного банка (memory sdcc_banked_call_rules). */
|
||||
static void cd_heal(uint8_t who)
|
||||
{
|
||||
pop_cdraw_t *s = &pop_cd[who];
|
||||
uint8_t p = gfx_get_draw_page() & 1;
|
||||
@@ -166,6 +170,101 @@ void pop_char_heal(uint8_t who) __banked
|
||||
s->valid[p] = s->ovalid[p] = 0;
|
||||
}
|
||||
|
||||
void pop_char_heal(uint8_t who) __banked { cd_heal(who); }
|
||||
|
||||
/* ---- Пропуск неизменившегося кадра (DRAW-COST; контракт — pop_cdraw.h) -- */
|
||||
|
||||
/* Снимок ВХОДОВ отрисовки слота. Кадр анимации (kid_frame/pop_gframe)
|
||||
* однозначно определяется полем frame, поэтому его самого в снимке нет;
|
||||
* тайлы вокруг персонажа (clip_char, fore-проход) в снимок тоже не входят —
|
||||
* их изменение ловит pop_cd_dirty. */
|
||||
typedef struct {
|
||||
uint8_t frame, x, y, action, sword, charid, room, hurt;
|
||||
int8_t dir, ccol, crow;
|
||||
int dx; /* render_dx: straddle-смещение */
|
||||
} cd_sig_t;
|
||||
|
||||
static cd_sig_t cd_sig[POP_CH_N][2];
|
||||
|
||||
static void cd_sig_make(uint8_t who, cd_sig_t *g)
|
||||
{
|
||||
const pop_char_t *ch = (who == POP_CH_KID) ? &Kid : &Guard;
|
||||
g->frame = ch->frame; g->x = ch->x; g->y = ch->y;
|
||||
g->action = ch->action; g->sword = ch->sword; g->charid = ch->charid;
|
||||
g->room = ch->room;
|
||||
g->hurt = (uint8_t)((who == POP_CH_KID) ? pop_kid_hurt : pop_guard_hurt);
|
||||
g->dir = ch->direction;
|
||||
g->ccol = ch->curr_col; g->crow = ch->curr_row;
|
||||
g->dx = pop_cd[who].render_dx;
|
||||
}
|
||||
|
||||
/* Габарит слота на странице: спрайт + накладные (клинок/брызги). y —
|
||||
* КОМНАТНЫЙ (как в pop_cd), перевод в экранный делает вызывающий. */
|
||||
static void cd_bbox(uint8_t who, uint8_t p, int *x0, int *y0, int *x1, int *y1)
|
||||
{
|
||||
const pop_cdraw_t *s = &pop_cd[who];
|
||||
*x0 = s->x[p]; *y0 = s->y[p];
|
||||
*x1 = *x0 + (int)s->w[p]; *y1 = *y0 + (int)s->h[p];
|
||||
if (s->ovalid[p]) {
|
||||
if (s->ox[p] < *x0) *x0 = s->ox[p];
|
||||
if (s->oy[p] < *y0) *y0 = s->oy[p];
|
||||
if (s->ox[p] + (int)s->ow[p] > *x1) *x1 = s->ox[p] + (int)s->ow[p];
|
||||
if (s->oy[p] + (int)s->oh[p] > *y1) *y1 = s->oy[p] + (int)s->oh[p];
|
||||
}
|
||||
}
|
||||
|
||||
/* «Тихий» слот: спрайт этой страницы цел (heal его не трогал), фон в его
|
||||
* прямоугольнике не трогали, и все входы отрисовки те же, что в момент,
|
||||
* когда этот кадр на эту страницу и рисовали. valid[p] здесь ключевой: он
|
||||
* снимается heal'ом, поэтому решение остаётся согласованным, сколько раз его
|
||||
* ни перепроверяй за кадр. */
|
||||
static uint8_t cd_quiet(uint8_t who, uint8_t p)
|
||||
{
|
||||
cd_sig_t g;
|
||||
const uint8_t *a, *b;
|
||||
uint8_t i;
|
||||
if (!pop_cd[who].valid[p]) return 0;
|
||||
if (pop_cd_dirty & (1 << p)) { /* фон правили — но задели ли нас? */
|
||||
int x0, y0, x1, y1;
|
||||
cd_bbox(who, p, &x0, &y0, &x1, &y1);
|
||||
y0 += POP_YOFF; y1 += POP_YOFF; /* метка фона — в ЭКРАННЫХ */
|
||||
if (x0 < pop_cd_dx1[p] && pop_cd_dx0[p] < x1 &&
|
||||
y0 < pop_cd_dy1[p] && pop_cd_dy0[p] < y1) return 0;
|
||||
}
|
||||
cd_sig_make(who, &g);
|
||||
a = (const uint8_t *)&g; b = (const uint8_t *)&cd_sig[who][p];
|
||||
for (i = 0; i < sizeof(cd_sig_t); i++)
|
||||
if (*a++ != *b++) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Запас вокруг габарита соседа: за кадр персонаж проходит заметно меньше
|
||||
* (бег — ~11 экранных px), а прыжок с падением укладывается сюда же. */
|
||||
#define CD_MARGIN 32
|
||||
|
||||
uint8_t pop_char_skip_mask(void) __banked
|
||||
{
|
||||
uint8_t p = gfx_get_draw_page() & 1;
|
||||
uint8_t q0 = cd_quiet(POP_CH_KID, p);
|
||||
uint8_t q1 = cd_quiet(POP_CH_OPP, p);
|
||||
if (q0 && q1) return 3; /* оба стоят — пересечься нечему */
|
||||
if (q0 || q1) {
|
||||
/* Один перерисовывается: его heal (и fore-проход по его окну) может
|
||||
* задеть «тихого» соседа и выесть из него кусок. Прямоугольники
|
||||
* рядом — перерисовываем ОБОИХ. */
|
||||
uint8_t st = (uint8_t)(q0 ? POP_CH_KID : POP_CH_OPP);
|
||||
uint8_t mv = (uint8_t)(st ^ 1);
|
||||
if (pop_cd[mv].valid[p]) {
|
||||
int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1;
|
||||
cd_bbox(st, p, &ax0, &ay0, &ax1, &ay1);
|
||||
cd_bbox(mv, p, &bx0, &by0, &bx1, &by1);
|
||||
if (ax0 < bx1 + CD_MARGIN && bx0 < ax1 + CD_MARGIN &&
|
||||
ay0 < by1 + CD_MARGIN && by0 < ay1 + CD_MARGIN) return 0;
|
||||
}
|
||||
}
|
||||
return (uint8_t)((q0 ? 1 : 0) | (q1 ? 2 : 0));
|
||||
}
|
||||
|
||||
void pop_char_set_render_dx(uint8_t who, int dx) __banked
|
||||
{
|
||||
pop_cd[who].render_dx = dx;
|
||||
@@ -243,6 +342,12 @@ void pop_char_draw(uint8_t who) __banked
|
||||
uint16_t w, h, vis_w;
|
||||
int obj_x, obj_y, fp_x, fwd, top, bx, skip, rows, ct, cr;
|
||||
|
||||
/* Страховка пропуска (DRAW-COST): главный цикл мог решить, что слот
|
||||
* «тихий», и heal не звать — а тик после этого персонажа всё-таки
|
||||
* сдвинул. Здесь прошлый кадр уже точно надо стереть. Если heal в этом
|
||||
* кадре был, вызов бесплатен: valid/ovalid уже сняты. */
|
||||
cd_heal(who);
|
||||
|
||||
s->cw = 0; /* окно fore-клипа: пусто, пока не нарисовали */
|
||||
s->fpw = 0; /* габарит кадра: нечего перекрывать */
|
||||
|
||||
@@ -360,6 +465,10 @@ void pop_char_draw(uint8_t who) __banked
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
/* Что именно нарисовано на ЭТОЙ странице — снимок для пропуска
|
||||
* следующих кадров (DRAW-COST). Только на полном пути: ранние выходы
|
||||
* оставляют valid[dp] снятым, и cd_quiet отсеет слот и без снимка. */
|
||||
cd_sig_make(who, &cd_sig[who][dp]);
|
||||
}
|
||||
|
||||
void pop_char_fore(uint8_t who) __banked
|
||||
|
||||
Reference in New Issue
Block a user