PoP: факелы и зелья (chtab_1) + быстрый блит фона gfx_blit_noclip

Графика chtab_1 (flame/sword/potion) — новый атлас pop_pot.atl:
- склянка зелья берётся из chtab_1 (seg008 draw_tile_fore: id 12 малая /
  13 большая при типах 2..4), а НЕ chtab_6 id 12 — res212 в VDUNGEON нет,
  каскад падал на VPALACE и рисовал кусок дворцовой декорации («мусорный
  объект» в комнате 5);
- пакуем chtab_1 целиком (id 1..23): пламя, склянки, кадры пузырька;
- MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из
  ОБЩЕЙ 16-цветной палитры экрана — она добавлена в 0x30..0x3F, палитра
  chtab_1 в 0x40..0x4F; пузырёк пакуется силуэтом цвета 12 (красный),
  его маска — цвета 0;
- в env-атлас добавлено основание факела (env 146, seg008:489 — рисует
  правый сосед); в статический render_room оно не попадало.

Анимация (seg007 animate_torch/animate_potion + seg000:0B12
anim_tile_modif): при входе в комнату факелам и зельям задаётся случайная
стартовая фаза и заводится trob; факел — get_torch_frame, пламя в ячейке
ПРАВОГО соседа (xh = draw_xh+1, y = draw_main_y−40); зелье —
bubble_next_frame по младшим 3 битам модификатора (старшие 5 = тип, при
загрузке уровня modif <<= 3, seg009).  Пламя и пузырёк не запекаются в
фон: heal своей области + кадр поверх.  Скорость пламени /2 — наш
логический кадр короче игрового тика оригинала.

Скорость отрисовки (профиль в MAME, кадр = 430 080 тактов):
- замер показал, что цена блита почти НЕ зависит от размера — 13 288
  тактов на спрайт 32×3 против 4 617 у линейного спрайтового ядра без
  клипа; платим за проход gfx_blit → gfx_blit_part → _gfx_blit_full;
- в libbgi добавлен gfx_blit_noclip() — блит без клипа в ТЕКУЩЕМ банке
  (putsprite не годится: навязывает GFX_BANK_SPRITE, фону нужен
  TRANSPARENT ради ОЗУ-копии); pop_bg.blit_b уходит на него, когда
  спрайт целиком на экране и не нужен g_clip_top → ~2.9× на блит;
- W3-скобку ставит САМА libbgi: из модуля с --w3 её вызывать нельзя —
  после _bgi_begin окно W3 занято видеобанком и код вызывающего исчезает
  (проявлялось белым экраном);
- редрой шва больше не перерисовывает полосу потолка (bar начинается с
  POP_YOFF+3) — это удваивало стоимость блока при анимации решётки;
- docs/size_optimization_plan.md §7–§8: замеры, сделанное и запас
  (батчинг W3-скобки, решётка одним спрайтом, лишние блиты).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 20:53:04 +03:00
parent 63a3b60524
commit c127a4b0d2
16 changed files with 377 additions and 16 deletions
+104 -10
View File
@@ -21,6 +21,7 @@
static atlas_t env[5];
static atlas_t wall_at;
static atlas_t fore_at;
static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */
static const char *const ENV_ATL[5] = {
"pop_env0.atl", "pop_env1.atl", "pop_env2.atl",
@@ -34,6 +35,7 @@ int pop_bg_load(void)
if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1;
if (atlas_load(&wall_at, "pop_wall.atl") != 0) return -1;
if (atlas_load(&fore_at, "pop_fore.atl") != 0) return -1;
if (atlas_load(&pot_at, "pop_pot.atl") != 0) return -1;
return 0;
}
@@ -44,6 +46,7 @@ void pop_bg_free(void)
atlas_free(&env[i]);
atlas_free(&wall_at);
atlas_free(&fore_at);
atlas_free(&pot_at);
}
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра с screen_y <
@@ -72,8 +75,16 @@ static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
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);
} else {
gfx_blit(x, top, img);
gfx_blit(x, top, img); /* край экрана — с клипом */
}
}
gfx_w0_unmap();
@@ -115,6 +126,9 @@ void pop_fore_heal(void)
static void env_b(uint8_t id, int x, int yb) { blit_b(&env[id >> 5], (uint8_t)(id & 31), x, yb); }
static void wall_b(uint8_t id, int x, int yb) { blit_b(&wall_at, id, x, yb); }
static void fore_b(uint8_t id, int x, int yb) { blit_b(&fore_at, id, x, yb); }
static void pot_b(uint8_t id, int x, int yb) { blit_b(&pot_at, id, x, yb); }
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
typedef struct {
@@ -180,6 +194,40 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
#define RES_WALL_MARK_TR 16
#define RES_WALL_MARK_BR 17
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
* склянка (id 13) и pot_size=1, остальные — маленькая (id 12). Спрайты — из
* chtab_1 (атлас pop_pot), поэтому НЕ из env: раньше мы рисовали chtab_6 id 12
* (res212), которого в VDUNGEON нет — оттуда и «мусорный объект».
* Позиции оригинала: склянка xh = fore_x + draw_xh, xl = 6, ybottom = fore_y +
* draw_main_y; пузырёк/маска xh = draw_xh + 3, xl = 1,
* ybottom = draw_main_y (pot_size << 2) 14. */
#define POT_MASK_ID 23
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
static void potion_flask(int row, int col, uint8_t modif) /* draw_tile_fore */
{
uint8_t type = potion_type(modif);
uint8_t id = 12;
if (type == 0) return;
if (type >= 2 && type < 5) id = 13; /* большая склянка */
pot_b(id, (tile_table[10].fore_x + COL_XH[col]) * 8 + 6,
tile_table[10].fore_y + 63 * row + 62);
}
static void potion_bubble(int row, int col, uint8_t modif, uint8_t fore)
{
uint8_t type = potion_type(modif);
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
int x = (COL_XH[col] + 3) * 8 + 1;
int yb = 63 * row + 62 - (pot_size << 2) - 14;
if (type == 0) return;
if (fore) pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
else pot_b(POT_MASK_ID, x, yb); /* маска (под ним) */
}
/* ---- Текущая комната (globals для tile_code/mod) ------------------- */
static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg;
static const uint8_t *g_below; /* row0-коды комнаты СНИЗУ (для topright под нижним рядом) */
@@ -435,6 +483,9 @@ static void draw_tile(int row, int col)
} else {
if (lt->right_id)
env_b(lt->right_id, x, lt->right_y + dmy);
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
if (lcode == 19 || lcode == 30)
env_b(146, x, dby - 28);
/* stripe — только палас, dungeon пропускает */
}
}
@@ -452,6 +503,9 @@ static void draw_tile(int row, int col)
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
}
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
}
/* --- draw_tile_bottom (seg008.c:570) --- */
@@ -497,8 +551,12 @@ static void draw_tile(int row, int col)
wall_b(WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
wall_pattern(row, col, 1);
}
if (t->fore_id)
if (code == 10) { /* зелье: chtab_1, не env fore_id.
* Пузырёк — динамика (pop_potion_draw). */
potion_flask(row, col, tile_mod(row, col));
} else if (t->fore_id) {
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
}
}
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
@@ -565,15 +623,13 @@ void pop_room_redraw_seam_left(void)
{
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(0, POP_YOFF, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
* а бары ворот начинаются с y=3 (gate_top_y = dby62). Раньше bar шёл от
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
/* Bar стёр и полосу кладки У ПОТОЛКА (ряд −1 = ряд 2 комнаты сверху,
* draw_tile_aboveroom): без её перерисовки потолок над решёткой пропадал
* после первой же анимации ворот. Клип — как в pop_room_draw. */
g_clip_top = POP_YOFF;
draw_tile(-1, 0);
g_clip_top = 0;
gfx_set_bank(GFX_BANK_NORMAL);
}
@@ -611,6 +667,41 @@ void pop_ceil_bake_empty(int col)
gfx_set_bank(GFX_BANK_NORMAL);
}
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
* draw_main_y 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y
* (pot_size<<2) 14 (seg008:668). */
void pop_torch_draw(int row, int col, uint8_t modif)
{
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
* 146), которое тоже лежит в ячейке соседа. */
int x = (col + 1 < 10 ? COL_XH[col + 1] : COL_XH[col] + 4) * 8 + 8;
int yb = 63 * row + 62 - 40;
if (modif >= 9) return;
heal_off(x - 2, yb - 19, 22, 22);
gfx_set_bank(GFX_BANK_SPRITE);
pot_b((uint8_t)(modif + 1), x, yb);
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_potion_draw(int row, int col, uint8_t modif)
{
uint8_t type = potion_type(modif);
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
int x = (COL_XH[col] + 3) * 8 + 1;
int yb = 63 * row + 62 - (pot_size << 2) - 14;
if (type == 0) return;
heal_off(x - 1, yb - 10, 8, 12);
gfx_set_bank(GFX_BANK_SPRITE);
pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
@@ -917,8 +1008,11 @@ static void fore_only_tile(int row, int col)
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
}
if (t->fore_id)
if (code == 10) { /* зелье: склянка (пузырёк — динамика) */
potion_flask(row, col, tile_mod(row, col));
} else if (t->fore_id) {
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
}
}
/* Полоса кладки У ПОТОЛКА поверх Kid — порт redraw_needed_above (seg008:0221).