mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
Recover saturated winding at guarded native-wide rendering branches
This commit is contained in:
@@ -33,3 +33,27 @@ ordinary conservative geometry consumer continues to reject it.
|
||||
This policy cannot recover a projection whose provenance has already been lost
|
||||
through unsupported CPU arithmetic, or repair a guest culling decision made
|
||||
before GPU submission. Those paths still need title-specific qualification.
|
||||
|
||||
## Saturation-aware winding consumers
|
||||
|
||||
A title can separately bind exact branch words with
|
||||
`psx_mod_set_native_wide_nclip_sites(addresses, expected, count)` and list its
|
||||
BLEZ, BGTZ or BGEZ consumers under `[widescreen.cull] nclip_exact_sites`.
|
||||
Enable `[widescreen] precise_nclip = true` to collect checked GTE projections.
|
||||
No sites are bound by default. Architectural NCLIP MAC0 and FLAG remain stock;
|
||||
only the selected rendering branch consumes the corrected winding.
|
||||
|
||||
This narrower predicate requires a matching native MAC0, live projection
|
||||
generation, matching packed words and integer Y, positive depth outside the
|
||||
GTE near region, bounded precision coordinates, and an X projection that
|
||||
exceeds its corresponding saturation rail. It leaves ordinary subpixel winding
|
||||
unchanged. Zero reveal, stale/missing precision, altered branch words, other
|
||||
addresses, speculative execution and timeline invalidation retain the native
|
||||
decision. Both triangle rejection and the quad's winding keep branches need
|
||||
to be identified from the title's renderer.
|
||||
|
||||
Generated functions, cached overlay callbacks and the interpreter use the same
|
||||
full-word-bound host predicate. Overlay callback ABI v27 and codegen version 14
|
||||
prevent older cached modules from hiding the new forwarding surface.
|
||||
The `ws_nw` diagnostic also reports `nclip_rescues`, the number of branch
|
||||
decisions corrected since the title last bound its sites.
|
||||
|
||||
@@ -821,6 +821,13 @@ std::string CodeGenerator::generate_branch_condition(uint32_t instr, uint32_t ad
|
||||
// sweeps data-as-code into a function; emitting the hardware decode keeps
|
||||
// the regen alive AND matches the oracle if the word is ever executed.
|
||||
check_explicit_branch_site(addr, instr);
|
||||
if (config_.ws_cull_nclip_exact_sites.count(addr) &&
|
||||
(opcode == 0x06 || opcode == 0x07 ||
|
||||
(opcode == 0x01 && ((instr >> 16) & 31) == 1))) {
|
||||
const char* compare = opcode == 0x06 ? "<= 0" : opcode == 0x07 ? "> 0" : ">= 0";
|
||||
return fmt::format("psx_ws_nclip_branch(0x{:08X}u, 0x{:08X}u, (int32_t){}, ((int32_t){} {})) /* guarded wide nclip */",
|
||||
addr, instr, reg_name(rs), reg_name(rs), compare);
|
||||
}
|
||||
if (opcode == 0x01) {
|
||||
uint32_t regimm_op = (instr >> 16) & 0x1F;
|
||||
if ((regimm_op & 0x01u) == 0x00u) { // bltz family (incl. bltzal + undefined mirrors)
|
||||
@@ -3450,6 +3457,7 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const {
|
||||
ss << "extern int psx_datashard_enter(CPUState* cpu, uint32_t key); /* data-shard replay/capture (data_shards.c) */\n";
|
||||
ss << "extern int psx_mod_function_entry(CPUState* cpu, uint32_t address); /* trusted opt-in game-mod hook */\n";
|
||||
ss << "extern int psx_ws_masked_reject(uint32_t flags, uint32_t mask); /* guarded packed-coordinate reject */\n";
|
||||
ss << "extern int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla);\n";
|
||||
ss << "extern void psx_datashard_ret(CPUState* cpu); /* data-shard capture finalize */\n";
|
||||
ss << "extern int psx_vsync_query_hle_enter(CPUState* cpu, uint32_t func, uint32_t counter_addr, uint32_t gpustat_ptr_addr, uint32_t timer1_ptr_addr, uint32_t timer1_cache_addr); /* load_accel.c */\n";
|
||||
ss << "extern void psx_ws_sprite_tag(CPUState* cpu); /* widescreen prim tag (gpu.c) */\n";
|
||||
|
||||
@@ -822,6 +822,12 @@ void codegen_tests() {
|
||||
std::string::npos &&
|
||||
nclip_exact.find("ws exact nclip") != std::string::npos,
|
||||
"codegen emits title-scoped exact NCLIP predicate");
|
||||
for (uint32_t word : {0x18400002u, 0x1C400002u, 0x04410002u}) {
|
||||
const std::string guarded = generate_first_instruction(word, {}, false, nclip_exact_config);
|
||||
check(guarded.find("psx_ws_nclip_branch(0x80010000u,") != std::string::npos &&
|
||||
guarded.find("guarded wide nclip") != std::string::npos,
|
||||
"inclusive/keep NCLIP branches carry full-word identity to the host");
|
||||
}
|
||||
|
||||
PSXRecomp::CodeGenConfig plane_nx_config;
|
||||
plane_nx_config.ws_cull_plane_nx_sites.insert(0x80010000u);
|
||||
|
||||
@@ -239,6 +239,8 @@ extern void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks,
|
||||
* only when it belongs to the supplied native MAC0; otherwise preserves the
|
||||
* 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 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).
|
||||
* The emitter writes them unconditionally; they expand to real calls only
|
||||
|
||||
@@ -339,6 +339,8 @@ void gpu_ws_set_masked_reject_sites(const uint32_t *addresses,
|
||||
int psx_ws_masked_reject(uint32_t flags, uint32_t mask);
|
||||
int psx_ws_masked_reject_site(uint32_t pc, uint32_t instr,
|
||||
uint32_t flags, int vanilla);
|
||||
int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla);
|
||||
uint64_t gpu_ws_native_wide_nclip_rescues(void);
|
||||
int psx_ws_is_cull_clip_edge_x_load_site(uint32_t pc);
|
||||
uint32_t psx_ws_clip_edge_width(void);
|
||||
uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced);
|
||||
|
||||
@@ -113,6 +113,11 @@ int32_t psx_mod_widescreen_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);
|
||||
/* 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);
|
||||
|
||||
/* 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
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
* retaining the v24 last_store_pc pointer. */
|
||||
/* v26: guarded packed-coordinate rejection callback, preserving the selected
|
||||
* rejection axes while the native-wide renderer clips horizontal margins. */
|
||||
#define PSX_OVERLAY_ABI_VERSION 26
|
||||
/* v27: full-word-bound native-wide winding branch callback. */
|
||||
#define PSX_OVERLAY_ABI_VERSION 27
|
||||
|
||||
/* Process-lifetime overlay candidate capacity. Every accepted manifest F
|
||||
* record consumes one slot, even when another DLL carries an identical
|
||||
@@ -183,7 +184,8 @@
|
||||
* so packaged pairs cannot be hidden by an older user cache on first boot. */
|
||||
/* 13: merge adaptive backdrop v12: independent quotient consumers, proven
|
||||
* copied/biased camera inputs, and two biased bounds for terrain strips. */
|
||||
#define PSX_OVERLAY_CODEGEN_VER 13
|
||||
/* 14: guarded saturation-aware NCLIP branch consumers and ABI v27 forwarding. */
|
||||
#define PSX_OVERLAY_CODEGEN_VER 14
|
||||
|
||||
/* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen
|
||||
* sources). Folded into the cache PATH next to cg<N> (gcc/<arch-abi>/cg<N>_<hash>/)
|
||||
@@ -360,6 +362,7 @@ typedef struct OverlayCallbacks {
|
||||
* do. NULL leaves the DLL writing a private copy nothing reads. */
|
||||
uint32_t *last_store_pc;
|
||||
int (*ws_masked_reject)(uint32_t flags, uint32_t mask);
|
||||
int (*ws_nclip_branch)(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla);
|
||||
} OverlayCallbacks;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -268,6 +268,9 @@ uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced) {
|
||||
int psx_ws_masked_reject(uint32_t flags, uint32_t mask) {
|
||||
return g_cbs.ws_masked_reject ? g_cbs.ws_masked_reject(flags, mask) : flags != 0u;
|
||||
}
|
||||
int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla) {
|
||||
return g_cbs.ws_nclip_branch ? g_cbs.ws_nclip_branch(pc, instr, mac0, vanilla) : vanilla;
|
||||
}
|
||||
uint32_t psx_ws_aspect_cone_result(uint32_t site, uint32_t vanilla,
|
||||
uint32_t object, int32_t x, int32_t z,
|
||||
int32_t y) {
|
||||
|
||||
@@ -8081,9 +8081,10 @@ 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}",
|
||||
"\"projection_correction\":%d,\"projection_vertices\":%llu,\"nclip_rescues\":%llu}",
|
||||
id, psx_ws_get_native_wide(), ws.mode, ws.nw_extra,
|
||||
correction, (unsigned long long)corrected_vertices);
|
||||
correction, (unsigned long long)corrected_vertices,
|
||||
(unsigned long long)gpu_ws_native_wide_nclip_rescues());
|
||||
}
|
||||
|
||||
/* Live scanline post-process toggle (A/B): `scanline on=<0|1> pct=<0..100>`.
|
||||
|
||||
@@ -1986,6 +1986,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
}
|
||||
case 0x06: { /* BLEZ */
|
||||
int taken = ws_branch_keep(pc, (int32_t)cpu->gpr[rs] <= 0);
|
||||
taken = psx_ws_nclip_branch(pc, insn, (int32_t)cpu->gpr[rs], taken);
|
||||
exec_delay_slot(cpu, pc + 4);
|
||||
cosim_exec_one_transfer_hook(pc + 4);
|
||||
cpu->pc = taken ? (pc + 4 + (simm << 2)) : (pc + 8);
|
||||
@@ -1993,6 +1994,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
}
|
||||
case 0x07: { /* BGTZ */
|
||||
int taken = ws_branch_keep(pc, (int32_t)cpu->gpr[rs] > 0);
|
||||
taken = psx_ws_nclip_branch(pc, insn, (int32_t)cpu->gpr[rs], taken);
|
||||
exec_delay_slot(cpu, pc + 4);
|
||||
cosim_exec_one_transfer_hook(pc + 4);
|
||||
cpu->pc = taken ? (pc + 4 + (simm << 2)) : (pc + 8);
|
||||
@@ -2026,6 +2028,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
default: return abort_unsupported(pc, insn, "REGIMM rt");
|
||||
}
|
||||
taken = ws_branch_keep(pc, taken);
|
||||
taken = psx_ws_nclip_branch(pc, insn, (int32_t)cpu->gpr[rs], taken);
|
||||
exec_delay_slot(cpu, pc + 4);
|
||||
cosim_exec_one_transfer_hook(pc + 4);
|
||||
cpu->pc = taken ? (pc + 4 + (simm << 2)) : (pc + 8);
|
||||
|
||||
+34
-1
@@ -274,7 +274,7 @@ void gpu_ws_set_precise_nclip(int on) {
|
||||
* PGXP correction is disabled. Re-derive the internal transport arm. */
|
||||
gpu_pgxp_rederive_enable();
|
||||
}
|
||||
int gpu_ws_precise_nclip_enabled(void) { return ws_precise_nclip_cfg && ws_active(); }
|
||||
int gpu_ws_precise_nclip_enabled(void) { return ws_precise_nclip_cfg && (ws_active() || ws_nw_extra() > 0); }
|
||||
void gpu_ws_set_gameplay_state_gate(uint32_t addr,
|
||||
const uint32_t *values, int nvalues) {
|
||||
if (nvalues < 0) nvalues = 0;
|
||||
@@ -883,6 +883,39 @@ int psx_ws_masked_reject_site(uint32_t pc, uint32_t instr,
|
||||
return vanilla;
|
||||
}
|
||||
|
||||
static WsMaskedRejectSite ws_nclip_sites[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; }
|
||||
void psx_mod_set_native_wide_nclip_sites(const uint32_t* addresses,
|
||||
const uint32_t* expected, int count) {
|
||||
if (count < 0 || count > WS_EXPLICIT_CULL_SITES_MAX) abort();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const uint32_t op = expected[i] >> 26, rt = (expected[i] >> 16) & 31;
|
||||
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_site_count = count;
|
||||
ws_nclip_rescues = 0;
|
||||
}
|
||||
extern int gte_nclip_native_wide_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 uint32_t op = instr >> 26;
|
||||
const int result = op == 6 ? sign <= 0 : op == 7 ? sign > 0 :
|
||||
((instr >> 16) & 1) ? sign >= 0 : sign < 0;
|
||||
if (result != vanilla) ++ws_nclip_rescues;
|
||||
return result;
|
||||
}
|
||||
return vanilla;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t address;
|
||||
uint32_t expected;
|
||||
|
||||
@@ -940,6 +940,8 @@ static uint64_t s_nclip_disagreements = 0;
|
||||
static int32_t s_nclip_last_native = 0;
|
||||
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;
|
||||
extern "C" void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks,
|
||||
uint64_t *disagreements) {
|
||||
if (hits) *hits = s_nclip_precise_hits;
|
||||
@@ -951,6 +953,13 @@ extern "C" int gte_nclip_precise_bltz(int32_t native_mac0) {
|
||||
return native_mac0 < 0;
|
||||
return s_nclip_last_precise_sign < 0;
|
||||
}
|
||||
extern "C" int gte_nclip_native_wide_sign(int32_t native_mac0, int* sign) {
|
||||
if (!s_nclip_last_precise_valid || !s_nclip_last_horizontal_saturated ||
|
||||
native_mac0 != s_nclip_last_native || s_nclip_last_generation != s_geom_generation ||
|
||||
s_gte_replay_sandbox || s_speculative_depth != 0) return 0;
|
||||
*sign = s_nclip_last_precise_sign;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
gte->FLAG = 0;
|
||||
@@ -967,6 +976,7 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
int32_t out = static_cast<int32_t>(mac0);
|
||||
s_nclip_last_native = out;
|
||||
s_nclip_last_precise_valid = false;
|
||||
s_nclip_last_horizontal_saturated = false;
|
||||
/* Compute an exact 16.16 determinant for configured branch consumers, but
|
||||
* preserve native guest-visible MAC0 for every architectural reader. */
|
||||
int32_t px0, py0, px1, py1, px2, py2;
|
||||
@@ -983,6 +993,22 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
const int64_t cross = dx10 * dy20 - dy10 * dx20;
|
||||
s_nclip_last_precise_sign = cross < 0 ? -1 : (cross > 0 ? 1 : 0);
|
||||
s_nclip_last_precise_valid = true;
|
||||
s_nclip_last_generation = s_geom_generation;
|
||||
/* Narrow rescue: exact Y still agrees with the architectural FIFO,
|
||||
* depth is outside the GTE near region, and at least one X exceeded
|
||||
* the matching saturation rail. Ordinary subpixel winding stays stock. */
|
||||
const int raw_x[3] = {sx0, sx1, sx2}, raw_y[3] = {sy0, sy1, sy2};
|
||||
const int32_t exact_x[3] = {px0, px1, px2}, exact_y[3] = {py0, py1, py2};
|
||||
bool checked = true, saturated = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int x = exact_x[i] >> 16;
|
||||
checked &= (exact_y[i] >> 16) == raw_y[i] && x > -4096 && x < 4096;
|
||||
checked &= gte->SZ[i + 1] >= gte->H / 2 && gte->SZ[i + 1] != 0;
|
||||
if (raw_x[i] == 1023 && x > 1023) saturated = true;
|
||||
else if (raw_x[i] == -1024 && x < -1024) saturated = true;
|
||||
else checked &= x == raw_x[i];
|
||||
}
|
||||
s_nclip_last_horizontal_saturated = checked && saturated;
|
||||
if ((cross > 0 && out <= 0) || (cross < 0 && out >= 0))
|
||||
s_nclip_disagreements++;
|
||||
} else if (gpu_ws_precise_nclip_enabled() &&
|
||||
|
||||
@@ -2629,6 +2629,8 @@ static void init_callbacks(void) {
|
||||
{
|
||||
extern int psx_ws_masked_reject(uint32_t flags, uint32_t mask);
|
||||
s_callbacks.ws_masked_reject = psx_ws_masked_reject;
|
||||
extern int psx_ws_nclip_branch(uint32_t, uint32_t, int32_t, int);
|
||||
s_callbacks.ws_nclip_branch = psx_ws_nclip_branch;
|
||||
}
|
||||
{
|
||||
extern uint32_t psx_ws_aspect_cone_result(
|
||||
|
||||
@@ -335,3 +335,6 @@ int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
|
||||
(void)out; (void)pitch; (void)base_x; (void)disp_y; (void)disp_h;
|
||||
return 0;
|
||||
}
|
||||
int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; (void)sign; return 0;
|
||||
}
|
||||
|
||||
@@ -625,6 +625,39 @@ int test_precise_nclip_is_title_scoped() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_saturated_nclip_keeps_architectural_result() {
|
||||
CPUState cpu{};
|
||||
gte_precision_tracking_set(1);
|
||||
g_test_precise_nclip_enabled = 1;
|
||||
cpu.gte_ctrl[26] = 300;
|
||||
const int px[] = {1100, 1200, 1300}, py[] = {100, 200, 400};
|
||||
for (unsigned i=0;i<3;i++) {
|
||||
uint32_t packed = 1023u | ((uint32_t)py[i] << 16);
|
||||
cpu.gte_data[12+i] = packed; cpu.gte_data[17+i] = 1000;
|
||||
gte_test_seed_precise_projection(i, packed, px[i]*65536, py[i]*65536, 1000);
|
||||
}
|
||||
gte_execute(&cpu, 6);
|
||||
int sign = 0;
|
||||
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]);
|
||||
cpu.gte_data[17] = 10;
|
||||
gte_execute(&cpu, 6);
|
||||
if (gte_nclip_native_wide_sign(0, &sign) || cpu.gte_data[24] != 0)
|
||||
return fail_value("near depth retains native reject",0,6,0,0,cpu.gte_data[24]);
|
||||
cpu.gte_data[17] = 1000;
|
||||
gte_execute(&cpu, 6);
|
||||
gte_precision_timeline_invalidate();
|
||||
if (gte_nclip_native_wide_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);
|
||||
if (gte_nclip_native_wide_sign(0, &sign))
|
||||
return fail_value("stale NCLIP projection refuses rescue",0,6,0,0,1);
|
||||
g_test_precise_nclip_enabled = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_precision_speculative_transaction() {
|
||||
constexpr uint32_t address = 0x00123450u;
|
||||
constexpr uint32_t packed = 0x00420021u;
|
||||
@@ -739,6 +772,7 @@ int main() {
|
||||
if (int rc = test_command_timing_hook()) return rc;
|
||||
if (int rc = test_precise_sxy_invalidation()) return rc;
|
||||
if (int rc = test_precise_nclip_is_title_scoped()) return rc;
|
||||
if (int rc = test_saturated_nclip_keeps_architectural_result()) return rc;
|
||||
if (int rc = test_precision_speculative_transaction()) return rc;
|
||||
std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle");
|
||||
return 0;
|
||||
|
||||
@@ -68,6 +68,10 @@ uint32_t psx_cyc_lwc2_read(CPUState *cpu, uint32_t a) { (void)cpu; return psx_re
|
||||
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;
|
||||
int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
|
||||
(void)mac0; *sign = precise_sign; return precise_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; }
|
||||
@@ -413,12 +417,40 @@ static void test_masked_reject(void) {
|
||||
gpu_ws_set_margin_override(-1);
|
||||
}
|
||||
|
||||
static void test_wide_nclip_branches(void) {
|
||||
CPUState cpu = {0};
|
||||
ws_xnum = 3; ws_xden = 4; ws_mode = 0;
|
||||
ws_precise_nclip_cfg = 1;
|
||||
precise_sign_valid = 1; precise_sign = 1;
|
||||
const uint32_t sites[] = {CODE+0x300,CODE+0x340,CODE+0x380};
|
||||
const uint32_t words[] = {i_type(6,A0,0,3),i_type(7,A0,0,3),i_type(1,A0,1,3)};
|
||||
psx_mod_set_native_wide_nclip_sites(sites,words,3);
|
||||
for (int margin=0;margin<=53;margin+=53) {
|
||||
gpu_ws_set_margin_override(margin);
|
||||
cpu.gpr[A0]=0;
|
||||
CHECK(branch_taken(&cpu,sites[0],words[0]) == (margin==0), "BLEZ native zero vs precise positive");
|
||||
CHECK(branch_taken(&cpu,sites[1],words[1]) == (margin!=0), "BGTZ native zero vs precise positive");
|
||||
CHECK(cpu.gpr[A0]==0, "winding helper leaves guest MAC0 register alone");
|
||||
cpu.gpr[A0]=(uint32_t)-1;
|
||||
CHECK(branch_taken(&cpu,sites[2],words[2]) == (margin!=0), "BGEZ exact keep predicate");
|
||||
CHECK(branch_taken(&cpu,sites[0]+4,words[0]), "unlisted twin stays native");
|
||||
cpu.gpr[V0]=(uint32_t)-1;
|
||||
CHECK(branch_taken(&cpu,sites[0],i_type(6,V0,0,3)), "wrong full word stays native");
|
||||
}
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1) s_capture_path = argv[1];
|
||||
test_store();
|
||||
test_helpers();
|
||||
test_interpreter();
|
||||
test_masked_reject();
|
||||
test_wide_nclip_branches();
|
||||
if (failures) {
|
||||
fprintf(stderr, "%d check(s) failed\n", failures);
|
||||
return 1;
|
||||
|
||||
@@ -22,6 +22,10 @@ static uint32_t seen_address, cycles;
|
||||
static int32_t bound(int32_t x) { return x * 2; }
|
||||
static int margin120(void) { return 120; }
|
||||
static int packed_reject(uint32_t flags, uint32_t mask) { return (flags & mask) != 0; }
|
||||
static int nclip_branch(uint32_t pc, uint32_t word, int32_t mac0, int vanilla) {
|
||||
assert(pc == 0x80021EAC && word == 0x1900FFCF && mac0 == 0 && vanilla == 1);
|
||||
return 0;
|
||||
}
|
||||
static void advance(uint32_t n) { cycles += n; }
|
||||
static int entry(CPUState *cpu, uint32_t address) {
|
||||
assert(cycles == 17); seen_cpu = cpu; seen_address = address;
|
||||
@@ -66,6 +70,10 @@ int main(void) {
|
||||
assert(psx_ws_masked_reject(0xFE00u, 0xFFFF0000u) == 0);
|
||||
assert(psx_ws_masked_reject(0xFF00FE00u, 0xFFFF0000u) == 1);
|
||||
assert(psx_ws_masked_reject(0u, 0xFFFF0000u) == 0);
|
||||
assert(psx_ws_nclip_branch(0x80021EAC, 0x1900FFCF, 0, 1) == 1);
|
||||
callbacks.ws_nclip_branch = nclip_branch;
|
||||
overlay_init(&callbacks);
|
||||
assert(psx_ws_nclip_branch(0x80021EAC, 0x1900FFCF, 0, 1) == 0);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user