SprPoP: кадр катсцены — блоками по кадровому интервалу, а не «отрисовка плюс пять»
Катсцены шли ~8,2 fps вместо десяти. Делитель тут ни при чём: у оригинала cutscene_frame_time = 6 тиков по 1/60 с (reset_cutscene, seg001:527; его зовёт load_intro прямо перед сценой) = 100 мс, у нас 5 кадров луча по 50 Гц = те же 100 мс. Причина в том, ГДЕ отсчитывался интервал: сначала рисовали кадр целиком, и только потом ждали vsync и ещё четыре — то есть отрисовка ПРИБАВЛЯЛАСЬ к делителю. Полноэкранная gfx_copy_page стоит ~547 000 тактов = 1,27 кадра, отсюда 6+ кадров вместо 5 (замер PV-RENDER-BOUND: 49 тиков/с вместо 60). Теперь отрисовка разложена на блоки, каждый из которых заведомо влезает в кадровый интервал, и после каждого честно ждём vsync: фон верхняя половина -> vsync | фон нижняя половина -> vsync | актёры и декорации -> vsync (+ флип) | служебный блок (звук, подкачка трека) и добор до CUT_FRAME_VSYNC. Фон восстанавливаем только по картинке (200 строк с POP_YOFF), а не по всем 256: сверху и снизу чёрная рамка. Общий хелпер pv_restore_bg на все три цикла — cut_run (сцены 8/9/12 и финал), pre_room_animated (2_6/4/12 и time_expired) и intro_pv_draw_frame (сцена с Джафаром); последний теперь возвращает 3 кадра вместо 1 (13 вместо 11 со вспышкой), вызывающий их и так учитывал. Сверка делителей с SDLPoP: у всех сцен 6 тиков = 5 наших кадров; плавает только pv_scene (6 -> 8 -> 7, seg001:434/455) — это уже сделано кумулятивно через pv_seq_period + POP_T60, и минимальный бюджет (5 кадров) больше трёх съедаемых блоками. Замер в MAME пока НЕ сделан: до финальной сцены на отладочном старте LEVEL=14 не добраться (решётка перед комнатой 5 закрывается по таймеру, а Ctrl-комбинации через MCP-мост до игры не доходят). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -430,6 +430,44 @@ static int intro_pv_draw_base(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ВОССТАНОВЛЕНИЕ ФОНА ЗАДНЕЙ СТРАНИЦЫ — ДВУМЯ БЛОКАМИ ПО КАДРОВОМУ ИНТЕРВАЛУ.
|
||||
*
|
||||
* Персонажи и живой фон рисуются банком спрайтов и в ОЗУ-копию не оседают,
|
||||
* поэтому «стереть прошлый кадр» = скопировать чистый фон из теневой копии
|
||||
* соседней страницы. Раньше это была ОДНА копия на весь экран, и она стоила
|
||||
* ~547 000 тактов = 1,27 кадра луча (модель blit_cost_model). Хвост кадра
|
||||
* сцены ждал vsync ПОСЛЕ отрисовки, так что эти 1,27 кадра ПРИБАВЛЯЛИСЬ к
|
||||
* пяти интервалам делителя: сцена шла 6+ кадров вместо 5, то есть ~8,2 fps
|
||||
* вместо десяти (замер 49/60 тиков — docs/BUGS_OPEN.md PV-RENDER-BOUND).
|
||||
*
|
||||
* Лечение — не «удешевить», а РАЗЛОЖИТЬ по интервалам: каждый блок заведомо
|
||||
* влезает в кадр, и после него честно ждём vsync. Тогда отрисовка живёт
|
||||
* ВНУТРИ делителя, а не сверх него.
|
||||
* - копируем только картинку (200 строк с POP_YOFF), а не все 256:
|
||||
* сверху и снизу чёрная рамка, туда никто не рисует;
|
||||
* - двумя половинами по 100 строк: ~219 000 тактов каждая, чуть больше
|
||||
* половины кадра — запас двукратный.
|
||||
*
|
||||
* ТРАТИТ РОВНО ДВА кадровых интервала (по gfx_wait_vsync на блок) — это
|
||||
* учтено в раскладке кадра у всех трёх сцен (см. вызовы). */
|
||||
#define PV_BG_Y0 POP_YOFF
|
||||
#define PV_BG_H 200
|
||||
|
||||
static void pv_restore_bg(void)
|
||||
{
|
||||
gfx_rect_t r;
|
||||
|
||||
r.x = 0;
|
||||
r.w = GFX_WIDTH;
|
||||
r.h = PV_BG_H / 2;
|
||||
r.y = PV_BG_Y0;
|
||||
gfx_copy_page(&r, GFX_COPY_DIRECT);
|
||||
gfx_wait_vsync();
|
||||
r.y = PV_BG_Y0 + PV_BG_H / 2;
|
||||
gfx_copy_page(&r, GFX_COPY_DIRECT);
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
|
||||
static void pv_blit_floor(const atlas_t *at, uint8_t frame, int x, int floor)
|
||||
{
|
||||
const uint8_t *img;
|
||||
@@ -701,12 +739,10 @@ static uint8_t intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
uint8_t sand_frame, uint8_t flash,
|
||||
uint16_t tick)
|
||||
{
|
||||
gfx_rect_t full;
|
||||
uint8_t back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
|
||||
gfx_set_draw_page(back);
|
||||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||
pv_restore_bg(); /* блоки 1-2: фон, два интервала */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
/* y=116 в princess_room_torch — нижняя строка, не верх спрайта. */
|
||||
pv_blit_floor(torch, torch_left, 93, 116);
|
||||
@@ -730,7 +766,7 @@ static uint8_t intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
* его надо повторить ПОСЛЕ актёров, иначе Jaffar проходит перед
|
||||
* правой передней колонной. */
|
||||
pv_blit_floor(hourglass, 0, 240, 167);
|
||||
gfx_wait_vsync();
|
||||
gfx_wait_vsync(); /* блок 3: актёры и декорации */
|
||||
gfx_set_visible_page(back);
|
||||
if (flash) {
|
||||
uint8_t pulse;
|
||||
@@ -743,9 +779,9 @@ static uint8_t intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
pop_pal_flash_white(0);
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
return 11;
|
||||
return 13; /* три интервала кадра + десять на молнию */
|
||||
}
|
||||
return 1;
|
||||
return 3; /* сколько кадровых интервалов съели */
|
||||
}
|
||||
|
||||
/* Сцена pv_scene() SDLPoP: ожидание Princess с факелами/звёздами, поворот,
|
||||
@@ -995,7 +1031,6 @@ static uint8_t pre_hourglass_state(void)
|
||||
static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
|
||||
{
|
||||
atlas_t hourglass, torch;
|
||||
gfx_rect_t full;
|
||||
uint8_t torch_left = 1, torch_right = 6, sand = 0, frame;
|
||||
uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 };
|
||||
intro_skip_t skip;
|
||||
@@ -1025,7 +1060,6 @@ static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
|
||||
|
||||
pop_screen_present(1);
|
||||
result = 0;
|
||||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||||
intro_skip_begin(&skip);
|
||||
for (frame = 0; frame < frames; frame++) {
|
||||
uint8_t back, hold, star;
|
||||
@@ -1033,7 +1067,7 @@ static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
|
||||
if (intro_skip_requested(&skip)) { result = 1; break; }
|
||||
back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
gfx_set_draw_page(back);
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||
pv_restore_bg(); /* блоки 1-2: фон, два интервала */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pv_blit_floor(&torch, torch_left, 93, 116);
|
||||
pv_blit_floor(&torch, torch_right, 211, 116);
|
||||
@@ -1046,10 +1080,11 @@ static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
|
||||
pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164);
|
||||
}
|
||||
pv_blit_floor(&hourglass, 0, 240, 167); /* передняя колонна */
|
||||
gfx_wait_vsync();
|
||||
gfx_wait_vsync(); /* блок 3: живой фон */
|
||||
gfx_set_visible_page(back);
|
||||
/* Первый тик уже потрачен на commit: ещё четыре = 5/50 с. */
|
||||
for (hold = 1; hold < 5; hold++) gfx_wait_vsync();
|
||||
/* Три интервала уже потрачены на блоки отрисовки — добираем до
|
||||
* пяти (5/50 с = кадр катсцены оригинала). */
|
||||
for (hold = 3; hold < 5; hold++) gfx_wait_vsync();
|
||||
|
||||
/* Проявляем ПОСЛЕ первого кадра: иначе две секунды fade идут по
|
||||
* пустой комнате, а часы и пламя возникают уже на свету. */
|
||||
@@ -1128,8 +1163,14 @@ static void intro_restore_game_palette(void)
|
||||
/* Набор PV2 (res901..930) не влезает в одну EMM: image 0..16 в a1,
|
||||
* 17..29 в a2. Номер кадра при этом остаётся исходным image-id. */
|
||||
#define CUT_PV2_SPLIT 17
|
||||
/* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен
|
||||
* (cutscene_frame_time SDLPoP; см. pre_room_animated). */
|
||||
/* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен: у
|
||||
* оригинала `cutscene_frame_time = 6` тиков по 1/60 с (reset_cutscene,
|
||||
* seg001:527, и его зовёт load_intro прямо перед сценой) = 100 мс, у нас
|
||||
* 5 кадров луча по 50 Гц = те же 100 мс.
|
||||
*
|
||||
* РАСКЛАДКА ПО ИНТЕРВАЛАМ (иначе отрисовка прибавлялась к делителю, а не
|
||||
* жила внутри него — см. pv_restore_bg): фон половинами 1-2, актёры и
|
||||
* декорации 3, служебный блок (звук/подкачка музыки) 4-5. */
|
||||
#define CUT_FRAME_VSYNC 5
|
||||
|
||||
static atlas_t cut_pv1, cut_pv2a, cut_pv2b, cut_hg, cut_torch;
|
||||
@@ -1290,11 +1331,9 @@ static void cut_shad_init(uint8_t x, uint8_t y, uint8_t seq)
|
||||
* прервал сцену (любое НОВОЕ нажатие). */
|
||||
static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||||
{
|
||||
gfx_rect_t full;
|
||||
uint16_t i;
|
||||
uint8_t hold;
|
||||
|
||||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||||
for (i = 0; i < frames; i++) {
|
||||
uint8_t back;
|
||||
kframe kid_fr, pr_fr;
|
||||
@@ -1316,8 +1355,9 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||||
back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
gfx_set_draw_page(back);
|
||||
/* Чистый фон обеих страниц лежит в shadow-копии; персонажи рисуются
|
||||
* банком спрайтов и потому в неё не оседают. */
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||
* банком спрайтов и потому в неё не оседают. Двумя блоками по
|
||||
* кадровому интервалу — см. pv_restore_bg. */
|
||||
pv_restore_bg(); /* блоки 1-2 */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pv_blit_floor(&cut_torch, cut_torch_l, 93, 116);
|
||||
pv_blit_floor(&cut_torch, cut_torch_r, 211, 116);
|
||||
@@ -1339,12 +1379,17 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||||
* сквозь неё — поймано пользователем 2026-08-25). */
|
||||
pv_blit_floor(&cut_hg, 0, 240, 167);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
gfx_wait_vsync();
|
||||
gfx_wait_vsync(); /* блок 3: актёры и декорации */
|
||||
gfx_set_visible_page(back);
|
||||
for (hold = 1; hold < CUT_FRAME_VSYNC; hold++) gfx_wait_vsync();
|
||||
|
||||
/* Блок 4 — служебный, и он НЕ добавляется к кадру, а живёт внутри
|
||||
* оставшихся интервалов: подкачка страницы трека стоит ~33 мс, то
|
||||
* есть меньше двух интервалов, которые всё равно надо переждать.
|
||||
* Порядок как в сцене с Джафаром: шаг подкачки — безусловный (см.
|
||||
* memory pv_music_stall_regression). */
|
||||
pop_sfx_tick();
|
||||
pop_music_service(); /* «встреча с принцессой» — трек, а не эффект */
|
||||
for (hold = 3; hold < CUT_FRAME_VSYNC; hold++) gfx_wait_vsync();
|
||||
cut_torch_l = (uint8_t)((cut_torch_l + 1) % 9);
|
||||
cut_torch_r = (uint8_t)((cut_torch_r + 1) % 9);
|
||||
cut_sand = (uint8_t)((cut_sand + 1) % 3);
|
||||
|
||||
Reference in New Issue
Block a user