Files
psxrecomp/runtime/include/gpu.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

259 lines
13 KiB
C

/* gpu.h — PS1 GPU hardware simulation (Phase 3).
*
* Implements GPUSTAT, GP0, GP1, and 1024x512 16-bit VRAM.
* No rendering to screen — just correct hardware state transitions.
*/
#ifndef PSXRECOMP_GPU_H
#define PSXRECOMP_GPU_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void gpu_init(void);
uint32_t gpu_read_gpustat(void); /* 0x1F801814 read */
uint32_t gpu_read_gpuread(void); /* 0x1F801810 read */
void gpu_write_gp0(uint32_t val); /* 0x1F801810 write */
void gpu_write_gp1(uint32_t val); /* 0x1F801814 write */
void gpu_set_gp0_source(uint32_t addr); /* diagnostic source for next GP0 word */
void gpu_vblank_tick(void); /* Toggle LCF, called at each simulated vblank */
/* Display presentation accessors (Phase 3). */
const uint16_t* gpu_get_vram(void); /* Pointer to 1024x512 16-bit VRAM */
typedef struct {
uint32_t display_x, display_y; /* VRAM start of display area (GP1(05h)) */
uint32_t width, height; /* Derived from display mode + ranges */
int depth24; /* GP1(08h) display depth flag: RGB888 scanout */
int disabled; /* GP1(03h) display disable flag */
} GpuDisplayInfo;
void gpu_get_display_info(GpuDisplayInfo* out);
void gpu_display_pixel_rgb(const GpuDisplayInfo* di, uint32_t x, uint32_t y,
uint8_t* r, uint8_t* g, uint8_t* b);
uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y);
uint64_t gpu_get_gp0_count(void); /* Total GP0 writes since init */
void gpu_get_gp0_stats(uint64_t* nop, uint64_t* fill, uint64_t* draw, uint64_t* env, uint64_t* copy);
typedef struct {
uint32_t left, top, right, bottom;
int32_t offset_x, offset_y;
} GpuDrawArea;
void gpu_get_draw_area(GpuDrawArea* out);
uint16_t gpu_vram_peek(int x, int y);
/* Shaded quad vertex capture (Phase 4.5 debug). */
typedef struct {
int32_t vx[4], vy[4];
uint32_t color[4];
} GpuSqCapEntry;
void gpu_arm_shaded_quad_capture(void);
int gpu_get_shaded_quad_capture(const GpuSqCapEntry** out);
/* Per-frame GP0 command ring (always-on; query via debug server).
* Each entry records the GP0 command header + up to 6 payload words
* (longer commands like 0x3C shaded textured quad are truncated to 6).
* Stamped with the s_frame_count value at issue time so a debug
* client can pull all commands for any frame in the ring window. */
#define GPU_GP0_RING_MAX_WORDS 12
typedef struct {
uint32_t frame;
uint32_t seq;
uint32_t src_addr; /* RAM/MMIO source address of command header, if known */
uint32_t pc; /* g_debug_last_store_pc when command completes */
uint32_t func; /* g_debug_current_func_addr at issue (executing guest fn) */
uint32_t ra; /* guest $ra at issue: direct caller of the leaf GP0 helper */
uint8_t opcode;
uint8_t n_words; /* total command length; >MAX means truncated */
uint16_t pad;
uint32_t cmd[GPU_GP0_RING_MAX_WORDS];
/* Builder attribution (populated only for VRAM->VRAM copies, op 0x80):
* bounded guest-stack unwind of validated return addresses, innermost
* first. Lets a caller skip the libgpu funnel band and name the game-level
* routine that assembled the copy. Zero for non-copy commands. */
uint32_t bld[6];
uint32_t csp; /* raw guest $sp at copy time (diagnostic) */
} GpuGp0RingEntry;
uint64_t gpu_gp0_ring_total(void);
uint32_t gpu_gp0_ring_capacity(void);
uint32_t gpu_gp0_ring_max_words(void);
int gpu_gp0_ring_dump_frame(uint32_t frame, GpuGp0RingEntry *out, int max_out);
void gpu_gp0_ring_frame_span(uint32_t *out_oldest, uint32_t *out_newest);
/* Vblank presentation callback — called from gpu_vblank_tick(). */
typedef void (*gpu_vblank_cb)(void);
void gpu_set_vblank_callback(gpu_vblank_cb cb);
/* Present-time screen-colour model (see color_lut.h ScreenKind: 0=raw,
* 1=crt, 2=composite, 3=trinitron). Config/launcher-driven; the PSX_SCREEN
* env var, if set, overrides this. Default 0 (raw) is byte-identical. */
void gpu_set_screen_kind(int kind);
/* Widescreen proportion correction (active only when aspect != 4:3 and the
* game's [widescreen] block opts in — see config_loader.h). Tagged
* character/billboard prims are re-squashed around their projected anchor;
* untagged SPRT prims (screen-space HUD/menus) around the display centre.
* psx_ws_sprite_tag is the per-prim callback the recompiler emits at the
* entry of each [widescreen] sprite_tag_funcs function. */
/* mode: 0 = off (4:3 identity), 1 = squash (legacy hack), 2 = native-wide
* (render the wider FOV into a wider frame, present 1:1; GTE not squashed). */
void gpu_ws_configure(int aspect_num, int aspect_den,
uint32_t sprite_anchor_addr, int hud_sprt_squash, int mode);
/* [widescreen] full_2d: opt a pure-2D sprite game into the widescreen present
* path (treat every in-game frame as gameplay, since it never tags 3D prims). */
void gpu_ws_set_full_2d(int on);
/* [widescreen.bg2d] MMX6 background tile-loop widen — hooked at the renderer's
* column-count / start-tile-col / start-screen-x instructions. Identity at 4:3
* and in the engine's 512 hi-res mode. */
int psx_ws_mmx6_bg_cols(int base);
int psx_ws_mmx6_bg_startcol(int col);
int psx_ws_mmx6_bg_startx(int x);
int psx_ws_mmx6_bg_stream_left(int x);
int psx_ws_mmx6_bg_stream_right(int x);
struct CPUState;
void psx_ws_sprite_tag(struct CPUState* cpu);
/* Native-wide (mode 2) on a game frame. ws_nw_extra() is the total width the
* frame grows by, in display pixels (the present path widens the display read
* by this; 0 when native-wide is inactive). */
int ws_native_wide_active(void);
int ws_nw_extra(void);
/* True when the current frame must present at native 4:3 (FMV video or a
* full-2D menu/title screen), so the squash is suppressed and content drawn
* pixel-native. The present path uses the same predicate to pillarbox. */
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);
/* 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
* `sltiu rt, sx, 0x140/0x141` through this one helper so every overlay execution
* path widens identically. Returns the sltiu verdict (1 = keep). 0 at 4:3. */
int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm);
/* Signed-funnel variants (min/max + center±halfwidth idioms — see
* ws_cull_detect.h): right-edge widen for `slti v, minSX, W` and left-edge
* widen for the paired `bltz maxSX` reject. Identity at 4:3. */
int psx_ws_cull_slti(uint32_t sx, uint32_t imm);
int psx_ws_cull_bltz(uint32_t v);
/* True if a run of instruction words carries the screen-extent reject signature
* (a width compare AND a height compare from the configured immediate sets).
* Used by sljit/interp to gate the widening to real render funnels. */
int psx_ws_func_has_screen_cull(const uint32_t *words, int n);
/* Classify words[idx] as an X left-edge reject bltz (runtime imm sets). */
int psx_ws_cull_bltz_at(const uint32_t *words, int n, int idx);
/* Per-game cull signature immediates ([widescreen.cull] screen_w_imms /
* screen_h_imms); defaults 0x140/0x141 + 0xE0/0xF1. */
void gpu_ws_set_cull_imms(const uint32_t *w, int nw, const uint32_t *h, int nh);
int psx_ws_is_cull_w_imm(uint32_t imm);
/* Per-game opt-in gates for the pattern-scanned interp/sljit widen hooks
* (auto_screen_x cull + auto_backdrop preload). Default OFF: a title that
* never opted in must never have its live code pattern-scanned and rewritten. */
void gpu_ws_set_auto_hooks(int cull_on, int backdrop_on);
int psx_ws_auto_cull_on(void);
/* GTE-activity gameplay detector ([widescreen] gte_game_mode) for 3D titles
* with no sprite-tag helper: gte.cpp notes every RTPS/RTPT projection; a frame
* that projects enough vertices is stamped as gameplay. */
void gpu_ws_set_gte_game_mode(int on);
void psx_ws_note_gte_project(int nverts);
/* Native-wide HUD corner re-anchoring ([widescreen] nw_hud_corners): push
* 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);
/* Native-wide full-frame 2D backdrop stretch ([widescreen] nw_backdrop):
* stretch a screen-space quad that covers the whole 4:3 framebuffer (sky
* gradient / backdrop image) to fill the wide frame, so it no longer
* pillarboxes at the reveal margins. Runtime-only. Off by default. */
void gpu_ws_set_nw_backdrop(int on);
/* Backdrop screen-X correction ([widescreen.backdrop] x_sites). The parallax
* 2D backdrop layer computes screen-X without the GTE, so it misses the
* widescreen squash; the recompiler emits this on each backdrop handler's
* final screenX store to squash it around the screen centre (identity at 4:3).
* Pulls far backdrop pieces in from past the 320px edge to cover the 16:9 FOV. */
int psx_ws_backdrop_x(int x);
/* Backdrop PRELOAD ([widescreen.cull] auto_backdrop). psx_ws_backdrop_preload()
* is nonzero only while native-wide widescreen is engaged (0 at 4:3 / boot /
* FMV / full-2D), so the rewrite is byte-identical when off. psx_ws_backdrop_value()
* is the one value substitution shared by the gcc emit, the sljit JIT, and the
* interpreter for a detected window bound: it returns `orig` unless preload is
* engaged, in which case it forces the bound to preload the WHOLE finite tile
* row (START -> 0, END -> large sentinel pinned by the generator's high clamp to
* extent-1). Every column is submitted, so the widened 16:9 viewport never gaps;
* the generator's clamps + the byte-sized row extent keep it bounded. */
int psx_ws_backdrop_preload(void);
uint32_t psx_ws_backdrop_value(uint32_t orig, int is_end, int window_cols);
/* auto_backdrop diagnostic ring (always-on). The interpreter records every
* window rewrite (with the live extent / DL count / camera-X); `ws_backdrop_ring`
* dumps it. psx_ws_backdrop_ring_json() formats the recent entries into `buf`
* (returns bytes written, excluding the caller's JSON envelope). */
void psx_ws_backdrop_ring_note(uint32_t pc, int kind, int wcols, uint32_t orig,
uint32_t finalv, int extent, int camx, int count,
uint32_t base, uint32_t dl);
int psx_ws_backdrop_ring_json(char *buf, int cap);
/* Live-tunable backdrop widen amount (ws_backdrop_margin command): <0 whole-row,
* 0 off, >0 N-column widen. g_ws_bd_from_interp is the interp's one-shot
* "I will record the rich entry myself" flag (see gpu.c / dirty_ram_interp.c). */
extern int g_ws_bd_margin;
extern int g_ws_bd_from_interp;
/* Native-wide: nonzero if the GP0 prim currently being drawn is sprite-tagged
* (a character / Tomba / HUD element). The GL 2D-backdrop stretch uses this to
* EXCLUDE foreground sprites (only the untagged 2D backdrop is stretched). */
int psx_ws_prim_is_tagged(void);
/* Flower-field backdrop data-structure address range + a predicate matching the
* prim being drawn against it (gp0_cmd_source_addr ∈ [lo,hi]). The dirty-RAM
* interp sets lo/hi when the backdrop generator runs; the GL stretch gate uses
* psx_ws_prim_in_backdrop() to identify the flower-field tiles precisely. */
extern uint32_t g_ws_backdrop_lo, g_ws_backdrop_hi;
int psx_ws_prim_in_backdrop(void);
/* Backdrop store-site registry: the runtime registers the [widescreen.backdrop]
* x_sites here (from game.toml) so the dirty-RAM interpreter applies the same
* psx_ws_backdrop_x() squash at those `sh` PCs that the recompiler emits into
* native cache DLLs — overlay code frequently runs interpreted, where the emit
* can't reach. PCs are masked to the physical (KUSEG) range. */
void psx_ws_set_backdrop_sites(const uint32_t* pcs, int n);
int psx_ws_is_backdrop_site(uint32_t pc);
/* Live widescreen state for diagnostics (TCP gpu_state). All pointers
* optional. last_tag_frame/cur_frame let the caller see game_mode freshness. */
typedef struct {
int configured; /* ws_xnum != ws_xden */
int active; /* squash currently applied this frame */
int game_mode; /* tagged char/billboard prim within 2 frames */
int present_native_43; /* frame presents pillarboxed 4:3 (FMV/full-2D) */
int x_margin; /* psx_ws_x_margin() right now */
int xnum, xden; /* squash factor */
int mode; /* 0 = off, 1 = squash, 2 = native-wide */
int nw_extra; /* native-wide frame growth (display px), 0 if off */
uint64_t cur_frame;
uint32_t last_tag_frame; /* frame of newest tagged prim */
} GpuWsDebug;
void gpu_ws_get_debug(GpuWsDebug* out);
/* Diagnostic: force psx_ws_x_margin() to return v (>=0) regardless of state,
* or -1 to restore the normal computed margin. For live cull-margin sweeps. */
void gpu_ws_set_margin_override(int v);
/* Always-on draw-census ring: every drawn primitive records frame / source
* addr / camera / first-vertex screen pos, so object spawn/despawn and edge
* culls are observable in data. Dump frames [f0,f1] to a CSV file. */
int gpu_ws_census_dump(uint32_t f0, uint32_t f1, const char *path);
void gpu_ws_census_set(int on);
uint64_t gpu_ws_census_seq(void);
#ifdef __cplusplus
}
#endif
#endif /* PSXRECOMP_GPU_H */