mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Widen guarded packed screen-X predicates in native and interpreted code
This commit is contained in:
@@ -240,6 +240,23 @@ Changing a site, guard or mask changes the overlay configuration fingerprint.
|
||||
|
||||
### Guarded widescreen participation comparisons
|
||||
|
||||
`[[widescreen.cull.packed_x]]` handles a proven mesh screen-X predicate
|
||||
expressed as `SLTU rd, (SX << 16), (width << 16)`:
|
||||
|
||||
```toml
|
||||
[[widescreen.cull.packed_x]]
|
||||
address = "0x801466A0"
|
||||
expected = "0x0079C02B"
|
||||
```
|
||||
|
||||
The complete instruction guards native overlays and the interpreter. While
|
||||
wide, X must lie in `[-margin, width + margin)`; both sides widen together.
|
||||
At 4:3 the original unsigned comparison is exact. Nonzero low-half operands,
|
||||
zero width, and widths above 1024 retain the original comparison. It changes
|
||||
only the predicate, preserving GPR operands, vertical, backface, depth and
|
||||
ordering-table limits. Sites affect the overlay cache identity; regenerate
|
||||
main/native overlays after changing them. Empty lists are inert.
|
||||
|
||||
Games may disable a proven object/model cull verdict in widened world views
|
||||
without changing true 4:3 behavior:
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ struct CodeGenConfig {
|
||||
// while widescreen reveals extra world. 4:3 evaluates the original compare.
|
||||
std::vector<PSXRecompV4::WidescreenCullKeepSite> ws_cull_keep_sites;
|
||||
std::vector<PSXRecompV4::WidescreenMaskedRejectSite> ws_cull_masked_reject_sites;
|
||||
std::vector<PSXRecompV4::WidescreenPackedXSite> ws_cull_packed_x_sites;
|
||||
std::vector<PSXRecompV4::WidescreenCullScaleSite> ws_cull_scale_sites;
|
||||
|
||||
// Exact `addi[u] rt,zero,imm` 12-bit angular half-extents. The runtime
|
||||
|
||||
@@ -1137,6 +1137,25 @@ std::string CodeGenerator::translate_instruction_core(uint32_t addr, uint32_t in
|
||||
reg_name(dst), vanilla, site.result, comment);
|
||||
}
|
||||
|
||||
for (const auto& site : config_.ws_cull_packed_x_sites) {
|
||||
if ((site.address & 0x1FFFFFFFu) != (addr & 0x1FFFFFFFu)) continue;
|
||||
if (instr != site.expected) {
|
||||
if (config_.overlay_mode) continue;
|
||||
fmt::print(stderr, "ERROR: packed_x expected 0x{:08X} at 0x{:08X}, found 0x{:08X}\n",
|
||||
site.expected, addr, instr);
|
||||
std::exit(1);
|
||||
}
|
||||
if (opcode != 0u || funct != 0x2Bu) {
|
||||
fmt::print(stderr, "ERROR: packed_x site 0x{:08X} must be SLTU\n", addr);
|
||||
std::exit(1);
|
||||
}
|
||||
return fmt::format(
|
||||
"{} = psx_ws_cull_packed_x_value({}, {}, psx_ws_x_margin());"
|
||||
" /* guarded packed screen-X range */{}",
|
||||
reg_name(get_rd(instr)), reg_name(get_rs(instr)),
|
||||
reg_name(get_rt(instr)), comment);
|
||||
}
|
||||
|
||||
// Register-register frustum-edge compares ([[widescreen.cull.scale]]):
|
||||
// the configured operand is a +/-half_extent*z bound computed at runtime,
|
||||
// so it is scaled by the live reveal instead of rewriting an immediate.
|
||||
@@ -3631,6 +3650,8 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const {
|
||||
ss << "extern uint32_t psx_ws_xclip_bound(uint32_t vanilla); /* ws per-prim X reject bound load (gpu.c) */\n";
|
||||
ss << "extern uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced); /* ws guarded keep compare (gpu.c) */\n";
|
||||
ss << "extern int32_t psx_ws_cull_scale(int32_t bound, int32_t half_extent); /* ws frustum-edge bound scale (gpu.c) */\n";
|
||||
if (!config_.ws_cull_packed_x_sites.empty())
|
||||
ss << "#include \"ws_cull_edge.h\"\n";
|
||||
ss << "extern 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); /* aspect-aware participation cone */\n";
|
||||
ss << "extern uint32_t psx_ws_angle_widen(uint32_t vanilla); /* aspect-scaled 12-bit terrain-frustum half-angle */\n";
|
||||
ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n";
|
||||
|
||||
@@ -64,6 +64,18 @@ uint32_t overlay_codegen_config_hash(const GameConfig& c) {
|
||||
|
||||
h.words("sprite_tag_funcs", c.ws_sprite_tag_funcs);
|
||||
h.words("mod_function_entry_funcs", c.mod_function_entry_funcs);
|
||||
if (!c.ws_cull_packed_x_sites.empty()) {
|
||||
h.tag("cull_packed_x");
|
||||
auto sites = c.ws_cull_packed_x_sites;
|
||||
std::sort(sites.begin(), sites.end(), [](const auto& a, const auto& b) {
|
||||
return (a.address & 0x1FFFFFFFu) < (b.address & 0x1FFFFFFFu);
|
||||
});
|
||||
h.u32((uint32_t)sites.size());
|
||||
for (const auto& site : sites) {
|
||||
h.u32(site.address & 0x1FFFFFFFu);
|
||||
h.u32(site.expected);
|
||||
}
|
||||
}
|
||||
if (!c.ws_cull_masked_reject_sites.empty()) {
|
||||
h.tag("cull_masked_reject");
|
||||
auto sites = c.ws_cull_masked_reject_sites;
|
||||
@@ -1981,6 +1993,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
uint32_t ws_cull_clip_edge_width = 0;
|
||||
std::vector<WidescreenCullKeepSite> ws_cull_keep_sites;
|
||||
std::vector<WidescreenMaskedRejectSite> ws_cull_masked_reject_sites;
|
||||
std::vector<WidescreenPackedXSite> ws_cull_packed_x_sites;
|
||||
std::vector<WidescreenCullScaleSite> ws_cull_scale_sites;
|
||||
std::vector<WidescreenAngleSite> ws_cull_angle_sites;
|
||||
WidescreenAspectConeConfig ws_aspect_cone;
|
||||
@@ -2049,6 +2062,27 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
throw std::runtime_error("masked_reject supports at most 256 sites");
|
||||
}
|
||||
}
|
||||
if (cull.contains("packed_x")) {
|
||||
std::set<uint32_t> seen;
|
||||
for (const auto& item : toml::find<toml::array>(cull, "packed_x")) {
|
||||
WidescreenPackedXSite site;
|
||||
site.address = parse_hex(toml::find<std::string>(item, "address"),
|
||||
"widescreen.cull.packed_x.address");
|
||||
site.expected = parse_hex(toml::find<std::string>(item, "expected"),
|
||||
"widescreen.cull.packed_x.expected");
|
||||
if ((site.address & 3u) || (site.expected >> 26) != 0u ||
|
||||
(site.expected & 0x7FFu) != 0x2Bu ||
|
||||
((site.expected >> 21) & 31u) == 0u ||
|
||||
((site.expected >> 16) & 31u) == 0u ||
|
||||
((site.expected >> 11) & 31u) == 0u)
|
||||
throw std::runtime_error("packed_x needs aligned SLTU rd,rs,rt with nonzero registers");
|
||||
if (!seen.insert(site.address & 0x1FFFFFFFu).second)
|
||||
throw std::runtime_error("duplicate packed_x address");
|
||||
ws_cull_packed_x_sites.push_back(site);
|
||||
if (ws_cull_packed_x_sites.size() > 256)
|
||||
throw std::runtime_error("packed_x supports at most 256 sites");
|
||||
}
|
||||
}
|
||||
if (cull.contains("keep")) {
|
||||
std::set<uint32_t> seen;
|
||||
for (const auto& item : toml::find<toml::array>(cull, "keep")) {
|
||||
@@ -2577,6 +2611,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
std::move(ws_cull_clip_edge_x_load_sites);
|
||||
loaded.ws_cull_clip_edge_width = ws_cull_clip_edge_width;
|
||||
loaded.ws_cull_masked_reject_sites = std::move(ws_cull_masked_reject_sites);
|
||||
loaded.ws_cull_packed_x_sites = std::move(ws_cull_packed_x_sites);
|
||||
loaded.draw_distance_clamp_sites =
|
||||
parse_draw_distance_clamps(cfg, config_path);
|
||||
return loaded;
|
||||
|
||||
@@ -824,6 +824,11 @@ struct WidescreenMaskedRejectSite {
|
||||
uint32_t reject_mask = 0;
|
||||
};
|
||||
|
||||
struct WidescreenPackedXSite {
|
||||
uint32_t address = 0;
|
||||
uint32_t expected = 0;
|
||||
};
|
||||
|
||||
struct GameConfig {
|
||||
std::filesystem::path config_path;
|
||||
std::filesystem::path project_root;
|
||||
@@ -1335,6 +1340,8 @@ struct GameConfig {
|
||||
// [[draw_distance.clamp]] -- opt-in "keep far geometry" clamps. Empty by
|
||||
// default; inert until a mod switches them on; regen required.
|
||||
std::vector<DrawDistanceClampSite> draw_distance_clamp_sites;
|
||||
// Guarded SLTU on SX<<16 against screen width<<16; both wide edges.
|
||||
std::vector<WidescreenPackedXSite> ws_cull_packed_x_sites;
|
||||
};
|
||||
|
||||
// Effective clip_edge_width: explicit value, else screen_w_imms[0], else 320.
|
||||
|
||||
@@ -244,6 +244,7 @@ static int psxrecomp_game_main(int argc, char** argv) {
|
||||
std::set<uint32_t> ws_cull_clip_edge_x_load; // [widescreen.cull] clip_edge_x_load_sites
|
||||
uint32_t ws_cull_clip_edge_width = 0; // [widescreen.cull] clip_edge_width
|
||||
std::vector<PSXRecompV4::WidescreenCullKeepSite> ws_cull_keep;
|
||||
std::vector<PSXRecompV4::WidescreenPackedXSite> ws_cull_packed_x;
|
||||
std::vector<PSXRecompV4::WidescreenCullScaleSite> ws_cull_scale;
|
||||
std::vector<PSXRecompV4::WidescreenAngleSite> ws_cull_angle;
|
||||
std::vector<PSXRecompV4::DrawDistanceClampSite> draw_distance_clamps; // [[draw_distance.clamp]]
|
||||
@@ -322,6 +323,7 @@ static int psxrecomp_game_main(int argc, char** argv) {
|
||||
if (!cfg.ws_cull_clip_edge_x_load_sites.empty())
|
||||
ws_cull_clip_edge_width = PSXRecompV4::ws_cull_clip_edge_width(cfg);
|
||||
ws_cull_keep = cfg.ws_cull_keep_sites;
|
||||
ws_cull_packed_x = cfg.ws_cull_packed_x_sites;
|
||||
ws_cull_masked_reject = cfg.ws_cull_masked_reject_sites;
|
||||
ws_cull_scale = cfg.ws_cull_scale_sites;
|
||||
ws_cull_angle = cfg.ws_cull_angle_sites;
|
||||
@@ -431,6 +433,7 @@ static int psxrecomp_game_main(int argc, char** argv) {
|
||||
if (!wscfg.ws_cull_clip_edge_x_load_sites.empty() && !ws_cull_clip_edge_width)
|
||||
ws_cull_clip_edge_width = PSXRecompV4::ws_cull_clip_edge_width(wscfg);
|
||||
if (ws_cull_keep.empty()) ws_cull_keep = wscfg.ws_cull_keep_sites;
|
||||
if (ws_cull_packed_x.empty()) ws_cull_packed_x = wscfg.ws_cull_packed_x_sites;
|
||||
if (ws_cull_masked_reject.empty()) ws_cull_masked_reject = wscfg.ws_cull_masked_reject_sites;
|
||||
if (ws_cull_scale.empty()) ws_cull_scale = wscfg.ws_cull_scale_sites;
|
||||
if (ws_cull_angle.empty()) ws_cull_angle = wscfg.ws_cull_angle_sites;
|
||||
@@ -1504,6 +1507,7 @@ static int psxrecomp_game_main(int argc, char** argv) {
|
||||
else if (!ws_cull_w_imms.empty())
|
||||
codegen_config.ws_cull_clip_edge_width = ws_cull_w_imms.front();
|
||||
codegen_config.ws_cull_keep_sites = ws_cull_keep;
|
||||
codegen_config.ws_cull_packed_x_sites = ws_cull_packed_x;
|
||||
codegen_config.ws_cull_scale_sites = ws_cull_scale;
|
||||
codegen_config.ws_cull_angle_sites = ws_cull_angle;
|
||||
codegen_config.draw_distance_clamp_sites = draw_distance_clamps;
|
||||
|
||||
@@ -275,6 +275,27 @@ void runtime_math() {
|
||||
|
||||
} // namespace
|
||||
|
||||
static void packed_x_forms() {
|
||||
PSXRecomp::CodeGenConfig config;
|
||||
config.ws_cull_packed_x_sites.push_back({kBase, 0x0079C02Bu});
|
||||
const auto emitted = generate_first_instruction(0x0079C02Bu, config);
|
||||
check(has(emitted, "psx_ws_cull_packed_x_value"), "packed X emits the shared predicate");
|
||||
config.overlay_mode = true;
|
||||
const auto alias = generate_first_instruction(0x0079C02Au, config);
|
||||
check(!has(alias, "psx_ws_cull_packed_x_value"), "different overlay opcode stays vanilla");
|
||||
for (int sx = -32768; sx <= 32767; ++sx) {
|
||||
const uint32_t x = (uint32_t)sx << 16;
|
||||
const uint32_t w = 320u << 16;
|
||||
check(psx_ws_cull_packed_x_value(x, w, 0) == (x < w), "packed X 4:3 identity");
|
||||
check(psx_ws_cull_packed_x_value(x, w, 120) == (sx >= -120 && sx < 440),
|
||||
"packed X covers exactly the expanded horizontal range");
|
||||
}
|
||||
check(psx_ws_cull_packed_x_value(0xFFFF0001u, 320u << 16, 120) == 0,
|
||||
"nonzero packed low half stays vanilla");
|
||||
check(psx_ws_cull_packed_x_value(0xFFFF0000u, 2048u << 16, 120) == 0,
|
||||
"non-screen width stays vanilla");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_self = argv[0];
|
||||
if (argc == 3 && std::string(argv[1]) == "--exit-probe") {
|
||||
@@ -287,6 +308,7 @@ int main(int argc, char** argv) {
|
||||
main_exe_guard_and_overlay_variants();
|
||||
declarations_only_when_the_key_is_used();
|
||||
runtime_math();
|
||||
packed_x_forms();
|
||||
|
||||
if (g_exit_probe >= 0) return 3; // no call had that probe index
|
||||
if (failures != 0) {
|
||||
|
||||
@@ -366,6 +366,10 @@ uint32_t psx_ws_clip_edge_width(void);
|
||||
uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced);
|
||||
int psx_ws_cull_keep_site(uint32_t pc, uint32_t instr, uint32_t vanilla,
|
||||
uint32_t *out);
|
||||
void gpu_ws_set_packed_x_sites(const uint32_t *addresses,
|
||||
const uint32_t *expected, int count);
|
||||
int psx_ws_cull_packed_x_site(uint32_t pc, uint32_t instr, uint32_t x,
|
||||
uint32_t width, uint32_t *out);
|
||||
uint32_t psx_ws_angle_widen(uint32_t vanilla);
|
||||
int psx_ws_angle_site(uint32_t pc, uint32_t instr, uint32_t *out);
|
||||
/* bound * (half_extent + margin) / half_extent; identity at 4:3. */
|
||||
|
||||
@@ -55,6 +55,20 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* bgez predicate (1 = branch taken = keep) for a signed screen X. */
|
||||
/* SLTU on screen X and width shifted into the high half. Only proven packed
|
||||
* operands widen; original comparison is exact at 4:3 and for other layouts. */
|
||||
static inline uint32_t psx_ws_cull_packed_x_value(uint32_t x, uint32_t width,
|
||||
int32_t m)
|
||||
{
|
||||
if (m <= 0 || ((x | width) & 0xFFFFu) || width == 0u ||
|
||||
(width >> 16) > 1024u)
|
||||
return x < width ? 1u : 0u;
|
||||
int32_t sx = (int16_t)(x >> 16);
|
||||
int64_t offset = (int64_t)sx + m;
|
||||
int64_t span = (int64_t)(width >> 16) + 2LL * m;
|
||||
return offset >= 0 && offset < span ? 1u : 0u;
|
||||
}
|
||||
|
||||
static inline int psx_ws_cull_bgez_value(int32_t v, int32_t m)
|
||||
{
|
||||
return v >= -(m > 0 ? m : 0);
|
||||
|
||||
@@ -1963,7 +1963,8 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
{
|
||||
uint32_t vanilla = (cpu->gpr[rs] < cpu->gpr[rt]) ? 1u : 0u;
|
||||
uint32_t kept = vanilla;
|
||||
if (!psx_ws_cull_scale_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept))
|
||||
if (!psx_ws_cull_packed_x_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept) &&
|
||||
!psx_ws_cull_scale_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept))
|
||||
(void)psx_ws_cull_keep_site(pc, insn, vanilla, &kept);
|
||||
cpu->gpr[rd] = kept;
|
||||
psx_pgxp_alu(cpu, insn, kept, 0, 0);
|
||||
|
||||
@@ -1014,6 +1014,29 @@ typedef struct {
|
||||
uint32_t expected;
|
||||
uint32_t result;
|
||||
} WsCullKeepSite;
|
||||
typedef struct { uint32_t address, expected; } WsPackedXSite;
|
||||
static WsPackedXSite ws_packed_x_sites[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static int ws_packed_x_n;
|
||||
void gpu_ws_set_packed_x_sites(const uint32_t *addresses,
|
||||
const uint32_t *expected, int count) {
|
||||
if (count < 0) count = 0;
|
||||
if (count > WS_EXPLICIT_CULL_SITES_MAX) count = WS_EXPLICIT_CULL_SITES_MAX;
|
||||
ws_packed_x_n = count;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ws_packed_x_sites[i].address = addresses[i] & 0x1FFFFFFFu;
|
||||
ws_packed_x_sites[i].expected = expected[i];
|
||||
}
|
||||
}
|
||||
int psx_ws_cull_packed_x_site(uint32_t pc, uint32_t instr, uint32_t x,
|
||||
uint32_t width, uint32_t *out) {
|
||||
for (int i = 0; i < ws_packed_x_n; ++i) {
|
||||
if (ws_packed_x_sites[i].address != (pc & 0x1FFFFFFFu) ||
|
||||
ws_packed_x_sites[i].expected != instr) continue;
|
||||
if (out) *out = psx_ws_cull_packed_x_value(x, width, psx_ws_x_margin());
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static WsCullKeepSite ws_cull_keep_sites[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static int ws_cull_keep_n = 0;
|
||||
void gpu_ws_set_cull_keep_sites(const uint32_t *addresses,
|
||||
|
||||
@@ -14811,6 +14811,15 @@ int main(int argc, char** argv) {
|
||||
addresses.data(), expected.data(), results.data(),
|
||||
(int)addresses.size());
|
||||
}
|
||||
{
|
||||
std::vector<uint32_t> addresses, expected;
|
||||
for (const auto& site : gc.ws_cull_packed_x_sites) {
|
||||
addresses.push_back(site.address);
|
||||
expected.push_back(site.expected);
|
||||
}
|
||||
gpu_ws_set_packed_x_sites(addresses.data(), expected.data(),
|
||||
(int)addresses.size());
|
||||
}
|
||||
{
|
||||
std::vector<uint32_t> addresses, expected, masks;
|
||||
for (const auto& site : gc.ws_cull_masked_reject_sites) {
|
||||
|
||||
Reference in New Issue
Block a user