mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Clip proven native-wide world faces across camera and screen planes
This commit is contained in:
@@ -1280,3 +1280,29 @@ identity when off; main executable only (overlay code keeps its own code);
|
||||
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
|
||||
|
||||
`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,
|
||||
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
|
||||
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.
|
||||
|
||||
MediEvil II's title hallway provided the camera-crossing reproduction. The
|
||||
GL authority/order fixture passes 197 checks at 1x and 4x; provenance tests
|
||||
cover negative depth, stale packets, identical partial writes, rollback and
|
||||
mode guards. Live captures remove the previously observed triangular holes;
|
||||
the owner confirmed the hallway walls stay intact. Broader map coverage
|
||||
remains separate from this reproduction.
|
||||
|
||||
@@ -240,6 +240,7 @@ extern void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks,
|
||||
* native comparison. This does not change guest-visible GTE state. */
|
||||
extern int gte_nclip_precise_bltz(int32_t native_mac0);
|
||||
extern int gte_nclip_native_wide_sign(int32_t native_mac0, int* sign);
|
||||
extern int gte_nclip_native_wide_previous_sign(int32_t native_mac0, int* sign);
|
||||
extern int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla);
|
||||
|
||||
/* PGXP dataflow-shadowing hook macros (PGXP_LOAD/STORE/ALU/MULDIV/COP2).
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* same struct tag) so this header needs no SDL include. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gpu_projective_clip.h"
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
@@ -331,6 +332,16 @@ int gl_renderer_get_ws_ablate(void);
|
||||
/* Cumulative textured fraction of scene primitives since boot (flat vs textured
|
||||
* batching decision). Sets *out_tex_frac; returns total prim count. */
|
||||
uint64_t gl_renderer_perf_prim_split(double *out_tex_frac);
|
||||
/* Counts proven projected world triangles admitted past the hardware size
|
||||
* reject. Diagnostic only; they use the normal canonical and mirror passes. */
|
||||
void gl_renderer_note_wide_triangle_recovery(int enabled);
|
||||
uint64_t gl_renderer_wide_triangle_recovery_count(void);
|
||||
/* Exact-provenance world polygons only. Returns zero when the GPU context or
|
||||
* authority policy cannot support clipping; the caller then uses its stock path. */
|
||||
int gl_renderer_projective_supported(void);
|
||||
void gl_renderer_draw_projected_triangle(const PSXProjectedVertex vertices[3],
|
||||
uint16_t texpage, uint16_t clut_x, uint16_t clut_y, int raw, int semi,
|
||||
int perspective);
|
||||
/* Cumulative textured-batch diagnostics: total, then flushes caused by
|
||||
* isolation, blend-mode, mask, filter, backdrop-gate, texture-window, capacity. */
|
||||
void gl_renderer_batch_diag(uint64_t out[8]);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef PSX_GPU_PROJECTIVE_CLIP_H
|
||||
#define PSX_GPU_PROJECTIVE_CLIP_H
|
||||
|
||||
/* Clip before dividing by signed camera depth. Attributes follow the same
|
||||
* edge parameter, so clipped triangles retain their original texture plane. */
|
||||
typedef struct PSXProjectedVertex {
|
||||
double x, y, z, u, v, r, g, b;
|
||||
} PSXProjectedVertex;
|
||||
|
||||
static inline PSXProjectedVertex psx_projective_lerp(
|
||||
PSXProjectedVertex a, PSXProjectedVertex b, double t) {
|
||||
PSXProjectedVertex p;
|
||||
#define LERP(f) p.f = a.f + (b.f-a.f)*t
|
||||
LERP(x); LERP(y); LERP(z); LERP(u); LERP(v);
|
||||
LERP(r); LERP(g); LERP(b);
|
||||
#undef LERP
|
||||
return p;
|
||||
}
|
||||
static inline double psx_projective_distance(PSXProjectedVertex p, int plane,
|
||||
double left, double right, double top, double bottom) {
|
||||
switch (plane) {
|
||||
case 0: return p.z - 1.0;
|
||||
case 1: return p.x - left*p.z;
|
||||
case 2: return right*p.z - p.x;
|
||||
case 3: return p.y - top*p.z;
|
||||
default: return bottom*p.z - p.y;
|
||||
}
|
||||
}
|
||||
static inline int psx_projective_clip_triangle(const PSXProjectedVertex in[3],
|
||||
PSXProjectedVertex out[12], double left, double right, double top, double bottom) {
|
||||
PSXProjectedVertex a[12], b[12];
|
||||
int n=3;
|
||||
for (int i=0;i<3;++i) a[i]=in[i];
|
||||
for (int plane=0;plane<5 && n;++plane) {
|
||||
int m=0;
|
||||
PSXProjectedVertex prev=a[n-1];
|
||||
double dp=psx_projective_distance(prev,plane,left,right,top,bottom);
|
||||
for (int i=0;i<n;++i) {
|
||||
PSXProjectedVertex cur=a[i];
|
||||
double dc=psx_projective_distance(cur,plane,left,right,top,bottom);
|
||||
if ((dp>=0)!=(dc>=0))
|
||||
b[m++]=psx_projective_lerp(prev,cur,dp/(dp-dc));
|
||||
if (dc>=0) b[m++]=cur;
|
||||
prev=cur; dp=dc;
|
||||
}
|
||||
n=m;
|
||||
for (int i=0;i<n;++i) a[i]=b[i];
|
||||
}
|
||||
for (int i=0;i<n;++i) out[i]=a[i];
|
||||
return n;
|
||||
}
|
||||
#endif
|
||||
@@ -153,11 +153,21 @@ int32_t psx_mod_widescreen_view_x_margin(void);
|
||||
* native-wide gameplay. Requires exact packet-address/word provenance and
|
||||
* depth; never changes guest SXY, vertical coordinates, 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. */
|
||||
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
|
||||
* MAC0 and flags remain architectural. Empty registration disables the sites. */
|
||||
void psx_mod_set_native_wide_nclip_sites(const uint32_t* addresses,
|
||||
const uint32_t* expected, int count);
|
||||
/* After registering sites, bind a verified first-of-two quad branch to the
|
||||
* preceding NCLIP result. Exact address/word must match an existing site;
|
||||
* registering the base site list again resets every site to the latest result. */
|
||||
void psx_mod_set_native_wide_nclip_previous_site(uint32_t address, uint32_t expected);
|
||||
|
||||
/* Mark a guest GPU packet (P_TAG address) as persistent screen-space HUD.
|
||||
* edge = -1 left, +1 right, 0 clears a reused packet's tag. The native-wide
|
||||
|
||||
@@ -162,6 +162,16 @@ void pgxp_suppress_end(void);
|
||||
* sees. Shifts the shadow FIFO exactly like push_sxy (regs 12..15). */
|
||||
void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3, uint32_t packed);
|
||||
|
||||
/* Host-only homogeneous screen projection, before SZ/SXY/divider saturation.
|
||||
* x/z and y/z are screen pixels; signed z retains camera-plane crossings.
|
||||
* Exact full-word copies carry it. Arithmetic/partial writes invalidate it. */
|
||||
typedef struct PGXPProjection { float x, y, z, near_z; } PGXPProjection;
|
||||
void pgxp_set_projection_tracking(int enabled);
|
||||
int pgxp_projection_tracking(void);
|
||||
void pgxp_gte_set_projection(const PGXPProjection *projection);
|
||||
int pgxp_load_projection(uint32_t addr, uint32_t packed, PGXPProjection *out);
|
||||
int pgxp_get_gte_projection(uint32_t index, uint32_t packed, PGXPProjection *out);
|
||||
|
||||
/* Current SXY FIFO shadow (index 0..3 selects GTE data regs 12..15).
|
||||
* Returns nonzero when the shadow is live and carries X/Y precision. */
|
||||
int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16);
|
||||
|
||||
@@ -8177,10 +8177,12 @@ static void handle_ws_nw(int id, const char *json)
|
||||
GpuWsDebug ws;
|
||||
gpu_ws_get_debug(&ws);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"native_wide\":%d,\"mode\":%d,\"nw_extra\":%d,"
|
||||
"\"projection_correction\":%d,\"projection_vertices\":%llu,\"nclip_rescues\":%llu}",
|
||||
"\"projection_correction\":%d,\"projection_vertices\":%llu,\"nclip_rescues\":%llu,"
|
||||
"\"oversize_triangles\":%llu}",
|
||||
id, psx_ws_get_native_wide(), ws.mode, ws.nw_extra,
|
||||
correction, (unsigned long long)corrected_vertices,
|
||||
(unsigned long long)gpu_ws_native_wide_nclip_rescues());
|
||||
(unsigned long long)gpu_ws_native_wide_nclip_rescues(),
|
||||
(unsigned long long)gl_renderer_wide_triangle_recovery_count());
|
||||
}
|
||||
|
||||
/* Live scanline post-process toggle (A/B): `scanline on=<0|1> pct=<0..100>`.
|
||||
|
||||
+121
-13
@@ -897,6 +897,7 @@ int psx_ws_masked_reject_site(uint32_t pc, uint32_t instr,
|
||||
}
|
||||
|
||||
static WsMaskedRejectSite ws_nclip_sites[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static uint8_t ws_nclip_previous[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static int ws_nclip_site_count;
|
||||
static uint64_t ws_nclip_rescues;
|
||||
uint64_t gpu_ws_native_wide_nclip_rescues(void) { return ws_nclip_rescues; }
|
||||
@@ -908,18 +909,33 @@ void psx_mod_set_native_wide_nclip_sites(const uint32_t* addresses,
|
||||
if (!((op == 1 && rt <= 1) || ((op == 6 || op == 7) && rt == 0))) abort();
|
||||
ws_nclip_sites[i].address = addresses[i] & 0x1FFFFFFFu;
|
||||
ws_nclip_sites[i].expected = expected[i];
|
||||
ws_nclip_previous[i] = 0;
|
||||
}
|
||||
ws_nclip_site_count = count;
|
||||
ws_nclip_rescues = 0;
|
||||
}
|
||||
void psx_mod_set_native_wide_nclip_previous_site(uint32_t address, uint32_t expected) {
|
||||
for (int i = 0; i < ws_nclip_site_count; ++i) {
|
||||
if (ws_nclip_sites[i].address == (address & 0x1FFFFFFFu) &&
|
||||
ws_nclip_sites[i].expected == expected) {
|
||||
ws_nclip_previous[i] = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
abort();
|
||||
}
|
||||
extern int gte_nclip_native_wide_sign(int32_t mac0, int* sign);
|
||||
extern int gte_nclip_native_wide_previous_sign(int32_t mac0, int* sign);
|
||||
int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla) {
|
||||
if (psx_ws_x_margin() <= 0 || !gpu_ws_precise_nclip_enabled()) return vanilla;
|
||||
for (int i = 0; i < ws_nclip_site_count; ++i) {
|
||||
if (ws_nclip_sites[i].address != (pc & 0x1FFFFFFFu) ||
|
||||
ws_nclip_sites[i].expected != instr) continue;
|
||||
int sign;
|
||||
if (!gte_nclip_native_wide_sign(mac0, &sign)) return vanilla;
|
||||
const int valid = ws_nclip_previous[i] ?
|
||||
gte_nclip_native_wide_previous_sign(mac0, &sign) :
|
||||
gte_nclip_native_wide_sign(mac0, &sign);
|
||||
if (!valid) return vanilla;
|
||||
const uint32_t op = instr >> 26;
|
||||
const int result = op == 6 ? sign <= 0 : op == 7 ? sign > 0 :
|
||||
((instr >> 16) & 1) ? sign >= 0 : sign < 0;
|
||||
@@ -4039,6 +4055,15 @@ int gpu_ws_native_wide_projection_correction(uint64_t *vertices) {
|
||||
return s_native_wide_projection_correction;
|
||||
}
|
||||
|
||||
static int gpu_ws_projective_enabled(void) {
|
||||
return pgxp_projection_tracking() && s_native_wide_projection_correction && ws_native_wide_active() &&
|
||||
ws_nw_extra() > 0 && gr_backend() == GR_BACKEND_OPENGL &&
|
||||
gl_renderer_projective_supported();
|
||||
}
|
||||
void psx_mod_set_native_wide_near_clip(int enabled) {
|
||||
pgxp_set_projection_tracking(enabled);
|
||||
}
|
||||
|
||||
/* The PS1 clamps each projected X independently. At a wider FOV that can
|
||||
* bend an otherwise planar quad, even while both clamped vertices remain
|
||||
* outside the viewport. Recover only a proven saturated projection. An
|
||||
@@ -4064,6 +4089,38 @@ static int native_wide_projection_x(uint32_t addr, uint32_t word,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Original, unsubdivided world faces can exceed the PS1 size limit after the
|
||||
* wider view admits them. Proven projected triangles use normal GL clipping,
|
||||
* with the SAME geometry in canonical and wide passes: margin-only recovery
|
||||
* splits a face at the presentation center-copy edge. Missing/stale provenance,
|
||||
* depth behind the camera, saturated Y and UI retain the hardware reject. */
|
||||
static int gpu_triangle_rejected(const int32_t* x, const int32_t* y,
|
||||
int a, int b, int c) {
|
||||
if (!psx_gpu_triangle_oversize(x, y, a, b, c)) return 0;
|
||||
if (!s_native_wide_projection_correction || !ws_native_wide_active() ||
|
||||
ws_nw_extra() <= 0 || gr_backend() != GR_BACKEND_OPENGL ||
|
||||
gp0_cmd_source_addr == UINT32_MAX) return 1;
|
||||
const uint32_t op = gp0_cmd_buf[0] >> 24;
|
||||
const unsigned stride = 1u + ((op & 4u) != 0) + ((op & 16u) != 0);
|
||||
const int vertices[3] = {a, b, c};
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
const unsigned index = 1u + stride * (unsigned)vertices[i];
|
||||
const uint32_t word = gp0_cmd_buf[index];
|
||||
int32_t px, py, rx, ry;
|
||||
uint16_t z;
|
||||
parse_vertex(word, &rx, &ry);
|
||||
if (!gte_precision_load_word(gp0_cmd_source_addr + 4u * index,
|
||||
word, &px, &py, &z) || !z ||
|
||||
(py >> 16) != ry || ry == -1024 || ry == 1023 ||
|
||||
px <= -4096 * 65536 ||
|
||||
px >= 4096 * 65536 - 1) return 1;
|
||||
const int exact_x = px >> 16;
|
||||
if (rx == -1024 ? exact_x > -1024 :
|
||||
rx == 1023 ? exact_x < 1023 : exact_x != rx) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gpu_texture_correction_set(int enabled) {
|
||||
s_texture_correction_enabled = enabled ? 1 : 0;
|
||||
gpu_pgxp_rederive_enable();
|
||||
@@ -4091,6 +4148,15 @@ uint32_t gpu_texture_correction_hits(void) {
|
||||
* caller. */
|
||||
static void prepare_precise_triangle(int i0, int i1, int i2,
|
||||
const int32_t vx[3], const int32_t vy[3]) {
|
||||
if (gr_backend() == GR_BACKEND_OPENGL) {
|
||||
const int indices[3] = {i0,i1,i2};
|
||||
int32_t raw_x[3], raw_y[3];
|
||||
for (unsigned i=0; i<3; ++i)
|
||||
parse_vertex(gp0_cmd_buf[indices[i]], &raw_x[i], &raw_y[i]);
|
||||
gl_renderer_note_wide_triangle_recovery(s_native_wide_projection_correction &&
|
||||
ws_native_wide_active() &&
|
||||
psx_gpu_triangle_oversize(raw_x,raw_y,0,1,2));
|
||||
}
|
||||
gr_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
|
||||
const int geometry = gte_geometry_correction_enabled();
|
||||
if (!geometry && !s_native_wide_projection_correction) {
|
||||
@@ -4395,7 +4461,7 @@ static void gp0_exec_mono_tri(void) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
parse_vertex(gp0_cmd_buf[1 + i], &vx[i], &vy[i]);
|
||||
}
|
||||
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vx[i] += draw_offset_x;
|
||||
@@ -4415,8 +4481,8 @@ static void gp0_exec_mono_quad(void) {
|
||||
int32_t vx[4], vy[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
parse_vertex(gp0_cmd_buf[1 + i], &vx[i], &vy[i]);
|
||||
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
|
||||
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
|
||||
int rej_a = gpu_triangle_rejected(vx, vy, 0, 1, 2);
|
||||
int rej_b = gpu_triangle_rejected(vx, vy, 2, 1, 3);
|
||||
if (rej_a && rej_b) return;
|
||||
int radial_mask=ws_nw_radial_mask_transform(vx,vy);
|
||||
int screen_mask=ws_native_wide_active() && gp0_cmd_source_addr != UINT32_MAX &&
|
||||
@@ -4513,7 +4579,7 @@ static void gp0_exec_shaded_tri(void) {
|
||||
c[i] = rgb888_to_rgb555(gp0_cmd_buf[i * 2] & 0xFFFFFFu);
|
||||
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
|
||||
}
|
||||
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vx[i] += draw_offset_x;
|
||||
@@ -4545,8 +4611,8 @@ static void gp0_exec_shaded_quad(void) {
|
||||
c[i] = rgb888_to_rgb555(gp0_cmd_buf[i * 2] & 0xFFFFFFu);
|
||||
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
|
||||
}
|
||||
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
|
||||
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
|
||||
int rej_a = gpu_triangle_rejected(vx, vy, 0, 1, 2);
|
||||
int rej_b = gpu_triangle_rejected(vx, vy, 2, 1, 3);
|
||||
if (rej_a && rej_b) return;
|
||||
int radial_mask=ws_nw_radial_mask_transform(vx,vy);
|
||||
if (!radial_mask) {
|
||||
@@ -4619,8 +4685,47 @@ static void setup_textured_draw(uint32_t color24, int semi_trans, int raw_textur
|
||||
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
|
||||
}
|
||||
|
||||
/* A close corner may cross the camera plane while the face is still visible.
|
||||
* The hardware's clamped H/Z and unsigned SZ cannot describe that polygon.
|
||||
* Use only intact GTE-derived words, clip their signed homogeneous projection,
|
||||
* and submit through the normal ordered GL passes and VRAM authority path. */
|
||||
static int native_wide_projective_draw(void) {
|
||||
if (!gpu_ws_projective_enabled() || gp0_cmd_source_addr == UINT32_MAX) return 0;
|
||||
const unsigned op=gp0_cmd_buf[0]>>24;
|
||||
const unsigned stride=(op&16u)?3u:2u, n=(op&8u)?4u:3u;
|
||||
PSXProjectedVertex vertices[4];
|
||||
int needs_clip=0;
|
||||
for (unsigned i=0;i<n;++i) {
|
||||
const unsigned index=1+stride*i;
|
||||
const uint32_t word=gp0_cmd_buf[index];
|
||||
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;
|
||||
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,
|
||||
p.y+(double)draw_offset_y*p.z,p.z,uv&255,(uv>>8)&255,
|
||||
(color&255)/255.0,((color>>8)&255)/255.0,((color>>16)&255)/255.0};
|
||||
}
|
||||
if (!needs_clip) return 0;
|
||||
const uint16_t clut=gp0_cmd_buf[2]>>16;
|
||||
const uint16_t tpage=gp0_cmd_buf[2+stride]>>16;
|
||||
set_tpage_from_poly(tpage);
|
||||
const int semi=(op&2u)?(int)semi_transparency:-1;
|
||||
gl_renderer_draw_projected_triangle(vertices,tpage&0x1ff,
|
||||
(clut&63)*16,(clut>>6)&511,op&1u,semi,s_texture_correction_enabled);
|
||||
if (n==4) {
|
||||
const PSXProjectedVertex second[3]={vertices[2],vertices[1],vertices[3]};
|
||||
gl_renderer_draw_projected_triangle(second,tpage&0x1ff,
|
||||
(clut&63)*16,(clut>>6)&511,op&1u,semi,s_texture_correction_enabled);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Execute textured triangle (GP0 0x24-0x27) */
|
||||
static void gp0_exec_textured_tri(void) {
|
||||
if (native_wide_projective_draw()) return;
|
||||
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
|
||||
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
@@ -4640,7 +4745,7 @@ static void gp0_exec_textured_tri(void) {
|
||||
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[4] >> 16);
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -4661,6 +4766,7 @@ static void gp0_exec_textured_tri(void) {
|
||||
|
||||
/* Execute textured quad (GP0 0x2C-0x2F) */
|
||||
static void gp0_exec_textured_quad(void) {
|
||||
if (native_wide_projective_draw()) return;
|
||||
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
|
||||
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
@@ -4681,8 +4787,8 @@ static void gp0_exec_textured_quad(void) {
|
||||
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[4] >> 16);
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
|
||||
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
|
||||
int rej_a = gpu_triangle_rejected(vx, vy, 0, 1, 2);
|
||||
int rej_b = gpu_triangle_rejected(vx, vy, 2, 1, 3);
|
||||
if (rej_a && rej_b) return;
|
||||
|
||||
/* Widescreen: tagged billboard quads carry CPU-computed pixel offsets the
|
||||
@@ -4764,6 +4870,7 @@ static void gp0_exec_textured_quad(void) {
|
||||
|
||||
/* Execute shaded textured triangle (GP0 0x34-0x37) */
|
||||
static void gp0_exec_shaded_textured_tri(void) {
|
||||
if (native_wide_projective_draw()) return;
|
||||
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
int32_t vx[3], vy[3];
|
||||
@@ -4785,7 +4892,7 @@ static void gp0_exec_shaded_textured_tri(void) {
|
||||
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[5] >> 16);
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -4828,6 +4935,7 @@ int psx_mod_texture_banks_supported(void) {
|
||||
|
||||
/* Execute shaded textured quad (GP0 0x3C-0x3F) */
|
||||
static void gp0_exec_shaded_textured_quad(void) {
|
||||
if (native_wide_projective_draw()) return;
|
||||
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
int32_t vx[4], vy[4];
|
||||
@@ -4852,8 +4960,8 @@ static void gp0_exec_shaded_textured_quad(void) {
|
||||
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[5] >> 16);
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
|
||||
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
|
||||
int rej_a = gpu_triangle_rejected(vx, vy, 0, 1, 2);
|
||||
int rej_b = gpu_triangle_rejected(vx, vy, 2, 1, 3);
|
||||
if (rej_a && rej_b) return;
|
||||
|
||||
ws_auto_ui_transform_quad(vx, vy);
|
||||
|
||||
@@ -555,6 +555,8 @@ static GLuint s_pack_prog = 0, s_stencil_prog = 0, s_empty_vao = 0;
|
||||
* gpu.c sets these immediately before the matching gr_draw_*_triangle and they
|
||||
* are consumed by it. All-zero == the faithful integer/affine path. */
|
||||
static int s_pc_valid = 0; /* sub-pixel positions present */
|
||||
static int s_projected_uv_valid;
|
||||
static float s_projected_u[3], s_projected_v[3];
|
||||
static float s_pc_x[3], s_pc_y[3]; /* native VRAM px, fractional */
|
||||
static int s_pq_valid = 0; /* perspective weights present */
|
||||
static float s_pq[3];
|
||||
@@ -697,6 +699,16 @@ static int g_wide_cur_base = 0; /* base_x of g_wide_cur */
|
||||
* gpu_geometry wide mirror is skipped and the flat path emits its own
|
||||
* full-wide-width pass instead (mirrors sw_draw_flat_rect). */
|
||||
static int s_wide_suppress = 0;
|
||||
/* Diagnostic only. The frontend admits proven world geometry into both the
|
||||
* canonical and wide passes so a face cannot split at the center-copy edge. */
|
||||
static uint64_t s_wide_triangle_recovery_count;
|
||||
void gl_renderer_note_wide_triangle_recovery(int recovered) {
|
||||
if (recovered && s_raster_ok && g_wide_cur) ++s_wide_triangle_recovery_count;
|
||||
}
|
||||
uint64_t gl_renderer_wide_triangle_recovery_count(void) {
|
||||
return s_wide_triangle_recovery_count;
|
||||
}
|
||||
|
||||
|
||||
/* X-translation (native px) from canonical VRAM space into the active wide
|
||||
* surface: local_x = vram_x - base_x + OFFSET. Same as SW wide_dx(). */
|
||||
@@ -3295,7 +3307,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
int semi, const int *lim) {
|
||||
// Mode 2 needs proven world geometry. Sprites, HUD and untracked packets
|
||||
// retain point sampling; their cutout pixels must stay sharp.
|
||||
const int filter=s_tex_filter==2 && (lim || (!s_pc_valid && !s_pq_valid)) ? 0 : s_tex_filter;
|
||||
const int filter=s_tex_filter==2 && ((lim && !s_projected_uv_valid) || (!s_pc_valid && !s_pq_valid)) ? 0 : s_tex_filter;
|
||||
int lim_buf[4];
|
||||
int uv_buf[6];
|
||||
if (!lim) {
|
||||
@@ -3386,7 +3398,8 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
for (int i = 0; i < 3; i++, vp += TEXV) {
|
||||
vp[0] = s_pc_valid ? s_pc_x[i] : (float)xs[i];
|
||||
vp[1] = s_pc_valid ? s_pc_y[i] : (float)ys[i];
|
||||
vp[2] = (float)us[i]; vp[3] = (float)vs[i];
|
||||
vp[2] = s_projected_uv_valid ? s_projected_u[i] : (float)us[i];
|
||||
vp[3] = s_projected_uv_valid ? s_projected_v[i] : (float)vs[i];
|
||||
vp[4] = col[i*3+0]; vp[5] = col[i*3+1]; vp[6] = col[i*3+2]; vp[7] = 1.0f;
|
||||
vp[8] = (float)base_x; vp[9] = (float)base_y; /* a_tpage */
|
||||
vp[10] = (float)clut_x; vp[11] = (float)clut_y; /* a_clut */
|
||||
@@ -3704,7 +3717,44 @@ static int cpu_raster_required(void) {
|
||||
}
|
||||
/* The sub-pixel / perspective override describes exactly one triangle; drop it
|
||||
* once that triangle has been submitted so a later prim can never inherit it. */
|
||||
static inline void precise_consumed(void) { s_pc_valid = 0; s_pq_valid = 0; }
|
||||
static inline void precise_consumed(void) { s_pc_valid = 0; s_pq_valid = 0; s_projected_uv_valid = 0; }
|
||||
|
||||
int gl_renderer_projective_supported(void) {
|
||||
return s_raster_ok && g_wide_cur && !cpu_vram_authoritative();
|
||||
}
|
||||
void gl_renderer_draw_projected_triangle(const PSXProjectedVertex vertices[3],
|
||||
uint16_t texpage, uint16_t cx, uint16_t cy, int raw, int semi, int perspective) {
|
||||
PSXProjectedVertex polygon[12];
|
||||
const double left=fmin(s_area_x1, -wide_dx())-1.0;
|
||||
const double right=fmax(s_area_x2+1, g_wide_w-wide_dx())+1.0;
|
||||
const int n=psx_projective_clip_triangle(vertices,polygon,
|
||||
left,right,s_area_y1-1.0,s_area_y2+1.0);
|
||||
int limits[4]={(int)vertices[0].u,(int)vertices[0].v,
|
||||
(int)vertices[0].u,(int)vertices[0].v};
|
||||
for (int i=1;i<3;++i) {
|
||||
if (vertices[i].u<limits[0]) limits[0]=(int)vertices[i].u;
|
||||
if (vertices[i].v<limits[1]) limits[1]=(int)vertices[i].v;
|
||||
if (vertices[i].u>limits[2]) limits[2]=(int)vertices[i].u;
|
||||
if (vertices[i].v>limits[3]) limits[3]=(int)vertices[i].v;
|
||||
}
|
||||
for (int k=1;k+1<n;++k) {
|
||||
const PSXProjectedVertex v[3]={polygon[0],polygon[k],polygon[k+1]};
|
||||
int xs[3],ys[3],us[3],vs[3]; float col[9];
|
||||
s_pc_valid=1; s_pq_valid=perspective; s_projected_uv_valid=1;
|
||||
for (int i=0;i<3;++i) {
|
||||
s_pc_x[i]=(float)(v[i].x/v[i].z);
|
||||
s_pc_y[i]=(float)(v[i].y/v[i].z);
|
||||
xs[i]=(int)floorf(s_pc_x[i]); ys[i]=(int)floorf(s_pc_y[i]);
|
||||
s_pq[i]=(float)(1.0/v[i].z);
|
||||
s_projected_u[i]=(float)v[i].u; s_projected_v[i]=(float)v[i].v;
|
||||
us[i]=(int)v[i].u; vs[i]=(int)v[i].v;
|
||||
col[i*3]=(float)v[i].r; col[i*3+1]=(float)v[i].g; col[i*3+2]=(float)v[i].b;
|
||||
}
|
||||
gpu_textured_triangle(xs,ys,us,vs,col,texpage,cx,cy,raw,semi,limits);
|
||||
precise_consumed();
|
||||
}
|
||||
precise_consumed();
|
||||
}
|
||||
|
||||
static void glb_draw_flat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,uint16_t col) {
|
||||
if (cpu_raster_required())
|
||||
|
||||
@@ -240,7 +240,9 @@ static uint32_t s_geom_miss_ambig = 0; /* recorded, but not unambiguously *
|
||||
static uint32_t s_speculative_depth = 0;
|
||||
static int s_speculative_timeline_invalidated = 0;
|
||||
|
||||
static void gte_nclip_precision_invalidate();
|
||||
static void gte_geom_generation_advance(void) {
|
||||
gte_nclip_precision_invalidate();
|
||||
if (++s_geom_generation == 0) {
|
||||
if (s_geom_cache)
|
||||
std::memset(s_geom_cache, 0, GEOM_CACHE_SIZE * sizeof(GeomVertex));
|
||||
@@ -1006,6 +1008,19 @@ 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;
|
||||
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};
|
||||
pgxp_gte_set_projection(&projection);
|
||||
}
|
||||
}
|
||||
geom_note((uint32_t)gte->SXY[2], sx16, sy16);
|
||||
|
||||
@@ -1045,6 +1060,15 @@ static int8_t s_nclip_last_precise_sign = 0;
|
||||
static bool s_nclip_last_precise_valid = false;
|
||||
static bool s_nclip_last_horizontal_saturated = false;
|
||||
static uint32_t s_nclip_last_generation;
|
||||
static int32_t s_nclip_previous_native;
|
||||
static int8_t s_nclip_previous_precise_sign;
|
||||
static bool s_nclip_previous_precise_valid;
|
||||
static bool s_nclip_previous_horizontal_saturated;
|
||||
static uint32_t s_nclip_previous_generation;
|
||||
static void gte_nclip_precision_invalidate() {
|
||||
s_nclip_last_precise_valid = false;
|
||||
s_nclip_previous_precise_valid = false;
|
||||
}
|
||||
extern "C" void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks,
|
||||
uint64_t *disagreements) {
|
||||
if (hits) *hits = s_nclip_precise_hits;
|
||||
@@ -1063,6 +1087,17 @@ extern "C" int gte_nclip_native_wide_sign(int32_t native_mac0, int* sign) {
|
||||
*sign = s_nclip_last_precise_sign;
|
||||
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
|
||||
* as a fallback for arbitrary architectural MAC0 readers. */
|
||||
extern "C" int gte_nclip_native_wide_previous_sign(int32_t native_mac0, int* sign) {
|
||||
if (!s_nclip_previous_precise_valid || !s_nclip_previous_horizontal_saturated ||
|
||||
native_mac0 != s_nclip_previous_native || s_nclip_previous_generation != s_geom_generation ||
|
||||
s_gte_replay_sandbox || s_speculative_depth != 0) return 0;
|
||||
*sign = s_nclip_previous_precise_sign;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
gte->FLAG = 0;
|
||||
@@ -1108,6 +1143,12 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
}
|
||||
}
|
||||
}
|
||||
s_nclip_previous_native = s_nclip_last_native;
|
||||
s_nclip_previous_precise_sign = s_nclip_last_precise_sign;
|
||||
s_nclip_previous_precise_valid = s_nclip_last_precise_valid &&
|
||||
!s_gte_replay_sandbox && s_speculative_depth == 0;
|
||||
s_nclip_previous_horizontal_saturated = s_nclip_last_horizontal_saturated;
|
||||
s_nclip_previous_generation = s_nclip_last_generation;
|
||||
s_nclip_last_native = out;
|
||||
s_nclip_last_precise_valid = false;
|
||||
s_nclip_last_horizontal_saturated = false;
|
||||
|
||||
+34
-3
@@ -52,6 +52,7 @@ enum {
|
||||
PGXP_F_VX = 1u << 0, /* low half (screen X) tracked */
|
||||
PGXP_F_VY = 1u << 1, /* high half (screen Y) tracked */
|
||||
PGXP_F_VZ = 1u << 2, /* projected depth rode along intact */
|
||||
PGXP_F_PROJECTION = 1u << 3,
|
||||
PGXP_F_VXY = PGXP_F_VX | PGXP_F_VY,
|
||||
};
|
||||
|
||||
@@ -61,6 +62,7 @@ struct PGXPValue {
|
||||
uint16_t flags;
|
||||
uint32_t value; /* the guest word this shadow describes */
|
||||
uint32_t gen; /* valid iff == s_gen (O(1) invalidate-all) */
|
||||
PGXPProjection projection;
|
||||
};
|
||||
|
||||
/* Shadow covers the host RAM backing so the opt-in 8 MB map tracks its high
|
||||
@@ -70,7 +72,7 @@ struct PGXPValue {
|
||||
#define PGXP_REG_HI 32
|
||||
#define PGXP_REG_LO 33
|
||||
|
||||
static PGXPValue *s_ram = nullptr; /* lazily allocated, ~40 MB VA */
|
||||
static PGXPValue *s_ram = nullptr; /* lazily allocated, 72 MiB VA */
|
||||
static PGXPValue s_scratch[PGXP_SCRATCH_WORDS];
|
||||
static PGXPValue s_gpr[34]; /* 32 GPRs + HI + LO */
|
||||
static PGXPValue s_gte[32]; /* GTE data registers */
|
||||
@@ -78,6 +80,7 @@ static PGXPValue s_gte[32]; /* GTE data registers */
|
||||
static uint32_t s_gen = 1;
|
||||
static int s_enabled = 0;
|
||||
static int s_cpu_mode = 0;
|
||||
static int s_projection_tracking = 0;
|
||||
static float s_tolerance = 0.5f; /* user-validated seam clamp (G1.10) */
|
||||
static int s_position_fallback = 1; /* G1.4 cache tier (G1.11 switch) */
|
||||
static int s_preserve_projection = 0; /* exact-projection shadows (G1.11) */
|
||||
@@ -135,6 +138,13 @@ extern "C" int pgxp_active(void) { return g_pgxp_active; }
|
||||
extern "C" void pgxp_set_cpu_mode(int enabled) { s_cpu_mode = enabled ? 1 : 0; }
|
||||
extern "C" int pgxp_cpu_mode(void) { return s_cpu_mode; }
|
||||
|
||||
extern "C" void pgxp_set_projection_tracking(int enabled) {
|
||||
if (s_projection_tracking == !!enabled) return;
|
||||
s_projection_tracking = !!enabled;
|
||||
pgxp_invalidate_all();
|
||||
}
|
||||
extern "C" int pgxp_projection_tracking(void) { return s_projection_tracking; }
|
||||
|
||||
extern "C" void pgxp_set_tolerance(float pixels) { s_tolerance = pixels; }
|
||||
extern "C" float pgxp_tolerance(void) { return s_tolerance; }
|
||||
|
||||
@@ -340,6 +350,7 @@ static inline int pv_live(const PGXPValue *pv) {
|
||||
static inline void pv_validate(PGXPValue *pv, uint32_t actual) {
|
||||
if (pv->gen != s_gen) return;
|
||||
uint32_t diff = pv->value ^ actual;
|
||||
if (diff) pv->flags &= (uint16_t)~PGXP_F_PROJECTION;
|
||||
if ((pv->flags & PGXP_F_VX) && (diff & 0x0000FFFFu))
|
||||
pv->flags &= (uint16_t)~(PGXP_F_VX | PGXP_F_VZ);
|
||||
if ((pv->flags & PGXP_F_VY) && (diff & 0xFFFF0000u))
|
||||
@@ -448,7 +459,7 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr,
|
||||
* half-write — the vertex it described no longer exists whole. */
|
||||
if (hi_half) dst->value = (dst->value & 0x0000FFFFu) | (half << 16);
|
||||
else dst->value = (dst->value & 0xFFFF0000u) | half;
|
||||
dst->flags &= (uint16_t)~((hi_half ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ);
|
||||
dst->flags &= (uint16_t)~((hi_half ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ | PGXP_F_PROJECTION);
|
||||
dst->z = 0;
|
||||
if (src && src->gen == s_gen && (src->flags & PGXP_F_VX) &&
|
||||
((src->value ^ value) & 0xFFFFu) == 0) {
|
||||
@@ -462,7 +473,7 @@ extern "C" void psx_pgxp_store(struct CPUState *cpu, uint32_t instr,
|
||||
uint32_t shift = (addr & 3u) * 8u;
|
||||
dst->value = (dst->value & ~(0xFFu << shift)) |
|
||||
((value & 0xFFu) << shift);
|
||||
dst->flags &= (uint16_t)~(((addr & 2u) ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ);
|
||||
dst->flags &= (uint16_t)~(((addr & 2u) ? PGXP_F_VY : PGXP_F_VX) | PGXP_F_VZ | PGXP_F_PROJECTION);
|
||||
dst->z = 0;
|
||||
return;
|
||||
}
|
||||
@@ -874,6 +885,26 @@ extern "C" int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16) {
|
||||
return pgxp_get_gte_sxy_checked(index, 0u, 0, x16, y16);
|
||||
}
|
||||
|
||||
extern "C" void pgxp_gte_set_projection(const PGXPProjection *projection) {
|
||||
if (!g_pgxp_active || !s_projection_tracking || !projection) return;
|
||||
s_gte[14].projection = *projection;
|
||||
s_gte[14].flags |= PGXP_F_PROJECTION;
|
||||
s_gte[15] = s_gte[14];
|
||||
}
|
||||
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 ||
|
||||
(pv->flags & flags) != flags) return 0;
|
||||
if (out) *out = pv->projection;
|
||||
return 1;
|
||||
}
|
||||
extern "C" int pgxp_load_projection(uint32_t addr, uint32_t packed, PGXPProjection *out) {
|
||||
return projection_read(pgxp_ptr(addr), packed, out);
|
||||
}
|
||||
extern "C" int pgxp_get_gte_projection(uint32_t index, uint32_t packed, PGXPProjection *out) {
|
||||
return index < 4 ? projection_read(&s_gte[12+index], packed, out) : 0;
|
||||
}
|
||||
|
||||
extern "C" int pgxp_get_gte_sxy_checked(uint32_t index, uint32_t expect,
|
||||
int check, int32_t *x16, int32_t *y16) {
|
||||
if (index >= 4) return 0;
|
||||
|
||||
@@ -16,8 +16,30 @@ uint32_t g_psx_ram_mask = 0x001FFFFFu;
|
||||
/* This fixture exercises stock GPU packets without a GL context or mod bank.
|
||||
* Keep the gpu.c dependencies explicit, including on linkers that retain
|
||||
* additional exported command handlers despite --gc-sections. */
|
||||
GrBackend gr_backend(void) { return GR_BACKEND_SOFTWARE; }
|
||||
static GrBackend fixture_backend = GR_BACKEND_SOFTWARE;
|
||||
GrBackend gr_backend(void) { return fixture_backend; }
|
||||
int gl_renderer_texture_banks_supported(void) { return 0; }
|
||||
static int fixture_projective_supported, fixture_projective_draws;
|
||||
static PSXProjectedVertex fixture_projective_vertices[3];
|
||||
int gl_renderer_projective_supported(void) { return fixture_projective_supported; }
|
||||
void gl_renderer_draw_projected_triangle(const PSXProjectedVertex v[3],
|
||||
uint16_t tp, uint16_t cx, uint16_t cy, int raw, int semi, int perspective) {
|
||||
memcpy(fixture_projective_vertices,v,sizeof fixture_projective_vertices);
|
||||
fixture_projective_draws++;
|
||||
(void)tp;(void)cx;(void)cy;(void)raw;(void)semi;(void)perspective;
|
||||
}
|
||||
static struct { uint32_t addr,word; PGXPProjection p; int valid; } fixture_homogeneous[4];
|
||||
int pgxp_load_projection(uint32_t addr, uint32_t word, PGXPProjection *out) {
|
||||
for(int i=0;i<4;++i) if(fixture_homogeneous[i].valid &&
|
||||
fixture_homogeneous[i].addr==addr && fixture_homogeneous[i].word==word) {
|
||||
if(out)*out=fixture_homogeneous[i].p;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int fixture_projection_tracking;
|
||||
int pgxp_projection_tracking(void) { return fixture_projection_tracking; }
|
||||
void pgxp_set_projection_tracking(int enabled) { fixture_projection_tracking=!!enabled; }
|
||||
int gl_renderer_select_texture_bank(uint16_t id) { return id == 0; }
|
||||
int gl_renderer_select_texture_bank_live_clut(uint16_t id) { return id == 0; }
|
||||
uint16_t mod_texture_packet_bank(uint32_t source, const uint32_t *words,
|
||||
@@ -376,3 +398,10 @@ int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
|
||||
int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; (void)sign; return 0;
|
||||
}
|
||||
int gte_nclip_native_wide_previous_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; (void)sign; return 0;
|
||||
}
|
||||
static int fixture_triangle_recovered;
|
||||
void gl_renderer_note_wide_triangle_recovery(int enabled) {
|
||||
fixture_triangle_recovered=enabled;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,98 @@ static void verify_bank_batching(void) {
|
||||
psx_mod_set_texture_bank_batching(0);s_tex_filter=0;
|
||||
glb_set_mask_bits(0,0);glb_set_semi_transparency(0,0);
|
||||
}
|
||||
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. */
|
||||
glb_set_draw_area(0,0,511,239);glb_set_precise_triangle(0,0,0,0,0,0,0);
|
||||
glb_wide_configure(848,168);glb_wide_set_target(0);
|
||||
glb_wide_set_view(0,0,0,0);s_wide_fast=1;
|
||||
glb_wide_clear(0,0,240,0);
|
||||
glb_draw_flat_rect(-168,0,848,240,0x7c00);
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_flat_triangle(-300,-200,300,100,-100,500,0x001f);
|
||||
check(glb_vram_read(100,100)==0x001f,"recovered flat updates canonical pixels coherently");
|
||||
/* A line queued immediately after a recovered flat triangle must use
|
||||
* the ordinary batch at both native and supersampled resolutions. */
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_flat_triangle(-300,-200,300,100,-100,500,0x001f);
|
||||
glb_draw_line(203,40,219,40,0x03e0);
|
||||
check(glb_vram_read(210,40)==0x03e0,"ordinary line after oversize flat");
|
||||
glb_vram_write(512,0,0x03e0);
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_shaded_textured_triangle(561,184,0,0,0x808080,
|
||||
1023,-724,0,0,0x808080,1023,334,0,0,0x808080,0,0,0x108,1);
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_shaded_textured_triangle(-300,-200,0,0,0x808080,
|
||||
300,100,0,0,0x808080,-100,500,0,0,0x808080,0,0,0x108,1);
|
||||
check(glb_vram_read(100,100)==0x03e0,"recovered textured updates canonical pixels coherently");
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_shaded_textured_triangle(400,-200,0,0,0x808080,
|
||||
900,100,0,0,0x808080,400,500,0,0,0x808080,0,0,0x108,1);
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_shaded_textured_triangle(561,184,0,0,0x808080,
|
||||
1023,-724,0,0,0x808080,1023,334,0,0,0x808080,0,0,0x108,1);
|
||||
glb_draw_shaded_textured_triangle(40,100,0,0,0x808080,
|
||||
70,100,0,0,0x808080,40,130,0,0,0x808080,0,0,0x108,1);
|
||||
check(glb_vram_read(45,105)==0x03e0,"ordinary textured after oversize textured");
|
||||
verify("recovered geometry keeps CPU/GPU readback coherent");
|
||||
uint32_t *pixels=calloc((size_t)848*240*scale*scale,sizeof(uint32_t));
|
||||
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");
|
||||
free(pixels);
|
||||
}
|
||||
glb_wide_disable_target();
|
||||
gl_renderer_note_wide_triangle_recovery(1);
|
||||
glb_draw_flat_triangle(-300,-200,300,100,-100,500,0x001f);
|
||||
check(glb_vram_read(100,100)==0x001f,"admitted backend geometry remains coherent without mirror");
|
||||
glb_wide_set_target(0);
|
||||
}
|
||||
static void verify_camera_plane_clip(int scale) {
|
||||
glb_set_draw_area(0,0,511,239);glb_wide_configure(848,168);
|
||||
glb_wide_set_target(0);glb_wide_set_view(0,0,0,0);
|
||||
glb_wide_clear(0,0,240,0);glb_draw_flat_rect(-168,0,848,240,0x7c00);
|
||||
glb_vram_write(512,0,0x03e0);
|
||||
/* One corner is behind the camera, with two at z=2 in front. The
|
||||
* visible trapezoid contains (100,100), and extends into the left margin. */
|
||||
const PSXProjectedVertex crossing[3]={
|
||||
{-1600,0,-1,0,0,1,1,1},{600,0,2,0,0,1,1,1},{600,480,2,0,0,1,1,1}};
|
||||
check(gl_renderer_projective_supported(),"projective GPU path ready");
|
||||
gl_renderer_draw_projected_triangle(crossing,0x108,0,0,1,-1,1);
|
||||
check(glb_vram_read(100,100)==0x03e0,"crossing wall retained in front of camera");
|
||||
check(glb_vram_read(400,100)==0x7c00,"outside crossing wall remains untouched");
|
||||
const PSXProjectedVertex hidden[3]={
|
||||
{-400,0,-2,0,0,1,1,1},{600,0,-1,0,0,1,1,1},{600,480,-2,0,0,1,1,1}};
|
||||
gl_renderer_draw_projected_triangle(hidden,0x108,0,0,1,-1,1);
|
||||
check(glb_vram_read(400,100)==0x7c00,"entirely behind-camera triangle rejected");
|
||||
glb_draw_flat_rect(120,100,4,4,0x001f);
|
||||
check(glb_vram_read(121,101)==0x001f,"ordinary draw after clipped wall keeps painter order");
|
||||
check(!s_pc_valid && !s_pq_valid && !s_projected_uv_valid,"clipped overrides consumed");
|
||||
verify("camera-plane clipping preserves GPU and CPU authority");
|
||||
uint32_t *pixels=calloc((size_t)848*240*scale*scale,sizeof(uint32_t));
|
||||
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");
|
||||
free(pixels);
|
||||
}
|
||||
/* Perspective UVs must survive intersections without integer rounding.
|
||||
* Analytic barycentric values at the two samples are (15.789,5.702)
|
||||
* and (17.5,6.771), independent of the clipped polygon's triangulation. */
|
||||
for(int v=0;v<32;++v) for(int u=0;u<32;++u)
|
||||
glb_vram_write(512+u,v,(uint16_t)((u+1)|((v+1)<<5)));
|
||||
PSXProjectedVertex mapped[3]={crossing[0],crossing[1],crossing[2]};
|
||||
mapped[1].u=mapped[2].u=20;mapped[2].v=20;
|
||||
gl_renderer_draw_projected_triangle(mapped,0x108,0,0,1,-1,1);
|
||||
check(glb_vram_read(100,100)==(16|(6<<5)),"fractional clipped UV sample one");
|
||||
check(glb_vram_read(200,100)==(18|(7<<5)),"fractional clipped UV sample two");
|
||||
}
|
||||
int main(int argc,char **argv){
|
||||
int scale=argc>1?atoi(argv[1]):1;
|
||||
if(SDL_Init(SDL_INIT_VIDEO)!=0)return 2;
|
||||
@@ -178,6 +270,8 @@ int main(int argc,char **argv){
|
||||
check(glb_vram_read(482,252)==0x001f,"palette update visible without replacing indices");
|
||||
verify("retained indices with animated guest CLUT");
|
||||
verify_bank_batching();
|
||||
verify_oversize_wide_geometry(scale);
|
||||
verify_camera_plane_clip(scale);
|
||||
/* World and UI use different origins in an anchored wide frame. Keep the
|
||||
* canonical-center optimization enabled to catch an erroneous blit over the
|
||||
* completed mirror, and change origins with a pending flat batch. */
|
||||
|
||||
@@ -697,6 +697,21 @@ int test_saturated_nclip_keeps_architectural_result() {
|
||||
if (cpu.gte_data[24] != 0 || !gte_nclip_native_wide_sign(0, &sign) || sign != 1 ||
|
||||
gte_nclip_native_wide_sign(1, &sign))
|
||||
return fail_value("saturated winding rescue preserves native zero MAC0",0,6,0,0,cpu.gte_data[24]);
|
||||
/* Both quad MAC0 values can saturate to the same zero. Keep each exact
|
||||
* sign separately: the preceding triangle points forward, latest back. */
|
||||
for (unsigned i=0;i<3;i++) {
|
||||
uint32_t packed=1023u | ((uint32_t)py[2-i]<<16);
|
||||
cpu.gte_data[12+i]=packed;
|
||||
gte_test_seed_precise_projection(i,packed,px[2-i]*65536,py[2-i]*65536,1000);
|
||||
}
|
||||
gte_execute(&cpu,6);
|
||||
if (cpu.gte_data[24]!=0 || !gte_nclip_native_wide_sign(0,&sign) || sign!=-1 ||
|
||||
!gte_nclip_native_wide_previous_sign(0,&sign) || sign!=1 ||
|
||||
gte_nclip_native_wide_previous_sign(1,&sign))
|
||||
return fail_value("quad retains independent saturated winding results",0,6,0,0,cpu.gte_data[24]);
|
||||
gte_execute(&cpu,6);
|
||||
if (!gte_nclip_native_wide_previous_sign(0,&sign) || sign!=-1)
|
||||
return fail_value("preceding winding advances one command only",0,6,0,0,1);
|
||||
cpu.gte_data[17] = 10;
|
||||
gte_execute(&cpu, 6);
|
||||
if (gte_nclip_native_wide_sign(0, &sign) || cpu.gte_data[24] != 0)
|
||||
@@ -704,7 +719,7 @@ int test_saturated_nclip_keeps_architectural_result() {
|
||||
cpu.gte_data[17] = 1000;
|
||||
gte_execute(&cpu, 6);
|
||||
gte_precision_timeline_invalidate();
|
||||
if (gte_nclip_native_wide_sign(0, &sign))
|
||||
if (gte_nclip_native_wide_sign(0, &sign) || gte_nclip_native_wide_previous_sign(0,&sign))
|
||||
return fail_value("timeline invalidation clears winding rescue",0,6,0,0,1);
|
||||
gte_test_seed_precise_projection(0, cpu.gte_data[12] ^ 1u, px[0]*65536, py[0]*65536, 1000);
|
||||
gte_execute(&cpu, 6);
|
||||
@@ -1082,6 +1097,28 @@ int main() {
|
||||
if (int rc = test_precision_speculative_transaction()) return rc;
|
||||
if (int rc = test_preserve_projection_is_shadow_only()) return rc;
|
||||
if (int rc = test_pgxp_culling()) return rc;
|
||||
/* Signed host projection spans the camera plane without changing any
|
||||
* architectural register, including unsigned SZ and divider/screen flags. */
|
||||
pgxp_set_enabled(1);
|
||||
for (int depth : {-158,-1,0,1,149,150,151,900}) {
|
||||
GTEState native{}, enhanced{};
|
||||
native.RT[0][0]=native.RT[1][1]=native.RT[2][2]=4096;
|
||||
native.V0[0]=100;native.V0[1]=30;native.TR[2]=depth;
|
||||
native.H=300;native.OFX=256*65536;native.OFY=120*65536;
|
||||
enhanced=native;
|
||||
pgxp_set_projection_tracking(0);
|
||||
PSXRecomp::GTE::gte_rtps(&native,0x0180001u);
|
||||
pgxp_set_projection_tracking(1);
|
||||
PSXRecomp::GTE::gte_rtps(&enhanced,0x0180001u);
|
||||
PGXPProjection p;
|
||||
if (std::memcmp(&native,&enhanced,sizeof native) ||
|
||||
!pgxp_get_gte_projection(2,enhanced.SXY[2],&p) ||
|
||||
p.z!=depth || p.x!=256*depth+30000 || p.y!=120*depth+9000 || p.near_z!=150) {
|
||||
std::fprintf(stderr,"FAIL signed camera projection at depth %d\n",depth);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pgxp_set_projection_tracking(0);
|
||||
std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -82,10 +82,87 @@ int main(void) {
|
||||
prepare_precise_triangle(7,4,10,bx,by);
|
||||
assert(!fixture_precise_triangle.enabled); /* native-wide selected, zero reveal */
|
||||
configure_native_wide_16_9();
|
||||
/* Captured corridor wall: height908 exceeds the PS1 limit, but its
|
||||
* original projected portion occupies the right widescreen margin. */
|
||||
fixture_backend=GR_BACKEND_OPENGL;
|
||||
hres1=2;
|
||||
psx_mod_set_native_wide_projection_correction(1);
|
||||
gp0_cmd_buf[0]=0x3C808080u;
|
||||
const int32_t wall_x[3]={818,1023,561}, wall_y[3]={-258,-724,184};
|
||||
for(unsigned i=0;i<3;i++) {
|
||||
const unsigned k=1+3*i;
|
||||
fixture_projections[i].addr=gp0_cmd_source_addr+4*k;
|
||||
fixture_projections[i].word=gp0_cmd_buf[k]=pack_vertex(wall_x[i],wall_y[i]);
|
||||
fixture_projections[i].x16=wall_x[i]*65536;
|
||||
fixture_projections[i].y16=wall_y[i]*65536;
|
||||
fixture_projections[i].z=900;
|
||||
fixture_projections[i].valid=1;
|
||||
}
|
||||
assert(psx_gpu_triangle_oversize(wall_x,wall_y,0,1,2));
|
||||
assert(!gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
prepare_precise_triangle(1,4,7,wall_x,wall_y);
|
||||
assert(fixture_triangle_recovered);
|
||||
fixture_backend=GR_BACKEND_SOFTWARE;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_backend=GR_BACKEND_OPENGL;
|
||||
fixture_projections[1].z=0;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_projections[1].z=900;
|
||||
fixture_projections[1].word^=1;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_projections[1].word^=1;
|
||||
fixture_projections[1].y16=-1200*65536;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_projections[1].y16=wall_y[1]*65536;
|
||||
fixture_projections[1].word=gp0_cmd_buf[4]=pack_vertex(wall_x[1],1023);
|
||||
fixture_projections[1].y16=1023*65536;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_projections[1].word=gp0_cmd_buf[4]=pack_vertex(wall_x[1],wall_y[1]);
|
||||
fixture_projections[1].y16=wall_y[1]*65536;
|
||||
fixture_projections[1].x16=4096*65536-1;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
fixture_projections[1].x16=wall_x[1]*65536;
|
||||
ws_cfg_num=4;ws_cfg_den=3;
|
||||
assert(gpu_triangle_rejected(wall_x,wall_y,0,1,2));
|
||||
configure_native_wide_16_9();
|
||||
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);
|
||||
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. */
|
||||
psx_mod_set_native_wide_projection_correction(1);
|
||||
configure_native_wide_16_9();
|
||||
fixture_projective_supported=1;fixture_backend=GR_BACKEND_OPENGL;
|
||||
gp0_cmd_source_addr=0x10004u;gp0_cmd_buf[0]=0x3c808080;
|
||||
for(unsigned i=0;i<4;++i) {
|
||||
unsigned k=1+3*i;
|
||||
gp0_cmd_buf[k]=pack_vertex(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){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);
|
||||
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;
|
||||
assert(!native_wide_projective_draw());
|
||||
gp0_cmd_buf[4]^=1;fixture_homogeneous[2].valid=0;
|
||||
assert(!native_wide_projective_draw());
|
||||
fixture_homogeneous[2].valid=1;fixture_backend=GR_BACKEND_SOFTWARE;
|
||||
assert(!native_wide_projective_draw());
|
||||
fixture_backend=GR_BACKEND_OPENGL;ws_cfg_num=4;ws_cfg_den=3;
|
||||
assert(!native_wide_projective_draw());
|
||||
configure_native_wide_16_9();fixture_projective_supported=0;
|
||||
assert(!native_wide_projective_draw());
|
||||
fixture_projective_supported=1;gp0_cmd_source_addr=UINT32_MAX;
|
||||
assert(!native_wide_projective_draw());
|
||||
assert(fixture_projective_draws==2);
|
||||
puts("native_wide_projection_test: PASS");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -566,6 +566,33 @@ int main(void) {
|
||||
CHECK(lookup(ADDR_B, packed, 1023, 152, &x, &y, &z) == PGXP_SRC_NATIVE);
|
||||
}
|
||||
|
||||
/* Signed camera depth survives exact word transport and sandbox rollback;
|
||||
* byte-identical partial writes must still discard the 3D association. */
|
||||
{
|
||||
PGXPProjection p={12345.5f,-7311.0f,-62.0f,150.0f}, out;
|
||||
pgxp_set_projection_tracking(1);
|
||||
pgxp_gte_push_sxy(X16,Y16,0,PACKED);
|
||||
pgxp_gte_set_projection(&p);
|
||||
CHECK(pgxp_get_gte_projection(2,PACKED,&out) && out.z==-62.0f);
|
||||
CHECK(!pgxp_get_gte_projection(2,PACKED^1,&out));
|
||||
psx_pgxp_cop2(nullptr,MFC2(9,14),PACKED,0);
|
||||
psx_pgxp_store(nullptr,SW(1,9),ADDR_B,PACKED);
|
||||
CHECK(pgxp_load_projection(ADDR_B,PACKED,&out) && out.x==p.x && out.z==p.z);
|
||||
CHECK(!pgxp_load_projection(ADDR_B,PACKED^1,&out));
|
||||
pgxp_checkpoint_begin();
|
||||
psx_pgxp_store(nullptr,SB(1,9),ADDR_B,PACKED&255);
|
||||
CHECK(!pgxp_load_projection(ADDR_B,PACKED,&out));
|
||||
pgxp_checkpoint_rollback();
|
||||
CHECK(pgxp_load_projection(ADDR_B,PACKED,&out) && out.z==p.z);
|
||||
psx_pgxp_store(nullptr,SH(1,9),ADDR_B,PACKED&65535);
|
||||
CHECK(!pgxp_load_projection(ADDR_B,PACKED,&out));
|
||||
pgxp_store_gte_reg(ADDR_B,14);
|
||||
CHECK(pgxp_load_projection(ADDR_B,PACKED,&out));
|
||||
pgxp_invalidate_all();
|
||||
CHECK(!pgxp_load_projection(ADDR_B,PACKED,&out));
|
||||
pgxp_set_projection_tracking(0);
|
||||
}
|
||||
|
||||
/* --- stats sanity: dataflow hits were counted --- */
|
||||
{
|
||||
PGXPStats st;
|
||||
|
||||
@@ -39,6 +39,10 @@ uint32_t g_debug_current_func_addr;
|
||||
uint32_t g_debug_last_store_pc;
|
||||
uint32_t g_dirty_ram_code_gen;
|
||||
uint64_t g_dispatch_static_hits;
|
||||
uint32_t g_psx_mod_instruction_hooks;
|
||||
void psx_mod_instruction(CPUState *cpu, uint32_t pc, uint32_t instruction) {
|
||||
(void)cpu; (void)pc; (void)instruction; abort();
|
||||
}
|
||||
int g_rfe_escape_pending;
|
||||
int g_exc_escape_reason;
|
||||
int g_psx_call_bail;
|
||||
@@ -69,9 +73,13 @@ int dirty_ram_is_dirty(uint32_t phys) { (void)phys; return 1; }
|
||||
int mdec_recently_active(uint32_t f) { (void)f; return 0; }
|
||||
void gte_execute(CPUState *cpu, uint32_t cmd) { (void)cpu; (void)cmd; }
|
||||
static int precise_sign_valid, precise_sign;
|
||||
static int previous_sign_valid, previous_sign;
|
||||
int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; *sign = precise_sign; return precise_sign_valid;
|
||||
}
|
||||
int gte_nclip_native_wide_previous_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; *sign = previous_sign; return previous_sign_valid;
|
||||
}
|
||||
void gte_precision_store_word(uint32_t a, uint8_t r) { (void)a; (void)r; }
|
||||
uint32_t gte_read_ctrl(CPUState *cpu, uint8_t r) { (void)cpu; (void)r; return 0; }
|
||||
uint32_t gte_read_data(CPUState *cpu, uint8_t r) { (void)cpu; (void)r; return 0; }
|
||||
@@ -439,6 +447,18 @@ static void test_wide_nclip_branches(void) {
|
||||
}
|
||||
precise_sign_valid=0; gpu_ws_set_margin_override(53); cpu.gpr[A0]=0;
|
||||
CHECK(branch_taken(&cpu,sites[0],words[0]), "missing precision stays native");
|
||||
precise_sign_valid=1; precise_sign=-1;
|
||||
previous_sign_valid=1; previous_sign=1;
|
||||
psx_mod_set_native_wide_nclip_previous_site(sites[1],words[1]);
|
||||
CHECK(branch_taken(&cpu,sites[1],words[1]), "first quad branch selects preceding positive winding");
|
||||
CHECK(!branch_taken(&cpu,sites[2],words[2]), "second quad branch selects latest negative winding");
|
||||
previous_sign_valid=0;
|
||||
CHECK(!branch_taken(&cpu,sites[1],words[1]), "missing preceding winding cannot use latest result");
|
||||
previous_sign_valid=1; gpu_ws_set_margin_override(0);
|
||||
CHECK(!branch_taken(&cpu,sites[1],words[1]), "preceding winding is disabled in native 4:3");
|
||||
gpu_ws_set_margin_override(53);
|
||||
psx_mod_set_native_wide_nclip_sites(sites,words,3);
|
||||
CHECK(!branch_taken(&cpu,sites[1],words[1]), "registration resets preceding-result bindings");
|
||||
psx_mod_set_native_wide_nclip_sites(NULL,NULL,0);
|
||||
ws_precise_nclip_cfg=0; ws_xnum=ws_xden=1;
|
||||
gpu_ws_set_margin_override(-1);
|
||||
|
||||
Reference in New Issue
Block a user