diff --git a/applications/SprPoP/src/pop_intro.c b/applications/SprPoP/src/pop_intro.c index 834fb35..f53822b 100644 --- a/applications/SprPoP/src/pop_intro.c +++ b/applications/SprPoP/src/pop_intro.c @@ -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);