mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
138 lines
6.2 KiB
C
138 lines
6.2 KiB
C
/*
|
|
ARMSX PS1 core — rewind + runahead.
|
|
|
|
============================================================================
|
|
ONE SNAPSHOT POOL, TWO FEATURES
|
|
============================================================================
|
|
|
|
Both features are "take a save state, put it back later", so they share ONE
|
|
internal snapshot type and one capture/restore path (psx_rewind_snapshot_t in
|
|
rewind.c). They differ only in how many snapshots are kept and when they are
|
|
put back:
|
|
|
|
REWIND keeps a RING of snapshots taken every Nth frame, and steps
|
|
BACKWARDS through it on demand. The ring is the memory cost.
|
|
|
|
RUNAHEAD keeps exactly ONE snapshot, taken after the frame that really
|
|
happened, and puts it back at the start of the next frame. The
|
|
cost is CPU, not memory: one save + one load + N re-simulated
|
|
frames, every single frame.
|
|
|
|
Snapshots are the ordinary save-state stream (psx_save_state_to_memory)
|
|
minus the preview image — the PNG encode is far too expensive to run twice a
|
|
second, and the section is optional by design, so a snapshot loads through
|
|
the ordinary psx_load_state_from_memory() with no special case anywhere.
|
|
|
|
Because it IS an ordinary save state, everything the state format guarantees
|
|
holds here too: the PGXP shadow caches are NOT serialised and are reset by
|
|
the load (psx/pgxp.h), and the cached-interpreter block cache is invalidated.
|
|
|
|
============================================================================
|
|
MEMORY IS THE DESIGN CONSTRAINT
|
|
============================================================================
|
|
|
|
A PS1 snapshot is ~3.5 MiB (2 MiB RAM + 1 MiB VRAM + 512 KiB SPU RAM + the
|
|
small device blocks). That is small next to a PS2 state and ENORMOUS next to
|
|
what a handheld can spare once it is multiplied by a ring:
|
|
|
|
10 s at 2/s = 20 snapshots ~= 70 MiB (the default)
|
|
30 s at 4/s = 120 snapshots ~= 420 MiB
|
|
60 s at 4/s = 240 snapshots ~= 840 MiB (unusable)
|
|
|
|
So: nothing is allocated until rewind is actually switched on, the ring is
|
|
capped by PSX_REWIND_MAX_BYTES regardless of what the settings ask for (the
|
|
depth shrinks instead of the allocator failing), and a front-end is expected
|
|
to show the computed figure next to the control rather than hiding it.
|
|
|
|
============================================================================
|
|
THREADING
|
|
============================================================================
|
|
|
|
EVERY function here must be called on the emulation thread. They read and
|
|
write the machine. A host whose UI thread wants to change the configuration
|
|
parks the request and lets the emulation thread apply it — see
|
|
psxe_host_set_rewind() in frontend/main.cpp.
|
|
*/
|
|
|
|
#ifndef PSX_REWIND_H
|
|
#define PSX_REWIND_H
|
|
|
|
#include <stddef.h>
|
|
|
|
struct psx_t;
|
|
|
|
/* Hard ceiling on everything the ring may hold, whatever the settings say. The
|
|
depth is reduced to fit; it is never the allocator's job to say no. */
|
|
#define PSX_REWIND_MAX_BYTES ((size_t)384u * 1024u * 1024u)
|
|
|
|
/* Bounds the front-end and the config parser share, so "10 s at 2/s" means the
|
|
same number of snapshots on both sides. */
|
|
#define PSX_REWIND_MIN_SECONDS 1
|
|
#define PSX_REWIND_MAX_SECONDS 60
|
|
#define PSX_REWIND_MIN_FREQUENCY 1
|
|
#define PSX_REWIND_MAX_FREQUENCY 8
|
|
#define PSX_REWIND_DEFAULT_SECONDS 10
|
|
#define PSX_REWIND_DEFAULT_FREQUENCY 2
|
|
|
|
#define PSX_RUNAHEAD_MAX_FRAMES 5
|
|
|
|
/* Rough size of one snapshot before any has actually been taken, so a front-end
|
|
can label its control before a game is even running. Replaced by the real
|
|
figure (psx_rewind_snapshot_bytes) as soon as one snapshot exists. */
|
|
#define PSX_REWIND_ESTIMATED_SNAPSHOT_BYTES ((size_t)3670016u) /* ~3.5 MiB */
|
|
|
|
/* ---- Configuration (emulation thread) ---------------------------------- */
|
|
|
|
/* enabled == 0 frees the ring immediately. seconds/frequency are clamped into
|
|
the ranges above. Re-configuring with the same values is a no-op; changing
|
|
the depth drops what is held (the timeline it described no longer matches). */
|
|
void psx_rewind_configure(int enabled, int seconds, int frequency);
|
|
int psx_rewind_enabled(void);
|
|
|
|
/* 0 disables. Clamped to 0..PSX_RUNAHEAD_MAX_FRAMES. */
|
|
void psx_runahead_configure(int frames);
|
|
int psx_runahead_frames(void);
|
|
|
|
/* ---- Rewind ------------------------------------------------------------ */
|
|
|
|
/* Call once per emulated frame, AFTER the frame has been produced.
|
|
frame_rate is the machine's nominal rate (used only to turn the configured
|
|
snapshots-per-second into a frame interval); <= 0 falls back to 60. */
|
|
void psx_rewind_notify_frame(struct psx_t* psx, double frame_rate);
|
|
|
|
/* Put the machine back to the newest snapshot and drop it, so repeated calls
|
|
walk backwards. Returns PSX_STATE_OK, PSX_STATE_ERR_MISSING when the ring is
|
|
empty (i.e. "as far back as it goes" — not an error worth reporting), or a
|
|
negative psx/state.h code when the snapshot would not apply. */
|
|
int psx_rewind_step_back(struct psx_t* psx);
|
|
|
|
/* Drop every snapshot AND the runahead slot. The timeline they describe is gone
|
|
after a reset, a disc swap or a state load, and putting one back afterwards
|
|
would resume a machine that no longer exists. */
|
|
void psx_rewind_reset(void);
|
|
|
|
/* Release every allocation. Called from psx_destroy(). */
|
|
void psx_rewind_shutdown(void);
|
|
|
|
/* ---- Runahead ---------------------------------------------------------- */
|
|
|
|
/* Capture / restore the single look-ahead snapshot. save() overwrites whatever
|
|
was there; restore() applies it and KEEPS it (the caller decides when the
|
|
slot is stale — see psx_runahead_discard). */
|
|
int psx_runahead_save(struct psx_t* psx);
|
|
int psx_runahead_restore(struct psx_t* psx);
|
|
int psx_runahead_has_snapshot(void);
|
|
void psx_runahead_discard(void);
|
|
|
|
/* ---- Introspection, for a front-end's UI ------------------------------- */
|
|
|
|
/* Bytes currently held by the ring (allocated, not merely used). */
|
|
size_t psx_rewind_bytes_used(void);
|
|
/* Size of one snapshot: measured if any has been taken, otherwise the estimate. */
|
|
size_t psx_rewind_snapshot_bytes(void);
|
|
/* Snapshots held right now, and how many the current configuration can hold. */
|
|
int psx_rewind_snapshot_count(void);
|
|
int psx_rewind_capacity(void);
|
|
|
|
#endif
|