using System; using System.Threading; using System.ComponentModel; using Gdk; using OpenTK.Graphics; using Gtk; namespace OpenTK { /// /// The is a GTK widget for which an OpenGL context can be used to draw arbitrary graphics. /// [CLSCompliant(false)] [ToolboxItem(true)] public class GLWidget : GLArea { private static int _GraphicsContextCount; private static bool _SharedContextInitialized = false; private IGraphicsContext _GraphicsContext; private bool _Initialized = false; /// /// The previous frame time reported by GTK. /// private double? _PreviousFrameTime; /// /// Gets the time taken to render the last frame (in seconds). /// public double DeltaTime { get; private set; } /// /// The set for this widget. /// public GraphicsContextFlags ContextFlags { get; } /// /// Initializes a new instance of the class. /// public GLWidget() : this(GraphicsMode.Default) { } /// Constructs a new GLWidget using a given GraphicsMode public GLWidget(GraphicsMode graphicsMode) : this(graphicsMode, 1, 0, GraphicsContextFlags.Default) { } /// /// Initializes a new instance of the class. /// /// The which the widget should be constructed with. /// The major OpenGL version to attempt to initialize. /// The minor OpenGL version to attempt to initialize. /// /// Any flags which should be used during initialization of the . /// public GLWidget(GraphicsMode graphicsMode, int glVersionMajor, int glVersionMinor, GraphicsContextFlags contextFlags) { ContextFlags = contextFlags; AddTickCallback(UpdateFrameTime); SetRequiredVersion(glVersionMajor, glVersionMinor); if (graphicsMode.Depth > 0) { HasDepthBuffer = true; } if (graphicsMode.Stencil > 0) { HasStencilBuffer = true; } if (graphicsMode.ColorFormat.Alpha > 0) { HasAlpha = true; } } /// /// Updates the time delta with a new value from the last frame. /// /// The sending widget. /// The relevant frame clock. /// true if the callback should be called again; otherwise, false. private bool UpdateFrameTime(Widget widget, FrameClock frameClock) { var frameTimeµSeconds = frameClock.FrameTime; if (!_PreviousFrameTime.HasValue) { _PreviousFrameTime = frameTimeµSeconds; return true; } var frameTimeSeconds = (frameTimeµSeconds - _PreviousFrameTime) / 10e6; DeltaTime = (float)frameTimeSeconds; _PreviousFrameTime = frameTimeµSeconds; return true; } /// protected override GLContext OnCreateContext() { var gdkGLContext = Window.CreateGlContext(); GetRequiredVersion(out var major, out var minor); gdkGLContext.SetRequiredVersion(major, minor); gdkGLContext.DebugEnabled = ContextFlags.HasFlag(GraphicsContextFlags.Debug); gdkGLContext.ForwardCompatible = ContextFlags.HasFlag(GraphicsContextFlags.ForwardCompatible); gdkGLContext.Realize(); return gdkGLContext; } /// /// Destructs this object. /// ~GLWidget() { Dispose(false); } /// /// Destroys this , disposing it and destroying it in the context of GTK. /// public override void Destroy() { GC.SuppressFinalize(this); Dispose(true); base.Destroy(); } /// /// Disposes the current object, releasing any native resources it was using. /// /// protected override void Dispose(bool disposing) { if (disposing) { MakeCurrent(); OnShuttingDown(); if (GraphicsContext.ShareContexts && (Interlocked.Decrement(ref _GraphicsContextCount) == 0)) { OnGraphicsContextShuttingDown(); _SharedContextInitialized = false; } _GraphicsContext.Dispose(); } } /// /// Called when the first is created in the case where /// GraphicsContext.ShareContexts == true; /// public static event EventHandler GraphicsContextInitialized; /// /// Invokes the event. /// private static void OnGraphicsContextInitialized() { if (GraphicsContextInitialized != null) { GraphicsContextInitialized(null, EventArgs.Empty); } } /// /// Called when the first is being destroyed in the case where /// GraphicsContext.ShareContext == true; /// public static event EventHandler GraphicsContextShuttingDown; /// /// Invokes the event. /// private static void OnGraphicsContextShuttingDown() { if (GraphicsContextShuttingDown != null) { GraphicsContextShuttingDown(null, EventArgs.Empty); } } /// /// Called when this has finished initializing and has a valid /// . /// public event EventHandler Initialized; /// /// Invokes the event. /// protected virtual void OnInitialized() { if (Initialized != null) { Initialized(this, EventArgs.Empty); } } /// /// Called when this is being disposed. /// public event EventHandler ShuttingDown; /// /// Invokes the event. /// protected virtual void OnShuttingDown() { if (ShuttingDown != null) { ShuttingDown(this, EventArgs.Empty); } } /// /// Called when the widget needs to be (fully or partially) redrawn. /// /// /// protected override bool OnDrawn(Cairo.Context cr) { if (!_Initialized) { Initialize(); } var result = base.OnDrawn(cr); return result; } /// /// Initializes the with its given values and creates a . /// private void Initialize() { _Initialized = true; // Make the GDK GL context current MakeCurrent(); // Create a dummy context that will grab the GdkGLContext that is current on the thread _GraphicsContext = new GraphicsContext(ContextHandle.Zero, null); if (ContextFlags.HasFlag(GraphicsContextFlags.Debug)) { _GraphicsContext.ErrorChecking = true; } if (GraphicsContext.ShareContexts) { Interlocked.Increment(ref _GraphicsContextCount); if (!_SharedContextInitialized) { _SharedContextInitialized = true; ((IGraphicsContextInternal)_GraphicsContext).LoadAll(); OnGraphicsContextInitialized(); } } else { ((IGraphicsContextInternal)_GraphicsContext).LoadAll(); OnGraphicsContextInitialized(); } OnInitialized(); } } }