mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
Widescreen precision: exact-sign NCLIP + GTE clamp rescue; backend-invariant entry hooks
Three changes that ride the existing PGXP dataflow engine: - Arm the PGXP engine whenever a precision consumer needs it, not only when a correction feature is on. gpu_pgxp_rederive_enable() derives the arm from texture correction OR geometry correction OR the widescreen precise-NCLIP opt-in, and every setter re-derives. Previously disabling both corrections silently disabled the shadows precise_nclip depends on. - gte_nclip: when the sub-pixel shadow of all three SXY FIFO entries is live, take the winding SIGN from the exact double cross product and keep the hardware integer magnitude. The X-squash scales areas by a positive factor, so only integer rounding ever flips a sign; this removes both the vanished trackside signs and the per-frame tile flicker at wide aspects without any epsilon tolerance. The producer clamps shadow coords to +/-4096 px so a projection blowup cannot wrap the 16.16 int32. - Clamp rescue (prepare_precise_triangle): a vertex parked exactly on a GTE saturation rail (+/-1024) is hardware-mangled; when the validated dataflow shadow knows its true position, rasterize with that instead. Kills the "polygon spike" streak class that 4:3 framing plus CRT overscan hid. Every in-range vertex stays native, so normal geometry is bit-identical. - dirty_ram_interp: fire psx_mod_function_entry on interp dispatch so function-entry mod hooks do not depend on which backend executes a mod-patched page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2811,6 +2811,17 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
|
||||
* pages overwritten by a runtime overlay (Tomba 2), not just [FLOOR, RAM). */
|
||||
int allow_local_dirty_flow = phys_is_overlay_flow_region(phys);
|
||||
|
||||
/* Backend-invariant mod_function_entry hooks: generated code fires
|
||||
* psx_mod_function_entry at listed function entries, but a mod-patched
|
||||
* (dirty) page runs HERE instead and would silently skip them. Fire the
|
||||
* same hook on interp dispatch so the contract does not depend on which
|
||||
* backend executes the page. The runtime filters by exact address, so
|
||||
* this is a short scan over the (tiny) registered-plugin list. */
|
||||
{
|
||||
extern void psx_mod_function_entry(CPUState* cpu, uint32_t address);
|
||||
psx_mod_function_entry(cpu, addr);
|
||||
}
|
||||
|
||||
/* Per-PC entry counter (visible via dirty_ram_stats). */
|
||||
DirtyRamPcEntry *pc_entry = pc_table_get_or_insert(phys);
|
||||
if (pc_entry) pc_entry->hits++;
|
||||
|
||||
+70
-5
@@ -219,7 +219,13 @@ static int ws_gameplay_state_value_count = 0;
|
||||
* this many consecutive frames (save/options/memory-card) — reverts to 4:3. */
|
||||
#define WS_GTE_GAME_MODE_HYSTERESIS 45u
|
||||
void gpu_ws_set_gte_game_mode(int on) { ws_gte_game_mode_cfg = on ? 1 : 0; }
|
||||
void gpu_ws_set_precise_nclip(int on) { ws_precise_nclip_cfg = on ? 1 : 0; }
|
||||
void gpu_pgxp_rederive_enable(void);
|
||||
void gpu_ws_set_precise_nclip(int on) {
|
||||
ws_precise_nclip_cfg = on ? 1 : 0;
|
||||
/* Precise NCLIP and clamp rescue consume PGXP dataflow shadows even when
|
||||
* both correction features are off — re-derive the engine arm. */
|
||||
gpu_pgxp_rederive_enable();
|
||||
}
|
||||
int gpu_ws_precise_nclip_enabled(void) { return ws_precise_nclip_cfg && ws_active(); }
|
||||
void gpu_ws_set_gameplay_state_gate(uint32_t addr,
|
||||
const uint32_t *values, int nvalues) {
|
||||
@@ -3386,12 +3392,19 @@ static int s_texture_correction_enabled = 0;
|
||||
extern int gte_precision_load_word(uint32_t addr, uint32_t packed,
|
||||
int32_t *x16, int32_t *y16, uint16_t *z);
|
||||
|
||||
/* The PGXP dataflow engine feeds the two correction features AND the
|
||||
* widescreen precision consumers (precise NCLIP, clamp rescue). Arm it while
|
||||
* any of them is on. Every setter re-derives through here so init order and
|
||||
* runtime toggles (debug server) can never leave the engine stale. */
|
||||
void gpu_pgxp_rederive_enable(void) {
|
||||
pgxp_set_enabled(s_texture_correction_enabled ||
|
||||
gte_geometry_correction_enabled() ||
|
||||
ws_precise_nclip_cfg);
|
||||
}
|
||||
|
||||
void gpu_texture_correction_set(int enabled) {
|
||||
s_texture_correction_enabled = enabled ? 1 : 0;
|
||||
/* The PGXP dataflow engine feeds BOTH corrections; arm it while either
|
||||
* is on (geometry correction is toggled in gte.cpp, so re-derive here). */
|
||||
pgxp_set_enabled(s_texture_correction_enabled ||
|
||||
gte_geometry_correction_enabled());
|
||||
gpu_pgxp_rederive_enable();
|
||||
}
|
||||
|
||||
int gpu_texture_correction_enabled(void) {
|
||||
@@ -3436,10 +3449,62 @@ int gpu_pgxp_census_dump(uint32_t *frame_out, uint32_t *tris, uint32_t *clean,
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Clamp rescue (widescreen precision, rides the precise-NCLIP opt-in): a
|
||||
* vertex parked exactly on a GTE saturation rail (+/-1024 screen units) is
|
||||
* hardware-mangled — its true projection is somewhere far off-frame, and
|
||||
* rasterizing the clamped position folds that error INTO the frame as the
|
||||
* classic PS1 "polygon spike" (a sliver streaking across the screen). 4:3
|
||||
* framing plus CRT overscan hid most of these; a wide aspect stares right at
|
||||
* them. When the address-keyed dataflow shadow knows the true sub-pixel
|
||||
* position (clamped to +/-4096 px at the producer), substitute it for railed
|
||||
* vertices only — every in-range vertex stays native, so normal geometry is
|
||||
* bit-identical and shared edges cannot crack. */
|
||||
static int ws_clamp_rescue_vertex(uint32_t addr, uint32_t word,
|
||||
int32_t raw_x, int32_t raw_y,
|
||||
int32_t *px, int32_t *py) {
|
||||
if (raw_x != -1024 && raw_x != 1023 && raw_y != -1024 && raw_y != 1023)
|
||||
return 0;
|
||||
uint16_t sz;
|
||||
if (pgxp_get_precise_vertex(addr, word, raw_x, raw_y, px, py, &sz)
|
||||
!= PGXP_SRC_DATAFLOW)
|
||||
return 0;
|
||||
/* Only substitute when the shadow actually differs — a legit vertex that
|
||||
* genuinely sits on the rail keeps its native position. */
|
||||
return ((*px >> 16) != raw_x) || ((*py >> 16) != raw_y);
|
||||
}
|
||||
|
||||
static void prepare_precise_triangle(int i0, int i1, int i2,
|
||||
const int32_t vx[3], const int32_t vy[3]) {
|
||||
gr_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
|
||||
if (!gte_geometry_correction_enabled()) {
|
||||
if (gpu_ws_precise_nclip_enabled() &&
|
||||
gp0_cmd_source_addr != 0xFFFFFFFFu) {
|
||||
const int idx[3] = { i0, i1, i2 };
|
||||
int32_t fx[3], fy[3];
|
||||
int any_rescued = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
uint32_t word = gp0_cmd_buf[idx[i]];
|
||||
int32_t raw_x, raw_y;
|
||||
parse_vertex(word, &raw_x, &raw_y);
|
||||
uint32_t addr = gp0_cmd_source_addr + (uint32_t)idx[i] * 4u;
|
||||
int32_t px, py;
|
||||
if (ws_clamp_rescue_vertex(addr, word, raw_x, raw_y, &px, &py)) {
|
||||
any_rescued = 1;
|
||||
fx[i] = (int32_t)((int64_t)px +
|
||||
(int64_t)(vx[i] - raw_x) * 65536);
|
||||
fy[i] = (int32_t)((int64_t)py +
|
||||
(int64_t)(vy[i] - raw_y) * 65536);
|
||||
} else {
|
||||
fx[i] = vx[i] << 16;
|
||||
fy[i] = vy[i] << 16;
|
||||
}
|
||||
}
|
||||
if (any_rescued) {
|
||||
gr_set_precise_triangle(1, fx[0],fy[0], fx[1],fy[1],
|
||||
fx[2],fy[2]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
gr_set_precise_triangle(0, 0,0, 0,0, 0,0);
|
||||
return;
|
||||
}
|
||||
|
||||
+37
-15
@@ -312,6 +312,8 @@ static inline int64_t geom_slot(uint32_t packed) {
|
||||
return (int64_t)(y + GEOM_BIAS) * GEOM_AXIS + (x + GEOM_BIAS);
|
||||
}
|
||||
|
||||
extern "C" void gpu_pgxp_rederive_enable(void);
|
||||
|
||||
extern "C" void gte_geometry_correction_set(int enabled) {
|
||||
s_geom_enabled = enabled ? 1 : 0;
|
||||
s_geom_hits = 0;
|
||||
@@ -324,6 +326,7 @@ extern "C" void gte_geometry_correction_set(int enabled) {
|
||||
if (!s_geom_cache) s_geom_enabled = 0; /* fail closed: stay faithful */
|
||||
}
|
||||
gte_geom_generation_advance();
|
||||
gpu_pgxp_rederive_enable();
|
||||
}
|
||||
|
||||
/* Lookup census for the debug server: attempted, hit, and the two miss classes.
|
||||
@@ -888,9 +891,17 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0, uint32_t instr)
|
||||
int64_t sx = sx16 >> 16;
|
||||
int64_t sy = sy16 >> 16;
|
||||
gte->push_sxy(sx, sy);
|
||||
if (!s_gte_replay_sandbox)
|
||||
pgxp_gte_push_sxy((int32_t)sx16, (int32_t)sy16, gte->SZ[3],
|
||||
if (!s_gte_replay_sandbox) {
|
||||
/* Shadow the pre-truncation position, clamped to +/-4096 px so an
|
||||
* extreme projection blowup can't wrap the 16.16 int32 and land a
|
||||
* garbage shadow back inside the frame. Well past the +/-1024
|
||||
* hardware rails either way, so direction survives for clamp rescue. */
|
||||
const int64_t kLim = (int64_t)4096 << 16;
|
||||
int64_t cx16 = sx16 < -kLim ? -kLim : (sx16 > kLim - 1 ? kLim - 1 : sx16);
|
||||
int64_t cy16 = sy16 < -kLim ? -kLim : (sy16 > kLim - 1 ? kLim - 1 : sy16);
|
||||
pgxp_gte_push_sxy((int32_t)cx16, (int32_t)cy16, gte->SZ[3],
|
||||
(uint32_t)gte->SXY[2]);
|
||||
}
|
||||
geom_note((uint32_t)gte->SXY[2], sx16, sy16);
|
||||
|
||||
// Step 5: Depth cueing (MAC0/IR0) — only for last vertex of RTPT or RTPS
|
||||
@@ -929,23 +940,34 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
int32_t sy1 = static_cast<int16_t>(gte->SXY[1] >> 16);
|
||||
int32_t sx2 = static_cast<int16_t>(gte->SXY[2] & 0xFFFF);
|
||||
int32_t sy2 = static_cast<int16_t>(gte->SXY[2] >> 16);
|
||||
int32_t px0 = 0, py0 = 0, px1 = 0, py1 = 0, px2 = 0, py2 = 0;
|
||||
if (gpu_ws_precise_nclip_enabled() &&
|
||||
pgxp_get_gte_sxy(0, &px0, &py0) &&
|
||||
pgxp_get_gte_sxy(1, &px1, &py1) &&
|
||||
pgxp_get_gte_sxy(2, &px2, &py2)) {
|
||||
sx0 = px0 >> 16;
|
||||
sy0 = py0 >> 16;
|
||||
sx1 = px1 >> 16;
|
||||
sy1 = py1 >> 16;
|
||||
sx2 = px2 >> 16;
|
||||
sy2 = py2 >> 16;
|
||||
}
|
||||
int64_t mac0 = (int64_t)sx0 * (sy1 - sy2) +
|
||||
(int64_t)sx1 * (sy2 - sy0) +
|
||||
(int64_t)sx2 * (sy0 - sy1);
|
||||
gte->check_mac0_overflow(mac0);
|
||||
gte->MAC0 = static_cast<int32_t>(mac0);
|
||||
int32_t out = static_cast<int32_t>(mac0);
|
||||
/* Widescreen precise NCLIP: the X-squash scales winding areas by a
|
||||
* positive factor, but integer SXY rounding flips the sign of thin
|
||||
* glancing faces (trackside signs vanishing / flickering only at wide
|
||||
* aspects). When the full sub-pixel shadow of all three vertices is
|
||||
* live, take the SIGN from the exact cross product; keep the hardware
|
||||
* magnitude so NCLIP-magnitude consumers (LOD heuristics) are untouched.
|
||||
* A sign disagreement replaces a near-zero hardware value, so the +/-1
|
||||
* substitute is honest about the face's true orientation. */
|
||||
int32_t px0, py0, px1, py1, px2, py2;
|
||||
if (gpu_ws_precise_nclip_enabled() &&
|
||||
pgxp_get_gte_sxy(0, &px0, &py0) &&
|
||||
pgxp_get_gte_sxy(1, &px1, &py1) &&
|
||||
pgxp_get_gte_sxy(2, &px2, &py2)) {
|
||||
const double fx0 = px0 / 65536.0, fy0 = py0 / 65536.0;
|
||||
const double fx1 = px1 / 65536.0, fy1 = py1 / 65536.0;
|
||||
const double fx2 = px2 / 65536.0, fy2 = py2 / 65536.0;
|
||||
const double cross = fx0 * (fy1 - fy2) +
|
||||
fx1 * (fy2 - fy0) +
|
||||
fx2 * (fy0 - fy1);
|
||||
if (cross > 0.0 && out <= 0) out = 1;
|
||||
else if (cross < 0.0 && out >= 0) out = -1;
|
||||
}
|
||||
gte->MAC0 = out;
|
||||
gte->set_error_flag();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user