mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Preserve CPU near-clip projections and crossing quad winding
This commit is contained in:
@@ -283,6 +283,18 @@ void psx_mod_anchor_hud_primitive(uint32_t primitive, int edge);
|
||||
* Both services are render-only, word guarded and inert at native 4:3. */
|
||||
void psx_mod_tag_screen_mask_quad(uint32_t primitive);
|
||||
void psx_mod_tag_radial_screen_mask_quad(uint32_t primitive, float scale);
|
||||
/* A verified CPU near-clip producer may publish its unclamped screen point.
|
||||
* The original aligned RAM word must equal the producer's +/-1023 clamp;
|
||||
* the complete word is rechecked through cpu->read_word. Metadata follows
|
||||
* ordinary tracked loads/stores and is invalidated by every later writer.
|
||||
* This records presentation provenance only; RAM and CPU state are untouched. */
|
||||
int psx_mod_record_cpu_projected_vertex(const struct CPUState* cpu,
|
||||
uint32_t address, uint32_t expected, int32_t x, int32_t y,
|
||||
uint16_t depth, uint16_t near_depth);
|
||||
/* A verified quad renderer may prove its fourth source corner lies in front
|
||||
* when the three current NCLIP corners are behind the near plane. This only
|
||||
* supplements the current full-word winding proof; guest registers stay stock. */
|
||||
int psx_mod_extend_nclip_for_quad_vertex(const struct CPUState* cpu, uint32_t vertex);
|
||||
/* Exclude a known world packet from screen-space backdrop stretching, even
|
||||
* if it sorts before the first shaded polygon. Zero clears a recycled tag. */
|
||||
void psx_mod_tag_world_primitive(uint32_t primitive, int is_world);
|
||||
|
||||
+5
-1
@@ -5239,7 +5239,11 @@ static int native_wide_projective_draw(void) {
|
||||
PGXPProjection p;
|
||||
if (!pgxp_load_projection(gp0_cmd_source_addr+4*index,word,&p)) return 0;
|
||||
int32_t x,y; parse_vertex(word,&x,&y);
|
||||
needs_clip |= p.z<p.near_z || x==-1024 || x==1023 || y==-1024 || y==1023;
|
||||
/* CPU near clippers can use -1023 for their left rail. Its explicit
|
||||
* full projection proves whether that rail actually truncated it. */
|
||||
needs_clip |= p.z<p.near_z || x==-1024 || x==1023 || y==-1024 || y==1023 ||
|
||||
(x==-1023 && p.x < -1023.0f*p.z) ||
|
||||
(y==-1023 && p.y < -1023.0f*p.z);
|
||||
const uint32_t color=gp0_cmd_buf[(op&16u)?index-1:0];
|
||||
const uint32_t uv=gp0_cmd_buf[index+1];
|
||||
vertices[i]=(PSXProjectedVertex){p.x+(double)draw_offset_x*p.z,
|
||||
|
||||
+43
-3
@@ -1403,6 +1403,39 @@ extern "C" int psx_mod_projected_triangle_clipping_supported(const CPUState* cpu
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
extern "C" int psx_mod_extend_nclip_for_quad_vertex(const CPUState* cpu, uint32_t vertex) {
|
||||
const uint32_t physical=vertex&0x1fffffffu;
|
||||
if (!cpu || !cpu->read_word || (vertex&3u) || physical<0x10000u ||
|
||||
physical>0x200000u-8u || !gpu_ws_precise_nclip_enabled() ||
|
||||
!gpu_ws_projective_clipping_supported() || !pgxp_projection_tracking() ||
|
||||
s_gte_replay_sandbox || s_speculative_depth ||
|
||||
s_nclip_last_generation!=s_geom_generation ||
|
||||
(int32_t)cpu->gte_data[24]!=s_nclip_last_native) return 0;
|
||||
PGXPProjection p[3];
|
||||
for (unsigned i=0;i<3;++i) {
|
||||
if (!pgxp_get_gte_projection(i,cpu->gte_data[12+i],&p[i]) ||
|
||||
!std::isfinite(p[i].x) || !std::isfinite(p[i].y) ||
|
||||
!std::isfinite(p[i].z) || !std::isfinite(p[i].near_z) ||
|
||||
p[i].near_z<=0 || p[i].z>=p[i].near_z) return 0;
|
||||
}
|
||||
const uint32_t xy=cpu->read_word(vertex), zw=cpu->read_word(vertex+4u);
|
||||
const int16_t v[3]={(int16_t)xy,(int16_t)(xy>>16),(int16_t)zw};
|
||||
const int16_t r[3]={(int16_t)cpu->gte_ctrl[3],(int16_t)(cpu->gte_ctrl[3]>>16),
|
||||
(int16_t)cpu->gte_ctrl[4]};
|
||||
const int64_t mac3=(int64_t)(int32_t)cpu->gte_ctrl[7]*4096 +
|
||||
(int64_t)r[0]*v[0]+(int64_t)r[1]*v[1]+(int64_t)r[2]*v[2];
|
||||
/* Fail closed when the fourth transform would overflow the GTE MAC lane. */
|
||||
if (mac3<-(1ll<<43) || mac3>=(1ll<<43) ||
|
||||
(double)(mac3>>12)<p[0].near_z) return 0;
|
||||
const double determinant=
|
||||
(double)p[0].x*((double)p[1].y*p[2].z-(double)p[1].z*p[2].y)-
|
||||
(double)p[0].y*((double)p[1].x*p[2].z-(double)p[1].z*p[2].x)+
|
||||
(double)p[0].z*((double)p[1].x*p[2].y-(double)p[1].y*p[2].x);
|
||||
if (!std::isfinite(determinant) || determinant==0) return 0;
|
||||
s_nclip_last_projective_sign=determinant<0?-1:1;
|
||||
s_nclip_last_projective_valid=true;
|
||||
return 1;
|
||||
}
|
||||
/* Some guarded quad consumers save two MAC0 values before branching. Their
|
||||
* first branch must use the first command's provenance, even when both native
|
||||
* results are equal. This is explicitly selected by the title, never searched
|
||||
@@ -1499,7 +1532,8 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int x = exact_x[i] >> 16;
|
||||
const int y = exact_y[i] >> 16;
|
||||
checked &= x > -4096 && x < 4096 && y > -4096 && y < 4096;
|
||||
checked &= exact_x[i] > -4096 * 65536 && exact_x[i] < 4096 * 65536 - 1 &&
|
||||
exact_y[i] > -4096 * 65536 && exact_y[i] < 4096 * 65536 - 1;
|
||||
checked &= gte->SZ[i + 1] >= gte_h_scaled(gte) / 2 && gte->SZ[i + 1] != 0;
|
||||
if (raw_x[i] == 1023 && x > 1023) saturated = true;
|
||||
else if (raw_x[i] == -1024 && x < -1024) saturated = true;
|
||||
@@ -1539,15 +1573,21 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
if (gpu_ws_precise_nclip_enabled() && gpu_ws_projective_clipping_supported() && pgxp_projection_tracking() &&
|
||||
!s_gte_replay_sandbox && s_speculative_depth == 0) {
|
||||
PGXPProjection p[3];
|
||||
bool valid = true, crosses = false, front = false;
|
||||
bool valid = true, crosses = false, front = false, saturated = false;
|
||||
for (unsigned i=0;i<3;i++) {
|
||||
if (!pgxp_get_gte_projection(i, gte->SXY[i], &p[i])) { valid=false; break; }
|
||||
valid &= std::isfinite(p[i].x) && std::isfinite(p[i].y) &&
|
||||
std::isfinite(p[i].z) && std::isfinite(p[i].near_z) && p[i].near_z > 0;
|
||||
crosses |= p[i].z < p[i].near_z;
|
||||
front |= p[i].z >= p[i].near_z;
|
||||
const int x = (int16_t)(gte->SXY[i] & 0xffffu);
|
||||
const int y = (int16_t)(gte->SXY[i] >> 16);
|
||||
saturated |= x == -1024 || x == 1023 || y == -1024 || y == 1023;
|
||||
}
|
||||
if (valid && crosses && front) {
|
||||
/* Close, forward-facing terrain can exceed the bounded 16.16
|
||||
* transport without crossing the camera. Its clamped SXY winding
|
||||
* is equally unreliable. The full homogeneous source stays valid. */
|
||||
if (valid && (crosses || saturated) && front) {
|
||||
const double determinant =
|
||||
(double)p[0].x * ((double)p[1].y*p[2].z-(double)p[1].z*p[2].y) -
|
||||
(double)p[0].y * ((double)p[1].x*p[2].z-(double)p[1].z*p[2].x) +
|
||||
|
||||
@@ -1481,6 +1481,31 @@ extern "C" void pgxp_gte_set_projection(const PGXPProjection *projection) {
|
||||
s_gte[14].flags |= PGXP_F_PROJECTION;
|
||||
s_gte[15] = s_gte[14];
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_record_cpu_projected_vertex(const CPUState* cpu,
|
||||
uint32_t address, uint32_t expected, int32_t x, int32_t y,
|
||||
uint16_t depth, uint16_t near_depth) {
|
||||
const uint32_t physical = address & 0x1fffffffu;
|
||||
if (!g_pgxp_active || !s_projection_tracking || !cpu || !cpu->read_word ||
|
||||
(address & 3u) || physical < 0x10000u ||
|
||||
physical > psx_ram_live_bytes() - 4u || !depth || !near_depth ||
|
||||
depth < near_depth || x < -1048576 || x > 1048576 ||
|
||||
y < -1048576 || y > 1048576) return 0;
|
||||
const int32_t cx = x < -1023 ? -1023 : x > 1023 ? 1023 : x;
|
||||
const int32_t cy = y < -1023 ? -1023 : y > 1023 ? 1023 : y;
|
||||
const uint32_t clamped = (uint16_t)cx | ((uint32_t)(uint16_t)cy << 16);
|
||||
if (expected != clamped || cpu->read_word(address) != expected) return 0;
|
||||
PGXPValue* pv = pgxp_ptr(address);
|
||||
if (!pv) return 0;
|
||||
pv->value = expected; pv->gen = s_gen;
|
||||
/* The ordinary precision tier remains at the original CPU coordinates.
|
||||
* Only the explicit full projection carries values outside those rails. */
|
||||
pv->x16 = cx * 65536; pv->y16 = cy * 65536; pv->z = depth;
|
||||
pv->flags = PGXP_F_VXY | PGXP_F_VZ | PGXP_F_PROJECTION;
|
||||
pv->projection = {(float)((double)x * depth), (float)((double)y * depth),
|
||||
(float)depth, (float)near_depth};
|
||||
return 1;
|
||||
}
|
||||
static int projection_read(const PGXPValue *pv, uint32_t packed, PGXPProjection *out) {
|
||||
const uint16_t flags = PGXP_F_VXY | PGXP_F_PROJECTION;
|
||||
if (!s_enabled || !s_projection_tracking || !pv || pv->gen != s_gen || pv->value != packed ||
|
||||
|
||||
@@ -62,6 +62,13 @@ extern "C" void gte_test_execute_reference(CPUState *cpu, uint32_t cmd);
|
||||
extern "C" int gpu_ws_present_native_43(void) { return 0; }
|
||||
static int g_test_precise_nclip_enabled;
|
||||
static int g_test_projective_supported = 1;
|
||||
static uint32_t g_test_cpu_projection_word;
|
||||
static uint32_t g_test_quad_vertex[2];
|
||||
static uint32_t cpu_projection_read(uint32_t address) {
|
||||
if (address==0x80019000u) return g_test_quad_vertex[0];
|
||||
if (address==0x80019004u) return g_test_quad_vertex[1];
|
||||
return g_test_cpu_projection_word;
|
||||
}
|
||||
extern "C" int gpu_ws_projective_clipping_supported(void) { return g_test_projective_supported; }
|
||||
extern "C" int gpu_ws_precise_nclip_enabled(void) {
|
||||
return g_test_precise_nclip_enabled;
|
||||
@@ -823,9 +830,42 @@ int test_saturated_nclip_keeps_architectural_result() {
|
||||
if (psx_mod_projected_triangle_clipping_supported(&cpu))
|
||||
return fail_value("background clipping refuses unsupported renderer",0,0x30,0,0,1);
|
||||
g_test_projective_supported=1;
|
||||
/* All corners remain forward, but their screen projections exceed the
|
||||
* 16.16 transport window and collapse onto one hardware corner. */
|
||||
cpu.gte_data[0]=8000u|(8000u<<16); cpu.gte_data[1]=300;
|
||||
cpu.gte_data[2]=9000u|(7000u<<16); cpu.gte_data[3]=300;
|
||||
cpu.gte_data[4]=10000u|(9000u<<16); cpu.gte_data[5]=300;
|
||||
gte_execute(&cpu,0x80030u);
|
||||
gte_execute(&cpu,6);
|
||||
if (cpu.gte_data[24]!=0 || !gte_nclip_native_wide_sign(0,&sign) || sign!=1)
|
||||
return fail_value("forward winding beyond fixed-point transport",0,6,0,1,sign);
|
||||
g_test_projective_supported=0;
|
||||
gte_execute(&cpu,6);
|
||||
if (gte_nclip_native_wide_sign(0,&sign))
|
||||
return fail_value("unbounded winding requires host clipper",0,6,0,0,1);
|
||||
g_test_projective_supported=1;
|
||||
cpu.gte_data[12]^=1u;
|
||||
if (psx_mod_projected_triangle_clipping_supported(&cpu))
|
||||
return fail_value("background clipping requires all original SXY words",0,0x30,0,0,1);
|
||||
/* The first triangle of a crossing quad may be wholly behind. Only its
|
||||
* actual fourth source vertex can establish a front component. */
|
||||
cpu.gte_data[0]=0;cpu.gte_data[1]=(uint16_t)-300;
|
||||
cpu.gte_data[2]=100;cpu.gte_data[3]=(uint16_t)-300;
|
||||
cpu.gte_data[4]=100u<<16;cpu.gte_data[5]=(uint16_t)-300;
|
||||
cpu.read_word=cpu_projection_read;g_test_quad_vertex[0]=0;g_test_quad_vertex[1]=300;
|
||||
gte_execute(&cpu,0x80030u);gte_execute(&cpu,6);
|
||||
CHECK(!gte_nclip_native_wide_sign((int32_t)cpu.gte_data[24],&sign));
|
||||
const CPUState quad_before=cpu;
|
||||
CHECK(psx_mod_extend_nclip_for_quad_vertex(&cpu,0x80019000u));
|
||||
CHECK(gte_nclip_native_wide_sign((int32_t)cpu.gte_data[24],&sign) && sign==-1);
|
||||
CHECK(!std::memcmp(&quad_before,&cpu,sizeof cpu));
|
||||
gte_execute(&cpu,6);g_test_quad_vertex[1]=(uint16_t)-300;
|
||||
CHECK(!psx_mod_extend_nclip_for_quad_vertex(&cpu,0x80019000u));
|
||||
CHECK(!gte_nclip_native_wide_sign((int32_t)cpu.gte_data[24],&sign));
|
||||
g_test_quad_vertex[1]=300;g_test_projective_supported=0;
|
||||
CHECK(!psx_mod_extend_nclip_for_quad_vertex(&cpu,0x80019000u));g_test_projective_supported=1;
|
||||
CHECK(!psx_mod_extend_nclip_for_quad_vertex(&cpu,0x80019001u));
|
||||
cpu.gte_data[12]^=1u;CHECK(!psx_mod_extend_nclip_for_quad_vertex(&cpu,0x80019000u));
|
||||
pgxp_set_projection_tracking(old_tracking);
|
||||
g_test_precise_nclip_enabled = 0;
|
||||
return 0;
|
||||
@@ -1438,6 +1478,31 @@ int main() {
|
||||
}
|
||||
gte_set_fov_scale(1,1);
|
||||
pgxp_set_projection_tracking(0);
|
||||
/* Explicit CPU-generated intersections retain their original word while
|
||||
* carrying full unclamped projection through ordinary memory transport. */
|
||||
pgxp_set_enabled(1); pgxp_set_projection_tracking(1);
|
||||
CPUState clipped_cpu{}; clipped_cpu.read_word=cpu_projection_read;
|
||||
const uint32_t clipped_addr=0x80018000u, copied_addr=clipped_addr+4;
|
||||
const uint32_t clipped_word=(uint16_t)-1023|((uint32_t)(uint16_t)1023<<16);
|
||||
g_test_cpu_projection_word=clipped_word;
|
||||
CHECK(psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr,clipped_word,-6000,8000,320,320));
|
||||
PGXPProjection cp;
|
||||
CHECK(pgxp_load_projection(clipped_addr,clipped_word,&cp));
|
||||
CHECK(cp.x==-6000.f*320.f && cp.y==8000.f*320.f && cp.z==320 && cp.near_z==320);
|
||||
psx_pgxp_load(&clipped_cpu,0x8c020000u,clipped_addr,clipped_word);
|
||||
psx_pgxp_store(&clipped_cpu,0xac020000u,copied_addr,clipped_word);
|
||||
CHECK(pgxp_load_projection(copied_addr,clipped_word,&cp));
|
||||
CHECK(!pgxp_load_projection(copied_addr,clipped_word^1,&cp));
|
||||
CHECK(!psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr+1,clipped_word,-6000,8000,320,320));
|
||||
CHECK(!psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr,clipped_word,-200,8000,320,320));
|
||||
CHECK(!psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr,clipped_word,-6000,8000,319,320));
|
||||
CHECK(!psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr,clipped_word,-1048577,8000,320,320));
|
||||
g_test_cpu_projection_word^=1;
|
||||
CHECK(!psx_mod_record_cpu_projected_vertex(&clipped_cpu,clipped_addr,clipped_word,-6000,8000,320,320));
|
||||
pgxp_invalidate_word(copied_addr);CHECK(!pgxp_load_projection(copied_addr,clipped_word,&cp));
|
||||
pgxp_invalidate_all();CHECK(!pgxp_load_projection(clipped_addr,clipped_word,&cp));
|
||||
pgxp_set_projection_tracking(0);
|
||||
std::puts("PASS: CPU near intersections preserve full-word projection and writer invalidation");
|
||||
std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -165,6 +165,21 @@ int main(void) {
|
||||
fixture_projective_supported=1;gp0_cmd_source_addr=UINT32_MAX;
|
||||
assert(!native_wide_projective_draw());
|
||||
assert(fixture_projective_draws==2);
|
||||
/* The retail CPU clipper uses -1023, with an explicit full projection
|
||||
* supplied by its verified producer. It must reach normal host clipping. */
|
||||
fixture_projective_draws=0;gp0_cmd_source_addr=0x10004u;
|
||||
for(unsigned i=0;i<4;++i) {
|
||||
unsigned k=1+3*i;
|
||||
gp0_cmd_buf[k]=pack_vertex(i==1?-1023:100+i*30,40+i*20);
|
||||
fixture_homogeneous[i].addr=gp0_cmd_source_addr+4*k;
|
||||
fixture_homogeneous[i].word=gp0_cmd_buf[k];
|
||||
fixture_homogeneous[i].p=(PGXPProjection){(i==1?-6000.f:100.f+i*30.f)*500.f,
|
||||
(40.f+i*20.f)*500.f,500,320};fixture_homogeneous[i].valid=1;
|
||||
}
|
||||
assert(native_wide_projective_draw() && fixture_projective_draws==2);
|
||||
assert(fixture_projective_vertices[1].x==-6000.f*500.f);
|
||||
fixture_homogeneous[3].valid=0;assert(!native_wide_projective_draw());
|
||||
assert(fixture_projective_draws==2);
|
||||
puts("native_wide_projection_test: PASS");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user