mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 18:52:11 -04:00
FMV optimizations
This commit is contained in:
committed by
Matthew Stanley
parent
0b890cdc13
commit
7cf9958135
@@ -213,6 +213,56 @@ on a fixed region -> next.
|
||||
|
||||
## 5. Status / Log (update every session)
|
||||
|
||||
- **2026-07-21 (VLC load-charge batching — shipped; dual still ~22 ms):**
|
||||
Runtime-only batch: under `psx_next_service_cycle`, `psx_cyc_charge`
|
||||
accumulates into `g_psx_cyc_batch` (no per-insn `psx_cycle_count` store);
|
||||
flush at IRQ check / MMIO sync / savestate / advance-past-deadline.
|
||||
Absorb/fudge still per-insn; guest totals at barriers unchanged; no MotK
|
||||
regen. Dual headless MotK (`PSX_NETPLAY_TIMING=1`, pinned halves): heavy
|
||||
25–50 band host med ≈39.7 fps / guest ≈21.9 ms/f / admit ≈3.1 (guest peer
|
||||
≈39.8 / 22.5 / 2.8) — same floor as pre-batch (~21 ms / ~40–42). Counter
|
||||
publish was not the dual-peer tax; residual remains load-delay volume /
|
||||
LLC under phase-locked VLC. Next: PGO retrain after hot-path edits, or
|
||||
accept same-machine lockstep FMV floor.
|
||||
- **2026-07-21 (MotK FMV host cost — MDEC/IRQ/charge; dual still ~21 ms):**
|
||||
Aimed to cut MotK FMV host work so two lockstep peers fit ~16.7 ms/f.
|
||||
Shipped bit-exact host opts: MDEC MB output reserve (no per-byte
|
||||
ensure_capacity), sparse-column IDCT, ch0 DMA burst feed
|
||||
(`mdec_dma_write_words`), sticky-IRQ undeliverable early-out in
|
||||
`psx_check_interrupts`, `psx_cyc_charge` pre-deadline bump on compiled
|
||||
loads/steps. Dual headless MotK (`PSX_NETPLAY_TIMING=1`, pinned halves):
|
||||
band 25–50 fps still guest ≈21 ms/f / fps ≈40–42 (admit ≈2.5–3) — same
|
||||
floor as before. HARD_CAP 16K→64K tried, no gain (real CD/timer events
|
||||
already shorten the deadline); reverted. Residual is phase-locked dual
|
||||
VLC load-delay volume / LLC contention, not MDEC FIFO or present/admit.
|
||||
Next: emitter-level load-charge batching for VLC leaves, PGO retrain
|
||||
after these hot-path edits, or accept same-machine lockstep FMV floor.
|
||||
- **2026-07-21 (netplay FMV — lockstep guest inflation, not present/admit):**
|
||||
User A/B: two windowed offline MotK intros fine; headless netplay FMV still
|
||||
slow vs offline headless. Opt-in `PSX_NETPLAY_TIMING=1` splits the [FPS]
|
||||
line into guest ms/f vs admit ms/f. Heavy FMV (~25–50 fps samples): guest
|
||||
≈21 ms/f, admit ≈3 ms/f (median) — frame time is dominated by the guest
|
||||
quantum under phase-locked dual MDEC, not Swap and not INPUT_CONFIRM wait.
|
||||
Offline headless same stretch is ~17 ms/f (≈57 fps). Pipelined CONFIRM
|
||||
tried in recomp-net (tip publish, drain next tick) — no FPS gain on
|
||||
localhost (~41→~41.5); reverted. Present-path / half-rate work is a dead
|
||||
end for this regression. Next lever: reduce MotK FMV host cost so two
|
||||
aligned peers fit a 16.7 ms budget (or accept same-machine lockstep floor).
|
||||
- **2026-07-21 (netplay FMV — restore present-before-admit):**
|
||||
User confirmed early same-machine netplay FMV was ~50–60 and gameplay
|
||||
under lockstep stays ~60 — so the rematch-safe `finish→admit→pace→present`
|
||||
order was the FMV regression (expensive depth24 CPU present after admit).
|
||||
Restored `finish→present→admit/pace` via RAII `NetplayVblankTail` (admit
|
||||
on every return path; offline still paces before present). Kept half-rate
|
||||
depth24 present skip + UDP `poll()` barrier; no `SDL_Delay(0)`. Verify
|
||||
MotK intro FPS + tick-0 arm after rebuild.
|
||||
- **2026-07-21 (netplay FMV — re-land half-rate depth24 present):**
|
||||
Windowed same-machine MotK netplay FMV was back at ~30–40 after the
|
||||
rematch-safe `finish→admit→pace→present` order. Re-landed host-only
|
||||
half-rate depth24 present: after admit, skip pace+Swap every other
|
||||
depth24 vblank (present first, then alternate); admit/barrier unchanged
|
||||
(no `SDL_Delay(0)` / present-before-admit). Offline path untouched.
|
||||
Rebuild MotK `build-release` + verify intro FPS and tick-0 arm.
|
||||
- **2026-07-21 (lobby game_version + release pins):**
|
||||
WS lobby now carries `game_version` alongside `game_name` (create/list/join).
|
||||
Server rejects `version_mismatch` / `game_mismatch`; list can filter by either.
|
||||
|
||||
@@ -38,11 +38,40 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Load-charge batching (MotK VLC): under the published deadline, accumulate
|
||||
* into g_psx_cyc_batch instead of storing psx_cycle_count every insn. Flush
|
||||
* at IRQ edges / MMIO (psx_cyc_batch_flush). Absorb/fudge state still updates
|
||||
* per insn — only the host counter publish is deferred. */
|
||||
static inline void psx_cyc_charge(uint32_t cycles) {
|
||||
if (cycles == 0u) return;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
if (__builtin_expect(g_ls_replay_active | g_event_step_conservative, 0)) {
|
||||
#else
|
||||
if (g_ls_replay_active || g_event_step_conservative) {
|
||||
#endif
|
||||
psx_advance_cycles(cycles);
|
||||
return;
|
||||
}
|
||||
if (psx_in_device_service) {
|
||||
psx_cycle_count += (uint64_t)cycles;
|
||||
return;
|
||||
}
|
||||
uint64_t next = psx_cycle_count + (uint64_t)g_psx_cyc_batch + (uint64_t)cycles;
|
||||
if (psx_next_service_cycle != 0u && next < psx_next_service_cycle) {
|
||||
uint32_t sum = g_psx_cyc_batch + cycles;
|
||||
if (sum >= g_psx_cyc_batch) { /* no uint32 wrap */
|
||||
g_psx_cyc_batch = sum;
|
||||
return;
|
||||
}
|
||||
}
|
||||
psx_advance_cycles(cycles); /* publishes any pending batch first */
|
||||
}
|
||||
|
||||
/* §1 base (Beetle cpu.cpp:795-798). */
|
||||
static inline void psx_cyc_base(CPUState* cpu) {
|
||||
uint8_t w = cpu->read_absorb_which;
|
||||
if (cpu->read_absorb[w]) cpu->read_absorb[w]--;
|
||||
else psx_advance_cycles(1u);
|
||||
else psx_cyc_charge(1u);
|
||||
}
|
||||
|
||||
/* GPR_DEPRES (Beetle cpu.cpp:702-705): zero ReadAbsorb[n] for every source/dest
|
||||
@@ -114,7 +143,7 @@ static inline uint32_t psx_cyc_load_word(CPUState* cpu, uint32_t addr,
|
||||
cpu->read_absorb_which = 0u;
|
||||
uint32_t fudge = (uint32_t)((cpu->read_fudge >> 4) & 2u);
|
||||
cpu->ld_absorb = 5u; /* main-RAM wait 3 + completion 2 */
|
||||
psx_advance_cycles(fudge + 5u);
|
||||
psx_cyc_charge(fudge + 5u);
|
||||
cpu->ld_which_t = (uint8_t)rt;
|
||||
}
|
||||
uint32_t off = phys & 0x1FFFFFu;
|
||||
@@ -146,7 +175,7 @@ static inline uint16_t psx_cyc_load_half(CPUState* cpu, uint32_t addr,
|
||||
cpu->read_absorb_which = 0u;
|
||||
uint32_t fudge = (uint32_t)((cpu->read_fudge >> 4) & 2u);
|
||||
cpu->ld_absorb = 5u;
|
||||
psx_advance_cycles(fudge + 5u);
|
||||
psx_cyc_charge(fudge + 5u);
|
||||
cpu->ld_which_t = (uint8_t)rt;
|
||||
}
|
||||
uint32_t off = phys & 0x1FFFFFu;
|
||||
|
||||
@@ -35,6 +35,12 @@ void psx_cycles_pc_sample_fire(void);
|
||||
/* Lockstep replay flag (defined in dirty_ram_interp.c). */
|
||||
extern int g_ls_replay_active;
|
||||
|
||||
/* Deferred under-deadline charges (MotK VLC load-charge batching).
|
||||
* psx_cyc_charge accumulates here; publish via psx_cyc_batch_flush /
|
||||
* psx_advance_cycles before IRQ checks, MMIO, or any cycle read that must
|
||||
* match the published counter. Guest totals at those barriers are unchanged. */
|
||||
extern uint32_t g_psx_cyc_batch;
|
||||
|
||||
/* Advance guest time. The common production path is inlined: bump the
|
||||
* counter and only service devices when the next event deadline is due.
|
||||
* Guest-visible timing is unchanged (service_to_now replays exact events).
|
||||
@@ -44,6 +50,22 @@ extern int g_ls_replay_active;
|
||||
* per-instruction charge. MotK VLC issues millions of advances/s; two add+
|
||||
* branch pairs there were pure host tax. */
|
||||
static inline void psx_advance_cycles(uint32_t cycles) {
|
||||
#if !defined(PSX_COSIM)
|
||||
if (g_psx_cyc_batch) {
|
||||
uint32_t b = g_psx_cyc_batch;
|
||||
g_psx_cyc_batch = 0;
|
||||
if (cycles <= UINT32_MAX - b) cycles += b;
|
||||
else {
|
||||
/* Extreme: publish b first, then continue with cycles. */
|
||||
psx_cycle_count += (uint64_t)b;
|
||||
if (!psx_in_device_service &&
|
||||
(psx_next_service_cycle == 0u ||
|
||||
psx_cycle_count >= psx_next_service_cycle)) {
|
||||
psx_devices_service_to_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (cycles == 0u) return;
|
||||
#if defined(PSX_COSIM)
|
||||
psx_advance_cycles_slow(cycles);
|
||||
@@ -69,7 +91,17 @@ static inline void psx_advance_cycles(uint32_t cycles) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Read accessor for telemetry. */
|
||||
/* Publish deferred charges (IRQ edge / MMIO / savestate). */
|
||||
static inline void psx_cyc_batch_flush(void) {
|
||||
#if !defined(PSX_COSIM)
|
||||
uint32_t b = g_psx_cyc_batch;
|
||||
if (!b) return;
|
||||
g_psx_cyc_batch = 0;
|
||||
psx_advance_cycles(b);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Read accessor for telemetry (includes deferred batch). */
|
||||
uint64_t psx_get_cycle_count(void);
|
||||
|
||||
/* Idle-loop cycle skip (see psx_cycles.c "Idle-loop cycle skip"). */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "boot_state.h"
|
||||
#include "overlay_api.h" /* PSX_OVERLAY_CODEGEN_HASH / _ABI_TAG / _CODEGEN_VER */
|
||||
#include "gpu_render.h" /* gr_vram_transfer_in / gr_vram_transfer_out */
|
||||
#include "psx_cycles.h"
|
||||
#include "pst_wire.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -196,6 +197,8 @@ int boot_state_save(const CPUState* cpu, uint32_t bios_checksum,
|
||||
uint8_t cyc[8];
|
||||
PstW w;
|
||||
pst_w_init(&w, cyc, sizeof cyc);
|
||||
/* Publish deferred load-charge batch before snapshotting the clock. */
|
||||
psx_cyc_batch_flush();
|
||||
ok = pst_w_u64(&w, psx_cycle_count) &&
|
||||
write_section(f, BS_SEC_CLOCK, cyc, 8);
|
||||
}
|
||||
|
||||
@@ -731,6 +731,7 @@ int psx_interrupt_delivery_needed(const CPUState* cpu) {
|
||||
}
|
||||
|
||||
void psx_check_interrupts(CPUState* cpu) {
|
||||
psx_cyc_batch_flush();
|
||||
extern int g_ls_suppress_record;
|
||||
#define PSX_CHECK_INTERRUPTS_RETURN() do { if (g_ls_suppress_record > 0) g_ls_suppress_record--; return; } while (0)
|
||||
#ifdef PSX_COSIM
|
||||
@@ -739,6 +740,24 @@ void psx_check_interrupts(CPUState* cpu) {
|
||||
#define COSIM_IRQ_NOTE(kind_) cosim_irq_note(cpu, (kind_), COSIM_IRQ_TAKE_PC(), g_dirty_safe_resume_pc, s_compiled_interrupt_resume_pc, cpu->cop0[COP0_SR])
|
||||
#endif
|
||||
|
||||
/* MotK VLC / FMV hot edge: sticky unmasked I_STAT (CD/VBlank) while
|
||||
* IEc or IM2 is clear — no architectural delivery possible. Skip the
|
||||
* mid-path bookkeeping / irq_deliver_eval that used to run every BB.
|
||||
* Guest-visible timing unchanged (same non-delivery). LTO can collapse
|
||||
* this into the VLC call sites. */
|
||||
static uint32_t s_fast_maintenance;
|
||||
if (!in_exception && !s_defer_switch_pending && (i_stat & i_mask) != 0) {
|
||||
uint32_t sr = cpu->cop0[COP0_SR];
|
||||
if (!(sr & 0x01u) || !(sr & (1u << 10))) {
|
||||
if ((++s_fast_maintenance & 0x3FFFu) == 0) {
|
||||
extern void savestate_poll(CPUState* cpu, uint32_t resume_pc);
|
||||
savestate_poll(cpu, s_compiled_interrupt_resume_pc);
|
||||
debug_server_poll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Genuine entry fast paths. Generated resident code calls this at every
|
||||
* basic-block edge, which can mean tens of millions of calls inside an FMV
|
||||
* polling loop. psx_advance_cycles() has already serviced every device
|
||||
@@ -758,7 +777,6 @@ void psx_check_interrupts(CPUState* cpu) {
|
||||
* Poll host maintenance periodically so save/load and the debug socket stay
|
||||
* responsive even when the guest spends a long time in this path. At the
|
||||
* observed 1M+ block edges/s this is sub-frame latency. */
|
||||
static uint32_t s_fast_maintenance;
|
||||
if (g_idle_skip_enabled < 0) (void)psx_idle_skip_is_enabled();
|
||||
/* COP0 software interrupts (CAUSE.IP0/IP1 vs SR.IM0/IM1, bits 8-9): raised
|
||||
* purely by guest mtc0 to CAUSE, with no INTC involvement. Games use this
|
||||
@@ -1613,7 +1631,7 @@ irq_deliver_eval:
|
||||
void psx_check_interrupts_at(CPUState* cpu, uint32_t resume_pc) {
|
||||
uint32_t prev = s_compiled_interrupt_resume_pc;
|
||||
s_compiled_interrupt_resume_pc = resume_pc;
|
||||
psx_check_interrupts(cpu);
|
||||
psx_check_interrupts(cpu); /* flushes load-charge batch on entry */
|
||||
s_compiled_interrupt_resume_pc = prev;
|
||||
}
|
||||
|
||||
|
||||
+123
-29
@@ -333,6 +333,8 @@ static std::string s_fps_base_title;
|
||||
static FramePacer s_frame_pacer = { 0 };
|
||||
static int s_turbo_present_skip = 0;
|
||||
static int s_fmv_skip_present_skip = 0;
|
||||
/* Netplay + depth24: present every other vblank (admit still every tick). */
|
||||
static int s_netplay_depth24_present_skip = 0;
|
||||
static uint32_t s_fmv_skip_last_mdec = 0;
|
||||
static int s_fmv_skip_hold = 0;
|
||||
|
||||
@@ -354,6 +356,7 @@ static void present_session_reset(void) {
|
||||
s_frame_pacer = FramePacer{ 0 };
|
||||
s_turbo_present_skip = 0;
|
||||
s_fmv_skip_present_skip = 0;
|
||||
s_netplay_depth24_present_skip = 0;
|
||||
s_fmv_skip_last_mdec = 0;
|
||||
s_fmv_skip_hold = 0;
|
||||
smooth_60_reset();
|
||||
@@ -2672,12 +2675,35 @@ static void capture_override_pad(int override_word, PsxNetPad* out) {
|
||||
out->connected = 1;
|
||||
}
|
||||
|
||||
/* Opt-in netplay barrier split: guest quantum vs admit wait (PSX_NETPLAY_TIMING=1).
|
||||
* Printed on the existing [FPS] line — no separate log files. */
|
||||
static int s_np_timing_enabled = -1;
|
||||
static uint64_t s_np_admit_ticks = 0;
|
||||
static uint64_t s_np_guest_ticks = 0;
|
||||
static uint64_t s_np_last_admit_end = 0;
|
||||
static uint64_t s_np_timing_frames = 0;
|
||||
|
||||
static int netplay_timing_on(void) {
|
||||
if (s_np_timing_enabled < 0) {
|
||||
const char *e = std::getenv("PSX_NETPLAY_TIMING");
|
||||
s_np_timing_enabled = (e && e[0] && e[0] != '0') ? 1 : 0;
|
||||
}
|
||||
return s_np_timing_enabled;
|
||||
}
|
||||
|
||||
/* Stage local pad + poll admit until published. Parks the guest fiber when
|
||||
* called from the vblank callback (or before scheduler entry). Latches one
|
||||
* sample per sim tick; stalls on INPUT_CONFIRM desync. */
|
||||
static void netplay_barrier_admit(int override) {
|
||||
if (!psx_netplay_active()) return;
|
||||
static int desync_logged = 0;
|
||||
const uint64_t admit_t0 =
|
||||
netplay_timing_on() ? SDL_GetPerformanceCounter() : 0;
|
||||
/* Guest quantum = time since previous admit returned (fiber ran). */
|
||||
if (admit_t0 && s_np_last_admit_end && admit_t0 >= s_np_last_admit_end) {
|
||||
s_np_guest_ticks += admit_t0 - s_np_last_admit_end;
|
||||
s_np_timing_frames++;
|
||||
}
|
||||
for (;;) {
|
||||
uint32_t dt = 0, lh = 0, rh = 0;
|
||||
/* Load apply/ready suppresses INPUT (and can sit silent for seconds on
|
||||
@@ -2719,6 +2745,11 @@ static void netplay_barrier_admit(int override) {
|
||||
}
|
||||
if (psx_netplay_poll_admit()) {
|
||||
desync_logged = 0;
|
||||
if (admit_t0) {
|
||||
const uint64_t t1 = SDL_GetPerformanceCounter();
|
||||
if (t1 >= admit_t0) s_np_admit_ticks += t1 - admit_t0;
|
||||
s_np_last_admit_end = t1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
@@ -3040,8 +3071,24 @@ static void sdl_vblank_present(void) {
|
||||
s_fps_base_title.c_str(), fps, speed);
|
||||
SDL_SetWindowTitle(sdl_window, title);
|
||||
}
|
||||
std::fprintf(stderr, "[FPS] game: %.1f fps (%.2fx) | frames: %llu\n",
|
||||
fps, speed, (unsigned long long)s_frame_count);
|
||||
if (netplay_timing_on() && s_np_timing_frames > 0) {
|
||||
const double invf = 1000.0 / (double)frequency;
|
||||
const double admit_ms =
|
||||
(double)s_np_admit_ticks * invf / (double)s_np_timing_frames;
|
||||
const double guest_ms =
|
||||
(double)s_np_guest_ticks * invf / (double)s_np_timing_frames;
|
||||
std::fprintf(stderr,
|
||||
"[FPS] game: %.1f fps (%.2fx) | frames: %llu | "
|
||||
"guest=%.2f ms/f admit=%.2f ms/f (n=%llu)\n",
|
||||
fps, speed, (unsigned long long)s_frame_count,
|
||||
guest_ms, admit_ms, (unsigned long long)s_np_timing_frames);
|
||||
s_np_admit_ticks = 0;
|
||||
s_np_guest_ticks = 0;
|
||||
s_np_timing_frames = 0;
|
||||
} else {
|
||||
std::fprintf(stderr, "[FPS] game: %.1f fps (%.2fx) | frames: %llu\n",
|
||||
fps, speed, (unsigned long long)s_frame_count);
|
||||
}
|
||||
std::fflush(stderr);
|
||||
s_fps_last_time = now;
|
||||
s_fps_last_frame = s_frame_count;
|
||||
@@ -3154,12 +3201,32 @@ static void sdl_vblank_present(void) {
|
||||
* (below) for the interactive present path; it still covers the turbo /
|
||||
* FMV-skip paths that early-return before pacing.
|
||||
*
|
||||
* Delay-sync netplay (pre-rematch order): finish the completed tick, then
|
||||
* admit the next tick's pads (publish → SIO) BEFORE pace/present so both
|
||||
* peers share the same input before host GL work. */
|
||||
* Delay-sync netplay host FPS (MotK FMV): finish → present → admit/pace.
|
||||
* Local Swap overlaps the peer's guest quantum; admit still runs every
|
||||
* tick via the RAII tail (including early returns that skip present).
|
||||
* Offline keeps pace-before-present. Barrier wait stays UDP poll — do
|
||||
* not pair this order with SDL_Delay(0) busy-spin (tick-0 hang). */
|
||||
struct NetplayVblankTail {
|
||||
int override_ = -1;
|
||||
bool active_ = false;
|
||||
bool skip_pace_ = false;
|
||||
explicit NetplayVblankTail(int ov)
|
||||
: override_(ov), active_(psx_netplay_active() != 0) {}
|
||||
void skip_pace() { skip_pace_ = true; }
|
||||
~NetplayVblankTail() {
|
||||
if (!active_) return;
|
||||
if (psx_return_to_lobby_requested()) return;
|
||||
netplay_barrier_admit(override_);
|
||||
if (skip_pace_ || psx_return_to_lobby_requested()) return;
|
||||
uint64_t perf_start = runtime_perf_section_begin();
|
||||
frame_pacer_wait(&s_frame_pacer, g_frame_period_ms);
|
||||
runtime_perf_section_end(perf_start, &g_runtime_perf.pacer_ticks);
|
||||
latency_ring_mark(LAT_PACED);
|
||||
}
|
||||
} netplay_tail(override);
|
||||
|
||||
if (psx_netplay_active()) {
|
||||
psx_netplay_finish_frame();
|
||||
netplay_barrier_admit(override);
|
||||
} else if (g_headless) {
|
||||
sample_headless_pad_into_sio(override);
|
||||
} else {
|
||||
@@ -3259,13 +3326,19 @@ static void sdl_vblank_present(void) {
|
||||
turbo_loads_active && g_turbo_audio_sink_enabled);
|
||||
#endif
|
||||
|
||||
if (g_headless) return;
|
||||
if (g_headless) {
|
||||
netplay_tail.skip_pace();
|
||||
return;
|
||||
}
|
||||
|
||||
/* TCP turbo is for automated validation and trace capture. It keeps the
|
||||
* simulation advancing and the debug server polling, but removes frontend
|
||||
* presentation and wall-clock pacing. */
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
if (debug_server_turbo_enabled()) return;
|
||||
if (debug_server_turbo_enabled()) {
|
||||
netplay_tail.skip_pace();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Turbo mode: while TAB is held, skip both VRAM->ARGB conversion and
|
||||
@@ -3279,7 +3352,10 @@ static void sdl_vblank_present(void) {
|
||||
const int TURBO_PRESENT_EVERY = 30;
|
||||
if (keys[SDL_SCANCODE_TAB]) {
|
||||
turbo_skip = (turbo_skip + 1) % TURBO_PRESENT_EVERY;
|
||||
if (turbo_skip != 0) return; /* skip render this frame */
|
||||
if (turbo_skip != 0) {
|
||||
netplay_tail.skip_pace();
|
||||
return; /* skip render this frame */
|
||||
}
|
||||
} else {
|
||||
turbo_skip = 0;
|
||||
}
|
||||
@@ -3297,7 +3373,10 @@ static void sdl_vblank_present(void) {
|
||||
g_turbo_loads_frames++;
|
||||
const int TL_PRESENT_EVERY = 30;
|
||||
s_turbo_present_skip = (s_turbo_present_skip + 1) % TL_PRESENT_EVERY;
|
||||
if (s_turbo_present_skip != 0) return;
|
||||
if (s_turbo_present_skip != 0) {
|
||||
netplay_tail.skip_pace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* FMV auto-skip: run uncapped (no wall-clock pacing) and suppress nearly all
|
||||
@@ -3306,28 +3385,43 @@ static void sdl_vblank_present(void) {
|
||||
if (fmv_skip_active) {
|
||||
const int FMV_PRESENT_EVERY = 30;
|
||||
s_fmv_skip_present_skip = (s_fmv_skip_present_skip + 1) % FMV_PRESENT_EVERY;
|
||||
if (s_fmv_skip_present_skip != 0) return;
|
||||
if (s_fmv_skip_present_skip != 0) {
|
||||
netplay_tail.skip_pace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wall-clock pacing: always runs once fast_boot has ended, even when the
|
||||
* display is still disabled (e.g. game crt0 setup). Skipped only by the
|
||||
* turbo and fast_boot early-returns above. Netplay admits first (above),
|
||||
* then paces here with everyone else, then presents — pre-rematch order.
|
||||
* frame_pacer_wait is the race-free replacement for the old open-coded
|
||||
* loop whose double counter read could underflow into a ~24.7-day
|
||||
* SDL_Delay (Bug B hard freeze). */
|
||||
frame_pacer_wait(&s_frame_pacer, g_frame_period_ms);
|
||||
latency_ring_mark(LAT_PACED);
|
||||
/* Netplay FMV: skip present every other depth24 vblank (admit still runs
|
||||
* in the RAII tail). Present-first frame, then alternate. */
|
||||
if (psx_netplay_active() && gpu_display_is_depth24()) {
|
||||
if (s_netplay_depth24_present_skip) {
|
||||
s_netplay_depth24_present_skip = 0;
|
||||
netplay_tail.skip_pace();
|
||||
return;
|
||||
}
|
||||
s_netplay_depth24_present_skip = 1;
|
||||
} else {
|
||||
s_netplay_depth24_present_skip = 0;
|
||||
}
|
||||
|
||||
/* Low-latency input: the early sample above is now ~one pacer-wait old.
|
||||
* Refresh the device state and re-sample right before present so the next
|
||||
* CPU frame reads near-fresh input (the dominant input->photon cost on a
|
||||
* vsync-light box). Re-stamp the ring's input mark to measure from here. */
|
||||
if (g_low_latency_input && !psx_netplay_active()) {
|
||||
SDL_GameControllerUpdate(); /* refresh pad state after the wait */
|
||||
SDL_PumpEvents(); /* refresh keyboard state */
|
||||
sample_pad_into_sio(override);
|
||||
latency_ring_restamp_input();
|
||||
/* Offline wall-clock pacing before present. Netplay paces in NetplayVblankTail
|
||||
* AFTER present so Swap overlaps the peer's guest quantum. */
|
||||
if (!psx_netplay_active()) {
|
||||
uint64_t perf_start = runtime_perf_section_begin();
|
||||
frame_pacer_wait(&s_frame_pacer, g_frame_period_ms);
|
||||
runtime_perf_section_end(perf_start, &g_runtime_perf.pacer_ticks);
|
||||
latency_ring_mark(LAT_PACED);
|
||||
|
||||
/* Low-latency input: the early sample above is now ~one pacer-wait old.
|
||||
* Refresh the device state and re-sample right before present so the next
|
||||
* CPU frame reads near-fresh input (the dominant input->photon cost on a
|
||||
* vsync-light box). Re-stamp the ring's input mark to measure from here. */
|
||||
if (g_low_latency_input) {
|
||||
SDL_GameControllerUpdate(); /* refresh pad state after the wait */
|
||||
SDL_PumpEvents(); /* refresh keyboard state */
|
||||
sample_pad_into_sio(override);
|
||||
latency_ring_restamp_input();
|
||||
}
|
||||
}
|
||||
|
||||
/* Mod hooks. Run after all normal input sampling. */
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#endif
|
||||
|
||||
uint64_t psx_cycle_count = 0;
|
||||
uint32_t g_psx_cyc_batch = 0;
|
||||
static int s_cycle_replay_active = 0;
|
||||
static uint64_t s_cycle_replay_live = 0;
|
||||
|
||||
@@ -83,10 +84,13 @@ static void advance_devices(uint32_t c) {
|
||||
#define PSX_DEADLINE_HARD_CAP 16384u
|
||||
|
||||
static uint64_t s_devices_synced_cycle = 0; /* devices are advanced up to here */
|
||||
/* Exported for the inlined psx_advance_cycles / psx_cyc_charge hot path. */
|
||||
uint64_t psx_next_service_cycle = 0; /* absolute; 0 = dirty, recompute */
|
||||
int psx_in_device_service = 0; /* re-entrancy guard (header hot path) */
|
||||
static uint64_t s_next_watchdog = 0; /* sparse throttle anchors in service_to_now */
|
||||
static uint64_t s_next_pc_sample = 0;
|
||||
int psx_in_device_service = 0; /* re-entrancy guard */
|
||||
#define s_next_service_cycle psx_next_service_cycle
|
||||
#define s_in_device_service psx_in_device_service
|
||||
static uint64_t s_next_watchdog = 0;
|
||||
static uint64_t s_next_pc_sample = 0;
|
||||
|
||||
/* Absolute inclusive limit for dirty_ram_interp.c's exact one-cycle path.
|
||||
* Zero means that the next charge must visit psx_advance_cycles(). */
|
||||
@@ -187,6 +191,7 @@ void psx_devices_service_to_now(void) {
|
||||
* advance through service_to_now even when already synced — MotK FMV pays
|
||||
* that on every GPU/CD/MDEC MMIO touch. Recompute here instead. */
|
||||
void psx_devices_mmio_sync(void) {
|
||||
psx_cyc_batch_flush();
|
||||
if (s_devices_synced_cycle != psx_cycle_count) {
|
||||
psx_devices_service_to_now();
|
||||
} else {
|
||||
@@ -270,7 +275,7 @@ void psx_advance_cycles_slow(uint32_t cycles) {
|
||||
}
|
||||
|
||||
uint64_t psx_get_cycle_count(void) {
|
||||
return psx_cycle_count;
|
||||
return psx_cycle_count + (uint64_t)g_psx_cyc_batch;
|
||||
}
|
||||
|
||||
/* ===== Idle-loop cycle skip (wait-loop elision, 2026-07-06) ==================
|
||||
@@ -486,6 +491,7 @@ void psx_idle_note_check(CPUState *cpu, uint32_t check_pc) {
|
||||
* would try to replay a bogus gap. Re-anchor devices at the restored cycle and
|
||||
* force a fresh deadline on the next charge. */
|
||||
void psx_cycles_resync_after_restore(void) {
|
||||
g_psx_cyc_batch = 0;
|
||||
s_devices_synced_cycle = psx_cycle_count;
|
||||
psx_next_service_cycle = 0; /* recompute on next charge */
|
||||
psx_in_device_service = 0;
|
||||
@@ -500,6 +506,17 @@ void psx_cycles_reset_for_boot(void) {
|
||||
s_next_pc_sample = 0;
|
||||
}
|
||||
|
||||
void psx_cycles_reset_for_boot(void) {
|
||||
g_psx_cyc_batch = 0;
|
||||
psx_cycle_count = 0;
|
||||
s_devices_synced_cycle = 0;
|
||||
s_next_service_cycle = 0;
|
||||
s_in_device_service = 0;
|
||||
g_psx_cycle_fast_limit = 0;
|
||||
s_next_watchdog = 0;
|
||||
s_next_pc_sample = 0;
|
||||
}
|
||||
|
||||
/* ---- Mult/div completion-stall timing (faithful R3000A; Beetle muldiv_ts_done) ----
|
||||
*
|
||||
* MULT/MULTU/DIV/DIVU don't stall at the op; they set a completion DEADLINE.
|
||||
|
||||
@@ -128,6 +128,8 @@ static int list_filter_version_strict(void)
|
||||
return gv && gv[0] && strcmp(gv, "dev") != 0;
|
||||
}
|
||||
|
||||
static void queue_send(const char *json);
|
||||
|
||||
static void queue_list_request(void)
|
||||
{
|
||||
char msg[384];
|
||||
@@ -159,7 +161,6 @@ static int json_extract_object(const char *json, const char *key, char *out, siz
|
||||
static void parse_match_caps_object(const char *obj, PsxLobbyMatchCaps *out);
|
||||
static void ingest_match_caps_from_json(const char *json);
|
||||
static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatchCaps *caps);
|
||||
static void queue_send(const char *json);
|
||||
|
||||
const char *psx_lobby_default_url(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user