Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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}"
"$<TARGET_FILE_DIR:psx-runtime>/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}"
"$<TARGET_FILE_DIR:${TOMBA_RUNTIME_TARGET}>/mods"
COMMENT "Preloading the Tomba mod catalog"
VERBATIM)
endif()
endforeach()
endif()
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
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"

[[target]]
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"
Expand All @@ -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" }
63 changes: 33 additions & 30 deletions src/mods/tomba_frame_interpolation_plugin.c
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
#include "mod_plugins.h"

#include <stdlib.h>
#include <string.h>

/*
* 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);
}