Preserve wide backdrop composites and guard GTE flag rejection

(cherry picked from commit 6b6402e7f0)
This commit is contained in:
Matthew Stanley
2026-10-08 18:39:43 -07:00
parent 6f37a0edee
commit 42ddce61b5
7 changed files with 72 additions and 6 deletions
+6 -1
View File
@@ -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
+5
View File
@@ -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);
}
+4 -2
View File
@@ -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]] ----------------------------------------------
+1
View File
@@ -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
View File
@@ -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) {
+26
View File
@@ -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);