From 8ea0352fedeba9b3a670e05c29e6f4319abc7e13 Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 13 Apr 2026 12:43:42 -0400 Subject: [PATCH] Frame interp: Initial presentation sync implementation --- include/dusk/frame_interpolation.h | 5 ++ .../src/JStudio/JStudio/jstudio-object.cpp | 19 ++++++++ src/d/d_camera.cpp | 38 ++++++++++++++- src/dusk/frame_interpolation.cpp | 46 ++++++++++++++----- src/m_Do/m_Do_main.cpp | 2 + 5 files changed, 98 insertions(+), 12 deletions(-) diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 5c2dc2e1b8..423ac9af37 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -20,6 +20,11 @@ void begin_record(); void end_record(); void interpolate(float step); float get_interpolation_step(); + +void notify_presentation_frame(); +void request_presentation_sync(); +bool presentation_sync_active(); + void notify_sim_tick_complete(); uint32_t begin_presentation_ui_pass(); uint32_t get_presentation_ui_advance_ticks(); diff --git a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp index 3b87a6e34a..ee99429ee2 100644 --- a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp +++ b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp @@ -2,7 +2,11 @@ #include "JSystem/JStudio/JStudio/jstudio-object.h" +#if TARGET_PC #include "dusk/audio.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" +#endif namespace JStudio { namespace { @@ -650,10 +654,25 @@ value_or_fun: return; value: +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation && u <= 5 && + (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) + { + dusk::frame_interp::request_presentation_sync(); + } +#endif adaptor->adaptor_setVariableValue(control, u, operation, param_2, param_3); return; value_n: +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation && + (pN == TAdaptor_camera::sauVariableValue_3_POSITION_XYZ || pN == TAdaptor_camera::sauVariableValue_3_TARGET_POSITION_XYZ) && + (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) + { + dusk::frame_interp::request_presentation_sync(); + } +#endif adaptor->adaptor_setVariableValue_n(control, pN, u, operation, param_2, param_3); return; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 955683e05b..7b95a0d8ea 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -20,7 +20,6 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_lib.h" -#include "dusk/frame_interpolation.h" #include #include @@ -29,6 +28,11 @@ #include "d/d_debug_camera.h" #endif +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#include "dusk/logging.h" +#endif + namespace { static f32 limitf(f32 value, f32 min, f32 max) { @@ -2048,6 +2052,18 @@ s32 dCamera_c::nextType(s32 i_curType) { bool dCamera_c::onTypeChange(s32 i_curType, s32 i_nextType) { daAlink_c* unusedPlayer = daAlink_getAlinkActorClass(); +#if TARGET_PC + const s32 event_type_id = specialType[CAM_TYPE_EVENT]; + DuskLog.debug( + "frameInterp: onTypeChange {} -> {} (event_type_id={}, leaving_event={}, entering_event={})", + static_cast(i_curType), + static_cast(i_nextType), + static_cast(event_type_id), + i_curType == event_type_id, + i_nextType == event_type_id + ); +#endif + if (i_curType == specialType[CAM_TYPE_EVENT]) { if (mCamSetup.CheckFlag(0x4000)) { mGear = 0; @@ -10161,6 +10177,26 @@ bool dCamera_c::eventCamera(s32 param_0) { ActionNames[var_r29]); #endif +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + switch (var_r29) { + case 3: + case 4: + case 5: + case 12: + dusk::frame_interp::request_presentation_sync(); + break; + default: + DuskLog.debug( + "frameInterp: presentation sync not requested for ZEV event [{}] (staff idx {})", + static_cast(ActionNames[var_r29]), + static_cast(mEventData.mStaffIdx) + ); + break; + } + } +#endif + if (getEvFloatData(&sp28, "KeepDist") != 0 && mViewCache.mDirection.R() < sp28) { mViewCache.mDirection.R(sp28); diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index e9b35da1f0..8c45c034c6 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -63,6 +63,10 @@ bool s_initialized = false; bool g_enabled = false; bool g_recording = false; bool g_interpolating = false; +bool g_sync_presentation = false; +uint32_t g_presentation_counter = 0; +uint32_t g_presentation_sync_end = 0; + float g_step = 0.0f; uint32_t g_pending_presentation_ui_ticks = 0; uint32_t g_current_presentation_ui_ticks = 0; @@ -235,7 +239,7 @@ void interpolate_branch(const Path& old_path, const Path& new_path, float step) } const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { - if (!g_interpolating || source == nullptr) { + if (!g_interpolating || source == nullptr || dusk::frame_interp::presentation_sync_active()) { return source; } @@ -268,6 +272,7 @@ void begin_record() { ensure_initialized(); if (!g_enabled) { g_interpolating = false; + g_sync_presentation = false; g_previous_recording = {}; g_current_recording = {}; g_current_path.clear(); @@ -275,6 +280,10 @@ void begin_record() { return; } + if (g_sync_presentation && g_presentation_counter > g_presentation_sync_end) { + g_sync_presentation = false; + } + g_previous_recording = std::move(g_current_recording); g_current_recording = {}; g_current_path.clear(); @@ -292,21 +301,38 @@ void interpolate(float step) { ensure_initialized(); clear_replacements(); g_step = std::clamp(step, 0.0f, 1.0f); - g_interpolating = g_enabled && !g_recording && has_recording_data(g_current_recording); + g_interpolating = g_enabled && !g_recording && !g_sync_presentation && has_recording_data(g_current_recording); if (!g_interpolating) { return; } + const Path& old_root = has_recording_data(g_previous_recording) ? g_previous_recording.root : g_current_recording.root; + interpolate_branch(old_root, g_current_recording.root, g_step); +} - if (!has_recording_data(g_previous_recording)) { - interpolate_branch(g_current_recording.root, g_current_recording.root, g_step); +void notify_presentation_frame() { + ensure_initialized(); + ++g_presentation_counter; +} + +void request_presentation_sync() { + ensure_initialized(); + if (!g_enabled) { return; } + g_sync_presentation = true; + g_presentation_sync_end = g_presentation_counter + 1; +} - interpolate_branch(g_previous_recording.root, g_current_recording.root, g_step); +bool presentation_sync_active() { + if (!s_initialized || !g_enabled) { + return false; + } + return g_sync_presentation; } float get_interpolation_step() { - return g_step; + ensure_initialized(); + return presentation_sync_active() ? 1.0f : g_step; } void notify_sim_tick_complete() { @@ -371,7 +397,7 @@ void record_final_mtx_raw(const Mtx* dest, const Mtx src) { } bool lookup_replacement(const void* source, Mtx out) { - if (!s_initialized || !g_interpolating || source == nullptr) { + if (presentation_sync_active() || !g_interpolating || source == nullptr) { return false; } @@ -385,7 +411,7 @@ bool lookup_replacement(const void* source, Mtx out) { } bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { - if (!s_initialized || !g_interpolating || lhs == nullptr || rhs == nullptr) { + if (presentation_sync_active() || !g_interpolating || lhs == nullptr || rhs == nullptr) { return false; } @@ -393,9 +419,7 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { Mtx rhs_scratch; const Mtx* resolved_lhs = resolve_replacement(reinterpret_cast(lhs), &lhs_scratch); const Mtx* resolved_rhs = resolve_replacement(reinterpret_cast(rhs), &rhs_scratch); - if (resolved_lhs == reinterpret_cast(lhs) && - resolved_rhs == reinterpret_cast(rhs)) - { + if (resolved_lhs == reinterpret_cast(lhs) && resolved_rhs == reinterpret_cast(rhs)) { return false; } diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 7aaec4b005..ce575bd18f 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -240,6 +240,8 @@ void main01(void) { } if (dusk::getSettings().game.enableFrameInterpolation) { + dusk::frame_interp::notify_presentation_frame(); + while (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); if (dusk::getSettings().game.enableGyroAim) {