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>
61 lines
2.6 KiB
C
61 lines
2.6 KiB
C
#ifndef PSX_RENDER_PASS_H
|
|
#define PSX_RENDER_PASS_H
|
|
|
|
/* Host-timed render passes: runtime side of psx_mod_render_pass_plan() and
|
|
* psx_mod_render_pass() (mod_plugins.h). See docs/RENDER_PASSES.md. */
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "psx_cycle_freeze.h" /* g_psx_render_pass_active, the time freeze */
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Stores a pass may not make (memory.c render_pass_store): they would escape
|
|
* the restore, so they are dropped and counted per device class. */
|
|
enum {
|
|
RENDER_PASS_DROP_SPU = 0,
|
|
RENDER_PASS_DROP_CD,
|
|
RENDER_PASS_DROP_TIMER,
|
|
RENDER_PASS_DROP_DMA, /* DMA channels other than GPU (2) and OTC (6) */
|
|
RENDER_PASS_DROP_GPU, /* GP1 commands other than DMA mode / info */
|
|
RENDER_PASS_DROP_OTHER, /* SIO, MDEC, memory control, mod arenas, ... */
|
|
RENDER_PASS_DROP_CLASSES
|
|
};
|
|
extern uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
|
|
|
|
typedef struct RenderPassStats {
|
|
uint64_t plans; /* psx_mod_render_pass_plan calls that planned >= 1 */
|
|
uint64_t planned; /* phases planned (after shedding) */
|
|
uint64_t wanted; /* phases wanted before shedding */
|
|
uint64_t refused; /* plan calls that returned 0 while enabled */
|
|
uint64_t passes; /* passes run to completion and presented */
|
|
uint64_t aborted; /* passes rolled back by a fault */
|
|
uint64_t discarded; /* passes whose plugin declined the image */
|
|
uint64_t watchdog; /* of which: guest-cycle watchdog overruns */
|
|
uint64_t vram_leaks; /* GPU writes outside the declared rect (dropped) */
|
|
uint64_t verify_checks; /* PSX_RENDER_PASS_VERIFY comparisons */
|
|
uint64_t verify_mismatch; /* ... that found a difference */
|
|
uint64_t dropped[RENDER_PASS_DROP_CLASSES];
|
|
double last_pass_ms; /* host time of the last pass (sandbox + draw) */
|
|
double avg_pass_ms; /* smoothed */
|
|
double avg_begin_ms; /* smoothed: VRAM backup + checkpoint */
|
|
double avg_guest_ms; /* smoothed: the plugin's guest draw code */
|
|
double avg_end_ms; /* smoothed: GPU flush, capture, VRAM restore */
|
|
double avg_restore_ms; /* smoothed: machine state restore */
|
|
uint64_t guest_cycles_last; /* guest cycles the last pass executed */
|
|
int disabled; /* sticky: passes disabled after repeated faults */
|
|
int watchdog_flag; /* internal: the current pass overran */
|
|
} RenderPassStats;
|
|
|
|
void render_pass_get_stats(RenderPassStats *out);
|
|
/* Clears counters and the sticky fault latch (new mod session). */
|
|
void render_pass_reset_session(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|