Major update! Updated timing routines. Added documentation. Added VSync property.

This commit is contained in:
the_fiddler 2007-10-15 11:12:56 +00:00
parent f647ae14d8
commit f0e5cbb8aa

View file

@ -47,9 +47,8 @@ namespace OpenTK
double target_update_period, target_render_period, target_render_period_doubled; double target_update_period, target_render_period, target_render_period_doubled;
// TODO: Implement these: // TODO: Implement these:
double update_time, render_time, event_time; double update_time, render_time, event_time;
//bool allow_sleep = true; // If true, GameWindow will call Timer.Sleep() if there is enough time.
int width, height; int width, height;
VSyncMode vsync; VSyncMode vsync;
#endregion #endregion
@ -208,8 +207,8 @@ namespace OpenTK
/// </summary> /// </summary>
public bool Fullscreen public bool Fullscreen
{ {
get { return glWindow.Fullscreen; } get { return false;/* return glWindow.Fullscreen; */ }
set { glWindow.Fullscreen = value; } set { /* glWindow.Fullscreen = value; */}
} }
#endregion #endregion
@ -414,16 +413,16 @@ namespace OpenTK
#region void Run() #region void Run()
/// <summary> /// <summary>
/// Enters the game loop of GameWindow, updating and rendering at the maximum possible frequency. /// Enters the game loop of the GameWindow updating and rendering at the maximum possible frequency.
/// </summary> /// </summary>
/// <see cref="public virtual void Run(float update_frequency, float render_frequency)"/> /// <see cref="public virtual void Run(double update_frequency, double render_frequency)"/>
public void Run() public void Run()
{ {
Run(0.0, 0.0); Run(0.0, 0.0);
} }
/// <summary> /// <summary>
/// Runs the default game loop on GameWindow at the specified update frequency, maintaining the /// Enters the game loop of the GameWindow updating the specified update frequency, while maintaining the
/// maximum possible render frequency. /// maximum possible render frequency.
/// </summary> /// </summary>
/// <see cref="public virtual void Run(double updateFrequency, double renderFrequency)"/> /// <see cref="public virtual void Run(double updateFrequency, double renderFrequency)"/>
@ -432,194 +431,11 @@ 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. /// Enters the game loop of the GameWindow updating and rendering at the specified frequency.
/// </summary> /// </summary>
/// <param name="updateFrequency">If greater than zero, indicates how many times UpdateFrame will be called per second. If less than or equal to zero, UpdateFrame is raised at maximum possible frequency.</param> /// <param name="updates_per_second">The frequency of UpdateFrame events.</param>
/// <param name="renderFrequency">If greater than zero, indicates how many times RenderFrame will be called per second. If less than or equal to zero, RenderFrame is raised at maximum possible frequency.</param> /// <param name="frames_per_second">The frequency of RenderFrame events.</param>
/// <remarks>
/// <para>
/// A default game loop consists of three parts: Event processing, frame updating and a frame rendering.
/// This function will try to maintain the requested updateFrequency at all costs, dropping the renderFrequency if
/// there is not enough CPU time.
/// </para>
/// <para>
/// It is recommended that you specify a target for update- and renderFrequency.
/// Doing so, will yield unused CPU time to other processes, dropping power consumption
/// and maximizing batter life. If either frequency is left unspecified, the GameWindow
/// will consume all available CPU time (only useful for benchmarks and stress tests).
/// </para>
/// <para>
/// Override this function if you want to change the behaviour of the
/// default game loop. If you override this function, you must place
/// a call to the ProcessEvents function, to ensure window will respond
/// to Operating System events.
/// </para>
/// </remarks>
public virtual void Run(double updateFrequency, double renderFrequency)
{
// Setup timer
Stopwatch watch = new Stopwatch();
UpdateFrameEventArgs updateArgs = new UpdateFrameEventArgs();
RenderFrameEventArgs renderArgs = new RenderFrameEventArgs();
// Setup update and render rates. If updateFrequency or renderFrequency <= 0.0, use full throttle for that frequency.
double next_update = 0.0, next_render = 0.0;
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)
{
next_update = updateTimeTarget = 1.0 / updateFrequency;
}
if (renderFrequency > 0.0)
{
next_render = renderTimeTarget = 1.0 / renderFrequency;
}
renderTargetDoubled = renderTimeTarget * 2.0;
this.OnLoad(EventArgs.Empty);
// Enter main loop:
// (1) Update total frame time (capped at 0.1 sec)
// (2) Process events and update event_time
// (3) Raise UpdateFrame event(s) and update update_time.
// If there is enough CPU time, update and render events will be 1 on 1.
// If there is not enough time, render events will be dropped in order to match the requested updateFrequency.
// If the requested updateFrequency can't be matched, processing will slow down.
// (4) Raise RenderFrame event and update render_time.
// (5) If there is any CPU time left, and we are not running full-throttle, Sleep() to lower CPU usage.
Debug.Print("Entering main loop.");
while (this.Exists && !IsExiting)
{
watch.Reset();
watch.Start();
//frameTime = 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();
if (!IsExiting)
{
// --- UpdateFrame ---
// Raise the necessary amount of UpdateFrame events to keep
// the UpdateFrame rate constant. If the user didn't set an
// UpdateFrame rate, raise only one event.
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.
start_time = t0 + t1 + t2;
render_watch += start_time;
next_render -= start_time;
if (next_render <= 0.0)
{
// Update framerate counters
renderTime = renderArgs.Time = render_watch;
render_watch = 0.0;
this.OnRenderFrameInternal(renderArgs);
next_render += renderTimeTarget;
next_render -= (watch.Elapsed.TotalSeconds - start_time);
}
// --------------------
// If there is any CPU time left, and we are not running full-throttle, Sleep() to lower CPU usage.
/*
if (renderTime < renderTimeTarget && updateTime < updateTimeTarget)
{
int sleep_time = (int)System.Math.Truncate(1000.0 * System.Math.Min(renderTimeTarget - renderTime - eventTime,
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) public void Run(double updates_per_second, double frames_per_second)
{ {
if (updates_per_second < 0.0 || updates_per_second > 200.0) if (updates_per_second < 0.0 || updates_per_second > 200.0)
@ -636,24 +452,38 @@ namespace OpenTK
UpdateFrameEventArgs update_args = new UpdateFrameEventArgs(); UpdateFrameEventArgs update_args = new UpdateFrameEventArgs();
RenderFrameEventArgs render_args = new RenderFrameEventArgs(); RenderFrameEventArgs render_args = new RenderFrameEventArgs();
GC.Collect(2); double sleep_granularity; // In seconds.
GC.WaitForPendingFinalizers();
GC.Collect(2); //GC.Collect(2);
//GC.WaitForPendingFinalizers();
//GC.Collect(2);
// Find the minimum granularity of the Thread.Sleep() function.
// TODO: Disabled - see comment on Thread.Sleep() problems below.
//update_watch.Start();
//const int test_times = 5;
//for (int i = test_times; --i > 0; )
// Thread.Sleep(1);
//update_watch.Stop();
//sleep_granularity = System.Math.Round(1000.0 * update_watch.Elapsed.TotalSeconds / test_times, MidpointRounding.AwayFromZero) / 1000.0;
//update_watch.Reset(); // We don't want to affect the first UpdateFrame!
OnLoadInternal(EventArgs.Empty); OnLoadInternal(EventArgs.Empty);
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
while (!isExiting) while (!isExiting)
{ {
// Events // Process events
ProcessEvents(); ProcessEvents();
if (isExiting) if (isExiting)
break; break;
// Updates // Raise UpdateFrame events
time = update_watch.Elapsed.TotalSeconds; time = update_watch.Elapsed.TotalSeconds;
if (time > 0.1) if (time > 1.0)
time = 0.1; time = 1.0;
while (next_update - time <= 0.0) while (next_update - time <= 0.0)
{ {
next_update = next_update - time + TargetUpdatePeriod; next_update = next_update - time + TargetUpdatePeriod;
@ -673,6 +503,11 @@ namespace OpenTK
time = update_watch.Elapsed.TotalSeconds; time = update_watch.Elapsed.TotalSeconds;
next_update -= time; next_update -= time;
update_time_counter += time; update_time_counter += time;
// Allow up to 10 frames to be dropped.
// Prevent the application from hanging with very high update frequencies.
if (num_updates >= 10)
break;
} }
if (num_updates > 0) if (num_updates > 0)
{ {
@ -681,13 +516,13 @@ namespace OpenTK
update_time_counter = 0.0; update_time_counter = 0.0;
} }
// Frame // Raise RenderFrame events
if (isExiting) if (isExiting)
break; break;
time = render_watch.Elapsed.TotalSeconds; time = render_watch.Elapsed.TotalSeconds;
if (time > 0.1) if (time > 1.0)
time = 0.1; time = 1.0;
if (next_render - time <= 0.0) if (next_render - time <= 0.0)
{ {
next_render = next_render - time + TargetRenderPeriod; next_render = next_render - time + TargetRenderPeriod;
@ -698,8 +533,18 @@ namespace OpenTK
render_args.ScaleFactor = RenderPeriod / UpdatePeriod; render_args.ScaleFactor = RenderPeriod / UpdatePeriod;
OnRenderFrameInternal(render_args); OnRenderFrameInternal(render_args);
} }
// Yield CPU time, if the Thread.Sleep() granularity allows it.
// TODO: Disabled because it does not work reliably enough on all systems.
// Enable vsync as a workaround.
//if (AllowSleep && next_render > sleep_granularity && next_update > sleep_granularity)
//{
// Thread.Sleep((int)(1000.0 * System.Math.Min(next_render - sleep_granularity, next_update - sleep_granularity)));
//}
} }
Thread.CurrentThread.Priority = ThreadPriority.Normal;
OnUnloadInternal(EventArgs.Empty); OnUnloadInternal(EventArgs.Empty);
if (this.Exists) if (this.Exists)
@ -981,6 +826,17 @@ namespace OpenTK
#region --- GameWindow Timing --- #region --- GameWindow Timing ---
// TODO: Disabled because it is not reliable enough. Use vsync as a workaround.
//#region public bool AllowSleep
//public bool AllowSleep
//{
// get { return allow_sleep; }
// set { allow_sleep = value; }
//}
//#endregion
#region public double TargetRenderPeriod #region public double TargetRenderPeriod
@ -1032,7 +888,7 @@ namespace OpenTK
} }
set set
{ {
if (value <= 1.0) if (value < 1.0)
{ {
TargetRenderPeriod = 0.0; TargetRenderPeriod = 0.0;
} }
@ -1096,7 +952,7 @@ namespace OpenTK
} }
set set
{ {
if (value <= 1.0) if (value < 1.0)
{ {
TargetUpdatePeriod = 0.0; TargetUpdatePeriod = 0.0;
} }
@ -1354,7 +1210,7 @@ namespace OpenTK
#region public enum VSyncMode #region public enum VSyncMode
/// <summary> /// <summary>
/// Indicates the available VSync modes. /// Enumerates the available VSync modes.
/// </summary> /// </summary>
public enum VSyncMode public enum VSyncMode
{ {
@ -1369,7 +1225,7 @@ namespace OpenTK
/// <summary> /// <summary>
/// VSync enabled, but automatically disabled if framerate falls below a specified limit. /// VSync enabled, but automatically disabled if framerate falls below a specified limit.
/// </summary> /// </summary>
Adaptive Adaptive,
} }
#endregion #endregion
@ -1426,4 +1282,12 @@ namespace OpenTK
} }
#endregion #endregion
#region --- GameWindow Exceptions ---
public class GameWindowExitException : ApplicationException
{
}
#endregion
} }