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.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
/// <param name="sharedContext">An IGraphicsContext to share resources with.</param>
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)
{}
/// <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,
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
/// </summary>
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>
/// Override to add custom cleanup logic.
/// </summary>