From 0f9552e39a677959d198fcc2ab1c2a806d2f150b Mon Sep 17 00:00:00 2001 From: Tzach Shabtay Date: Sat, 23 Dec 2017 01:58:07 -0500 Subject: [PATCH] Game Window- allow configuring separate update thread --- src/OpenTK/GameWindow.cs | 74 +++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/src/OpenTK/GameWindow.cs b/src/OpenTK/GameWindow.cs index 4a0a5818..c49fc82a 100644 --- a/src/OpenTK/GameWindow.cs +++ b/src/OpenTK/GameWindow.cs @@ -25,6 +25,7 @@ using System; using System.Diagnostics; +using System.Threading; using OpenTK.Graphics; using OpenTK.Platform; @@ -66,7 +67,11 @@ namespace OpenTK { private const double MaxFrequency = 500.0; // Frequency cap for Update/RenderFrame events - private readonly Stopwatch watch = new Stopwatch(); + private readonly Stopwatch watchRender = new Stopwatch(); + private readonly Stopwatch watchUpdate = new Stopwatch(); + private Thread updateThread; + + private readonly bool isSingleThreaded; private IGraphicsContext glContext; @@ -161,12 +166,30 @@ namespace OpenTK /// An IGraphicsContext to share resources with. public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext) + : this(width, height, mode, title, options, device, major, minor, flags, sharedContext, true) + {} + + /// Constructs a new GameWindow with the specified attributes. + /// The width of the GameWindow in pixels. + /// The height of the GameWindow in pixels. + /// The OpenTK.Graphics.GraphicsMode of the GameWindow. + /// The title of the GameWindow. + /// GameWindow options regarding window appearance and behavior. + /// The OpenTK.Graphics.DisplayDevice to construct the GameWindow in. + /// The major version for the OpenGL GraphicsContext. + /// The minor version for the OpenGL GraphicsContext. + /// The GraphicsContextFlags version for the OpenGL GraphicsContext. + /// An IGraphicsContext to share resources with. + /// Should the update and render frames be fired on the same thread? If false, render and update events will be fired from separate threads. + public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, + int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext, bool isSingleThreaded) : base(width, height, title, options, mode == null ? GraphicsMode.Default : mode, device == null ? DisplayDevice.Default : device) { try { + this.isSingleThreaded = isSingleThreaded; glContext = new GraphicsContext(mode == null ? GraphicsMode.Default : mode, WindowInfo, major, minor, flags); glContext.MakeCurrent(WindowInfo); (glContext as IGraphicsContextInternal).LoadAll(); @@ -334,13 +357,22 @@ namespace OpenTK //Resize += DispatchUpdateAndRenderFrame; Debug.Print("Entering main loop."); - watch.Start(); + if (!isSingleThreaded) + { + updateThread = new Thread(UpdateThread); + updateThread.Start(); + } + watchRender.Start(); while (true) { ProcessEvents(); if (Exists && !IsExiting) { - DispatchUpdateAndRenderFrame(this, EventArgs.Empty); + if (isSingleThreaded) + { + DispatchUpdateFrame(watchRender); + } + DispatchRenderFrame(); } else { @@ -350,9 +382,6 @@ namespace OpenTK } finally { - Move -= DispatchUpdateAndRenderFrame; - Resize -= DispatchUpdateAndRenderFrame; - if (Exists) { // TODO: Should similar behaviour be retained, possibly on native window level? @@ -362,21 +391,30 @@ namespace OpenTK } } + private void UpdateThread() + { + OnUpdateThreadStarted(this, new EventArgs()); + watchUpdate.Start(); + while (Exists && !IsExiting) + { + DispatchUpdateFrame(watchUpdate); + } + } + private double ClampElapsed(double elapsed) { return MathHelper.Clamp(elapsed, 0.0, 1.0); } - private void DispatchUpdateAndRenderFrame(object sender, EventArgs e) + private void DispatchUpdateFrame(Stopwatch watch) { int is_running_slowly_retries = 4; double timestamp = watch.Elapsed.TotalSeconds; - double elapsed = 0; + double elapsed = ClampElapsed(timestamp - update_timestamp); - elapsed = ClampElapsed(timestamp - update_timestamp); while (elapsed > 0 && elapsed + update_epsilon >= TargetUpdatePeriod) { - RaiseUpdateFrame(elapsed, ref timestamp); + RaiseUpdateFrame(watch, elapsed, ref timestamp); // Calculate difference (positive or negative) between // actual elapsed time and target elapsed time. We must @@ -403,15 +441,19 @@ namespace OpenTK break; } } + } - elapsed = ClampElapsed(timestamp - render_timestamp); + private void DispatchRenderFrame() + { + double timestamp = watchRender.Elapsed.TotalSeconds; + double elapsed = ClampElapsed(timestamp - render_timestamp); if (elapsed > 0 && elapsed >= TargetRenderPeriod) { RaiseRenderFrame(elapsed, ref timestamp); } } - private void RaiseUpdateFrame(double elapsed, ref double timestamp) + private void RaiseUpdateFrame(Stopwatch watch, double elapsed, ref double timestamp) { // Raise UpdateFrame event update_args.Time = elapsed; @@ -438,7 +480,7 @@ namespace OpenTK // Update RenderTime property render_timestamp = timestamp; - timestamp = watch.Elapsed.TotalSeconds; + timestamp = watchRender.Elapsed.TotalSeconds; render_time = timestamp - render_timestamp; } @@ -790,6 +832,12 @@ namespace OpenTK /// public event EventHandler UpdateFrame = delegate { }; + /// + /// If game window is configured to run with a dedicated update thread (by passing isSingleThreaded = false in the constructor), + /// occurs when the update thread has started. This would be a good place to initialize thread specific stuff (like setting a synchronization context). + /// + public event EventHandler OnUpdateThreadStarted = delegate { }; + /// /// Override to add custom cleanup logic. ///