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.
This commit is contained in:
lawnjelly 2021-07-22 12:04:22 +01:00
parent d3f500cf33
commit 93557927e4
2 changed files with 35 additions and 5 deletions

View file

@ -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.
</member>
<member name="application/run/delta_sync_after_draw" type="bool" setter="" getter="" default="false">
[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.
</member>
<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 @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.
</member>
<member name="rendering/2d/opengl/batching_send_null" type="int" setter="" getter="" default="0">
[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.
</member>
<member name="rendering/2d/opengl/batching_stream" type="int" setter="" getter="" default="0">
[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.
</member>
<member name="rendering/2d/opengl/legacy_orphan_buffers" type="int" setter="" getter="" default="0">
[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.
</member>
<member name="rendering/2d/opengl/legacy_stream" type="int" setter="" getter="" default="0">
[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.
</member>
<member name="rendering/2d/options/ninepatch_mode" type="int" setter="" getter="" default="1">
@ -1097,7 +1100,7 @@
When batching is on, this regularly prints a frame diagnosis log. Note that this will degrade performance.
</member>
<member name="rendering/batching/debug/flash_batching" type="bool" setter="" getter="" default="false">
[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.
</member>
<member name="rendering/batching/lights/max_join_items" type="int" setter="" getter="" default="32">
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.

View file

@ -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;