CRT Filter, Image Filtering, Enhanced Audio and Rewind become default-off mods (netplay = host_only, outside the plan fingerprint); Internal resolution gains Auto in place of dynamic resolution. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.0 KiB
Frame rate: presentation above the guest's cadence
A game-owned mod can present a PS1 title at a higher rate than it renders
(60, 120, 240 Hz, the display's refresh, ...) without changing how fast the
game runs. Guest VBlanks, timers, CD, SPU and game logic stay at their stock
cadence; only what the OpenGL presenter shows between guest VBlanks changes.
Never use psx_mod_set_native_vblank_rate for this: it speeds up the whole
machine.
Enabling it (mod_plugins.h)
From a trusted plugin's activation callback:
psx_mod_set_frame_interpolation_source(PSX_MOD_FRAME_SOURCE_FLIP); /* optional */
psx_mod_set_frame_interpolation_blend(PSX_MOD_FRAME_INTERPOLATION_LINEAR);
psx_mod_set_frame_interpolation(120); /* 0 = follow the display refresh */
psx_mod_set_frame_interpolation accepts 0 or 60..1000, selects the OpenGL
renderer and turns vsync off (the presenter paces itself with a sleep+spin
schedule on the emulation thread). Blend and source are reset to their
defaults at every session start (first boot and the lobby rematch), so a
netplay session or a later offline one never inherits them.
Because the presenter owns vsync while it runs, the mod's manifest declares
vsync = "off" on the feature (format 11, docs/MOD_PACKAGES.md): the
launcher then shows its VSync row overridden by that feature instead of a
choice that would not apply. A feature that does not declare it is named on
stdout the first time its activation forces vsync off.
The presenter uses the high-resolution host timer for microsecond waits, leaving only a short final spin for scheduling jitter. Whole-millisecond waits previously spent up to almost two milliseconds spinning per output, which could starve game execution on a CPU-constrained host at high refresh. The presentation deadlines and guest cadence remain unchanged.
Blend modes
| Mode | Output between two source frames |
|---|---|
LINEAR (default) |
crossfade mix(prev, cur, a) |
MOTION_ADAPTIVE |
crossfade small changes, switch large changes at a = 0.5 |
HOLD |
no crossfade: repeat the newest frame (for titles whose plugin supplies in-between images with render passes) |
Blending never invents motion: moving objects ghost, and the image is one source frame behind the newest one. True in-between frames need the game's own draw code: see RENDER_PASSES.md.
Blend source: every VBlank or real flips
The presenter plans one output schedule per guest VBlank present.
PSX_MOD_FRAME_SOURCE_VBLANK(default, and the historical behaviour): every guest VBlank is a new source frame. Right for games that flip every VBlank.PSX_MOD_FRAME_SOURCE_FLIP: a VBlank is a new source frame only when the game really flipped -- the displayed VRAM origin moved, or the displayed rect was redrawn. The presenter tracks the flip period P (VBlanks between flips, clamped 1..4) and gives VBlank k after a flip the blend window [k/P, (k+1)/P], so one crossfade spans the whole game frame. A 30 Hz game (P = 2) would otherwise blend for one VBlank and hold for the next. A frame that arrives late holds at a = 1; one that arrives early restarts from the frame that was fully shown.
gl_interp (TCP) reports source, flip_period, captures (new frames)
and duplicates (VBlanks that re-presented the same frame); a 30 Hz game in
FLIP mode shows about 30 captures and 30 duplicates per second.
Limits
- OpenGL only; a command-line
--rendereroverride after activation drops it. Netplay sessions run without mods, so without interpolation. - FMV (MDEC / 24-bit) frames suspend it, as does rewind.
- Vsync is off: on a fixed-refresh panel pick the display refresh rate.
Tests: frame_interpolation_schedule_test (runtime ctest) covers the phase
windows, the flip tracker, the new-frame decision and output counts per rate
at 30 Hz. frame_interpolation_flip_source (source guard, recompiler ctest)
pins the wiring no unit test reaches: the session-start hand-off of the
source to the renderer, and interp_capture's FLIP branch (origin and redraw
into the decision, an early return on a duplicate).