@@ -82,6 +82,7 @@ static int ws_hud_sprt = 0; /* edge-anchor untagged HUD SPRTs *
8282 * in native-wide mode. */
8383static int ws_mode = 0 ;
8484static int ws_cfg_num = 4 , ws_cfg_den = 3 ;
85+ static void ws_nw_sync_target (void );
8586
8687#define WS_TAG_BUCKETS 4096 /* power of two */
8788#define WS_TAG_PROBES 8
@@ -279,13 +280,22 @@ static int ws_active(void) { return ws_configured() && !gpu_ws_present_native_43
279280int ws_native_wide_active (void ) {
280281 return ws_mode == 2 && !gpu_ws_present_native_43 ();
281282}
283+ /* Cull/spawn setup often runs while a scene is loading, before the first GTE
284+ * frame can classify it as gameplay. Keep that pre-render setup aware of the
285+ * configured native-wide viewport; presentation itself remains gated by
286+ * ws_native_wide_active(). */
287+ static int ws_native_wide_configured (void ) {
288+ return ws_mode == 2 && ws_cfg_num * 3 > ws_cfg_den * 4 ;
289+ }
290+ static int ws_nw_configured_offset (void ) {
291+ if (!ws_native_wide_configured ()) return 0 ;
292+ int numr = 3 * ws_cfg_num - 4 * ws_cfg_den ;
293+ int w = (int )ws_disp_w ();
294+ return (w * numr + 4 * ws_cfg_den ) / (8 * ws_cfg_den );
295+ }
282296static int ws_nw_offset (void ) {
283297 if (!ws_native_wide_active ()) return 0 ;
284- int numr = 3 * ws_cfg_num - 4 * ws_cfg_den ; /* > 0 for aspects wider than 4:3 */
285- if (numr <= 0 ) return 0 ;
286- int w = (int )ws_disp_w ();
287- int v = (w * numr + 4 * ws_cfg_den ) / (8 * ws_cfg_den ); /* round-to-nearest */
288- return v ;
298+ return ws_nw_configured_offset ();
289299}
290300int ws_nw_extra (void ) { return 2 * ws_nw_offset (); }
291301
@@ -446,7 +456,12 @@ int psx_ws_x_margin(void) {
446456 * that previously fell outside the 4:3 cull window; the wide compositor then
447457 * rasterizes it into the revealed margins. Same recompiler emit sites as the
448458 * squash path ([widescreen.cull]); 0 at 4:3 so the cull stays byte-identical. */
449- if (ws_native_wide_active ()) return ws_nw_offset () + ws_cull_guard_pixels ;
459+ /* Unlike rendering/presentation, do not wait for game-mode detection here.
460+ * Tomba 2 builds its terrain-cell and actor spawn lists during scene load;
461+ * returning zero until the first 3D frame permanently bakes a 4:3 frustum
462+ * into those lists. */
463+ if (ws_native_wide_configured ())
464+ return ws_nw_configured_offset () + ws_cull_guard_pixels ;
450465 if (!ws_active ()) return 0 ;
451466 return (160 * (ws_xden - ws_xnum ) + ws_xnum / 2 ) / ws_xnum ;
452467}
@@ -1261,6 +1276,10 @@ void gpu_ws_configure(int aspect_num, int aspect_den,
12611276 }
12621277 ws_anchor_addr = sprite_anchor_addr ;
12631278 ws_hud_sprt = hud_sprt_squash ;
1279+ /* Adaptive view can change mode/extent without the guest reissuing E3/E4
1280+ * immediately. Keep the active mirror target and its scissor in lockstep
1281+ * with the new live aspect instead of waiting for another draw-env packet. */
1282+ ws_nw_sync_target ();
12641283}
12651284
12661285/* Called from generated code at the entry of each [widescreen]
@@ -2349,9 +2368,44 @@ static void raster_pixel(int32_t x, int32_t y, uint16_t color) {
23492368 * clips those for free; our GL path was building two triangles per clipped
23502369 * prim (gpu_share ~0.9, host FPS ~5–10). Skip the host rasterizer when the
23512370 * post-offset bbox cannot touch the draw area. Side effects that must still
2352- * run (texpage latch, oversize reject) happen before these checks. */
2371+ * run (texpage latch, oversize reject) happen before these checks.
2372+ *
2373+ * Native-wide is intentionally different in X: the mirror renderer translates
2374+ * canonical framebuffer coordinates by the live reveal offset and scissors to
2375+ * the FULL wide surface, not GP0(E3/E4)'s 4:3 X range. Commit 31015ce originally
2376+ * compared every primitive only with the guest draw area here, rejecting the
2377+ * margin geometry before the mirror renderer could see it. Match the mirror's
2378+ * exact X extent while its framebuffer target is active; Y remains the guest
2379+ * draw area because native-wide does not extend vertically. At 4:3, in FMV/
2380+ * menus, in squash mode, and for offscreen texture targets, margin is zero and
2381+ * this remains the original fast reject byte-for-byte.
2382+ *
2383+ * OpokXeno independently identified the same host-side regression on Xenogears
2384+ * and contributed the original generalized fix in psxrecomp PR #73:
2385+ * https://github.com/mstan/psxrecomp/pull/73
2386+ * Keep that credit with this guarded framebuffer-target variant. */
2387+ static inline int32_t draw_area_wide_x_margin (void ) {
2388+ if (!ws_native_wide_active () || !ws_is_fb_base (draw_area_left )) return 0 ;
2389+ return (int32_t )ws_nw_offset ();
2390+ }
2391+
2392+ static inline void draw_area_host_x_bounds (int32_t * left , int32_t * right ) {
2393+ int32_t margin = draw_area_wide_x_margin ();
2394+ if (margin > 0 ) {
2395+ /* ws_nw_sync_target uses draw_area_left as the framebuffer base and
2396+ * configures a surface ws_disp_w()+2*margin pixels wide. */
2397+ * left = (int32_t )draw_area_left - margin ;
2398+ * right = (int32_t )draw_area_left + (int32_t )ws_disp_w () + margin - 1 ;
2399+ } else {
2400+ * left = (int32_t )draw_area_left ;
2401+ * right = (int32_t )draw_area_right ;
2402+ }
2403+ }
2404+
23532405static inline int draw_area_out_point (int32_t x , int32_t y ) {
2354- return x < (int32_t )draw_area_left || x > (int32_t )draw_area_right
2406+ int32_t left , right ;
2407+ draw_area_host_x_bounds (& left , & right );
2408+ return x < left || x > right
23552409 || y < (int32_t )draw_area_top || y > (int32_t )draw_area_bottom ;
23562410}
23572411
@@ -2363,14 +2417,18 @@ static inline int draw_area_out_bbox(const int32_t *vx, const int32_t *vy, int n
23632417 if (vy [i ] < miny ) miny = vy [i ];
23642418 if (vy [i ] > maxy ) maxy = vy [i ];
23652419 }
2366- return maxx < (int32_t )draw_area_left || minx > (int32_t )draw_area_right
2420+ int32_t left , right ;
2421+ draw_area_host_x_bounds (& left , & right );
2422+ return maxx < left || minx > right
23672423 || maxy < (int32_t )draw_area_top || miny > (int32_t )draw_area_bottom ;
23682424}
23692425
23702426static inline int draw_area_out_rect (int32_t x , int32_t y , int w , int h ) {
23712427 if (w <= 0 || h <= 0 ) return 1 ;
2372- return (x + w - 1 ) < (int32_t )draw_area_left
2373- || x > (int32_t )draw_area_right
2428+ int32_t left , right ;
2429+ draw_area_host_x_bounds (& left , & right );
2430+ return (x + w - 1 ) < left
2431+ || x > right
23742432 || (y + h - 1 ) < (int32_t )draw_area_top
23752433 || y > (int32_t )draw_area_bottom ;
23762434}
0 commit comments