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.
///