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:
@@ -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 = dby−62). Раньше 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).
|
||||
|
||||
Reference in New Issue
Block a user