Merge pull request #485 from TwilitRealm/frame-pacing-2

Rework interpolation pacing; interpolate every frame
This commit is contained in:
TakaRikka
2026-04-21 22:05:33 -07:00
committed by GitHub
5 changed files with 70 additions and 54 deletions
+1
View File
@@ -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();
+6 -13
View File
@@ -1,13 +1,8 @@
#ifndef DUSK_GAME_CLOCK_H
#define DUSK_GAME_CLOCK_H
#pragma once
#include <stddef.h>
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
+10 -4
View File
@@ -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() {
+35 -22
View File
@@ -5,34 +5,32 @@
#include <cmath>
#include <unordered_map>
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<uintptr_t, clock::time_point> s_interval_last_sample;
constexpr clock::duration kSimPeriodDuration =
std::chrono::duration_cast<clock::duration>(std::chrono::duration<float>(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<float>(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<float>(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
+18 -15
View File
@@ -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