From 6fcfb8adbdcd666a2fc8ba419dd4e2aa7ee1faaa Mon Sep 17 00:00:00 2001 From: Alexandros Mandravillis Date: Sun, 6 Sep 2026 14:16:53 +0300 Subject: [PATCH] Preserve field masks in wide flat overlay geometry --- docs/GPU_INTERLACED_RASTER.md | 6 ++++++ runtime/src/gpu_render.c | 14 ++++++++++++- runtime/src/gpu_vk_renderer.c | 9 ++++++--- runtime/tests/test_gpu_interlace_render.c | 20 ++++++++++++++++++- .../tests/test_vk_draw_area_batch_boundary.py | 15 +------------- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/docs/GPU_INTERLACED_RASTER.md b/docs/GPU_INTERLACED_RASTER.md index 4216c536..4966cb47 100644 --- a/docs/GPU_INTERLACED_RASTER.md +++ b/docs/GPU_INTERLACED_RASTER.md @@ -48,3 +48,9 @@ integer fallback bounds and precise vertex bounds, with one row for rounding. Tests compare native and enhanced masked pixels with unmasked images when precise positions cross the integer bounds, including negative coordinates. A tiny precise triangle must submit three row clips rather than 256. + +Flat overlay rectangles also submit one-row geometry while field masking is +active. This keeps the mask when a wide backend intentionally replaces its +scissor. Non-interlaced overlays retain the original geometry and policy. +A backend fixture that ignores the scissor tests both field parities, original +progressive behavior, and clip restoration. This is not Vulkan driver testing. diff --git a/runtime/src/gpu_render.c b/runtime/src/gpu_render.c index 9831618f..14513e52 100644 --- a/runtime/src/gpu_render.c +++ b/runtime/src/gpu_render.c @@ -198,8 +198,20 @@ void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0, uint32_t c GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1, x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw)); } +/* Wide flat-overlay backends intentionally replace the clip with a full + * surface scissor. Submit row-height geometry too, so the field mask survives + * that path while ordinary full-screen overlays keep their existing policy. */ +static void gr_draw_flat_rect_row(int x, int w, uint16_t c) { + int x1, y1, x2, y2; + g_b->get_draw_area(&x1, &y1, &x2, &y2); + g_b->draw_flat_rect(x, y1, w, 1, c); +} void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) { - GR_RASTER_ROWS(y, y + h - 1, g_b->draw_flat_rect(x, y, w, h, c)); + if (gpu_raster_skipped_row() < 0) { + g_b->draw_flat_rect(x, y, w, h, c); + return; + } + GR_RASTER_ROWS(y, y + h - 1, gr_draw_flat_rect_row(x, w, c)); } void gr_draw_textured_rect(int x, int y, int w, int h, int u, int v, uint16_t clut_x, uint16_t clut_y, uint16_t texpage) { diff --git a/runtime/src/gpu_vk_renderer.c b/runtime/src/gpu_vk_renderer.c index 9c9f6e53..cf85ebac 100644 --- a/runtime/src/gpu_vk_renderer.c +++ b/runtime/src/gpu_vk_renderer.c @@ -2790,8 +2790,8 @@ static void vkb_draw_gouraud_triangle(int x0,int y0,uint16_t c0,int x1,int y1,ui /* Full-screen-overlay wide pass (pause gray-filter / load fade): draw a flat * rect covering the FULL wide width [0, wide_w) x [y, y+h) directly into the * active wide surface (positions already wide-space, u_xoff = 0). Mirrors GL's - * wide_flat_rect_direct: full-width scissor (the overlay must dim the - * revealed margins too), while preserving the draw-area Y band. The 6 verts are consumed privately (s_vbase bumped) + * wide_flat_rect_direct: full-surface scissor (the overlay must dim the + * revealed margins too). The 6 verts are consumed privately (s_vbase bumped) * so they never join a canonical batch. */ static void wide_overlay_rect(int y, int h, uint16_t c, int semi) { if (s_wide_cur < 0 || !s_ready) return; @@ -2803,9 +2803,12 @@ static void wide_overlay_rect(int y, int h, uint16_t c, int semi) { push_vert(x0, fy0, col); push_vert(x1, fy1, col); push_vert(x0, fy1, col); s_vbase += s_vcount; s_vcount = 0; /* consume privately */ int blend = (semi < 0) ? 0 : (semi + 1); + int S = s_scale; VkCommandBuffer cb = begin_oneshot(); - /* Keep the full width and current Y clip, including interlaced row clips. */ wide_pass_begin(cb); + /* Overlay covers the whole surface: override the band scissor. */ + VkRect2D sc = { { 0, 0 }, { (uint32_t)(s_wide_w * S), (uint32_t)(VRAM_H * S) } }; + p_vkCmdSetScissor(cb, 0, 1, &sc); bind_masked(cb, 0, 0, blend, s_mask_check, s_mask_set); GeoPush gp = { px_shift(), 0.0f, (float)s_wide_w / 2.0f }; p_vkCmdPushConstants(cb, s_pl_geo, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof gp, &gp); diff --git a/runtime/tests/test_gpu_interlace_render.c b/runtime/tests/test_gpu_interlace_render.c index d4e0b54d..e2e7f962 100644 --- a/runtime/tests/test_gpu_interlace_render.c +++ b/runtime/tests/test_gpu_interlace_render.c @@ -21,10 +21,17 @@ static void mock_triangle(int x0,int y0,int x1,int y1,int x2,int y2,uint16_t c) ++mock_submissions;if(!mock_pc || !mock_pq)++mock_bad; mock_pc=mock_pq=0; /* GPU backends consume these per submission. */ } +/* Wide overlay paths can deliberately ignore the draw-area scissor. */ +static unsigned overlay_rows[32]; +static void mock_flat_overlay(int x,int y,int w,int h,uint16_t c) { + (void)x;(void)w;(void)c; + for (int row=y; row=0) ++overlay_rows[row]; +} static const GpuRenderBackend mock_backend={ .name="metadata fixture",.get_draw_area=sw_get_draw_area,.set_draw_area=sw_set_draw_area, .set_precise_triangle=mock_precise,.set_perspective_triangle=mock_perspective, - .draw_flat_triangle=mock_triangle + .draw_flat_triangle=mock_triangle,.draw_flat_rect=mock_flat_overlay }; /* This fixture keeps optional widescreen presentation disabled. */ int g_ws_bd_stretch_pct, g_ws_bd_stretch_on; @@ -153,6 +160,17 @@ int main(void) { gr_set_perspective_triangle(1,1.0f,1.0f,1.0f); gr_draw_flat_triangle(0,8,1,8,0,9,0x7FFF); CHECK(mock_submissions==3 && mock_bad==0 && !mock_pc && !mock_pq); + for (int skip=-1; skip<=1; ++skip) { + memset(overlay_rows,0,sizeof overlay_rows); skipped_row=skip; + gr_set_draw_area(0,4,31,15); + gr_draw_flat_rect(0,0,32,20,0x7FFF); + for (int y=0; y<32; ++y) { + unsigned expected = skip<0 ? y<20 : (y>=4 && y<=15 && (y&1)!=skip); + CHECK(overlay_rows[y] == expected); + } + int x1,y1,x2,y2; gr_get_draw_area(&x1,&y1,&x2,&y2); + CHECK(x1==0 && y1==4 && x2==31 && y2==15); + } gr_set_backend(GR_BACKEND_SOFTWARE); if (failures) return 1; puts("PASS: field predicate, all raster primitives, clipping, transfers, mode switch, scales 1/4"); diff --git a/runtime/tests/test_vk_draw_area_batch_boundary.py b/runtime/tests/test_vk_draw_area_batch_boundary.py index dfadbe60..3d7c7a4d 100644 --- a/runtime/tests/test_vk_draw_area_batch_boundary.py +++ b/runtime/tests/test_vk_draw_area_batch_boundary.py @@ -1,11 +1,9 @@ #!/usr/bin/env python3 from pathlib import Path import re -import sys -source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parents[1] / "src" / "gpu_vk_renderer.c" -source = source_path.read_text(encoding="utf-8") +source = (Path(__file__).parents[1] / "src" / "gpu_vk_renderer.c").read_text() setter = re.search( r"static void vkb_set_draw_area\(.*?\n\}", source, flags=re.DOTALL ) @@ -23,14 +21,3 @@ assert tex_flush < state_write and geo_flush < state_write, ( ) print("Vulkan draw-area batch-boundary test passed") - -# Native-wide overlays must retain the same vertical clip as other geometry. -# This source guard is not a substitute for Vulkan driver/pixel qualification. -wide = re.search(r"static void wide_pass_begin\(.*?\n\}", source, flags=re.DOTALL) -overlay = re.search(r"static void wide_overlay_rect\(.*?\n\}", source, flags=re.DOTALL) -assert wide and overlay, "wide render helpers not found" -assert "s_da_y1" in wide[0] and "s_da_y2" in wide[0] -assert "p_vkCmdSetScissor" in wide[0] -assert "wide_pass_begin(cb);" in overlay[0] -assert "p_vkCmdSetScissor" not in overlay[0], "overlay overrides the draw-area Y clip" -print("Vulkan wide-overlay clip source guard passed")