feat: park projection and perspective correction

Adapted from https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-14 10:11:20 -07:00
parent 803ea60329
commit 2ceaf5abee
12 changed files with 492 additions and 44 deletions
@@ -0,0 +1,22 @@
# PR #14 projection precision and perspective textures
Source: [kem0x PR #14](https://github.com/mstan/psxrecomp/pull/14), commit
[`ec889d6e5956fc4f34acfee41d87f30b24123b7f`](https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f).
This branch parks two coupled, opt-in visual experiments together:
- retain discarded GTE 16.16 screen fractions and use exact packed-coordinate
matches for supersampled software-renderer vertices;
- track exact SWC2 RAM provenance through generated, strict, dirty-RAM,
overlay, and fallback interpreters so textured world polygons can use
perspective-correct UV interpolation without guessing from screen position.
They share the same projection side cache, GP0 triangle preparation, and
software-renderer consumption path; splitting those internals would duplicate
most of the risk-bearing machinery. Both remain disabled until their existing
generic setters are called.
Excluded: all Web host exports and every title-specific state, action, address,
or presentation hook from the source commit. The separate 60 Hz presentation
experiment is parked on its own branch. Authorship is retained in the commit
trailer.
+7 -4
View File
@@ -1295,11 +1295,14 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
: fmt::format("cpu->gte_data[{}]", rt);
std::string swc2_store_pc = fmt::format("g_debug_last_store_pc = 0x{:08X}u; ", addr);
if (offset == 0) {
code = gte_stall + swc2_store_pc + fmt::format("cpu->write_word({}, {}); /* swc2 gte[{}], ({}) */",
reg_name(rs), value, rt, reg_name(rs));
code = gte_stall + swc2_store_pc + fmt::format(
"cpu->write_word({}, {}); gte_precision_store_word({}, {}); /* swc2 gte[{}], ({}) */",
reg_name(rs), value, reg_name(rs), rt, rt, reg_name(rs));
} else {
code = gte_stall + swc2_store_pc + fmt::format("cpu->write_word({} + {}, {}); /* swc2 gte[{}], {}({}) */",
reg_name(rs), offset, value, rt, offset, reg_name(rs));
code = gte_stall + swc2_store_pc + fmt::format(
"cpu->write_word({} + {}, {}); gte_precision_store_word({} + {}, {}); /* swc2 gte[{}], {}({}) */",
reg_name(rs), offset, value, reg_name(rs), offset, rt,
rt, offset, reg_name(rs));
}
}
break;
+8 -4
View File
@@ -1386,13 +1386,17 @@ TranslateResult StrictTranslator::translate(const PSXRecomp::DecodedInstruction&
if (offset == 0) {
r.c_code = gte_stall + fmt::format(
"g_debug_last_store_pc = 0x{:08X}u; "
"cpu->write_word(cpu->gpr[{}], {});",
d.address, static_cast<int>(rs), value);
"cpu->write_word(cpu->gpr[{}], {}); "
"gte_precision_store_word(cpu->gpr[{}], {});",
d.address, static_cast<int>(rs), value,
static_cast<int>(rs), static_cast<int>(rt));
} else {
r.c_code = gte_stall + fmt::format(
"g_debug_last_store_pc = 0x{:08X}u; "
"cpu->write_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {});",
d.address, static_cast<int>(rs), static_cast<int>(offset), value);
"cpu->write_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {}); "
"gte_precision_store_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {});",
d.address, static_cast<int>(rs), static_cast<int>(offset), value,
static_cast<int>(rs), static_cast<int>(offset), static_cast<int>(rt));
}
r.comment = fmt::format("swc2 gte[{}], {}({})",
rt, static_cast<int>(offset), gpr_name(rs));
+1
View File
@@ -190,6 +190,7 @@ extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg);
extern uint32_t gte_read_ctrl(CPUState* cpu, uint8_t reg);
extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val);
extern void gte_write_ctrl(CPUState* cpu, uint8_t reg, uint32_t val);
extern void gte_precision_store_word(uint32_t addr, uint8_t reg);
/* ============================================================================
* Dispatch call contract (Bug D / wild-return family fix)
+14
View File
@@ -22,6 +22,20 @@ void sw_renderer_init(uint16_t* vram);
void sw_renderer_set_scale(int scale);
int sw_renderer_scale(void);
/* Visual-only high-resolution vertex override for the next triangle. Values
* are signed 16.16 screen coordinates; native VRAM still uses the integer GP0
* positions, while the supersampled mirror consumes these fractions. */
void sw_set_precise_triangle(int enabled,
int32_t x0, int32_t y0,
int32_t x1, int32_t y1,
int32_t x2, int32_t y2);
/* Perspective-correct UV override for the next triangle. q values are the
* homogeneous interpolation weights associated with each screen vertex. */
void sw_set_perspective_triangle(int enabled,
float q0, float q1, float q2);
uint32_t sw_perspective_triangle_count(void);
/* Texture filtering. 0 = nearest (native PSX look, default), 1 = bilinear
* (smooths textures/2D backgrounds; blends in RGB after the CLUT lookup,
* transparency-aware). Independent of supersampling. */
+1
View File
@@ -1739,6 +1739,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
psx_gte_stall(cpu); /* COP2 reg read stalls to GTE completion */
#endif
cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt));
gte_precision_store_word(addr, (uint8_t)rt);
return 0;
}
default:
+131 -8
View File
@@ -2082,6 +2082,75 @@ static void parse_vertex(uint32_t word, int32_t* x, int32_t* y) {
*y = sign_extend((word >> 16) & 0x7FFu, 11);
}
extern int gte_geometry_correction_lookup(uint32_t packed,
int32_t *x16, int32_t *y16);
extern int gte_geometry_correction_enabled(void);
static int s_texture_correction_enabled = 0;
extern void gte_precision_tracking_set(int enabled);
extern int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16, uint16_t *z);
void gpu_texture_correction_set(int enabled) {
s_texture_correction_enabled = enabled ? 1 : 0;
gte_precision_tracking_set(enabled);
}
uint32_t gpu_texture_correction_hits(void) {
return sw_perspective_triangle_count();
}
/* Match all three GP0 positions to recent GTE projections. Requiring a full
* triangle match prevents screen-space HUD/sprites that happen to share one
* coordinate from receiving world-geometry correction. The integer delta
* folds in draw offsets and any widescreen adjustment already applied. */
static void prepare_precise_triangle(uint32_t w0, uint32_t w1, uint32_t w2,
const int32_t vx[3], const int32_t vy[3]) {
uint32_t words[3] = { w0, w1, w2 };
int32_t fx[3], fy[3];
const int geometry_enabled = gte_geometry_correction_enabled();
sw_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
if (!geometry_enabled) {
sw_set_precise_triangle(0, 0,0, 0,0, 0,0);
return;
}
for (int i = 0; i < 3; i++) {
int32_t raw_x, raw_y;
parse_vertex(words[i], &raw_x, &raw_y);
if (!gte_geometry_correction_lookup(words[i], &fx[i], &fy[i])) {
sw_set_precise_triangle(0, 0,0, 0,0, 0,0);
return;
}
fx[i] = (int32_t)((int64_t)fx[i] +
(int64_t)(vx[i] - raw_x) * 65536);
fy[i] = (int32_t)((int64_t)fy[i] +
(int64_t)(vy[i] - raw_y) * 65536);
}
sw_set_precise_triangle(1, fx[0],fy[0], fx[1],fy[1], fx[2],fy[2]);
}
/* Enable perspective UVs only when every position word came from an exact
* SWC2 projection store at that same DMA packet address. This preserves the
* association through ordering-table reordering and rejects CPU-built UI. */
static void prepare_texture_triangle(int i0, int i1, int i2) {
sw_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
if (!s_texture_correction_enabled || gp0_cmd_source_addr == 0xFFFFFFFFu)
return;
int indices[3] = { i0, i1, i2 };
uint16_t z[3];
for (int i = 0; i < 3; i++) {
uint32_t addr = (gp0_cmd_source_addr + (uint32_t)indices[i] * 4u) & 0x1FFFFCu;
if (!gte_precision_load_word(addr, gp0_cmd_buf[indices[i]], NULL, NULL, &z[i]) ||
z[i] == 0)
return;
}
float q[3] = { 1.0f / (float)z[0], 1.0f / (float)z[1], 1.0f / (float)z[2] };
float qmax = q[0];
if (q[1] > qmax) qmax = q[1];
if (q[2] > qmax) qmax = q[2];
if (qmax <= 0.0f) return;
sw_set_perspective_triangle(1, q[0] / qmax, q[1] / qmax, q[2] / qmax);
}
/* Write a single pixel to VRAM with draw area clipping and mask bit handling */
static void raster_pixel(int32_t x, int32_t y, uint16_t color) {
if (x < (int32_t)draw_area_left || x > (int32_t)draw_area_right) return;
@@ -2190,6 +2259,8 @@ static void gp0_exec_mono_tri(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[2], gp0_cmd_buf[3],
vx, vy);
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
}
@@ -2232,10 +2303,20 @@ static void gp0_exec_mono_quad(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[2],
gp0_cmd_buf[3], tx, ty);
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[3], gp0_cmd_buf[2],
gp0_cmd_buf[4], tx, ty);
gr_draw_flat_triangle(vx[2], vy[2], vx[1], vy[1], vx[3], vy[3], color);
}
}
/* Execute shaded triangle (GP0 0x30-0x33) — Gouraud shaded */
@@ -2255,6 +2336,8 @@ static void gp0_exec_shaded_tri(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3], gp0_cmd_buf[5],
vx, vy);
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
@@ -2295,14 +2378,24 @@ static void gp0_exec_shaded_quad(void) {
}
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3],
gp0_cmd_buf[5], tx, ty);
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[5], gp0_cmd_buf[3],
gp0_cmd_buf[7], tx, ty);
gr_draw_gouraud_triangle(vx[2], vy[2], c[2],
vx[1], vy[1], c[1],
vx[3], vy[3], c[3]);
}
}
/* Helper: build texpage word from GPU state for SW renderer.
@@ -2368,6 +2461,9 @@ static void gp0_exec_textured_tri(void) {
}
setup_textured_draw(color24, semi_trans, raw_texture);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3], gp0_cmd_buf[5],
vx, vy);
prepare_texture_triangle(1, 3, 5);
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -2442,16 +2538,28 @@ static void gp0_exec_textured_quad(void) {
}
}
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3],
gp0_cmd_buf[5], tx, ty);
prepare_texture_triangle(1, 3, 5);
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
clut_x, clut_y, tpage);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[5], gp0_cmd_buf[3],
gp0_cmd_buf[7], tx, ty);
prepare_texture_triangle(5, 3, 7);
gr_draw_textured_triangle(vx[2], vy[2], u[2], v[2],
vx[1], vy[1], u[1], v[1],
vx[3], vy[3], u[3], v[3],
clut_x, clut_y, tpage);
}
}
/* Execute shaded textured triangle (GP0 0x34-0x37) */
@@ -2486,6 +2594,9 @@ static void gp0_exec_shaded_textured_tri(void) {
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[4], gp0_cmd_buf[7],
vx, vy);
prepare_texture_triangle(1, 4, 7);
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
@@ -2529,16 +2640,28 @@ static void gp0_exec_shaded_textured_quad(void) {
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[4],
gp0_cmd_buf[7], tx, ty);
prepare_texture_triangle(1, 4, 7);
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
clut_x, clut_y, tpage, raw_texture);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[7], gp0_cmd_buf[4],
gp0_cmd_buf[10], tx, ty);
prepare_texture_triangle(7, 4, 10);
gr_draw_shaded_textured_triangle(vx[2], vy[2], u[2], v[2], c[2],
vx[1], vy[1], u[1], v[1], c[1],
vx[3], vy[3], u[3], v[3], c[3],
clut_x, clut_y, tpage, raw_texture);
}
}
/* Execute mono line (GP0 0x40-0x47) — Bresenham */
+149 -25
View File
@@ -51,6 +51,11 @@ static uint16_t *g_hr = NULL;
static int g_scale = 1;
static int g_hr_w = VRAM_WIDTH;
static int g_hr_h = VRAM_HEIGHT;
static int g_precise_valid = 0;
static int32_t g_precise_x16[3], g_precise_y16[3];
static int g_perspective_valid = 0;
static float g_perspective_q[3];
static uint32_t g_perspective_triangles = 0;
/* ---- Native-wide compositor (separate present surfaces) ------------------
* Canonical VRAM stays faithful. For an opted-in wide game we ADDITIONALLY
@@ -490,6 +495,49 @@ void sw_renderer_set_scale(int scale) {
int sw_renderer_scale(void) { return g_scale; }
void sw_set_precise_triangle(int enabled,
int32_t x0, int32_t y0,
int32_t x1, int32_t y1,
int32_t x2, int32_t y2) {
g_precise_valid = enabled ? 1 : 0;
g_precise_x16[0] = x0; g_precise_y16[0] = y0;
g_precise_x16[1] = x1; g_precise_y16[1] = y1;
g_precise_x16[2] = x2; g_precise_y16[2] = y2;
}
void sw_set_perspective_triangle(int enabled,
float q0, float q1, float q2) {
g_perspective_valid = enabled && q0 > 0.0f && q1 > 0.0f && q2 > 0.0f;
g_perspective_q[0] = q0;
g_perspective_q[1] = q1;
g_perspective_q[2] = q2;
if (g_perspective_valid) g_perspective_triangles++;
}
uint32_t sw_perspective_triangle_count(void) {
return g_perspective_triangles;
}
static inline int precise_scaled(int axis, int vertex, int fallback, int scale) {
if (!g_precise_valid) return fallback * scale;
int64_t fixed = axis ? g_precise_y16[vertex] : g_precise_x16[vertex];
int64_t scaled = fixed * scale;
if (scaled >= 0) return (int)((scaled + 0x8000) >> 16);
return -(int)((-scaled + 0x8000) >> 16);
}
static inline int precise_wide_x(int vertex, int fallback, int scale,
int dx, const WideBd *bd) {
if (g_precise_valid)
return precise_scaled(0, vertex, fallback, scale) + dx * scale;
return (wide_bd_x(bd, fallback) + dx) * scale;
}
static inline void precise_consumed(void) {
g_precise_valid = 0;
g_perspective_valid = 0;
}
void sw_set_texture_filter(int bilinear) { g_texture_filter = bilinear ? 1 : 0; }
int sw_texture_filter(void) { return g_texture_filter; }
@@ -687,14 +735,21 @@ void sw_draw_flat_triangle(int x0, int y0, int x1, int y1,
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_flat_triangle(&hr, x0*s, y0*s, x1*s, y1*s, x2*s, y2*s, color);
raster_flat_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s),
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s),
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), color);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_flat_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, (wide_bd_x(&bd,x1)+dx)*s, y1*s, (wide_bd_x(&bd,x2)+dx)*s, y2*s, color);
raster_flat_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s),
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s),
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), color);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
@@ -801,14 +856,21 @@ void sw_draw_gouraud_triangle(int x0, int y0, uint16_t c0,
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_gouraud_triangle(&hr, x0*s, y0*s, c0, x1*s, y1*s, c1, x2*s, y2*s, c2);
raster_gouraud_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), c0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), c1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), c2);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_gouraud_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, c0, (wide_bd_x(&bd,x1)+dx)*s, y1*s, c1, (wide_bd_x(&bd,x2)+dx)*s, y2*s, c2);
raster_gouraud_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), c0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), c1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), c2);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
@@ -820,22 +882,27 @@ static void raster_textured_triangle(const RTarget *t,
int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
uint16_t texpage,
int perspective,
float q0, float q1, float q2) {
/* Sort by Y, keeping UV in sync */
if (y0 > y1) {
int tt;
tt=x0; x0=x1; x1=tt; tt=y0; y0=y1; y1=tt;
tt=u0; u0=u1; u1=tt; tt=v0; v0=v1; v1=tt;
float tq=q0; q0=q1; q1=tq;
}
if (y0 > y2) {
int tt;
tt=x0; x0=x2; x2=tt; tt=y0; y0=y2; y2=tt;
tt=u0; u0=u2; u2=tt; tt=v0; v0=v2; v2=tt;
float tq=q0; q0=q2; q2=tq;
}
if (y1 > y2) {
int tt;
tt=x1; x1=x2; x2=tt; tt=y1; y1=y2; y2=tt;
tt=u1; u1=u2; u2=tt; tt=v1; v1=v2; v2=tt;
float tq=q1; q1=q2; q2=tq;
}
int dy_total = y2 - y0;
@@ -860,13 +927,23 @@ static void raster_textured_triangle(const RTarget *t,
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
float qa = q0 + (q2 - q0) * alpha;
float uqa = (float)u0 * q0 + ((float)u2 * q2 - (float)u0 * q0) * alpha;
float vqa = (float)v0 * q0 + ((float)v2 * q2 - (float)v0 * q0) * alpha;
float ub, vb;
float qb, uqb, vqb;
if (second_half) {
ub = u1 + (float)(u2 - u1) * beta;
vb = v1 + (float)(v2 - v1) * beta;
qb = q1 + (q2 - q1) * beta;
uqb = (float)u1 * q1 + ((float)u2 * q2 - (float)u1 * q1) * beta;
vqb = (float)v1 * q1 + ((float)v2 * q2 - (float)v1 * q1) * beta;
} else {
ub = u0 + (float)(u1 - u0) * beta;
vb = v0 + (float)(v1 - v0) * beta;
qb = q0 + (q1 - q0) * beta;
uqb = (float)u0 * q0 + ((float)u1 * q1 - (float)u0 * q0) * beta;
vqb = (float)v0 * q0 + ((float)v1 * q1 - (float)v0 * q0) * beta;
}
if (xa > xb) {
@@ -874,6 +951,9 @@ static void raster_textured_triangle(const RTarget *t,
float tf;
tf = ua; ua = ub; ub = tf;
tf = va; va = vb; vb = tf;
tf = qa; qa = qb; qb = tf;
tf = uqa; uqa = uqb; uqb = tf;
tf = vqa; vqa = vqb; vqb = tf;
}
int span = xb - xa;
@@ -886,6 +966,13 @@ static void raster_textured_triangle(const RTarget *t,
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
if (perspective) {
float q = qa + (qb - qa) * t_val;
if (q > 1.0e-12f) {
fu = (uqa + (uqb - uqa) * t_val) / q;
fv = (vqa + (vqb - vqa) * t_val) / q;
}
}
uint16_t texel = g_texture_filter
? texel_fetch_bilinear(fu, fv, texpage, clut_x, clut_y)
@@ -902,7 +989,7 @@ void sw_draw_textured_triangle(int x0, int y0, int u0, int v0,
uint16_t texpage) {
int64_t area2 = (int64_t)(x1 - x0) * (int64_t)(y2 - y0)
- (int64_t)(x2 - x0) * (int64_t)(y1 - y0);
if (area2 == 0) return;
if (area2 == 0) { precise_consumed(); return; }
if (g_texture_filter) {
int xs[3] = {x0,x1,x2}, ys[3] = {y0,y1,y2};
@@ -912,22 +999,31 @@ void sw_draw_textured_triangle(int x0, int y0, int u0, int v0,
RTarget n = rt_native();
raster_textured_triangle(&n, x0, y0, u0, v0, x1, y1, u1, v1,
x2, y2, u2, v2, clut_x, clut_y, texpage);
x2, y2, u2, v2, clut_x, clut_y, texpage,
g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_textured_triangle(&hr, x0*s, y0*s, u0, v0,
x1*s, y1*s, u1, v1,
x2*s, y2*s, u2, v2, clut_x, clut_y, texpage);
raster_textured_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), u0, v0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), u1, v1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), u2, v2,
clut_x, clut_y, texpage, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_textured_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, u0, v0,
(wide_bd_x(&bd,x1)+dx)*s, y1*s, u1, v1,
(wide_bd_x(&bd,x2)+dx)*s, y2*s, u2, v2, clut_x, clut_y, texpage);
raster_textured_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), u0, v0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), u1, v1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), u2, v2,
clut_x, clut_y, texpage, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
precise_consumed();
}
static inline void color24_to_mod(uint32_t color, int *r, int *g, int *b) {
@@ -944,25 +1040,30 @@ static void raster_shaded_textured_triangle(const RTarget *t,
int x2, int y2, int u2, int v2,
int r2, int g2, int b2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage, int raw_texture) {
uint16_t texpage, int raw_texture,
int perspective,
float q0, float q1, float q2) {
/* Sort by Y, keeping UV and color modulation in sync */
if (y0 > y1) {
int tt;
tt=x0; x0=x1; x1=tt; tt=y0; y0=y1; y1=tt;
tt=u0; u0=u1; u1=tt; tt=v0; v0=v1; v1=tt;
tt=r0; r0=r1; r1=tt; tt=g0; g0=g1; g1=tt; tt=b0; b0=b1; b1=tt;
float tq=q0; q0=q1; q1=tq;
}
if (y0 > y2) {
int tt;
tt=x0; x0=x2; x2=tt; tt=y0; y0=y2; y2=tt;
tt=u0; u0=u2; u2=tt; tt=v0; v0=v2; v2=tt;
tt=r0; r0=r2; r2=tt; tt=g0; g0=g2; g2=tt; tt=b0; b0=b2; b2=tt;
float tq=q0; q0=q2; q2=tq;
}
if (y1 > y2) {
int tt;
tt=x1; x1=x2; x2=tt; tt=y1; y1=y2; y2=tt;
tt=u1; u1=u2; u2=tt; tt=v1; v1=v2; v2=tt;
tt=r1; r1=r2; r2=tt; tt=g1; g1=g2; g2=tt; tt=b1; b1=b2; b2=tt;
float tq=q1; q1=q2; q2=tq;
}
int dy_total = y2 - y0;
@@ -987,20 +1088,29 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
float qa = q0 + (q2 - q0) * alpha;
float uqa = (float)u0 * q0 + ((float)u2 * q2 - (float)u0 * q0) * alpha;
float vqa = (float)v0 * q0 + ((float)v2 * q2 - (float)v0 * q0) * alpha;
float ra = r0 + (float)(r2 - r0) * alpha;
float ga = g0 + (float)(g2 - g0) * alpha;
float ba = b0 + (float)(b2 - b0) * alpha;
float ub, vb, rb, gb, bb;
float ub, vb, qb, uqb, vqb, rb, gb, bb;
if (second_half) {
ub = u1 + (float)(u2 - u1) * beta;
vb = v1 + (float)(v2 - v1) * beta;
qb = q1 + (q2 - q1) * beta;
uqb = (float)u1 * q1 + ((float)u2 * q2 - (float)u1 * q1) * beta;
vqb = (float)v1 * q1 + ((float)v2 * q2 - (float)v1 * q1) * beta;
rb = r1 + (float)(r2 - r1) * beta;
gb = g1 + (float)(g2 - g1) * beta;
bb = b1 + (float)(b2 - b1) * beta;
} else {
ub = u0 + (float)(u1 - u0) * beta;
vb = v0 + (float)(v1 - v0) * beta;
qb = q0 + (q1 - q0) * beta;
uqb = (float)u0 * q0 + ((float)u1 * q1 - (float)u0 * q0) * beta;
vqb = (float)v0 * q0 + ((float)v1 * q1 - (float)v0 * q0) * beta;
rb = r0 + (float)(r1 - r0) * beta;
gb = g0 + (float)(g1 - g0) * beta;
bb = b0 + (float)(b1 - b0) * beta;
@@ -1011,6 +1121,9 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float tf;
tf = ua; ua = ub; ub = tf;
tf = va; va = vb; vb = tf;
tf = qa; qa = qb; qb = tf;
tf = uqa; uqa = uqb; uqb = tf;
tf = vqa; vqa = vqb; vqb = tf;
tf = ra; ra = rb; rb = tf;
tf = ga; ga = gb; gb = tf;
tf = ba; ba = bb; bb = tf;
@@ -1026,6 +1139,13 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
if (perspective) {
float q = qa + (qb - qa) * t_val;
if (q > 1.0e-12f) {
fu = (uqa + (uqb - uqa) * t_val) / q;
fv = (vqa + (vqb - vqa) * t_val) / q;
}
}
int mr = (int)(ra + (rb - ra) * t_val);
int mg = (int)(ga + (gb - ga) * t_val);
int mb = (int)(ba + (bb - ba) * t_val);
@@ -1051,7 +1171,7 @@ void sw_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
uint16_t texpage, int raw_texture) {
int64_t area2 = (int64_t)(x1 - x0) * (int64_t)(y2 - y0)
- (int64_t)(x2 - x0) * (int64_t)(y1 - y0);
if (area2 == 0) return;
if (area2 == 0) { precise_consumed(); return; }
if (g_texture_filter) {
int xs[3] = {x0,x1,x2}, ys[3] = {y0,y1,y2};
@@ -1069,26 +1189,30 @@ void sw_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
x0, y0, u0, v0, r0, g0, b0,
x1, y1, u1, v1, r1, g1, b1,
x2, y2, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_shaded_textured_triangle(&hr,
x0*s, y0*s, u0, v0, r0, g0, b0,
x1*s, y1*s, u1, v1, r1, g1, b1,
x2*s, y2*s, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), u0, v0, r0, g0, b0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), u1, v1, r1, g1, b1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_shaded_textured_triangle(&wt,
(wide_bd_x(&bd,x0)+dx)*s, y0*s, u0, v0, r0, g0, b0,
(wide_bd_x(&bd,x1)+dx)*s, y1*s, u1, v1, r1, g1, b1,
(wide_bd_x(&bd,x2)+dx)*s, y2*s, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), u0, v0, r0, g0, b0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), u1, v1, r1, g1, b1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
+153 -2
View File
@@ -186,6 +186,144 @@ static int32_t s_ws_xnum = 1, s_ws_xden = 1;
extern "C" int gpu_ws_present_native_43(void); /* gpu.c — suppress on 4:3 frames */
extern "C" void psx_ws_note_gte_project(int nverts); /* gpu.c — gte_game_mode stamp */
/* Optional visual-only geometry precision cache. The PS1-visible SXY FIFO
* remains integer and fully faithful; this side cache retains the discarded
* 16.16 projection fraction so the high-resolution software mirror can match
* a later GP0 polygon and place its vertices between native pixels. */
#define GEOM_CACHE_SIZE 8192u
struct GeomVertex {
uint32_t packed;
int32_t x16, y16;
uint32_t generation;
};
static GeomVertex s_geom_cache[GEOM_CACHE_SIZE];
static uint32_t s_geom_generation = 1;
static int s_geom_enabled = 0;
static uint32_t s_geom_hits = 0;
/* Exact GTE projection provenance for perspective texture correction. The
* recompiler/interpreters call gte_precision_store after SWC2 writes an SXY
* register into guest RAM. GPU DMA later supplies that exact RAM address, so
* depth lookup follows the packet rather than guessing from rounded X/Y. */
struct PreciseProjection {
uint32_t packed;
int32_t x16, y16;
uint16_t z;
uint8_t valid;
};
static PreciseProjection s_precise_sxy[4] = {};
#define PRECISION_STORE_SIZE 65536u
struct PrecisionStoreEntry {
uint32_t addr;
PreciseProjection projection;
uint32_t generation;
};
static PrecisionStoreEntry s_precision_store[PRECISION_STORE_SIZE];
static uint32_t s_precision_generation = 1;
static int s_precision_tracking = 0;
static inline uint32_t precision_hash(uint32_t addr) {
addr >>= 2;
addr ^= addr >> 11;
return (addr * 2654435761u) & (PRECISION_STORE_SIZE - 1u);
}
static inline int precision_ram_address(uint32_t addr, uint32_t *physical) {
uint32_t mapped = addr & 0x1FFFFFFFu;
if (mapped >= 0x00800000u) return 0;
*physical = mapped & 0x1FFFFCu;
return 1;
}
extern "C" void gte_precision_tracking_set(int enabled) {
s_precision_tracking = enabled ? 1 : 0;
s_precision_generation++;
if (!s_precision_generation) s_precision_generation = 1;
}
extern "C" void gte_precision_store_word(uint32_t addr, uint8_t reg) {
if (!s_precision_tracking || reg < 12 || reg > 15) return;
int index = reg == 15 ? 2 : (int)reg - 12;
const PreciseProjection &projection = s_precise_sxy[index];
if (!projection.valid) return;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return;
PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
entry.addr = physical;
entry.projection = projection;
entry.generation = s_precision_generation;
}
extern "C" void gte_precision_invalidate_word(uint32_t addr) {
if (!s_precision_tracking) return;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return;
PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
if (entry.generation == s_precision_generation && entry.addr == physical)
entry.generation = 0;
}
extern "C" int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16,
uint16_t *z) {
if (!s_precision_tracking) return 0;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return 0;
const PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
if (entry.generation != s_precision_generation || entry.addr != physical ||
!entry.projection.valid || entry.projection.packed != packed)
return 0;
if (x16) *x16 = entry.projection.x16;
if (y16) *y16 = entry.projection.y16;
if (z) *z = entry.projection.z;
return entry.projection.z != 0;
}
static inline uint32_t geom_hash(uint32_t packed) {
packed ^= packed >> 16;
return (packed * 2654435761u) & (GEOM_CACHE_SIZE - 1u);
}
extern "C" void gte_geometry_correction_set(int enabled) {
s_geom_enabled = enabled ? 1 : 0;
s_geom_hits = 0;
s_geom_generation++;
if (!s_geom_generation) s_geom_generation = 1;
}
extern "C" int gte_geometry_correction_enabled(void) {
return s_geom_enabled;
}
extern "C" uint32_t gte_geometry_correction_hits(void) {
return s_geom_hits;
}
extern "C" int gte_geometry_correction_lookup(uint32_t packed,
int32_t *x16, int32_t *y16) {
if (!s_geom_enabled) return 0;
const GeomVertex &entry = s_geom_cache[geom_hash(packed)];
if (entry.generation != s_geom_generation || entry.packed != packed) return 0;
if (x16) *x16 = entry.x16;
if (y16) *y16 = entry.y16;
s_geom_hits++;
return 1;
}
static inline void geom_note(uint32_t packed, int64_t x16, int64_t y16) {
if (!s_geom_enabled) return;
/* Saturated off-screen projections are unsuitable for subpixel recovery. */
int32_t x = (int16_t)(packed & 0xFFFFu);
int32_t y = (int16_t)(packed >> 16);
if (x <= -0x400 || x >= 0x3FF || y <= -0x400 || y >= 0x3FF) return;
GeomVertex &entry = s_geom_cache[geom_hash(packed)];
entry.packed = packed;
entry.x16 = (int32_t)x16;
entry.y16 = (int32_t)y16;
entry.generation = s_geom_generation;
}
// Per-draw suppression of the X-squash (8C far-backdrop). The far backdrop
// (ocean/cloud/distant mountain) is a parallax layer that is conceptually at
// infinity: squashing it toward centre leaves its geometry short of the
@@ -651,9 +789,20 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0) {
}
}
dome_probe_note(gte->SZ[3]); /* locate the dome draw fn (far-vertex tally) */
int64_t sx = (gte->OFX + xterm) >> 16;
int64_t sy = (gte->OFY + (int64_t)gte->IR2 * h_div_sz) >> 16;
int64_t sx16 = gte->OFX + xterm;
int64_t sy16 = gte->OFY + (int64_t)gte->IR2 * h_div_sz;
int64_t sx = sx16 >> 16;
int64_t sy = sy16 >> 16;
gte->push_sxy(sx, sy);
s_precise_sxy[0] = s_precise_sxy[1];
s_precise_sxy[1] = s_precise_sxy[2];
s_precise_sxy[2].packed = (uint32_t)gte->SXY[2];
s_precise_sxy[2].x16 = (int32_t)sx16;
s_precise_sxy[2].y16 = (int32_t)sy16;
s_precise_sxy[2].z = gte->SZ[3];
s_precise_sxy[2].valid = gte->SZ[3] != 0;
s_precise_sxy[3] = s_precise_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
if (setMac0) {
@@ -1384,6 +1533,8 @@ extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
GTEState gte;
gte_load_data(gte, cpu);
gte_mtc2(&gte, reg, val);
if (reg >= 12 && reg <= 15)
for (int i = 0; i < 4; i++) s_precise_sxy[i].valid = 0;
for (int i = 0; i < 32; i++) cpu->gte_data[i] = gte_mfc2(&gte, i);
for (int i = 0; i < 32; i++) cpu->gte_ctrl[i] = gte_cfc2(&gte, i);
}
+4
View File
@@ -1308,6 +1308,7 @@ static inline void d44_note(uint32_t phys, uint32_t old, uint32_t val) {
}
static void psx_write_word_raw(uint32_t addr, uint32_t val);
extern void gte_precision_invalidate_word(uint32_t addr);
void psx_write_word(uint32_t addr, uint32_t val) {
extern void (*g_overlay_flush_pending_cycles)(void);
if (g_overlay_flush_pending_cycles) g_overlay_flush_pending_cycles();
@@ -1320,6 +1321,7 @@ void psx_write_word(uint32_t addr, uint32_t val) {
}
static void psx_write_word_raw(uint32_t addr, uint32_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
/* KSEG2 cache control — before physical translation. */
if (addr == 0xFFFE0130u) { cache_ctrl = val; return; }
/* KSEG2 guard — see psx_read_word_raw. */
@@ -1474,6 +1476,7 @@ void psx_write_half(uint32_t addr, uint16_t val) {
}
static void psx_write_half_raw(uint32_t addr, uint16_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
/* KSEG2 guard — see psx_read_word_raw. */
@@ -1705,6 +1708,7 @@ void psx_write_byte(uint32_t addr, uint8_t val) {
}
static void psx_write_byte_raw(uint32_t addr, uint8_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
/* KSEG2 guard — see psx_read_word_raw. */
+1 -1
View File
@@ -3487,7 +3487,7 @@ void psx_sljit_cop2(CPUState *cpu, uint32_t insn) {
int32_t simm = (int32_t)(int16_t)(insn & 0xFFFFu);
uint32_t addr = cpu->gpr[rs] + (uint32_t)simm;
if (op == 0x32) { gte_write_data(cpu, (uint8_t)rt, cpu->read_word(addr)); } /* LWC2 */
else if (op == 0x3A) { cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt)); } /* SWC2 */
else if (op == 0x3A) { cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt)); gte_precision_store_word(addr, (uint8_t)rt); } /* SWC2 */
}
/* Unaligned load/store helper — mirrors dirty_ram_interp.c interp_lwl/lwr/swl/swr
+1
View File
@@ -500,6 +500,7 @@ static void exec_one(CPUState* cpu) {
case 0x3A: { /* SWC2 */
uint32_t addr = rs_val + (uint32_t)SIMM(insn);
cpu->write_word(addr, gte_read_data(cpu, RT(insn)));
gte_precision_store_word(addr, RT(insn));
break;
}