From d78c46a628980ac8729711eaa444ddd5a7d20b6b Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 21 Apr 2026 22:45:18 -0600 Subject: [PATCH] Rework interpolation pacing; interpolate every frame --- include/dusk/frame_interpolation.h | 1 + include/dusk/game_clock.h | 19 ++++------ src/dusk/frame_interpolation.cpp | 14 +++++--- src/dusk/game_clock.cpp | 57 ++++++++++++++++++------------ src/m_Do/m_Do_main.cpp | 33 +++++++++-------- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index bec9b600cf..8c19e7e59b 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -16,6 +16,7 @@ void ensure_initialized(); void begin_record(); void end_record(); +void begin_sim_tick(); void begin_frame(bool enabled, bool is_sim_frame, float step); void interpolate(); float get_interpolation_step(); diff --git a/include/dusk/game_clock.h b/include/dusk/game_clock.h index 4a394b5c2e..8bb277e070 100644 --- a/include/dusk/game_clock.h +++ b/include/dusk/game_clock.h @@ -1,13 +1,8 @@ -#ifndef DUSK_GAME_CLOCK_H -#define DUSK_GAME_CLOCK_H +#pragma once -#include - -namespace dusk { -namespace game_clock { +namespace dusk::game_clock { void ensure_initialized(); -void reset_accumulator(); void reset_frame_timer(); constexpr float sim_pace() { return 1.0f / 30.0f; } @@ -18,16 +13,14 @@ constexpr float ui_initial_dt() { return 1.0f / 60.0f; } struct MainLoopPacer { float presentation_dt_seconds; bool is_interpolating; - bool do_sim_tick; - float interpolation_step; + int sim_ticks_to_run; float sim_pace; }; MainLoopPacer advance_main_loop(); +void commit_sim_tick(); +float sample_interpolation_step(); float consume_interval(const void* consumer); -} // namespace game_clock -} // namespace dusk - -#endif // DUSK_GAME_CLOCK_H +} // namespace dusk::game_clock diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index e0057790df..f3f8a842e1 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -127,14 +127,20 @@ void ensure_initialized() { s_initialized = true; } +void begin_sim_tick() { + ensure_initialized(); + if (!g_enabled) { + return; + } + + s_interpolationCallBackWork.clear(); + s_cam_prev = std::move(s_cam_curr); +} + void begin_frame(bool enabled, bool is_sim_frame, float step) { g_enabled = enabled; g_is_sim_frame = is_sim_frame; g_step = std::clamp(step, 0.0f, 1.0f); - if (is_sim_frame) { - s_interpolationCallBackWork.clear(); - s_cam_prev = std::move(s_cam_curr); - } } bool is_enabled() { diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp index a262d0283c..29bd699c7d 100644 --- a/src/dusk/game_clock.cpp +++ b/src/dusk/game_clock.cpp @@ -5,34 +5,32 @@ #include #include -namespace dusk { -namespace game_clock { +namespace dusk::game_clock { using clock = std::chrono::steady_clock; bool s_initialized = false; clock::time_point s_previous_sample{}; -float s_sim_accumulator = 0.0f; +clock::time_point s_current_snapshot_time{}; std::unordered_map s_interval_last_sample; +constexpr clock::duration kSimPeriodDuration = + std::chrono::duration_cast(std::chrono::duration(sim_pace())); +constexpr int kMaxSimTicksPerFrame = 2; + void ensure_initialized() { if (s_initialized) { return; } s_previous_sample = clock::now(); - s_sim_accumulator = sim_pace(); + s_current_snapshot_time = s_previous_sample; s_initialized = true; } -void reset_accumulator() { - ensure_initialized(); - s_sim_accumulator = fmodf(s_sim_accumulator, sim_pace()); -} - void reset_frame_timer() { s_previous_sample = clock::now(); - s_sim_accumulator = 0.0f; + s_current_snapshot_time = s_previous_sample; } MainLoopPacer advance_main_loop() { @@ -42,25 +40,41 @@ MainLoopPacer advance_main_loop() { const float presentation_dt = std::chrono::duration(now - s_previous_sample).count(); s_previous_sample = now; - s_sim_accumulator += presentation_dt; - MainLoopPacer out{}; out.presentation_dt_seconds = presentation_dt; - const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit; + const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation && + !dusk::getTransientSettings().skipFrameRateLimit; out.is_interpolating = should_interpolate; out.sim_pace = sim_pace(); if (!should_interpolate) { - s_sim_accumulator = 0.0f; - out.do_sim_tick = true; - out.interpolation_step = 0.0f; - return out; - } else { - out.do_sim_tick = s_sim_accumulator >= sim_pace(); - out.interpolation_step = out.do_sim_tick ? 0.0f : s_sim_accumulator / sim_pace(); + s_current_snapshot_time = now; + out.sim_ticks_to_run = 1; return out; } + + int sim_ticks_to_run = 0; + clock::time_point projected_snapshot_time = s_current_snapshot_time; + const clock::time_point render_time = now - kSimPeriodDuration; + while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) { + projected_snapshot_time += kSimPeriodDuration; + sim_ticks_to_run++; + } + out.sim_ticks_to_run = sim_ticks_to_run; + return out; +} + +void commit_sim_tick() { + ensure_initialized(); + s_current_snapshot_time += kSimPeriodDuration; +} + +float sample_interpolation_step() { + ensure_initialized(); + const float step = + std::chrono::duration(clock::now() - s_current_snapshot_time).count() / sim_pace(); + return std::clamp(step, 0.0f, 1.0f); } float consume_interval(const void* consumer) { @@ -78,5 +92,4 @@ float consume_interval(const void* consumer) { return dt; } -} // namespace game_clock -} // namespace dusk +} // namespace dusk::game_clock diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 2164089e9f..579cbd8c8b 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -242,8 +242,6 @@ void main01(void) { continue; } - const dusk::game_clock::MainLoopPacer pacing = dusk::game_clock::advance_main_loop(); - VIWaitForRetrace(); dusk::lastFrameAuroraStats = *aurora_get_stats(); @@ -254,28 +252,33 @@ void main01(void) { mDoGph_gInf_c::updateRenderSize(); - dusk::frame_interp::begin_frame(pacing.is_interpolating, pacing.do_sim_tick, pacing.interpolation_step); + const auto pacing = dusk::game_clock::advance_main_loop(); if (pacing.is_interpolating) { - if (pacing.do_sim_tick) { + if (pacing.sim_ticks_to_run > 0) { + dusk::frame_interp::begin_frame(true, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); - mDoCPd_c::read(); - DuskDebugPad(); - dusk::gyro::read(pacing.sim_pace); - fapGm_Execute(); - mDoAud_Execute(); - dusk::game_clock::reset_accumulator(); + for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) { + dusk::frame_interp::begin_sim_tick(); + mDoCPd_c::read(); + DuskDebugPad(); + dusk::gyro::read(pacing.sim_pace); + fapGm_Execute(); + mDoAud_Execute(); + dusk::game_clock::commit_sim_tick(); + } } + + dusk::frame_interp::begin_frame(true, false, + dusk::game_clock::sample_interpolation_step()); dusk::frame_interp::interpolate(); dusk::frame_interp::begin_presentation_camera(); - if (!pacing.do_sim_tick) { - // run draw functions for anything specially marked to handle interp on non-sim - // ticks - fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw); - } + // run draw functions for anything specially marked to handle interp + fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw); cAPIGph_Painter(); dusk::frame_interp::end_presentation_camera(); dusk::frame_interp::set_ui_tick_pending(false); } else { + dusk::frame_interp::begin_frame(false, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); // Game Inputs