From a156aa4141a88822d1e4419e63bc5572a1f601ba Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Thu, 27 Aug 2026 20:35:19 -0700 Subject: [PATCH] mods: align frame blending with Ape Escape --- CMakeLists.txt | 19 ++++-- .../1.0.0/README.txt | 31 +++------ .../1.0.0/manifest.toml | 55 ++++++++++------ src/mods/tomba_frame_interpolation_plugin.c | 63 ++++++++++--------- 4 files changed, 90 insertions(+), 78 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a11c39..2e02a6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,9 @@ psxrecomp_add_game_runtime(psx-runtime ) target_sources(psx-runtime PRIVATE ${TOMBA_MOD_PLUGIN_SOURCES}) +if(TARGET psx-runtime-pgxp) + target_sources(psx-runtime-pgxp PRIVATE ${TOMBA_MOD_PLUGIN_SOURCES}) +endif() # Stage configs beside the binary for direct launches. Gate on EXISTS so a # setup-host zip that forgot an optional file still links successfully. @@ -50,10 +53,14 @@ endforeach() set(TOMBA_PRELOADED_MODS "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded") if(EXISTS "${TOMBA_PRELOADED_MODS}/packages") - add_custom_command(TARGET psx-runtime POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${TOMBA_PRELOADED_MODS}" - "$/mods" - COMMENT "Preloading the Tomba mod catalog" - VERBATIM) + foreach(TOMBA_RUNTIME_TARGET psx-runtime psx-runtime-pgxp) + if(TARGET ${TOMBA_RUNTIME_TARGET}) + add_custom_command(TARGET ${TOMBA_RUNTIME_TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${TOMBA_PRELOADED_MODS}" + "$/mods" + COMMENT "Preloading the Tomba mod catalog" + VERBATIM) + endif() + endforeach() endif() diff --git a/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/README.txt b/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/README.txt index b5f13cf..ac3bf98 100644 --- a/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/README.txt +++ b/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/README.txt @@ -1,24 +1,11 @@ -Tomba Frame Interpolation +Tomba Temporal Frame Blending -This default-disabled mod presents blended intermediate frames above the game's -60Hz output through the stable tomba.frame-interpolation plugin id. +This mod leaves Tomba's executable, VSync waits, simulation, timers, input, and +audio untouched. It combines the two most recent completed game frames in +PSXrecomp's OpenGL presentation path at the display refresh or a fixed 60, 120, +144, or 165 presentation rate. -It is presentation only. Guest VBlank, game logic, timers, and audio keep their -stock cadence, so this changes how smooth the game looks and not how fast it -runs. The separate native-VBlank-rate mechanism does change whole-machine speed -and is deliberately not exposed by this package. - -Output rate selects the presentation cadence: 'Display refresh' follows the -measured monitor refresh, and the fixed rates pace presentation at that many -frames per second. Interpolation is an OpenGL presenter feature, so enabling it -selects the OpenGL renderer and uses its presentation scheduler instead of -driver vsync. - -This replaces the launcher's former Settings row for frame interpolation, which -the shared PSX launcher profile offered on every title. Tomba already owned -widescreen and Skip FMVs as mods; this brings the third generic display toggle -in line so every optional change to the game lives in one place. - -Credit - -mstan — mod integration +The motion-adaptive clarity blend avoids crossfading large pixel changes to +reduce double-image trails. It is presentation-only temporal blending, not +motion-vector frame generation, so it cannot reconstruct true in-between +positions. diff --git a/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/manifest.toml b/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/manifest.toml index 7419807..52342a6 100644 --- a/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/manifest.toml +++ b/mods/preloaded/packages/tomba.enhancement.frame-interpolation/1.0.0/manifest.toml @@ -1,9 +1,9 @@ format_version = 5 id = "tomba.enhancement.frame-interpolation" version = "1.0.0" -name = "Tomba Frame Interpolation" +name = "Tomba Frame Rate" author = "mstan" -description = "Presents blended intermediate frames above the game's 60Hz output. Guest VBlank, game logic, timers, and audio stay at their stock cadence." +description = "Presentation-only temporal frame blending. Game logic, timers, and audio remain at their stock cadence." resolver = "declarative" save_compatibility = "shared" @@ -11,31 +11,25 @@ save_compatibility = "shared" game_id = "SCUS-94236" disc_sha256 = "25ada3dd51e70eb9f9218cd83fb02d73139750229fb44b012d62d183ab32eb13" -# Presentation only: this does NOT change machine speed. The distinct -# native-VBlank-rate mechanism does, and is deliberately not exposed here. [[feature]] id = "frame-interpolation" -name = "Frame Interpolation" -description = "Blend between completed guest frames to present above 60Hz. Presentation only — the game still simulates at its stock rate, so this changes smoothness, not speed or timing. Requires the OpenGL renderer, which it selects, and runs without vsync." -group = "Display" +name = "Temporal Frame Blending" +description = "Blend completed game frames at the selected presentation rate without accelerating gameplay, timers, or audio. This is not motion-vector frame generation." +group = "Frame Rate" default_enabled = false [[option]] feature = "frame-interpolation" id = "rate" -label = "Output rate" -description = "Presentation rate. 'Display refresh' follows the measured monitor refresh; the fixed rates pace presentation at that many frames per second." -group = "Display" +label = "Presentation rate" +description = "Follow the display for even cadence, or choose a fixed diagnostic rate. The original game simulation remains untouched. Requires OpenGL." +group = "Frame Rate" type = "choice" -default = "display" +default = "uncapped" [[option.choice]] -value = "display" -label = "Display refresh" - -[[option.choice]] -value = "90" -label = "90 FPS" +value = "60" +label = "60 FPS" [[option.choice]] value = "120" @@ -50,9 +44,30 @@ value = "165" label = "165 FPS" [[option.choice]] -value = "240" -label = "240 FPS" +value = "uncapped" +label = "Display refresh (Recommended)" + +[[plugin]] +feature = "frame-interpolation" +id = "tomba.framerate.60" +when = { rate = "60" } + +[[plugin]] +feature = "frame-interpolation" +id = "tomba.framerate.120" +when = { rate = "120" } + +[[plugin]] +feature = "frame-interpolation" +id = "tomba.framerate.144" +when = { rate = "144" } + +[[plugin]] +feature = "frame-interpolation" +id = "tomba.framerate.165" +when = { rate = "165" } [[plugin]] feature = "frame-interpolation" -id = "tomba.frame-interpolation" +id = "tomba.framerate.uncapped" +when = { rate = "uncapped" } diff --git a/src/mods/tomba_frame_interpolation_plugin.c b/src/mods/tomba_frame_interpolation_plugin.c index 3d9008b..7fe3baf 100644 --- a/src/mods/tomba_frame_interpolation_plugin.c +++ b/src/mods/tomba_frame_interpolation_plugin.c @@ -1,42 +1,45 @@ #include "mod_plugins.h" -#include -#include - /* - * Presentation-only frame interpolation, moved out of generic recomp-ui - * Settings into the mod catalog (game.toml sets - * [video] offer_frame_interpolation = false), joining the widescreen and - * Skip FMVs features Tomba already owns as mods. - * - * Deliberately psx_mod_set_frame_interpolation and NOT - * psx_mod_set_native_vblank_rate: the former blends between completed guest - * frames and leaves VBlank, logic, timers, and audio at their stock cadence, - * while the latter changes whole-machine realtime speed. Conflating the two is - * how "smoother" turns into "the game runs fast", so this package exposes only - * the presentation half. + * Keep Tomba's guest cadence completely stock. These callbacks only select how + * frequently PSXrecomp's OpenGL presentation thread blends between the two most + * recent completed game frames. */ -#define PKG "tomba.enhancement.frame-interpolation" -#define FEATURE "frame-interpolation" +static void tomba_frame_rate_set(unsigned frames_per_second) { + (void)psx_mod_set_frame_interpolation_blend( + PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE); + (void)psx_mod_set_frame_interpolation(frames_per_second); +} + +static void tomba_frame_rate_60_activate(void) { + tomba_frame_rate_set(60u); +} + +static void tomba_frame_rate_120_activate(void) { + tomba_frame_rate_set(120u); +} -static void tomba_frame_interpolation_activate(void) { - char rate[16]; - unsigned long fps = 0ul; /* 0 = follow measured display refresh */ +static void tomba_frame_rate_144_activate(void) { + tomba_frame_rate_set(144u); +} - /* An unreadable or unrecognised value falls back to the manifest default - * ("display"), which is the conservative choice: it follows the monitor - * instead of pinning a rate the panel may not support. */ - if (psx_mod_option_value(PKG, FEATURE, "rate", rate, sizeof rate) && - strcmp(rate, "display") != 0) { - char* end = rate; - const unsigned long parsed = strtoul(rate, &end, 10); - if (end != rate && *end == '\0') fps = parsed; - } +static void tomba_frame_rate_165_activate(void) { + tomba_frame_rate_set(165u); +} - (void)psx_mod_set_frame_interpolation((uint32_t)fps); +static void tomba_frame_rate_display_activate(void) { + tomba_frame_rate_set(0u); } PSX_MOD_CONSTRUCTOR(tomba_register_frame_interpolation_plugin) { (void)psx_mod_register_activation_plugin( - "tomba.frame-interpolation", tomba_frame_interpolation_activate); + "tomba.framerate.60", tomba_frame_rate_60_activate); + (void)psx_mod_register_activation_plugin( + "tomba.framerate.120", tomba_frame_rate_120_activate); + (void)psx_mod_register_activation_plugin( + "tomba.framerate.144", tomba_frame_rate_144_activate); + (void)psx_mod_register_activation_plugin( + "tomba.framerate.165", tomba_frame_rate_165_activate); + (void)psx_mod_register_activation_plugin( + "tomba.framerate.uncapped", tomba_frame_rate_display_activate); }