Delta smoothing - fix overflow for long frames

Extremely long frames caused by suspending and resuming the machine could result in an overflow in the delta smoothing because it uses 32 bit math on delta values measured in nanoseconds.

This PR puts a cap of a second as the maximum frame delta that will be processed by the smoothing, otherwise it returns the frame delta 64 bit value unaltered. It also converts internal math to explicitly use 64 bit integers.
This commit is contained in:
lawnjelly 2021-08-11 08:03:45 +01:00
parent 03c41fa34c
commit 3025b6d299
2 changed files with 23 additions and 17 deletions

View file

@ -43,7 +43,7 @@ void MainFrameTime::clamp_idle(float min_idle_step, float max_idle_step) {
///////////////////////////////// /////////////////////////////////
void MainTimerSync::DeltaSmoother::update_refresh_rate_estimator(int p_delta) { void MainTimerSync::DeltaSmoother::update_refresh_rate_estimator(int64_t p_delta) {
// the calling code should prevent 0 or negative values of delta // the calling code should prevent 0 or negative values of delta
// (preventing divide by zero) // (preventing divide by zero)
@ -195,7 +195,7 @@ void MainTimerSync::DeltaSmoother::update_refresh_rate_estimator(int p_delta) {
} }
} }
bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int p_delta) { bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int64_t p_delta) {
_measurement_time += p_delta; _measurement_time += p_delta;
_measurement_frame_count++; _measurement_frame_count++;
@ -209,8 +209,8 @@ bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int p_delta) {
// estimate fps // estimate fps
if (time_passed) { if (time_passed) {
float fps = 1000000.0f / time_passed; double fps = 1000000.0 / time_passed;
float ratio = fps / (float)_estimated_fps; double ratio = fps / (double)_estimated_fps;
//print_line("ratio : " + String(Variant(ratio))); //print_line("ratio : " + String(Variant(ratio)));
@ -230,7 +230,7 @@ bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int p_delta) {
return _measurement_allows_smoothing; return _measurement_allows_smoothing;
} }
int MainTimerSync::DeltaSmoother::smooth_delta(int p_delta) { int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
// Conditions to disable smoothing. // Conditions to disable smoothing.
// Note that vsync is a request, it cannot be relied on, the OS may override this. // Note that vsync is a request, it cannot be relied on, the OS may override this.
// If the OS turns vsync on without vsync in the app, smoothing will not be enabled. // If the OS turns vsync on without vsync in the app, smoothing will not be enabled.
@ -240,6 +240,12 @@ int MainTimerSync::DeltaSmoother::smooth_delta(int p_delta) {
return p_delta; return p_delta;
} }
// Very important, ignore long deltas and pass them back unmodified.
// This is to deal with resuming after suspend for long periods.
if (p_delta > 1000000) {
return p_delta;
}
// keep a running guesstimate of the FPS, and turn off smoothing if // keep a running guesstimate of the FPS, and turn off smoothing if
// conditions not close to the estimated FPS // conditions not close to the estimated FPS
if (!fps_allows_smoothing(p_delta)) { if (!fps_allows_smoothing(p_delta)) {
@ -266,7 +272,7 @@ int MainTimerSync::DeltaSmoother::smooth_delta(int p_delta) {
_leftover_time += p_delta; _leftover_time += p_delta;
// how many vsyncs units can we fit? // how many vsyncs units can we fit?
int units = _leftover_time / _vsync_delta; int64_t units = _leftover_time / _vsync_delta;
// a delta must include minimum 1 vsync // a delta must include minimum 1 vsync
// (if it is less than that, it is either random error or we are no longer running at the vsync rate, // (if it is less than that, it is either random error or we are no longer running at the vsync rate,

View file

@ -48,11 +48,11 @@ class MainTimerSync {
class DeltaSmoother { class DeltaSmoother {
public: public:
// pass the recorded delta, returns a smoothed delta // pass the recorded delta, returns a smoothed delta
int smooth_delta(int p_delta); int64_t smooth_delta(int64_t p_delta);
private: private:
void update_refresh_rate_estimator(int p_delta); void update_refresh_rate_estimator(int64_t p_delta);
bool fps_allows_smoothing(int p_delta); bool fps_allows_smoothing(int64_t p_delta);
// estimated vsync delta (monitor refresh rate) // estimated vsync delta (monitor refresh rate)
int64_t _vsync_delta = 16666; int64_t _vsync_delta = 16666;
@ -75,19 +75,19 @@ class MainTimerSync {
// we can estimate the fps by growing it on condition // we can estimate the fps by growing it on condition
// that a large proportion of frames are higher than the current estimate. // that a large proportion of frames are higher than the current estimate.
int _estimated_fps = 0; int32_t _estimated_fps = 0;
int _hits_at_estimated = 0; int32_t _hits_at_estimated = 0;
int _hits_above_estimated = 0; int32_t _hits_above_estimated = 0;
int _hits_below_estimated = 0; int32_t _hits_below_estimated = 0;
int _hits_one_above_estimated = 0; int32_t _hits_one_above_estimated = 0;
int _hits_one_below_estimated = 0; int32_t _hits_one_below_estimated = 0;
bool _estimate_complete = false; bool _estimate_complete = false;
bool _estimate_locked = false; bool _estimate_locked = false;
// data for averaging the delta over a second or so // data for averaging the delta over a second or so
// to prevent spurious values // to prevent spurious values
int _estimator_total_delta = 0; int64_t _estimator_total_delta = 0;
int _estimator_delta_readings = 0; int32_t _estimator_delta_readings = 0;
void made_new_estimate() { void made_new_estimate() {
_hits_above_estimated = 0; _hits_above_estimated = 0;