From 3856fcd48e679e6122d675b1cf1f9f19def63d31 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 7 Jan 2014 08:52:02 +0100 Subject: [PATCH] [OpenTK] More robust timing for UpdateFrame and RenderFrame FrameEventArgs.Time should no longer drift from clock time measured outside GameWindow. --- Source/OpenTK/GameWindow.cs | 145 +++++++++++++++--------------------- 1 file changed, 62 insertions(+), 83 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index 0f41a3f8..048d5f64 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -1,4 +1,4 @@ -#region License +#region License // // The Open Toolkit Library License // @@ -75,7 +75,7 @@ namespace OpenTK { #region --- Fields --- - object exit_lock = new object(); + readonly Stopwatch watch = new Stopwatch(); IGraphicsContext glContext; @@ -83,6 +83,10 @@ namespace OpenTK double update_period, render_period; double target_update_period, target_render_period; + + double update_timestamp; // timestamp of last UpdateFrame event + double render_timestamp; // timestamp of last RenderFrame event + // TODO: Implement these: double update_time, render_time; VSyncMode vsync; @@ -402,10 +406,6 @@ namespace OpenTK //Move += DispatchUpdateAndRenderFrame; //Resize += DispatchUpdateAndRenderFrame; - Debug.Print("Calibrating Stopwatch to account for drift"); - CalibrateStopwatch(); - Debug.Print("Stopwatch overhead: {0}", stopwatch_overhead); - Debug.Print("Entering main loop."); watch.Start(); while (true) @@ -431,97 +431,76 @@ namespace OpenTK } } - double stopwatch_overhead = 0; - void CalibrateStopwatch() - { - // Make sure everything is JITted - watch.Start(); - stopwatch_overhead = watch.Elapsed.TotalSeconds; - watch.Stop(); - watch.Reset(); - // Measure stopwatch overhead - const int count = 10; - for (int i = 0; i < count; i++) - { - watch.Start(); - double sample = watch.Elapsed.TotalSeconds; - if (sample < 0 || sample > 0.1) - { - // calculation failed, repeat - i--; - continue; - } - stopwatch_overhead += sample; - watch.Stop(); - watch.Reset(); - } - stopwatch_overhead /= 10; - } - - Stopwatch watch = new Stopwatch(); - double update_timestamp = 0; - double render_timestamp = 0; - double update_elapsed = 0; - double render_elapsed = 0; void DispatchUpdateAndRenderFrame(object sender, EventArgs e) { const int max_frameskip = 10; int frameskip = 0; - double timestamp = 0; + double timestamp = watch.Elapsed.TotalSeconds; do { // Raise UpdateFrame events until we catch up with our target update rate. - timestamp = watch.Elapsed.TotalSeconds; - update_elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); - update_timestamp = timestamp; - RaiseUpdateFrame(update_elapsed, ref next_update); - } while (next_update > 0 && ++frameskip < max_frameskip); - // Calculate statistics - //update_period = total_update_time / (double)num_updates; - - timestamp = watch.Elapsed.TotalSeconds; - render_elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); - render_timestamp = timestamp; - RaiseRenderFrame(render_elapsed, ref next_render); - } - - void RaiseUpdateFrame(double time, ref double next_update) - { - double time_left = next_render - time; - if (time_left <= 0.0 && time > 0) - { - update_args.Time = time; - OnUpdateFrameInternal(update_args); - - // Don't schedule a new update more than 1 second in the future. - // Sometimes the hardware cannot keep up with updates - // (e.g. when the update rate is too high, or the UpdateFrame processing - // is too costly). This cap ensures we can catch up in a reasonable time - // once the load becomes lighter. - next_update = time_left + TargetUpdatePeriod; - next_update = Math.Max(next_update, -1.0); - } - } - - void RaiseRenderFrame(double time, ref double next_render) - { - double time_left = next_render - time; - if (time_left <= 0.0 && time > 0) - { - if (time > 0) + double update_elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); + if (RaiseUpdateFrame(update_elapsed, ref next_update)) { - render_period = render_args.Time = time; - OnRenderFrameInternal(render_args); + update_timestamp = timestamp; } + timestamp = watch.Elapsed.TotalSeconds; + } while (next_update <= 0 && ++frameskip < max_frameskip); - // Schedule next render event. The 1 second cap ensures - // the process does not appear to hang. - next_render = time_left + TargetRenderPeriod; - next_update = Math.Max(next_update, -1.0); + double render_elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); + if (RaiseRenderFrame(render_elapsed, ref next_render)) + { + render_timestamp = timestamp; } } + bool RaiseUpdateFrame(double time, ref double next_update) + { + if (time > 0) + { + next_update -= time; + if (next_update <= 0.0) + { + update_args.Time = time; + OnUpdateFrameInternal(update_args); + + // Don't schedule a new update more than 1 second in the future. + // Sometimes the hardware cannot keep up with updates + // (e.g. when the update rate is too high, or the UpdateFrame processing + // is too costly). This cap ensures we can catch up in a reasonable time + // once the load becomes lighter. + next_update += TargetUpdatePeriod; + next_update = Math.Max(next_update, -1.0); + update_period = Math.Max(next_update, 0.0); + return true; + } + } + return false; + } + + + bool RaiseRenderFrame(double time, ref double next_render) + { + if (time > 0) + { + next_render -= time; + if (next_render <= 0.0) + { + render_args.Time = time; + OnRenderFrameInternal(render_args); + + // Schedule next render event. The 1 second cap ensures + // the process does not appear to hang. + next_render += TargetRenderPeriod; + next_render = Math.Max(next_render, -1.0); + render_period = Math.Max(next_render, 0.0); + return true; + } + } + return false; + } + #endregion #region SwapBuffers