mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
feat: finish MMX6 widescreen edge handling
This commit is contained in:
@@ -760,6 +760,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
// Optional [widescreen.cull] block — world-space draw-cull widening.
|
||||
std::vector<uint32_t> ws_cull_bias_sites, ws_cull_range_sites, ws_cull_a1_sites;
|
||||
std::vector<uint32_t> ws_cull_slti_sites;
|
||||
int ws_cull_guard_pixels = 0;
|
||||
// Cull-signature immediates (screen_w_imms / screen_h_imms). Defaults are
|
||||
// the original Tomba signature (320-display: 0x140/0x141 + 0xE0/0xF1); a
|
||||
// game with a different display width overrides them (Ape Escape: 0x181).
|
||||
@@ -780,6 +781,13 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
load_sites("range_sites", ws_cull_range_sites);
|
||||
load_sites("a1_sites", ws_cull_a1_sites);
|
||||
load_sites("slti_sites", ws_cull_slti_sites);
|
||||
if (cull.contains("guard_pixels")) {
|
||||
ws_cull_guard_pixels = toml::find<int>(cull, "guard_pixels");
|
||||
if (ws_cull_guard_pixels < 0 || ws_cull_guard_pixels > 256)
|
||||
throw std::runtime_error(fmt::format(
|
||||
"{}: [widescreen.cull] guard_pixels must be in [0, 256]",
|
||||
config_path.string()));
|
||||
}
|
||||
if (cull.contains("screen_w_imms")) {
|
||||
ws_cull_w_imms.clear();
|
||||
load_sites("screen_w_imms", ws_cull_w_imms);
|
||||
@@ -867,6 +875,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_cull_slti_sites*/ ws_cull_slti_sites,
|
||||
/*ws_cull_guard_pixels*/ ws_cull_guard_pixels,
|
||||
/*ws_cull_w_imms*/ ws_cull_w_imms,
|
||||
/*ws_cull_h_imms*/ ws_cull_h_imms,
|
||||
/*ws_backdrop_x_sites*/ ws_backdrop_x_sites,
|
||||
|
||||
@@ -422,6 +422,8 @@ struct GameConfig {
|
||||
// auto-detector cannot qualify (e.g. an X-only test with no height compare
|
||||
// in the same function — Ape Escape 0x8004AB64). Empty by default; regen.
|
||||
std::vector<uint32_t> ws_cull_slti_sites;
|
||||
// Extra per-side actor overdraw beyond the visible widescreen edge.
|
||||
int ws_cull_guard_pixels = 0;
|
||||
|
||||
// [widescreen.cull] screen_w_imms / screen_h_imms — the width/height
|
||||
// immediates of the GTE screen-extent reject signature, per game (the
|
||||
@@ -499,10 +501,9 @@ struct GameConfig {
|
||||
bool ws_nw_hud_corners = false;
|
||||
|
||||
// [widescreen] nw_left_hud_packet_lo / nw_left_hud_packet_hi — optional
|
||||
// half-open physical-RAM source range for a specifically identified left
|
||||
// HUD assembly. In native-wide, only primitives submitted from this packet
|
||||
// range move left by the reveal offset. This is safer for sprite-heavy 2D
|
||||
// games where the broad nw_hud_corners heuristic would also catch scenery.
|
||||
// half-open physical-RAM source range for a specifically identified HUD
|
||||
// pool. In native-wide, primitives from this pool anchor by screen third
|
||||
// (left/right), without moving similarly placed scenery from other pools.
|
||||
// Both values must be present together. Runtime-only; no regen required.
|
||||
uint32_t ws_nw_left_hud_packet_lo = 0;
|
||||
uint32_t ws_nw_left_hud_packet_hi = 0;
|
||||
|
||||
@@ -129,6 +129,11 @@ int gpu_ws_present_native_43(void);
|
||||
/* Per-side X cull-margin (screen/world units) emitted into the game's draw-
|
||||
* cull immediates by the recompiler ([widescreen.cull]); 0 unless stretching. */
|
||||
int psx_ws_x_margin(void);
|
||||
void gpu_ws_set_cull_guard_pixels(int pixels);
|
||||
void gpu_ws_set_explicit_cull_sites(const uint32_t *bias, int nbias,
|
||||
const uint32_t *slti, int nslti);
|
||||
int psx_ws_is_cull_bias_site(uint32_t pc);
|
||||
int psx_ws_is_cull_slti_site(uint32_t pc);
|
||||
|
||||
/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x):
|
||||
* the gcc emit, the sljit JIT, and the interpreter all route a flagged
|
||||
@@ -164,8 +169,8 @@ void psx_ws_note_gte_project(int nverts);
|
||||
* outer-third screen-space HUD sprites out to the true wide-frame corners
|
||||
* (they otherwise sit inset by the reveal). Runtime-only. Off by default. */
|
||||
void gpu_ws_set_nw_hud_corners(int on);
|
||||
/* Targeted alternative for sprite-heavy 2D games: move only primitives whose
|
||||
* ordering-table packet lives in the configured half-open physical-RAM range. */
|
||||
/* Targeted alternative for sprite-heavy 2D games: corner-anchor only primitives
|
||||
* whose ordering-table packet lives in the configured half-open RAM range. */
|
||||
void gpu_ws_set_nw_left_hud_packet_range(uint32_t lo, uint32_t hi);
|
||||
void gpu_ws_begin_linked_list(void);
|
||||
/* Native-wide full-frame 2D backdrop stretch ([widescreen] nw_backdrop):
|
||||
|
||||
@@ -1528,18 +1528,21 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
|
||||
return 1;
|
||||
}
|
||||
case 0x08: /* ADDI rt, rs, simm — same as ADDIU, sans overflow trap (we don't model traps here) */
|
||||
cpu->gpr[rt] = cpu->gpr[rs] + (uint32_t)simm;
|
||||
cpu->gpr[rt] = cpu->gpr[rs] + (uint32_t)simm
|
||||
+ (psx_ws_is_cull_bias_site(pc) ? (uint32_t)psx_ws_x_margin() : 0u);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
case 0x09: /* ADDIU rt, rs, simm */
|
||||
cpu->gpr[rt] = cpu->gpr[rs] + (uint32_t)simm;
|
||||
cpu->gpr[rt] = cpu->gpr[rs] + (uint32_t)simm
|
||||
+ (psx_ws_is_cull_bias_site(pc) ? (uint32_t)psx_ws_x_margin() : 0u);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
case 0x0A: /* SLTI */
|
||||
/* Widescreen render-funnel RIGHT-edge widen (auto_screen_x) for the
|
||||
* signed min/max funnel idiom (`slti v, minSX, W`) — the paired left
|
||||
* edge is the bltz above. Identity at 4:3 (margin 0). */
|
||||
if (psx_ws_auto_cull_on() && psx_ws_is_cull_w_imm(imm) && ws_cull_site(pc))
|
||||
if (psx_ws_is_cull_slti_site(pc) ||
|
||||
(psx_ws_auto_cull_on() && psx_ws_is_cull_w_imm(imm) && ws_cull_site(pc)))
|
||||
cpu->gpr[rt] = (uint32_t)psx_ws_cull_slti(cpu->gpr[rs], imm);
|
||||
else
|
||||
cpu->gpr[rt] = ((int32_t)cpu->gpr[rs] < simm) ? 1u : 0u;
|
||||
|
||||
+50
-32
@@ -226,7 +226,41 @@ int ws_nw_extra(void) { return 2 * ws_nw_offset(); }
|
||||
* cull while still stretching; large = over-draw) at a fixed camera position.
|
||||
* -1 = normal computed margin. */
|
||||
static int ws_margin_override = -1;
|
||||
static int ws_cull_guard_pixels = 0;
|
||||
void gpu_ws_set_margin_override(int v) { ws_margin_override = v; }
|
||||
void gpu_ws_set_cull_guard_pixels(int pixels) {
|
||||
if (pixels < 0) pixels = 0;
|
||||
if (pixels > 256) pixels = 256;
|
||||
ws_cull_guard_pixels = pixels;
|
||||
}
|
||||
|
||||
#define WS_EXPLICIT_CULL_SITES_MAX 64
|
||||
static uint32_t ws_explicit_bias_sites[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static uint32_t ws_explicit_slti_sites[WS_EXPLICIT_CULL_SITES_MAX];
|
||||
static int ws_explicit_bias_n = 0;
|
||||
static int ws_explicit_slti_n = 0;
|
||||
void gpu_ws_set_explicit_cull_sites(const uint32_t *bias, int nbias,
|
||||
const uint32_t *slti, int nslti) {
|
||||
if (nbias < 0) nbias = 0;
|
||||
if (nslti < 0) nslti = 0;
|
||||
if (nbias > WS_EXPLICIT_CULL_SITES_MAX) nbias = WS_EXPLICIT_CULL_SITES_MAX;
|
||||
if (nslti > WS_EXPLICIT_CULL_SITES_MAX) nslti = WS_EXPLICIT_CULL_SITES_MAX;
|
||||
ws_explicit_bias_n = nbias;
|
||||
ws_explicit_slti_n = nslti;
|
||||
for (int i = 0; i < nbias; i++) ws_explicit_bias_sites[i] = bias[i] & 0x1FFFFFFFu;
|
||||
for (int i = 0; i < nslti; i++) ws_explicit_slti_sites[i] = slti[i] & 0x1FFFFFFFu;
|
||||
}
|
||||
static int ws_explicit_site(const uint32_t *sites, int n, uint32_t pc) {
|
||||
uint32_t p = pc & 0x1FFFFFFFu;
|
||||
for (int i = 0; i < n; i++) if (sites[i] == p) return 1;
|
||||
return 0;
|
||||
}
|
||||
int psx_ws_is_cull_bias_site(uint32_t pc) {
|
||||
return ws_explicit_site(ws_explicit_bias_sites, ws_explicit_bias_n, pc);
|
||||
}
|
||||
int psx_ws_is_cull_slti_site(uint32_t pc) {
|
||||
return ws_explicit_site(ws_explicit_slti_sites, ws_explicit_slti_n, pc);
|
||||
}
|
||||
|
||||
int psx_ws_x_margin(void) {
|
||||
if (ws_margin_override >= 0) return ws_margin_override;
|
||||
@@ -235,7 +269,7 @@ int psx_ws_x_margin(void) {
|
||||
* that previously fell outside the 4:3 cull window; the wide compositor then
|
||||
* rasterizes it into the revealed margins. Same recompiler emit sites as the
|
||||
* squash path ([widescreen.cull]); 0 at 4:3 so the cull stays byte-identical. */
|
||||
if (ws_native_wide_active()) return ws_nw_offset();
|
||||
if (ws_native_wide_active()) return ws_nw_offset() + ws_cull_guard_pixels;
|
||||
if (!ws_active()) return 0;
|
||||
return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum;
|
||||
}
|
||||
@@ -268,7 +302,6 @@ static int ws_mmx6_left_cols(void) {
|
||||
if (off <= 0) return 0;
|
||||
return (off + 15) / 16; /* ceil to whole tile columns */
|
||||
}
|
||||
static void mmx6_clear_void_before_bg(void);
|
||||
/* HOST-SIDE reveal mode (elective, default ON): the guest-side widen below overruns
|
||||
* the engine's 1024-slot BG packet buffer / 999-tile cap in dense stages (void/stale
|
||||
* columns). When host-side mode is active we keep the GUEST renderer at its NATIVE 21
|
||||
@@ -1165,15 +1198,11 @@ static int ws_nw_left_hud_packet(void) {
|
||||
int matched = a >= ws_nw_left_hud_packet_lo && a < ws_nw_left_hud_packet_hi;
|
||||
return matched;
|
||||
}
|
||||
static int32_t ws_nw_left_hud_shift(void) {
|
||||
return ws_nw_left_hud_packet() ? -ws_nw_offset() : 0;
|
||||
}
|
||||
static int32_t ws_nw_hud_shift(int32_t x, int32_t w) {
|
||||
if (!ws_native_wide_active()) return 0;
|
||||
int32_t off = ws_nw_offset();
|
||||
if (off <= 0) return 0;
|
||||
if (ws_nw_left_hud_packet()) return -off;
|
||||
if (!ws_nw_hud_corners) return 0;
|
||||
if (!ws_nw_left_hud_packet() && !ws_nw_hud_corners) return 0;
|
||||
int32_t W = ws_disp_w();
|
||||
int32_t cx = 2 * x + w; /* 2*centre, avoids losing the half */
|
||||
if (3 * cx < 2 * W) return -off; /* left third -> pull to left edge */
|
||||
@@ -1338,19 +1367,11 @@ static void ws_clear_all_reveal_margins(void) {
|
||||
gr_wide_clear_margins((int)ws_fb_base[i], 0, 512, 0, 3);
|
||||
}
|
||||
|
||||
static void mmx6_clear_void_before_bg(void) {
|
||||
static uint32_t cleared_generation[WS_FB_BASES] = { 0, 0, 0, 0 };
|
||||
if (!ws_clear_reveal || !ws_native_wide_active() || !g_mmx6_void_sides)
|
||||
return;
|
||||
int fb = -1;
|
||||
for (int i = 0; i < ws_fb_n; i++)
|
||||
if (ws_fb_base[i] == draw_area_left) { fb = i; break; }
|
||||
if (fb < 0 || cleared_generation[fb] == g_mmx6_void_generation) return;
|
||||
gr_wide_clear_margins((int)draw_area_left, 0, 512, 0,
|
||||
g_mmx6_void_sides);
|
||||
cleared_generation[fb] = g_mmx6_void_generation;
|
||||
}
|
||||
void gpu_ws_begin_linked_list(void) { mmx6_clear_void_before_bg(); }
|
||||
/* Stage-init already clears both synthetic margins once. Do not keep clearing
|
||||
* a guessed finite-map side here: MMX6's authored layers enter the reveal at
|
||||
* different times, so the side guess produced a moving black trim over valid
|
||||
* stage art. A stale reveal tile is safer than deleting submitted content. */
|
||||
void gpu_ws_begin_linked_list(void) { }
|
||||
|
||||
|
||||
/* Horizontal display range (GP1(06h)) */
|
||||
@@ -1898,7 +1919,12 @@ static void gp0_exec_shaded_quad(void) {
|
||||
c[i] = rgb888_to_rgb555(gp0_cmd_buf[i * 2] & 0xFFFFFFu);
|
||||
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
|
||||
}
|
||||
int32_t hud_dx = ws_nw_left_hud_shift();
|
||||
int32_t minx = vx[0], maxx = vx[0];
|
||||
for (int i = 1; i < 4; i++) {
|
||||
if (vx[i] < minx) minx = vx[i];
|
||||
if (vx[i] > maxx) maxx = vx[i];
|
||||
}
|
||||
int32_t hud_dx = ws_nw_hud_shift(minx, maxx - minx);
|
||||
if (hud_dx)
|
||||
for (int i = 0; i < 4; i++) vx[i] += hud_dx;
|
||||
ws_nw_backdrop_stretch_quad(vx, vy); /* full-frame 2D backdrop stretch (sky gradient; no-op else) */
|
||||
@@ -2280,17 +2306,9 @@ static void gp0_exec_textured_16x16(void) {
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
int32_t x0, y0;
|
||||
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
|
||||
/* MMX6 finite-map void: the widened renderer still submits stale ring slots
|
||||
* for columns before/after the real map. DMA linked-list start has cleared
|
||||
* that synthetic side once for this stage generation; suppress only those
|
||||
* extra BG packets so they cannot paint stale tiles back over the void. */
|
||||
if (ws_clear_reveal && gp0_cmd_source_addr != 0xFFFFFFFFu) {
|
||||
uint32_t src = gp0_cmd_source_addr & 0x1FFFFFFFu;
|
||||
if (src >= MMX6_BG_BUF_LO && src < MMX6_BG_BUF_HI) {
|
||||
if ((g_mmx6_void_sides & 1) && x0 < 0) return;
|
||||
if ((g_mmx6_void_sides & 2) && x0 >= (int32_t)ws_disp_w()) return;
|
||||
}
|
||||
}
|
||||
/* Never suppress MMX6 BG packets at a guessed finite-map boundary. The
|
||||
* classifier cannot distinguish an authored layer entering the reveal from
|
||||
* a stale ring slot; suppressing here caused the stage-start black flicker. */
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, 16);
|
||||
x0 += ws_nw_hud_shift(x0, 16); /* native-wide HUD corner re-anchor (no-op else) */
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
|
||||
@@ -2172,6 +2172,10 @@ int main(int argc, char** argv) {
|
||||
/* [widescreen] clear_reveal — enable opted-in scene/map-boundary
|
||||
* cleanup of synthetic native-wide margins. */
|
||||
gpu_ws_set_clear_reveal(gc.ws_clear_reveal ? 1 : 0);
|
||||
gpu_ws_set_cull_guard_pixels(gc.ws_cull_guard_pixels);
|
||||
gpu_ws_set_explicit_cull_sites(
|
||||
gc.ws_cull_bias_sites.data(), (int)gc.ws_cull_bias_sites.size(),
|
||||
gc.ws_cull_slti_sites.data(), (int)gc.ws_cull_slti_sites.size());
|
||||
/* [widescreen.cull] per-game gates + signature immediates for the
|
||||
* pattern-scanned interp/sljit widen hooks. A title that never
|
||||
* opted in must never have its live code scanned and rewritten. */
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sljitLir.h"
|
||||
#include "gpu.h"
|
||||
#include "ws_backdrop_detect.h" /* shared backdrop-window detector (auto_backdrop) */
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -834,6 +835,17 @@ void overlay_sljit_try_compile(uint32_t entry,
|
||||
if (!found_term || frag_words == 0 || frag_words > SLJIT_MAX_FRAG_INSNS) {
|
||||
s_declines++; return;
|
||||
}
|
||||
|
||||
/* Explicit address-listed cull hooks carry a live aspect term. Generated
|
||||
* overlay DLLs and the interpreter implement it directly; keep these rare
|
||||
* fragments out of the generic JIT so a vanilla immediate is never cached. */
|
||||
for (uint32_t i = 0; i < frag_words; i++) {
|
||||
uint32_t pc = entry + i * 4u;
|
||||
if (psx_ws_is_cull_bias_site(pc) || psx_ws_is_cull_slti_site(pc)) {
|
||||
s_declines++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (off0 + frag_words * 4u > size) { s_declines++; return; } /* delay slot off image */
|
||||
|
||||
/* Validate branch targets: in-range, aligned, not landing on a delay slot. */
|
||||
|
||||
Reference in New Issue
Block a user