Andy
Andy

Reputation: 3789

How do I call openGL from C#?

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

Answers (1)

Luca
Luca

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

Related Questions