mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
widescreen(8C): far-backdrop GTE un-squash, depth-gated
The far backdrop (ocean/cloud/distant mountain) is GTE-projected 3D drawn by the main-EXE driver FUN_8004db3c (sole caller of the GTE emitter FUN_80027600). The global widescreen GTE X-squash compresses it toward centre, so its geometry falls short of the revealed 16:9 edges -> blue void that pops as the camera moves. Fix: bracket the backdrop driver with gte_ws_set_suppress(1)/(0) so its draws skip the X-squash and fill the stretched frame (skybox treatment), while the near 3D world keeps the wider FOV. FUN_8004db3c draws a MIX (far backdrop + nearer props/scenery), so the suppress is DEPTH-GATED in gte_rtps_internal: only vertices with projected SZ >= s_ws_far_threshold (default 900, tunable live via the ws_far_threshold TCP cmd) un-squash; nearer props stay squashed and aligned. Validated: threshold 900 fills the void, keeps the foreground door + background scenery (path/tree) aligned. Residual: the mountain grass-top straddles the depth band (per-vertex limitation) -> flickers when the camera dips; this is the checkpoint before exploring native-wide rendering to remove the squash (and this whole artifact class) entirely. Mechanism: - recompiler: [widescreen.backdrop] unsquash_funcs emits gte_ws_set_suppress(1) at entry + (0) before each jr-$ra return (code_generator + config_loader + main_psx). Main-EXE -> regen-class. - runtime: gte.cpp depth-gated squash + s_ws_far_threshold; cpu_state.h decl; debug_server.c ws_aspect (live squash on/off) + ws_far_threshold (set split + read SZ stats) diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,13 @@ struct CodeGenConfig {
|
||||
// proportion correction at GP0 submission. Empty = no hooks (default).
|
||||
std::set<uint32_t> ws_sprite_tag_funcs;
|
||||
|
||||
// Widescreen far-backdrop un-squash ([widescreen] backdrop_unsquash_funcs):
|
||||
// functions whose body is bracketed by gte_ws_set_suppress(1)/(0) so the
|
||||
// GTE X-squash is OFF for their (far-backdrop) draws — the backdrop fills
|
||||
// the stretched 16:9 frame instead of leaving edge void (8C). Suppress is
|
||||
// set at entry and cleared before every jr-$ra return. Empty = none.
|
||||
std::set<uint32_t> ws_backdrop_unsquash_funcs;
|
||||
|
||||
// Widescreen cull-margin widening ([widescreen] cull_* sites). At these
|
||||
// addresses the immediate is emitted with a runtime margin term
|
||||
// psx_ws_x_margin() so the world-space draw cull widens with the aspect
|
||||
|
||||
@@ -1118,6 +1118,10 @@ std::string CodeGenerator::translate_basic_block(
|
||||
}
|
||||
if (ra_loaded_from_non_sp) break;
|
||||
}
|
||||
if (config_.ws_backdrop_unsquash_funcs.count(cfg.function_start)) {
|
||||
ss << config_.indent
|
||||
<< "gte_ws_set_suppress(0); /* widescreen: end far-backdrop un-squash (8C) */\n";
|
||||
}
|
||||
if (ra_loaded_from_non_sp) {
|
||||
ss << config_.indent
|
||||
<< "cpu->pc = cpu->gpr[31]; psx_restore_state_escape(); return;"
|
||||
@@ -1363,6 +1367,10 @@ GeneratedFunction CodeGenerator::generate_function(
|
||||
body_ss << config_.indent
|
||||
<< "psx_ws_sprite_tag(cpu); /* widescreen: record prim ($a0) + anchor */\n";
|
||||
}
|
||||
if (config_.ws_backdrop_unsquash_funcs.count(func.start_addr)) {
|
||||
body_ss << config_.indent
|
||||
<< "gte_ws_set_suppress(1); /* widescreen: un-squash far backdrop (8C) */\n";
|
||||
}
|
||||
|
||||
// Add function comment
|
||||
if (config_.emit_comments) {
|
||||
@@ -1692,7 +1700,8 @@ std::string CodeGenerator::generate_file(
|
||||
ss << "extern void debug_server_log_call_entry(uint32_t func_addr);\n";
|
||||
ss << "extern void psx_ws_sprite_tag(CPUState* cpu); /* widescreen prim tag (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n\n";
|
||||
ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n";
|
||||
ss << "extern void gte_ws_set_suppress(int on); /* widescreen far-backdrop un-squash (gte.cpp) */\n\n";
|
||||
|
||||
// Emit reference implementations for unaligned memory helpers.
|
||||
// These implement the MIPS lwl/lwr/swl/swr semantics.
|
||||
|
||||
@@ -511,8 +511,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional [widescreen.backdrop] block — parallax 2D backdrop screenX squash.
|
||||
// Optional [widescreen.backdrop] block — parallax 2D backdrop screenX squash
|
||||
// (x_sites) + far-backdrop GTE un-squash (unsquash_funcs).
|
||||
std::vector<uint32_t> ws_backdrop_x_sites;
|
||||
std::vector<uint32_t> ws_backdrop_unsquash_funcs;
|
||||
if (cfg.contains("widescreen")) {
|
||||
const toml::value& ws = toml::find(cfg, "widescreen");
|
||||
if (ws.contains("backdrop")) {
|
||||
@@ -521,6 +523,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
for (const auto& a : toml::find<std::vector<std::string>>(bd, "x_sites"))
|
||||
ws_backdrop_x_sites.push_back(
|
||||
parse_hex(a, "widescreen.backdrop.x_sites"));
|
||||
if (bd.contains("unsquash_funcs"))
|
||||
for (const auto& a : toml::find<std::vector<std::string>>(bd, "unsquash_funcs"))
|
||||
ws_backdrop_unsquash_funcs.push_back(
|
||||
parse_hex(a, "widescreen.backdrop.unsquash_funcs"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,6 +557,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
/*ws_cull_range_sites*/ ws_cull_range_sites,
|
||||
/*ws_cull_a1_sites*/ ws_cull_a1_sites,
|
||||
/*ws_backdrop_x_sites*/ ws_backdrop_x_sites,
|
||||
/*ws_backdrop_unsquash_funcs*/ ws_backdrop_unsquash_funcs,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +251,12 @@ struct GameConfig {
|
||||
// code, so the overlay compile must see this config (--ws-config). A regen
|
||||
// is required; empty by default.
|
||||
std::vector<uint32_t> ws_backdrop_x_sites;
|
||||
|
||||
// [widescreen.backdrop] unsquash_funcs — far-backdrop driver functions whose
|
||||
// body is bracketed with gte_ws_set_suppress(1)/(0) so the GTE X-squash is
|
||||
// OFF for their (far, parallax) draws: the backdrop fills the stretched 16:9
|
||||
// frame instead of leaving edge void (8C). Main-EXE addresses; regen-class.
|
||||
std::vector<uint32_t> ws_backdrop_unsquash_funcs;
|
||||
};
|
||||
|
||||
// UserSettings — the launcher-written, user-editable override layer.
|
||||
|
||||
@@ -95,6 +95,7 @@ int main(int argc, char** argv) {
|
||||
std::set<uint32_t> ws_tag_funcs; // [widescreen] sprite_tag_funcs
|
||||
std::set<uint32_t> ws_cull_bias, ws_cull_range, ws_cull_a1; // [widescreen.cull]
|
||||
std::set<uint32_t> ws_backdrop_x; // [widescreen.backdrop] x_sites
|
||||
std::set<uint32_t> ws_backdrop_unsquash; // [widescreen.backdrop] unsquash_funcs
|
||||
std::filesystem::path out_dir = "generated";
|
||||
|
||||
if (!config_path.empty()) {
|
||||
@@ -109,6 +110,7 @@ int main(int argc, char** argv) {
|
||||
ws_cull_range.insert(cfg.ws_cull_range_sites.begin(), cfg.ws_cull_range_sites.end());
|
||||
ws_cull_a1.insert(cfg.ws_cull_a1_sites.begin(), cfg.ws_cull_a1_sites.end());
|
||||
ws_backdrop_x.insert(cfg.ws_backdrop_x_sites.begin(), cfg.ws_backdrop_x_sites.end());
|
||||
ws_backdrop_unsquash.insert(cfg.ws_backdrop_unsquash_funcs.begin(), cfg.ws_backdrop_unsquash_funcs.end());
|
||||
fmt::print("config: {}\n", config_path.string());
|
||||
fmt::print(" exe = {}\n", exe_path.string());
|
||||
fmt::print(" seeds = {}\n", extra_funcs_storage);
|
||||
@@ -160,8 +162,9 @@ int main(int argc, char** argv) {
|
||||
ws_cull_range.insert(wscfg.ws_cull_range_sites.begin(), wscfg.ws_cull_range_sites.end());
|
||||
ws_cull_a1.insert(wscfg.ws_cull_a1_sites.begin(), wscfg.ws_cull_a1_sites.end());
|
||||
ws_backdrop_x.insert(wscfg.ws_backdrop_x_sites.begin(), wscfg.ws_backdrop_x_sites.end());
|
||||
fmt::print("ws-config: {} (backdrop_x sites={})\n",
|
||||
ws_config_path.string(), ws_backdrop_x.size());
|
||||
ws_backdrop_unsquash.insert(wscfg.ws_backdrop_unsquash_funcs.begin(), wscfg.ws_backdrop_unsquash_funcs.end());
|
||||
fmt::print("ws-config: {} (backdrop_x sites={}, unsquash funcs={})\n",
|
||||
ws_config_path.string(), ws_backdrop_x.size(), ws_backdrop_unsquash.size());
|
||||
}
|
||||
|
||||
// Parse the PS1-EXE file
|
||||
@@ -657,6 +660,7 @@ int main(int argc, char** argv) {
|
||||
codegen_config.ws_cull_range_sites = ws_cull_range;
|
||||
codegen_config.ws_cull_a1_sites = ws_cull_a1;
|
||||
codegen_config.ws_backdrop_x_sites = ws_backdrop_x;
|
||||
codegen_config.ws_backdrop_unsquash_funcs = ws_backdrop_unsquash;
|
||||
|
||||
// Load per-game annotations: annotations/<exe_stem>_annotations.csv
|
||||
// Silently skipped if the file doesn't exist.
|
||||
|
||||
@@ -54,6 +54,7 @@ extern void gte_execute(CPUState* cpu, uint32_t cmd);
|
||||
* RTPS/RTPT screen-X around the game's OFX so a 4:3 frame stretched to the
|
||||
* wide aspect shows a wider field of view. */
|
||||
extern void gte_set_display_aspect(int num, int den);
|
||||
extern void gte_ws_set_suppress(int on); /* 8C: un-squash far backdrop draws */
|
||||
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);
|
||||
|
||||
@@ -5206,6 +5206,36 @@ static void handle_ws_margin(int id, const char *json)
|
||||
id, v, ws.x_margin, ws.active);
|
||||
}
|
||||
|
||||
/* Live GTE widescreen-squash toggle (diagnostic for 8C far-backdrop void):
|
||||
* ws_aspect num=<n> den=<d> calls gte_set_display_aspect at runtime so we can
|
||||
* compare squash ON (e.g. 16/9) vs OFF (1/1) in-place without a relaunch. */
|
||||
extern void gte_set_display_aspect(int num, int den);
|
||||
static void handle_ws_aspect(int id, const char *json)
|
||||
{
|
||||
int num = json_get_int(json, "num", -1);
|
||||
int den = json_get_int(json, "den", -1);
|
||||
if (num <= 0 || den <= 0) { send_err(id, "need num>0 den>0 (1 1 = squash off)"); return; }
|
||||
gte_set_display_aspect(num, den);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"num\":%d,\"den\":%d}", id, num, den);
|
||||
}
|
||||
|
||||
/* 8C far-backdrop depth split. ws_far_threshold [t=<SZ>] sets the SZ cutoff
|
||||
* above which backdrop-driver geometry is un-squashed (near props stay
|
||||
* squashed). With no t=, just reports the observed SZ stats since last read so
|
||||
* the threshold can be set from data. */
|
||||
extern void gte_ws_set_far_threshold(int t);
|
||||
extern int gte_ws_get_far_threshold(void);
|
||||
extern void gte_ws_get_sz_stats(int* mn, int* mx, unsigned* n, unsigned* far_n);
|
||||
static void handle_ws_far_threshold(int id, const char *json)
|
||||
{
|
||||
int t = json_get_int(json, "t", -123456789); /* sentinel = no change */
|
||||
if (t != -123456789) gte_ws_set_far_threshold(t);
|
||||
int mn = 0, mx = 0; unsigned n = 0, far_n = 0;
|
||||
gte_ws_get_sz_stats(&mn, &mx, &n, &far_n);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"threshold\":%d,\"sz_min\":%d,\"sz_max\":%d,\"sz_n\":%u,\"sz_far\":%u}",
|
||||
id, gte_ws_get_far_threshold(), mn, mx, n, far_n);
|
||||
}
|
||||
|
||||
static void handle_ws_census(int id, const char *json)
|
||||
{
|
||||
char act[16] = {0};
|
||||
@@ -8473,6 +8503,8 @@ static const CmdEntry s_commands[] = {
|
||||
{ "write_ram", handle_write_ram },
|
||||
{ "gpu_state", handle_gpu_state },
|
||||
{ "ws_margin", handle_ws_margin },
|
||||
{ "ws_aspect", handle_ws_aspect },
|
||||
{ "ws_far_threshold", handle_ws_far_threshold },
|
||||
{ "ws_census", handle_ws_census },
|
||||
{ "mem_words", handle_mem_words },
|
||||
{ "vram_peek", handle_vram_peek },
|
||||
|
||||
+41
-1
@@ -121,6 +121,36 @@ static void depth_cue_from_ir(GTEState* gte) {
|
||||
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 */
|
||||
|
||||
// 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
|
||||
// revealed 16:9 edges (blue void). Drawing it UN-squashed lets its 4:3-extent
|
||||
// fill the stretched frame (skybox treatment) while the near 3D world keeps the
|
||||
// wider FOV. The recompiler brackets the backdrop driver (FUN_8004db3c) with
|
||||
// gte_ws_set_suppress(1)/(0) ([widescreen] backdrop_unsquash_funcs).
|
||||
// The driver (FUN_8004db3c) actually draws a MIX: the far backdrop (which we
|
||||
// want un-squashed) AND nearer props (doors/mansion models) that must stay
|
||||
// squashed or they drift relative to the rest of the world. So suppression is
|
||||
// DEPTH-GATED: only vertices with SZ (projected Z) >= s_ws_far_threshold get
|
||||
// un-squashed while suppress is active; nearer props keep the squash. Threshold
|
||||
// tuned live via the ws_far_threshold TCP cmd; SZ stats below characterize the
|
||||
// driver's depth range so the split is set from data, not a guess.
|
||||
static int s_ws_suppress = 0;
|
||||
static int32_t s_ws_far_threshold = 900; /* SZ split: near props squashed, far backdrop un-squashed (8C, tunable via ws_far_threshold) */
|
||||
extern "C" void gte_ws_set_suppress(int on) { s_ws_suppress = on ? 1 : 0; }
|
||||
extern "C" void gte_ws_set_far_threshold(int t) { s_ws_far_threshold = t; }
|
||||
extern "C" int gte_ws_get_far_threshold(void) { return (int)s_ws_far_threshold; }
|
||||
/* SZ stats observed while suppress is active (one frame window, reset on read). */
|
||||
static int32_t s_ws_sz_min = 0x7FFFFFFF, s_ws_sz_max = 0;
|
||||
static uint32_t s_ws_sz_n = 0, s_ws_sz_far = 0;
|
||||
extern "C" void gte_ws_get_sz_stats(int* mn, int* mx, unsigned* n, unsigned* far_n) {
|
||||
if (mn) *mn = (s_ws_sz_n ? s_ws_sz_min : 0);
|
||||
if (mx) *mx = s_ws_sz_max;
|
||||
if (n) *n = s_ws_sz_n;
|
||||
if (far_n) *far_n = s_ws_sz_far;
|
||||
s_ws_sz_min = 0x7FFFFFFF; s_ws_sz_max = 0; s_ws_sz_n = 0; s_ws_sz_far = 0;
|
||||
}
|
||||
|
||||
extern "C" void gte_set_display_aspect(int num, int den) {
|
||||
if (num <= 0 || den <= 0) { s_ws_xnum = s_ws_xden = 1; return; }
|
||||
// squash = (4/3) / (num/den) = (4*den) / (3*num); identity for 4:3.
|
||||
@@ -170,7 +200,17 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0) {
|
||||
// this frame is being stretched — never on a 4:3-presented frame (FMV /
|
||||
// full-2D screen), so content and present stay locked.
|
||||
int64_t xterm = (int64_t)gte->IR1 * h_div_sz;
|
||||
if (s_ws_xnum != s_ws_xden && !gpu_ws_present_native_43())
|
||||
bool do_squash = (s_ws_xnum != s_ws_xden) && !gpu_ws_present_native_43();
|
||||
if (do_squash && s_ws_suppress) {
|
||||
/* Depth-gated: un-squash only FAR geometry (the backdrop); keep near
|
||||
* props squashed so they stay aligned. Record SZ stats for tuning. */
|
||||
int32_t sz = gte->SZ[3];
|
||||
if (sz < s_ws_sz_min) s_ws_sz_min = sz;
|
||||
if (sz > s_ws_sz_max) s_ws_sz_max = sz;
|
||||
s_ws_sz_n++;
|
||||
if (sz >= s_ws_far_threshold) { do_squash = false; s_ws_sz_far++; }
|
||||
}
|
||||
if (do_squash)
|
||||
xterm = xterm * s_ws_xnum / s_ws_xden;
|
||||
int64_t sx = (gte->OFX + xterm) >> 16;
|
||||
int64_t sy = (gte->OFY + (int64_t)gte->IR2 * h_div_sz) >> 16;
|
||||
|
||||
Reference in New Issue
Block a user