Add frame delta smoothing option (4.x)
Frame deltas are currently measured by querying the OS timer each frame. This is subject to random error. Frame delta smoothing instead filters the delta read from the OS by replacing it with the refresh rate delta wherever possible. This PR also contains code to estimate the refresh rate based on the input deltas, without reading the refresh rate from the host OS. The delta_smooth_enabled setting can also be modified at runtime through OS::, and there is also now a command line setting to override the project setting.
This commit is contained in:
parent
ffd32a244b
commit
7925670f81
|
@ -224,6 +224,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const {
|
||||||
return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec();
|
return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OS::set_delta_smoothing(bool p_enabled) {
|
||||||
|
::OS::get_singleton()->set_delta_smoothing(p_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OS::is_delta_smoothing_enabled() const {
|
||||||
|
return ::OS::get_singleton()->is_delta_smoothing_enabled();
|
||||||
|
}
|
||||||
|
|
||||||
void OS::alert(const String &p_alert, const String &p_title) {
|
void OS::alert(const String &p_alert, const String &p_title) {
|
||||||
::OS::get_singleton()->alert(p_alert, p_title);
|
::OS::get_singleton()->alert(p_alert, p_title);
|
||||||
}
|
}
|
||||||
|
@ -556,6 +564,9 @@ void OS::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec);
|
ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec);
|
||||||
ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec);
|
ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_delta_smoothing", "delta_smoothing_enabled"), &OS::set_delta_smoothing);
|
||||||
|
ClassDB::bind_method(D_METHOD("is_delta_smoothing_enabled"), &OS::is_delta_smoothing_enabled);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count);
|
ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count);
|
||||||
ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name);
|
ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name);
|
||||||
|
|
||||||
|
@ -631,6 +642,7 @@ void OS::_bind_methods() {
|
||||||
|
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode");
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec");
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled");
|
||||||
|
|
||||||
// Those default values need to be specified for the docs generator,
|
// Those default values need to be specified for the docs generator,
|
||||||
// to avoid using values from the documentation writer's own OS instance.
|
// to avoid using values from the documentation writer's own OS instance.
|
||||||
|
|
|
@ -141,6 +141,9 @@ public:
|
||||||
void set_low_processor_usage_mode_sleep_usec(int p_usec);
|
void set_low_processor_usage_mode_sleep_usec(int p_usec);
|
||||||
int get_low_processor_usage_mode_sleep_usec() const;
|
int get_low_processor_usage_mode_sleep_usec() const;
|
||||||
|
|
||||||
|
void set_delta_smoothing(bool p_enabled);
|
||||||
|
bool is_delta_smoothing_enabled() const;
|
||||||
|
|
||||||
void alert(const String &p_alert, const String &p_title = "ALERT!");
|
void alert(const String &p_alert, const String &p_title = "ALERT!");
|
||||||
void crash(const String &p_message);
|
void crash(const String &p_message);
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const {
|
||||||
return low_processor_usage_mode_sleep_usec;
|
return low_processor_usage_mode_sleep_usec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OS::set_delta_smoothing(bool p_enabled) {
|
||||||
|
_delta_smoothing_enabled = p_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OS::is_delta_smoothing_enabled() const {
|
||||||
|
return _delta_smoothing_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
String OS::get_executable_path() const {
|
String OS::get_executable_path() const {
|
||||||
return _execpath;
|
return _execpath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ class OS {
|
||||||
bool _keep_screen_on = true; // set default value to true, because this had been true before godot 2.0.
|
bool _keep_screen_on = true; // set default value to true, because this had been true before godot 2.0.
|
||||||
bool low_processor_usage_mode = false;
|
bool low_processor_usage_mode = false;
|
||||||
int low_processor_usage_mode_sleep_usec = 10000;
|
int low_processor_usage_mode_sleep_usec = 10000;
|
||||||
|
bool _delta_smoothing_enabled = false;
|
||||||
bool _verbose_stdout = false;
|
bool _verbose_stdout = false;
|
||||||
bool _debug_stdout = false;
|
bool _debug_stdout = false;
|
||||||
String _local_clipboard;
|
String _local_clipboard;
|
||||||
|
@ -154,6 +155,9 @@ public:
|
||||||
virtual void set_low_processor_usage_mode_sleep_usec(int p_usec);
|
virtual void set_low_processor_usage_mode_sleep_usec(int p_usec);
|
||||||
virtual int get_low_processor_usage_mode_sleep_usec() const;
|
virtual int get_low_processor_usage_mode_sleep_usec() const;
|
||||||
|
|
||||||
|
void set_delta_smoothing(bool p_enabled);
|
||||||
|
bool is_delta_smoothing_enabled() const;
|
||||||
|
|
||||||
virtual Vector<String> get_system_fonts() const { return Vector<String>(); };
|
virtual Vector<String> get_system_fonts() const { return Vector<String>(); };
|
||||||
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); };
|
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); };
|
||||||
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
|
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
|
||||||
|
|
|
@ -670,6 +670,9 @@
|
||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
<members>
|
<members>
|
||||||
|
<member name="delta_smoothing" type="bool" setter="set_delta_smoothing" getter="is_delta_smoothing_enabled" default="true">
|
||||||
|
If [code]true[/code], the engine filters the time delta measured between each frame, and attempts to compensate for random variation. This will only operate on systems where V-Sync is active.
|
||||||
|
</member>
|
||||||
<member name="low_processor_usage_mode" type="bool" setter="set_low_processor_usage_mode" getter="is_in_low_processor_usage_mode" default="false">
|
<member name="low_processor_usage_mode" type="bool" setter="set_low_processor_usage_mode" getter="is_in_low_processor_usage_mode" default="false">
|
||||||
If [code]true[/code], the engine optimizes for low processor usage by only refreshing the screen if needed. Can improve battery consumption on mobile.
|
If [code]true[/code], the engine optimizes for low processor usage by only refreshing the screen if needed. Can improve battery consumption on mobile.
|
||||||
</member>
|
</member>
|
||||||
|
|
|
@ -284,6 +284,11 @@
|
||||||
<member name="application/config/windows_native_icon" type="String" setter="" getter="" default="""">
|
<member name="application/config/windows_native_icon" type="String" setter="" getter="" default="""">
|
||||||
Icon set in [code].ico[/code] format used on Windows to set the game's icon. This is done automatically on start by calling [method DisplayServer.set_native_icon].
|
Icon set in [code].ico[/code] format used on Windows to set the game's icon. This is done automatically on start by calling [method DisplayServer.set_native_icon].
|
||||||
</member>
|
</member>
|
||||||
|
<member name="application/run/delta_smoothing" type="bool" setter="" getter="" default="true">
|
||||||
|
Time samples for frame deltas are subject to random variation introduced by the platform, even when frames are displayed at regular intervals thanks to V-Sync. This can lead to jitter. Delta smoothing can often give a better result by filtering the input deltas to correct for minor fluctuations from the refresh rate.
|
||||||
|
[b]Note:[/b] Delta smoothing is only attempted when [member display/window/vsync/vsync_mode] is set to [code]enabled[/code], as it does not work well without V-Sync.
|
||||||
|
It may take several seconds at a stable frame rate before the smoothing is initially activated. It will only be active on machines where performance is adequate to render frames at the refresh rate.
|
||||||
|
</member>
|
||||||
<member name="application/run/disable_stderr" type="bool" setter="" getter="" default="false">
|
<member name="application/run/disable_stderr" type="bool" setter="" getter="" default="false">
|
||||||
If [code]true[/code], disables printing to standard error. If [code]true[/code], this also hides error and warning messages printed by [method @GlobalScope.push_error] and [method @GlobalScope.push_warning]. See also [member application/run/disable_stdout].
|
If [code]true[/code], disables printing to standard error. If [code]true[/code], this also hides error and warning messages printed by [method @GlobalScope.push_error] and [method @GlobalScope.push_warning]. See also [member application/run/disable_stdout].
|
||||||
Changes to this setting will only be applied upon restarting the application.
|
Changes to this setting will only be applied upon restarting the application.
|
||||||
|
|
|
@ -468,6 +468,7 @@ void Main::print_help(const char *p_binary) {
|
||||||
OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
|
OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n");
|
||||||
OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
|
OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n");
|
||||||
OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
|
OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
|
||||||
|
OS::get_singleton()->print(" --delta-smoothing <enable> Enable or disable frame delta smoothing ['enable', 'disable'].\n");
|
||||||
OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n");
|
OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n");
|
||||||
OS::get_singleton()->print("\n");
|
OS::get_singleton()->print("\n");
|
||||||
|
|
||||||
|
@ -791,6 +792,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
||||||
Vector<String> breakpoints;
|
Vector<String> breakpoints;
|
||||||
bool use_custom_res = true;
|
bool use_custom_res = true;
|
||||||
bool force_res = false;
|
bool force_res = false;
|
||||||
|
bool delta_smoothing_override = false;
|
||||||
|
|
||||||
String default_renderer = "";
|
String default_renderer = "";
|
||||||
String default_renderer_mobile = "";
|
String default_renderer_mobile = "";
|
||||||
|
@ -1000,6 +1002,29 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
||||||
OS::get_singleton()->print("Missing tablet driver argument, aborting.\n");
|
OS::get_singleton()->print("Missing tablet driver argument, aborting.\n");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
} else if (I->get() == "--delta-smoothing") {
|
||||||
|
if (I->next()) {
|
||||||
|
String string = I->next()->get();
|
||||||
|
bool recognised = false;
|
||||||
|
if (string == "enable") {
|
||||||
|
OS::get_singleton()->set_delta_smoothing(true);
|
||||||
|
delta_smoothing_override = true;
|
||||||
|
recognised = true;
|
||||||
|
}
|
||||||
|
if (string == "disable") {
|
||||||
|
OS::get_singleton()->set_delta_smoothing(false);
|
||||||
|
delta_smoothing_override = false;
|
||||||
|
recognised = true;
|
||||||
|
}
|
||||||
|
if (!recognised) {
|
||||||
|
OS::get_singleton()->print("Delta-smoothing argument not recognised, aborting.\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
N = I->next()->next();
|
||||||
|
} else {
|
||||||
|
OS::get_singleton()->print("Missing delta-smoothing argument, aborting.\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
} else if (I->get() == "--single-window") { // force single window
|
} else if (I->get() == "--single-window") { // force single window
|
||||||
|
|
||||||
single_window = true;
|
single_window = true;
|
||||||
|
@ -1908,6 +1933,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
|
||||||
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(
|
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(
|
||||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater"), 6900)); // Roughly 144 FPS
|
GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater"), 6900)); // Roughly 144 FPS
|
||||||
|
|
||||||
|
GLOBAL_DEF("application/run/delta_smoothing", true);
|
||||||
|
if (!delta_smoothing_override) {
|
||||||
|
OS::get_singleton()->set_delta_smoothing(GLOBAL_GET("application/run/delta_smoothing"));
|
||||||
|
}
|
||||||
|
|
||||||
GLOBAL_DEF("display/window/ios/allow_high_refresh_rate", true);
|
GLOBAL_DEF("display/window/ios/allow_high_refresh_rate", true);
|
||||||
GLOBAL_DEF("display/window/ios/hide_home_indicator", true);
|
GLOBAL_DEF("display/window/ios/hide_home_indicator", true);
|
||||||
GLOBAL_DEF("display/window/ios/hide_status_bar", true);
|
GLOBAL_DEF("display/window/ios/hide_status_bar", true);
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
|
|
||||||
#include "main_timer_sync.h"
|
#include "main_timer_sync.h"
|
||||||
|
|
||||||
|
#include "core/os/os.h"
|
||||||
|
#include "servers/display_server.h"
|
||||||
|
|
||||||
void MainFrameTime::clamp_process_step(double min_process_step, double max_process_step) {
|
void MainFrameTime::clamp_process_step(double min_process_step, double max_process_step) {
|
||||||
if (process_step < min_process_step) {
|
if (process_step < min_process_step) {
|
||||||
process_step = min_process_step;
|
process_step = min_process_step;
|
||||||
|
@ -40,6 +43,258 @@ void MainFrameTime::clamp_process_step(double min_process_step, double max_proce
|
||||||
|
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
|
|
||||||
|
void MainTimerSync::DeltaSmoother::update_refresh_rate_estimator(int64_t p_delta) {
|
||||||
|
// the calling code should prevent 0 or negative values of delta
|
||||||
|
// (preventing divide by zero)
|
||||||
|
|
||||||
|
// note that if the estimate gets locked, and something external changes this
|
||||||
|
// (e.g. user changes to non-vsync in the OS), then the results may be less than ideal,
|
||||||
|
// but usually it will detect this via the FPS measurement and not attempt smoothing.
|
||||||
|
// This should be a rare occurrence anyway, and will be cured next time user restarts game.
|
||||||
|
if (_estimate_locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First average the delta over NUM_READINGS
|
||||||
|
_estimator_total_delta += p_delta;
|
||||||
|
_estimator_delta_readings++;
|
||||||
|
|
||||||
|
const int NUM_READINGS = 60;
|
||||||
|
|
||||||
|
if (_estimator_delta_readings < NUM_READINGS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use average
|
||||||
|
p_delta = _estimator_total_delta / NUM_READINGS;
|
||||||
|
|
||||||
|
// reset the averager for next time
|
||||||
|
_estimator_delta_readings = 0;
|
||||||
|
_estimator_total_delta = 0;
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
int fps = Math::round(1000000.0 / p_delta);
|
||||||
|
|
||||||
|
// initial estimation, to speed up converging, special case we will estimate the refresh rate
|
||||||
|
// from the first average FPS reading
|
||||||
|
if (_estimated_fps == 0) {
|
||||||
|
// below 50 might be chugging loading stuff, or else
|
||||||
|
// dropping loads of frames, so the estimate will be inaccurate
|
||||||
|
if (fps >= 50) {
|
||||||
|
_estimated_fps = fps;
|
||||||
|
#ifdef GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
print_line("initial guess (average measured) refresh rate: " + itos(fps));
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
// can't get started until above 50
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we hit our exact estimated refresh rate.
|
||||||
|
// increase our confidence in the estimate.
|
||||||
|
if (fps == _estimated_fps) {
|
||||||
|
// note that each hit is an average of NUM_READINGS frames
|
||||||
|
_hits_at_estimated++;
|
||||||
|
|
||||||
|
if (_estimate_complete && _hits_at_estimated == 20) {
|
||||||
|
_estimate_locked = true;
|
||||||
|
#ifdef GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
print_line("estimate LOCKED at " + itos(_estimated_fps) + " fps");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are getting pretty confident in this estimate, decide it is complete
|
||||||
|
// (it can still be increased later, and possibly lowered but only for a short time)
|
||||||
|
if ((!_estimate_complete) && (_hits_at_estimated > 2)) {
|
||||||
|
// when the estimate is complete we turn on smoothing
|
||||||
|
if (_estimated_fps) {
|
||||||
|
_estimate_complete = true;
|
||||||
|
_vsync_delta = 1000000 / _estimated_fps;
|
||||||
|
|
||||||
|
#ifdef GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
print_line("estimate complete. vsync_delta " + itos(_vsync_delta) + ", fps " + itos(_estimated_fps));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
if ((_hits_at_estimated % (400 / NUM_READINGS)) == 0) {
|
||||||
|
String sz = "hits at estimated : " + itos(_hits_at_estimated) + ", above : " + itos(_hits_above_estimated) + "( " + itos(_hits_one_above_estimated) + " ), below : " + itos(_hits_below_estimated) + " (" + itos(_hits_one_below_estimated) + " )";
|
||||||
|
|
||||||
|
print_line(sz);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int SIGNIFICANCE_UP = 1;
|
||||||
|
const int SIGNIFICANCE_DOWN = 2;
|
||||||
|
|
||||||
|
// we are not usually interested in slowing the estimate
|
||||||
|
// but we may have overshot, so make it possible to reduce
|
||||||
|
if (fps < _estimated_fps) {
|
||||||
|
// micro changes
|
||||||
|
if (fps == (_estimated_fps - 1)) {
|
||||||
|
_hits_one_below_estimated++;
|
||||||
|
|
||||||
|
if ((_hits_one_below_estimated > _hits_at_estimated) && (_hits_one_below_estimated > SIGNIFICANCE_DOWN)) {
|
||||||
|
_estimated_fps--;
|
||||||
|
made_new_estimate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_hits_below_estimated++;
|
||||||
|
|
||||||
|
// don't allow large lowering if we are established at a refresh rate, as it will probably be dropped frames
|
||||||
|
bool established = _estimate_complete && (_hits_at_estimated > 10);
|
||||||
|
|
||||||
|
// macro changes
|
||||||
|
// note there is a large barrier to macro lowering. That is because it is more likely to be dropped frames
|
||||||
|
// than mis-estimation of the refresh rate.
|
||||||
|
if (!established) {
|
||||||
|
if (((_hits_below_estimated / 8) > _hits_at_estimated) && (_hits_below_estimated > SIGNIFICANCE_DOWN)) {
|
||||||
|
// decrease the estimate
|
||||||
|
_estimated_fps--;
|
||||||
|
made_new_estimate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes increasing the estimate.
|
||||||
|
// micro changes
|
||||||
|
if (fps == (_estimated_fps + 1)) {
|
||||||
|
_hits_one_above_estimated++;
|
||||||
|
|
||||||
|
if ((_hits_one_above_estimated > _hits_at_estimated) && (_hits_one_above_estimated > SIGNIFICANCE_UP)) {
|
||||||
|
_estimated_fps++;
|
||||||
|
made_new_estimate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_hits_above_estimated++;
|
||||||
|
|
||||||
|
// macro changes
|
||||||
|
if ((_hits_above_estimated > _hits_at_estimated) && (_hits_above_estimated > SIGNIFICANCE_UP)) {
|
||||||
|
// increase the estimate
|
||||||
|
int change = fps - _estimated_fps;
|
||||||
|
change /= 2;
|
||||||
|
change = MAX(1, change);
|
||||||
|
|
||||||
|
_estimated_fps += change;
|
||||||
|
made_new_estimate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainTimerSync::DeltaSmoother::fps_allows_smoothing(int64_t p_delta) {
|
||||||
|
_measurement_time += p_delta;
|
||||||
|
_measurement_frame_count++;
|
||||||
|
|
||||||
|
if (_measurement_frame_count == _measurement_end_frame) {
|
||||||
|
// only switch on or off if the estimate is complete
|
||||||
|
if (_estimate_complete) {
|
||||||
|
int64_t time_passed = _measurement_time - _measurement_start_time;
|
||||||
|
|
||||||
|
// average delta
|
||||||
|
time_passed /= MEASURE_FPS_OVER_NUM_FRAMES;
|
||||||
|
|
||||||
|
// estimate fps
|
||||||
|
if (time_passed) {
|
||||||
|
double fps = 1000000.0 / time_passed;
|
||||||
|
double ratio = fps / (double)_estimated_fps;
|
||||||
|
|
||||||
|
//print_line("ratio : " + String(Variant(ratio)));
|
||||||
|
|
||||||
|
if ((ratio > 0.95) && (ratio < 1.05)) {
|
||||||
|
_measurement_allows_smoothing = true;
|
||||||
|
} else {
|
||||||
|
_measurement_allows_smoothing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // estimate complete
|
||||||
|
|
||||||
|
// new start time for next iteration
|
||||||
|
_measurement_start_time = _measurement_time;
|
||||||
|
_measurement_end_frame += MEASURE_FPS_OVER_NUM_FRAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _measurement_allows_smoothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
|
||||||
|
// Conditions to disable smoothing.
|
||||||
|
// 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 off with sync enabled in the app, the smoothing must detect this
|
||||||
|
// via the error metric and switch off.
|
||||||
|
// Also only try smoothing if vsync is enabled (classical vsync, not new types) ..
|
||||||
|
// This condition is currently checked before calling smooth_delta().
|
||||||
|
if (!OS::get_singleton()->is_delta_smoothing_enabled() || Engine::get_singleton()->is_editor_hint()) {
|
||||||
|
return p_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only attempt smoothing if vsync is selected
|
||||||
|
DisplayServer::VSyncMode vsync_mode = DisplayServer::get_singleton()->window_get_vsync_mode(DisplayServer::MAIN_WINDOW_ID);
|
||||||
|
if (vsync_mode != DisplayServer::VSYNC_ENABLED) {
|
||||||
|
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
|
||||||
|
// conditions not close to the estimated FPS
|
||||||
|
if (!fps_allows_smoothing(p_delta)) {
|
||||||
|
return p_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't cope with negative deltas .. OS bug on some hardware
|
||||||
|
// and also very small deltas caused by vsync being off.
|
||||||
|
// This could possibly be part of a hiccup, this value isn't fixed in stone...
|
||||||
|
if (p_delta < 1000) {
|
||||||
|
return p_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note still some vsync off will still get through to this point...
|
||||||
|
// and we need to cope with it by not converging the estimator / and / or not smoothing
|
||||||
|
update_refresh_rate_estimator(p_delta);
|
||||||
|
|
||||||
|
// no smoothing until we know what the refresh rate is
|
||||||
|
if (!_estimate_complete) {
|
||||||
|
return p_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate the time we have available to use
|
||||||
|
_leftover_time += p_delta;
|
||||||
|
|
||||||
|
// how many vsyncs units can we fit?
|
||||||
|
int64_t units = _leftover_time / _vsync_delta;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
// in which case we should switch off delta smoothing, or re-estimate the refresh rate)
|
||||||
|
units = MAX(units, 1);
|
||||||
|
|
||||||
|
_leftover_time -= units * _vsync_delta;
|
||||||
|
// print_line("units " + itos(units) + ", leftover " + itos(_leftover_time/1000) + " ms");
|
||||||
|
|
||||||
|
return units * _vsync_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
|
||||||
// returns the fraction of p_physics_step required for the timer to overshoot
|
// returns the fraction of p_physics_step required for the timer to overshoot
|
||||||
// before advance_core considers changing the physics_steps return from
|
// before advance_core considers changing the physics_steps return from
|
||||||
// the typical values as defined by typical_physics_steps
|
// the typical values as defined by typical_physics_steps
|
||||||
|
@ -236,6 +491,8 @@ double MainTimerSync::get_cpu_process_step() {
|
||||||
uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec;
|
uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec;
|
||||||
last_cpu_ticks_usec = current_cpu_ticks_usec;
|
last_cpu_ticks_usec = current_cpu_ticks_usec;
|
||||||
|
|
||||||
|
cpu_ticks_elapsed = _delta_smoother.smooth_delta(cpu_ticks_elapsed);
|
||||||
|
|
||||||
return cpu_ticks_elapsed / 1000000.0;
|
return cpu_ticks_elapsed / 1000000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
|
|
||||||
#include "core/config/engine.h"
|
#include "core/config/engine.h"
|
||||||
|
|
||||||
|
// Uncomment this define to get more debugging logs for the delta smoothing.
|
||||||
|
// #define GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
|
||||||
struct MainFrameTime {
|
struct MainFrameTime {
|
||||||
double process_step; // delta time to advance during process()
|
double process_step; // delta time to advance during process()
|
||||||
int physics_steps; // number of times to iterate the physics engine
|
int physics_steps; // number of times to iterate the physics engine
|
||||||
|
@ -42,6 +45,66 @@ struct MainFrameTime {
|
||||||
};
|
};
|
||||||
|
|
||||||
class MainTimerSync {
|
class MainTimerSync {
|
||||||
|
class DeltaSmoother {
|
||||||
|
public:
|
||||||
|
// pass the recorded delta, returns a smoothed delta
|
||||||
|
int64_t smooth_delta(int64_t p_delta);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void update_refresh_rate_estimator(int64_t p_delta);
|
||||||
|
bool fps_allows_smoothing(int64_t p_delta);
|
||||||
|
|
||||||
|
// estimated vsync delta (monitor refresh rate)
|
||||||
|
int64_t _vsync_delta = 16666;
|
||||||
|
|
||||||
|
// keep track of accumulated time so we know how many vsyncs to advance by
|
||||||
|
int64_t _leftover_time = 0;
|
||||||
|
|
||||||
|
// keep a rough measurement of the FPS as we run.
|
||||||
|
// If this drifts a long way below or above the refresh rate, the machine
|
||||||
|
// is struggling to keep up, and we can switch off smoothing. This
|
||||||
|
// also deals with the case that the user has overridden the vsync in the GPU settings,
|
||||||
|
// in which case we don't want to try smoothing.
|
||||||
|
static const int MEASURE_FPS_OVER_NUM_FRAMES = 64;
|
||||||
|
|
||||||
|
int64_t _measurement_time = 0;
|
||||||
|
int64_t _measurement_frame_count = 0;
|
||||||
|
int64_t _measurement_end_frame = MEASURE_FPS_OVER_NUM_FRAMES;
|
||||||
|
int64_t _measurement_start_time = 0;
|
||||||
|
bool _measurement_allows_smoothing = true;
|
||||||
|
|
||||||
|
// we can estimate the fps by growing it on condition
|
||||||
|
// that a large proportion of frames are higher than the current estimate.
|
||||||
|
int32_t _estimated_fps = 0;
|
||||||
|
int32_t _hits_at_estimated = 0;
|
||||||
|
int32_t _hits_above_estimated = 0;
|
||||||
|
int32_t _hits_below_estimated = 0;
|
||||||
|
int32_t _hits_one_above_estimated = 0;
|
||||||
|
int32_t _hits_one_below_estimated = 0;
|
||||||
|
bool _estimate_complete = false;
|
||||||
|
bool _estimate_locked = false;
|
||||||
|
|
||||||
|
// data for averaging the delta over a second or so
|
||||||
|
// to prevent spurious values
|
||||||
|
int64_t _estimator_total_delta = 0;
|
||||||
|
int32_t _estimator_delta_readings = 0;
|
||||||
|
|
||||||
|
void made_new_estimate() {
|
||||||
|
_hits_above_estimated = 0;
|
||||||
|
_hits_at_estimated = 0;
|
||||||
|
_hits_below_estimated = 0;
|
||||||
|
_hits_one_above_estimated = 0;
|
||||||
|
_hits_one_below_estimated = 0;
|
||||||
|
|
||||||
|
_estimate_complete = false;
|
||||||
|
|
||||||
|
#ifdef GODOT_DEBUG_DELTA_SMOOTHER
|
||||||
|
print_line("estimated fps " + itos(_estimated_fps));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} _delta_smoother;
|
||||||
|
|
||||||
// wall clock time measured on the main thread
|
// wall clock time measured on the main thread
|
||||||
uint64_t last_cpu_ticks_usec = 0;
|
uint64_t last_cpu_ticks_usec = 0;
|
||||||
uint64_t current_cpu_ticks_usec = 0;
|
uint64_t current_cpu_ticks_usec = 0;
|
||||||
|
|
Loading…
Reference in New Issue