Files
psxrecomp/runtime/include/ws_cull_detect.h
T
Matthew Stanley ee09ab9bbb widescreen: generic Ape-Escape-class support + native-wide GL perf + cutscene flicker fix
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>
2026-07-06 19:30:09 -07:00

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 */