diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 355b8344..d11d9d41 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -44,6 +44,10 @@ uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y * trailing margin without shrinking the CRTC-derived width globally. */ uint32_t gpu_depth24_rgb_limit(uint32_t display_x, uint32_t crtc_w); void gpu_depth24_upload_span_reset(void); +/* MotK intro cuts retarget GP1(07h). While hold > 0, present should skip + * Swap (keep prior frame) so stale trailing VRAM never flashes. One tick + * per vblank; returns non-zero while the hold is still active after tick. */ +int gpu_depth24_present_hold_tick(void); /* GP1(06h)/GP1(07h)/GP1(08h) fields for debug (gpu_state). */ void gpu_get_crtc_debug(uint32_t *x1, uint32_t *x2, uint32_t *y1, uint32_t *y2, uint32_t *hres1_out, uint32_t *hres2_out); diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 4398753d..8f3b0a4f 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -1655,6 +1655,8 @@ static uint16_t vram_write_pixels[1024 * 512]; /* Depth24 CPU→VRAM upload span (halfwords, exclusive end). See * gpu_depth24_rgb_limit — declared early so gpu_reset_state can clear it. */ static uint32_t s_d24_upload_x1 = 0; +static int s_d24_present_hold = 0; /* vblanks to skip Swap after GP1(07h) */ +static uint32_t s_d24_prev_disp_h = 0; /* last GP1(07h) band height */ static void depth24_note_upload(uint32_t x, uint32_t w); static void gp0_commit_cpu_to_vram(void) { @@ -1892,6 +1894,8 @@ static void gpu_reset_state(int clear_vram) { s_ws_fmv_frame_cache = 0xFFFFFFFFu; s_ws_fmv_cached = 0; s_d24_upload_x1 = 0; + s_d24_present_hold = 0; + s_d24_prev_disp_h = 0; } void gpu_init(void) { @@ -2070,9 +2074,12 @@ static uint8_t gpu_vram_byte(uint32_t byte_x, uint32_t y) { /* Depth24: note/query/reset the CPU→VRAM upload span tracked above. Used to * hide trailing RGB columns when a movie blit doesn't fill the full CRTC - * width — MotK's Star Wars crawl leaves ~8px of stale VRAM on the right. */ + * width — MotK's Star Wars crawl leaves ~8px of stale VRAM on the right. + * Only FB-class A0s (w >= 256 halfwords) grow the span; texture uploads must + * not collapse it. During present-hold, ignore updates entirely. */ static void depth24_note_upload(uint32_t x, uint32_t w) { - if (!(display_depth & 1u) || w == 0u) return; + if (!(display_depth & 1u) || w < 256u) return; + if (s_d24_present_hold > 0) return; uint32_t x1 = x + w; if (x1 > 1024u) x1 = 1024u; if (x1 > s_d24_upload_x1) s_d24_upload_x1 = x1; @@ -2093,6 +2100,12 @@ void gpu_depth24_upload_span_reset(void) { s_d24_upload_x1 = 0; } +int gpu_depth24_present_hold_tick(void) { + if (s_d24_present_hold <= 0) return 0; + s_d24_present_hold--; + return 1; +} + /* ---- Present-time screen-colour LUT (verified-enhancement, opt-in) ------- * * PRESENT-TIME ONLY. This sits on the 15-bit-scanout -> RGB888 conversion that @@ -4192,8 +4205,19 @@ static void gp1_v_display_range(uint32_t val) { /* GP1(07h): Vertical display range * bits 0-9: Y1 * bits 10-19: Y2 */ - v_display_y1 = val & 0x3FF; - v_display_y2 = (val >> 10) & 0x3FF; + uint32_t y1 = val & 0x3FF; + uint32_t y2 = (val >> 10) & 0x3FF; + uint32_t h = (y2 > y1) ? (y2 - y1) : 0u; + /* MotK intro→crawl retargets the band while staying in depth24. Stale + * trailing RGB from the prior movie would flash for a frame or two — + * reset the upload span and hold present (skip Swap) for 3 vblanks. */ + if ((display_depth & 1u) && s_d24_prev_disp_h != 0u && h != s_d24_prev_disp_h) { + s_d24_upload_x1 = 0; + s_d24_present_hold = 3; + } + v_display_y1 = y1; + v_display_y2 = y2; + if (h != 0u) s_d24_prev_disp_h = h; } static void gp1_display_mode(uint32_t val) { diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 6778f30d..709530b7 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -2998,36 +2998,23 @@ static void load_transition_note(int read_active, int load_active, prev_turbo = turbo_active; } -/* Depth24 FMV: last ~8 RGB columns can be stale/chroma junk while CRTC width - * stays full (e.g. MotK 512). Present width is never shrunk — cropping the - * GL/SDL rect left a flickering black pillar when upload coverage varied. +/* Depth24 FMV: trailing RGB columns can be stale while CRTC width stays full + * (MotK 512). Present width is never shrunk — cropping the GL/SDL rect left a + * flickering black pillar when upload coverage varied. * - * Do NOT replicate the last good column: on MotK's starfield intro that turns - * a single tinted edge pixel into an 8-wide horizontal streak (flickering - * stretch into the pillar). Black-fill the margin instead. Require a dense - * chroma signal so sparse stars never trip the repair. */ -static void depth24_fix_trailing_margin(uint32_t *buf, uint32_t w, uint32_t h) { - if (!buf || w < 24u || h == 0u) return; - const uint32_t margin = 8u; - const uint32_t edge = w - margin; - const uint32_t total = margin * h; - uint32_t junk_px = 0; - for (uint32_t x = edge; x < w; x++) { - for (uint32_t y = 0; y < h; y++) { - uint32_t p = buf[y * w + x]; - int r = (int)((p >> 16) & 255u); - int g = (int)((p >> 8) & 255u); - int b = (int)(p & 255u); - int m = (r + g + b) / 3; - int ch = (r > m ? r - m : m - r) + (g > m ? g - m : m - g) + - (b > m ? b - m : m - b); - if (ch > 40) junk_px++; - } - } - /* ~12% of margin texels — sparse stars stay; dense chroma fringe cleans. */ - if (total == 0u || junk_px * 100u < total * 12u) return; + * Always black-fill the last 8 RGB cols. Also blank beyond the tracked FB A0 + * span when it falls short of the CRTC (movie cut / partial cover). Do NOT + * replicate the last good column — that smeared MotK's starfield into an + * 8-wide streak. */ +static void depth24_fix_trailing_margin(uint32_t *buf, uint32_t w, uint32_t h, + uint32_t display_x) { + if (!buf || w < 8u || h == 0u) return; + uint32_t start = w - 8u; + uint32_t lim = gpu_depth24_rgb_limit(display_x, w); + if (lim > 0u && lim < w && lim < start) + start = lim; for (uint32_t y = 0; y < h; y++) { - for (uint32_t x = edge; x < w; x++) + for (uint32_t x = start; x < w; x++) buf[y * w + x] = 0xFF000000u; } } @@ -3437,6 +3424,11 @@ static void sdl_vblank_present(void) { /* Mod hooks. Run after all normal input sampling. */ mod_call_frame_hooks(); + /* Depth24 GP1(07h) retarget (MotK intro→crawl): keep the prior Swap for a + * few vblanks so stale trailing VRAM never flashes on the right edge. */ + if (gpu_depth24_present_hold_tick()) + return; + /* Engage widescreen at game entry: BIOS boot stays authentic 4:3. */ if (!g_ws_engaged) { extern int fntrace_is_game_started(void); @@ -3576,9 +3568,10 @@ static void sdl_vblank_present(void) { for (uint32_t x = 0; x < present_w; x++) sdl_pixel_buf[y * present_w + x] = gpu_display_pixel_argb(&di, x, y); - /* Trailing margin: replicate last good column into junk cols - * inside the full-width buffer — never shrink present width. */ - depth24_fix_trailing_margin(sdl_pixel_buf, present_w, h); + /* Trailing margin: blank junk cols inside the full-width + * buffer — never shrink present width. */ + depth24_fix_trailing_margin(sdl_pixel_buf, present_w, h, + di.display_x); vk_renderer_present_cpu(sdl_pixel_buf, (int)present_w, (int)h, 0 /* nearest */, fmv_frame ? 1 : 0); } else if (wide_present && @@ -3640,11 +3633,12 @@ static void sdl_vblank_present(void) { } } - /* Depth24 trailing margin: MotK CRTC is 512 RGB but the last ~8 cols + /* Depth24 trailing margin: MotK CRTC is 512 RGB but trailing cols * can be stale. Fix pixels in-place at full present_w — never crop the * GL/SDL draw width (that caused a flickering black pillar). */ if (di.depth24 && active_scale == 1 && !wide_present) - depth24_fix_trailing_margin(sdl_pixel_buf, present_w, h); + depth24_fix_trailing_margin(sdl_pixel_buf, present_w, h, + di.display_x); smooth_60_present(sdl_pixel_buf, present_w * (uint32_t)active_scale,