Big update: added timing information. Improved update and render frequency limiting algorithms (they extremely accurate now!) Added VSync property and VSyncMode enum. Adaptive VSync not available yet.

This commit is contained in:
the_fiddler 2007-09-30 12:44:42 +00:00
parent e932f8e162
commit fa395a5325

View file

@ -23,6 +23,7 @@ namespace OpenTK
/// GameWindow contains several events you can hook or override to add your custom logic: /// GameWindow contains several events you can hook or override to add your custom logic:
/// <list> /// <list>
/// <item>OnLoad: Occurs after creating the OpenGL context, but before entering the main loop. Override to load resources.</item> /// <item>OnLoad: Occurs after creating the OpenGL context, but before entering the main loop. Override to load resources.</item>
/// <item>OnUnload: Occurs after exiting the main loop, but before deleting the OpenGL context. Override to unload resources.</item>
/// <item>OnResize: Occurs whenever GameWindow is resized. You should update the OpenGL Viewport and Projection Matrix here.</item> /// <item>OnResize: Occurs whenever GameWindow is resized. You should update the OpenGL Viewport and Projection Matrix here.</item>
/// <item>OnUpdateFrame: Occurs at the specified logic update rate. Override to add your game logic.</item> /// <item>OnUpdateFrame: Occurs at the specified logic update rate. Override to add your game logic.</item>
/// <item>OnRenderFrame: Occurs at the specified frame render rate. Override to add your rendering code.</item> /// <item>OnRenderFrame: Occurs at the specified frame render rate. Override to add your rendering code.</item>
@ -30,7 +31,7 @@ namespace OpenTK
/// Call the Run() method to start the application's main loop. Run(double, double) takes two parameters that /// Call the Run() method to start the application's main loop. Run(double, double) takes two parameters that
/// specify the logic update rate, and the render update rate. /// specify the logic update rate, and the render update rate.
/// </remarks> /// </remarks>
public class GameWindow : IGameWindow public class GameWindow : INativeGLWindow
{ {
#region --- Fields --- #region --- Fields ---
@ -42,13 +43,18 @@ namespace OpenTK
bool isExiting; bool isExiting;
bool disposed; bool disposed;
double updateTime, renderTime, eventTime, frameTime; double update_period, render_period;
double target_update_period, target_render_period, target_render_period_doubled;
// TODO: Implement these:
double update_time, render_time, event_time;
int width, height; int width, height;
VSyncMode vsync;
#endregion #endregion
#region --- Internal Fields --- #region --- Internal Properties ---
bool MustResize bool MustResize
{ {
@ -403,7 +409,7 @@ namespace OpenTK
#endregion #endregion
#region --- IGameWindow Members --- #region --- GameWindow Methods ---
#region void Run() #region void Run()
@ -426,6 +432,7 @@ namespace OpenTK
Run(updateFrequency, 0.0); Run(updateFrequency, 0.0);
} }
#if false
/// <summary> /// <summary>
/// Runs the default game loop on GameWindow at the specified update and render frequency. /// Runs the default game loop on GameWindow at the specified update and render frequency.
/// </summary> /// </summary>
@ -452,25 +459,30 @@ namespace OpenTK
/// </remarks> /// </remarks>
public virtual void Run(double updateFrequency, double renderFrequency) public virtual void Run(double updateFrequency, double renderFrequency)
{ {
this.OnLoad(EventArgs.Empty);
// Setup timer // Setup timer
Stopwatch watch = new Stopwatch(); Stopwatch watch = new Stopwatch();
UpdateFrameEventArgs updateArgs = new UpdateFrameEventArgs(); UpdateFrameEventArgs updateArgs = new UpdateFrameEventArgs();
RenderFrameEventArgs renderArgs = new RenderFrameEventArgs(); RenderFrameEventArgs renderArgs = new RenderFrameEventArgs();
// Setup update and render rates. If updateFrequency or renderFrequency <= 0.0, use full throttle for that frequency. // Setup update and render rates. If updateFrequency or renderFrequency <= 0.0, use full throttle for that frequency.
double update_target = 0.0, render_target = 0.0, next_update = 0.0, next_render = 0.0; double next_update = 0.0, next_render = 0.0;
double time, total_time; double start_time;
double update_watch = 0.0, render_watch = 0.0;
int num_updates = 1;
double t0 = 0.0, t1 = 0.0, t2 = 0.0, t3 = 0.0;
if (updateFrequency > 0.0) if (updateFrequency > 0.0)
{ {
next_update = update_target = 1.0 / updateFrequency; next_update = updateTimeTarget = 1.0 / updateFrequency;
} }
if (renderFrequency > 0.0) if (renderFrequency > 0.0)
{ {
next_render = render_target = 1.0 / renderFrequency; next_render = renderTimeTarget = 1.0 / renderFrequency;
} }
renderTargetDoubled = renderTimeTarget * 2.0;
this.OnLoad(EventArgs.Empty);
// Enter main loop: // Enter main loop:
// (1) Update total frame time (capped at 0.1 sec) // (1) Update total frame time (capped at 0.1 sec)
@ -484,52 +496,207 @@ namespace OpenTK
Debug.Print("Entering main loop."); Debug.Print("Entering main loop.");
while (this.Exists && !IsExiting) while (this.Exists && !IsExiting)
{ {
// Update total frame time.
total_time = frameTime = watch.Elapsed.TotalSeconds;
if (total_time > 0.1)
total_time = 0.1;
updateArgs.Time = renderArgs.Time = total_time;
watch.Reset(); watch.Reset();
watch.Start(); watch.Start();
// Process events and update event_time //frameTime = watch.Elapsed.TotalSeconds;
time = watch.Elapsed.TotalSeconds; /*
// Adaptive VSync control:
bool disable_vsync = VSync == VSyncMode.Adaptive && Context.VSync && renderTime > renderTargetDoubled;
bool enable_vsync = VSync == VSyncMode.Adaptive && !Context.VSync && renderTime <= renderTargetDoubled;
if (disable_vsync)
{
//Debug.Print("Disabled vsync");
Title = "Off";
Context.VSync = false;
}
else if (enable_vsync)
{
//Debug.Print("Enabled vsync");
Title = "On";
Context.VSync = true;
}
*/
t0 = watch.Elapsed.TotalSeconds;
// Process events and update eventTime
eventTime = t2 + t3 + t0; // t2 and t3 come from the previous run through the loop.
this.ProcessEvents(); this.ProcessEvents();
eventTime = watch.Elapsed.TotalSeconds - time;
if (!IsExiting) if (!IsExiting)
{ {
// Raise UpdateFrame event(s) and update update_time. // --- UpdateFrame ---
time = watch.Elapsed.TotalSeconds; // Raise the necessary amount of UpdateFrame events to keep
next_update -= (total_time + time); // the UpdateFrame rate constant. If the user didn't set an
while (next_update <= 0.0) // UpdateFrame rate, raise only one event.
{
updateArgs.Time += watch.Elapsed.TotalSeconds;
this.OnUpdateFrameInternal(updateArgs);
if (update_target == 0.0)
break;
next_update += update_target;
}
updateTime = watch.Elapsed.TotalSeconds - time;
t1 = watch.Elapsed.TotalSeconds - t0;
start_time = t3 + t0 + t1; // t3 come from the previous run through the loop.
update_watch += start_time;
if (num_updates > 0)
{
updateTime = update_watch / (double)num_updates;
num_updates = 0;
update_watch = 0.0;
}
next_update -= start_time;
updateArgs.Time = update_watch;
if (next_update <= 0.0)
{
//updateArgs.Time += watch.Elapsed.TotalSeconds;
double prev_update = watch.Elapsed.TotalSeconds;
this.OnUpdateFrameInternal(updateArgs);
updateArgs.Time = watch.Elapsed.TotalSeconds - prev_update;
++num_updates;
// Schedule next update
//if (updateTimeTarget != 0.0)
{
next_update += updateTimeTarget;
next_update -= (watch.Elapsed.TotalSeconds - start_time);
}
//else
// break; // User didn't request a fixed UpdateFrame rate.
}
// --------------------
t2 = watch.Elapsed.TotalSeconds - t1;
// --- Render Frame ---
// Raise RenderFrame event and update render_time. // Raise RenderFrame event and update render_time.
time = watch.Elapsed.TotalSeconds;
next_render -= (total_time + time); start_time = t0 + t1 + t2;
render_watch += start_time;
next_render -= start_time;
if (next_render <= 0.0) if (next_render <= 0.0)
{ {
renderArgs.Time += time; // Update framerate counters
renderTime = renderArgs.Time = render_watch;
render_watch = 0.0;
this.OnRenderFrameInternal(renderArgs); this.OnRenderFrameInternal(renderArgs);
next_render += render_target;
next_render += renderTimeTarget;
next_render -= (watch.Elapsed.TotalSeconds - start_time);
} }
renderTime = watch.Elapsed.TotalSeconds - time;
// --------------------
// If there is any CPU time left, and we are not running full-throttle, Sleep() to lower CPU usage. // If there is any CPU time left, and we are not running full-throttle, Sleep() to lower CPU usage.
if (renderTime < render_target && updateTime < update_target) /*
if (renderTime < renderTimeTarget && updateTime < updateTimeTarget)
{ {
Thread.Sleep((int)(1000.0 * System.Math.Min( int sleep_time = (int)System.Math.Truncate(1000.0 * System.Math.Min(renderTimeTarget - renderTime - eventTime,
render_target - renderTime, update_target - updateTime))); updateTimeTarget - updateTime - eventTime));
if (sleep_time < 0)
sleep_time = 0;
Thread.Sleep(sleep_time);
} }
*/
/*
loop_time_clock = watch.Elapsed.TotalSeconds;
if (loop_time_clock > 0.05)
loop_time_clock = 0.05;
render_time_clock += loop_time_clock;
update_time_clock += loop_time_clock;
*/
//if (loop_time_clock > 0.1)
// loop_time_clock = 0.1;
t3 = watch.Elapsed.TotalSeconds - t2;
}
}
OnUnloadInternal(EventArgs.Empty);
if (this.Exists)
{
glWindow.DestroyWindow();
while (this.Exists)
{
this.ProcessEvents();
}
}
}
#endif
public void Run(double updates_per_second, double frames_per_second)
{
if (updates_per_second < 0.0 || updates_per_second > 200.0)
throw new ArgumentOutOfRangeException("updates_per_second", updates_per_second, "Parameter should be inside the range [0.0, 200.0]");
if (frames_per_second < 0.0 || frames_per_second > 200.0)
throw new ArgumentOutOfRangeException("frames_per_second", frames_per_second, "Parameter should be inside the range [0.0, 200.0]");
TargetUpdateFrequency = updates_per_second;
TargetRenderFrequency = frames_per_second;
Stopwatch update_watch = new Stopwatch(), render_watch = new Stopwatch();
double time, next_render = 0.0, next_update = 0.0, update_time_counter = 0.0;
int num_updates = 0;
UpdateFrameEventArgs update_args = new UpdateFrameEventArgs();
RenderFrameEventArgs render_args = new RenderFrameEventArgs();
GC.Collect(2);
GC.WaitForPendingFinalizers();
GC.Collect(2);
OnLoadInternal(EventArgs.Empty);
while (!isExiting)
{
// Events
ProcessEvents();
if (isExiting)
break;
// Updates
time = update_watch.Elapsed.TotalSeconds;
if (time > 0.1)
time = 0.1;
while (next_update - time <= 0.0)
{
next_update = next_update - time + TargetUpdatePeriod;
update_time_counter += time;
++num_updates;
update_watch.Reset();
update_watch.Start();
update_args.Time = time;
OnUpdateFrameInternal(update_args);
if (TargetUpdateFrequency == 0.0)
break;
time = update_watch.Elapsed.TotalSeconds;
next_update -= time;
update_time_counter += time;
}
if (num_updates > 0)
{
update_period = update_time_counter / (double)num_updates;
num_updates = 0;
update_time_counter = 0.0;
}
// Frame
if (isExiting)
break;
time = render_watch.Elapsed.TotalSeconds;
if (time > 0.1)
time = 0.1;
if (next_render - time <= 0.0)
{
next_render = next_render - time + TargetRenderPeriod;
render_watch.Reset();
render_watch.Start();
render_period = render_args.Time = time;
render_args.ScaleFactor = RenderPeriod / UpdatePeriod;
OnRenderFrameInternal(render_args);
} }
} }
@ -562,8 +729,8 @@ namespace OpenTK
/// </remarks> /// </remarks>
public void ProcessEvents() public void ProcessEvents()
{ {
if (!isExiting) //if (!isExiting)
InputDriver.Poll(); // InputDriver.Poll();
glWindow.ProcessEvents(); glWindow.ProcessEvents();
} }
@ -614,7 +781,7 @@ namespace OpenTK
{ {
if (!this.Exists && !this.IsExiting) if (!this.Exists && !this.IsExiting)
{ {
Debug.Print("WARNING: UpdateFrame event raised, without a valid render window. This may indicate a programming error. Creating render window."); Debug.Print("WARNING: UpdateFrame event raised without a valid render window. This may indicate a programming error. Creating render window.");
mode = new DisplayMode(640, 480); mode = new DisplayMode(640, 480);
this.CreateWindow(mode); this.CreateWindow(mode);
} }
@ -721,20 +888,6 @@ namespace OpenTK
#endregion #endregion
#region public void SwapBuffers()
/// <summary>
/// Swaps the front and back buffer, presenting the rendered scene to the user.
/// Only useful in double- or triple-buffered formats.
/// </summary>
/// <remarks>Calling this function is equivalent to calling Context.SwapBuffers()</remarks>
public void SwapBuffers()
{
Context.SwapBuffers();
}
#endregion
#region public bool IsExiting #region public bool IsExiting
/// <summary> /// <summary>
@ -786,6 +939,241 @@ namespace OpenTK
#endregion #endregion
#region public VSyncMode VSync
/// <summary>
/// Gets or sets the VSyncMode.
/// </summary>
public VSyncMode VSync
{
get
{
return vsync;
}
set
{
if (value == VSyncMode.Off)
Context.VSync = false;
else if (value == VSyncMode.On)
Context.VSync = true;
vsync = value;
}
}
#endregion
#region public void SwapBuffers()
/// <summary>
/// Swaps the front and back buffer, presenting the rendered scene to the user.
/// Only useful in double- or triple-buffered formats.
/// </summary>
/// <remarks>Calling this function is equivalent to calling Context.SwapBuffers()</remarks>
public void SwapBuffers()
{
Context.SwapBuffers();
}
#endregion
#endregion
#region --- GameWindow Timing ---
#region public double TargetRenderPeriod
/// <summary>
/// Gets or sets the target render period in seconds.
/// </summary>
/// <para>A value of 0.0 indicates that RenderFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities).</para>
/// <para>Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0.</para>
/// </remarks>
public double TargetRenderPeriod
{
get
{
return target_render_period;
}
set
{
if (value <= 0.005)
{
target_render_period = target_render_period_doubled = 0.0;
}
else if (value <= 1.0)
{
target_render_period = value;
target_render_period_doubled = 2.0 * target_render_period;
}
else Debug.Print("Target render period clamped to 1.0 seconds.");
}
}
#endregion
#region public double TargetRenderFrequency
/// <summary>
/// Gets or sets the target render frequency in Herz.
/// </summary>
/// <remarks>
/// <para>A value of 0.0 indicates that RenderFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities).</para>
/// <para>Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz.</para>
/// </remarks>
public double TargetRenderFrequency
{
get
{
if (TargetRenderPeriod == 0.0)
return 0.0;
return 1.0 / TargetRenderPeriod;
}
set
{
if (value <= 1.0)
{
TargetRenderPeriod = 0.0;
}
else if (value <= 200.0)
{
TargetRenderPeriod = 1.0 / value;
}
else Debug.Print("Target render frequency clamped to 200.0Hz.");
}
}
#endregion
#region public double TargetUpdatePeriod
/// <summary>
/// Gets or sets the target update period in seconds.
/// </summary>
/// <remarks>
/// <para>A value of 0.0 indicates that UpdateFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities).</para>
/// <para>Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0.</para>
/// </remarks>
public double TargetUpdatePeriod
{
get
{
return target_update_period;
}
set
{
if (value <= 0.005)
{
target_update_period = 0.0;
}
else if (value <= 1.0)
{
target_update_period = value;
}
else Debug.Print("Target update period clamped to 1.0 seconds.");
}
}
#endregion
#region public double TargetUpdateFrequency
/// <summary>
/// Gets or sets the target update frequency in Herz.
/// </summary>
/// <remarks>
/// <para>A value of 0.0 indicates that UpdateFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities).</para>
/// <para>Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz.</para>
/// </remarks>
public double TargetUpdateFrequency
{
get
{
if (TargetUpdatePeriod == 0.0)
return 0.0;
return 1.0 / TargetUpdatePeriod;
}
set
{
if (value <= 1.0)
{
TargetUpdatePeriod = 0.0;
}
else if (value <= 200.0)
{
TargetUpdatePeriod = 1.0 / value;
}
else Debug.Print("Target update frequency clamped to 200.0Hz.");
}
}
#endregion
#region public double RenderFrequency
/// <summary>
/// Gets the actual frequency of RenderFrame events in Herz (i.e. FPS or Frames Per Second).
/// </summary>
public double RenderFrequency
{
get
{
if (render_period == 0.0)
return 1.0;
return 1.0 / render_period;
}
}
#endregion
#region public double RenderPeriod
/// <summary>
/// Gets the period of RenderFrame events in seconds.
/// </summary>
public double RenderPeriod
{
get
{
return render_period;
}
}
#endregion
#region public double UpdateFrequency
/// <summary>
/// Gets the frequency of UpdateFrame events in Herz.
/// </summary>
public double UpdateFrequency
{
get
{
if (update_period == 0.0)
return 1.0;
return 1.0 / update_period;
}
}
#endregion
#region public double UpdatePeriod
/// <summary>
/// Gets the period of UpdateFrame events in seconds.
/// </summary>
public double UpdatePeriod
{
get
{
return update_period;
}
}
#endregion
#endregion #endregion
#region --- IResizable Members --- #region --- IResizable Members ---
@ -963,6 +1351,36 @@ namespace OpenTK
#endregion #endregion
} }
#region public enum VSyncMode
/// <summary>
/// Indicates the available VSync modes.
/// </summary>
public enum VSyncMode
{
/// <summary>
/// Vsync disabled.
/// </summary>
Off = 0,
/// <summary>
/// VSync enabled.
/// </summary>
On,
/// <summary>
/// VSync enabled, but automatically disabled if framerate falls below a specified limit.
/// </summary>
Adaptive
}
#endregion
#region --- GameWindow Events ---
public delegate void UpdateFrameEvent(GameWindow sender, UpdateFrameEventArgs e);
public delegate void RenderFrameEvent(GameWindow sender, RenderFrameEventArgs e);
public delegate void LoadEvent(GameWindow sender, EventArgs e);
public delegate void UnloadEvent(GameWindow sender, EventArgs e);
public class UpdateFrameEventArgs : EventArgs public class UpdateFrameEventArgs : EventArgs
{ {
private double time; private double time;
@ -980,6 +1398,7 @@ namespace OpenTK
public class RenderFrameEventArgs : EventArgs public class RenderFrameEventArgs : EventArgs
{ {
private double time; private double time;
private double scale_factor;
/// <summary> /// <summary>
/// Gets the Time elapsed between frame updates, in seconds. /// Gets the Time elapsed between frame updates, in seconds.
@ -989,5 +1408,22 @@ namespace OpenTK
get { return time; } get { return time; }
internal set { time = value; } internal set { time = value; }
} }
public double ScaleFactor
{
get
{
return scale_factor;
} }
internal set
{
if (value != 0.0 && !Double.IsNaN(value))
scale_factor = value;
else
scale_factor = 1.0;
}
}
}
#endregion
} }