Merge remote-tracking branch 'origin/master' into spike/load-time-zero

This commit is contained in:
Matthew Stanley
2026-07-14 23:09:27 -07:00
26 changed files with 868 additions and 60 deletions
@@ -0,0 +1,10 @@
# Typed widescreen signed bounds
Source: [PSXRecomp PR #15](https://github.com/mstan/psxrecomp/pull/15),
commit [`f44f365eb91a30396f05f0f52529d4b01b026099`](https://github.com/douglasjv/psxrecomp-tweaks/commit/f44f365eb91a30396f05f0f52529d4b01b026099).
This branch isolates the opt-in guarded LUI transform across static generation,
captured overlays, SLJIT, and the dirty interpreter. The source behavior is
parked for testing with two known review questions: runtime registration clamps
at 64 sites and scaling currently includes the renderer cull margin. Textured
edges, debug tooling, and title-specific addresses are excluded.
@@ -0,0 +1,10 @@
# Typed native-wide textured edge expansion
Source: [PSXRecomp PR #15](https://github.com/mstan/psxrecomp/pull/15),
commit [`cab3946855e74918d3ce006062d3e1b843b155d6`](https://github.com/douglasjv/psxrecomp-tweaks/commit/cab3946855e74918d3ce006062d3e1b843b155d6).
This branch preserves the source feature for direct temperature testing. It is
opt-in. The known review question is software-renderer parity: the GL path
distinguishes outside-edge expansion from ordinary backdrop stretch, while the
software path currently consumes the gate as a boolean. Signed bounds and
title-specific configuration are excluded.
@@ -0,0 +1,22 @@
# PR #14 projection precision and perspective textures
Source: [kem0x PR #14](https://github.com/mstan/psxrecomp/pull/14), commit
[`ec889d6e5956fc4f34acfee41d87f30b24123b7f`](https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f).
This branch parks two coupled, opt-in visual experiments together:
- retain discarded GTE 16.16 screen fractions and use exact packed-coordinate
matches for supersampled software-renderer vertices;
- track exact SWC2 RAM provenance through generated, strict, dirty-RAM,
overlay, and fallback interpreters so textured world polygons can use
perspective-correct UV interpolation without guessing from screen position.
They share the same projection side cache, GP0 triangle preparation, and
software-renderer consumption path; splitting those internals would duplicate
most of the risk-bearing machinery. Both remain disabled until their existing
generic setters are called.
Excluded: all Web host exports and every title-specific state, action, address,
or presentation hook from the source commit. The separate 60 Hz presentation
experiment is parked on its own branch. Authorship is retained in the commit
trailer.
@@ -0,0 +1,17 @@
# PR #14 duplicate-frame presentation smoothing
Source: [kem0x PR #14](https://github.com/mstan/psxrecomp/pull/14), commit
[`ec889d6e5956fc4f34acfee41d87f30b24123b7f`](https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f).
This branch generalizes the source's presentation-only 30-to-60 Hz experiment:
when software output repeats an exact guest image for two vblanks, the next
distinct image is presented first as a temporal midpoint. A scene-cut heuristic
avoids blending unrelated images. Guest execution, SPU timing, and GPU-direct or
FMV paths remain untouched. It is off by default, exposed through a generic C
setter, a Web export, and `PSX_SMOOTH_60FPS`.
The smoother is mutually exclusive with the existing software frame-blend
masker. It is distinct from master's GL high-refresh interpolation path.
Excluded: every title name, address, state hook, and scripted action from the
source commit. Authorship is retained in the commit trailer.
+2
View File
@@ -11,6 +11,7 @@
#include "function_analysis.h"
#include "control_flow.h"
#include "../src/annotations.hpp"
#include "../src/config_loader.h"
namespace PSXRecomp {
@@ -45,6 +46,7 @@ struct CodeGenConfig {
// mismatch is EXPECTED (apply the transform only where the bytes match, skip
// it elsewhere) rather than a hard config error.
bool overlay_mode = false;
std::vector<PSXRecompV4::WidescreenSignedBoundSite> ws_signed_x_bound_sites;
std::string indent; // Indentation string (default: " ")
// Widescreen sprite-tag hooks ([widescreen] sprite_tag_funcs): functions
// that get a psx_ws_sprite_tag(cpu) callback emitted at entry, so the
+23 -4
View File
@@ -758,6 +758,21 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
std::string code;
for (const auto& site : config_.ws_signed_x_bound_sites) {
if ((site.address & 0x1FFFFFFFu) != (addr & 0x1FFFFFFFu)) continue;
if (instr != site.expected) {
if (config_.overlay_mode) continue;
fmt::print(stderr,
"ERROR: signed_x_bound expected 0x{:08X} at 0x{:08X}, found 0x{:08X}\n",
site.expected, addr, instr);
std::exit(1);
}
const int32_t vanilla = (int32_t)((instr & 0xFFFFu) << 16);
return fmt::format("{} = (uint32_t)psx_ws_player_x_bound((int32_t)0x{:08X});"
" /* typed native-wide signed X bound */{}",
reg_name(get_rt(instr)), (uint32_t)vanilla, comment);
}
// Widescreen automatic far-backdrop column PRELOAD ([widescreen.cull]
// auto_backdrop). At a detected window's START/END finalize, route the value
// through psx_ws_backdrop_value(orig, is_end): identity at 4:3, but in
@@ -1280,11 +1295,14 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
: fmt::format("cpu->gte_data[{}]", rt);
std::string swc2_store_pc = fmt::format("g_debug_last_store_pc = 0x{:08X}u; ", addr);
if (offset == 0) {
code = gte_stall + swc2_store_pc + fmt::format("cpu->write_word({}, {}); /* swc2 gte[{}], ({}) */",
reg_name(rs), value, rt, reg_name(rs));
code = gte_stall + swc2_store_pc + fmt::format(
"cpu->write_word({}, {}); gte_precision_store_word({}, {}); /* swc2 gte[{}], ({}) */",
reg_name(rs), value, reg_name(rs), rt, rt, reg_name(rs));
} else {
code = gte_stall + swc2_store_pc + fmt::format("cpu->write_word({} + {}, {}); /* swc2 gte[{}], {}({}) */",
reg_name(rs), offset, value, rt, offset, reg_name(rs));
code = gte_stall + swc2_store_pc + fmt::format(
"cpu->write_word({} + {}, {}); gte_precision_store_word({} + {}, {}); /* swc2 gte[{}], {}({}) */",
reg_name(rs), offset, value, reg_name(rs), offset, rt,
rt, offset, reg_name(rs));
}
}
break;
@@ -2666,6 +2684,7 @@ std::string CodeGenerator::generate_file(
ss << "extern void psx_ws_sprite_tag(CPUState* cpu); /* widescreen prim tag (gpu.c) */\n";
ss << "extern void psx_ws_mmx6_bg_stage_init(void); /* ws 2D stage reveal invalidation (gpu.c) */\n";
ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n";
ss << "extern int32_t psx_ws_player_x_bound(int32_t vanilla); /* typed gameplay X bound */\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_cull_slti(uint32_t sx, uint32_t imm); /* ws cull signed right edge (gpu.c) */\n";
ss << "extern int psx_ws_cull_bltz(uint32_t v); /* ws cull signed left edge (gpu.c) */\n";
+39
View File
@@ -855,6 +855,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
bool ws_clear_reveal = false;
bool ws_nw_flat_backdrop = false;
bool ws_nw_phase_backdrop = false;
bool ws_nw_textured_edges = false;
int ws_nw_textured_edge_scale = 0;
bool ws_nw_full_mirror = false;
std::vector<WidescreenSignedBoundSite> ws_signed_x_bound_sites;
bool ws_offered = true;
bool ws_ultrawide_offered = false;
// Optional [data_shards] block — memoized pure-function replay hooks.
@@ -977,6 +981,37 @@ GameConfig load_game_config(const fs::path& config_path_in) {
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("nw_textured_edges"))
ws_nw_textured_edges = toml::find<bool>(ws, "nw_textured_edges");
if (ws.contains("nw_textured_edge_scale")) {
ws_nw_textured_edge_scale = toml::find<int>(ws, "nw_textured_edge_scale");
if (ws_nw_textured_edge_scale != 0 &&
(ws_nw_textured_edge_scale < 100 || ws_nw_textured_edge_scale > 400))
throw std::runtime_error(fmt::format(
"{}: [widescreen] nw_textured_edge_scale must be 0 or in [100, 400]",
config_path.string()));
}
if (ws.contains("nw_full_mirror"))
ws_nw_full_mirror = toml::find<bool>(ws, "nw_full_mirror");
if (ws.contains("signed_x_bound")) {
std::set<uint32_t> seen;
for (const auto& item : toml::find<toml::array>(ws, "signed_x_bound")) {
WidescreenSignedBoundSite site;
site.address = parse_hex(toml::find<std::string>(item, "address"),
"widescreen.signed_x_bound.address");
site.expected = parse_hex(toml::find<std::string>(item, "expected"),
"widescreen.signed_x_bound.expected");
if ((site.expected >> 26) != 0x0Fu)
throw std::runtime_error(fmt::format(
"{}: [[widescreen.signed_x_bound]] expected must be LUI",
config_path.string()));
if (!seen.insert(site.address & 0x1FFFFFFFu).second)
throw std::runtime_error(fmt::format(
"{}: duplicate [[widescreen.signed_x_bound]] address 0x{:08X}",
config_path.string(), site.address));
ws_signed_x_bound_sites.push_back(site);
}
}
if (ws.contains("offer"))
ws_offered = toml::find<bool>(ws, "offer");
if (ws.contains("offer_ultrawide"))
@@ -1175,6 +1210,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
/*ws_clear_reveal*/ ws_clear_reveal,
/*ws_nw_flat_backdrop*/ ws_nw_flat_backdrop,
/*ws_nw_phase_backdrop*/ ws_nw_phase_backdrop,
/*ws_nw_textured_edges*/ ws_nw_textured_edges,
/*ws_nw_textured_edge_scale*/ ws_nw_textured_edge_scale,
/*ws_nw_full_mirror*/ ws_nw_full_mirror,
/*ws_signed_x_bound_sites*/ ws_signed_x_bound_sites,
/*ws_offered*/ ws_offered,
/*ws_ultrawide_offered*/ ws_ultrawide_offered,
/*ws_bg2d_count_site*/ ws_bg2d_count_site,
+19
View File
@@ -39,6 +39,10 @@ namespace PSXRecompV4 {
// digital — always present a digital pad (id 0x41); sticks disabled.
enum PadMode { PAD_MODE_HYBRID = 0, PAD_MODE_ANALOG = 1, PAD_MODE_DIGITAL = 2 };
struct WidescreenSignedBoundSite {
uint32_t address = 0;
uint32_t expected = 0; // guarded LUI instruction
};
// Parse/format a pad mode. pad_mode_from_string accepts "hybrid"/"analog"/
// "digital" (case-insensitive) and returns `fallback` for anything else.
int pad_mode_from_string(const std::string& s, int fallback);
@@ -607,6 +611,21 @@ struct GameConfig {
// off by default because draw ordering is title-specific.
bool ws_nw_phase_backdrop = false;
// Expand only textured polygon vertices that already lie beyond the
// canonical 4:3 boundary. Useful for finite arena/background meshes while
// leaving actors, HUD, sprites, and centre geometry untouched.
bool ws_nw_textured_edges = false;
int ws_nw_textured_edge_scale = 0; // percent; 0 = aspect-derived
// Render the complete wide mirror instead of splicing its centre from
// canonical VRAM. Required when edge-crossing polygon interpolation is
// transformed in the mirror.
bool ws_nw_full_mirror = false;
// [[widescreen.signed_x_bound]] guarded LUI sites whose signed Q16
// constants scale with the active native-wide field and remain identity in
// 4:3/menus/FMV. Shared by static codegen, overlay JIT, and interpreter.
std::vector<WidescreenSignedBoundSite> ws_signed_x_bound_sites;
// [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):
+6
View File
@@ -133,6 +133,7 @@ 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)
std::vector<PSXRecompV4::RecompilerPatch> instruction_patches;
std::vector<PSXRecompV4::WidescreenSignedBoundSite> ws_signed_x_bound_sites;
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,
@@ -179,6 +180,7 @@ int main(int argc, char** argv) {
if (cfg.ws_bg2d_bufbase_site) ws_bg2d_bufbase_site = cfg.ws_bg2d_bufbase_site;
if (cfg.ws_bg2d_cap_site) ws_bg2d_cap_site = cfg.ws_bg2d_cap_site;
if (cfg.ws_bg2d_init_func) ws_bg2d_init_func = cfg.ws_bg2d_init_func;
ws_signed_x_bound_sites = cfg.ws_signed_x_bound_sites;
// [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:
@@ -252,6 +254,9 @@ int main(int argc, char** argv) {
ws_auto_backdrop_preload = ws_auto_backdrop_preload || wscfg.ws_auto_backdrop_preload;
PSXRecompV4::merge_recompiler_patches(
instruction_patches, wscfg.recompiler_patches);
ws_signed_x_bound_sites.insert(ws_signed_x_bound_sites.end(),
wscfg.ws_signed_x_bound_sites.begin(),
wscfg.ws_signed_x_bound_sites.end());
if (wscfg.ws_bg2d_init_func) ws_bg2d_init_func = wscfg.ws_bg2d_init_func;
fmt::print("ws-config: {} (backdrop_x sites={}, unsquash funcs={})\n",
ws_config_path.string(), ws_backdrop_x.size(), ws_backdrop_unsquash.size());
@@ -818,6 +823,7 @@ int main(int argc, char** argv) {
codegen_config.emit_line_numbers = true;
codegen_config.split_mid_function_targets = !overlay_mode;
codegen_config.overlay_mode = overlay_mode;
codegen_config.ws_signed_x_bound_sites = ws_signed_x_bound_sites;
codegen_config.ws_sprite_tag_funcs = ws_tag_funcs;
codegen_config.data_shard_funcs = ds_funcs;
codegen_config.vsync_query_hle_funcs = vsync_query_hle_funcs;
+8 -4
View File
@@ -1386,13 +1386,17 @@ TranslateResult StrictTranslator::translate(const PSXRecomp::DecodedInstruction&
if (offset == 0) {
r.c_code = gte_stall + fmt::format(
"g_debug_last_store_pc = 0x{:08X}u; "
"cpu->write_word(cpu->gpr[{}], {});",
d.address, static_cast<int>(rs), value);
"cpu->write_word(cpu->gpr[{}], {}); "
"gte_precision_store_word(cpu->gpr[{}], {});",
d.address, static_cast<int>(rs), value,
static_cast<int>(rs), static_cast<int>(rt));
} else {
r.c_code = gte_stall + fmt::format(
"g_debug_last_store_pc = 0x{:08X}u; "
"cpu->write_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {});",
d.address, static_cast<int>(rs), static_cast<int>(offset), value);
"cpu->write_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {}); "
"gte_precision_store_word((uint32_t)((int32_t)cpu->gpr[{}] + ({})), {});",
d.address, static_cast<int>(rs), static_cast<int>(offset), value,
static_cast<int>(rs), static_cast<int>(offset), static_cast<int>(rt));
}
r.comment = fmt::format("swc2 gte[{}], {}({})",
rt, static_cast<int>(offset), gpr_name(rs));
+2
View File
@@ -33,6 +33,7 @@ enum {
SLJIT_HLP_CALL, /* psx_sljit_call — jal/jalr call contract */
SLJIT_HLP_WS_CULL_SLTI, /* psx_ws_cull_slti — signed funnel right edge (appended) */
SLJIT_HLP_WS_CULL_BLTZ, /* psx_ws_cull_bltz — signed funnel left edge (appended) */
SLJIT_HLP_WS_PLAYER_X_BOUND, /* typed native-wide signed gameplay bound */
SLJIT_HLP_COUNT
};
@@ -189,6 +190,7 @@ extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg);
extern uint32_t gte_read_ctrl(CPUState* cpu, uint8_t reg);
extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val);
extern void gte_write_ctrl(CPUState* cpu, uint8_t reg, uint32_t val);
extern void gte_precision_store_word(uint32_t addr, uint8_t reg);
/* ============================================================================
* Dispatch call contract (Bug D / wild-return family fix)
+6
View File
@@ -145,6 +145,12 @@ void gpu_ws_set_explicit_cull_sites(const uint32_t *bias, int nbias,
const uint32_t *slti, int nslti);
int psx_ws_is_cull_bias_site(uint32_t pc);
int psx_ws_is_cull_slti_site(uint32_t pc);
/* Scale a signed Q16 horizontal gameplay limit into the active native-wide
* game field. Identity at 4:3 / menus / FMV. */
int32_t psx_ws_player_x_bound(int32_t vanilla);
void gpu_ws_set_signed_x_bound_sites(const uint32_t *addresses,
const uint32_t *expected, int count);
int psx_ws_is_signed_x_bound_site(uint32_t pc, uint32_t instr);
/* 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
+4
View File
@@ -67,6 +67,10 @@ int gl_renderer_present_wide_fbo(int disp_x, int disp_y, int disp_h, int linear)
* for the widescreen field-of-view hack. */
void gl_renderer_set_display_aspect(int num, int den);
/* Select full native-wide mirror rendering instead of the centre-splice fast
* path. Textured edge expansion needs the complete mirror surface. */
void gl_renderer_set_wide_fast(int on);
void gl_renderer_shutdown(void);
/* Diagnostics (debug server): read GPU-side VRAM without touching the CPU
+14
View File
@@ -22,6 +22,20 @@ void sw_renderer_init(uint16_t* vram);
void sw_renderer_set_scale(int scale);
int sw_renderer_scale(void);
/* Visual-only high-resolution vertex override for the next triangle. Values
* are signed 16.16 screen coordinates; native VRAM still uses the integer GP0
* positions, while the supersampled mirror consumes these fractions. */
void sw_set_precise_triangle(int enabled,
int32_t x0, int32_t y0,
int32_t x1, int32_t y1,
int32_t x2, int32_t y2);
/* Perspective-correct UV override for the next triangle. q values are the
* homogeneous interpolation weights associated with each screen vertex. */
void sw_set_perspective_triangle(int enabled,
float q0, float q1, float q2);
uint32_t sw_perspective_triangle_count(void);
/* Texture filtering. 0 = nearest (native PSX look, default), 1 = bilinear
* (smooths textures/2D backgrounds; blends in RGB after the CLUT lookup,
* transparency-aware). Independent of supersampling. */
+1 -1
View File
@@ -94,7 +94,7 @@
* 4: continuation-passing (PSX_CPS, RECURSION_BUG.md §25) — overlay funcs
* tail-transfer + carry an entry-switch; the loader routes continuations.
* Fresh namespace so CPS DLLs never reuse a legacy (unit-model) cg3 DLL. */
#define PSX_OVERLAY_CODEGEN_VER 4
#define PSX_OVERLAY_CODEGEN_VER 5
/* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen
* sources). Folded into the cache PATH next to cg<N> (gcc/<arch-abi>/cg<N>_<hash>/)
+17 -4
View File
@@ -1911,10 +1911,23 @@ static void exec_command(uint8_t cmd) {
if (param_count >= 1 && param_fifo[0] != 0)
requested_track = bcd_to_bin(param_fifo[0]);
if (!start_cdda_playback(requested_track)) {
response_push(stat_reg | CDSTAT_ERROR);
response_push(0x10); /* invalid track / position */
set_irq(CDIRQ_ERROR);
break;
if (requested_track == 0) {
/* Some games probe Play from a data-track SetLoc before
* selecting their real audio track. The previous CD model
* acknowledged that command and entered a silent PLAY
* state; rejecting it makes Tomba 2 reset and retry its CD
* initialization forever. Preserve that compatibility
* behavior without feeding data sectors to the SPU. */
stop_read_stream();
cdda_data_end_pending = 0;
stat_reg = (stat_reg & ~(CDSTAT_SEEK | CDSTAT_READ)) |
CDSTAT_MOTOR | CDSTAT_PLAY;
} else {
response_push(stat_reg | CDSTAT_ERROR);
response_push(0x10); /* invalid explicit track */
set_irq(CDIRQ_ERROR);
break;
}
}
}
response_push(stat_reg);
+5 -1
View File
@@ -1575,7 +1575,10 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
cpu->gpr[0] = 0;
return 0;
case 0x0F: /* LUI rt, imm */
cpu->gpr[rt] = imm << 16;
if (psx_ws_is_signed_x_bound_site(pc, insn))
cpu->gpr[rt] = (uint32_t)psx_ws_player_x_bound((int32_t)(imm << 16));
else
cpu->gpr[rt] = imm << 16;
cpu->gpr[0] = 0;
return 0;
case 0x10: { /* COP0 */
@@ -1736,6 +1739,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
psx_gte_stall(cpu); /* COP2 reg read stalls to GTE completion */
#endif
cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt));
gte_precision_store_word(addr, (uint8_t)rt);
return 0;
}
default:
+171 -8
View File
@@ -347,6 +347,35 @@ int psx_ws_x_margin(void) {
return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum;
}
int32_t psx_ws_player_x_bound(int32_t vanilla)
{
const int margin = psx_ws_x_margin();
if (margin <= 0) return vanilla;
return (int32_t)(((int64_t)vanilla * (320 + 2 * margin)) / 320);
}
#define WS_SIGNED_BOUND_MAX 64
static uint32_t ws_signed_bound_addr[WS_SIGNED_BOUND_MAX];
static uint32_t ws_signed_bound_expected[WS_SIGNED_BOUND_MAX];
static int ws_signed_bound_count = 0;
void gpu_ws_set_signed_x_bound_sites(const uint32_t *addresses,
const uint32_t *expected, int count) {
if (count < 0) count = 0;
if (count > WS_SIGNED_BOUND_MAX) count = WS_SIGNED_BOUND_MAX;
ws_signed_bound_count = count;
for (int i = 0; i < count; i++) {
ws_signed_bound_addr[i] = addresses[i] & 0x1FFFFFFFu;
ws_signed_bound_expected[i] = expected[i];
}
}
int psx_ws_is_signed_x_bound_site(uint32_t pc, uint32_t instr) {
const uint32_t phys = pc & 0x1FFFFFFFu;
for (int i = 0; i < ws_signed_bound_count; i++)
if (ws_signed_bound_addr[i] == phys && ws_signed_bound_expected[i] == instr)
return 1;
return 0;
}
/* ---- Capcom 2D background tile-loop widen ([widescreen.bg2d]) --------------
* Mega Man X5/X6 use 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
@@ -1185,6 +1214,12 @@ static int ws_tagged_anchor(int32_t *out_ax) {
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; }
static int ws_nw_textured_edges = 0;
int g_ws_tex_edge_pct = 0;
void gpu_ws_set_nw_textured_edges(int on, int scale_pct) {
ws_nw_textured_edges = on ? 1 : 0;
g_ws_tex_edge_pct = scale_pct;
}
/* 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;
@@ -1270,6 +1305,11 @@ 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_textured_edges) {
uint32_t op = (gp0_cmd_buf[0] >> 24) & 0xFFu;
if (op >= 0x20u && op <= 0x3Fu && (op & 0x04u))
return 2; /* GL gate: expand only vertices beyond canonical edges */
}
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
@@ -2042,6 +2082,75 @@ static void parse_vertex(uint32_t word, int32_t* x, int32_t* y) {
*y = sign_extend((word >> 16) & 0x7FFu, 11);
}
extern int gte_geometry_correction_lookup(uint32_t packed,
int32_t *x16, int32_t *y16);
extern int gte_geometry_correction_enabled(void);
static int s_texture_correction_enabled = 0;
extern void gte_precision_tracking_set(int enabled);
extern int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16, uint16_t *z);
void gpu_texture_correction_set(int enabled) {
s_texture_correction_enabled = enabled ? 1 : 0;
gte_precision_tracking_set(enabled);
}
uint32_t gpu_texture_correction_hits(void) {
return sw_perspective_triangle_count();
}
/* Match all three GP0 positions to recent GTE projections. Requiring a full
* triangle match prevents screen-space HUD/sprites that happen to share one
* coordinate from receiving world-geometry correction. The integer delta
* folds in draw offsets and any widescreen adjustment already applied. */
static void prepare_precise_triangle(uint32_t w0, uint32_t w1, uint32_t w2,
const int32_t vx[3], const int32_t vy[3]) {
uint32_t words[3] = { w0, w1, w2 };
int32_t fx[3], fy[3];
const int geometry_enabled = gte_geometry_correction_enabled();
sw_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
if (!geometry_enabled) {
sw_set_precise_triangle(0, 0,0, 0,0, 0,0);
return;
}
for (int i = 0; i < 3; i++) {
int32_t raw_x, raw_y;
parse_vertex(words[i], &raw_x, &raw_y);
if (!gte_geometry_correction_lookup(words[i], &fx[i], &fy[i])) {
sw_set_precise_triangle(0, 0,0, 0,0, 0,0);
return;
}
fx[i] = (int32_t)((int64_t)fx[i] +
(int64_t)(vx[i] - raw_x) * 65536);
fy[i] = (int32_t)((int64_t)fy[i] +
(int64_t)(vy[i] - raw_y) * 65536);
}
sw_set_precise_triangle(1, fx[0],fy[0], fx[1],fy[1], fx[2],fy[2]);
}
/* Enable perspective UVs only when every position word came from an exact
* SWC2 projection store at that same DMA packet address. This preserves the
* association through ordering-table reordering and rejects CPU-built UI. */
static void prepare_texture_triangle(int i0, int i1, int i2) {
sw_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f);
if (!s_texture_correction_enabled || gp0_cmd_source_addr == 0xFFFFFFFFu)
return;
int indices[3] = { i0, i1, i2 };
uint16_t z[3];
for (int i = 0; i < 3; i++) {
uint32_t addr = (gp0_cmd_source_addr + (uint32_t)indices[i] * 4u) & 0x1FFFFCu;
if (!gte_precision_load_word(addr, gp0_cmd_buf[indices[i]], NULL, NULL, &z[i]) ||
z[i] == 0)
return;
}
float q[3] = { 1.0f / (float)z[0], 1.0f / (float)z[1], 1.0f / (float)z[2] };
float qmax = q[0];
if (q[1] > qmax) qmax = q[1];
if (q[2] > qmax) qmax = q[2];
if (qmax <= 0.0f) return;
sw_set_perspective_triangle(1, q[0] / qmax, q[1] / qmax, q[2] / qmax);
}
/* Write a single pixel to VRAM with draw area clipping and mask bit handling */
static void raster_pixel(int32_t x, int32_t y, uint16_t color) {
if (x < (int32_t)draw_area_left || x > (int32_t)draw_area_right) return;
@@ -2150,6 +2259,8 @@ static void gp0_exec_mono_tri(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[2], gp0_cmd_buf[3],
vx, vy);
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
}
@@ -2192,10 +2303,20 @@ static void gp0_exec_mono_quad(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[2],
gp0_cmd_buf[3], tx, ty);
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[3], gp0_cmd_buf[2],
gp0_cmd_buf[4], tx, ty);
gr_draw_flat_triangle(vx[2], vy[2], vx[1], vy[1], vx[3], vy[3], color);
}
}
/* Execute shaded triangle (GP0 0x30-0x33) — Gouraud shaded */
@@ -2215,6 +2336,8 @@ static void gp0_exec_shaded_tri(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3], gp0_cmd_buf[5],
vx, vy);
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
@@ -2255,14 +2378,24 @@ static void gp0_exec_shaded_quad(void) {
}
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3],
gp0_cmd_buf[5], tx, ty);
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[5], gp0_cmd_buf[3],
gp0_cmd_buf[7], tx, ty);
gr_draw_gouraud_triangle(vx[2], vy[2], c[2],
vx[1], vy[1], c[1],
vx[3], vy[3], c[3]);
}
}
/* Helper: build texpage word from GPU state for SW renderer.
@@ -2328,6 +2461,9 @@ static void gp0_exec_textured_tri(void) {
}
setup_textured_draw(color24, semi_trans, raw_texture);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3], gp0_cmd_buf[5],
vx, vy);
prepare_texture_triangle(1, 3, 5);
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -2402,16 +2538,28 @@ static void gp0_exec_textured_quad(void) {
}
}
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[3],
gp0_cmd_buf[5], tx, ty);
prepare_texture_triangle(1, 3, 5);
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
clut_x, clut_y, tpage);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[5], gp0_cmd_buf[3],
gp0_cmd_buf[7], tx, ty);
prepare_texture_triangle(5, 3, 7);
gr_draw_textured_triangle(vx[2], vy[2], u[2], v[2],
vx[1], vy[1], u[1], v[1],
vx[3], vy[3], u[3], v[3],
clut_x, clut_y, tpage);
}
}
/* Execute shaded textured triangle (GP0 0x34-0x37) */
@@ -2446,6 +2594,9 @@ static void gp0_exec_shaded_textured_tri(void) {
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[4], gp0_cmd_buf[7],
vx, vy);
prepare_texture_triangle(1, 4, 7);
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
@@ -2489,16 +2640,28 @@ static void gp0_exec_shaded_textured_quad(void) {
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
if (!rej_a)
if (!rej_a) {
int32_t tx[3] = { vx[0], vx[1], vx[2] };
int32_t ty[3] = { vy[0], vy[1], vy[2] };
prepare_precise_triangle(gp0_cmd_buf[1], gp0_cmd_buf[4],
gp0_cmd_buf[7], tx, ty);
prepare_texture_triangle(1, 4, 7);
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
clut_x, clut_y, tpage, raw_texture);
if (!rej_b)
}
if (!rej_b) {
int32_t tx[3] = { vx[2], vx[1], vx[3] };
int32_t ty[3] = { vy[2], vy[1], vy[3] };
prepare_precise_triangle(gp0_cmd_buf[7], gp0_cmd_buf[4],
gp0_cmd_buf[10], tx, ty);
prepare_texture_triangle(7, 4, 10);
gr_draw_shaded_textured_triangle(vx[2], vy[2], u[2], v[2], c[2],
vx[1], vy[1], u[1], v[1], c[1],
vx[3], vy[3], u[3], v[3], c[3],
clut_x, clut_y, tpage, raw_texture);
}
}
/* Execute mono line (GP0 0x40-0x47) — Bresenham */
+21 -5
View File
@@ -754,7 +754,12 @@ static const char *GEO_VS =
"uniform float u_xcenter;/* stretch centre in VRAM px; 0 canonical */\n"
"noperspective out vec4 v_col;\n"
"void main(){ v_col = a_col;\n"
" float xb = (a_pos.x - u_xcenter)*u_xscale + u_xcenter;\n"
" float xb = a_pos.x;\n"
" if (u_xscale < 0.0) {\n"
" float s = -u_xscale; float h = u_xhalf / s;\n"
" float l = u_xcenter - h, r = u_xcenter + h;\n"
" if (xb < l) xb = l + (xb-l)*s; else if (xb > r) xb = r + (xb-r)*s;\n"
" } else xb = (xb - u_xcenter)*u_xscale + u_xcenter;\n"
" gl_Position = vec4((xb+u_shift+u_xoff)/u_xhalf - 1.0, (a_pos.y+u_shift)/256.0 - 1.0, 0.0, 1.0); }\n";
static const char *GEO_FS =
"#version 330\n"
@@ -798,7 +803,12 @@ static const char *TEX_VS =
" v_limits = ivec4(floor(a_limits + 0.5));\n"
" /* u_shift: align GL's center-sample grid with the PS1 integer grid (see\n"
" * GEO_VS) so interpolated uv at a fragment equals the PS1 DDA value. */\n"
" float xb = (a_pos.x - u_xcenter)*u_xscale + u_xcenter;\n"
" float xb = a_pos.x;\n"
" if (u_xscale < 0.0) {\n"
" float s = -u_xscale; float h = u_xhalf / s;\n"
" float l = u_xcenter - h, r = u_xcenter + h;\n"
" if (xb < l) xb = l + (xb-l)*s; else if (xb > r) xb = r + (xb-r)*s;\n"
" } else xb = (xb - u_xcenter)*u_xscale + u_xcenter;\n"
" gl_Position = vec4((xb+u_shift+u_xoff)/u_xhalf - 1.0, (a_pos.y+u_shift)/256.0 - 1.0, 0.0, 1.0); }\n";
static const char *TEX_FS =
"#version 330\n"
@@ -1370,13 +1380,19 @@ static int mirror_geo_center_only(const int *xs, int n) {
/* Set / clear the 2D-backdrop x-stretch for a wide-mirror draw, per the current
* gate (s_bd_gate, set by the caller from bd_prim_gate / the batch gate). */
static void wide_set_bd_scale(GLint uScale, GLint uCenter) {
static void wide_set_bd_scale(GLint uScale, GLint uCenter) {
extern int g_ws_tex_edge_pct;
float scale = 1.0f, center = 0.0f;
if (s_bd_gate && g_ws_bd_stretch_on && g_wide_w > 0) {
int native_w = g_wide_w - 2 * g_wide_off;
if (native_w > 0) {
scale = g_ws_bd_stretch_pct > 0 ? (float)g_ws_bd_stretch_pct / 100.0f
: (float)g_wide_w / (float)native_w;
scale = g_ws_bd_stretch_pct > 0 ? (float)g_ws_bd_stretch_pct / 100.0f
: (float)g_wide_w / (float)native_w;
if (s_bd_gate == 2) {
scale = g_ws_tex_edge_pct > 0
? (float)g_ws_tex_edge_pct / 100.0f : scale;
scale = -scale; /* shader: expand only beyond canonical edges */
}
center = (float)g_wide_cur_base + (float)native_w / 2.0f;
}
}
+149 -25
View File
@@ -51,6 +51,11 @@ static uint16_t *g_hr = NULL;
static int g_scale = 1;
static int g_hr_w = VRAM_WIDTH;
static int g_hr_h = VRAM_HEIGHT;
static int g_precise_valid = 0;
static int32_t g_precise_x16[3], g_precise_y16[3];
static int g_perspective_valid = 0;
static float g_perspective_q[3];
static uint32_t g_perspective_triangles = 0;
/* ---- Native-wide compositor (separate present surfaces) ------------------
* Canonical VRAM stays faithful. For an opted-in wide game we ADDITIONALLY
@@ -490,6 +495,49 @@ void sw_renderer_set_scale(int scale) {
int sw_renderer_scale(void) { return g_scale; }
void sw_set_precise_triangle(int enabled,
int32_t x0, int32_t y0,
int32_t x1, int32_t y1,
int32_t x2, int32_t y2) {
g_precise_valid = enabled ? 1 : 0;
g_precise_x16[0] = x0; g_precise_y16[0] = y0;
g_precise_x16[1] = x1; g_precise_y16[1] = y1;
g_precise_x16[2] = x2; g_precise_y16[2] = y2;
}
void sw_set_perspective_triangle(int enabled,
float q0, float q1, float q2) {
g_perspective_valid = enabled && q0 > 0.0f && q1 > 0.0f && q2 > 0.0f;
g_perspective_q[0] = q0;
g_perspective_q[1] = q1;
g_perspective_q[2] = q2;
if (g_perspective_valid) g_perspective_triangles++;
}
uint32_t sw_perspective_triangle_count(void) {
return g_perspective_triangles;
}
static inline int precise_scaled(int axis, int vertex, int fallback, int scale) {
if (!g_precise_valid) return fallback * scale;
int64_t fixed = axis ? g_precise_y16[vertex] : g_precise_x16[vertex];
int64_t scaled = fixed * scale;
if (scaled >= 0) return (int)((scaled + 0x8000) >> 16);
return -(int)((-scaled + 0x8000) >> 16);
}
static inline int precise_wide_x(int vertex, int fallback, int scale,
int dx, const WideBd *bd) {
if (g_precise_valid)
return precise_scaled(0, vertex, fallback, scale) + dx * scale;
return (wide_bd_x(bd, fallback) + dx) * scale;
}
static inline void precise_consumed(void) {
g_precise_valid = 0;
g_perspective_valid = 0;
}
void sw_set_texture_filter(int bilinear) { g_texture_filter = bilinear ? 1 : 0; }
int sw_texture_filter(void) { return g_texture_filter; }
@@ -687,14 +735,21 @@ void sw_draw_flat_triangle(int x0, int y0, int x1, int y1,
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_flat_triangle(&hr, x0*s, y0*s, x1*s, y1*s, x2*s, y2*s, color);
raster_flat_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s),
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s),
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), color);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_flat_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, (wide_bd_x(&bd,x1)+dx)*s, y1*s, (wide_bd_x(&bd,x2)+dx)*s, y2*s, color);
raster_flat_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s),
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s),
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), color);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
@@ -801,14 +856,21 @@ void sw_draw_gouraud_triangle(int x0, int y0, uint16_t c0,
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_gouraud_triangle(&hr, x0*s, y0*s, c0, x1*s, y1*s, c1, x2*s, y2*s, c2);
raster_gouraud_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), c0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), c1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), c2);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_gouraud_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, c0, (wide_bd_x(&bd,x1)+dx)*s, y1*s, c1, (wide_bd_x(&bd,x2)+dx)*s, y2*s, c2);
raster_gouraud_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), c0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), c1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), c2);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
@@ -820,22 +882,27 @@ static void raster_textured_triangle(const RTarget *t,
int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
uint16_t texpage,
int perspective,
float q0, float q1, float q2) {
/* Sort by Y, keeping UV in sync */
if (y0 > y1) {
int tt;
tt=x0; x0=x1; x1=tt; tt=y0; y0=y1; y1=tt;
tt=u0; u0=u1; u1=tt; tt=v0; v0=v1; v1=tt;
float tq=q0; q0=q1; q1=tq;
}
if (y0 > y2) {
int tt;
tt=x0; x0=x2; x2=tt; tt=y0; y0=y2; y2=tt;
tt=u0; u0=u2; u2=tt; tt=v0; v0=v2; v2=tt;
float tq=q0; q0=q2; q2=tq;
}
if (y1 > y2) {
int tt;
tt=x1; x1=x2; x2=tt; tt=y1; y1=y2; y2=tt;
tt=u1; u1=u2; u2=tt; tt=v1; v1=v2; v2=tt;
float tq=q1; q1=q2; q2=tq;
}
int dy_total = y2 - y0;
@@ -860,13 +927,23 @@ static void raster_textured_triangle(const RTarget *t,
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
float qa = q0 + (q2 - q0) * alpha;
float uqa = (float)u0 * q0 + ((float)u2 * q2 - (float)u0 * q0) * alpha;
float vqa = (float)v0 * q0 + ((float)v2 * q2 - (float)v0 * q0) * alpha;
float ub, vb;
float qb, uqb, vqb;
if (second_half) {
ub = u1 + (float)(u2 - u1) * beta;
vb = v1 + (float)(v2 - v1) * beta;
qb = q1 + (q2 - q1) * beta;
uqb = (float)u1 * q1 + ((float)u2 * q2 - (float)u1 * q1) * beta;
vqb = (float)v1 * q1 + ((float)v2 * q2 - (float)v1 * q1) * beta;
} else {
ub = u0 + (float)(u1 - u0) * beta;
vb = v0 + (float)(v1 - v0) * beta;
qb = q0 + (q1 - q0) * beta;
uqb = (float)u0 * q0 + ((float)u1 * q1 - (float)u0 * q0) * beta;
vqb = (float)v0 * q0 + ((float)v1 * q1 - (float)v0 * q0) * beta;
}
if (xa > xb) {
@@ -874,6 +951,9 @@ static void raster_textured_triangle(const RTarget *t,
float tf;
tf = ua; ua = ub; ub = tf;
tf = va; va = vb; vb = tf;
tf = qa; qa = qb; qb = tf;
tf = uqa; uqa = uqb; uqb = tf;
tf = vqa; vqa = vqb; vqb = tf;
}
int span = xb - xa;
@@ -886,6 +966,13 @@ static void raster_textured_triangle(const RTarget *t,
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
if (perspective) {
float q = qa + (qb - qa) * t_val;
if (q > 1.0e-12f) {
fu = (uqa + (uqb - uqa) * t_val) / q;
fv = (vqa + (vqb - vqa) * t_val) / q;
}
}
uint16_t texel = g_texture_filter
? texel_fetch_bilinear(fu, fv, texpage, clut_x, clut_y)
@@ -902,7 +989,7 @@ void sw_draw_textured_triangle(int x0, int y0, int u0, int v0,
uint16_t texpage) {
int64_t area2 = (int64_t)(x1 - x0) * (int64_t)(y2 - y0)
- (int64_t)(x2 - x0) * (int64_t)(y1 - y0);
if (area2 == 0) return;
if (area2 == 0) { precise_consumed(); return; }
if (g_texture_filter) {
int xs[3] = {x0,x1,x2}, ys[3] = {y0,y1,y2};
@@ -912,22 +999,31 @@ void sw_draw_textured_triangle(int x0, int y0, int u0, int v0,
RTarget n = rt_native();
raster_textured_triangle(&n, x0, y0, u0, v0, x1, y1, u1, v1,
x2, y2, u2, v2, clut_x, clut_y, texpage);
x2, y2, u2, v2, clut_x, clut_y, texpage,
g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_textured_triangle(&hr, x0*s, y0*s, u0, v0,
x1*s, y1*s, u1, v1,
x2*s, y2*s, u2, v2, clut_x, clut_y, texpage);
raster_textured_triangle(&hr,
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), u0, v0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), u1, v1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), u2, v2,
clut_x, clut_y, texpage, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_textured_triangle(&wt, (wide_bd_x(&bd,x0)+dx)*s, y0*s, u0, v0,
(wide_bd_x(&bd,x1)+dx)*s, y1*s, u1, v1,
(wide_bd_x(&bd,x2)+dx)*s, y2*s, u2, v2, clut_x, clut_y, texpage);
raster_textured_triangle(&wt,
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), u0, v0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), u1, v1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), u2, v2,
clut_x, clut_y, texpage, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
precise_consumed();
}
static inline void color24_to_mod(uint32_t color, int *r, int *g, int *b) {
@@ -944,25 +1040,30 @@ static void raster_shaded_textured_triangle(const RTarget *t,
int x2, int y2, int u2, int v2,
int r2, int g2, int b2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage, int raw_texture) {
uint16_t texpage, int raw_texture,
int perspective,
float q0, float q1, float q2) {
/* Sort by Y, keeping UV and color modulation in sync */
if (y0 > y1) {
int tt;
tt=x0; x0=x1; x1=tt; tt=y0; y0=y1; y1=tt;
tt=u0; u0=u1; u1=tt; tt=v0; v0=v1; v1=tt;
tt=r0; r0=r1; r1=tt; tt=g0; g0=g1; g1=tt; tt=b0; b0=b1; b1=tt;
float tq=q0; q0=q1; q1=tq;
}
if (y0 > y2) {
int tt;
tt=x0; x0=x2; x2=tt; tt=y0; y0=y2; y2=tt;
tt=u0; u0=u2; u2=tt; tt=v0; v0=v2; v2=tt;
tt=r0; r0=r2; r2=tt; tt=g0; g0=g2; g2=tt; tt=b0; b0=b2; b2=tt;
float tq=q0; q0=q2; q2=tq;
}
if (y1 > y2) {
int tt;
tt=x1; x1=x2; x2=tt; tt=y1; y1=y2; y2=tt;
tt=u1; u1=u2; u2=tt; tt=v1; v1=v2; v2=tt;
tt=r1; r1=r2; r2=tt; tt=g1; g1=g2; g2=tt; tt=b1; b1=b2; b2=tt;
float tq=q1; q1=q2; q2=tq;
}
int dy_total = y2 - y0;
@@ -987,20 +1088,29 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
float qa = q0 + (q2 - q0) * alpha;
float uqa = (float)u0 * q0 + ((float)u2 * q2 - (float)u0 * q0) * alpha;
float vqa = (float)v0 * q0 + ((float)v2 * q2 - (float)v0 * q0) * alpha;
float ra = r0 + (float)(r2 - r0) * alpha;
float ga = g0 + (float)(g2 - g0) * alpha;
float ba = b0 + (float)(b2 - b0) * alpha;
float ub, vb, rb, gb, bb;
float ub, vb, qb, uqb, vqb, rb, gb, bb;
if (second_half) {
ub = u1 + (float)(u2 - u1) * beta;
vb = v1 + (float)(v2 - v1) * beta;
qb = q1 + (q2 - q1) * beta;
uqb = (float)u1 * q1 + ((float)u2 * q2 - (float)u1 * q1) * beta;
vqb = (float)v1 * q1 + ((float)v2 * q2 - (float)v1 * q1) * beta;
rb = r1 + (float)(r2 - r1) * beta;
gb = g1 + (float)(g2 - g1) * beta;
bb = b1 + (float)(b2 - b1) * beta;
} else {
ub = u0 + (float)(u1 - u0) * beta;
vb = v0 + (float)(v1 - v0) * beta;
qb = q0 + (q1 - q0) * beta;
uqb = (float)u0 * q0 + ((float)u1 * q1 - (float)u0 * q0) * beta;
vqb = (float)v0 * q0 + ((float)v1 * q1 - (float)v0 * q0) * beta;
rb = r0 + (float)(r1 - r0) * beta;
gb = g0 + (float)(g1 - g0) * beta;
bb = b0 + (float)(b1 - b0) * beta;
@@ -1011,6 +1121,9 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float tf;
tf = ua; ua = ub; ub = tf;
tf = va; va = vb; vb = tf;
tf = qa; qa = qb; qb = tf;
tf = uqa; uqa = uqb; uqb = tf;
tf = vqa; vqa = vqb; vqb = tf;
tf = ra; ra = rb; rb = tf;
tf = ga; ga = gb; gb = tf;
tf = ba; ba = bb; bb = tf;
@@ -1026,6 +1139,13 @@ static void raster_shaded_textured_triangle(const RTarget *t,
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
if (perspective) {
float q = qa + (qb - qa) * t_val;
if (q > 1.0e-12f) {
fu = (uqa + (uqb - uqa) * t_val) / q;
fv = (vqa + (vqb - vqa) * t_val) / q;
}
}
int mr = (int)(ra + (rb - ra) * t_val);
int mg = (int)(ga + (gb - ga) * t_val);
int mb = (int)(ba + (bb - ba) * t_val);
@@ -1051,7 +1171,7 @@ void sw_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
uint16_t texpage, int raw_texture) {
int64_t area2 = (int64_t)(x1 - x0) * (int64_t)(y2 - y0)
- (int64_t)(x2 - x0) * (int64_t)(y1 - y0);
if (area2 == 0) return;
if (area2 == 0) { precise_consumed(); return; }
if (g_texture_filter) {
int xs[3] = {x0,x1,x2}, ys[3] = {y0,y1,y2};
@@ -1069,26 +1189,30 @@ void sw_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
x0, y0, u0, v0, r0, g0, b0,
x1, y1, u1, v1, r1, g1, b1,
x2, y2, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
if (g_hr) {
int s = g_scale;
RTarget hr = rt_hires();
raster_shaded_textured_triangle(&hr,
x0*s, y0*s, u0, v0, r0, g0, b0,
x1*s, y1*s, u1, v1, r1, g1, b1,
x2*s, y2*s, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
precise_scaled(0,0,x0,s), precise_scaled(1,0,y0,s), u0, v0, r0, g0, b0,
precise_scaled(0,1,x1,s), precise_scaled(1,1,y1,s), u1, v1, r1, g1, b1,
precise_scaled(0,2,x2,s), precise_scaled(1,2,y2,s), u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
if (g_wide_cur) {
int s = g_scale, dx = wide_dx();
RTarget wt = rt_wide();
WideBd bd = wide_bd_get();
raster_shaded_textured_triangle(&wt,
(wide_bd_x(&bd,x0)+dx)*s, y0*s, u0, v0, r0, g0, b0,
(wide_bd_x(&bd,x1)+dx)*s, y1*s, u1, v1, r1, g1, b1,
(wide_bd_x(&bd,x2)+dx)*s, y2*s, u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture);
precise_wide_x(0,x0,s,dx,&bd), precise_scaled(1,0,y0,s), u0, v0, r0, g0, b0,
precise_wide_x(1,x1,s,dx,&bd), precise_scaled(1,1,y1,s), u1, v1, r1, g1, b1,
precise_wide_x(2,x2,s,dx,&bd), precise_scaled(1,2,y2,s), u2, v2, r2, g2, b2,
clut_x, clut_y, texpage, raw_texture, g_perspective_valid,
g_perspective_q[0], g_perspective_q[1], g_perspective_q[2]);
}
precise_consumed();
}
/* ------------------------------------------------------------------ */
+153 -2
View File
@@ -186,6 +186,144 @@ static int32_t s_ws_xnum = 1, s_ws_xden = 1;
extern "C" int gpu_ws_present_native_43(void); /* gpu.c — suppress on 4:3 frames */
extern "C" void psx_ws_note_gte_project(int nverts); /* gpu.c — gte_game_mode stamp */
/* Optional visual-only geometry precision cache. The PS1-visible SXY FIFO
* remains integer and fully faithful; this side cache retains the discarded
* 16.16 projection fraction so the high-resolution software mirror can match
* a later GP0 polygon and place its vertices between native pixels. */
#define GEOM_CACHE_SIZE 8192u
struct GeomVertex {
uint32_t packed;
int32_t x16, y16;
uint32_t generation;
};
static GeomVertex s_geom_cache[GEOM_CACHE_SIZE];
static uint32_t s_geom_generation = 1;
static int s_geom_enabled = 0;
static uint32_t s_geom_hits = 0;
/* Exact GTE projection provenance for perspective texture correction. The
* recompiler/interpreters call gte_precision_store after SWC2 writes an SXY
* register into guest RAM. GPU DMA later supplies that exact RAM address, so
* depth lookup follows the packet rather than guessing from rounded X/Y. */
struct PreciseProjection {
uint32_t packed;
int32_t x16, y16;
uint16_t z;
uint8_t valid;
};
static PreciseProjection s_precise_sxy[4] = {};
#define PRECISION_STORE_SIZE 65536u
struct PrecisionStoreEntry {
uint32_t addr;
PreciseProjection projection;
uint32_t generation;
};
static PrecisionStoreEntry s_precision_store[PRECISION_STORE_SIZE];
static uint32_t s_precision_generation = 1;
static int s_precision_tracking = 0;
static inline uint32_t precision_hash(uint32_t addr) {
addr >>= 2;
addr ^= addr >> 11;
return (addr * 2654435761u) & (PRECISION_STORE_SIZE - 1u);
}
static inline int precision_ram_address(uint32_t addr, uint32_t *physical) {
uint32_t mapped = addr & 0x1FFFFFFFu;
if (mapped >= 0x00800000u) return 0;
*physical = mapped & 0x1FFFFCu;
return 1;
}
extern "C" void gte_precision_tracking_set(int enabled) {
s_precision_tracking = enabled ? 1 : 0;
s_precision_generation++;
if (!s_precision_generation) s_precision_generation = 1;
}
extern "C" void gte_precision_store_word(uint32_t addr, uint8_t reg) {
if (!s_precision_tracking || reg < 12 || reg > 15) return;
int index = reg == 15 ? 2 : (int)reg - 12;
const PreciseProjection &projection = s_precise_sxy[index];
if (!projection.valid) return;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return;
PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
entry.addr = physical;
entry.projection = projection;
entry.generation = s_precision_generation;
}
extern "C" void gte_precision_invalidate_word(uint32_t addr) {
if (!s_precision_tracking) return;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return;
PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
if (entry.generation == s_precision_generation && entry.addr == physical)
entry.generation = 0;
}
extern "C" int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16,
uint16_t *z) {
if (!s_precision_tracking) return 0;
uint32_t physical;
if (!precision_ram_address(addr, &physical)) return 0;
const PrecisionStoreEntry &entry = s_precision_store[precision_hash(physical)];
if (entry.generation != s_precision_generation || entry.addr != physical ||
!entry.projection.valid || entry.projection.packed != packed)
return 0;
if (x16) *x16 = entry.projection.x16;
if (y16) *y16 = entry.projection.y16;
if (z) *z = entry.projection.z;
return entry.projection.z != 0;
}
static inline uint32_t geom_hash(uint32_t packed) {
packed ^= packed >> 16;
return (packed * 2654435761u) & (GEOM_CACHE_SIZE - 1u);
}
extern "C" void gte_geometry_correction_set(int enabled) {
s_geom_enabled = enabled ? 1 : 0;
s_geom_hits = 0;
s_geom_generation++;
if (!s_geom_generation) s_geom_generation = 1;
}
extern "C" int gte_geometry_correction_enabled(void) {
return s_geom_enabled;
}
extern "C" uint32_t gte_geometry_correction_hits(void) {
return s_geom_hits;
}
extern "C" int gte_geometry_correction_lookup(uint32_t packed,
int32_t *x16, int32_t *y16) {
if (!s_geom_enabled) return 0;
const GeomVertex &entry = s_geom_cache[geom_hash(packed)];
if (entry.generation != s_geom_generation || entry.packed != packed) return 0;
if (x16) *x16 = entry.x16;
if (y16) *y16 = entry.y16;
s_geom_hits++;
return 1;
}
static inline void geom_note(uint32_t packed, int64_t x16, int64_t y16) {
if (!s_geom_enabled) return;
/* Saturated off-screen projections are unsuitable for subpixel recovery. */
int32_t x = (int16_t)(packed & 0xFFFFu);
int32_t y = (int16_t)(packed >> 16);
if (x <= -0x400 || x >= 0x3FF || y <= -0x400 || y >= 0x3FF) return;
GeomVertex &entry = s_geom_cache[geom_hash(packed)];
entry.packed = packed;
entry.x16 = (int32_t)x16;
entry.y16 = (int32_t)y16;
entry.generation = s_geom_generation;
}
// Per-draw suppression of the X-squash (8C far-backdrop). The far backdrop
// (ocean/cloud/distant mountain) is a parallax layer that is conceptually at
// infinity: squashing it toward centre leaves its geometry short of the
@@ -651,9 +789,20 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0) {
}
}
dome_probe_note(gte->SZ[3]); /* locate the dome draw fn (far-vertex tally) */
int64_t sx = (gte->OFX + xterm) >> 16;
int64_t sy = (gte->OFY + (int64_t)gte->IR2 * h_div_sz) >> 16;
int64_t sx16 = gte->OFX + xterm;
int64_t sy16 = gte->OFY + (int64_t)gte->IR2 * h_div_sz;
int64_t sx = sx16 >> 16;
int64_t sy = sy16 >> 16;
gte->push_sxy(sx, sy);
s_precise_sxy[0] = s_precise_sxy[1];
s_precise_sxy[1] = s_precise_sxy[2];
s_precise_sxy[2].packed = (uint32_t)gte->SXY[2];
s_precise_sxy[2].x16 = (int32_t)sx16;
s_precise_sxy[2].y16 = (int32_t)sy16;
s_precise_sxy[2].z = gte->SZ[3];
s_precise_sxy[2].valid = gte->SZ[3] != 0;
s_precise_sxy[3] = s_precise_sxy[2];
geom_note((uint32_t)gte->SXY[2], sx16, sy16);
// Step 5: Depth cueing (MAC0/IR0) — only for last vertex of RTPT or RTPS
if (setMac0) {
@@ -1384,6 +1533,8 @@ extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
GTEState gte;
gte_load_data(gte, cpu);
gte_mtc2(&gte, reg, val);
if (reg >= 12 && reg <= 15)
for (int i = 0; i < 4; i++) s_precise_sxy[i].valid = 0;
for (int i = 0; i < 32; i++) cpu->gte_data[i] = gte_mfc2(&gte, i);
for (int i = 0; i < 32; i++) cpu->gte_ctrl[i] = gte_cfc2(&gte, i);
}
+144 -1
View File
@@ -58,7 +58,11 @@
#include "launcher.h"
#endif
#include <SDL.h>
#if defined(PSX_WEB)
#include <emscripten/emscripten.h>
#endif
#include <algorithm>
#include <atomic>
#include <cctype>
#include <cmath>
#include <cstdio>
@@ -276,6 +280,120 @@ static PlayerInput g_players[2];
* (sized for the native 640x512 when supersampling is off). */
static uint32_t* sdl_pixel_buf = nullptr;
/* Presentation-only interpolation for software-rendered content that repeats
* each guest image for two vblanks. This never changes guest or audio timing. */
static std::atomic<int> g_smooth_60fps{0};
struct Smooth60State {
std::vector<uint32_t> previous_source;
uint64_t source_hash = 0;
uint32_t width = 0;
uint32_t height = 0;
bool have_source = false;
bool previous_was_duplicate = false;
bool announced = false;
};
static Smooth60State g_smooth_60_state;
static void smooth_60_reset(void) {
g_smooth_60_state.previous_source.clear();
g_smooth_60_state.source_hash = 0;
g_smooth_60_state.width = 0;
g_smooth_60_state.height = 0;
g_smooth_60_state.have_source = false;
g_smooth_60_state.previous_was_duplicate = false;
}
static uint64_t smooth_60_frame_hash(const uint32_t* pixels, size_t count) {
uint64_t hash = 1469598103934665603ull;
const size_t step = std::max<size_t>(1, count / 4096u);
for (size_t i = 0; i < count; i += step) {
hash ^= pixels[i];
hash *= 1099511628211ull;
}
hash ^= count;
return hash * 1099511628211ull;
}
static bool smooth_60_scene_cut(const uint32_t* current,
const uint32_t* previous,
size_t count) {
const size_t step = std::max<size_t>(1, count / 4096u);
uint64_t difference = 0;
size_t samples = 0;
size_t large_changes = 0;
for (size_t i = 0; i < count; i += step) {
const uint32_t a = current[i];
const uint32_t b = previous[i];
const unsigned dr = (unsigned)std::abs((int)((a >> 16) & 0xFFu) -
(int)((b >> 16) & 0xFFu));
const unsigned dg = (unsigned)std::abs((int)((a >> 8) & 0xFFu) -
(int)((b >> 8) & 0xFFu));
const unsigned db = (unsigned)std::abs((int)(a & 0xFFu) -
(int)(b & 0xFFu));
const unsigned pixel_difference = dr + dg + db;
difference += pixel_difference;
if (pixel_difference > 320u) large_changes++;
samples++;
}
return samples && large_changes * 4u > samples * 3u &&
difference > (uint64_t)samples * 360u;
}
static void smooth_60_present(uint32_t* pixels, uint32_t width, uint32_t height,
bool eligible) {
if (!eligible || !pixels || !g_smooth_60fps.load(std::memory_order_acquire)) {
if (g_smooth_60_state.have_source) smooth_60_reset();
return;
}
Smooth60State& state = g_smooth_60_state;
const size_t count = (size_t)width * height;
if (!count) { smooth_60_reset(); return; }
const uint64_t hash = smooth_60_frame_hash(pixels, count);
if (!state.have_source || state.width != width || state.height != height) {
state.previous_source.assign(pixels, pixels + count);
state.source_hash = hash;
state.width = width;
state.height = height;
state.have_source = true;
state.previous_was_duplicate = false;
return;
}
const bool duplicate = hash == state.source_hash &&
std::memcmp(pixels, state.previous_source.data(), count * sizeof(uint32_t)) == 0;
if (duplicate) { state.previous_was_duplicate = true; return; }
const bool interpolate = state.previous_was_duplicate &&
!smooth_60_scene_cut(pixels, state.previous_source.data(), count);
state.previous_was_duplicate = false;
state.source_hash = hash;
if (!interpolate) {
std::copy(pixels, pixels + count, state.previous_source.begin());
return;
}
if (!state.announced) {
std::fprintf(stdout,
"psxrecomp: 60 FPS smoothing active (guest timing preserved)\n");
state.announced = true;
}
for (size_t i = 0; i < count; i++) {
const uint32_t current = pixels[i];
const uint32_t previous = state.previous_source[i];
state.previous_source[i] = current;
pixels[i] = 0xFF000000u |
(((current & 0x00FEFEFEu) >> 1) + ((previous & 0x00FEFEFEu) >> 1));
}
}
extern "C" void psx_smooth_60fps_set(int enabled) {
g_smooth_60fps.store(enabled ? 1 : 0, std::memory_order_release);
}
#if defined(PSX_WEB)
extern "C" EMSCRIPTEN_KEEPALIVE void psx_web_set_smooth_60fps(int enabled) {
psx_smooth_60fps_set(enabled);
}
#endif
/* [video] options, resolved from the game config (defaults: native + AA). */
static int g_video_scale = 1; /* internal-resolution SSAA factor */
static bool g_video_aa = true; /* linear present filtering */
@@ -348,6 +466,8 @@ static bool g_ws_hud_sprt = false;
/* Runtime-only transition cleanup; kept out of gpu.h because generated game
* units include that ABI header and do not need this frontend-only setter. */
extern "C" void gpu_ws_set_clear_reveal(int on);
extern "C" void gpu_ws_set_nw_textured_edges(int on, int scale_pct);
extern "C" void gpu_ws_set_signed_x_bound_sites(const uint32_t*, const uint32_t*, int);
/* Widescreen engages at game entry (fntrace_is_game_started): the BIOS boot
* — Sony logo, PS logo, shell — presents authentic 4:3 with no GTE squash.
* Starts true when the configured aspect is already 4:3 (nothing to engage). */
@@ -2420,6 +2540,7 @@ static void sdl_vblank_present(void) {
GpuDisplayInfo di;
gpu_get_display_info(&di);
if (di.disabled || di.width == 0 || di.height == 0) {
smooth_60_present(nullptr, 0, 0, false);
present_ring_commit(PRES_PATH_BLANK, (uint16_t)di.width,
(uint16_t)di.height, 0);
#ifndef PSX_SDL_NO_RENDER
@@ -2583,6 +2704,11 @@ static void sdl_vblank_present(void) {
}
}
smooth_60_present(sdl_pixel_buf,
present_w * (uint32_t)active_scale,
h * (uint32_t)active_scale,
!g_gl_active && !g_vk_active && !di.depth24 && !fmv_frame);
/* Frame blending (CRT-persistence masker for 30fps double-buffered
* content). Some games (e.g. Crash Bash menus/characters) leave a
* dynamic object in only one of the two display buffers per 30fps cycle,
@@ -2601,7 +2727,8 @@ static void sdl_vblank_present(void) {
}
const bool simple_sw = !g_gl_active && !g_vk_active && !wide_present &&
!di.depth24 && active_scale == 1;
if (blend_cfg && simple_sw) {
if (blend_cfg && simple_sw &&
!g_smooth_60fps.load(std::memory_order_acquire)) {
static uint32_t prev_buf[640 * 512];
static uint32_t prev_px = 0;
const uint32_t npx = present_w * h;
@@ -2944,6 +3071,20 @@ int main(int argc, char** argv) {
/* [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);
gpu_ws_set_nw_textured_edges(gc.ws_nw_textured_edges ? 1 : 0,
gc.ws_nw_textured_edge_scale);
gl_renderer_set_wide_fast(gc.ws_nw_full_mirror ? 0 : 1);
{
std::vector<uint32_t> addresses, expected;
addresses.reserve(gc.ws_signed_x_bound_sites.size());
expected.reserve(gc.ws_signed_x_bound_sites.size());
for (const auto& site : gc.ws_signed_x_bound_sites) {
addresses.push_back(site.address);
expected.push_back(site.expected);
}
gpu_ws_set_signed_x_bound_sites(addresses.data(), expected.data(),
(int)addresses.size());
}
/* [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);
@@ -3252,6 +3393,8 @@ int main(int argc, char** argv) {
* PSX_FRAME_INTERPOLATION=0/1; PSX_FRAME_INTERPOLATION_FPS=0|90+. */
if (const char *e = std::getenv("PSX_LOW_LATENCY_INPUT")) g_low_latency_input = atoi(e) ? 1 : 0;
if (const char *e = std::getenv("PSX_VSYNC")) g_video_vsync = atoi(e);
if (const char *e = std::getenv("PSX_SMOOTH_60FPS"))
psx_smooth_60fps_set(atoi(e) ? 1 : 0);
if (const char *e = std::getenv("PSX_FRAME_INTERPOLATION"))
g_frame_interpolation = atoi(e) ? 1 : 0;
if (const char *e = std::getenv("PSX_FRAME_INTERPOLATION_FPS")) {
+4
View File
@@ -1315,6 +1315,7 @@ static inline void d44_note(uint32_t phys, uint32_t old, uint32_t val) {
}
static void psx_write_word_raw(uint32_t addr, uint32_t val);
extern void gte_precision_invalidate_word(uint32_t addr);
void psx_write_word(uint32_t addr, uint32_t val) {
extern void (*g_overlay_flush_pending_cycles)(void);
if (g_overlay_flush_pending_cycles) g_overlay_flush_pending_cycles();
@@ -1331,6 +1332,7 @@ void psx_write_word(uint32_t addr, uint32_t val) {
}
static void psx_write_word_raw(uint32_t addr, uint32_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
/* KSEG2 cache control — before physical translation. */
if (addr == 0xFFFE0130u) { cache_ctrl = val; return; }
/* KSEG2 guard — see psx_read_word_raw. */
@@ -1491,6 +1493,7 @@ void psx_write_half(uint32_t addr, uint16_t val) {
}
static void psx_write_half_raw(uint32_t addr, uint16_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
/* KSEG2 guard — see psx_read_word_raw. */
@@ -1726,6 +1729,7 @@ void psx_write_byte(uint32_t addr, uint8_t val) {
}
static void psx_write_byte_raw(uint32_t addr, uint8_t val) {
g_guest_store_count++;
gte_precision_invalidate_word(addr);
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
/* KSEG2 guard — see psx_read_word_raw. */
+1 -1
View File
@@ -3496,7 +3496,7 @@ void psx_sljit_cop2(CPUState *cpu, uint32_t insn) {
int32_t simm = (int32_t)(int16_t)(insn & 0xFFFFu);
uint32_t addr = cpu->gpr[rs] + (uint32_t)simm;
if (op == 0x32) { gte_write_data(cpu, (uint8_t)rt, cpu->read_word(addr)); } /* LWC2 */
else if (op == 0x3A) { cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt)); } /* SWC2 */
else if (op == 0x3A) { cpu->write_word(addr, gte_read_data(cpu, (uint8_t)rt)); gte_precision_store_word(addr, (uint8_t)rt); } /* SWC2 */
}
/* Unaligned load/store helper — mirrors dirty_ram_interp.c interp_lwl/lwr/swl/swr
+19 -4
View File
@@ -163,6 +163,7 @@ static uint32_t s_gpr_lru;
static int s_frag_ws_cull;
extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* gpu.c — shared widen */
extern int psx_ws_cull_slti(uint32_t sx, uint32_t imm); /* gpu.c — signed right edge */
extern int32_t psx_ws_player_x_bound(int32_t vanilla); /* gpu.c — gameplay X clamp */
extern int psx_ws_cull_bltz(uint32_t v); /* gpu.c — signed left edge */
extern int psx_ws_is_cull_w_imm(uint32_t imm); /* gpu.c — per-game W-imm set */
extern int psx_ws_auto_cull_on(void); /* gpu.c — per-game opt-in gate */
@@ -371,6 +372,17 @@ void overlay_sljit_init_helpers(CPUState *cpu) {
cpu->sljit_helpers[SLJIT_HLP_CALL] = (void *)psx_sljit_call;
cpu->sljit_helpers[SLJIT_HLP_WS_CULL_SLTI] = (void *)psx_ws_cull_slti;
cpu->sljit_helpers[SLJIT_HLP_WS_CULL_BLTZ] = (void *)psx_ws_cull_bltz;
cpu->sljit_helpers[SLJIT_HLP_WS_PLAYER_X_BOUND] = (void *)psx_ws_player_x_bound;
}
static void emit_ws_player_x_bound(struct sljit_compiler *C, uint32_t rt, int32_t vanilla) {
sljit_emit_op1(C, SLJIT_MOV32, SLJIT_R0, 0, SLJIT_IMM, (sljit_sw)vanilla);
sljit_emit_op1(C, SLJIT_MOV, SLJIT_R2, 0, SLJIT_MEM1(R_CPU),
(sljit_sw)(offsetof(CPUState, sljit_helpers)
+ (size_t)SLJIT_HLP_WS_PLAYER_X_BOUND * sizeof(void *)));
sljit_emit_icall(C, SLJIT_CALL, SLJIT_ARGS1(32, 32), SLJIT_R2, 0);
sljit_emit_op1(C, SLJIT_MOV32, gpr_dst(C, rt), 0, SLJIT_R0, 0);
gpr_unpin();
}
/* Emit `rt = <cull helper>(gpr[rs], imm)` for a flagged auto_screen_x cull
@@ -401,7 +413,7 @@ enum { EMIT_OK = 0, EMIT_TERM = 1, EMIT_ABORT = 2 };
* is `jr $ra` (the only terminator this slice supports — caller then emits the
* delay slot + the shard return), or EMIT_ABORT for anything outside the slice
* (any other control transfer, or an unsupported opcode). */
static int emit_one(struct sljit_compiler *C, uint32_t insn, int bd_kind, int bd_cols) {
static int emit_one(struct sljit_compiler *C, uint32_t pc, uint32_t insn, int bd_kind, int bd_cols) {
uint32_t op = f_op(insn), rs = f_rs(insn), rt = f_rt(insn);
uint32_t rd = f_rd(insn), sh = f_sh(insn), fn = f_fn(insn);
uint32_t imm = f_imm(insn);
@@ -615,7 +627,10 @@ static int emit_one(struct sljit_compiler *C, uint32_t insn, int bd_kind, int bd
return EMIT_OK;
}
case 0x0F: /* LUI rt = imm << 16 */
sljit_emit_op1(C, SLJIT_MOV32, gpr_dst(C, rt), 0, SLJIT_IMM, (sljit_sw)(imm << 16));
if (psx_ws_is_signed_x_bound_site(pc, insn))
emit_ws_player_x_bound(C, rt, (int32_t)(imm << 16));
else
sljit_emit_op1(C, SLJIT_MOV32, gpr_dst(C, rt), 0, SLJIT_IMM, (sljit_sw)(imm << 16));
return EMIT_OK;
case 0x20: emit_load(C, offsetof(CPUState, read_byte), SLJIT_MOV_S8, rt, rs, simm); return EMIT_OK; /* LB */
case 0x21: emit_load(C, offsetof(CPUState, read_half), SLJIT_MOV_S16, rt, rs, simm); return EMIT_OK; /* LH */
@@ -996,7 +1011,7 @@ void overlay_sljit_try_compile(uint32_t entry,
pending = PEND_CALL;
continue;
}
if (emit_one(C, insn, s_bd_kind[i], s_bd_cols[i]) != EMIT_OK) { aborted = 1; break; }
if (emit_one(C, entry + i * 4u, insn, s_bd_kind[i], s_bd_cols[i]) != EMIT_OK) { aborted = 1; break; }
} else {
/* This instruction is the delay slot of the pending control insn;
* it must be a plain, supported, non-control op (the constraint the
@@ -1006,7 +1021,7 @@ void overlay_sljit_try_compile(uint32_t entry,
* the pending transfer. */
int32_t dummy = 0;
if (classify_control(insn, i * 4u, entry_phys, &dummy) != CTRL_NONE) { aborted = 1; break; }
if (emit_one(C, insn, s_bd_kind[i], s_bd_cols[i]) != EMIT_OK) { aborted = 1; break; }
if (emit_one(C, entry + i * 4u, insn, s_bd_kind[i], s_bd_cols[i]) != EMIT_OK) { aborted = 1; break; }
gpr_flush(C);
if (pending == PEND_RET) {
if (g_psx_cps_mode) {
+1
View File
@@ -500,6 +500,7 @@ static void exec_one(CPUState* cpu) {
case 0x3A: { /* SWC2 */
uint32_t addr = rs_val + (uint32_t)SIMM(insn);
cpu->write_word(addr, gte_read_data(cpu, RT(insn)));
gte_precision_store_word(addr, RT(insn));
break;
}