#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 { IGraphicsContext glContext; bool isExiting = false; double render_period; // TODO: Implement these: double render_time; bool vsync; Stopwatch render_watch = new Stopwatch(); double next_render = 0.0; FrameEventArgs render_args = new FrameEventArgs(); /// 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, bool nullContext, GameWindowFlags options, DisplayDevice device) : base(width, height, title, options, mode, device) { try { glContext = nullContext ? new NullContext() : Factory.Default.CreateGLContext(mode, WindowInfo); glContext.MakeCurrent(WindowInfo); glContext.LoadAll(); VSync = true; } catch (Exception e) { Debug.Print(e.ToString()); base.Dispose(); throw; } } /// Disposes of the GameWindow, releasing all resources consumed by it. public override void Dispose() { try { Dispose(true); } finally { try { if (glContext != null) { glContext.Dispose(); glContext = null; } } finally { base.Dispose(); } } GC.SuppressFinalize(this); } /// Closes the GameWindow. Equivalent to method. /// 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(); } /// Makes the GraphicsContext current on the calling thread. public void MakeCurrent() { EnsureUndisposed(); Context.MakeCurrent(WindowInfo); } /// Called when the NativeWindow is about to close. /// /// The for this event. /// Set e.Cancel to true in order to stop the GameWindow from closing. protected override void OnClosing(object sender, System.ComponentModel.CancelEventArgs e) { base.OnClosing(sender, e); if (!e.Cancel) { isExiting = true; OnUnload(EventArgs.Empty); } } /// Called after an OpenGL context has been established, but before entering the main loop. /// Not used. protected virtual void OnLoad(EventArgs e) { if (Load != null) Load(this, e); } /// Called after GameWindow.Exit was called, but before destroying the OpenGL context. /// Not used. protected virtual void OnUnload(EventArgs e) { if (Unload != null) Unload(this, e); } /// Enters the game loop of the GameWindow updating and rendering at maximum 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 RenderFrame events. public void Run() { EnsureUndisposed(); try { Visible = true; // Make sure the GameWindow is visible. OnLoad(EventArgs.Empty); OnResize(null, EventArgs.Empty); Debug.Print("Entering main loop."); render_watch.Start(); while (true) { ProcessEvents(); if (Exists && !IsExiting) RaiseRenderFrame(render_watch, ref next_render, render_args); else return; } } finally { if (Exists) { // TODO: Should similar behaviour be retained, possibly on native window level? //while (this.Exists) // ProcessEvents(false); } } } void RaiseRenderFrame(Stopwatch render_watch, ref double next_render, FrameEventArgs render_args) { // Cap the maximum time drift to 1 second (e.g. when the process is suspended). double time = render_watch.Elapsed.TotalSeconds; if (time > 1.0) time = 1.0; if (time <= 0) return; double time_left = next_render - time; if (time_left <= 0.0) { // Schedule next render event. The 1 second cap ensures the process does not appear to hang. next_render = time_left; if (next_render < -1.0) 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; } } } /// Swaps the front and back buffer, presenting the rendered scene to the user. public void SwapBuffers() { EnsureUndisposed(); this.Context.SwapBuffers(); } /// Returns the opengl IGraphicsContext associated with the current GameWindow. public IGraphicsContext Context { get { EnsureUndisposed(); return glContext; } } /// /// 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; } } /// Gets a double representing the actual frequency of RenderFrame events, in hertz (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; } } /// Gets a double representing the period of RenderFrame events, in seconds. public double RenderPeriod { get { EnsureUndisposed(); return render_period; } } /// 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; } } /// Gets or sets the VSyncMode. public bool VSync { get { EnsureUndisposed(); return vsync; } set { EnsureUndisposed(); Context.VSync = (vsync = value); } } /// Gets or states the state of the NativeWindow. public override WindowState WindowState { get { return base.WindowState; } set { base.WindowState = value; Debug.Print("Updating Context after setting WindowState to {0}", value); if (Context != null) Context.Update(WindowInfo); } } /// 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; /// 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) { } /// Called when the frame is rendered. /// Contains information necessary for frame rendering. /// Subscribe to the event instead of overriding this method. protected virtual void OnRenderFrame(FrameEventArgs e) { if (RenderFrame != null) RenderFrame(this, e); } protected override void OnResize(object sender, EventArgs e) { base.OnResize(sender, e); glContext.Update(base.WindowInfo); } private void OnRenderFrameInternal(FrameEventArgs e) { if (Exists && !isExiting) OnRenderFrame(e); } } }