mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge remote-tracking branch 'origin/master' into spike/load-time-zero
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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
@@ -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(>e, 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(>e, i);
|
||||
for (int i = 0; i < 32; i++) cpu->gte_ctrl[i] = gte_cfc2(>e, i);
|
||||
}
|
||||
|
||||
+144
-1
@@ -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")) {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user