From 1f02ca1855b6f351eaef31f5bfe222860f41f74f Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Tue, 18 Aug 2026 18:57:23 -0400 Subject: [PATCH] =?UTF-8?q?depth24:=20present=20never-written=20band=20row?= =?UTF-8?q?s=20as=20black=20=E2=80=94=20kills=20the=20pre-FMV=20splash=20f?= =?UTF-8?q?licker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ensure_cpu at depth24 entry made the letterbox bars TRUTHFUL, but the truth is leftover 15-bit screen bytes (the pre-movie SCEA splash), and a double-buffered movie holds that leftover at different rows in its two bands — so the bars alternate content every couple of frames. Track which rows have received a CPU->VRAM rect since the current depth24 session began (s_d24_row_written, cleared at every depth transition) and present unwritten rows as black in gpu_depth24_present_row. Savestate restore marks all rows (restored VRAM is authoritative); reset clears. First frame caveat: a title that uploads frame 1 before flipping GP1 depth24 shows one black movie-frame interval instead of the splash remnant. Co-Authored-By: Claude Fable 5 --- runtime/src/gpu.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index d734cdaf..62cbc033 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -2364,6 +2364,18 @@ 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; +/* Rows that received a CPU->VRAM rect since the current depth24 session began. + * While 24-bit scanout is on, the display band's rows outside the movie image + * were drawn by earlier 15-bit screens; on a double-buffered movie the two + * bands hold that leftover at DIFFERENT rows, so the bars alternate content + * every couple of frames -- the "old SCEA splash flickers behind the FMV" + * artifact. ensure_cpu at entry made the bars truthful, but truthful leftovers + * still flicker. Rows never written as 24-bit content present as black + * instead (gpu_depth24_present_row), which is what a letterboxed movie is + * supposed to look like. Cleared at every depth24 ENTRY; a savestate restore + * marks all rows (restored VRAM is authoritative). */ +static uint8_t s_d24_row_written[512]; +static uint32_t gpu_display_depth_bit(void); /* display_depth declared below */ 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); @@ -2377,6 +2389,11 @@ static void gp0_commit_cpu_to_vram(void) { gr_vram_transfer_in(vram_write_x, vram_write_y, vram_write_w, vram_write_h, vram_write_pixels); depth24_note_upload(vram_write_x, vram_write_w); + if (gpu_display_depth_bit()) { + uint32_t row; + for (row = 0; row < vram_write_h && row < 512u; row++) + s_d24_row_written[(vram_write_y + row) & 511u] = 1u; + } gp0_state = GP0_IDLE; vram_write_remaining = 0; text_xlate_vram_upload(vram_write_x, vram_write_y, @@ -2482,6 +2499,7 @@ static uint32_t hres1; /* bits 17-18: horizontal resolution 1 */ static uint32_t vres; /* bit 19: vertical resolution (0=240, 1=480) */ static uint32_t video_mode; /* bit 20: 0=NTSC, 1=PAL */ static uint32_t display_depth; /* bit 21: 0=15bit, 1=24bit */ +static uint32_t gpu_display_depth_bit(void) { return display_depth & 1u; } static uint32_t vertical_interlace; /* bit 22 */ /* Display enable (set by GP1(03h)) */ @@ -2703,6 +2721,7 @@ static void gpu_reset_state(int clear_vram) { vram_write_x = vram_write_y = 0; vram_write_w = vram_write_h = 0; vram_write_col = vram_write_row = 0; + memset(s_d24_row_written, 0, sizeof(s_d24_row_written)); vram_write_remaining = 0; vram_read_active = 0; vram_read_x = vram_read_y = 0; @@ -3240,6 +3259,13 @@ void gpu_depth24_present_row(const GpuDisplayInfo* di, uint32_t y, uint32_t* out if (valid > count) valid = count; + if (!s_d24_row_written[vy]) { + /* No 24-bit content has landed on this row this session: present the + * letterbox bar as black rather than leftover 15-bit screen bytes. */ + for (x = 0; x < count; x++) + out[x] = 0xFF000000u; + return; + } for (x = 0; x < valid; x++) { uint32_t byte_x = base_byte_x + x * 3u; uint32_t bx1 = byte_x + 1u; @@ -5834,8 +5860,10 @@ static void gp1_display_mode(uint32_t val) { vres = (val >> 2) & 1; video_mode = (val >> 3) & 1; gpu_vblank_period_refresh(); - if (new_depth != display_depth) + if (new_depth != display_depth) { s_d24_upload_x1 = 0; /* rising/falling: drop stale coverage */ + memset(s_d24_row_written, 0, sizeof(s_d24_row_written)); + } display_depth = new_depth; vertical_interlace = (val >> 5) & 1; /* GPUSTAT.13 holds the legacy constant 0 in progressive (see the vblank @@ -5997,6 +6025,7 @@ static int gpu_snap_parse(PstR *r) { RI(draw_offset_x); RI(draw_offset_y); RU(hres1); RU(hres2); RU(vres); RU(video_mode); RU(display_depth); RU(vertical_interlace); gpu_vblank_period_refresh(); /* video_mode just changed under the cache */ + memset(s_d24_row_written, 1, sizeof(s_d24_row_written)); RU(display_disabled); RU(irq1_flag); RU(dma_direction); RU(lcf); RU(display_area_x); RU(display_area_y); RU(h_display_x1); RU(h_display_x2); RU(v_display_y1); RU(v_display_y2);