mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
d22ebac9a4
psx_mod_render_pass_plan() / psx_mod_render_pass() let a trusted plugin redraw its game between two logic ticks with the game's own draw code. A pass is a sandboxed nested guest call: - time is frozen: cycles are counted (GTE and mult/div deadlines keep working) but no device advances, no VBlank or device event fires and no interrupt is delivered; GPU linked-list DMA and delayed completions finish synchronously (psx_cycles freeze, interrupts, dma); - stores go straight to RAM/scratchpad, bypassing code-page tracking, overlay watch and write traces; MMIO is an allow-list (GP0, GP1 DMA mode / info, GPU and OTC DMA, I_STAT/I_MASK) and SPU, CD, timer and other device stores are dropped and counted (memory.c); - afterwards CPU (with the GTE), RAM, scratchpad, I-cache tags, interrupt, timer, DMA and GPU registers are restored exactly (render_pass.c, a GPU register checkpoint without savestate side effects), with a guest-cycle watchdog that rolls a runaway pass back; - on OpenGL the declared display rect (hr colour + mask stencil, raw mirror, native-wide band, CPU VRAM rows) is backed up, drawn, captured and rolled back; writes outside it are journaled and restored, or refused. The presenter keeps two generations of images (the game's own frame plus one image per pass phase), promotes one when the flip-aware source sees the display flip to its rect, and shows the newest image at each output deadline's phase, crossfading across gaps when passes were shed. Presents that fall due while passes run are made between them. The plan predicts the output deadlines of the coming frame and sheds passes to fit a host-time budget learnt from the presenter's idle time (PSX_RENDER_PASS_BUDGET). PSX_MOD_FRAME_INTERPOLATION_HOLD repeats the newest frame where no pass image applies, so a pass-driven title never shows anything later than stock. Default off: nothing runs unless a plugin calls the API, and the plan refuses in netplay, rollback, rewind, turbo, self-check resimulation, while suspended (FMV) or without GL flip-aware interpolation. Pass writes are kept out of frame_fingerprint. Debug: render_pass_stats, render_pass_dump, PSX_RENDER_PASS_VERIFY=1 (state + VRAM hashes around every pass). The freeze API lives in the runtime-only psx_cycle_freeze.h. psx_cycles.h is part of the overlay codegen hash and stays byte-identical, so titles that never use passes keep their overlay caches and savestates. Tests: render_pass_plan_test (deadline-exact phases per rate, shedding, selection), render_pass_freeze_test (10^6 frozen cycles, exact restore, watchdog), render_pass_guards (sandbox choke points; the codegen-hashed headers stay free of the pass API). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
4.6 KiB
C
94 lines
4.6 KiB
C
#ifndef PSX_FRAME_INTERPOLATION_H
|
|
#define PSX_FRAME_INTERPOLATION_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct FrameInterpolationSchedule {
|
|
double source_deadline;
|
|
double next_present_deadline;
|
|
double frame_start;
|
|
double frame_end;
|
|
double target_period;
|
|
/* Blend-phase window of this source interval (begin_phase). begin()
|
|
* plans the whole 0..1 window, which is the historical behaviour. */
|
|
double phase_lo;
|
|
double phase_hi;
|
|
} FrameInterpolationSchedule;
|
|
|
|
/* Plan one stock guest-frame interval. Presentation deadlines remain anchored
|
|
* across calls so guest work consumes part of the interval instead of slowing
|
|
* the simulation. Returns zero when the rates or host clock are invalid. */
|
|
int frame_interpolation_schedule_begin(FrameInterpolationSchedule *schedule,
|
|
uint64_t now, uint64_t frequency,
|
|
double source_hz, double target_hz);
|
|
|
|
/* Return each presentation deadline in the current source interval. Alpha is
|
|
* the temporal blend position from the previous completed frame to the current
|
|
* one. Deadlines missed by more than one output period are coalesced. */
|
|
int frame_interpolation_schedule_next(FrameInterpolationSchedule *schedule,
|
|
uint64_t now, uint64_t *deadline,
|
|
float *alpha);
|
|
|
|
/* Same as begin(), but the returned blend alphas cover only [phase_lo,
|
|
* phase_hi] of the frame-to-frame transition (both clamped to 0..1). A game
|
|
* that flips every P VBlanks presents VBlank k (0-based since the flip) with
|
|
* the window [k/P, (k+1)/P], so one crossfade spans the whole game frame
|
|
* instead of finishing after the first VBlank and holding for the rest.
|
|
* begin() is begin_phase(0, 1). */
|
|
int frame_interpolation_schedule_begin_phase(FrameInterpolationSchedule *schedule,
|
|
uint64_t now, uint64_t frequency,
|
|
double source_hz, double target_hz,
|
|
double phase_lo, double phase_hi);
|
|
|
|
/* Render passes present between guest VBlanks. The next output deadline, if
|
|
* it is already due (<= now) and not later than `horizon` (host ticks), with
|
|
* stale deadlines coalesced to the newest one as begin() does. It is not
|
|
* consumed: call frame_interpolation_schedule_consume() once it is shown, so
|
|
* the next source interval simply continues after it. */
|
|
int frame_interpolation_schedule_due(FrameInterpolationSchedule *schedule,
|
|
uint64_t now, double horizon,
|
|
uint64_t *deadline);
|
|
void frame_interpolation_schedule_consume(FrameInterpolationSchedule *schedule);
|
|
|
|
uint64_t frame_interpolation_schedule_end(
|
|
const FrameInterpolationSchedule *schedule);
|
|
void frame_interpolation_schedule_reset(FrameInterpolationSchedule *schedule);
|
|
|
|
/* Flip-aware blend source (psx_mod_set_frame_interpolation_source FLIP).
|
|
* Pure bookkeeping, one call per presented guest VBlank: new_frame says the
|
|
* displayed image changed (display origin moved, or the displayed rect was
|
|
* redrawn). Tracks P, the last observed flip period in VBlanks (clamped
|
|
* 1..FRAME_FLIP_PERIOD_MAX; 1 until two new frames were seen), and k, the
|
|
* VBlanks since the last new frame, and yields this VBlank's blend window
|
|
* [k/P, (k+1)/P] clamped to 0..1. A frame that arrives late holds at 1; one
|
|
* that arrives early restarts at 0 from the frame that was fully shown. */
|
|
#define FRAME_FLIP_PERIOD_MAX 4u
|
|
typedef struct FrameFlipTracker {
|
|
uint32_t since_flip; /* VBlanks since the last new frame, before this one */
|
|
uint32_t period; /* P */
|
|
uint32_t frames; /* new frames seen (saturating) */
|
|
} FrameFlipTracker;
|
|
|
|
void frame_flip_tracker_reset(FrameFlipTracker *tracker);
|
|
/* Whether a presented VBlank shows a new guest frame (the tracker's
|
|
* new_frame): the presented geometry changed, no frame is held yet, the
|
|
* displayed rect was redrawn since the last present, or the display origin
|
|
* moved from (last_x, last_y) to (origin_x, origin_y), i.e. a flip. Anything
|
|
* else re-presents the image already held and is a duplicate. */
|
|
int frame_flip_is_new_frame(int geometry_changed, int history_empty,
|
|
int redrawn, int origin_x, int origin_y,
|
|
int last_x, int last_y);
|
|
/* Returns k for this VBlank. */
|
|
uint32_t frame_flip_tracker_vblank(FrameFlipTracker *tracker, int new_frame,
|
|
double *phase_lo, double *phase_hi);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|