From ddbb979f181c83278b77c5abb3f892f774626c87 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 10 Oct 2026 00:48:17 -0700 Subject: [PATCH] fix: enable camera-plane clipping with native-wide projection correction --- docs/ENHANCEMENTS.md | 22 ++++++++++++--------- runtime/include/mod_plugins.h | 15 +++++++------- runtime/src/gpu.c | 7 ++++++- runtime/src/main.cpp | 2 ++ runtime/tests/test_gl_readback_region.c | 21 +++++++++++++------- runtime/tests/test_native_wide_projection.c | 12 ++++++----- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/docs/ENHANCEMENTS.md b/docs/ENHANCEMENTS.md index 91fe4076..9b4da162 100644 --- a/docs/ENHANCEMENTS.md +++ b/docs/ENHANCEMENTS.md @@ -1436,24 +1436,28 @@ native code and the dirty-RAM interpreter agree (`draw_distance_codegen_test`, `draw_distance_interp_test`). First user: R4's course renderers (RidgeRacerType4Recomp, Max Detail). -### Opt-in camera-plane clipping for native-wide textured worlds +### Default camera-plane clipping for native-wide textured worlds -`psx_mod_set_native_wide_near_clip(1)` complements horizontal projection -recovery. A large face can remain visible while a corner crosses the camera -plane: unsigned SZ, the capped H/Z divider and saturated SXY cannot represent -that face. PGXP now optionally carries the signed homogeneous projection from -RTPS/RTPT through exact full-word copies. CPU arithmetic, partial writes, +`psx_mod_set_native_wide_projection_correction(1)` automatically enables both +horizontal projection recovery and camera-plane clipping. A large face can +remain visible while a corner crosses the camera plane: unsigned SZ, the capped +H/Z divider and saturated SXY cannot represent that face. PGXP carries the +signed homogeneous projection from RTPS/RTPT through exact full-word copies. CPU arithmetic, partial writes, stale words and timeline invalidation discard the association; sandbox rollback restores it with the rest of the precision shadow. -Opted-in native-wide OpenGL sessions clip proven textured faces at depth 1 -and the visible bounds before perspective division. UV and color attributes +Native-wide OpenGL sessions with projection correction clip proven textured +faces at depth 1 and the visible bounds before perspective division. UV and color attributes follow each intersection, and ordinary GPU batches, painter order, masking, texture filtering and canonical/wide readback remain in use. The existing guest GTE registers, gameplay state and title culling branches do not change. Software/CPU-authoritative rendering, 4:3 and missing provenance use the original path. The larger PGXP value increases the full 8 MiB RAM shadow -from 40 to 72 MiB; projection calculations are disabled unless a title opts in. +from 40 to 72 MiB; projection calculations run only while projection correction +is enabled. Session start resets that mode before the new plan's activation. +The old `psx_mod_set_native_wide_near_clip` entry point remains for source/ABI +compatibility, but its argument no longer changes clipping. The enhanced view +is elective; clipping its proven geometry correctly needs no separate opt-in. MediEvil II's title hallway provided the camera-crossing reproduction. The GL authority/order fixture passes 197 checks at 1x and 4x; provenance tests diff --git a/runtime/include/mod_plugins.h b/runtime/include/mod_plugins.h index 2b0d3605..34d35658 100644 --- a/runtime/include/mod_plugins.h +++ b/runtime/include/mod_plugins.h @@ -238,15 +238,14 @@ int32_t psx_mod_widescreen_x_margin(void); * Use for screen-space layout, including the first frame of a new scene. */ int32_t psx_mod_widescreen_view_x_margin(void); -/* Opt into render-only recovery of saturated horizontal GTE projections in - * native-wide gameplay. Requires exact packet-address/word provenance and - * depth; never changes guest SXY, vertical coordinates, or the 4:3 path. */ +/* Enable render-only recovery of saturated horizontal GTE projections and + * camera-plane clipping in native-wide gameplay. Requires exact packet-address/ + * word provenance and depth; never changes guest SXY or the 4:3 path. */ void psx_mod_set_native_wide_projection_correction(int enabled); -/* Separately qualify near-camera world clipping for a title. The GL path - * carries signed, unclamped projection through exact PGXP word transport, - * clips at the camera/view planes, and preserves ordered GPU/VRAM writes. - * Requires projection correction above; 4:3, software and untracked UI stay - * on their existing paths. No architectural GTE or gameplay changes. */ +/* Deprecated compatibility entry point; enabled is ignored. Camera/view-plane + * clipping is automatic with projection correction above. The GL path requires + * signed projection through exact PGXP word transport; 4:3, software and + * untracked UI stay on their existing paths. No guest GTE/gameplay changes. */ void psx_mod_set_native_wide_near_clip(int enabled); /* Bind render-only NCLIP branch consumers to full instruction words. These * recover winding only when valid horizontal projections saturated; guest diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 12c20343..8ed683e6 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -4468,6 +4468,9 @@ void gpu_pgxp_rederive_enable(void) { void psx_mod_set_native_wide_projection_correction(int enabled) { s_native_wide_projection_correction = enabled ? 1 : 0; s_native_wide_projection_vertices = 0; + /* Signed camera-space provenance is required for correct native-wide + * faces, including corners behind the camera. Arm it with the mode. */ + pgxp_set_projection_tracking(s_native_wide_projection_correction); gpu_pgxp_rederive_enable(); } @@ -4482,7 +4485,9 @@ static int gpu_ws_projective_enabled(void) { gl_renderer_projective_supported(); } void psx_mod_set_native_wide_near_clip(int enabled) { - pgxp_set_projection_tracking(enabled); + /* Compatibility entry point: clipping follows projection correction. + * A separate toggle must not disable this mode's correctness fix. */ + (void)enabled; } /* The PS1 clamps each projected X independently. At a wider FOV that can diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c593f577..fe37fa02 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1767,6 +1767,8 @@ static void reset_mod_owned_presentation(void) { gpu_ws_set_auto_ui_proportional(g_ws_auto_ui_proportional_cfg ? 1 : 0); psx_mod_set_world_scene_predicate(nullptr); gpu_ws_set_native_scene_predicate(nullptr); + /* Projection recovery and its clipping belong to this session's mod. */ + psx_mod_set_native_wide_projection_correction(0); psx_mod_set_retained_scene_predicate(nullptr); psx_mod_set_adaptive_backdrop_preload(0); (void)psx_mod_set_draw_distance_clamp(0); diff --git a/runtime/tests/test_gl_readback_region.c b/runtime/tests/test_gl_readback_region.c index b4dcf632..34b7f844 100644 --- a/runtime/tests/test_gl_readback_region.c +++ b/runtime/tests/test_gl_readback_region.c @@ -249,6 +249,13 @@ static void verify_stereo_transactions(void) { check(glGetError()==GL_NO_ERROR,"stereo transaction GL error"); } +static void check_wide_green(uint32_t pixel,const char *label) { + /* Accurate blending stores each 5-bit channel as k*8, so full green is + * 248 in the raw wide surface. The legacy basis expands it to 255. */ + const uint32_t expected=gl_renderer_accurate_blending()?0xff00f800u:0xff00ff00u; + if(pixel!=expected)fprintf(stderr,"%s: got %08x expected %08x\n",label,pixel,expected); + check(pixel==expected,label); +} static void verify_oversize_wide_geometry(int scale) { /* Captured hallway triangles exceed the PS1 height limit. Proven geometry * must use identical canonical/wide passes, including the center-copy edge. */ @@ -288,11 +295,11 @@ static void verify_oversize_wide_geometry(int scale) { check(pixels!=NULL,"oversize wide pixels allocation"); if(pixels) { check(glb_render_wide_display(pixels,848*scale*4,0,0,240)>0,"oversize wide readback"); - check(pixels[(100*scale)*(848*scale)+68*scale]==0xff00ff00u,"left oversize margin drawn in painter order"); - check(pixels[(100*scale)*(848*scale)+818*scale]==0xff00ff00u,"captured right hallway wall drawn"); - check(pixels[(100*scale)*(848*scale)+268*scale]==0xff00ff00u,"canonical center retains recovered faces"); - check(pixels[(100*scale)*(848*scale)+678*scale]==0xff00ff00u,"recovered wall before center-copy boundary"); - check(pixels[(100*scale)*(848*scale)+680*scale]==0xff00ff00u,"same recovered wall after center-copy boundary"); + check_wide_green(pixels[(100*scale)*(848*scale)+68*scale],"left oversize margin drawn in painter order"); + check_wide_green(pixels[(100*scale)*(848*scale)+818*scale],"captured right hallway wall drawn"); + check_wide_green(pixels[(100*scale)*(848*scale)+268*scale],"canonical center retains recovered faces"); + check_wide_green(pixels[(100*scale)*(848*scale)+678*scale],"recovered wall before center-copy boundary"); + check_wide_green(pixels[(100*scale)*(848*scale)+680*scale],"same recovered wall after center-copy boundary"); free(pixels); } glb_wide_disable_target(); @@ -326,8 +333,8 @@ static void verify_camera_plane_clip(int scale) { check(pixels!=NULL,"camera-plane wide allocation"); if(pixels) { check(glb_render_wide_display(pixels,848*scale*4,0,0,240)>0,"camera-plane wide readback"); - check(pixels[100*scale*(848*scale)+68*scale]==0xff00ff00u,"crossing wall retained in wide margin"); - check(pixels[100*scale*(848*scale)+268*scale]==0xff00ff00u,"same crossing wall across center copy"); + check_wide_green(pixels[100*scale*(848*scale)+68*scale],"crossing wall retained in wide margin"); + check_wide_green(pixels[100*scale*(848*scale)+268*scale],"same crossing wall across center copy"); free(pixels); } /* Perspective UVs must survive intersections without integer rounding. diff --git a/runtime/tests/test_native_wide_projection.c b/runtime/tests/test_native_wide_projection.c index ad2225fb..e11b399b 100644 --- a/runtime/tests/test_native_wide_projection.c +++ b/runtime/tests/test_native_wide_projection.c @@ -39,7 +39,7 @@ int main(void) { prepare_precise_triangle(1,4,7,ax,ay); assert(!fixture_precise_triangle.enabled); /* default remains stock */ psx_mod_set_native_wide_projection_correction(1); - assert(fixture_pgxp_enabled); + assert(fixture_pgxp_enabled && fixture_projection_tracking); prepare_precise_triangle(1,4,7,ax,ay); assert(fixture_precise_triangle.enabled && area(fixture_precise_triangle.xy)>0); assert(fixture_precise_triangle.xy[5]==152*65536); /* no Y fraction correction */ @@ -128,12 +128,14 @@ int main(void) { gp0_cmd_source_addr=UINT32_MAX; assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2)); psx_mod_set_native_wide_projection_correction(0); - assert(!fixture_pgxp_enabled); + assert(!fixture_pgxp_enabled && !fixture_projection_tracking); + psx_mod_set_native_wide_near_clip(1); /* legacy API cannot enable the mode */ + assert(!fixture_projection_tracking); prepare_precise_triangle(7,4,10,bx,by); assert(!fixture_triangle_recovered); assert(!fixture_precise_triangle.enabled); /* A camera-plane crossing needs intact homogeneous data for ALL corners. - * The native-wide clip opt-in is independent of horizontal recovery. */ + * Enabling projection correction alone must also correct these faces. */ psx_mod_set_native_wide_projection_correction(1); configure_native_wide_16_9(); fixture_projective_supported=1;fixture_backend=GR_BACKEND_OPENGL; @@ -146,8 +148,8 @@ int main(void) { fixture_homogeneous[i].p=(PGXPProjection){40000,8000,i==1?-62:500,150}; fixture_homogeneous[i].valid=1; } - assert(!native_wide_projective_draw()); - psx_mod_set_native_wide_near_clip(1); + psx_mod_set_native_wide_near_clip(0); /* legacy toggle cannot disable the fix */ + assert(fixture_projection_tracking); assert(native_wide_projective_draw() && fixture_projective_draws==2); assert(fixture_projective_vertices[1].z==-62); /* behind-camera depth retained */ gp0_cmd_buf[4]^=1;