From 93557927e42f1ef7d3e06bd59474c8d4c235b4e6 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Thu, 22 Jul 2021 12:04:22 +0100 Subject: [PATCH] Add option to sync frame delta after draw Investigations have showed that a lot of the random variation in frame deltas causing glitches may be due to sampling the time at the wrong place in the game loop. Although sampling at the start of Main::Iteration makes logical sense, the most consistent deltas may be better measured after the location likely to block at vsync - either the OpenGL draw commands or the SwapBuffers. Here we add an experimental setting to allow syncing after the OpenGL draw section of Main::Iteration. --- doc/classes/ProjectSettings.xml | 13 ++++++++----- main/main.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 9315e3b79e..4649a133fd 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -249,6 +249,9 @@ [b]Note:[/b] Delta smoothing is only attempted when [member display/window/vsync/use_vsync] is switched on, 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. + + [b]Experimental.[/b] Shifts the measurement of delta time for each frame to just after the drawing has taken place. This may lead to more consistent deltas and a reduction in frame stutters. + If [code]true[/code], disables printing to standard error. If [code]true[/code], this also hides error and warning messages printed by [method @GDScript.push_error] and [method @GDScript.push_warning]. See also [member application/run/disable_stdout]. Changes to this setting will only be applied upon restarting the application. @@ -1059,19 +1062,19 @@ [b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_jitter_fix] instead. - [b]Experimental[/b] Calls [code]glBufferData[/code] with NULL data prior to uploading batching data. This may not be necessary but can be used for safety. + [b]Experimental.[/b] Calls [code]glBufferData[/code] with NULL data prior to uploading batching data. This may not be necessary but can be used for safety. [b]Note:[/b] Use with care. You are advised to leave this as default for exports. A non-default setting that works better on your machine may adversely affect performance for end users. - [b]Experimental[/b] If set to on, uses the [code]GL_STREAM_DRAW[/code] flag for batching buffer uploads. If off, uses the [code]GL_DYNAMIC_DRAW[/code] flag. + [b]Experimental.[/b] If set to on, uses the [code]GL_STREAM_DRAW[/code] flag for batching buffer uploads. If off, uses the [code]GL_DYNAMIC_DRAW[/code] flag. [b]Note:[/b] Use with care. You are advised to leave this as default for exports. A non-default setting that works better on your machine may adversely affect performance for end users. - [b]Experimental[/b] If set to on, this applies buffer orphaning - [code]glBufferData[/code] is called with NULL data and the full buffer size prior to uploading new data. This can be important to avoid stalling on some hardware. + [b]Experimental.[/b] If set to on, this applies buffer orphaning - [code]glBufferData[/code] is called with NULL data and the full buffer size prior to uploading new data. This can be important to avoid stalling on some hardware. [b]Note:[/b] Use with care. You are advised to leave this as default for exports. A non-default setting that works better on your machine may adversely affect performance for end users. - [b]Experimental[/b] If set to on, uses the [code]GL_STREAM_DRAW[/code] flag for legacy buffer uploads. If off, uses the [code]GL_DYNAMIC_DRAW[/code] flag. + [b]Experimental.[/b] If set to on, uses the [code]GL_STREAM_DRAW[/code] flag for legacy buffer uploads. If off, uses the [code]GL_DYNAMIC_DRAW[/code] flag. [b]Note:[/b] Use with care. You are advised to leave this as default for exports. A non-default setting that works better on your machine may adversely affect performance for end users. @@ -1097,7 +1100,7 @@ When batching is on, this regularly prints a frame diagnosis log. Note that this will degrade performance. - [b]Experimental[/b] For regression testing against the old renderer. If this is switched on, and [code]use_batching[/code] is set, the renderer will swap alternately between using the old renderer, and the batched renderer, on each frame. This makes it easy to identify visual differences. Performance will be degraded. + [b]Experimental.[/b] For regression testing against the old renderer. If this is switched on, and [code]use_batching[/code] is set, the renderer will swap alternately between using the old renderer, and the batched renderer, on each frame. This makes it easy to identify visual differences. Performance will be degraded. Lights have the potential to prevent joining items, and break many of the performance benefits of batching. This setting enables some complex logic to allow joining items if their lighting is similar, and overlap tests pass. This can significantly improve performance in some games. Set to 0 to switch off. With large values the cost of overlap tests may lead to diminishing returns. diff --git a/main/main.cpp b/main/main.cpp index 6e66570fcd..8edec36e0f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -122,6 +122,7 @@ static String locale; static bool show_help = false; static bool auto_quit = false; static OS::ProcessID allow_focus_steal_pid = 0; +static bool delta_sync_after_draw = false; #ifdef TOOLS_ENABLED static bool auto_build_solutions = false; #endif @@ -1204,6 +1205,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(GLOBAL_DEF("application/run/low_processor_mode_sleep_usec", 6900)); // Roughly 144 FPS ProjectSettings::get_singleton()->set_custom_property_info("application/run/low_processor_mode_sleep_usec", PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater")); // No negative numbers + delta_sync_after_draw = GLOBAL_DEF("application/run/delta_sync_after_draw", false); GLOBAL_DEF("application/run/delta_smoothing", true); if (!delta_smoothing_override) { OS::get_singleton()->set_delta_smoothing(GLOBAL_GET("application/run/delta_smoothing")); @@ -2044,13 +2046,31 @@ bool Main::is_iterating() { static uint64_t physics_process_max = 0; static uint64_t idle_process_max = 0; +#ifndef TOOLS_ENABLED +static uint64_t frame_delta_sync_time = 0; +#endif + bool Main::iteration() { //for now do not error on this //ERR_FAIL_COND_V(iterating, false); iterating++; +#ifdef TOOLS_ENABLED uint64_t ticks = OS::get_singleton()->get_ticks_usec(); +#else + // we can either sync the delta from here, or later in the iteration + uint64_t ticks_at_start = OS::get_singleton()->get_ticks_usec(); + uint64_t ticks_difference = ticks_at_start - frame_delta_sync_time; + + // if we are syncing at start or if frame_delta_sync_time is being initialized + // or a large gap has happened between the last delta_sync_time and now + if (!delta_sync_after_draw || (ticks_difference > 100000)) { + frame_delta_sync_time = ticks_at_start; + } + uint64_t ticks = frame_delta_sync_time; +#endif + Engine::get_singleton()->_frame_ticks = ticks; main_timer_sync.set_cpu_ticks_usec(ticks); main_timer_sync.set_fixed_fps(fixed_fps); @@ -2138,6 +2158,13 @@ bool Main::iteration() { } } +#ifndef TOOLS_ENABLED + // we can choose to sync delta from here, just after the draw + if (delta_sync_after_draw) { + frame_delta_sync_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + idle_process_ticks = OS::get_singleton()->get_ticks_usec() - idle_begin; idle_process_max = MAX(idle_process_ticks, idle_process_max); uint64_t frame_time = OS::get_singleton()->get_ticks_usec() - ticks;