mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
pgxp: emit hook macros + dual build targets + flavor-namespaced caches (phase 2)
Emitter side of the value-propagation engine (ENHANCEMENTS.md G1.10):
- recompiler/src/pgxp_hook_emitter.{h,cpp} (new, in the codegen-hash
surface): ONE hook grammar shared by BOTH emitters, wrapping loads/
stores/COP2 transfers and precision-carrying ALU ops (moves, add/sub,
or-merge, shifts, LUI, HI/LO, muldiv) with PGXP_*() macros. Clobberable
sources are captured into locals before the statement. Precision-
destroying ops (AND/XOR/SLT/...) are deliberately unhooked - validate-
on-read drops their stale shadows for free.
- code_generator.cpp: central append in translate_instruction; LWC2/SWC2
emissions capture the RAW loaded/stored word (a masked GTE register
write must validate against the word as loaded).
- strict_translator.cpp: translate() wraps translate_impl()'s c_code with
the shared grammar; deferred load-delay variants hook the psx_ldd temp.
- BIOS regenerated, fingerprints verified CHANGED (SCPH1001 + OpenBIOS;
42.6k/12.2k macro sites). Base runtime + 33/33 tests green against it.
- Gating: macros expand only under -DPSX_PGXP=1. runtime.cmake grows a
PGXP option on psxrecomp_add_runtime_target (defines PSX_PGXP=1 +
PSX_OVERLAY_FLAVOR=2, _pgxp exe suffix) and PSX_PGXP_VARIANT to build
the sibling target from the same generated tree (framework runtime +
game wrapper). No ABI version bump needed: the appended PGXPHooks
table is NULL-tolerant and the flavor half of the ABI tag rejects any
host/DLL flavor mix.
- Shard caches: flavor folded into the cache tag (cg<V>_<hash>_gc<hash>
_f<flavor>) in overlay_loader.c + compile_overlays.py so base and pgxp
caches never share a directory; compile_overlays.py passes
-DPSX_PGXP=1 when flavor bit 2 is set; overlay preamble forwards
psx_pgxp_* through the table. PSX_OVERLAY_CODEGEN_VER 9 -> 10.
- Env overrides PSX_GEOMETRY_CORRECTION / PSX_PERSPECTIVE_TEXTURING /
PSX_PGXP_CPU_MODE (validation path: free-running headless boots arm
from the first vertex); census grew produced/swc2_stores and the pgxp
verb reports suppress/active gate state.
Validated: PSXRecomp_pgxp.exe boots the recompiled BIOS with hooks armed
(engine active, per-vertex lookups flowing; the BIOS shell has no RTPS
content so produced=0 there is correct - coverage measurement moves to a
3D title in phase 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+539
-534
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
#include "code_generator.h"
|
||||
#include "pgxp_hook_emitter.h"
|
||||
#include "control_flow.h"
|
||||
#include "gte_register_classification.h"
|
||||
#include "../src/bios_address_model.h"
|
||||
@@ -1572,12 +1573,19 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
|
||||
? reg_name(rs)
|
||||
: fmt::format("{} + {}", reg_name(rs), offset);
|
||||
bool special = PSXRecompGTERegisters::data_write_needs_helper(static_cast<uint8_t>(rt));
|
||||
/* The raw loaded word is captured for the PGXP hook: the
|
||||
* register may hold a MASKED value (write helper), and
|
||||
* the shadow must validate against the word as loaded. */
|
||||
if (special) {
|
||||
code = gte_stall + fmt::format("gte_write_data(cpu, {}, psx_cyc_lwc2_read(cpu, {})); /* lwc2 gte[{}] */",
|
||||
rt, addr, rt);
|
||||
code = gte_stall + fmt::format(
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = psx_cyc_lwc2_read(cpu, _pgxa); "
|
||||
"gte_write_data(cpu, {}, _pgxv); PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }} /* lwc2 gte[{}] */",
|
||||
addr, rt, instr, rt);
|
||||
} else {
|
||||
code = gte_stall + fmt::format("cpu->gte_data[{}] = psx_cyc_lwc2_read(cpu, {}); /* lwc2 gte[{}], ({}) */",
|
||||
rt, addr, rt, addr);
|
||||
code = gte_stall + fmt::format(
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = psx_cyc_lwc2_read(cpu, _pgxa); "
|
||||
"cpu->gte_data[{}] = _pgxv; PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }} /* lwc2 gte[{}], ({}) */",
|
||||
addr, rt, instr, rt, addr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1593,16 +1601,15 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
|
||||
? fmt::format("gte_read_data(cpu, {})", rt)
|
||||
: 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(
|
||||
"psx_store_cycle_barrier(); 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(
|
||||
"psx_store_cycle_barrier(); cpu->write_word({} + {}, {}); gte_precision_store_word({} + {}, {}); /* swc2 gte[{}], {}({}) */",
|
||||
reg_name(rs), offset, value, reg_name(rs), offset, rt,
|
||||
rt, offset, reg_name(rs));
|
||||
}
|
||||
std::string swc2_addr = (offset == 0)
|
||||
? reg_name(rs)
|
||||
: fmt::format("{} + {}", reg_name(rs), offset);
|
||||
code = gte_stall + swc2_store_pc + fmt::format(
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = {}; "
|
||||
"psx_store_cycle_barrier(); cpu->write_word(_pgxa, _pgxv); "
|
||||
"gte_precision_store_word(_pgxa, {}); "
|
||||
"PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }} /* swc2 gte[{}], {}({}) */",
|
||||
swc2_addr, value, rt, instr, rt, offset, reg_name(rs));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1610,6 +1617,7 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr)
|
||||
}
|
||||
}
|
||||
|
||||
PSXRecomp::append_pgxp_hooks(instr, code);
|
||||
return config_.indent + code + comment;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "pgxp_hook_emitter.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace PSXRecomp {
|
||||
|
||||
namespace {
|
||||
/* Same register naming both emitters use ("cpu->gpr[n]"); kept local so this
|
||||
* TU links into psxrecomp-bios without pulling the game CodeGenerator. */
|
||||
inline std::string reg_name(uint32_t n) { return fmt::format("cpu->gpr[{}]", n); }
|
||||
inline uint32_t get_rs(uint32_t i) { return (i >> 21) & 0x1F; }
|
||||
inline uint32_t get_rt(uint32_t i) { return (i >> 16) & 0x1F; }
|
||||
inline uint32_t get_rd(uint32_t i) { return (i >> 11) & 0x1F; }
|
||||
inline int16_t get_imm16(uint32_t i) { return (int16_t)(i & 0xFFFF); }
|
||||
}
|
||||
|
||||
/* PGXP dataflow-shadowing hook emission (ENHANCEMENTS.md G1.10; pgxp_hooks.h).
|
||||
*
|
||||
* Wraps the translated statement with a PGXP_*() macro carrying the raw
|
||||
* instruction word plus the operand values the runtime engine needs. Source
|
||||
* operands that the statement may clobber (a load/ALU destination aliasing a
|
||||
* source, a load overwriting its own base register) are captured into locals
|
||||
* BEFORE the statement; everything else is read after it. In the base build
|
||||
* the macros preprocess to ((void)0) and the optimizer erases the dead
|
||||
* capture locals, so base objects are unchanged; only a -DPSX_PGXP=1 TU pays.
|
||||
*
|
||||
* Deliberately unhooked: AND/XOR/NOR/SLT-family and the exotic immediates —
|
||||
* they only ever DESTROY precision, and the engine's validate-on-read drops
|
||||
* their stale shadows without help. The configured widescreen special sites
|
||||
* return early above translate_instruction's main dispatch and are likewise
|
||||
* unhooked (they are cull compares, not vertex moves; validation covers). */
|
||||
void append_pgxp_hooks(uint32_t instr, std::string& code) {
|
||||
const uint32_t opcode = (instr >> 26) & 0x3F;
|
||||
const uint32_t rs = get_rs(instr);
|
||||
const uint32_t rt = get_rt(instr);
|
||||
const uint32_t rd = get_rd(instr);
|
||||
const int16_t offset = get_imm16(instr);
|
||||
const auto addr_expr = [&]() {
|
||||
return (offset == 0) ? reg_name(rs)
|
||||
: fmt::format("{} + {}", reg_name(rs), offset);
|
||||
};
|
||||
|
||||
switch (opcode) {
|
||||
case 0x00: { /* SPECIAL */
|
||||
const uint32_t funct = instr & 0x3F;
|
||||
switch (funct) {
|
||||
case 0x00: case 0x02: case 0x03: /* SLL / SRL / SRA */
|
||||
if (rd == 0 || instr == 0) return; /* skip plain nop */
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; {} PGXP_ALU(0x{:08X}u, {}, _pgx1, {}u); }}",
|
||||
reg_name(rt), code, instr, reg_name(rd), (instr >> 6) & 31u);
|
||||
return;
|
||||
case 0x04: case 0x06: case 0x07: /* SLLV / SRLV / SRAV */
|
||||
if (rd == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; uint32_t _pgx2 = {}; {} "
|
||||
"PGXP_ALU(0x{:08X}u, {}, _pgx1, _pgx2); }}",
|
||||
reg_name(rt), reg_name(rs), code, instr, reg_name(rd));
|
||||
return;
|
||||
case 0x10: case 0x12: /* MFHI / MFLO */
|
||||
if (rd == 0) return;
|
||||
code = fmt::format("{} PGXP_ALU(0x{:08X}u, {}, {}, 0u);",
|
||||
code, instr, reg_name(rd),
|
||||
funct == 0x10 ? "cpu->hi" : "cpu->lo");
|
||||
return;
|
||||
case 0x11: case 0x13: /* MTHI / MTLO */
|
||||
code = fmt::format("{} PGXP_ALU(0x{:08X}u, {}, {}, 0u);",
|
||||
code, instr,
|
||||
funct == 0x11 ? "cpu->hi" : "cpu->lo",
|
||||
funct == 0x11 ? "cpu->hi" : "cpu->lo");
|
||||
return;
|
||||
case 0x18: case 0x19: case 0x1A: case 0x1B: /* MULT(U)/DIV(U) */
|
||||
code = fmt::format(
|
||||
"{} PGXP_MULDIV(0x{:08X}u, cpu->hi, cpu->lo, {}, {});",
|
||||
code, instr, reg_name(rs), reg_name(rt));
|
||||
return;
|
||||
case 0x20: case 0x21: case 0x22: case 0x23: /* ADD(U)/SUB(U) */
|
||||
case 0x25: /* OR */
|
||||
if (rd == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; uint32_t _pgx2 = {}; {} "
|
||||
"PGXP_ALU(0x{:08X}u, {}, _pgx1, _pgx2); }}",
|
||||
reg_name(rs), reg_name(rt), code, instr, reg_name(rd));
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
case 0x08: case 0x09: /* ADDI / ADDIU */
|
||||
if (rt == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; {} PGXP_ALU(0x{:08X}u, {}, _pgx1, 0x{:08X}u); }}",
|
||||
reg_name(rs), code, instr, reg_name(rt),
|
||||
(uint32_t)(int32_t)offset);
|
||||
return;
|
||||
case 0x0D: /* ORI */
|
||||
if (rt == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; {} PGXP_ALU(0x{:08X}u, {}, _pgx1, 0x{:04X}u); }}",
|
||||
reg_name(rs), code, instr, reg_name(rt),
|
||||
(uint32_t)(uint16_t)offset);
|
||||
return;
|
||||
case 0x0F: /* LUI */
|
||||
if (rt == 0) return;
|
||||
code = fmt::format("{} PGXP_ALU(0x{:08X}u, {}, 0u, 0u);",
|
||||
code, instr, reg_name(rt));
|
||||
return;
|
||||
case 0x12: { /* COP2 register transfers */
|
||||
const uint32_t cop_op = (instr >> 21) & 0x1F;
|
||||
if ((cop_op == 0x00 && rt != 0) || cop_op == 0x04) /* MFC2 / MTC2 */
|
||||
code = fmt::format("{} PGXP_COP2(0x{:08X}u, {}, 0u);",
|
||||
code, instr, reg_name(rt));
|
||||
return;
|
||||
}
|
||||
case 0x20: case 0x21: case 0x22: case 0x23:
|
||||
case 0x24: case 0x25: case 0x26: /* loads */
|
||||
if (rt == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgxa = {}; {} PGXP_LOAD(0x{:08X}u, _pgxa, {}); }}",
|
||||
addr_expr(), code, instr, reg_name(rt));
|
||||
return;
|
||||
case 0x28: case 0x29: case 0x2A: case 0x2B:
|
||||
case 0x2E: /* stores */
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgxa = {}; {} PGXP_STORE(0x{:08X}u, _pgxa, {}); }}",
|
||||
addr_expr(), code, instr, reg_name(rt));
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace PSXRecomp
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef PGXP_HOOK_EMITTER_H
|
||||
#define PGXP_HOOK_EMITTER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace PSXRecomp {
|
||||
|
||||
/* PGXP dataflow-shadowing hook emission (ENHANCEMENTS.md G1.10; runtime
|
||||
* pgxp_hooks.h). Shared by BOTH emitters — CodeGenerator (game) and
|
||||
* StrictTranslator (BIOS) — so the hook grammar can never drift between them.
|
||||
*
|
||||
* Wraps the translated statement `code` with a PGXP_*() macro invocation for
|
||||
* the instruction classes that move sub-pixel projection provenance. Source
|
||||
* operands the statement may clobber are captured into locals BEFORE it;
|
||||
* everything else is read after it. In the base build the macros preprocess
|
||||
* to ((void)0) and the optimizer erases the dead captures, so base objects
|
||||
* are unchanged; only a -DPSX_PGXP=1 TU pays.
|
||||
*
|
||||
* NOT applied to LWC2/SWC2 — those emissions capture the raw loaded/stored
|
||||
* word themselves (a masked GTE register write must validate against the
|
||||
* word as loaded, not the register's masked value).
|
||||
*
|
||||
* Deliberately unhooked: AND/XOR/NOR/SLT-family and the exotic immediates —
|
||||
* they only ever DESTROY precision, and the engine's validate-on-read drops
|
||||
* their stale shadows without help. */
|
||||
void append_pgxp_hooks(uint32_t instr, std::string& code);
|
||||
|
||||
} // namespace PSXRecomp
|
||||
|
||||
#endif /* PGXP_HOOK_EMITTER_H */
|
||||
@@ -7,6 +7,7 @@
|
||||
// records this substitution explicitly.
|
||||
|
||||
#include "strict_translator.h"
|
||||
#include "pgxp_hook_emitter.h" /* shared PGXP hook grammar */
|
||||
#include "gte_register_classification.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -63,6 +64,30 @@ TranslateResult unsupported(const PSXRecomp::DecodedInstruction& d, const std::s
|
||||
} // namespace
|
||||
|
||||
TranslateResult StrictTranslator::translate(const PSXRecomp::DecodedInstruction& d) {
|
||||
TranslateResult r = translate_impl(d);
|
||||
if (!r.supported) return r;
|
||||
const uint32_t opcode = (d.raw >> 26) & 0x3F;
|
||||
/* Shared PGXP hook grammar (ENHANCEMENTS.md G1.10). LWC2/SWC2 capture
|
||||
* their raw word inside translate_impl and are skipped here. */
|
||||
if (opcode != 0x32 && opcode != 0x3A && !r.c_code.empty())
|
||||
PSXRecomp::append_pgxp_hooks(d.raw, r.c_code);
|
||||
/* The deferred load-delay variant assigns into the function-scope
|
||||
* psx_ldd_<addr> temp instead of the GPR; hook it with that temp as the
|
||||
* loaded value (the shadow keys on the value, not on where it lands —
|
||||
* the emitter's later writeback makes the GPR match). */
|
||||
if (r.load_dest >= 0 && !r.c_code_deferred.empty()) {
|
||||
const uint32_t rs = (d.raw >> 21) & 0x1F;
|
||||
const int16_t offset = static_cast<int16_t>(d.raw & 0xFFFF);
|
||||
r.c_code_deferred = fmt::format(
|
||||
"{{ uint32_t _pgxa = cpu->gpr[{}] + {}; {} "
|
||||
"PGXP_LOAD(0x{:08X}u, _pgxa, psx_ldd_{:08X}); }}",
|
||||
static_cast<int>(rs), static_cast<int>(offset),
|
||||
r.c_code_deferred, d.raw, d.address);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
TranslateResult StrictTranslator::translate_impl(const PSXRecomp::DecodedInstruction& d) {
|
||||
TranslateResult r;
|
||||
|
||||
// NOP (raw == 0, i.e. SLL $zero,$zero,0) — emit as a comment with no semantics.
|
||||
@@ -1405,14 +1430,18 @@ TranslateResult StrictTranslator::translate(const PSXRecomp::DecodedInstruction&
|
||||
? fmt::format("cpu->gpr[{}]", static_cast<int>(rs))
|
||||
: fmt::format("(uint32_t)((int32_t)cpu->gpr[{}] + ({}))", static_cast<int>(rs), static_cast<int>(offset));
|
||||
bool special = PSXRecompGTERegisters::data_write_needs_helper(rt);
|
||||
/* The raw loaded word is captured for the PGXP hook: a masked
|
||||
* register write must validate against the word as loaded. */
|
||||
if (special) {
|
||||
r.c_code = gte_stall + fmt::format(
|
||||
"gte_write_data(cpu, {}, psx_cyc_lwc2_read(cpu, {}));",
|
||||
static_cast<int>(rt), addr);
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = psx_cyc_lwc2_read(cpu, _pgxa); "
|
||||
"gte_write_data(cpu, {}, _pgxv); PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }}",
|
||||
addr, static_cast<int>(rt), d.raw);
|
||||
} else {
|
||||
r.c_code = gte_stall + fmt::format(
|
||||
"cpu->gte_data[{}] = psx_cyc_lwc2_read(cpu, {});",
|
||||
static_cast<int>(rt), addr);
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = psx_cyc_lwc2_read(cpu, _pgxa); "
|
||||
"cpu->gte_data[{}] = _pgxv; PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }}",
|
||||
addr, static_cast<int>(rt), d.raw);
|
||||
}
|
||||
r.comment = fmt::format("lwc2 gte[{}], {}({})",
|
||||
rt, static_cast<int>(offset), gpr_name(rs));
|
||||
@@ -1431,20 +1460,18 @@ TranslateResult StrictTranslator::translate(const PSXRecomp::DecodedInstruction&
|
||||
std::string value = PSXRecompGTERegisters::data_read_needs_helper(rt)
|
||||
? fmt::format("gte_read_data(cpu, {})", static_cast<int>(rt))
|
||||
: fmt::format("cpu->gte_data[{}]", static_cast<int>(rt));
|
||||
if (offset == 0) {
|
||||
{
|
||||
std::string addr = offset == 0
|
||||
? fmt::format("cpu->gpr[{}]", static_cast<int>(rs))
|
||||
: fmt::format("(uint32_t)((int32_t)cpu->gpr[{}] + ({}))",
|
||||
static_cast<int>(rs), static_cast<int>(offset));
|
||||
r.c_code = gte_stall + fmt::format(
|
||||
"psx_store_cycle_barrier(); g_debug_last_store_pc = 0x{:08X}u; "
|
||||
"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(
|
||||
"psx_store_cycle_barrier(); g_debug_last_store_pc = 0x{:08X}u; "
|
||||
"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));
|
||||
"{{ uint32_t _pgxa = {}; uint32_t _pgxv = {}; "
|
||||
"cpu->write_word(_pgxa, _pgxv); "
|
||||
"gte_precision_store_word(_pgxa, {}); "
|
||||
"PGXP_COP2(0x{:08X}u, _pgxv, _pgxa); }}",
|
||||
d.address, addr, value, static_cast<int>(rt), d.raw);
|
||||
}
|
||||
r.comment = fmt::format("swc2 gte[{}], {}({})",
|
||||
rt, static_cast<int>(offset), gpr_name(rs));
|
||||
|
||||
@@ -63,6 +63,13 @@ class StrictTranslator {
|
||||
public:
|
||||
// Translate a single decoded instruction. Pure function — no state.
|
||||
static TranslateResult translate(const PSXRecomp::DecodedInstruction& d);
|
||||
|
||||
private:
|
||||
// The per-opcode translation body; translate() wraps its c_code with the
|
||||
// shared PGXP hook grammar (CodeGenerator::append_pgxp_hooks).
|
||||
static TranslateResult translate_impl(const PSXRecomp::DecodedInstruction& d);
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
} // namespace PSXRecompV4
|
||||
|
||||
@@ -17,6 +17,23 @@ psxrecomp_add_runtime_target(psx-runtime
|
||||
EXE_NAME "PSXRecomp"
|
||||
)
|
||||
|
||||
# PGXP variant of the framework runtime (ENHANCEMENTS.md G1.10): the same
|
||||
# recompiled BIOS compiled with -DPSX_PGXP=1 so the emitted PGXP_*() hook
|
||||
# macros arm the value-propagation engine. Framework-dev validation surface
|
||||
# (LLE boot logo is real GTE content); titles get theirs via
|
||||
# PSX_PGXP_VARIANT in psxrecomp_add_game_runtime.
|
||||
option(PSX_PGXP_VARIANT
|
||||
"Also build the _pgxp PGXP precision-shadowing variant" OFF)
|
||||
if(PSX_PGXP_VARIANT)
|
||||
psxrecomp_add_runtime_target(psx-runtime-pgxp
|
||||
PGXP
|
||||
DEBUG_PORT 4370
|
||||
WINDOW_TITLE "psxrecomp - BIOS"
|
||||
DEFAULT_BIOS_PATH "${PSXRECOMP_ROOT}/bios/SCPH1001.BIN"
|
||||
EXE_NAME "PSXRecomp"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(launcher_device_roundtrip_test
|
||||
tests/test_launcher_device_roundtrip.cpp)
|
||||
|
||||
@@ -28,6 +28,8 @@ set(PSXRECOMP_CODEGEN_HASH_SRCS
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/full_function_emitter.h
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/strict_translator.cpp
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/strict_translator.h
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/pgxp_hook_emitter.cpp
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/pgxp_hook_emitter.h
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/function_discovery.cpp
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/src/function_discovery.h
|
||||
${PSXRECOMP_CODEGEN_HASH_ROOT}/recompiler/include/gte_register_classification.h
|
||||
|
||||
@@ -86,6 +86,13 @@
|
||||
* model participation with queue-safe guard/hysteresis policy. */
|
||||
/* v21: ws_angle_widen forwarder for exact, aspect-scaled 12-bit terrain
|
||||
* frustum half-angle constants. */
|
||||
/* (PGXP, no version bump): OverlayCallbacks grew an appended-last PGXPHooks
|
||||
* table pointer (pgxp_hooks.h). Only pgxp-flavour shards (compiled with
|
||||
* -DPSX_PGXP=1, PSX_OVERLAY_FLAVOR_PGXP set) reference the psx_pgxp_*
|
||||
* forwarders, and the flavor half of the ABI tag already rejects any
|
||||
* host/DLL flavor mix — base-flavour DLLs and hosts are untouched, so
|
||||
* the version stays. The emit-content change (PGXP_*() macros in all
|
||||
* generated C) is covered by the codegen hash + CODEGEN_VER below. */
|
||||
#define PSX_OVERLAY_ABI_VERSION 21
|
||||
|
||||
/* Process-lifetime overlay candidate capacity. Every accepted manifest F
|
||||
@@ -113,6 +120,12 @@
|
||||
#define PSX_OVERLAY_FLAVOR 0
|
||||
#endif
|
||||
|
||||
/* Flavor bits (compose bitwise; base = 0):
|
||||
* 1 — widescreen variant codegen
|
||||
* 2 — PGXP precision shadowing (-DPSX_PGXP=1 objects; pgxp_hooks.h) */
|
||||
#define PSX_OVERLAY_FLAVOR_WIDESCREEN 1
|
||||
#define PSX_OVERLAY_FLAVOR_PGXP 2
|
||||
|
||||
/* Combined tag exported by overlay_abi() and checked by the loader. */
|
||||
#define PSX_OVERLAY_ABI_TAG \
|
||||
((int)((PSX_OVERLAY_ABI_VERSION & 0xFFFF) | ((PSX_OVERLAY_FLAVOR & 0xFFFF) << 16)))
|
||||
@@ -139,8 +152,11 @@
|
||||
* masked-16-bit screen-X, and depth cull emits.
|
||||
* 7: + configured side-plane normal-X (plane_nx) cull emit.
|
||||
* 8: + configured per-primitive bound-load (xclip) cull emit.
|
||||
* 9: + full-word-guarded object/model participation compare emit. */
|
||||
#define PSX_OVERLAY_CODEGEN_VER 9
|
||||
* 9: + full-word-guarded object/model participation compare emit.
|
||||
* 10: + PGXP dataflow-shadowing PGXP_*() macro emission at loads/stores/
|
||||
* COP2 transfers/precision-carrying ALU ops (pgxp_hooks.h; no-ops in
|
||||
* base-flavour objects, real hooks under -DPSX_PGXP=1). */
|
||||
#define PSX_OVERLAY_CODEGEN_VER 10
|
||||
|
||||
/* 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>/)
|
||||
|
||||
@@ -265,4 +265,27 @@ uint32_t psx_ws_angle_widen(uint32_t vanilla) {
|
||||
void gte_precision_store_word(uint32_t addr, uint8_t reg) {
|
||||
if (g_cbs.gte_precision_store_word) g_cbs.gte_precision_store_word(addr, reg);
|
||||
}
|
||||
#if defined(PSX_PGXP) && PSX_PGXP
|
||||
/* PGXP dataflow-shadowing hooks (pgxp_hooks.h; ENHANCEMENTS.md G1.10). Only a
|
||||
* pgxp-flavour shard (compiled -DPSX_PGXP=1, flavor bit set) references these
|
||||
* — base-flavour shards preprocess the PGXP_*() macros away, so this block is
|
||||
* compiled out of them entirely. Forwards to the host engine through the
|
||||
* appended-last table; every hook no-ops on a host that predates it
|
||||
* (precision shadowing is visual-only, never load-bearing). */
|
||||
void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) {
|
||||
if (g_cbs.pgxp && g_cbs.pgxp->load) g_cbs.pgxp->load(cpu, instr, addr, value);
|
||||
}
|
||||
void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) {
|
||||
if (g_cbs.pgxp && g_cbs.pgxp->store) g_cbs.pgxp->store(cpu, instr, addr, value);
|
||||
}
|
||||
void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2) {
|
||||
if (g_cbs.pgxp && g_cbs.pgxp->alu) g_cbs.pgxp->alu(cpu, instr, result, s1, s2);
|
||||
}
|
||||
void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2) {
|
||||
if (g_cbs.pgxp && g_cbs.pgxp->muldiv) g_cbs.pgxp->muldiv(cpu, instr, hi, lo, s1, s2);
|
||||
}
|
||||
void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr) {
|
||||
if (g_cbs.pgxp && g_cbs.pgxp->cop2) g_cbs.pgxp->cop2(cpu, instr, value, addr);
|
||||
}
|
||||
#endif /* PSX_PGXP */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
@@ -92,6 +92,8 @@ typedef struct PGXPStats {
|
||||
uint64_t trunc_reject; /* integer part disagreed with native parse */
|
||||
uint64_t tolerance_reject;
|
||||
uint64_t w_valid; /* lookups that also carried a usable depth */
|
||||
uint64_t produced; /* RTPS/RTPT projections pushed into shadows */
|
||||
uint64_t swc2_stores; /* GTE reg shadows copied to RAM shadows */
|
||||
} PGXPStats;
|
||||
|
||||
void pgxp_get_stats(PGXPStats *out);
|
||||
@@ -115,6 +117,8 @@ void pgxp_test_get_gte_sxy(uint32_t index, uint32_t *packed,
|
||||
int32_t *x16, int32_t *y16, uint16_t *z,
|
||||
uint8_t *valid);
|
||||
uint32_t pgxp_test_generation(void);
|
||||
uint32_t pgxp_test_suppress_depth(void);
|
||||
int pgxp_test_active(void);
|
||||
void pgxp_test_set_generation(uint32_t gen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+46
-8
@@ -761,7 +761,13 @@ function(psxrecomp_ensure_zlib)
|
||||
endfunction()
|
||||
|
||||
function(psxrecomp_add_runtime_target target)
|
||||
set(options ORACLE COSIM)
|
||||
# PGXP: build this target's objects with -DPSX_PGXP=1 so the PGXP_*()
|
||||
# hook macros the emitter writes into ALL generated C become real calls
|
||||
# into the value-propagation engine (pgxp_hooks.h; ENHANCEMENTS.md G1.10),
|
||||
# and stamp the pgxp overlay flavor so the shard cache and the ABI gate
|
||||
# keep pgxp and base DLLs fully separate. The base target is untouched —
|
||||
# the macros preprocess away without the define.
|
||||
set(options ORACLE COSIM PGXP)
|
||||
set(oneValueArgs
|
||||
GAME_GENERATED_DISPATCH_C
|
||||
GAME_OVERLAY_STATIC_C
|
||||
@@ -917,6 +923,15 @@ function(psxrecomp_add_runtime_target target)
|
||||
if(PSXRT_ORACLE)
|
||||
set(_psxrt_exe_name "${_psxrt_exe_name}_oracle")
|
||||
endif()
|
||||
if(PSXRT_PGXP)
|
||||
# Distinct binary beside the base one; the launcher (or the player)
|
||||
# picks the variant. Same debug port as the base build — run one at a
|
||||
# time (the A/B protocol is one-toggle-per-run anyway).
|
||||
set(_psxrt_exe_name "${_psxrt_exe_name}_pgxp")
|
||||
target_compile_definitions(${target} PRIVATE
|
||||
PSX_PGXP=1
|
||||
PSX_OVERLAY_FLAVOR=2) # PSX_OVERLAY_FLAVOR_PGXP (overlay_api.h)
|
||||
endif()
|
||||
set_target_properties(${target} PROPERTIES OUTPUT_NAME "${_psxrt_exe_name}")
|
||||
|
||||
# ---- Windows / desktop app icon ---------------------------------------
|
||||
@@ -1790,20 +1805,41 @@ function(psxrecomp_add_game_runtime target)
|
||||
GAME_GENERATED_DISPATCH_C "${_psxg_marker}"
|
||||
${_psxg_rt_args}
|
||||
)
|
||||
set(_psxg_targets ${target})
|
||||
# PGXP variant (ENHANCEMENTS.md G1.10): the SAME generated sources
|
||||
# compiled a second time with -DPSX_PGXP=1 into <exe>_pgxp.exe. The
|
||||
# emitted PGXP_*() macros become real hook calls, arming true
|
||||
# geometry correction; the base target is byte-for-byte the
|
||||
# pre-feature build. Opt-in per configure — it roughly doubles the
|
||||
# generated-C compile time.
|
||||
option(PSX_PGXP_VARIANT
|
||||
"Also build the <exe>_pgxp PGXP precision-shadowing variant" OFF)
|
||||
if(PSX_PGXP_VARIANT)
|
||||
psxrecomp_add_runtime_target(${target}-pgxp
|
||||
PGXP
|
||||
GAME_GENERATED_FULL_C ${_psxg_full_list}
|
||||
GAME_GENERATED_DISPATCH_C "${_psxg_marker}"
|
||||
${_psxg_rt_args}
|
||||
)
|
||||
list(APPEND _psxg_targets ${target}-pgxp)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS
|
||||
"psxrecomp: setup host (no game C, no BIOS backends) — "
|
||||
"first-run Generate & rebuild")
|
||||
psxrecomp_add_runtime_target(${target} ${_psxg_rt_args})
|
||||
set(_psxg_targets ${target})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${target} PRIVATE PSX_HAS_GAME_CODEGEN=1)
|
||||
foreach(_psxg_t IN LISTS _psxg_targets)
|
||||
target_compile_definitions(${_psxg_t} PRIVATE PSX_HAS_GAME_CODEGEN=1)
|
||||
|
||||
if(PSX_NET_LOBBY_DEFAULT_URL)
|
||||
# Stringify for C: PSX_NET_LOBBY_DEFAULT_URL="ws://..."
|
||||
target_compile_definitions(${target} PRIVATE
|
||||
"PSX_NET_LOBBY_DEFAULT_URL=\"${PSX_NET_LOBBY_DEFAULT_URL}\"")
|
||||
endif()
|
||||
if(PSX_NET_LOBBY_DEFAULT_URL)
|
||||
# Stringify for C: PSX_NET_LOBBY_DEFAULT_URL="ws://..."
|
||||
target_compile_definitions(${_psxg_t} PRIVATE
|
||||
"PSX_NET_LOBBY_DEFAULT_URL=\"${PSX_NET_LOBBY_DEFAULT_URL}\"")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Include the portable codegen host. Do NOT add CMAKE_CURRENT_SOURCE_DIR
|
||||
# wholesale to -I: on case-insensitive macOS, #include <version> can pick
|
||||
@@ -1816,5 +1852,7 @@ function(psxrecomp_add_game_runtime target)
|
||||
if(RECOMP_UI_ROOT)
|
||||
list(APPEND _psxg_inc "${RECOMP_UI_ROOT}/src")
|
||||
endif()
|
||||
target_include_directories(${target} PRIVATE ${_psxg_inc})
|
||||
foreach(_psxg_t IN LISTS _psxg_targets)
|
||||
target_include_directories(${_psxg_t} PRIVATE ${_psxg_inc})
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
@@ -5212,7 +5212,8 @@ static void handle_geom_correction(int id, const char *json)
|
||||
"\"pgxp\":{\"enabled\":%d,\"cpu_mode\":%d,\"tolerance\":%.3f,"
|
||||
"\"lookups\":%llu,\"dataflow_hit\":%llu,\"fallback_hit\":%llu,"
|
||||
"\"native\":%llu,\"value_mismatch\":%llu,\"trunc_reject\":%llu,"
|
||||
"\"tolerance_reject\":%llu,\"w_valid\":%llu}}",
|
||||
"\"tolerance_reject\":%llu,\"w_valid\":%llu,"
|
||||
"\"produced\":%llu,\"swc2_stores\":%llu}}",
|
||||
id,
|
||||
gte_geometry_correction_enabled(),
|
||||
(unsigned)hits,
|
||||
@@ -5226,7 +5227,9 @@ static void handle_geom_correction(int id, const char *json)
|
||||
(unsigned long long)ps.value_mismatch,
|
||||
(unsigned long long)ps.trunc_reject,
|
||||
(unsigned long long)ps.tolerance_reject,
|
||||
(unsigned long long)ps.w_valid);
|
||||
(unsigned long long)ps.w_valid,
|
||||
(unsigned long long)ps.produced,
|
||||
(unsigned long long)ps.swc2_stores);
|
||||
}
|
||||
|
||||
/* pgxp — live-tune the value-propagation engine for one-toggle isolation runs
|
||||
@@ -5260,8 +5263,9 @@ static void handle_pgxp(int id, const char *json)
|
||||
pgxp_set_tolerance((float)strtod(p, NULL));
|
||||
}
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"enabled\":%d,\"cpu_mode\":%d,"
|
||||
"\"tolerance\":%.3f}",
|
||||
id, pgxp_enabled(), pgxp_cpu_mode(), (double)pgxp_tolerance());
|
||||
"\"tolerance\":%.3f,\"suppress\":%u,\"active\":%d}",
|
||||
id, pgxp_enabled(), pgxp_cpu_mode(), (double)pgxp_tolerance(),
|
||||
(unsigned)pgxp_test_suppress_depth(), pgxp_test_active());
|
||||
}
|
||||
|
||||
static void handle_gpu_state(int id, const char *json)
|
||||
|
||||
@@ -12123,6 +12123,16 @@ session_reboot:
|
||||
/* Sub-pixel vertex precision + perspective-correct UVs. Both default off;
|
||||
* with both off every setter below leaves the tracking caches disabled and
|
||||
* the draw path is the faithful integer one, unchanged. */
|
||||
/* Env overrides (debug/validation path, like PSX_BIOS_HLE): arm the
|
||||
* corrections from process start so free-running (headless) boots can be
|
||||
* measured from the first projected vertex — a TCP toggle always arrives
|
||||
* after the interesting window. '0' = off, anything else = on. */
|
||||
if (const char* e = std::getenv("PSX_GEOMETRY_CORRECTION"))
|
||||
g_video_geometry_correction = (*e && *e != '0') ? 1 : 0;
|
||||
if (const char* e = std::getenv("PSX_PERSPECTIVE_TEXTURING"))
|
||||
g_video_perspective_texturing = (*e && *e != '0') ? 1 : 0;
|
||||
if (const char* e = std::getenv("PSX_PGXP_CPU_MODE"))
|
||||
g_video_pgxp_cpu_mode = (*e && *e != '0') ? 1 : 0;
|
||||
gte_geometry_correction_set(g_video_geometry_correction);
|
||||
gpu_texture_correction_set(g_video_perspective_texturing);
|
||||
pgxp_set_cpu_mode(g_video_pgxp_cpu_mode);
|
||||
|
||||
@@ -1727,9 +1727,9 @@ static void warn_on_cgtag_mismatch(const char *tier) {
|
||||
snprintf(base, sizeof base, "%s/%s/%s/%s",
|
||||
s_cache_dir, s_game_id, tier, PSX_OVERLAY_ARCH_ABI);
|
||||
char expect[64];
|
||||
snprintf(expect, sizeof expect, "cg%d_%08x_gc%08x",
|
||||
snprintf(expect, sizeof expect, "cg%d_%08x_gc%08x_f%u",
|
||||
PSX_OVERLAY_CODEGEN_VER, (unsigned)PSX_OVERLAY_CODEGEN_HASH,
|
||||
(unsigned)s_config_hash);
|
||||
(unsigned)s_config_hash, (unsigned)PSX_OVERLAY_FLAVOR);
|
||||
snprintf(pattern, sizeof pattern, "%s/cg*", base);
|
||||
WIN32_FIND_DATAA fd;
|
||||
HANDLE h = FindFirstFileA(pattern, &fd);
|
||||
@@ -1755,9 +1755,9 @@ static void warn_on_cgtag_mismatch(const char *tier) {
|
||||
char base[768], expect[64], found[256];
|
||||
snprintf(base, sizeof base, "%s/%s/%s/%s",
|
||||
s_cache_dir, s_game_id, tier, PSX_OVERLAY_ARCH_ABI);
|
||||
snprintf(expect, sizeof expect, "cg%d_%08x_gc%08x",
|
||||
snprintf(expect, sizeof expect, "cg%d_%08x_gc%08x_f%u",
|
||||
PSX_OVERLAY_CODEGEN_VER, (unsigned)PSX_OVERLAY_CODEGEN_HASH,
|
||||
(unsigned)s_config_hash);
|
||||
(unsigned)s_config_hash, (unsigned)PSX_OVERLAY_FLAVOR);
|
||||
if (psx_overlay_posix_find_other_cache_tag(base, expect, found,
|
||||
sizeof(found))) {
|
||||
loader_log("*** OVERLAY CACHE HASH MISMATCH: this build reads %s/%s but "
|
||||
@@ -1906,14 +1906,16 @@ static void scan_cache_dir(void) {
|
||||
/* Index both tiers and every immutable artifact. Runtime selection prefers
|
||||
* usable GCC over TCC; an invalid GCC artifact cannot suppress a valid TCC
|
||||
* fallback merely because its filename was enumerated first. */
|
||||
snprintf(dir, sizeof(dir), "%s/%s/gcc/%s/cg%d_%08x_gc%08x",
|
||||
snprintf(dir, sizeof(dir), "%s/%s/gcc/%s/cg%d_%08x_gc%08x_f%u",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER,
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH, (unsigned)s_config_hash);
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH, (unsigned)s_config_hash,
|
||||
(unsigned)PSX_OVERLAY_FLAVOR);
|
||||
scan_one_cache_dir(dir, CACHE_TIER_GCC);
|
||||
abi_preflight_sweep(dir);
|
||||
snprintf(dir, sizeof(dir), "%s/%s/tcc/%s/cg%d_%08x_gc%08x",
|
||||
snprintf(dir, sizeof(dir), "%s/%s/tcc/%s/cg%d_%08x_gc%08x_f%u",
|
||||
s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER,
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH, (unsigned)s_config_hash);
|
||||
(unsigned)PSX_OVERLAY_CODEGEN_HASH, (unsigned)s_config_hash,
|
||||
(unsigned)PSX_OVERLAY_FLAVOR);
|
||||
scan_one_cache_dir(dir, CACHE_TIER_TCC);
|
||||
abi_preflight_sweep(dir);
|
||||
|
||||
@@ -2355,6 +2357,21 @@ static void init_callbacks(void) {
|
||||
extern uint32_t psx_ws_angle_widen(uint32_t vanilla);
|
||||
s_callbacks.ws_angle_widen = psx_ws_angle_widen;
|
||||
}
|
||||
/* PGXP dataflow-shadowing hook table (pgxp_hooks.h, appended last).
|
||||
* Referenced only by pgxp-flavour shards; the flavor half of the ABI
|
||||
* tag already rejects any host/DLL flavor mix, and a NULL table on an
|
||||
* older host makes every hook a no-op (visual-only, never
|
||||
* load-bearing). */
|
||||
{
|
||||
static const PGXPHooks pgxp_hooks_table = {
|
||||
psx_pgxp_load,
|
||||
psx_pgxp_store,
|
||||
psx_pgxp_alu,
|
||||
psx_pgxp_muldiv,
|
||||
psx_pgxp_cop2,
|
||||
};
|
||||
s_callbacks.pgxp = &pgxp_hooks_table;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -631,6 +631,7 @@ extern "C" void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr,
|
||||
extern "C" void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3,
|
||||
uint32_t packed) {
|
||||
if (!g_pgxp_active) return;
|
||||
s_stats.produced++;
|
||||
s_gte[12] = s_gte[13];
|
||||
s_gte[13] = s_gte[14];
|
||||
PGXPValue *pv = &s_gte[14];
|
||||
@@ -735,6 +736,7 @@ extern "C" void pgxp_store_gte_reg(uint32_t addr, uint8_t reg) {
|
||||
if (!dst) return;
|
||||
const PGXPValue *src = &s_gte[reg & 31u];
|
||||
if (src->gen != s_gen) return;
|
||||
s_stats.swc2_stores++;
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
@@ -769,6 +771,9 @@ extern "C" void pgxp_test_get_gte_sxy(uint32_t index, uint32_t *packed,
|
||||
|
||||
extern "C" uint32_t pgxp_test_generation(void) { return s_gen; }
|
||||
|
||||
extern "C" uint32_t pgxp_test_suppress_depth(void) { return s_suppress; }
|
||||
extern "C" int pgxp_test_active(void) { return g_pgxp_active; }
|
||||
|
||||
extern "C" void pgxp_test_set_generation(uint32_t gen) { s_gen = gen; }
|
||||
|
||||
/* Address-keyed depth lookup for the shipped perspective-texturing path
|
||||
|
||||
@@ -4068,6 +4068,9 @@ def _compile_dll_tcc(c_path: str, out_dll: str, include_dirs, flavor: int,
|
||||
'-DPSX_NO_DEBUG_TOOLS',
|
||||
'-DPSX_ENABLE_BLOCK_CYCLES=1',
|
||||
f'-DPSX_OVERLAY_FLAVOR={int(flavor)}',
|
||||
# PGXP flavor bit (overlay_api.h PSX_OVERLAY_FLAVOR_PGXP): arm the
|
||||
# PGXP_*() hook macros the emitter writes into every overlay C.
|
||||
*(['-DPSX_PGXP=1'] if int(flavor) & 2 else []),
|
||||
native_path(c_path), '-o', native_path(out_dll)]
|
||||
for d in include_dirs:
|
||||
cmd.append('-I' + native_path(_bom_free_incdir(d)))
|
||||
@@ -4116,6 +4119,9 @@ def _compile_dll_direct(c_path: str, out_dll: str, include_dirs: list[str],
|
||||
# cache and a base cache can never cross-contaminate even if they share
|
||||
# a directory (they key by guest-bytes CRC, which is flavor-blind).
|
||||
f'-DPSX_OVERLAY_FLAVOR={int(flavor)}',
|
||||
# PGXP flavor bit (overlay_api.h PSX_OVERLAY_FLAVOR_PGXP): arm the
|
||||
# PGXP_*() hook macros the emitter writes into every overlay C.
|
||||
*(['-DPSX_PGXP=1'] if int(flavor) & 2 else []),
|
||||
c_path,
|
||||
'-o', out_dll,
|
||||
*includes,
|
||||
@@ -5303,7 +5309,7 @@ def main():
|
||||
ch = codegen_hash(args.runtime_include)
|
||||
gh = overlay_config_hash(args.recompiler, args.game_toml)
|
||||
cache_dir = os.path.join(args.out_dir, game_id, args.compiler, cache_arch_abi(),
|
||||
f'cg{cg}_{ch:08x}_gc{gh:08x}')
|
||||
f'cg{cg}_{ch:08x}_gc{gh:08x}_f{int(args.flavor)}')
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
print(f'Cache dir: {cache_dir} '
|
||||
f'(codegen ver {cg}, emitter {ch:08x}, config {gh:08x})')
|
||||
|
||||
Reference in New Issue
Block a user