2009-09-03 21:01:11 +02:00
#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
{
/// <summary>
2012-05-23 01:42:47 +02:00
/// 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 <see cref="GraphicsMode"/>s.
2009-09-03 21:01:11 +02:00
/// </summary>
public partial class GLControl : UserControl
{
IGraphicsContext context ;
IGLControl implementation ;
GraphicsMode format ;
int major , minor ;
GraphicsContextFlags flags ;
2009-09-18 17:46:00 +02:00
bool? initial_vsync_value ;
2009-10-22 19:56:55 +02:00
// 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 ;
2011-05-30 11:20:12 +02:00
// 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 ;
2009-09-03 21:01:11 +02:00
#region - - - Constructors - - -
/// <summary>
2012-05-23 01:42:47 +02:00
/// Constructs a new instance.
2009-09-03 21:01:11 +02:00
/// </summary>
public GLControl ( )
: this ( GraphicsMode . Default )
{ }
/// <summary>
2012-05-23 01:42:47 +02:00
/// Constructs a new instance with the specified GraphicsMode.
2009-09-03 21:01:11 +02:00
/// </summary>
/// <param name="mode">The OpenTK.Graphics.GraphicsMode of the control.</param>
public GLControl ( GraphicsMode mode )
: this ( mode , 1 , 0 , GraphicsContextFlags . Default )
{ }
/// <summary>
2012-05-23 01:42:47 +02:00
/// Constructs a new instance with the specified GraphicsMode.
2009-09-03 21:01:11 +02:00
/// </summary>
/// <param name="mode">The OpenTK.Graphics.GraphicsMode of the control.</param>
/// <param name="major">The major version for the OpenGL GraphicsContext.</param>
/// <param name="minor">The minor version for the OpenGL GraphicsContext.</param>
/// <param name="flags">The GraphicsContextFlags for the OpenGL GraphicsContext.</param>
public GLControl ( GraphicsMode mode , int major , int minor , GraphicsContextFlags flags )
{
2009-10-19 19:38:16 +02:00
if ( mode = = null )
throw new ArgumentNullException ( "mode" ) ;
2013-11-12 20:37:23 +01:00
// 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
} ) ;
2009-10-19 19:38:16 +02:00
2009-09-03 21:01:11 +02:00
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 ;
2009-10-19 19:38:16 +02:00
2011-05-30 11:20:12 +02:00
// 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 ;
2009-10-19 19:38:16 +02:00
InitializeComponent ( ) ;
2009-09-03 21:01:11 +02:00
}
#endregion
#region - - - Private Methods - - -
IGLControl Implementation
{
get
{
ValidateState ( ) ;
return implementation ;
}
}
2014-01-26 11:08:08 +01:00
[Conditional("DEBUG")]
void ValidateContext ( string message )
{
if ( ! Context . IsCurrent )
{
Debug . Print ( "[GLControl] Attempted to access {0} on a non-current context. Results undefined." , message ) ;
}
}
2009-09-03 21:01:11 +02:00
void ValidateState ( )
{
if ( IsDisposed )
throw new ObjectDisposedException ( GetType ( ) . Name ) ;
if ( ! IsHandleCreated )
CreateControl ( ) ;
2009-09-18 17:46:00 +02:00
2009-09-03 21:01:11 +02:00
if ( implementation = = null | | context = = null | | context . IsDisposed )
RecreateHandle ( ) ;
}
2009-09-18 17:46:00 +02:00
2009-09-03 21:01:11 +02:00
#endregion
#region - - - Protected Methods - - -
2013-11-16 22:38:17 +01:00
/// <summary>
/// Gets the <c>CreateParams</c> instance for this <c>GLControl</c>
/// </summary>
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 ;
}
}
2009-09-03 21:01:11 +02:00
/// <summary>Raises the HandleCreated event.</summary>
/// <param name="e">Not used.</param>
protected override void OnHandleCreated ( EventArgs e )
{
if ( context ! = null )
context . Dispose ( ) ;
if ( implementation ! = null )
implementation . WindowInfo . Dispose ( ) ;
2009-09-18 17:46:00 +02:00
2011-05-30 11:20:12 +02:00
if ( design_mode )
2009-09-03 21:01:11 +02:00
implementation = new DummyGLControl ( ) ;
else
implementation = new GLControlFactory ( ) . CreateGLControl ( format , this ) ;
context = implementation . CreateContext ( major , minor , flags ) ;
MakeCurrent ( ) ;
2011-05-30 11:20:12 +02:00
if ( ! design_mode )
2009-09-03 21:01:11 +02:00
( ( IGraphicsContextInternal ) Context ) . LoadAll ( ) ;
2009-09-18 17:46:00 +02:00
// Deferred setting of vsync mode. See VSync property for more information.
if ( initial_vsync_value . HasValue )
{
2011-09-06 14:06:30 +02:00
Context . SwapInterval = initial_vsync_value . Value ? 1 : 0 ;
2009-09-18 17:46:00 +02:00
initial_vsync_value = null ;
}
2009-09-03 21:01:11 +02:00
base . OnHandleCreated ( e ) ;
2009-10-22 19:56:55 +02:00
if ( resize_event_suppressed )
{
OnResize ( EventArgs . Empty ) ;
resize_event_suppressed = false ;
}
2009-09-03 21:01:11 +02:00
}
/// <summary>Raises the HandleDestroyed event.</summary>
/// <param name="e">Not used.</param>
protected override void OnHandleDestroyed ( EventArgs e )
{
if ( context ! = null )
{
context . Dispose ( ) ;
context = null ;
}
if ( implementation ! = null )
{
implementation . WindowInfo . Dispose ( ) ;
implementation = null ;
}
2009-09-18 17:46:00 +02:00
2009-09-03 21:01:11 +02:00
base . OnHandleDestroyed ( e ) ;
}
/// <summary>
/// Raises the System.Windows.Forms.Control.Paint event.
/// </summary>
/// <param name="e">A System.Windows.Forms.PaintEventArgs that contains the event data.</param>
protected override void OnPaint ( PaintEventArgs e )
{
ValidateState ( ) ;
2009-09-18 17:46:00 +02:00
2011-05-30 11:20:12 +02:00
if ( design_mode )
2009-09-03 21:01:11 +02:00
e . Graphics . Clear ( BackColor ) ;
base . OnPaint ( e ) ;
}
/// <summary>
/// Raises the Resize event.
2009-10-22 19:56:55 +02:00
/// Note: this method may be called before the OpenGL context is ready.
/// Check that IsHandleCreated is true before using any OpenGL methods.
2009-09-03 21:01:11 +02:00
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnResize ( EventArgs e )
{
2009-10-22 19:56:55 +02:00
// Do not raise OnResize event before the handle and context are created.
if ( ! IsHandleCreated )
{
resize_event_suppressed = true ;
return ;
}
2009-10-15 16:52:57 +02:00
2014-02-22 18:28:14 +01:00
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 ) ;
2009-09-03 21:01:11 +02:00
base . OnResize ( e ) ;
}
2014-02-22 18:28:14 +01:00
/// <summary>
/// Needed to delay the invoke on OS X. Also needed because OpenTK is .NET 2, otherwise I'd use an inline Action.
/// </summary>
public delegate void DelayUpdate ( ) ;
/// <summary>
/// Execute the delayed context update
/// </summary>
public void PerformContextUpdate ( )
{
if ( context ! = null )
context . Update ( Implementation . WindowInfo ) ;
}
2014-02-21 03:27:17 +01:00
2009-09-03 21:01:11 +02:00
/// <summary>
/// Raises the ParentChanged event.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnParentChanged ( EventArgs e )
{
if ( context ! = null )
context . Update ( Implementation . WindowInfo ) ;
base . OnParentChanged ( e ) ;
}
#endregion
#region - - - Public Methods - - -
#region public void SwapBuffers ( )
/// <summary>
/// Swaps the front and back buffers, presenting the rendered scene to the screen.
2014-01-26 10:36:52 +01:00
/// This method will have no effect on a single-buffered <c>GraphicsMode</c>.
2009-09-03 21:01:11 +02:00
/// </summary>
public void SwapBuffers ( )
{
ValidateState ( ) ;
Context . SwapBuffers ( ) ;
}
#endregion
#region public void MakeCurrent ( )
/// <summary>
2014-01-26 10:36:52 +01:00
/// <para>
/// Makes <see cref="GLControl.Context"/> current in the calling thread.
/// All OpenGL commands issued are hereafter interpreted by this context.
/// </para>
/// <para>
/// When using multiple <c>GLControl</c>s, calling <c>MakeCurrent</c> on
/// one control will make all other controls non-current in the calling thread.
/// </para>
/// <seealso cref="Context"/>
/// <para>
/// A <c>GLControl</c> can only be current in one thread at a time.
/// To make a control non-current, call <c>GLControl.Context.MakeCurrent(null)</c>.
/// </para>
2009-09-03 21:01:11 +02:00
/// </summary>
public void MakeCurrent ( )
{
ValidateState ( ) ;
Context . MakeCurrent ( Implementation . WindowInfo ) ;
}
#endregion
#region public bool IsIdle
/// <summary>
/// Gets a value indicating whether the current thread contains pending system messages.
/// </summary>
[Browsable(false)]
public bool IsIdle
{
get
{
ValidateState ( ) ;
return Implementation . IsIdle ;
}
}
#endregion
#region public IGraphicsContext Context
/// <summary>
2014-01-26 10:36:52 +01:00
/// Gets the <c>IGraphicsContext</c> instance that is associated with the <c>GLControl</c>.
/// The associated <c>IGraphicsContext</c> is updated whenever the <c>GLControl</c>
/// handle is created or recreated.
/// When using multiple <c>GLControl</c>s, ensure that <c>Context</c>
/// is current before performing any OpenGL operations.
/// <seealso cref="MakeCurrent"/>
2009-09-03 21:01:11 +02:00
/// </summary>
[Browsable(false)]
public IGraphicsContext Context
{
get
{
ValidateState ( ) ;
return context ;
}
private set { context = value ; }
}
#endregion
#region public float AspectRatio
/// <summary>
/// Gets the aspect ratio of this GLControl.
/// </summary>
[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
/// <summary>
2014-01-26 10:36:52 +01:00
/// Gets or sets a value indicating whether vsync is active for this <c>GLControl</c>.
/// When using multiple <c>GLControl</c>s, ensure that <see cref="Context"/>
/// is current before accessing this property.
/// <seealso cref="Context"/>
/// <seealso cref="MakeCurrent"/>
2009-09-03 21:01:11 +02:00
/// </summary>
2011-09-06 14:06:30 +02:00
[Description("Indicates whether GLControl updates are synced to the monitor's refresh rate.")]
2009-09-03 21:01:11 +02:00
public bool VSync
{
get
{
2009-09-18 17:46:00 +02:00
if ( ! IsHandleCreated )
2014-01-26 10:39:39 +01:00
{
return initial_vsync_value . HasValue ?
initial_vsync_value . Value : true ;
}
2009-09-18 17:46:00 +02:00
2009-09-03 21:01:11 +02:00
ValidateState ( ) ;
2014-01-26 11:08:08 +01:00
ValidateContext ( "VSync" ) ;
2014-01-26 10:39:39 +01:00
return Context . SwapInterval ! = 0 ;
2009-09-03 21:01:11 +02:00
}
set
{
2009-09-18 17:46:00 +02:00
// 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 ;
}
2009-09-03 21:01:11 +02:00
ValidateState ( ) ;
2014-01-26 11:08:08 +01:00
ValidateContext ( "VSync" ) ;
2014-01-26 10:39:39 +01:00
Context . SwapInterval = value ? 1 : 0 ;
2009-09-03 21:01:11 +02:00
}
}
#endregion
#region public GraphicsMode GraphicsMode
/// <summary>
2014-01-26 10:36:52 +01:00
/// Gets the <c>GraphicsMode</c> of the <c>IGraphicsContext</c> associated with
/// this <c>GLControl</c>. If you wish to change <c>GraphicsMode</c>, you must
/// destroy and recreate the <c>GLControl</c>.
2009-09-03 21:01:11 +02:00
/// </summary>
public GraphicsMode GraphicsMode
{
get
{
ValidateState ( ) ;
return Context . GraphicsMode ;
}
}
#endregion
2009-09-10 16:05:59 +02:00
#region WindowInfo
/// <summary>
/// Gets the <see cref="OpenTK.Platform.IWindowInfo"/> for this instance.
/// </summary>
public IWindowInfo WindowInfo
{
get { return implementation . WindowInfo ; }
}
#endregion
2009-09-03 21:01:11 +02:00
#region public Bitmap GrabScreenshot ( )
2014-01-26 10:36:52 +01:00
/// <summary>
/// Grabs a screenshot of the frontbuffer contents.
/// When using multiple <c>GLControl</c>s, ensure that <see cref="Context"/>
/// is current before accessing this property.
/// <seealso cref="Context"/>
/// <seealso cref="MakeCurrent"/>
/// </summary>
2009-09-03 21:01:11 +02:00
/// <returns>A System.Drawing.Bitmap, containing the contents of the frontbuffer.</returns>
/// <exception cref="OpenTK.Graphics.GraphicsContextException">
/// Occurs when no OpenTK.Graphics.GraphicsContext is current in the calling thread.
/// </exception>
2009-11-04 21:47:06 +01:00
[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).")]
2009-09-03 21:01:11 +02:00
public Bitmap GrabScreenshot ( )
{
ValidateState ( ) ;
2014-01-26 11:08:08 +01:00
ValidateContext ( "GrabScreenshot()" ) ;
2009-09-18 17:46:00 +02:00
2009-09-03 21:01:11 +02:00
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
}
}