mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Preserve wide backdrop composites and guard GTE flag rejection
(cherry picked from commit 6b6402e7f0)
This commit is contained in:
@@ -221,7 +221,8 @@ them.
|
||||
|
||||
### Guarded packed-coordinate rejection
|
||||
|
||||
Some polygon funnels combine packed SXY flags and reject on `BNE flags,zero`.
|
||||
Some polygon funnels combine packed SXY flags and reject on `BNE flags,zero`,
|
||||
or read the GTE error summary with `BLTZ flags`.
|
||||
The native-wide GPU can clip horizontal overflow while the game retains its
|
||||
vertical rejection bits. Declare only verified render rejection branches:
|
||||
|
||||
@@ -238,6 +239,10 @@ wide gameplay only the specified flag bits participate in the predicate; guest
|
||||
registers, the branch delay slot, vertical/depth/backface tests and timing remain
|
||||
intact. Adapters must provide sufficient bounded primitive/capture capacity.
|
||||
Changing a site, guard or mask changes the overlay configuration fingerprint.
|
||||
For a verified GTE `BLTZ` consumer, mask the underlying error bits rather than
|
||||
bit 31 itself. For example, `0x7F87A000` preserves the hardware error constituents
|
||||
except horizontal SX saturation. Positive non-summary flags retain their original
|
||||
acceptance at 4:3. Link branches and other REGIMM forms are not supported.
|
||||
|
||||
### Guarded widescreen participation comparisons
|
||||
|
||||
|
||||
@@ -802,6 +802,11 @@ std::string CodeGenerator::generate_branch_condition(uint32_t instr, uint32_t ad
|
||||
fmt::print(stderr, "ERROR: masked_reject instruction mismatch at 0x{:08X}\n", addr);
|
||||
std::exit(1);
|
||||
}
|
||||
// BLTZ GTE error-summary consumers retain the signed original
|
||||
// predicate at 4:3. Non-summary positive flags are not a reject.
|
||||
if (opcode == 1u)
|
||||
return fmt::format("psx_ws_x_margin() > 0 ? psx_ws_masked_reject({}, 0x{:08X}u) : ((int32_t){} < 0)",
|
||||
reg_name(rs), site.reject_mask, reg_name(rs));
|
||||
return fmt::format("psx_ws_masked_reject({}, 0x{:08X}u)",
|
||||
reg_name(rs), site.reject_mask);
|
||||
}
|
||||
|
||||
@@ -2559,10 +2559,12 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
"widescreen.cull.masked_reject.expected");
|
||||
site.reject_mask = parse_hex(toml::find<std::string>(item, "reject_mask"),
|
||||
"widescreen.cull.masked_reject.reject_mask");
|
||||
if ((site.address & 3u) || (site.expected >> 26) != 5u ||
|
||||
const uint32_t opcode = site.expected >> 26;
|
||||
const bool supported = opcode == 5u || opcode == 1u;
|
||||
if ((site.address & 3u) || !supported ||
|
||||
((site.expected >> 16) & 31u) != 0u ||
|
||||
((site.expected >> 21) & 31u) == 0u || !site.reject_mask)
|
||||
throw std::runtime_error("masked_reject needs aligned BNE reg,zero and nonzero reject_mask");
|
||||
throw std::runtime_error("masked_reject needs aligned BNE reg,zero or BLTZ reg and nonzero reject_mask");
|
||||
if (!seen.insert(site.address & 0x1FFFFFFFu).second)
|
||||
throw std::runtime_error("duplicate masked_reject address");
|
||||
ws_cull_masked_reject_sites.push_back(site);
|
||||
|
||||
@@ -413,6 +413,29 @@ reject_mask = "0xFFFF0000"
|
||||
const auto other = generate_first_instruction(0x14800003u, config).full_code;
|
||||
check(other.find("psx_ws_masked_reject(") == std::string::npos,
|
||||
"overlay with another instruction stays vanilla");
|
||||
|
||||
const std::string flag_body = plain + R"toml(
|
||||
[[widescreen.cull.masked_reject]]
|
||||
address = "0x80010000"
|
||||
expected = "0x04400002"
|
||||
reject_mask = "0x7F87A000"
|
||||
)toml";
|
||||
const auto flag_config = load(flag_body);
|
||||
config.ws_cull_masked_reject_sites = flag_config.ws_cull_masked_reject_sites;
|
||||
config.overlay_mode = false;
|
||||
const auto flags = generate_first_instruction(kBltzV0, config).full_code;
|
||||
check(flags.find("psx_ws_x_margin() > 0 ? psx_ws_masked_reject(cpu->gpr[2], 0x7F87A000u) : ((int32_t)cpu->gpr[2] < 0)") != std::string::npos,
|
||||
"GTE summary branch preserves signed predicate at 4:3");
|
||||
check(load_throws_with(plain + R"toml(
|
||||
[[widescreen.cull.masked_reject]]
|
||||
address = "0x80010000"
|
||||
expected = "0x04500002"
|
||||
reject_mask = "0x7F87A000"
|
||||
)toml", "BLTZ reg"), "masked reject excludes link branch");
|
||||
config.overlay_mode = true;
|
||||
const auto mismatched_flags = generate_first_instruction(kBgezV0, config).full_code;
|
||||
check(mismatched_flags.find("psx_ws_masked_reject(") == std::string::npos,
|
||||
"GTE overlay opcode mismatch keeps signed vanilla branch");
|
||||
}
|
||||
|
||||
// ---- [[widescreen.cull.sxy]] ----------------------------------------------
|
||||
|
||||
@@ -2193,6 +2193,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_masked_reject_site(pc, insn, cpu->gpr[rs], 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);
|
||||
|
||||
+7
-3
@@ -995,6 +995,7 @@ 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) {
|
||||
if (psx_ws_x_margin() <= 0) return vanilla;
|
||||
for (int i = 0; i < ws_masked_reject_n; ++i) {
|
||||
const WsMaskedRejectSite *s = &ws_masked_reject_sites[i];
|
||||
if (s->address == (pc & 0x1FFFFFFFu) && s->expected == instr)
|
||||
@@ -2655,13 +2656,18 @@ int gpu_ws_background_stretch_active(void) {
|
||||
frame - ws_background_tag_frame <= WS_HUD_ANCHOR_FRESH_FRAMES;
|
||||
}
|
||||
|
||||
/* Legacy title opt-ins also change background sampling inside the original
|
||||
* viewport. Keep their wide composite intact, just like explicit packet tags. */
|
||||
static int ws_nw_phase_backdrop = 0;
|
||||
static int ws_nw_flat_backdrop = 0;
|
||||
int gpu_ws_background_requires_full_composite(void) {
|
||||
/* Freshness governs reuse of a packet address, not the lifetime of the
|
||||
* pixels it produced. A slow game frame or a held display buffer can keep
|
||||
* those pixels on screen indefinitely. Conservatively keep full mirroring
|
||||
* once a composite has been tagged; GPU reset/snapshot restore clears the
|
||||
* latch together with the tags. Never modify the packet freshness guard. */
|
||||
return ws_background_tags_used && ws_native_wide_configured();
|
||||
return ws_native_wide_configured() &&
|
||||
(ws_background_tags_used || ws_nw_phase_backdrop || ws_nw_flat_backdrop);
|
||||
}
|
||||
|
||||
static int ws_nw_explicit_background(void) {
|
||||
@@ -2930,7 +2936,6 @@ static int ws_tagged_anchor(int32_t *out_ax) {
|
||||
* flower-field tiles — the 3D rock/foreground is untagged AND has narrow prims,
|
||||
* so tag/narrow heuristics alone mis-stretch and tear it. */
|
||||
uint32_t g_ws_backdrop_lo = 0, g_ws_backdrop_hi = 0;
|
||||
static int ws_nw_phase_backdrop = 0;
|
||||
void gpu_ws_set_nw_phase_backdrop(int on) { ws_nw_phase_backdrop = on ? 1 : 0; }
|
||||
static int ws_nw_textured_edges = 0;
|
||||
int g_ws_tex_edge_pct = 0;
|
||||
@@ -3475,7 +3480,6 @@ static void ws_nw_hud_shift_vertices(int32_t *vx, int count) {
|
||||
* Transforms vx[0..3] IN PLACE (pre-draw_offset); returns 1 if it applied. */
|
||||
static int ws_nw_backdrop = 0;
|
||||
void gpu_ws_set_nw_backdrop(int on) { ws_nw_backdrop = on ? 1 : 0; }
|
||||
static int ws_nw_flat_backdrop = 0;
|
||||
void gpu_ws_set_nw_flat_backdrop(int on) { ws_nw_flat_backdrop = on ? 1 : 0; }
|
||||
int gpu_ws_nw_flat_backdrop_enabled(void) { return ws_nw_flat_backdrop; }
|
||||
static int ws_nw_backdrop_stretch_quad(int32_t *vx, const int32_t *vy) {
|
||||
|
||||
@@ -648,6 +648,31 @@ static void test_masked_reject(void) {
|
||||
gpu_ws_set_margin_override(-1);
|
||||
}
|
||||
|
||||
static void test_gte_summary_reject(void) {
|
||||
CPUState cpu = {0};
|
||||
const uint32_t site = CODE + 0x240;
|
||||
const uint32_t word = i_type(1, A0, 0, 3);
|
||||
const uint32_t mask = 0x7F87A000u;
|
||||
gpu_ws_set_masked_reject_sites(&site, &word, &mask, 1);
|
||||
for (int margin = 0; margin <= 53; margin += 53) {
|
||||
gpu_ws_set_margin_override(margin);
|
||||
cpu.gpr[A0] = 0x80004000u;
|
||||
CHECK(branch_taken(&cpu, site, word) == (margin == 0), "GTE X-only summary at margin %d", margin);
|
||||
CHECK(cpu.gpr[A0] == 0x80004000u, "GTE mask preserves architectural FLAG copy");
|
||||
CHECK(branch_taken(&cpu, site + 0x40, word), "unlisted GTE branch stays vanilla");
|
||||
CHECK(!branch_taken(&cpu, site, i_type(1, A0, 1, 3)), "wrong GTE full word keeps BGEZ predicate");
|
||||
const uint32_t flags[] = {0x80002000u, 0x80020000u, 0x80040000u, 0xC0000000u};
|
||||
for (unsigned i = 0; i < sizeof flags / sizeof flags[0]; ++i) {
|
||||
cpu.gpr[A0] = flags[i];
|
||||
CHECK(branch_taken(&cpu, site, word), "GTE Y/depth flags retain rejection");
|
||||
}
|
||||
cpu.gpr[A0] = 0x00401000u;
|
||||
CHECK(!branch_taken(&cpu, site, word), "positive non-summary flags do not reject");
|
||||
}
|
||||
gpu_ws_set_masked_reject_sites(NULL, NULL, NULL, 0);
|
||||
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;
|
||||
@@ -695,6 +720,7 @@ int main(int argc, char **argv) {
|
||||
test_screen_x_precedence();
|
||||
test_masked_reject();
|
||||
test_sxy_fold();
|
||||
test_gte_summary_reject();
|
||||
test_wide_nclip_branches();
|
||||
if (failures) {
|
||||
fprintf(stderr, "%d check(s) failed\n", failures);
|
||||
|
||||
Reference in New Issue
Block a user