Merge pull request #695 from tzachshabtay/develop

Game Window- allow configuring separate update thread
This commit is contained in:
Jarl Gullberg 2017-12-26 00:15:24 +01:00 committed by GitHub
commit 6ba91adfbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -25,6 +25,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Platform; using OpenTK.Platform;
@ -66,7 +67,11 @@ namespace OpenTK
{ {
private const double MaxFrequency = 500.0; // Frequency cap for Update/RenderFrame events 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; private IGraphicsContext glContext;
@ -161,12 +166,30 @@ namespace OpenTK
/// <param name="sharedContext">An IGraphicsContext to share resources with.</param> /// <param name="sharedContext">An IGraphicsContext to share resources with.</param>
public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device,
int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext) int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext)
: this(width, height, mode, title, options, device, major, minor, flags, sharedContext, true)
{}
/// <summary>Constructs a new GameWindow with the specified attributes.</summary>
/// <param name="width">The width of the GameWindow in pixels.</param>
/// <param name="height">The height of the GameWindow in pixels.</param>
/// <param name="mode">The OpenTK.Graphics.GraphicsMode of the GameWindow.</param>
/// <param name="title">The title of the GameWindow.</param>
/// <param name="options">GameWindow options regarding window appearance and behavior.</param>
/// <param name="device">The OpenTK.Graphics.DisplayDevice to construct the GameWindow in.</param>
/// <param name="major">The major version for the OpenGL GraphicsContext.</param>
/// <param name="minor">The minor version for the OpenGL GraphicsContext.</param>
/// <param name="flags">The GraphicsContextFlags version for the OpenGL GraphicsContext.</param>
/// <param name="sharedContext">An IGraphicsContext to share resources with.</param>
/// <param name="isSingleThreaded">Should the update and render frames be fired on the same thread? If false, render and update events will be fired from separate threads.</param>
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, : base(width, height, title, options,
mode == null ? GraphicsMode.Default : mode, mode == null ? GraphicsMode.Default : mode,
device == null ? DisplayDevice.Default : device) device == null ? DisplayDevice.Default : device)
{ {
try try
{ {
this.isSingleThreaded = isSingleThreaded;
glContext = new GraphicsContext(mode == null ? GraphicsMode.Default : mode, WindowInfo, major, minor, flags); glContext = new GraphicsContext(mode == null ? GraphicsMode.Default : mode, WindowInfo, major, minor, flags);
glContext.MakeCurrent(WindowInfo); glContext.MakeCurrent(WindowInfo);
(glContext as IGraphicsContextInternal).LoadAll(); (glContext as IGraphicsContextInternal).LoadAll();
@ -334,13 +357,22 @@ namespace OpenTK
//Resize += DispatchUpdateAndRenderFrame; //Resize += DispatchUpdateAndRenderFrame;
Debug.Print("Entering main loop."); Debug.Print("Entering main loop.");
watch.Start(); if (!isSingleThreaded)
{
updateThread = new Thread(UpdateThread);
updateThread.Start();
}
watchRender.Start();
while (true) while (true)
{ {
ProcessEvents(); ProcessEvents();
if (Exists && !IsExiting) if (Exists && !IsExiting)
{ {
DispatchUpdateAndRenderFrame(this, EventArgs.Empty); if (isSingleThreaded)
{
DispatchUpdateFrame(watchRender);
}
DispatchRenderFrame();
} }
else else
{ {
@ -350,9 +382,6 @@ namespace OpenTK
} }
finally finally
{ {
Move -= DispatchUpdateAndRenderFrame;
Resize -= DispatchUpdateAndRenderFrame;
if (Exists) if (Exists)
{ {
// TODO: Should similar behaviour be retained, possibly on native window level? // 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) private double ClampElapsed(double elapsed)
{ {
return MathHelper.Clamp(elapsed, 0.0, 1.0); 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; int is_running_slowly_retries = 4;
double timestamp = watch.Elapsed.TotalSeconds; 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) while (elapsed > 0 && elapsed + update_epsilon >= TargetUpdatePeriod)
{ {
RaiseUpdateFrame(elapsed, ref timestamp); RaiseUpdateFrame(watch, elapsed, ref timestamp);
// Calculate difference (positive or negative) between // Calculate difference (positive or negative) between
// actual elapsed time and target elapsed time. We must // actual elapsed time and target elapsed time. We must
@ -403,15 +441,19 @@ namespace OpenTK
break; 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) if (elapsed > 0 && elapsed >= TargetRenderPeriod)
{ {
RaiseRenderFrame(elapsed, ref timestamp); 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 // Raise UpdateFrame event
update_args.Time = elapsed; update_args.Time = elapsed;
@ -438,7 +480,7 @@ namespace OpenTK
// Update RenderTime property // Update RenderTime property
render_timestamp = timestamp; render_timestamp = timestamp;
timestamp = watch.Elapsed.TotalSeconds; timestamp = watchRender.Elapsed.TotalSeconds;
render_time = timestamp - render_timestamp; render_time = timestamp - render_timestamp;
} }
@ -790,6 +832,12 @@ namespace OpenTK
/// </summary> /// </summary>
public event EventHandler<FrameEventArgs> UpdateFrame = delegate { }; public event EventHandler<FrameEventArgs> UpdateFrame = delegate { };
/// <summary>
/// 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).
/// </summary>
public event EventHandler OnUpdateThreadStarted = delegate { };
/// <summary> /// <summary>
/// Override to add custom cleanup logic. /// Override to add custom cleanup logic.
/// </summary> /// </summary>