mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
widescreen: MMX6 2D true-FOV — full_2d opt-in + [widescreen.bg2d] BG widen
Pure-2D sprite games (Mega Man X6) render a true 4:3 field of view with no
overscan to reveal, so 16:9 requires making the engine draw more. Two parts:
- [widescreen] full_2d opts a pure-2D game into the native-wide present path
(it never emits the 3D sprite-tag hook the gameplay detector keys on, so
every in-game frame was misclassified as a 4:3 menu and pillarboxed).
- [widescreen.bg2d] is a gen-time hook on the per-layer background tile
renderer (MMX6 FUN_800270d0) at its column-count / start-tile-column /
start-screen-x instructions, routed through gpu.c psx_ws_mmx6_bg_{cols,
startcol,startx}. It draws LEFT=ceil(nw_offset/16) extra 16px columns on
BOTH sides so the loop fills the 16:9 reveal margins with real adjacent
stage, existing columns staying pixel-aligned with objects. Identity at
4:3 and in the engine's own 512 hi-res mode (so the title is untouched).
Tooling: wide_full debug command + sw_wide_dump_full dump the entire
native-wide compositor surface (both vertical-double-buffer y-bands and both
reveal margins) for diagnosis.
Known follow-ups: object screen-X cull widen (actors in the margins);
top/bottom flicker + stale-margin refresh on the vertical double-buffer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,16 @@ struct CodeGenConfig {
|
||||
// FOV (identity at 4:3). Overlay-resident; see config_loader.h. Empty=default.
|
||||
std::set<uint32_t> ws_backdrop_x_sites;
|
||||
|
||||
// Widescreen pure-2D background tile-loop widen ([widescreen.bg2d]). Three
|
||||
// instruction addresses in a 2D game's per-layer BG renderer (MMX6's
|
||||
// FUN_800270d0): the column-count load, the start tile-column mask, and the
|
||||
// start screen-x shift, rewritten through the gpu.c psx_ws_mmx6_bg_* helpers
|
||||
// so the loop draws the 16:9 reveal columns on both sides (identity at 4:3 /
|
||||
// 512 hi-res). 0 = unset. Main-EXE addresses; verified by opcode at gen time.
|
||||
uint32_t ws_bg2d_count_site = 0;
|
||||
uint32_t ws_bg2d_startcol_site = 0;
|
||||
uint32_t ws_bg2d_startx_site = 0;
|
||||
|
||||
// Persistent game-option init-store hooks ([persist_options] in
|
||||
// game_options.toml). Each entry is the PC of a boot-init sb/sh that writes a
|
||||
// config global's DEFAULT value; the store is rewritten to route the value
|
||||
|
||||
@@ -821,6 +821,48 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
|
||||
// overlay variant: addr is different code here — fall through to vanilla.
|
||||
}
|
||||
|
||||
// Widescreen pure-2D background tile-loop widen ([widescreen.bg2d]). MMX6's
|
||||
// per-layer BG renderer draws `count` 16px tile columns from a start column /
|
||||
// start screen-x derived from the camera scroll. Rewrite those three values
|
||||
// (via the gpu.c psx_ws_mmx6_bg_* helpers — identity at 4:3 / 512 hi-res) so
|
||||
// the loop draws the 16:9 reveal columns on both sides. Each site's opcode is
|
||||
// verified; a mismatch is a loud build error (main-EXE addresses).
|
||||
if (config_.ws_bg2d_count_site && addr == config_.ws_bg2d_count_site) {
|
||||
if (opcode == 0x09 || opcode == 0x0D) { // addiu / ori (li rt,imm)
|
||||
uint32_t rt = get_rt(instr);
|
||||
uint16_t imm = get_imm16_u(instr);
|
||||
return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_cols({});{}",
|
||||
reg_name(rt), (int)imm, comment);
|
||||
} else if (!config_.overlay_mode) {
|
||||
fmt::print(stderr, "ERROR: [widescreen.bg2d] count_site 0x{:08X} is not "
|
||||
"addiu/ori (opcode 0x{:02X})\n", addr, opcode);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
if (config_.ws_bg2d_startcol_site && addr == config_.ws_bg2d_startcol_site) {
|
||||
if (opcode == 0x0C) { // andi rt,rs,imm (start tile-column mask)
|
||||
uint32_t rs = get_rs(instr), rt = get_rt(instr);
|
||||
uint16_t imm = get_imm16_u(instr);
|
||||
return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_startcol((int)({} & 0x{:X}u));{}",
|
||||
reg_name(rt), reg_name(rs), imm, comment);
|
||||
} else if (!config_.overlay_mode) {
|
||||
fmt::print(stderr, "ERROR: [widescreen.bg2d] startcol_site 0x{:08X} is not "
|
||||
"andi (opcode 0x{:02X})\n", addr, opcode);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
if (config_.ws_bg2d_startx_site && addr == config_.ws_bg2d_startx_site) {
|
||||
if (opcode == 0x00 && (instr & 0x3F) == 0x03) { // sra rd,rt,sa (start screen-x)
|
||||
uint32_t rt = get_rt(instr), rd = get_rd(instr), sh = get_shamt(instr);
|
||||
return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_startx((int32_t){} >> {});{}",
|
||||
reg_name(rd), reg_name(rt), sh, comment);
|
||||
} else if (!config_.overlay_mode) {
|
||||
fmt::print(stderr, "ERROR: [widescreen.bg2d] startx_site 0x{:08X} is not "
|
||||
"sra (instr 0x{:08X})\n", addr, instr);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent game-option init store ([persist_options] in game_options.toml).
|
||||
// The site is the boot-init sb/sh that writes a config global's DEFAULT value
|
||||
// (Tomba MESSAGE / SOUND / VIBRATION / ADJUST SCREEN). Route the stored value
|
||||
@@ -2018,6 +2060,9 @@ std::string CodeGenerator::generate_file(
|
||||
ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* ws auto screen-x cull (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_mmx6_bg_cols(int base); /* ws 2D bg tile-loop widen: col count (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_mmx6_bg_startcol(int col); /* ws 2D bg tile-loop widen: start tile col (gpu.c) */\n";
|
||||
ss << "extern int psx_ws_mmx6_bg_startx(int x); /* ws 2D bg tile-loop widen: start screen-x (gpu.c) */\n";
|
||||
ss << "extern int psx_game_option_store(uint32_t addr, int val); /* persisted OPTION restore-at-init (game_options.c) */\n";
|
||||
ss << "extern uint32_t psx_ws_backdrop_value(uint32_t orig, int is_end, int window_cols); /* ws backdrop preload (gpu.c) */\n";
|
||||
ss << "extern void gte_ws_set_suppress(int on); /* widescreen far-backdrop un-squash (gte.cpp) */\n\n";
|
||||
|
||||
@@ -572,6 +572,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
std::vector<uint32_t> ws_sprite_tag_funcs;
|
||||
uint32_t ws_sprite_anchor_addr = 0;
|
||||
bool ws_hud_sprt_squash = false;
|
||||
bool ws_full_2d = false;
|
||||
if (cfg.contains("widescreen")) {
|
||||
const toml::value& ws = toml::find(cfg, "widescreen");
|
||||
if (ws.contains("sprite_tag_funcs")) {
|
||||
@@ -591,6 +592,8 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
config_path.string()));
|
||||
if (ws.contains("hud_sprt_squash"))
|
||||
ws_hud_sprt_squash = toml::find<bool>(ws, "hud_sprt_squash");
|
||||
if (ws.contains("full_2d"))
|
||||
ws_full_2d = toml::find<bool>(ws, "full_2d");
|
||||
}
|
||||
|
||||
// Optional [widescreen.cull] block — world-space draw-cull widening.
|
||||
@@ -616,6 +619,24 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional [widescreen.bg2d] block — pure-2D background tile-loop widen.
|
||||
uint32_t ws_bg2d_count_site = 0, ws_bg2d_startcol_site = 0, ws_bg2d_startx_site = 0;
|
||||
if (cfg.contains("widescreen")) {
|
||||
const toml::value& ws = toml::find(cfg, "widescreen");
|
||||
if (ws.contains("bg2d")) {
|
||||
const toml::value& bg = toml::find(ws, "bg2d");
|
||||
auto load1 = [&](const char* key) -> uint32_t {
|
||||
return bg.contains(key)
|
||||
? parse_hex(toml::find<std::string>(bg, key),
|
||||
fmt::format("widescreen.bg2d.{}", key))
|
||||
: 0u;
|
||||
};
|
||||
ws_bg2d_count_site = load1("count_site");
|
||||
ws_bg2d_startcol_site = load1("startcol_site");
|
||||
ws_bg2d_startx_site = load1("startx_site");
|
||||
}
|
||||
}
|
||||
|
||||
// Optional [widescreen.backdrop] block — parallax 2D backdrop screenX squash
|
||||
// (x_sites) + far-backdrop GTE un-squash (unsquash_funcs).
|
||||
std::vector<uint32_t> ws_backdrop_x_sites;
|
||||
@@ -665,6 +686,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
/*ws_backdrop_unsquash_funcs*/ ws_backdrop_unsquash_funcs,
|
||||
/*ws_auto_screen_x_cull*/ ws_auto_screen_x_cull,
|
||||
/*ws_auto_backdrop_preload*/ ws_auto_backdrop_preload,
|
||||
/*ws_full_2d*/ ws_full_2d,
|
||||
/*ws_bg2d_count_site*/ ws_bg2d_count_site,
|
||||
/*ws_bg2d_startcol_site*/ ws_bg2d_startcol_site,
|
||||
/*ws_bg2d_startx_site*/ ws_bg2d_startx_site,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -358,6 +358,30 @@ struct GameConfig {
|
||||
// are overlay-resident, so the overlay compile must see this (--ws-config).
|
||||
// 0 at 4:3 (native-wide inactive) => byte-identical. Off by default; regen.
|
||||
bool ws_auto_backdrop_preload = false;
|
||||
|
||||
// [widescreen] full_2d — pure-2D sprite game (e.g. Mega Man X6) that never
|
||||
// emits the sprite_tag hook the 3D detector relies on. When true, every
|
||||
// in-game frame is treated as "gameplay" so native-wide engages and the 2D
|
||||
// scene is presented widescreen (the framework's normal full-2D screens
|
||||
// pillarbox 4:3, which is wrong for a game that IS 2D end-to-end). Runtime-
|
||||
// only (read at startup; no codegen impact) — no regen required. The wider
|
||||
// field of view itself is supplied by [widescreen.cull]/engine hooks; this
|
||||
// flag only opts the title into the 2D widescreen present path. Off by
|
||||
// default; the env var PSX_WS_FORCE_2D=1 forces it on for testing.
|
||||
bool ws_full_2d = false;
|
||||
|
||||
// [widescreen.bg2d] — pure-2D background tile-loop widen (e.g. MMX6's
|
||||
// FUN_800270d0). Three instruction addresses in the per-layer BG renderer
|
||||
// whose column count and loop start are rewritten so the loop draws the
|
||||
// 16:9 reveal columns on both sides of the 320 view (see gpu.c
|
||||
// psx_ws_mmx6_bg_* helpers — identity at 4:3 / 512 hi-res mode). Regen-class.
|
||||
// count_site: the `li rt,21` column-count load (addiu/ori).
|
||||
// startcol_site: the `andi rt,rs,0x3f` start tile-column mask.
|
||||
// startx_site: the `sra rd,rt,sa` start screen-x.
|
||||
// 0 = unset (feature off). Verified by opcode at gen time.
|
||||
uint32_t ws_bg2d_count_site = 0;
|
||||
uint32_t ws_bg2d_startcol_site = 0;
|
||||
uint32_t ws_bg2d_startx_site = 0;
|
||||
};
|
||||
|
||||
// UserSettings — the launcher-written, user-editable override layer.
|
||||
|
||||
@@ -99,6 +99,8 @@ int main(int argc, char** argv) {
|
||||
bool ws_auto_screen_x_cull = false; // [widescreen.cull] auto_screen_x
|
||||
std::set<uint32_t> persist_init_sites; // [persist_options] init-store hooks (game_options.toml)
|
||||
bool ws_auto_backdrop_preload = false; // [widescreen.cull] auto_backdrop
|
||||
uint32_t ws_bg2d_count_site = 0, ws_bg2d_startcol_site = 0,
|
||||
ws_bg2d_startx_site = 0; // [widescreen.bg2d]
|
||||
std::filesystem::path out_dir = "generated";
|
||||
|
||||
if (!config_path.empty()) {
|
||||
@@ -116,6 +118,9 @@ int main(int argc, char** argv) {
|
||||
ws_backdrop_unsquash.insert(cfg.ws_backdrop_unsquash_funcs.begin(), cfg.ws_backdrop_unsquash_funcs.end());
|
||||
ws_auto_screen_x_cull = ws_auto_screen_x_cull || cfg.ws_auto_screen_x_cull;
|
||||
ws_auto_backdrop_preload = ws_auto_backdrop_preload || cfg.ws_auto_backdrop_preload;
|
||||
if (cfg.ws_bg2d_count_site) ws_bg2d_count_site = cfg.ws_bg2d_count_site;
|
||||
if (cfg.ws_bg2d_startcol_site) ws_bg2d_startcol_site = cfg.ws_bg2d_startcol_site;
|
||||
if (cfg.ws_bg2d_startx_site) ws_bg2d_startx_site = cfg.ws_bg2d_startx_site;
|
||||
// [persist_options] init-store hook sites live in a dedicated
|
||||
// game_options.toml next to game.toml (the game's own native OPTION
|
||||
// settings, kept separate from game.toml/settings.toml). Best-effort:
|
||||
@@ -685,6 +690,12 @@ int main(int argc, char** argv) {
|
||||
codegen_config.ws_auto_screen_x_cull = ws_auto_screen_x_cull;
|
||||
codegen_config.persist_init_store_sites = persist_init_sites;
|
||||
codegen_config.ws_auto_backdrop_preload = ws_auto_backdrop_preload;
|
||||
codegen_config.ws_bg2d_count_site = ws_bg2d_count_site;
|
||||
codegen_config.ws_bg2d_startcol_site = ws_bg2d_startcol_site;
|
||||
codegen_config.ws_bg2d_startx_site = ws_bg2d_startx_site;
|
||||
if (ws_bg2d_count_site || ws_bg2d_startcol_site || ws_bg2d_startx_site)
|
||||
fmt::print(" ws_bg2d 2D-background widen = ON (count=0x{:08X} startcol=0x{:08X} startx=0x{:08X})\n",
|
||||
ws_bg2d_count_site, ws_bg2d_startcol_site, ws_bg2d_startx_site);
|
||||
if (ws_auto_screen_x_cull)
|
||||
fmt::print(" ws_auto_screen_x_cull = ON (render-funnel FOV widening)\n");
|
||||
if (!persist_init_sites.empty())
|
||||
|
||||
@@ -95,6 +95,15 @@ void gpu_set_screen_kind(int kind);
|
||||
* (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);
|
||||
struct CPUState;
|
||||
void psx_ws_sprite_tag(struct CPUState* cpu);
|
||||
|
||||
|
||||
@@ -6204,6 +6204,47 @@ static void handle_wide_shot(int id, const char *json)
|
||||
id, path, W, H);
|
||||
}
|
||||
|
||||
/* wide_full: dump the ENTIRE active wide compositor surface (both double-buffer
|
||||
* y-bands, full width incl. both reveal margins) to a PNG. Unlike wide_shot
|
||||
* (which crops to the displayed band), this shows where the over-draw actually
|
||||
* lands across the whole surface. SW backend only (diagnostic). base_x defaults
|
||||
* to 0 (the common vertical-double-buffer origin). */
|
||||
static void handle_wide_full(int id, const char *json)
|
||||
{
|
||||
extern int sw_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x);
|
||||
int base_x = json_get_int(json, "base_x", 0);
|
||||
char path[512];
|
||||
if (!json_get_str(json, "path", path, sizeof(path)))
|
||||
strncpy(path, "psx_wide_full.png", sizeof(path) - 1);
|
||||
path[sizeof(path) - 1] = '\0';
|
||||
|
||||
int cap = 1024 * 1024 * 4; /* up to 4 Mpix (e.g. 426*512*S^2) */
|
||||
uint32_t *buf = (uint32_t *)malloc((size_t)cap * sizeof(uint32_t));
|
||||
if (!buf) { send_err(id, "alloc failed"); return; }
|
||||
int W = 0, H = 0;
|
||||
int n = sw_wide_dump_full(buf, cap, &W, &H, base_x);
|
||||
if (n <= 0) { free(buf); send_err(id, "no wide surface (SW backend? native-wide engaged?)"); return; }
|
||||
|
||||
uint8_t *rgb = (uint8_t *)malloc((size_t)W * H * 3);
|
||||
if (!rgb) { free(buf); send_err(id, "alloc failed"); return; }
|
||||
for (int y = 0; y < H; y++)
|
||||
for (int x = 0; x < W; x++) {
|
||||
uint32_t px = buf[(size_t)y * W + x];
|
||||
uint8_t *p = rgb + ((size_t)y * W + x) * 3;
|
||||
p[0] = (uint8_t)((px >> 16) & 0xFF);
|
||||
p[1] = (uint8_t)((px >> 8) & 0xFF);
|
||||
p[2] = (uint8_t)(px & 0xFF);
|
||||
}
|
||||
free(buf);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) { free(rgb); send_err(id, "cannot open file"); return; }
|
||||
int ok = png_write_rgb(f, rgb, (uint32_t)W, (uint32_t)H);
|
||||
free(rgb); fclose(f);
|
||||
if (!ok) { send_err(id, "png encode failed"); return; }
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"path\":\"%s\",\"width\":%d,\"height\":%d}",
|
||||
id, path, W, H);
|
||||
}
|
||||
|
||||
static void handle_vram_peek(int id, const char *json)
|
||||
{
|
||||
int x = json_get_int(json, "x", 0);
|
||||
@@ -9254,6 +9295,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "get_snapshots", handle_get_snapshots },
|
||||
{ "screenshot", handle_screenshot_file },
|
||||
{ "screenshot_file", handle_screenshot_file }, /* alias */
|
||||
{ "wide_full", handle_wide_full },
|
||||
{ "wide_shot", handle_wide_shot },
|
||||
{ "gpu_opcodes", handle_gpu_opcodes },
|
||||
{ "gpu_ring_stats", handle_gpu_ring_stats },
|
||||
|
||||
+47
-9
@@ -103,16 +103,19 @@ static int32_t ws_disp_w(void);
|
||||
* within the last couple of frames => the actor render funnel is running =>
|
||||
* gameplay. A menu/title/save screen draws no tagged prims. The 2-frame window
|
||||
* makes this frame-stable regardless of per-prim ordering within a frame. */
|
||||
/* Full-2D widescreen ([widescreen] full_2d). A pure 2D sprite game (e.g. MMX6)
|
||||
* never emits the sprite-tag hook the 3D gameplay detector keys on, so every
|
||||
* in-game frame would be misclassified as a "full-2D menu" and pillarboxed 4:3.
|
||||
* When this is set, treat every in-game frame as gameplay so native-wide engages
|
||||
* and the 2D scene presents widescreen. Set once from game.toml at widescreen-
|
||||
* engage (gpu_ws_set_full_2d); PSX_WS_FORCE_2D=1 forces it on for testing. */
|
||||
static int ws_full_2d = 0;
|
||||
void gpu_ws_set_full_2d(int on) { ws_full_2d = on ? 1 : 0; }
|
||||
|
||||
static int ws_game_mode(void) {
|
||||
/* EXPERIMENT (PSX_WS_FORCE_2D=1): force "gameplay" so native-wide engages on
|
||||
* an all-2D sprite game (MMX6) that never emits the sprite-tag hook. This is
|
||||
* the 2D "overscan reveal" path — the game over-draws past the 320 viewport
|
||||
* (clipped by the draw area), and native-wide's draw_offset shift + draw-area
|
||||
* widen + wide-mirror + widened present reveal it. Env-gated so it's regen-
|
||||
* free and off for tagged 3D games. */
|
||||
static int force = -1;
|
||||
if (force < 0) { const char *e = getenv("PSX_WS_FORCE_2D"); force = (e && e[0] == '1') ? 1 : 0; }
|
||||
if (force) return 1;
|
||||
static int env = -1;
|
||||
if (env < 0) { const char *e = getenv("PSX_WS_FORCE_2D"); env = (e && e[0] == '1') ? 1 : 0; }
|
||||
if (ws_full_2d || env) return 1;
|
||||
return (uint32_t)s_frame_count - ws_last_tag_stamp <= 2;
|
||||
}
|
||||
|
||||
@@ -191,6 +194,41 @@ int psx_ws_x_margin(void) {
|
||||
return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum;
|
||||
}
|
||||
|
||||
/* ---- MMX6 2D background tile-loop widen ([widescreen.bg2d]) ---------------
|
||||
* Mega Man X6 is a pure-2D sprite engine that renders only a 4:3 (320px) field
|
||||
* of view — there is no overscan to "reveal." Its per-layer background renderer
|
||||
* (FUN_800270d0) draws `count` 16px tile columns × 16 rows from a start tile
|
||||
* column / start screen-x derived from the camera scroll. To produce a TRUE
|
||||
* wider FOV we widen that loop so it draws extra columns on BOTH sides of the
|
||||
* 320 view, filling the 16:9 reveal margins with real adjacent stage. The three
|
||||
* helpers below are hooked at the renderer's count / start-col / start-x
|
||||
* instructions by the recompiler (gen-time). They are IDENTITY unless native-
|
||||
* wide is engaged on a ~320 screen, so 4:3 (and the engine's own 512 hi-res
|
||||
* mode, which already draws 33 columns) stay byte-identical.
|
||||
*
|
||||
* LEFT = per-side reveal in whole 16px tile columns = ceil(nw_offset / 16). The
|
||||
* loop start moves LEFT columns earlier (tile col -LEFT in the 64-col ring,
|
||||
* screen-x -LEFT*16) and the count grows by 2*LEFT, so existing columns keep
|
||||
* their screen positions (stay aligned with objects/player) while new columns
|
||||
* are prepended left and appended right. The 64-column tile ring (mask 0x3f) is
|
||||
* far wider than the visible window, so the prepended columns fetch the correct
|
||||
* already-streamed adjacent tiles. */
|
||||
static int ws_mmx6_left_cols(void) {
|
||||
if (!ws_native_wide_active()) return 0;
|
||||
/* Only the ~320 gameplay mode; the engine's 512 hi-res mode (title) draws
|
||||
* its own 33 columns and centres itself — never double-shift it. */
|
||||
if (ws_disp_w() > 384) return 0;
|
||||
int off = ws_nw_offset(); /* per-side reveal in screen px */
|
||||
if (off <= 0) return 0;
|
||||
return (off + 15) / 16; /* ceil to whole tile columns */
|
||||
}
|
||||
/* Column count: base (21) + the columns revealed on both sides. */
|
||||
int psx_ws_mmx6_bg_cols(int base) { return base + 2 * ws_mmx6_left_cols(); }
|
||||
/* Start tile column: LEFT earlier, wrapped in the 64-column ring (mask 0x3f). */
|
||||
int psx_ws_mmx6_bg_startcol(int col) { return (col - ws_mmx6_left_cols()) & 0x3f; }
|
||||
/* Start screen-x: LEFT*16 further left (more negative). */
|
||||
int psx_ws_mmx6_bg_startx(int x) { return x - ws_mmx6_left_cols() * 16; }
|
||||
|
||||
/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x),
|
||||
* called identically by the gcc emit, the sljit JIT, and the interpreter so every
|
||||
* overlay execution path widens the same way. sx = the GTE screen-X as loaded by
|
||||
|
||||
@@ -1610,3 +1610,22 @@ int sw_render_wide_display(uint32_t *out_pixels, int out_pitch, int base_x,
|
||||
}
|
||||
|
||||
int sw_wide_width(void) { return g_wide_w; }
|
||||
|
||||
/* Diagnostic: dump the ENTIRE active wide surface for base_x (full g_wide_w*scale
|
||||
* × VRAM_HEIGHT*scale — BOTH double-buffer y-bands, all margins) to ARGB. Lets a
|
||||
* probe see exactly where the over-draw lands, independent of the per-frame band
|
||||
* the present selects. *ow/*oh receive the surface dims. Returns pixels written,
|
||||
* or 0 if no surface for base_x / it won't fit in cap_pixels. */
|
||||
int sw_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x) {
|
||||
uint16_t *surf = NULL;
|
||||
for (int i = 0; i < WIDE_MAX_SURF; i++)
|
||||
if (g_wide_surf[i] && g_wide_base[i] == base_x) { surf = g_wide_surf[i]; break; }
|
||||
if (!surf || g_wide_w <= 0) return 0;
|
||||
int s = g_scale;
|
||||
int W = g_wide_w * s, H = VRAM_HEIGHT * s;
|
||||
if ((long long)W * H > cap_pixels) return 0;
|
||||
for (int i = 0; i < W * H; i++) out[i] = rgb555_to_argb(surf[i]);
|
||||
if (ow) *ow = W;
|
||||
if (oh) *oh = H;
|
||||
return W * H;
|
||||
}
|
||||
|
||||
@@ -1915,6 +1915,10 @@ int main(int argc, char** argv) {
|
||||
g_fmv_skip_end_total = gc.runtime.video_fmv_skip_end_total;
|
||||
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
|
||||
* widescreen present path. Applied to the GPU layer up front so the
|
||||
* ws engage at game entry classifies every frame as gameplay. */
|
||||
gpu_ws_set_full_2d(gc.ws_full_2d ? 1 : 0);
|
||||
/* Register the [widescreen.backdrop] store PCs so the dirty-RAM
|
||||
* interpreter applies the backdrop screenX squash on the interp
|
||||
* path (overlay backdrop handlers run interpreted when no cache
|
||||
|
||||
Reference in New Issue
Block a user