diff --git a/support/zaparoo/alt_launcher.cpp b/support/zaparoo/alt_launcher.cpp index cac79c714..352537246 100644 --- a/support/zaparoo/alt_launcher.cpp +++ b/support/zaparoo/alt_launcher.cpp @@ -26,6 +26,10 @@ #include "user_io.h" #include "video.h" +// video_reinit() is intentionally internal to upstream's video module. The +// launcher uses it only for a bounded startup retry after video_init(). +extern void video_reinit(); + static const char s_launcher_path[] = "zaparoo/frontend"; void alt_launcher_cfg_apply(void) @@ -94,6 +98,9 @@ static pid_t s_pid = 0; static int s_crash_count = 0; static unsigned long s_respawn_timer = 0; static unsigned long s_native_fb_mode_timer = 0; +static unsigned long s_hdmi_fb_reassert_timer = 0; +static int s_hdmi_fb_reasserts_remaining = 0; +static unsigned long s_hdmi_edid_retry_timer = 0; static unsigned long s_tty_deadline = 0; static unsigned long s_native_crt_finish_timer = 0; static bool s_gave_up = false; @@ -381,6 +388,9 @@ static void return_to_normal_mode(void) else video_fb_enable(0); s_native_crt = false; s_respawn_timer = 0; + s_hdmi_fb_reassert_timer = 0; + s_hdmi_fb_reasserts_remaining = 0; + s_hdmi_edid_retry_timer = 0; s_crash_count = 0; s_gave_up = true; s_escaped = true; @@ -392,6 +402,9 @@ static void reset_launcher_state(void) s_respawn_timer = 0; s_tty_deadline = 0; s_native_fb_mode_timer = 0; + s_hdmi_fb_reassert_timer = 0; + s_hdmi_fb_reasserts_remaining = 0; + s_hdmi_edid_retry_timer = 0; s_native_crt_finish_timer = 0; s_crash_count = 0; s_gave_up = false; @@ -437,6 +450,8 @@ static void wait_launcher_stopped(pid_t pid) static void release_launcher_video(void) { + s_hdmi_fb_reassert_timer = 0; + s_hdmi_fb_reasserts_remaining = 0; if (s_native_crt) { s_native_crt = false; @@ -519,7 +534,18 @@ static void finalize_spawn(void) s_tty_deadline = 0; switch_to_vt(s_vt); if (!s_native_crt) - video_fb_enable(1); + { + // The frontend configures /dev/fb0 before Qt starts. Only publish its + // existing buffer here; rewriting the mode after Qt has painted clears + // the frame and leaves black until each region becomes dirty again. + video_fb_reassert(); + // Returning to the menu can keep settling its video mode after the + // frontend is already ready. Re-assert across that init window so a + // late reset cannot leave a live framebuffer behind menu static. + s_hdmi_fb_reasserts_remaining = 5; + s_hdmi_fb_reassert_timer = GetTimer(1000); + if (!s_hdmi_fb_reassert_timer) s_hdmi_fb_reassert_timer = 1; + } else input_switch(0); @@ -548,6 +574,12 @@ static void spawn(void) } else { + // Accept the child's pre-Qt vmode request deterministically. Enabling only + // from finalize_spawn races the child: video_cmd ignores fb_cmd while the + // HPS framebuffer is disabled. Re-assert without scheduling the launcher's + // current geometry back into the kernel; that delayed write can otherwise + // overwrite the child's smaller framebuffer after Qt has mapped it. + video_fb_reassert(); printf("alt_launcher: HPS framebuffer path enabled\n"); } @@ -585,7 +617,7 @@ bool alt_launcher_console_lease_active(void) bool alt_launcher_scheduler_sleep_enabled(void) { - return s_pid || s_init_pending || s_respawn_timer || s_tty_deadline || s_native_crt_finish_timer || s_native_fb_mode_timer; + return s_pid || s_init_pending || s_respawn_timer || s_tty_deadline || s_native_crt_finish_timer || s_native_fb_mode_timer || s_hdmi_fb_reassert_timer || s_hdmi_edid_retry_timer; } bool alt_launcher_native_crt(void) @@ -601,21 +633,15 @@ void alt_launcher_init(bool native_crt) s_respawn_timer = 0; s_tty_deadline = 0; s_native_crt = native_crt; + // user_io can request the launcher before video_init() has read EDID and + // selected the HDMI mode. Defer the child until that setup returns. If the + // first EDID read missed a slow monitor, allow one bounded retry after the + // link has had another 500 ms to settle. + s_hdmi_edid_retry_timer = native_crt ? 0 : GetTimer(500); + if (!native_crt && !s_hdmi_edid_retry_timer) s_hdmi_edid_retry_timer = 1; s_init_pending = true; } -static void alt_launcher_start(bool native_crt) -{ - if (!alt_launcher_configured() || s_pid || s_gave_up) - return; - s_crash_count = 0; - s_respawn_timer = 0; - s_tty_deadline = 0; - s_native_crt = native_crt; - s_init_pending = false; - spawn(); -} - void alt_launcher_prepare_for_script(void) { reset_launcher_tty(); @@ -766,11 +792,30 @@ void alt_launcher_poll(void) s_native_fb_mode_timer = 0; } + if (!s_native_crt && s_hdmi_fb_reassert_timer && CheckTimer(s_hdmi_fb_reassert_timer)) + { + // Re-send scanout state without reconfiguring /dev/fb0 under Qt; + // resetting the live kernel framebuffer clears it until dirty repaint. + video_fb_reassert(); + if (--s_hdmi_fb_reasserts_remaining > 0) + { + s_hdmi_fb_reassert_timer = GetTimer(2000); + if (!s_hdmi_fb_reassert_timer) s_hdmi_fb_reassert_timer = 1; + } + else + { + s_hdmi_fb_reassert_timer = 0; + } + printf("alt_launcher: HPS framebuffer path re-asserted (%d remaining)\n", s_hdmi_fb_reasserts_remaining); + } + int status; if (waitpid(s_pid, &status, WNOHANG) == s_pid) { s_pid = 0; s_tty_deadline = 0; + s_hdmi_fb_reassert_timer = 0; + s_hdmi_fb_reasserts_remaining = 0; user_io_osd_key_enable(1); bool exited = WIFEXITED(status); int exit_status = exited ? WEXITSTATUS(status) : 0; @@ -832,6 +877,13 @@ void alt_launcher_poll(void) if (s_init_pending) { + if (!s_native_crt && video_get_edid(NULL, NULL) == 0) + { + if (s_hdmi_edid_retry_timer && !CheckTimer(s_hdmi_edid_retry_timer)) return; + printf("alt_launcher: retrying EDID before frontend spawn\n"); + video_reinit(); + } + s_hdmi_edid_retry_timer = 0; s_init_pending = false; spawn(); return; @@ -952,20 +1004,22 @@ void zaparoo_alt_launcher_init_for_core(void) } } -static void zaparoo_alt_launcher_prepare_menu_state(bool start) +static void zaparoo_alt_launcher_prepare_menu_state(void) { bool crt = load_persisted_native_crt(); printf("alt_launcher: initializing menu frontend (persisted crt=%d)\n", crt); - if (start) alt_launcher_start(crt); - else alt_launcher_init(crt); + alt_launcher_init(crt); } void zaparoo_alt_launcher_init_for_menu(void) { - zaparoo_alt_launcher_prepare_menu_state(false); + zaparoo_alt_launcher_prepare_menu_state(); } void zaparoo_alt_launcher_start_for_menu(void) { - zaparoo_alt_launcher_prepare_menu_state(true); + // This hook runs before video_init(). Queue startup instead of forking + // immediately so EDID-selected output geometry is final before the frontend + // asks Main to choose a full or half framebuffer. + zaparoo_alt_launcher_prepare_menu_state(); } diff --git a/video.cpp b/video.cpp index b525136d9..1fe23bce3 100644 --- a/video.cpp +++ b/video.cpp @@ -3420,7 +3420,7 @@ static void fb_write_module_params() }); } -void video_fb_enable(int enable, int n) +static void video_fb_set(int enable, int n, int update_module) { PROFILE_FUNCTION(); @@ -3462,7 +3462,7 @@ void video_fb_enable(int enable, int n) //printf("Linux frame buffer: %dx%d, stride = %d bytes\n", fb_width, fb_height, fb_width * 4); if (!fb_num) { - fb_write_module_params(); + if (update_module) fb_write_module_params(); input_switch(0); } else @@ -3491,6 +3491,36 @@ void video_fb_enable(int enable, int n) } } +void video_fb_enable(int enable, int n) +{ + video_fb_set(enable, n, 1); +} + +void video_fb_reassert() +{ + int fmt, rb, width, height, stride; + FILE *fp = fopen("/sys/module/MiSTer_fb/parameters/mode", "rt"); + int fields = fp ? fscanf(fp, "%d %d %d %d %d", &fmt, &rb, &width, &height, &stride) : 0; + if (fp) fclose(fp); + + if (fields == 5 && fmt == 8888 && rb == 1 && width > 0 && height > 0 && stride == width * 4) + { + // vmode may resize the launcher's live buffer after video_fb_config(). + // Re-assert the kernel module's current RGB32 geometry, not stale menu geometry. + int configured_width = fb_width; + int configured_height = fb_height; + fb_width = width; + fb_height = height; + video_fb_set(1, 0, 0); + fb_width = configured_width; + fb_height = configured_height; + } + else + { + video_fb_set(1, 0, 0); + } +} + int video_fb_state() { if (is_menu()) @@ -4136,8 +4166,12 @@ void video_cmd(char *cmd) { if (div >= 1 && div <= 4) { + // Pixel repetition doubles the physical output width while hact + // remains the logical source width. Apply the matching extra + // vertical divisor so the framebuffer keeps the output aspect. + int vdiv = div * (v_cur.param.pr ? 2 : 1); width = v_cur.item[1] / div; - height = v_cur.item[5] / div; + height = v_cur.item[5] / vdiv; hmin = vmin = 0; hmax = v_cur.item[1] - 1; vmax = v_cur.item[5] - 1; @@ -4149,8 +4183,9 @@ void video_cmd(char *cmd) { if (div >= 1 && div <= 4) { + int vdiv = div * (v_cur.param.pr ? 2 : 1); width = v_cur.item[1] / div; - height = v_cur.item[5] / div; + height = v_cur.item[5] / vdiv; hmin = vmin = 0; hmax = v_cur.item[1] - 1; vmax = v_cur.item[5] - 1; diff --git a/video.h b/video.h index 5ad6dfe16..ff2ca9ea4 100644 --- a/video.h +++ b/video.h @@ -65,6 +65,7 @@ void video_mode_adjust(bool force = false); int hasAPI1_5(); void video_fb_enable(int enable, int n = 0); +void video_fb_reassert(); int video_fb_state(); void video_menu_bg(int n, int idle = 0); int video_bg_has_picture();