mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
ee09ab9bbb
Adds full 16:9/21:9 native-wide support for fully-3D titles (Ape Escape, SCUS-94423) as a generic, opt-in, 4:3-byte-identical framework capability, plus a native-wide GL compositor perf fix and a cutscene present-mode flicker fix. Widescreen (generic, per-game opt-in via [widescreen*]): - Shared screen-extent cull detector (runtime/include/ws_cull_detect.h) used by all three backends (recompiler, dirty-RAM interp, sljit). Per-game signature immediates ([widescreen.cull] screen_w_imms/screen_h_imms) — Ape's funnel tests 0x181/0xF1 on its 368px display, not Tomba's 0x140/0xE0. - Three funnel idioms widened: unsigned per-vertex sltiu (psx_ws_cull_sltiu), signed min/max slti right edge + bare-bltz left edge (psx_ws_cull_slti/bltz, new helpers + SLJIT_HLP appends), and billboard center+/-halfwidth. Explicit [widescreen.cull] slti_sites for X-only funnels the auto-detector can't qualify. - gte_game_mode: GTE-activity gameplay detector for 3D titles with no sprite-tag helper (gte.cpp notes every RTPS/RTPT). STICKY (>=3 verts, 45-frame hysteresis) so sparse cutscene frames never flip to a 4:3 pillarbox = fixes intro flicker. - nw_hud_corners: push outer-third screen-space HUD sprites to the true wide corners in native-wide (else inset by the reveal). - nw_backdrop: stretch a full-frame axis-aligned 2D backdrop quad to fill the wide frame (for titles with a flat sky plane). - Pattern-scanned interp/sljit widen hooks now gated per-game (auto_screen_x / auto_backdrop): fixes an ungated auto_backdrop false-positive that rewrote a live GPR = the ~24s native-wide crash on Ape. Native-wide GL perf (fixes 16:9 frame drops): - The wide surface centre equals the canonical 4:3 framebuffer, so wide_blit_center copies canonical->wide-centre at present (both present paths) and the per-prim mirror SKIPS prims fully inside the 4:3 frame (mirror_x/geo/batch_center_only). Only reveal-margin prims re-rasterize. Peak frame time 38.8ms -> 17.5ms, no vsync misses. Toggle gl_wide_fast; 4:3 untouched (all gated on g_wide_cur). Runtime-only except the recompiler cull emit (regen needed for cull widening). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
5.5 KiB
C
121 lines
5.5 KiB
C
#ifndef PSX_WS_CULL_DETECT_H
|
|
#define PSX_WS_CULL_DETECT_H
|
|
/* ============================================================================
|
|
* Generic widescreen SCREEN-EXTENT CULL detector ([widescreen.cull]
|
|
* auto_screen_x)
|
|
*
|
|
* Self-contained (depends only on <stdint.h>) so ONE implementation is shared
|
|
* verbatim by the recompiler (C++) and the runtime interpreter + sljit emitter
|
|
* (C) — the widen must be identical on every execution path or the backends
|
|
* diverge (same contract as ws_backdrop_detect.h).
|
|
*
|
|
* WHAT IT FINDS
|
|
* -------------
|
|
* A GTE render funnel trivially rejects a primitive whose projected vertices
|
|
* all fall outside the screen. Titles express the horizontal reject three ways
|
|
* (immediates are per-game: Tomba tests X < 0x140/0x141 on a 320 display, Ape
|
|
* Escape tests X < 0x181 on its 368 display — configured via
|
|
* [widescreen.cull] screen_w_imms / screen_h_imms):
|
|
*
|
|
* (1) UNSIGNED PER-VERTEX (Tomba, Ape sprite path):
|
|
* sltiu v, SX, W ; SX loaded lhu / masked andi 0xFFFF
|
|
* One compare covers both edges (off-left SX wraps to a large unsigned).
|
|
* Widened by psx_ws_cull_sltiu(): window [-m, W+m).
|
|
*
|
|
* (2) SIGNED MIN/MAX (Ape tri/quad funnel):
|
|
* bltz maxSX, reject ; all verts left of 0
|
|
* _slti v, minSX, W ; (delay slot) all verts right of W-1
|
|
* The right bound widens via psx_ws_cull_slti() (W+m); the LEFT bound is
|
|
* the bare bltz, widened via psx_ws_cull_bltz() (reject only when
|
|
* maxSX < -m). The X pair is discriminated from the identically-shaped Y
|
|
* pair by the delay-slot immediate (W-set vs H-set).
|
|
*
|
|
* (3) SIGNED CENTER±HALFWIDTH (Ape billboard funnel):
|
|
* addu t, cx, r
|
|
* bltz t, reject ; cx + r < 0 (fully left)
|
|
* _<any>
|
|
* subu t, cx, r
|
|
* slti v, t, W ; cx - r >= W (fully right)
|
|
* The bltz is recognised by the addu immediately before it whose (rs,rt)
|
|
* pair reappears in a subu feeding a W-immediate slti a few words later.
|
|
*
|
|
* A function/window QUALIFIES only when it contains BOTH a width compare
|
|
* (slti/sltiu, imm in the W set) and a height compare (imm in the H set) — the
|
|
* screen-extent signature. A lone width-immediate compare elsewhere stays
|
|
* vanilla.
|
|
*
|
|
* All widen helpers are identity at margin 0 (4:3 / boot / menu / FMV), so the
|
|
* transformed code is bit-for-bit the vanilla verdict outside native-wide.
|
|
* ========================================================================== */
|
|
#include <stdint.h>
|
|
|
|
static inline int psx_ws_cull_imm_in(uint32_t imm, const uint32_t *set, int n) {
|
|
for (int i = 0; i < n; i++)
|
|
if (set[i] == imm) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* is words[i] a slti (0x0A) or sltiu (0x0B) with an immediate in `set`? */
|
|
static inline int psx_ws_cull_is_slt_imm(uint32_t w, const uint32_t *set, int n) {
|
|
uint32_t op = w >> 26;
|
|
if (op != 0x0Au && op != 0x0Bu) return 0;
|
|
return psx_ws_cull_imm_in(w & 0xFFFFu, set, n);
|
|
}
|
|
|
|
/* Screen-extent signature over an instruction window: at least one W compare
|
|
* AND one H compare (slti or sltiu). Pure — identical verdicts on the
|
|
* recompiler's full-function scan and the runtime's ±window scan. */
|
|
static inline int psx_ws_cull_scan(const uint32_t *words, int n,
|
|
const uint32_t *w_imms, int nw,
|
|
const uint32_t *h_imms, int nh) {
|
|
int has_x = 0, has_y = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
uint32_t w = words[i];
|
|
uint32_t op = w >> 26;
|
|
if (op != 0x0Au && op != 0x0Bu) continue;
|
|
uint32_t im = w & 0xFFFFu;
|
|
if (psx_ws_cull_imm_in(im, w_imms, nw)) has_x = 1;
|
|
else if (psx_ws_cull_imm_in(im, h_imms, nh)) has_y = 1;
|
|
if (has_x && has_y) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Classify words[idx] as an X LEFT-EDGE reject bltz (idiom 2 or 3 above).
|
|
* Caller must already have qualified the surrounding window via
|
|
* psx_ws_cull_scan. Returns 1 iff the bltz belongs to a width reject. */
|
|
static inline int psx_ws_cull_bltz_here(const uint32_t *words, int n, int idx,
|
|
const uint32_t *w_imms, int nw) {
|
|
if (idx < 0 || idx >= n) return 0;
|
|
uint32_t w = words[idx];
|
|
if ((w >> 26) != 0x01u || ((w >> 16) & 0x1Fu) != 0x00u) return 0; /* bltz only */
|
|
uint32_t bltz_rs = (w >> 21) & 0x1Fu;
|
|
|
|
/* Idiom 2: the width slti/sltiu sits in the bltz's delay slot. (The Y pair
|
|
* has the same shape with an H immediate — excluded by the W-set test.) */
|
|
if (idx + 1 < n && psx_ws_cull_is_slt_imm(words[idx + 1], w_imms, nw))
|
|
return 1;
|
|
|
|
/* Idiom 3: addu t,x,y immediately before; a subu with the same (x,y) pair
|
|
* within the next few words feeds a W-immediate slti/sltiu right after. */
|
|
if (idx >= 1) {
|
|
uint32_t p = words[idx - 1];
|
|
if ((p >> 26) == 0x00u && (p & 0x3Fu) == 0x21u && /* addu */
|
|
((p >> 11) & 0x1Fu) == bltz_rs) { /* rd feeds bltz */
|
|
uint32_t ax = (p >> 21) & 0x1Fu, ay = (p >> 16) & 0x1Fu;
|
|
for (int j = idx + 1; j <= idx + 4 && j < n; j++) {
|
|
uint32_t s = words[j];
|
|
if ((s >> 26) == 0x00u && (s & 0x3Fu) == 0x23u && /* subu */
|
|
((s >> 21) & 0x1Fu) == ax && ((s >> 16) & 0x1Fu) == ay) {
|
|
for (int k = j + 1; k <= j + 2 && k < n; k++)
|
|
if (psx_ws_cull_is_slt_imm(words[k], w_imms, nw))
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif /* PSX_WS_CULL_DETECT_H */
|