Reputation: 3789
I am programming an application in C# using Winforms for GUI. I want to create a control that displays 3D graphics using OpenGL.
I experimented a bit with OpenTK and found that it was fairly simple to use its GLControl to display OpenGL graphics; I just had to implement an method that handled the Paint event of the GLControl and within this method call OpenTK wrapper methods, e.g instead of
glVertex2d(0.0, 0.0);
I would call:
GL.Vertex2(0.0, 0.0);
I also found that it was possible to mix OpenTK with calls directly to OpenGL using P/Invoke:
using System;
using System.Runtime.InteropServices;
public class gl
{
[DllImport("opengl32")]
public static extern void glVertex2d(double x, double y);
};
...
GL.Vertex2(0.0, 0.0); // OpenTK
gl.glVertex2d(1.0, 1.0); // OpenGL directly using Pinvoke
However, I wish to remove the dependence on OpenTK alltogether. I do not want to use any external library but instead call OpenGL directly.
How do I get OpenGL to "paint" on a winform? In other words; how do I implement a GLControl myself?
Upvotes: 1
Views: 7321
Reputation: 11961
how do I implement a GLControl myself?
More or less how OpenTK do; since it has source open, you can study it!
I warn you: it won't be easy. You must synchronize the window/control creation with the OpenGL context creation (overriding CreateHandle
, OnHandleCreated
, OnHandleDestroyed
), and override the OnPaint
routine in order to make the OpenGL context current (indeed allowing the drawing in the paint phase).
Just to give you an idea: this is my UserControl (partial) implementation:
protected override void CreateHandle()
{
// Create the render window
mRenderWindow = new RenderWindow(this);
mRenderWindow.Width = (uint)base.ClientSize.Width;
mRenderWindow.Height = (uint)base.ClientSize.Height;
// "Select" device pixel format before creating control handle
switch (Environment.OSVersion.Platform) {
case PlatformID.Win32Windows:
case PlatformID.Win32NT:
mRenderWindow.PreCreateObjectWgl(SurfaceFormat);
break;
case PlatformID.Unix:
mRenderWindow.PreCreateObjectX11(SurfaceFormat);
break;
}
// OVerride default swap interval
mRenderWindow.SwapInterval = SwapInterval;
// Base implementation
base.CreateHandle();
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.HandleCreated"/> event.
/// </summary>
/// <param name="e">
/// An <see cref="T:System.EventArgs"/> that contains the event data.
/// </param>
protected override void OnHandleCreated(EventArgs e)
{
if (DesignMode == false) {
// Finalize control handle creation
// - WGL: SetPixelFormat
// - GLX: store FBConfig and XVisualInfo selected in CreateHandle()
// ...
// - Setup swap interval, if supported
mRenderWindow.Create((RenderContext)null);
// Create the render context (before event handling)
mRenderContext = new RenderContext(mRenderWindow.GetDeviceContext(), mRenderContextFlags);
}
// Base implementation
base.OnHandleCreated(e);
// Raise CreateContext event
if (DesignMode == false) {
mRenderContext.MakeCurrent(true);
RaiseCreateContextEvent(new RenderEventArgs(mRenderContext, mRenderWindow));
mRenderContext.MakeCurrent(false);
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected override void OnHandleDestroyed(EventArgs e)
{
if (DesignMode == false) {
if (mRenderContext != null) {
// Raise DestroyContext event
mRenderContext.MakeCurrent(true);
RaiseDestroyContextEvent(new RenderEventArgs(mRenderContext, mRenderWindow));
mRenderContext.MakeCurrent(false);
// Dispose the renderer context
mRenderContext.Dispose();
mRenderContext = null;
}
// Dispose the renderer window
if (mRenderWindow != null) {
mRenderWindow.Dispose();
mRenderWindow = null;
}
}
// Base implementation
base.OnHandleDestroyed(e);
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
if (DesignMode == false) {
if (mRenderContext != null) {
// Render the UserControl
mRenderContext.MakeCurrent(true);
// Define viewport
OpenGL.Gl.Viewport(0, 0, ClientSize.Width, ClientSize.Height);
// Derived class implementation
try {
RenderThis(mRenderContext);
} catch (Exception exception) {
}
// Render event
RaiseRenderEvent(new RenderEventArgs(mRenderContext, mRenderWindow));
// Swap buffers if double-buffering
Surface.SwapSurface();
// Base implementation
base.OnPaint(e);
mRenderContext.MakeCurrent(false);
} else {
e.Graphics.DrawLines(mFailurePen, new Point[] {
new Point(e.ClipRectangle.Left, e.ClipRectangle.Bottom), new Point(e.ClipRectangle.Right, e.ClipRectangle.Top),
new Point(e.ClipRectangle.Left, e.ClipRectangle.Top), new Point(e.ClipRectangle.Right, e.ClipRectangle.Bottom),
});
// Base implementation
base.OnPaint(e);
}
} else {
e.Graphics.Clear(Color.Black);
e.Graphics.DrawLines(mDesignPen, new Point[] {
new Point(e.ClipRectangle.Left, e.ClipRectangle.Bottom), new Point(e.ClipRectangle.Right, e.ClipRectangle.Top),
new Point(e.ClipRectangle.Left, e.ClipRectangle.Top), new Point(e.ClipRectangle.Right, e.ClipRectangle.Bottom),
});
// Base implementation
base.OnPaint(e);
}
}
protected override void OnClientSizeChanged(EventArgs e)
{
if (mRenderWindow != null) {
mRenderWindow.Width = (uint)base.ClientSize.Width;
mRenderWindow.Height = (uint)base.ClientSize.Height;
}
// Base implementation
base.OnClientSizeChanged(e);
}
private static readonly Pen mFailurePen = new Pen(Color.Red, 1.5f);
private static readonly Pen mDesignPen = new Pen(Color.Green, 1.0f);
#endregion
(RenderWindow is an utility class for selecting the device pixel format). The key of the implementation is that you need to mimic the same calls that a classical C++ program does, integrated with the actual System.Windows.Forms implementation (.NET and Mono).
But the most big task is the OpenGL API definition with P/Invokes: OpenGL defines a LOT of enumerants (constants) and entry points (thousands...); As human kind, you cannot write all those declaration manually (mainly for the limited time, but it is also error prone). If you don't like OpenTK, you can start from the TAO framework. If you have time to waste and a lot of patience, you can generate the C# bindings yourself, using the OpenGL registry.
But... WAIT, BIG GOOD NEWS (you are very lucky): XML OpenGL API definition are publicly accessible! (!)
(!) For those that don't understand by enthusiastic answer: I'm waiting for it by many, many, many years!!!
Upvotes: 3