gte: fov_scale uses scaled H in PGXP clipper, keeps H=0, off in netplay; RTP ring keeps canonical H

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-04 10:55:58 -07:00
parent b6a659ca4a
commit cf2bdbb6fc
2 changed files with 64 additions and 9 deletions
+22 -8
View File
@@ -7,6 +7,7 @@
#include <cstdlib>
#include <cstdio>
#include <cstring>
extern "C" int psx_netplay_active(void);
extern "C" uint32_t psx_read_word(uint32_t addr);
extern "C" int gpu_ws_precise_nclip_enabled(void);
@@ -596,6 +597,7 @@ struct GteRtpRec {
int16_t RT[9];
int32_t TR[3];
uint16_t H; int32_t OFX, OFY;
uint16_t Hs; /* H after fov_scale (== H when identity); SXY0..2 used Hs */
int32_t SXY0, SXY1, SXY2;
uint16_t SZ1, SZ2, SZ3;
uint32_t FLAG;
@@ -640,7 +642,7 @@ static void gte_rtp_record(const GTEState* g, uint32_t cmd) {
for (int i = 0; i < 3; i++) { e->V0[i]=g->V0[i]; e->V1[i]=g->V1[i]; e->V2[i]=g->V2[i]; }
for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) e->RT[r*3+c]=g->RT[r][c];
for (int i = 0; i < 3; i++) e->TR[i]=g->TR[i];
e->H=(uint16_t)gte_h_scaled(g); e->OFX=g->OFX; e->OFY=g->OFY;
e->H=g->H; e->Hs=(uint16_t)gte_h_scaled(g); e->OFX=g->OFX; e->OFY=g->OFY;
e->SXY0=g->SXY[0]; e->SXY1=g->SXY[1]; e->SXY2=g->SXY[2];
e->SZ1=g->SZ[1]; e->SZ2=g->SZ[2]; e->SZ3=g->SZ[3];
e->FLAG=g->FLAG;
@@ -814,12 +816,12 @@ extern "C" int gte_latch_dump_json(char* out, int outsz, int max_count) {
if (pos>outsz-700) break;
pos+=snprintf(out+pos,outsz-pos,
"%s{\"frame\":%u,\"ra\":\"0x%08X\",\"cmd\":\"0x%08X\","
"\"RT\":[%d,%d,%d,%d,%d,%d,%d,%d,%d],\"TR\":[%d,%d,%d],\"H\":%u,"
"\"RT\":[%d,%d,%d,%d,%d,%d,%d,%d,%d],\"TR\":[%d,%d,%d],\"H\":%u,\"Hs\":%u,"
"\"V0\":[%d,%d,%d],\"V1\":[%d,%d,%d],\"V2\":[%d,%d,%d],"
"\"S0\":[%d,%d],\"S1\":[%d,%d],\"S2\":[%d,%d],\"SZ\":[%u,%u,%u],\"FLAG\":\"0x%08X\"}",
emitted?",":"", e->frame, e->caller_ra, e->cmd,
e->RT[0],e->RT[1],e->RT[2],e->RT[3],e->RT[4],e->RT[5],e->RT[6],e->RT[7],e->RT[8],
e->TR[0],e->TR[1],e->TR[2],(unsigned)e->H,
e->TR[0],e->TR[1],e->TR[2],(unsigned)e->H,(unsigned)e->Hs,
e->V0[0],e->V0[1],e->V0[2], e->V1[0],e->V1[1],e->V1[2], e->V2[0],e->V2[1],e->V2[2],
gte_sxx(e->SXY0),gte_syy(e->SXY0),gte_sxx(e->SXY1),gte_syy(e->SXY1),
gte_sxx(e->SXY2),gte_syy(e->SXY2),(unsigned)e->SZ1,(unsigned)e->SZ2,(unsigned)e->SZ3,e->FLAG);
@@ -849,13 +851,13 @@ extern "C" int gte_rtp_ring_dump_json(char* out, int outsz, int max_count,
"%s{\"seq\":%u,\"frame\":%u,\"ra\":\"0x%08X\",\"cmd\":\"0x%08X\","
"\"V0\":[%d,%d,%d],\"V1\":[%d,%d,%d],\"V2\":[%d,%d,%d],"
"\"RT\":[%d,%d,%d,%d,%d,%d,%d,%d,%d],\"TR\":[%d,%d,%d],"
"\"H\":%u,\"OFX\":%d,\"OFY\":%d,"
"\"H\":%u,\"Hs\":%u,\"OFX\":%d,\"OFY\":%d,"
"\"S0\":[%d,%d],\"S1\":[%d,%d],\"S2\":[%d,%d],"
"\"SZ\":[%u,%u,%u],\"FLAG\":\"0x%08X\"}",
emitted?",":"", e->seq, e->frame, e->caller_ra, e->cmd,
e->V0[0],e->V0[1],e->V0[2], e->V1[0],e->V1[1],e->V1[2], e->V2[0],e->V2[1],e->V2[2],
e->RT[0],e->RT[1],e->RT[2],e->RT[3],e->RT[4],e->RT[5],e->RT[6],e->RT[7],e->RT[8],
e->TR[0],e->TR[1],e->TR[2], (unsigned)e->H, e->OFX, e->OFY,
e->TR[0],e->TR[1],e->TR[2], (unsigned)e->H, (unsigned)e->Hs, e->OFX, e->OFY,
sxx(e->SXY0),syy(e->SXY0), sxx(e->SXY1),syy(e->SXY1), sxx(e->SXY2),syy(e->SXY2),
(unsigned)e->SZ1,(unsigned)e->SZ2,(unsigned)e->SZ3, e->FLAG);
emitted++;
@@ -896,6 +898,17 @@ extern "C" void gte_set_fov_scale(int num, int den) {
static int32_t gte_h_scaled(const GTEState* gte) {
if (s_h_scale_num == s_h_scale_den) return gte->H;
if (gte->H == 0) return 0; // a guest H of 0 is a real (degenerate) projection
// Both peers must compute identical SXY/MAC/FLAG: the scale is a local
// display option, so it is forced to identity for the whole netplay session.
if (psx_netplay_active()) {
static bool warned = false;
if (!warned) {
warned = true;
fprintf(stderr, "psxrecomp: fov_scale ignored while netplay is active\n");
}
return gte->H;
}
int64_t h = (int64_t)gte->H * s_h_scale_num / s_h_scale_den;
if (h < 1) h = 1;
if (h > 0xFFFF) h = 0xFFFF;
@@ -1040,15 +1053,16 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0, uint32_t instr)
pgxp_gte_push_sxy(px16, py16, gte->SZ[3], (uint32_t)gte->SXY[2]);
if (pgxp_projection_tracking() && shift == 12 && !lm) {
const double z = (double)(mac3 >> 12);
double hx = (double)gte->MAC1 * gte->H;
const double h_proj = (double)gte_h_scaled(gte);
double hx = (double)gte->MAC1 * h_proj;
if (do_squash) hx = hx * s_ws_xnum / s_ws_xden;
else if (s_ws_dome_on && s_ws_dome_num != s_ws_dome_den &&
!gpu_ws_present_native_43() && gte->SZ[3] >= s_ws_far_threshold)
hx = hx * s_ws_dome_num / s_ws_dome_den;
PGXPProjection projection = {
(float)(gte->OFX / 65536.0 * z + hx),
(float)(gte->OFY / 65536.0 * z + (double)gte->MAC2 * gte->H),
(float)z, (float)gte->H / 2.0f};
(float)(gte->OFY / 65536.0 * z + (double)gte->MAC2 * h_proj),
(float)z, (float)h_proj / 2.0f};
pgxp_gte_set_projection(&projection);
}
}
+42 -1
View File
@@ -2,9 +2,9 @@
#include "gte.h"
#include "pgxp.h"
#include "projection_scale.hpp"
#include "render_pass_projection.h"
#include <limits>
extern "C" void gte_set_fov_scale(int, int);
#include "render_pass_projection.h"
#include <array>
#include <cmath>
@@ -65,6 +65,8 @@ extern "C" void gpu_pgxp_rederive_enable(void) {}
static int g_test_shadow_diff = 0;
extern "C" int psx_overlay_shadow_diff_active(void) { return g_test_shadow_diff; }
extern "C" void psx_ws_note_gte_project(int) {}
static int g_test_netplay_active = 0;
extern "C" int psx_netplay_active(void) { return g_test_netplay_active; }
extern "C" {
uint64_t s_frame_count = 0;
}
@@ -1116,6 +1118,26 @@ int test_projection_scale() {
CPUState reset=seed; gte_set_fov_scale(ratio.first,ratio.second); gte_execute(&reset,cmd);
CHECK(same_gte(stock,reset));
}
/* Guest H == 0 is a real (degenerate) projection: it must stay 0, not be
* clamped to 1, under any scale. */
{
CPUState h0=seed; h0.gte_ctrl[26]=0;
CPUState h0_stock=h0; gte_set_fov_scale(1,1); gte_execute(&h0_stock,cmd);
CPUState h0_scaled=h0; gte_set_fov_scale(1,2); gte_execute(&h0_scaled,cmd);
CHECK(h0_scaled.gte_ctrl[26] == 0);
CHECK(same_gte(h0_stock,h0_scaled));
gte_set_fov_scale(1000,500); h0_scaled=h0; gte_execute(&h0_scaled,cmd);
CHECK(same_gte(h0_stock,h0_scaled));
}
/* Netplay forces identity so peers cannot diverge on SXY/MAC/FLAG. */
{
g_test_netplay_active = 1;
CPUState np=seed; gte_set_fov_scale(1,2); gte_execute(&np,cmd);
g_test_netplay_active = 0;
CHECK(same_gte(stock,np));
CPUState off=seed; gte_set_fov_scale(1,2); gte_execute(&off,cmd);
CHECK(!same_gte(stock,off));
}
gte_set_fov_scale(1,1);
return 0;
}
@@ -1157,6 +1179,25 @@ int main() {
return 1;
}
}
/* The clipper's projection (hx, OFY/MAC2*H term, near_z = H/2) must use the
* same scaled H as the guest's SXY, or culling disagrees with the draw. */
gte_set_fov_scale(1,2);
for (int depth : {1,149,900}) {
GTEState g{};
g.RT[0][0]=g.RT[1][1]=g.RT[2][2]=4096;
g.V0[0]=100;g.V0[1]=30;g.TR[2]=depth;
g.H=300;g.OFX=256*65536;g.OFY=120*65536;
pgxp_set_projection_tracking(1);
PSXRecomp::GTE::gte_rtps(&g,0x0180001u);
PGXPProjection p;
if (!pgxp_get_gte_projection(2,g.SXY[2],&p) ||
p.z!=depth || p.x!=256*depth+15000 || p.y!=120*depth+4500 || p.near_z!=75) {
std::fprintf(stderr,"FAIL scaled-H clipper projection at depth %d\n",depth);
gte_set_fov_scale(1,1);
return 1;
}
}
gte_set_fov_scale(1,1);
pgxp_set_projection_tracking(0);
std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle");
return 0;