mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
91cd6db705
Review of #561. Guest time now has two entry points (psx_cycles.h): the CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion, i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale, and the unscaled device advance psx_advance_cycles() (DMA, idle skips, mul/div and GTE deadline waits). The emitters' block-mode charges emit psx_cpu_charge; overlay DLLs accumulate it raw and publish through the cpu_charge callback (was advance_cycles, same slot) into the same host function, so nothing is scaled at the DLL boundary. The batched psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache refills and bios_hle.c all charge through it. At scale 1 it is exactly psx_advance_cycles. Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged at every VBlank edge (deterministic, works online, no mod needed), plus psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or to tie the scale to a mod (guest_cycle_scale_gated = true; every mod session reset shuts it). The carried fraction and both gate states are a boot_state section (BS_SEC_GCS, written only when the scale is not 1), so savestates and the netplay rollback snapshot re-simulate the same cycles. psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback re-simulation against the real psx_cycles.c. OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
4.5 KiB
Python
101 lines
4.5 KiB
Python
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
class OverlayWidescreenCallbacks(unittest.TestCase):
|
|
def test_native_module_forwards_screen_bounds_and_entry_hooks(self):
|
|
root = Path(__file__).resolve().parents[2]
|
|
gcc = shutil.which('gcc') or ('C:/msys64/mingw64/bin/gcc.exe' if os.name == 'nt' else None)
|
|
if not gcc or not Path(gcc).exists():
|
|
self.skipTest('C compiler unavailable')
|
|
source = '''
|
|
#define PSX_OVERLAY_DLL_BUILD 1
|
|
#define PSX_OVERLAY_EXPORT
|
|
#include "overlay_dispatch_preamble.c.inc"
|
|
#include <assert.h>
|
|
static CPUState *seen_cpu;
|
|
static uint32_t seen_address, cycles;
|
|
static int32_t bound(int32_t x) { return x * 2; }
|
|
static int margin120(void) { return 120; }
|
|
static int packed_reject(uint32_t flags, uint32_t mask) { return (flags & mask) != 0; }
|
|
static int nclip_branch(uint32_t pc, uint32_t word, int32_t mac0, int vanilla) {
|
|
assert(pc == 0x80021EAC && word == 0x1900FFCF && mac0 == 0 && vanilla == 1);
|
|
return 0;
|
|
}
|
|
static void advance(uint32_t n) { cycles += n; }
|
|
static int entry(CPUState *cpu, uint32_t address) {
|
|
assert(cycles == 17); seen_cpu = cpu; seen_address = address;
|
|
cpu->pc = cpu->gpr[31]; return 1;
|
|
}
|
|
int main(void) {
|
|
CPUState cpu = {0};
|
|
OverlayCallbacks callbacks = {0};
|
|
callbacks.ws_screen_x_bound = bound;
|
|
callbacks.mod_function_entry = entry;
|
|
callbacks.cpu_charge = advance;
|
|
overlay_init(&callbacks);
|
|
assert(overlay_abi() == PSX_OVERLAY_ABI_TAG);
|
|
assert(psx_ws_screen_x_bound(-256) == -512);
|
|
assert(psx_ws_screen_x_bound(256) == 512);
|
|
psx_advance_cycles(17);
|
|
cpu.gpr[31] = 0x80010000;
|
|
assert(psx_mod_function_entry(&cpu, 0x80045770) == 1);
|
|
assert(cpu.pc == 0x80010000);
|
|
assert(seen_cpu == &cpu && seen_address == 0x80045770);
|
|
callbacks.ws_screen_x_bound = 0; callbacks.mod_function_entry = 0;
|
|
overlay_init(&callbacks);
|
|
assert(psx_ws_screen_x_bound(-256) == -256);
|
|
assert(psx_mod_function_entry(&cpu, 0) == 0);
|
|
assert(seen_address == 0x80045770);
|
|
/* bgez_sites / clip_edge_x_load_sites in overlay code: identity with no
|
|
* margin callback (4:3 host), widened by the live margin with one. */
|
|
assert(psx_ws_cull_bgez(0u) == 1 && psx_ws_cull_bgez((uint32_t)-1) == 0);
|
|
assert(psx_ws_clip_edge_x(0u, 0x140u) == 0u);
|
|
assert(psx_ws_clip_edge_x(0x140u, 0x140u) == 0x140u);
|
|
/* screen_x_sites on lui / bltz: a screen X in the high half. */
|
|
assert(psx_ws_cull_lui_hi(0x0200u) == (0x0200u << 16));
|
|
assert(psx_ws_cull_bltz_hi((uint32_t)-1 << 16) == 1 && psx_ws_cull_bltz_hi(0u) == 0);
|
|
callbacks.ws_x_margin = margin120;
|
|
overlay_init(&callbacks);
|
|
assert(psx_ws_cull_bgez((uint32_t)-120) == 1);
|
|
assert(psx_ws_cull_bgez((uint32_t)-121) == 0);
|
|
assert(psx_ws_clip_edge_x(0u, 0x140u) == (uint32_t)-120);
|
|
assert(psx_ws_clip_edge_x(0x140u, 0x140u) == 0x140u + 120u);
|
|
assert(psx_ws_clip_edge_x(98u, 0x140u) == 98u); /* interior viewport edge */
|
|
assert(psx_ws_clip_edge_x(222u, 0x140u) == 222u);
|
|
assert(psx_ws_cull_lui_hi(0x0200u) == ((0x0200u + 120u) << 16));
|
|
assert(psx_ws_cull_bltz_hi((uint32_t)-120 << 16) == 0);
|
|
assert(psx_ws_cull_bltz_hi((uint32_t)-121 << 16) == 1);
|
|
/* Newly cached modules forward the guarded packed predicate. Missing
|
|
* callbacks retain the original reject, rather than widening unsafely. */
|
|
assert(psx_ws_masked_reject(0xFE00u, 0xFFFF0000u) == 1);
|
|
callbacks.ws_masked_reject = packed_reject;
|
|
overlay_init(&callbacks);
|
|
assert(psx_ws_masked_reject(0xFE00u, 0xFFFF0000u) == 0);
|
|
assert(psx_ws_masked_reject(0xFF00FE00u, 0xFFFF0000u) == 1);
|
|
assert(psx_ws_masked_reject(0u, 0xFFFF0000u) == 0);
|
|
assert(psx_ws_nclip_branch(0x80021EAC, 0x1900FFCF, 0, 1) == 1);
|
|
callbacks.ws_nclip_branch = nclip_branch;
|
|
overlay_init(&callbacks);
|
|
assert(psx_ws_nclip_branch(0x80021EAC, 0x1900FFCF, 0, 1) == 0);
|
|
return 0;
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp)
|
|
(path/'test.c').write_text(source, encoding='utf-8', newline='\n')
|
|
executable = path / ('test.exe' if os.name == 'nt' else 'test')
|
|
subprocess.run([gcc, '-std=c11', '-DPSX_ENABLE_BLOCK_CYCLES=1',
|
|
'-DPSX_NO_DEBUG_TOOLS', '-I'+str(root/'runtime/include'),
|
|
str(path/'test.c'), '-o', str(executable), '-lm'], check=True,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
subprocess.run([str(executable)], check=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|