#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2009 the Open Toolkit library, except where noted. // // 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.Data; using System.Text; using System.Windows.Forms; using OpenTK.Platform; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace OpenTK { /// /// OpenGL-aware WinForms control. /// The WinForms designer will always call the default constructor. /// Inherit from this class and call one of its specialized constructors /// to enable antialiasing or custom s. /// public partial class GLControl : UserControl { IGraphicsContext context; IGLControl implementation; GraphicsMode format; int major, minor; GraphicsContextFlags flags; bool? initial_vsync_value; // Indicates that OnResize was called before OnHandleCreated. // To avoid issues with missing OpenGL contexts, we suppress // the premature Resize event and raise it as soon as the handle // is ready. bool resize_event_suppressed; // Indicates whether the control is in design mode. Due to issues // wiith the DesignMode property and nested controls,we need to // evaluate this in the constructor. readonly bool design_mode; #region --- Constructors --- /// /// Constructs a new instance. /// public GLControl() : this(GraphicsMode.Default) { } /// /// Constructs a new instance with the specified GraphicsMode. /// /// The OpenTK.Graphics.GraphicsMode of the control. public GLControl(GraphicsMode mode) : this(mode, 1, 0, GraphicsContextFlags.Default) { } /// /// Constructs a new instance with the specified GraphicsMode. /// /// The OpenTK.Graphics.GraphicsMode of the control. /// The major version for the OpenGL GraphicsContext. /// The minor version for the OpenGL GraphicsContext. /// The GraphicsContextFlags for the OpenGL GraphicsContext. public GLControl(GraphicsMode mode, int major, int minor, GraphicsContextFlags flags) { if (mode == null) throw new ArgumentNullException("mode"); // SDL does not currently support embedding // on external windows. If Open.Toolkit is not yet // initialized, we'll try to request a native backend // that supports embedding. // Most people are using GLControl through the // WinForms designer in Visual Studio. This approach // works perfectly in that case. Toolkit.Init(new ToolkitOptions { Backend = PlatformBackend.PreferNative }); SetStyle(ControlStyles.Opaque, true); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); DoubleBuffered = false; this.format = mode; this.major = major; this.minor = minor; this.flags = flags; // Note: the DesignMode property may be incorrect when nesting controls. // We use LicenseManager.UsageMode as a workaround (this only works in // the constructor). design_mode = DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; InitializeComponent(); } #endregion #region --- Private Methods --- IGLControl Implementation { get { ValidateState(); return implementation; } } [Conditional("DEBUG")] void ValidateContext(string message) { if (!Context.IsCurrent) { Debug.Print("[GLControl] Attempted to access {0} on a non-current context. Results undefined.", message); } } void ValidateState() { if (IsDisposed) throw new ObjectDisposedException(GetType().Name); if (!IsHandleCreated) CreateControl(); if (implementation == null || context == null || context.IsDisposed) RecreateHandle(); } #endregion #region --- Protected Methods --- /// /// Gets the CreateParams instance for this GLControl /// protected override CreateParams CreateParams { get { const int CS_VREDRAW = 0x1; const int CS_HREDRAW = 0x2; const int CS_OWNDC = 0x20; CreateParams cp = base.CreateParams; if (Configuration.RunningOnWindows) { // Setup necessary class style for OpenGL on windows cp.ClassStyle |= CS_VREDRAW | CS_HREDRAW | CS_OWNDC; } return cp; } } /// Raises the HandleCreated event. /// Not used. protected override void OnHandleCreated(EventArgs e) { if (context != null) context.Dispose(); if (implementation != null) implementation.WindowInfo.Dispose(); if (design_mode) implementation = new DummyGLControl(); else implementation = new GLControlFactory().CreateGLControl(format, this); context = implementation.CreateContext(major, minor, flags); MakeCurrent(); if (!design_mode) ((IGraphicsContextInternal)Context).LoadAll(); // Deferred setting of vsync mode. See VSync property for more information. if (initial_vsync_value.HasValue) { Context.SwapInterval = initial_vsync_value.Value ? 1 : 0; initial_vsync_value = null; } base.OnHandleCreated(e); if (resize_event_suppressed) { OnResize(EventArgs.Empty); resize_event_suppressed = false; } } /// Raises the HandleDestroyed event. /// Not used. protected override void OnHandleDestroyed(EventArgs e) { if (context != null) { context.Dispose(); context = null; } if (implementation != null) { implementation.WindowInfo.Dispose(); implementation = null; } base.OnHandleDestroyed(e); } /// /// Raises the System.Windows.Forms.Control.Paint event. /// /// A System.Windows.Forms.PaintEventArgs that contains the event data. protected override void OnPaint(PaintEventArgs e) { ValidateState(); if (design_mode) e.Graphics.Clear(BackColor); base.OnPaint(e); } /// /// Raises the Resize event. /// Note: this method may be called before the OpenGL context is ready. /// Check that IsHandleCreated is true before using any OpenGL methods. /// /// A System.EventArgs that contains the event data. protected override void OnResize(EventArgs e) { // Do not raise OnResize event before the handle and context are created. if (!IsHandleCreated) { resize_event_suppressed = true; return; } if (Configuration.RunningOnMacOS) { DelayUpdate delay = PerformContextUpdate; BeginInvoke(delay); //Need the native window to resize first otherwise our control will be in the wrong place. } else if (context != null) context.Update (Implementation.WindowInfo); base.OnResize(e); } /// /// Needed to delay the invoke on OS X. Also needed because OpenTK is .NET 2, otherwise I'd use an inline Action. /// public delegate void DelayUpdate(); /// /// Execute the delayed context update /// public void PerformContextUpdate(){ if (context != null) context.Update (Implementation.WindowInfo); } /// /// Raises the ParentChanged event. /// /// A System.EventArgs that contains the event data. protected override void OnParentChanged(EventArgs e) { if (context != null) context.Update(Implementation.WindowInfo); base.OnParentChanged(e); } #endregion #region --- Public Methods --- #region public void SwapBuffers() /// /// Swaps the front and back buffers, presenting the rendered scene to the screen. /// This method will have no effect on a single-buffered GraphicsMode. /// public void SwapBuffers() { ValidateState(); Context.SwapBuffers(); } #endregion #region public void MakeCurrent() /// /// /// Makes current in the calling thread. /// All OpenGL commands issued are hereafter interpreted by this context. /// /// /// When using multiple GLControls, calling MakeCurrent on /// one control will make all other controls non-current in the calling thread. /// /// /// /// A GLControl can only be current in one thread at a time. /// To make a control non-current, call GLControl.Context.MakeCurrent(null). /// /// public void MakeCurrent() { ValidateState(); Context.MakeCurrent(Implementation.WindowInfo); } #endregion #region public bool IsIdle /// /// Gets a value indicating whether the current thread contains pending system messages. /// [Browsable(false)] public bool IsIdle { get { ValidateState(); return Implementation.IsIdle; } } #endregion #region public IGraphicsContext Context /// /// Gets the IGraphicsContext instance that is associated with the GLControl. /// The associated IGraphicsContext is updated whenever the GLControl /// handle is created or recreated. /// When using multiple GLControls, ensure that Context /// is current before performing any OpenGL operations. /// /// [Browsable(false)] public IGraphicsContext Context { get { ValidateState(); return context; } private set { context = value; } } #endregion #region public float AspectRatio /// /// Gets the aspect ratio of this GLControl. /// [Description("The aspect ratio of the client area of this GLControl.")] public float AspectRatio { get { ValidateState(); return ClientSize.Width / (float)ClientSize.Height; } } #endregion #region public bool VSync /// /// Gets or sets a value indicating whether vsync is active for this GLControl. /// When using multiple GLControls, ensure that /// is current before accessing this property. /// /// /// [Description("Indicates whether GLControl updates are synced to the monitor's refresh rate.")] public bool VSync { get { if (!IsHandleCreated) { return initial_vsync_value.HasValue ? initial_vsync_value.Value : true; } ValidateState(); ValidateContext("VSync"); return Context.SwapInterval != 0; } set { // The winforms designer sets this to false by default which forces control creation. // However, events are typically connected after the VSync = false assignment, which // can lead to "event xyz is not fired" issues. // Work around this issue by deferring VSync mode setting to the HandleCreated event. if (!IsHandleCreated) { initial_vsync_value = value; return; } ValidateState(); ValidateContext("VSync"); Context.SwapInterval = value ? 1 : 0; } } #endregion #region public GraphicsMode GraphicsMode /// /// Gets the GraphicsMode of the IGraphicsContext associated with /// this GLControl. If you wish to change GraphicsMode, you must /// destroy and recreate the GLControl. /// public GraphicsMode GraphicsMode { get { ValidateState(); return Context.GraphicsMode; } } #endregion #region WindowInfo /// /// Gets the for this instance. /// public IWindowInfo WindowInfo { get { return implementation.WindowInfo; } } #endregion #region public Bitmap GrabScreenshot() /// /// Grabs a screenshot of the frontbuffer contents. /// When using multiple GLControls, ensure that /// is current before accessing this property. /// /// /// /// A System.Drawing.Bitmap, containing the contents of the frontbuffer. /// /// Occurs when no OpenTK.Graphics.GraphicsContext is current in the calling thread. /// [Obsolete("This method will not work correctly with OpenGL|ES. Please use GL.ReadPixels to capture the contents of the framebuffer (refer to http://www.opentk.com/doc/graphics/save-opengl-rendering-to-disk for more information).")] public Bitmap GrabScreenshot() { ValidateState(); ValidateContext("GrabScreenshot()"); Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height); System.Drawing.Imaging.BitmapData data = bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); bmp.UnlockBits(data); bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); return bmp; } #endregion #endregion } }