mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge codex/tomba2-whoopee-beach-fix: native-wide flat/phase backdrop gates + FMV silent-hold
Tomba 2 widescreen enablers: [widescreen] nw_flat_backdrop / nw_phase_backdrop native-wide mirror gates (canonical 4:3 untouched), [video] fmv_skip_no_xa_hold for silent preloaded-logo auto-skip, ws_dbg_stretch modes 7/8, fmv_state JSON escaping. Conflicts (config_loader.h/.cpp, main.cpp) were both-sides-adds vs the MMX4/5 clear_reveal work - resolved keeping both feature sets. Compile + boot sanity: Ape build-release rebuilt and boots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -213,6 +213,17 @@ on a fixed region -> next.
|
||||
|
||||
## 5. Status / Log (update every session)
|
||||
|
||||
- **2026-07-10 (Tomba 2 Whoopee auto-skip dwell + native-wide Beach backdrop):**
|
||||
Added an opt-in silent-MDEC post-decode hold so presentation-side FMV
|
||||
auto-skip remains unpaced through a preloaded logo's authored release wait;
|
||||
the faithful guest timeline is unchanged and the default remains four
|
||||
vblanks. Added title-opted native-wide mirror gates for flat primitives and
|
||||
the textured pre-shaded backdrop phase. This keeps the canonical 4:3 buffer
|
||||
untouched while filling Tomba 2 Beach Town at 16:9 and 21:9 without stretching
|
||||
the later 3D foreground. Also fixed JSON escaping in the `fmv_state` path and
|
||||
extended its resolved-config reporting. Validated Release build, unattended
|
||||
first-attract captures at 4:3/16:9/21:9, and zero unknown dispatches.
|
||||
|
||||
- **2026-07-02 (HLE PIVOT implemented — HLE as a first-class swappable tier, gbarecomp model):**
|
||||
USER-DIRECTED pivot (supersedes "no HLE" §0; CLAUDE.md amended 2026-07-02, memory
|
||||
hle_tier_architecture.md). Built the full stack this session: (1) EMITTER —
|
||||
|
||||
@@ -244,6 +244,13 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
if (video.contains("fmv_skip_no_xa")) {
|
||||
rt.video_fmv_skip_no_xa = toml::find<bool>(video, "fmv_skip_no_xa");
|
||||
}
|
||||
if (video.contains("fmv_skip_no_xa_hold")) {
|
||||
rt.video_fmv_skip_no_xa_hold =
|
||||
(int)toml::find<int64_t>(video, "fmv_skip_no_xa_hold");
|
||||
if (rt.video_fmv_skip_no_xa_hold < 4 || rt.video_fmv_skip_no_xa_hold > 3600)
|
||||
throw std::runtime_error(
|
||||
"[video] fmv_skip_no_xa_hold must be between 4 and 3600 vblanks");
|
||||
}
|
||||
if (video.contains("low_latency_input")) {
|
||||
rt.video_low_latency_input = toml::find<bool>(video, "low_latency_input");
|
||||
}
|
||||
@@ -701,6 +708,8 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
uint32_t ws_nw_left_hud_packet_hi = 0;
|
||||
bool ws_nw_backdrop = false;
|
||||
bool ws_clear_reveal = false;
|
||||
bool ws_nw_flat_backdrop = false;
|
||||
bool ws_nw_phase_backdrop = false;
|
||||
bool ws_offered = true;
|
||||
bool ws_ultrawide_offered = false;
|
||||
if (cfg.contains("widescreen")) {
|
||||
@@ -751,6 +760,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
ws_nw_backdrop = toml::find<bool>(ws, "nw_backdrop");
|
||||
if (ws.contains("clear_reveal"))
|
||||
ws_clear_reveal = toml::find<bool>(ws, "clear_reveal");
|
||||
if (ws.contains("nw_flat_backdrop"))
|
||||
ws_nw_flat_backdrop = toml::find<bool>(ws, "nw_flat_backdrop");
|
||||
if (ws.contains("nw_phase_backdrop"))
|
||||
ws_nw_phase_backdrop = toml::find<bool>(ws, "nw_phase_backdrop");
|
||||
if (ws.contains("offer"))
|
||||
ws_offered = toml::find<bool>(ws, "offer");
|
||||
if (ws.contains("offer_ultrawide"))
|
||||
@@ -921,6 +934,8 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
/*ws_nw_left_hud_packet_hi*/ ws_nw_left_hud_packet_hi,
|
||||
/*ws_nw_backdrop*/ ws_nw_backdrop,
|
||||
/*ws_clear_reveal*/ ws_clear_reveal,
|
||||
/*ws_nw_flat_backdrop*/ ws_nw_flat_backdrop,
|
||||
/*ws_nw_phase_backdrop*/ ws_nw_phase_backdrop,
|
||||
/*ws_offered*/ ws_offered,
|
||||
/*ws_ultrawide_offered*/ ws_ultrawide_offered,
|
||||
/*ws_bg2d_count_site*/ ws_bg2d_count_site,
|
||||
|
||||
@@ -228,6 +228,12 @@ struct RuntimeConfig {
|
||||
// side only, guest timeline untouched. Per-game opt-in because MDEC use
|
||||
// outside movies (loading-screen stills) would briefly trigger it.
|
||||
bool video_fmv_skip_no_xa = false;
|
||||
// fmv_skip_no_xa_hold: presentation-side fast-forward latch, in guest
|
||||
// vblanks, after the most recent silent MDEC decode. Silent preloaded
|
||||
// logos can retain an authored post-decode wait; a title may opt into a
|
||||
// longer latch so auto-skip covers that wait too. Default preserves the
|
||||
// generic four-frame inter-decode hysteresis.
|
||||
int video_fmv_skip_no_xa_hold = 4;
|
||||
|
||||
// aspect_ratio: display aspect "W:H" (default "4:3" = native). A wider
|
||||
// aspect (e.g. "16:9") enables the widescreen hack: the GTE squashes
|
||||
@@ -524,6 +530,18 @@ struct GameConfig {
|
||||
// only gate; any game-specific init hook remains regen-class. Off by default.
|
||||
bool ws_clear_reveal = false;
|
||||
|
||||
// [widescreen] nw_flat_backdrop — in the native-wide mirror only, stretch
|
||||
// untextured flat primitives across the wider output. This is for games
|
||||
// whose authored 4:3 sky/backdrop is emitted as flat polygons rather than
|
||||
// a recognizable full-frame textured quad. Runtime-only; off by default.
|
||||
bool ws_nw_flat_backdrop = false;
|
||||
|
||||
// [widescreen] nw_phase_backdrop — stretch textured primitives emitted
|
||||
// before the frame's first shaded 3D primitive. This isolates an authored
|
||||
// 2D sky/backdrop phase from the later textured foreground. Runtime-only;
|
||||
// off by default because draw ordering is title-specific.
|
||||
bool ws_nw_phase_backdrop = false;
|
||||
|
||||
// [widescreen] offer — whether the launcher OFFERS its EXPERIMENTAL
|
||||
// Widescreen toggle for this title. Default true. Set false while a
|
||||
// title's widescreen is unported/unvalidated (e.g. MMX4 at bring-up):
|
||||
|
||||
@@ -189,6 +189,13 @@ void gpu_ws_begin_linked_list(void);
|
||||
* 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);
|
||||
/* Native-wide flat-polygon backdrop stretch ([widescreen] nw_flat_backdrop):
|
||||
* stretch untextured primitives in the wide mirror without changing the
|
||||
* canonical 4:3 framebuffer. Intended for flat-colour sky/water backdrops. */
|
||||
void gpu_ws_set_nw_flat_backdrop(int on);
|
||||
int gpu_ws_nw_flat_backdrop_enabled(void);
|
||||
/* Stretch the title-opted textured pre-3D backdrop phase in the wide mirror. */
|
||||
void gpu_ws_set_nw_phase_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
|
||||
|
||||
@@ -6867,7 +6867,7 @@ static void handle_ws_backdrop_stretch(int id, const char *json)
|
||||
/* prim<->pixel correlation gate (ws_dbg_stretch). Forces g_ws_bd_stretch_on=1 and
|
||||
* sets the selectable match mode used by the GL native-wide 2D-stretch gate so a
|
||||
* probe can stretch an exact prim set and screenshot which one fills the void.
|
||||
* Args: mode (0..6), lo, hi (OT addrs, hex via _hex fields or decimal), clut.
|
||||
* Args: mode (0..8), lo, hi (OT addrs, hex via _hex fields or decimal), clut.
|
||||
* Reports matched/tagged counts (last frame) + applied. */
|
||||
static void handle_ws_dbg_stretch(int id, const char *json)
|
||||
{
|
||||
@@ -9785,24 +9785,28 @@ static void handle_mdec_state(int id, const char *json)
|
||||
static void handle_fmv_state(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
extern void debug_get_fmv_config(int *, uint32_t *, uint32_t *, const char **);
|
||||
extern void debug_get_fmv_config(int *, uint32_t *, uint32_t *, int *, const char **);
|
||||
extern uint32_t mdec_get_decode_count(void);
|
||||
extern int cdrom_xa_stream_active(void);
|
||||
extern uint16_t sio_get_pad_buttons(void);
|
||||
extern uint64_t cdrom_get_dataready_fires(void);
|
||||
extern uint64_t g_cdrom_deliver_count;
|
||||
int auto_skip = -1; uint32_t total_table = 0, movie_id = 0; const char *cfg = "(null)";
|
||||
debug_get_fmv_config(&auto_skip, &total_table, &movie_id, &cfg);
|
||||
int auto_skip = -1, no_xa_hold = 0;
|
||||
uint32_t total_table = 0, movie_id = 0; const char *cfg = "(null)";
|
||||
char cfg_json[1024];
|
||||
debug_get_fmv_config(&auto_skip, &total_table, &movie_id, &no_xa_hold, &cfg);
|
||||
json_escape_string(cfg_json, sizeof(cfg_json), cfg ? cfg : "(null)");
|
||||
MDECDebugState s; mdec_debug_get_state(&s);
|
||||
send_fmt("{\"id\":%d,\"ok\":true,"
|
||||
"\"config_path\":\"%s\","
|
||||
"\"auto_skip_fmv\":%d,"
|
||||
"\"fmv_skip_no_xa_hold\":%d,"
|
||||
"\"fmv_skip_total_table\":\"0x%08X\",\"fmv_skip_movie_id\":\"0x%08X\","
|
||||
"\"mdec_decode_count\":%u,\"mdec_decode_macroblocks\":%u,\"mdec_dma_out_words\":%u,"
|
||||
"\"xa_stream_active\":%d,"
|
||||
"\"cd_dataready_fires\":%llu,\"cd_irq_delivered\":%llu,"
|
||||
"\"pad1\":\"0x%04X\"}",
|
||||
id, cfg ? cfg : "(null)", auto_skip,
|
||||
id, cfg_json, auto_skip, no_xa_hold,
|
||||
total_table, movie_id,
|
||||
mdec_get_decode_count(), s.decode_macroblocks, s.dma_out_words,
|
||||
cdrom_xa_stream_active(),
|
||||
|
||||
@@ -1105,6 +1105,8 @@ static int ws_tagged_anchor(int32_t *out_ax) {
|
||||
* flower-field tiles — the 3D rock/foreground is untagged AND has narrow prims,
|
||||
* so tag/narrow heuristics alone mis-stretch and tear it. */
|
||||
uint32_t g_ws_backdrop_lo = 0, g_ws_backdrop_hi = 0;
|
||||
static int ws_nw_phase_backdrop = 0;
|
||||
void gpu_ws_set_nw_phase_backdrop(int on) { ws_nw_phase_backdrop = on ? 1 : 0; }
|
||||
/* diag: per-frame min/max of the prim source addrs the GL gate sees */
|
||||
uint32_t g_bdg_src_lo = 0xFFFFFFFFu, g_bdg_src_hi = 0;
|
||||
static uint32_t bdg_src_frame = 0xFFFFFFFFu;
|
||||
@@ -1120,6 +1122,8 @@ static uint32_t bdg_src_frame = 0xFFFFFFFFu;
|
||||
* 4 tagged : prim is sprite-tagged (psx_ws_prim_is_tagged)
|
||||
* 5 untag-tex : textured AND not sprite-tagged
|
||||
* 6 all-tex : every textured prim (sanity: should fill void + distort 3D)
|
||||
* 7 all : every primitive (coarse correlation sanity check)
|
||||
* 8 all-flat : every untextured primitive
|
||||
* Counters (per-frame, snapshotted by the present hook) report how many prims
|
||||
* matched and how many of those were sprite-tagged. */
|
||||
int g_dbg_mode = 0;
|
||||
@@ -1143,6 +1147,8 @@ static int dbg_gate_match(void) {
|
||||
case 4: m = tagged; break;
|
||||
case 5: m = (textured && !tagged); break;
|
||||
case 6: m = textured; break;
|
||||
case 7: m = 1; break;
|
||||
case 8: m = !textured; break;
|
||||
default: m = 0; break;
|
||||
}
|
||||
if (m) { s_dbg_match_n++; if (tagged) s_dbg_match_tagged++; }
|
||||
@@ -1182,6 +1188,7 @@ int psx_ws_prim_in_backdrop(void) {
|
||||
if (gp0_cmd_source_addr > g_bdg_src_hi) g_bdg_src_hi = gp0_cmd_source_addr;
|
||||
}
|
||||
if (g_dbg_mode != 0) return dbg_gate_match(); /* correlation override */
|
||||
if (ws_nw_phase_backdrop && !ws_bg_phase_over()) return 1;
|
||||
/* Real gate: stretch the 2D backdrop = sprite-tagged prims drawn in the
|
||||
* background phase (before the 3D world). Fills the native-wide void for both
|
||||
* the flower grid and the sky band; HUD/characters (tagged but post-3D) and the
|
||||
@@ -1345,6 +1352,9 @@ static void ws_nw_hud_shift_vertices(int32_t *vx, int count) {
|
||||
* Transforms vx[0..3] IN PLACE (pre-draw_offset); returns 1 if it applied. */
|
||||
static int ws_nw_backdrop = 0;
|
||||
void gpu_ws_set_nw_backdrop(int on) { ws_nw_backdrop = on ? 1 : 0; }
|
||||
static int ws_nw_flat_backdrop = 0;
|
||||
void gpu_ws_set_nw_flat_backdrop(int on) { ws_nw_flat_backdrop = on ? 1 : 0; }
|
||||
int gpu_ws_nw_flat_backdrop_enabled(void) { return ws_nw_flat_backdrop; }
|
||||
static int ws_nw_backdrop_stretch_quad(int32_t *vx, const int32_t *vy) {
|
||||
if (!ws_nw_backdrop || !ws_native_wide_active()) return 0;
|
||||
int32_t extra = ws_nw_extra();
|
||||
|
||||
@@ -1118,14 +1118,20 @@ static void wide_target_end(GLint uXoff, GLint uXhalf) {
|
||||
|
||||
extern int psx_ws_prim_is_tagged(void); /* gpu.c: is the current GP0 prim sprite-tagged? */
|
||||
extern int psx_ws_prim_in_backdrop(void); /* gpu.c: is its source addr in the flower-field struct? */
|
||||
extern int gpu_ws_nw_flat_backdrop_enabled(void); /* gpu.c: per-title flat backdrop opt-in */
|
||||
|
||||
/* Per-prim gate: stretch this prim iff native-wide + feature on AND the prim's
|
||||
* source address is inside the flower-field backdrop data structure (precise —
|
||||
* excludes the 3D rock/foreground, which is untagged AND has narrow prims so the
|
||||
* earlier tag/narrow heuristic tore it). mode!=0 falls back to the old
|
||||
* tag+narrow heuristic for A/B. */
|
||||
static int bd_prim_gate(const int *xs, int n) {
|
||||
static int bd_prim_gate(const int *xs, int n, int textured) {
|
||||
if (g_wide_w <= 0 || !g_ws_bd_stretch_on) return 0;
|
||||
/* Some games draw their authored 4:3 sky/water as flat-colour polygons.
|
||||
* Stretch those only in the native-wide mirror: the canonical framebuffer
|
||||
* remains byte-for-byte 4:3, while the flat backdrop reaches the reveal
|
||||
* margins. Opt-in because flat foreground geometry is title-dependent. */
|
||||
if (!textured && gpu_ws_nw_flat_backdrop_enabled()) return 1;
|
||||
if (g_ws_bd_phase_mode != 0) return psx_ws_prim_in_backdrop(); /* default: precise address gate */
|
||||
/* mode 0: legacy tag+narrow heuristic (kept for comparison) */
|
||||
int native_w = g_wide_w - 2 * g_wide_off;
|
||||
@@ -1355,7 +1361,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
if (g_wide_cur && !s_wide_suppress && s_ws_ablate != 1 &&
|
||||
!(!g_ws_bd_stretch_on && mirror_geo_center_only(xs, n))) {
|
||||
int dx = wide_dx();
|
||||
s_bd_gate = bd_prim_gate(xs, n); /* flat prims are immediate -> gate per prim */
|
||||
s_bd_gate = bd_prim_gate(xs, n, 0); /* flat prims are immediate -> gate per prim */
|
||||
gl_perf_mirror_begin();
|
||||
wide_target_begin(dx, s_geo_uXoff, s_geo_uXhalf);
|
||||
wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
@@ -1420,7 +1426,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
* state goes in the vertex; only these keys force a new draw. */
|
||||
{
|
||||
int twx = s_tw_mask_x, twy = s_tw_mask_y, tox = s_tw_off_x, toy = s_tw_off_y;
|
||||
int gate = bd_prim_gate(xs, 3); /* backdrop-stretch gate is also a batch key */
|
||||
int gate = bd_prim_gate(xs, 3, 1); /* backdrop-stretch gate is also a batch key */
|
||||
/* STP draw-ORDER correctness. flush_tex_batch draws a batch in two passes
|
||||
* over the WHOLE batch (pass 1 = every prim's STP=0/opaque texels, pass 2
|
||||
* = every prim's STP=1/semi texels with the PSX blend). For overlapping
|
||||
|
||||
+13
-2
@@ -283,6 +283,7 @@ static int g_fmv_skip_end_total = 3;
|
||||
* movies never stream XA, so the default MDEC+XA detector misses them —
|
||||
* Tomba2's Whoopee Camp logo). Presentation-side fast-forward only. */
|
||||
static int g_fmv_skip_no_xa = 0;
|
||||
static int g_fmv_skip_no_xa_hold = 4;
|
||||
/* Low-latency present options (see [video] in config_loader). Measured on a
|
||||
* 60Hz box the dominant input->photon cost is NOT vsync at the swap (that
|
||||
* blocks ~tens of us) but that input is sampled ~one pacer-wait (~13.6ms)
|
||||
@@ -305,10 +306,12 @@ extern "C" uint32_t mdec_get_decode_count(void);
|
||||
* resolved. */
|
||||
const char *g_active_config_path = nullptr;
|
||||
extern "C" void debug_get_fmv_config(int *auto_skip, uint32_t *total_table,
|
||||
uint32_t *movie_id, const char **cfg_path) {
|
||||
uint32_t *movie_id, int *no_xa_hold,
|
||||
const char **cfg_path) {
|
||||
if (auto_skip) *auto_skip = g_auto_skip_fmv;
|
||||
if (total_table) *total_table = g_fmv_skip_total_table;
|
||||
if (movie_id) *movie_id = g_fmv_skip_movie_id;
|
||||
if (no_xa_hold) *no_xa_hold = g_fmv_skip_no_xa_hold;
|
||||
if (cfg_path) *cfg_path = g_active_config_path ? g_active_config_path : "(null)";
|
||||
}
|
||||
|
||||
@@ -1934,7 +1937,8 @@ static void sdl_vblank_present(void) {
|
||||
* satisfy the MDEC+XA detector; with the per-game opt-in, MDEC
|
||||
* activity alone qualifies. */
|
||||
int detected = mdec_decoding && (xa || g_fmv_skip_no_xa);
|
||||
if (detected) s_fmv_hold = 4;
|
||||
if (detected) s_fmv_hold = (!xa && g_fmv_skip_no_xa)
|
||||
? g_fmv_skip_no_xa_hold : 4;
|
||||
else if (s_fmv_hold > 0) s_fmv_hold--;
|
||||
fmv_skip_active = (s_fmv_hold > 0) && (xa || g_fmv_skip_no_xa);
|
||||
if (fmv_skip_active) {
|
||||
@@ -2486,6 +2490,7 @@ int main(int argc, char** argv) {
|
||||
if (gc.runtime.video_fmv_skip_end_total)
|
||||
g_fmv_skip_end_total = gc.runtime.video_fmv_skip_end_total;
|
||||
g_fmv_skip_no_xa = gc.runtime.video_fmv_skip_no_xa ? 1 : 0;
|
||||
g_fmv_skip_no_xa_hold = gc.runtime.video_fmv_skip_no_xa_hold;
|
||||
g_ws_anchor_addr = gc.ws_sprite_anchor_addr;
|
||||
g_ws_hud_sprt = gc.ws_hud_sprt_squash;
|
||||
/* [widescreen] full_2d — opt a pure-2D sprite game (MMX6) into the
|
||||
@@ -2511,6 +2516,12 @@ int main(int argc, char** argv) {
|
||||
gc.ws_nw_left_hud_packet_hi);
|
||||
/* [widescreen] nw_backdrop — stretch full-frame 2D sky backdrop. */
|
||||
gpu_ws_set_nw_backdrop(gc.ws_nw_backdrop ? 1 : 0);
|
||||
/* [widescreen] nw_flat_backdrop — stretch flat sky/backdrop prims
|
||||
* in the native-wide mirror, preserving the canonical 4:3 image. */
|
||||
gpu_ws_set_nw_flat_backdrop(gc.ws_nw_flat_backdrop ? 1 : 0);
|
||||
/* [widescreen] nw_phase_backdrop — stretch only the textured
|
||||
* backdrop phase emitted before shaded 3D foreground geometry. */
|
||||
gpu_ws_set_nw_phase_backdrop(gc.ws_nw_phase_backdrop ? 1 : 0);
|
||||
/* [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);
|
||||
|
||||
Reference in New Issue
Block a user