mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
139 lines
5.6 KiB
C
139 lines
5.6 KiB
C
/* Render-pass time freeze (psx_cycle_freeze.h psx_cycle_freeze_begin/end).
|
|
* A pass counts guest cycles but never services a device, and afterwards
|
|
* every clock value is exactly as before. Build/run:
|
|
* ctest -R render_pass_freeze_test */
|
|
|
|
#include "psx_cycles.h"
|
|
#include "psx_cycle_freeze.h"
|
|
|
|
#include <setjmp.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
int g_ls_replay_active = 0;
|
|
int g_ls_mode = 0;
|
|
int g_precise_mode = 0;
|
|
int g_psx_call_bail = 0;
|
|
uint32_t i_mask = 0;
|
|
uint64_t g_guest_store_count = 0;
|
|
uint64_t g_mmio_access_count = 0;
|
|
|
|
/* Mock devices: count every advance and event. The "CD" raises an event
|
|
* every 1000 cycles; the VBlank every 564480. */
|
|
static uint64_t s_dev_cycles = 0;
|
|
static uint64_t s_dev_calls = 0;
|
|
static uint64_t s_cd_events = 0;
|
|
static uint32_t s_cd_phase = 0;
|
|
static uint64_t s_vblank_phase = 0;
|
|
|
|
void sio_advance(uint32_t cycles) { (void)cycles; }
|
|
void cdrom_advance(uint32_t cycles) {
|
|
s_dev_calls++;
|
|
s_dev_cycles += cycles;
|
|
s_cd_phase += cycles;
|
|
while (s_cd_phase >= 1000u) { s_cd_phase -= 1000u; s_cd_events++; }
|
|
}
|
|
void dma_advance(uint32_t cycles) { (void)cycles; }
|
|
void timers_advance(uint32_t cycles) { (void)cycles; }
|
|
void interrupts_advance_cycles(uint32_t cycles) { s_vblank_phase += cycles; }
|
|
void interrupts_service_scheduled_events(void) {}
|
|
|
|
uint32_t interrupts_cycles_to_vblank(void) {
|
|
return (uint32_t)(564480u - (s_vblank_phase % 564480u));
|
|
}
|
|
uint32_t timers_cycles_to_irq(uint32_t mask) { (void)mask; return UINT32_MAX; }
|
|
uint32_t cdrom_cycles_to_irq(uint32_t mask) {
|
|
(void)mask;
|
|
return 1000u - s_cd_phase;
|
|
}
|
|
uint32_t dma_cycles_to_internal_event(void) { return UINT32_MAX; }
|
|
uint32_t dma_cycles_to_deliverable_irq(uint32_t mask) { (void)mask; return UINT32_MAX; }
|
|
uint32_t sio_cycles_to_irq(uint32_t mask) { (void)mask; return UINT32_MAX; }
|
|
uint32_t psx_spu_sample_event_cycles_to_next(void) { return UINT32_MAX; }
|
|
void psx_spu_sample_event_service(void) {}
|
|
int psx_get_in_exception(void) { return 0; }
|
|
void starvation_watchdog_check(void) {}
|
|
void starvation_ring_pc_sample(void) {}
|
|
/* memory.c mod arenas (render_pass_mod_store): nothing journaled here. */
|
|
void render_pass_mod_arenas_rollback(void) {}
|
|
uint64_t render_pass_mod_arenas_hash(void) { return 0; }
|
|
int psx_netplay_active(void) { return 0; }
|
|
int psx_selfcheck_enabled(void) { return 0; }
|
|
void dirty_ram_ld_delay_discard(void) {}
|
|
void dirty_ram_irq_ambient_resync_after_restore(void) {}
|
|
|
|
static int failures;
|
|
#define CHECK(c, m) do { if (!(c)) { fprintf(stderr, "FAIL: %s\n", m); failures++; } } while (0)
|
|
|
|
static jmp_buf s_jmp;
|
|
static int s_overruns;
|
|
static void overrun(void) { s_overruns++; longjmp(s_jmp, 1); }
|
|
|
|
int main(void) {
|
|
PsxCycleFreeze save;
|
|
uint64_t cyc0, next0, dev_calls0, dev_cycles0, cd0;
|
|
uint32_t local = 7;
|
|
|
|
psx_cycles_resync_after_restore(NULL);
|
|
psx_advance_cycles(12345); /* some live history */
|
|
g_psx_cyc_batch = 40; /* deferred charges of the guest code */
|
|
g_psx_cyc_local_acc = &local;
|
|
cyc0 = psx_cycle_count;
|
|
next0 = psx_next_service_cycle;
|
|
dev_calls0 = s_dev_calls;
|
|
dev_cycles0 = s_dev_cycles;
|
|
cd0 = s_cd_events;
|
|
|
|
CHECK(psx_cycle_freeze_begin(&save, 0, NULL), "freeze begins");
|
|
CHECK(g_psx_render_pass_active == 1, "pass flag set");
|
|
CHECK(!psx_cycle_freeze_begin(&save, 0, NULL), "freezes do not nest");
|
|
CHECK(g_psx_cyc_batch == 0 && g_psx_cyc_local_acc == NULL,
|
|
"the guest's pending charges are parked, not published");
|
|
for (int i = 0; i < 1000; i++) psx_advance_cycles(1000); /* 10^6 cycles */
|
|
psx_advance_cycles_slow(5000);
|
|
psx_devices_service_to_now();
|
|
psx_devices_mmio_sync();
|
|
CHECK(psx_cycle_count - cyc0 == 1005000u,
|
|
"cycles are still counted inside the pass (GTE/muldiv deadlines)");
|
|
CHECK(s_dev_calls == dev_calls0 && s_dev_cycles == dev_cycles0 &&
|
|
s_cd_events == cd0,
|
|
"no device advanced and no device event fired in a pass");
|
|
psx_cycle_freeze_end(&save);
|
|
CHECK(g_psx_render_pass_active == 0, "pass flag cleared");
|
|
CHECK(psx_cycle_count == cyc0, "cycle count restored exactly");
|
|
CHECK(psx_next_service_cycle == next0, "service deadline restored exactly");
|
|
CHECK(g_psx_cyc_batch == 40 && g_psx_cyc_local_acc == &local && local == 7,
|
|
"the guest's pending charges come back untouched");
|
|
|
|
/* The live timeline continues exactly as if the pass never happened. */
|
|
g_psx_cyc_batch = 0;
|
|
g_psx_cyc_local_acc = NULL;
|
|
psx_advance_cycles(3000);
|
|
CHECK(s_cd_events == cd0 + (uint64_t)((12345u % 1000u + 3000u) / 1000u) ||
|
|
s_cd_events >= cd0 + 3u,
|
|
"devices resume on the live clock after the pass");
|
|
|
|
/* Watchdog: a pass that runs away is cut off. The longjmp skips the
|
|
* bb_defer exits of the generated frames it leaves (their cleanup
|
|
* handlers never run); end() puts the interrupted depth back. */
|
|
s_overruns = 0;
|
|
cyc0 = psx_cycle_count;
|
|
g_psx_cyc_bb_defer = 2;
|
|
if (setjmp(s_jmp) == 0) {
|
|
CHECK(psx_cycle_freeze_begin(&save, 200000u, overrun), "freeze with watchdog");
|
|
g_psx_cyc_bb_defer += 3; /* three nested generated frames */
|
|
for (int i = 0; i < 1000; i++) psx_advance_cycles(1000);
|
|
CHECK(0, "watchdog should have fired");
|
|
}
|
|
psx_cycle_freeze_end(&save);
|
|
CHECK(s_overruns == 1, "watchdog fired once");
|
|
CHECK(psx_cycle_count == cyc0 && !g_psx_render_pass_active,
|
|
"state restored after a watchdog abort");
|
|
CHECK(g_psx_cyc_bb_defer == 2,
|
|
"cycle deferral depth restored after a watchdog abort");
|
|
g_psx_cyc_bb_defer = 0;
|
|
|
|
printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
|
|
return failures ? 1 : 0;
|
|
}
|