#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using OpenTK.Graphics;
using OpenTK.Input;
using OpenTK.Platform;
namespace OpenTK
{
///
/// The GameWindow class contains cross-platform methods to create and render on an OpenGL
/// window, handle input and load resources.
///
///
/// GameWindow contains several events you can hook or override to add your custom logic:
///
/// -
/// OnLoad: Occurs after creating the OpenGL context, but before entering the main loop.
/// Override to load resources.
///
/// -
/// OnUnload: Occurs after exiting the main loop, but before deleting the OpenGL context.
/// Override to unload resources.
///
/// -
/// OnResize: Occurs whenever GameWindow is resized. You should update the OpenGL Viewport
/// and Projection Matrix here.
///
/// -
/// OnUpdateFrame: Occurs at the specified logic update rate. Override to add your game
/// logic.
///
/// -
/// OnRenderFrame: Occurs at the specified frame render rate. Override to add your
/// rendering code.
///
///
/// 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.
///
public class GameWindow : NativeWindow, IGameWindow, IDisposable
{
#region --- Fields ---
//DisplayMode mode; // TODO: Removable?
object exit_lock = new object();
IGraphicsContext glContext;
bool hasMainLoop;
bool isExiting = false;
int main_loop_thread_id;
double update_period, render_period;
double target_update_period, target_render_period;
// TODO: Implement these:
double update_time, render_time;//, event_time;
//bool allow_sleep = true; // If true, GameWindow will call Timer.Sleep() if there is enough time.
VSyncMode vsync;
//InputDriver input_driver;
#endregion
#region --- Contructors ---
#region public GameWindow()
/// Constructs a new GameWindow with sensible default attributes.
public GameWindow()
: this(640, 480, GraphicsMode.Default, "OpenTK Game Window", 0, DisplayDevice.Default) { }
#endregion
#region public GameWindow(int width, int height)
/// Constructs a new GameWindow with the specified attributes.
/// The width of the GameWindow in pixels.
/// The height of the GameWindow in pixels.
public GameWindow(int width, int height)
: this(width, height, GraphicsMode.Default, "OpenTK Game Window", 0, DisplayDevice.Default) { }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode)
: this(width, height, mode, "OpenTK Game Window", 0, DisplayDevice.Default) { }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode, string title)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode, string title)
: this(width, height, mode, title, 0, DisplayDevice.Default) { }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options)
: this(width, height, mode, title, options, DisplayDevice.Default) { }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device)
: this(width, height, mode, title, options, device, 1, 0, GraphicsContextFlags.Default)
{ }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, GraphicsContextFlags flags)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device,
int major, int minor, GraphicsContextFlags flags)
: this(width, height, mode, title, options, device, major, minor, flags, null)
{ }
#endregion
#region public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device, int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext)
/// 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.
public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device,
int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext)
: base(width, height, title, options,
mode == null ? GraphicsMode.Default : mode,
device == null ? DisplayDevice.Default : device)
{
try
{
glContext = new GraphicsContext(mode == null ? GraphicsMode.Default : mode, WindowInfo, major, minor, flags);
glContext.MakeCurrent(WindowInfo);
(glContext as IGraphicsContextInternal).LoadAll();
VSync = VSyncMode.On;
//glWindow.WindowInfoChanged += delegate(object sender, EventArgs e) { OnWindowInfoChangedInternal(e); };
EnableEvents();
}
catch (Exception e)
{
Debug.Print(e.ToString());
base.Dispose();
throw;
}
}
#endregion
#endregion
#region --- Public Members ---
#region Methods
#region Dispose
///
/// Disposes of the GameWindow, releasing all resources consumed by it.
///
public new void Dispose()
{
try
{
Dispose(true);
}
finally
{
if (!IsDisposed())
{
if (glContext != null)
{
glContext.Dispose();
glContext = null;
}
base.Dispose();
}
}
GC.SuppressFinalize(this);
}
#endregion
#region Exit
///
/// Gracefully exits the GameWindow. May be called from any thread.
///
///
/// Override if you are not using .
/// If you override this method, place a call to base.Exit(), to ensure proper OpenTK shutdown.
///
public virtual void Exit()
{
Close();
}
#endregion
#region MakeCurrent
///
/// Makes the GraphicsContext current on the calling thread.
///
public void MakeCurrent()
{
EnsureUndisposed();
Context.MakeCurrent(WindowInfo);
}
#endregion
#region OnLoad
///
/// Occurs after establishing an OpenGL context, but before entering the main loop.
/// Override to load resources that should be maintained for the lifetime of the application.
///
/// Not used.
public virtual void OnLoad(EventArgs e)
{
EnsureUndisposed(); //if (disposed) throw new ObjectDisposedException("GameWindow"); // What is the exact purpose?
}
#endregion
#region OnUnload
///
/// Occurs after after calling GameWindow.Exit, but before destroying the OpenGL context.
/// Override to unload application resources.
///
/// Not used.
public virtual void OnUnload(EventArgs e)
{
EnsureUndisposed(); //if (disposed) throw new ObjectDisposedException("GameWindow"); // What is the exact purpose?
}
#endregion
#region public void Run()
///
/// Enters the game loop of the GameWindow using the maximum update rate.
///
///
public void Run()
{
Run(0.0, 0.0);
}
#endregion
#region public void Run(double updateFrequency)
///
/// Enters the game loop of the GameWindow using the specified update rate.
/// maximum possible render frequency.
///
public void Run(double updateRate)
{
Run(updateRate, 0.0);
}
#endregion
#region public void Run(double updates_per_second, double frames_per_second)
///
/// Enters the game loop of the GameWindow updating and rendering at the specified frequency.
///
///
/// When overriding the default game loop you should call ProcessEvents()
/// to ensure that your GameWindow responds to operating system events.
///
/// Once ProcessEvents() returns, it is time to call update and render the next frame.
///
///
/// The frequency of UpdateFrame events.
/// The frequency of RenderFrame events.
public void Run(double updates_per_second, double frames_per_second)
{
EnsureUndisposed();
try
{
// Necessary to be here, otherwise Exit() wouldn't work correctly when called inside OnLoad().
hasMainLoop = true;
main_loop_thread_id = Thread.CurrentThread.ManagedThreadId;
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;
FrameEventArgs update_args = new FrameEventArgs();
FrameEventArgs render_args = new FrameEventArgs();
update_watch.Reset();
render_watch.Reset();
OnLoadInternal(EventArgs.Empty);
Debug.Print("Entering main loop.");
while (!IsExiting && Exists && HasMainLoop)
{
ProcessEvents();
// Raise UpdateFrame events
time = update_watch.Elapsed.TotalSeconds;
if (time > 1.0)
time = 1.0;
while (next_update - time <= 0.0)
{
next_update = next_update - time + TargetUpdatePeriod;
if (next_update < -1.0) // Cap the maximum time drift, to avoid lengthy catch-up games.
next_update = -1.0;
update_time_counter += time;
++num_updates;
update_watch.Reset();
update_watch.Start();
if (time > 0)
{
update_args.Time = time;
OnUpdateFrameInternal(update_args);
update_time = update_watch.Elapsed.TotalSeconds;
}
if (TargetUpdateFrequency == 0.0)
break;
time = update_watch.Elapsed.TotalSeconds;
next_update -= time;
update_time_counter += time;
// Allow up to 10 frames to be dropped. Prevents the application from hanging
// with very high update frequencies.
if (num_updates >= 10)
break;
}
if (num_updates > 0)
{
update_period = update_time_counter / (double)num_updates;
num_updates = 0;
update_time_counter = 0.0;
}
// Raise RenderFrame event
time = render_watch.Elapsed.TotalSeconds;
if (time > 1.0)
time = 1.0;
double time_left = next_render - time;
if (VSync == VSyncMode.Adaptive)
{
// Check if we have enough time for a vsync
if (TargetRenderPeriod != 0 && RenderTime > 2.0 * TargetRenderPeriod)
Context.VSync = false;
else
Context.VSync = true;
}
if (time_left <= 0.0)
{
next_render = time_left + TargetRenderPeriod;
if (next_render < -1.0) // Cap the maximum time drift, to avoid lengthy catch-up games.
next_render = -1.0;
render_watch.Reset();
render_watch.Start();
if (time > 0)
{
render_period = render_args.Time = time;
OnRenderFrameInternal(render_args);
render_time = render_watch.Elapsed.TotalSeconds;
}
}
}
}
finally
{
Debug.Print("Restoring priority.");
Thread.CurrentThread.Priority = ThreadPriority.Normal;
OnUnloadInternal(EventArgs.Empty);
if (Exists)
{
Dispose();
//while (this.Exists) ProcessEvents(); // TODO: Should similar behaviour be retained, possibly on native window level?
}
}
}
#endregion
#region SwapBuffers
///
/// Swaps the front and back buffer, presenting the rendered scene to the user.
///
public void SwapBuffers()
{
EnsureUndisposed();
this.Context.SwapBuffers();
}
#endregion
#endregion
#region Properties
#region Context
///
/// Returns the opengl IGraphicsContext associated with the current GameWindow.
///
public IGraphicsContext Context
{
get
{
EnsureUndisposed();
return glContext;
}
}
#endregion
#region IsExiting
///
/// Gets a value indicating whether the shutdown sequence has been initiated
/// for this window, by calling GameWindow.Exit() or hitting the 'close' button.
/// If this property is true, it is no longer safe to use any OpenTK.Input or
/// OpenTK.Graphics.OpenGL functions or properties.
///
public bool IsExiting
{
get
{
EnsureUndisposed();
return isExiting;
}
}
#endregion
#region Joysticks
///
/// Gets a readonly IList containing all available OpenTK.Input.JoystickDevices.
///
[Obsolete]
public IList Joysticks
{
get { return InputDriver.Joysticks; }
}
#endregion
#region Keyboard
///
/// Gets the primary Keyboard device, or null if no Keyboard exists.
///
[Obsolete]
public KeyboardDevice Keyboard
{
get { return InputDriver.Keyboard.Count > 0 ? InputDriver.Keyboard[0] : null; }
}
#endregion
#region Mouse
///
/// Gets the primary Mouse device, or null if no Mouse exists.
///
[Obsolete]
public MouseDevice Mouse
{
get { return InputDriver.Mouse.Count > 0 ? InputDriver.Mouse[0] : null; }
}
#endregion
#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 RenderFrequency
///
/// Gets a double representing the actual frequency of RenderFrame events, in Herz (i.e. FPS or Frames Per Second).
///
public double RenderFrequency
{
get
{
EnsureUndisposed();
if (render_period == 0.0)
return 1.0;
return 1.0 / render_period;
}
}
#endregion
#region RenderPeriod
///
/// Gets a double representing the period of RenderFrame events, in seconds.
///
public double RenderPeriod
{
get
{
EnsureUndisposed();
return render_period;
}
}
#endregion
#region RenderTime
///
/// Gets a double representing the time spent in the RenderFrame function, in seconds.
///
public double RenderTime
{
get
{
EnsureUndisposed();
return render_time;
}
protected set
{
EnsureUndisposed();
render_time = value;
}
}
#endregion
#region TargetRenderFrequency
///
/// Gets or sets a double representing the target render frequency, in Herz.
///
///
/// 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).
/// Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz.
///
public double TargetRenderFrequency
{
get
{
EnsureUndisposed();
if (TargetRenderPeriod == 0.0)
return 0.0;
return 1.0 / TargetRenderPeriod;
}
set
{
EnsureUndisposed();
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."); // TODO: Where is it actually performed?
}
}
#endregion
#region TargetRenderPeriod
///
/// Gets or sets a double representing the target render period, in seconds.
///
///
/// 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).
/// Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0.
///
public double TargetRenderPeriod
{
get
{
EnsureUndisposed();
return target_render_period;
}
set
{
EnsureUndisposed();
if (value <= 0.005)
{
target_render_period = 0.0;
}
else if (value <= 1.0)
{
target_render_period = value;
}
else Debug.Print("Target render period clamped to 1.0 seconds.");
}
}
#endregion
#region TargetUpdateFrequency
///
/// Gets or sets a double representing the target update frequency, in Herz.
///
///
/// 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).
/// Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz.
///
public double TargetUpdateFrequency
{
get
{
EnsureUndisposed();
if (TargetUpdatePeriod == 0.0)
return 0.0;
return 1.0 / TargetUpdatePeriod;
}
set
{
EnsureUndisposed();
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."); // TODO: Where is it actually performed?
}
}
#endregion
#region TargetUpdatePeriod
///
/// Gets or sets a double representing the target update period, in seconds.
///
///
/// 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).
/// Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0.
///
public double TargetUpdatePeriod
{
get
{
EnsureUndisposed();
return target_update_period;
}
set
{
EnsureUndisposed();
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."); // TODO: Where is it actually performed?
}
}
#endregion
#region UpdateFrequency
///
/// Gets a double representing the frequency of UpdateFrame events, in Herz.
///
public double UpdateFrequency
{
get
{
EnsureUndisposed();
if (update_period == 0.0)
return 1.0;
return 1.0 / update_period;
}
}
#endregion
#region UpdatePeriod
///
/// Gets a double representing the period of UpdateFrame events, in seconds.
///
public double UpdatePeriod
{
get
{
EnsureUndisposed();
return update_period;
}
}
#endregion
#region UpdateTime
///
/// Gets a double representing the time spent in the UpdateFrame function, in seconds.
///
public double UpdateTime
{
get
{
EnsureUndisposed();
return update_time;
}
}
#endregion
#endregion
#region VSync
///
/// Gets or sets the VSyncMode.
///
public VSyncMode VSync
{
get
{
EnsureUndisposed();
GraphicsContext.Assert();
return vsync;
}
set
{
EnsureUndisposed();
GraphicsContext.Assert();
Context.VSync = (vsync = value) != VSyncMode.Off;
}
}
#endregion
#endregion
#region Events
///
/// Occurs before the window is displayed for the first time.
///
public event EventHandler Load;
///
/// Occurs when it is time to render a frame.
///
public event EventHandler RenderFrame;
///
/// Occurs before the window is destroyed.
///
public event EventHandler Unload;
///
/// Occurs when it is time to update a frame.
///
public event EventHandler UpdateFrame;
#endregion
#endregion
#region --- Protected Members ---
#region Dispose
///
/// Override to add custom cleanup logic.
///
/// True, if this method was called by the application; false if this was called by the finalizer thread.
protected virtual void Dispose(bool manual) { }
#endregion
#region OnRenderFrame
///
/// Override in derived classes to render a frame.
///
/// Contains information necessary for frame rendering.
///
/// The base implementation (base.OnRenderFrame) is empty, there is no need to call it.
///
protected virtual void OnRenderFrame(FrameEventArgs e) { }
#endregion
#region OnUpdateFrame
///
/// Override in derived classes to update a frame.
///
/// Contains information necessary for frame updating.
///
/// The base implementation (base.OnUpdateFrame) is empty, there is no need to call it.
///
protected virtual void OnUpdateFrame(FrameEventArgs e) { }
#endregion
#region OnWindowInfoChanged
///
/// Called when the WindowInfo for this GameWindow has changed.
///
/// Not used.
protected virtual void OnWindowInfoChanged(EventArgs e) { }
#endregion
#endregion
#region --- Assembly Members ---
#region Methods
#region ExitAsync
// Gracefully exits the GameWindow. May be called from any thread.
void ExitAsync()
{
HasMainLoop = false;
isExiting = true;
//UpdateFrame += delegate
//{
// ExitInternal();
//};
}
#endregion
#endregion
#region Properties
#region HasMainLoop
bool HasMainLoop
{
get { return hasMainLoop; }
set { hasMainLoop = value; }
}
#endregion
#endregion
#endregion
#region --- Private Members ---
#region OnLoadInternal
///
/// Raises the Load event, and calls the user's OnLoad override.
///
/// The event data.
private void OnLoadInternal(EventArgs e)
{
OnResize(EventArgs.Empty);
if (Load != null) Load(this, e);
OnLoad(e);
}
#endregion
#region OnRenderFrameInternal
private void OnRenderFrameInternal(FrameEventArgs e)
{
EnsureUndisposed();
if (!this.Exists || this.IsExiting) return; // TODO: Redundant because of EnsureUndisposed.
if (RenderFrame != null) RenderFrame(this, e);
OnRenderFrame(e);
}
#endregion
#region OnUnloadInternal
///
/// Raises the Unload event, and calls the user's OnUnload override.
///
/// The event data.
private void OnUnloadInternal(EventArgs e)
{
if (Unload != null) Unload(this, e);
OnUnload(e);
}
#endregion
#region OnUpdateFrameInternal
private void OnUpdateFrameInternal(FrameEventArgs e)
{
EnsureUndisposed();
if (!this.Exists || this.IsExiting) return; // TODO: Redundant because of EnsureUndisposed.
if (UpdateFrame != null) UpdateFrame(this, e);
OnUpdateFrame(e);
}
#endregion
#region OnWindowInfoChangedInternal
private void OnWindowInfoChangedInternal(EventArgs e)
{
glContext.MakeCurrent(WindowInfo);
OnWindowInfoChanged(e);
}
#endregion
#endregion
///// Finalizes unmanaged resources consumed by the GameWindow.
//~GameWindow()
//{
// Dispose(false);
// DisposeInternal(false);
//}
}
#region public enum VSyncMode
///
/// Enumerates available VSync modes.
///
public enum VSyncMode
{
///
/// Vsync disabled.
///
Off = 0,
///
/// VSync enabled.
///
On,
///
/// VSync enabled, but automatically disabled if framerate falls below a specified limit.
///
Adaptive,
}
#endregion
}