Files
Sprinter-SDCC/applications/PoP/roomtest/pop_kdraw.c
T
snark13 5b6664ffce Зеркальные кадры персонажей — готовыми файлами, а не разворотом в рантайме
Замеры в MAME (такты Z80, кадр = 430 000) решили вопрос однозначно:
  загрузка 28 атласов Кида с HDD  — 27 160 092 такта (1.26 с), 970 003 на страницу;
  разворот ОДНОЙ страницы на месте — 12 645 582 такта (0.59 с), т.е. 771 такт/байт.
Даже идеальный вариант (копия accel'ом ~0.25 кадра + asm-реверс ~2.2 кадра)
дал бы ~1.05 млн на страницу — то есть в лучшем случае сравнялся бы с чтением
готового файла.  На 34 страницы: 1.5 с загрузки против 20 с разворота.

* Упаковщики пишут второй набор: vflip_cols/vflip_page в pop_pack_kid.py ->
  kid0_v.atl…kid27_v.atl, sword_v.atl; pop_pack_guard.py -> g0_v.atl…g4_v.atl
  ТОЛЬКО для обычного стража (на уровне 9 tbl_guard_type = 0; тень рисуется
  атласами Кида, скелет/визирь с переворотом не встречаются).
* pop_vflip.c схлопнулся до таблицы «страница -> зеркальная пара»; весь
  побайтовый разворот и аллокация EMM удалены.
* pop_vflip_load_all (банк 8) грузит набор ОДНИМ вызовом и откуда угодно —
  задел под загрузку ресурсов во время интро.  Зовётся при старте и при
  смене уровня, если это POP_UPSIDE_LEVEL; чит U на других уровнях поднимает
  набор лениво (страховка в главном цикле).
* Стартовый уровень стал параметром сборки: make LEVEL=N (-DFIRST_LEVEL).

Проверено в MAME на уровне 9: переворот мгновенный, pop_flip_screen = 897 263
такта (2.1 кадра) — это сама копия экрана, загрузки в кадре больше нет.
tests-host 6/6.
2026-08-12 23:17:22 +03:00

141 lines
7.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_kdraw.c — отрисовка ИЗ АТЛАСОВ КИДА, которая нужна только слою
* персонажей: клинок в руке (add_sword_to_objtable, seg006:1798) и блит
* спрайта chtab_2 по его id (полоса HP). БАНК 4, вместе с pop_cdraw.c.
*
* Почему здесь. Оба вызова приходят РОВНО из pop_cdraw.c и больше ниоткуда
* (проверено грепом), а лежали они в pop_kid.c — то есть в резиденте W1,
* съедая ~1.2 КБ дефицитного окна ради кода, который зовёт сосед по банку.
* Раз модуль в том же банке 4 — вызовы прямые, без трамплина: объявления в
* _pop_kdraw.h НЕ помечены __banked (см. там же условие корректности).
*
* Загрузка атласа меча (pop_sword_load) осталась публичной и __banked: её
* зовёт инициализация, один раз за игру.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_kid.h"
#include "_pop_kdraw.h"
#include "pop_guard.h" /* CHARID_* — клинок есть и у стража */
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
#include "_pop_draw.h" /* pop_onscreen_cols */
#include "pop_map.h" /* pop_upside — зелье инверсии */
#include "pop_vflip.h" /* зеркальные страницы атласов */
#include "kid_data.h"
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок —
* ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража.
* Таблица sword_tbl (kid_data.h, генерится из seg006) индексируется
* sword_frame = кадр.sword & 0x3F; id 255 = клинка в этом кадре нет.
* Смещения — в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ
* calc_screen_x_coord, x — «вперёд» по направлению персонажа. */
static const uint8_t SWORD_ID[KID_NSWORD] = KID_SWORD_ID_INIT;
static const int8_t SWORD_DX[KID_NSWORD] = KID_SWORD_DX_INIT;
static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT;
static atlas_t swordp;
static uint8_t sword_ok;
int pop_sword_load(void) __banked
{
if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
sword_ok = 1;
return 0;
}
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_load. */
int pop_sword_vflip_load(void) __banked
{
if (!sword_ok) return 0;
return pop_vflip_add(&swordp, "KID\\sword_v.atl");
}
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
* кадрах подъёма/ножен, ИЛИ держит меч, ИЛИ это ЖИВОЙ страж (у стража меч
* в руке всегда, даже пока sword ещё sheathed). Страницу W0 оставляем
* замапленной — вызывающий делает gfx_w0_unmap сам. */
uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
int obj_x, int obj_y,
int *rx, int *ry, uint16_t *rw, uint16_t *rh)
{
uint8_t sf, id, flip;
const uint8_t *simg;
uint16_t w, h;
int bx, top;
if (!sword_ok) return 0;
if (!((ch->frame >= 229 && ch->frame < 238) ||
ch->sword != 0 ||
((ch->charid == CHARID_2_GUARD || ch->charid == CHARID_4_SKELETON) &&
ch->alive < 0)))
return 0;
sf = (uint8_t)(fr->sword & 0x3F);
if (sf == 0 || sf >= KID_NSWORD) return 0;
id = SWORD_ID[sf];
if (id == 0xFF) return 0;
simg = (const uint8_t *)atlas_image(&swordp, id);
{ /* ПЕРЕВОРОТ: зеркальная копия страницы меча (см. pop_vflip.h) */
uint8_t vpg = swordp.page;
if (pop_upside) {
uint8_t f = pop_vflip_page(vpg);
if (f) vpg = f;
}
gfx_w0_map(vpg);
}
w = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
h = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
if (!w || !h) return 0;
flip = (uint8_t)(ch->direction >= 0);
bx = obj_x + ((ch->direction < 0) ? -(int)SWORD_DX[sf] : (int)SWORD_DX[sf]);
top = obj_y + (int)SWORD_DY[sf] - (int)h + 1;
/* Та же формула, что у самого персонажа (pop_cdraw.c): верх кадра
* [top, top+h) уезжает в 192 - top - h. */
if (pop_upside) top = POP_PLAYFIELD_H - top - (int)h;
if (flip) bx -= (int)w;
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507): клинок
* обрезается по нему так же, как сам персонаж. Без этого у падающего
* в пропасть скелета меч оставался виден НИЖЕ поля, на полосе HP. */
{
int skip = 0, rows = (int)h;
if (top < 0) { skip = -top; rows -= skip; top = 0; }
if (top + rows > 192) rows = 192 - top;
if (rows <= 0) return 0;
gfx_set_bank(GFX_BANK_SPRITE);
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip,
(uint8_t)skip, (uint8_t)rows);
else
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, skip, rows);
gfx_set_bank(GFX_BANK_NORMAL);
*rx = bx; *ry = top; *rw = w; *rh = (uint16_t)rows;
}
return 1;
}
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
* Кида, а держать вторую копию раскладки в pop_cdraw ни к чему. */
void pop_kid_img_blit(uint8_t image, int x, int y)
{
uint8_t pg = (uint8_t)(image >> 3); /* раскладка атласов: 8 спрайтов/стр. */
uint8_t idx = (uint8_t)(image & 7);
const uint8_t *img;
uint16_t w, h;
if (pg >= kid_npages) return;
img = (const uint8_t *)atlas_image(&kidp[pg], idx);
gfx_w0_map(kidp[pg].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
/* Атласы Кида хранятся COLUMN-MAJOR (transpose_cols в pop_pack_kid —
* ради бесплатного горизонтального флипа), поэтому блит обязан быть
* колоночным. Row-major gfx_blit_noclip выдал бы марку транспонированной.
* noclip БЕЗ проверки (CLIP-1): единственный вызывающий — полоса HP
* (pop_cdraw.c), у неё координаты фиксированные и заведомо на экране. */
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
gfx_w0_unmap();
}