Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Minimalistic C# Application with GLUT Window on Linux

5.00/5 (2 votes)
9 Mar 2018CPOL1 min read 11.6K  
How to create an OpenGL (Mesa) window using C#, P/Invoke GLUT, GLU and GL libraries

Introduction

I wanted to test some OpenGL (on Linux platforms alternatively Mesa) libraries, that provide a GUI with capabilities comparable to Windows Presentation Foundation. But I needed a minimalistic window template like GLUT Window Template from for Linux and C#.

Background

There are a lot of forum questions/answers and tutorials, that deal with GLUT/GLU/GL window template, but almost all of them are based on Windows and C/C++. This tip shall provide quick access to a GLUT/GLU/GL window template for Linux/C#.

Using the Code

This is the main class:

C#
/// <summary>
/// The <see cref="MainClass"/> provides the application instance to test a X11 native window,
/// created with GLUT.
/// </summary>
class MainClass
{
    /// <summary>The X11 native window handle, used by GLUT.</summary>
    private int       _windowHandle;

    /// <summary>
    /// The entry point of the program, where the program control starts and ends.
    /// </summary>
    /// <param name="args">The command-line arguments.</param>
    public static void Main(string[] args)
    {
        MainClass instance = new MainClass();
        instance.Run(args);
    }

    /// <summary>
    /// Run the program instance with the specified command-line args.
    /// </summary>
    /// <param name="args">The command-line arguments.</param>
    private void Run(string[] args)
    {
        GLUT.Init(args.Length, args);
        GLUT.InitDisplayMode(GLUT.GLUT_RGB);
        GLUT.InitWindowSize(400, 300);
        _windowHandle = GLUT.CreateWindow("Hallo");

        GL.ClearColor(0.0f ,0.0f ,0.0f ,0.0f);
        GLU.Ortho2D(0,400,0,500);

        GLUT.DisplayFunc(new GLUT.DisplayProcDelegate(DisplayProc));
        GLUT.KeyboardFunc(new GLUT.KeyboardProcDelegate(KeyDownProc));
        GLUT.KeyboardUpFunc(new GLUT.KeyboardUpProcDelegate(KeyUpProc));

        GLUT.MouseFunc(new GLUT.MouseProcDelegate(MouseProcDelegate));
        GLUT.MainLoop();
    }

    /// <summary>
    /// The callback, called from GLUT on diaplay refresh request.
    /// </summary>
    private void DisplayProc()
    {
        GL.Clear(GL.GL_COLOR_BUFFER_BIT);
        GL.Color3f(1.0f , 1.0f , 1.0f);

        GL.Begin(GL.GL_POLYGON);
        GL.Vertex2i(200,125);
        GL.Vertex2i(100,375);
        GL.Vertex2i(300,375);
        GL.End();
        GL.Flush();
    }

    /// <summary>
    /// The callback, called from GLUT on keyboard key down request.
    /// </summary>
    /// <param name="key">Key.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    private void KeyDownProc (byte key, int x, int y)
    {
        // int modifier = GLUT.GetModifiers();
        if (key == 'q')
            GLUT.DestroyWindow(_windowHandle);
    }

    /// <summary>
    /// The callback, called from GLUT on keyboard key up request.
    /// </summary>
    /// <param name="key">Key.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    private void KeyUpProc (byte key, int x, int y)
    {
        // int modifier = GLUT.GetModifiers();
        if (key == 'q')
            GLUT.DestroyWindow(_windowHandle);
    }

    /// <summary>
    /// The callback, called from GLUT on mouse button pressed or released request.
    /// </summary>
    /// <param name="button">The pressed mouse button.</param>
    /// <param name="state">The button state.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    private void MouseProcDelegate(int button, int state, int x, int y)
    {
        if ((button == GLUT.GLUT_LEFT_BUTTON ) && (state == GLUT.GLUT_DOWN))
        {
            ;
        }

        if ((button == GLUT.GLUT_LEFT_BUTTON ) && (state == GLUT.GLUT_UP))
        {
            ;
        }
    }
}

To keep full control, I decided to declare the P/Invoke prototypes within the project. But I recommend you to take a look at Tao Framework and OpenTK as well.

First the GLUT function prototypes:

C#
public static class GLUT
{
    private const string LIB_GLUT = "libglut.so.3"; // "/usr/lib64/libglut.so.3"

    public const int GLUT_RGB  = 0;
    public const int GLUT_RGBA = GLUT_RGB;

    public const int GLUT_LEFT_BUTTON               = 0;
    public const int GLUT_MIDDLE_BUTTON             = 1;
    public const int GLUT_RIGHT_BUTTON              = 2;

    public const int GLUT_DOWN                      = 0;
    public const int GLUT_UP                        = 1;

    public const int  GLUT_ACTIVE_SHIFT              = 1;
    public const int  GLUT_ACTIVE_CTRL               = 2;
    public const int  GLUT_ACTIVE_ALT                = 4;

    #region Initialization and cleanup

    [DllImport(LIB_GLUT, EntryPoint="glutInit", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    private static extern void glutInit(ref int argcp, string[] argv);

    public static void Init(int argc, string[] argv)
    { int argcp = argc; glutInit(ref argcp, argv); }

    [DllImport(LIB_GLUT, EntryPoint="glutInitDisplayMode", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void InitDisplayMode(int argcp);

    [DllImport(LIB_GLUT, EntryPoint="glutInitWindowPosition", 
                         CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void InitWindowPosition(int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutInitWindowSize", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void InitWindowSize(int widt, int height);

    #endregion Initialization and cleanup

    #region Window handling and main loop

    [DllImport(LIB_GLUT, EntryPoint="glutMainLoop", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void MainLoop();

    [DllImport(LIB_GLUT, EntryPoint="glutCreateWindow", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern int CreateWindow(string title);

    [DllImport(LIB_GLUT, EntryPoint="glutDestroyWindow", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void DestroyWindow(int win);

    #endregion Window handling and main loop

    #region Callbacks (keyboard)

    // void glutKeyboardFunc(void (* callback)(unsigned char key, int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void KeyboardProcDelegate(byte key, int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutKeyboardFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void KeyboardFunc(KeyboardProcDelegate keyboardProc);

    // void glutKeyboardUpFunc(void (* callback)(unsigned char key, int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void KeyboardUpProcDelegate(byte key, int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutKeyboardUpFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void KeyboardUpFunc(KeyboardUpProcDelegate keyboardUpProc);

    // void glutSpecialFunc(void (* callback)(int key, int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void SpecialProcDelegate(int key, int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutSpecialFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void SpecialFunc(SpecialProcDelegate specialProc);

    // void glutSpecialUpFunc(void (* callback)(int key, int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void SpecialUpProcDelegate(int key, int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutSpecialUpFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    private static extern void SpecialUpFunc(SpecialUpProcDelegate specialUpProc);

    #endregion Callbacks (keyboard)

    #region Callbacks
    
    // void glutDisplayFunc(void (* callback)(void));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void DisplayProcDelegate();

    [DllImport(LIB_GLUT, EntryPoint="glutDisplayFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void DisplayFunc(DisplayProcDelegate displayProc);

    // void glutMouseFunc(void (* callback)(int button, int state, int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MouseProcDelegate(int button, int state, int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutMouseFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void MouseFunc(MouseProcDelegate mouseProc);

    // void glutMotionFunc(void (* callback)(int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MotionProcDelegate(int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutMotionFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void MotionFunc(MotionProcDelegate motionProc);

    // void glutPassiveMotionFunc(void (* callback)(int x, int y));
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void PassiveMotionProcDelegate(int x, int y);

    [DllImport(LIB_GLUT, EntryPoint="glutPassiveMotionFunc", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void PassiveMotionFunc(PassiveMotionProcDelegate passiveMotionProc);

    #endregion Callbacks

    [DllImport(LIB_GLUT, EntryPoint="glutGetModifiers", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern int GetModifiers();
}

Now the GLU function prototypes:

C#
public static class GLU
{
    private const string LIB_GLU = "libGLU.so"; // "/usr/lib64/libGLU.so"

    [DllImport(LIB_GLU, EntryPoint="gluOrtho2D", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Ortho2D (double left, double right, double bottom, double top);
}

And finally the GL function prototypes:

C#
public static class GL
{
    private const string LIB_GL = "libGL.so"; // "/usr/lib64/libGL.so"

    public const uint GL_DEPTH_BUFFER_BIT   = 0x00000100;
    public const uint GL_STENCIL_BUFFER_BIT = 0x00000400;
    public const uint GL_COLOR_BUFFER_BIT   = 0x00004000;

    public const int GL_POINTS                 = 0x0000;
    public const int GL_LINES                  = 0x0001;
    public const int GL_LINE_LOOP              = 0x0002;
    public const int GL_LINE_STRIP             = 0x0003;
    public const int GL_TRIANGLES              = 0x0004;
    public const int GL_TRIANGLE_STRIP         = 0x0005;
    public const int GL_TRIANGLE_FAN           = 0x0006;
    public const int GL_QUADS                  = 0x0007;
    public const int GL_QUAD_STRIP             = 0x0008;
    public const int GL_POLYGON                = 0x0009;

    [DllImport(LIB_GL, EntryPoint="glClearColor", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void ClearColor(float red, float green, float blue, float alpha);

    [DllImport(LIB_GL, EntryPoint="glClear", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Clear(uint mask);

    [DllImport(LIB_GL, EntryPoint="glColor3f", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Color3f(float red, float green, float blue);

    [DllImport(LIB_GL, EntryPoint="glBegin", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Begin(int mode);

    [DllImport(LIB_GL, EntryPoint="glVertex2i", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Vertex2i(int x, int y);

    [DllImport(LIB_GL, EntryPoint="glEnd", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void End();

    [DllImport(LIB_GL, EntryPoint="glFlush", CallingConvention=CallingConvention.Cdecl)]
    [System.Security.SuppressUnmanagedCodeSecurity()]
    public static extern void Flush();
}

My MonoDevelop 5.10 (older versions are possible as well) project target is AnyCPU on openSuse Leap 42.1 64 bit. Consequently, the application runs with 64 bit and accesses the GLUT/GLU/GL libraries from /usr/lib64. The only reference, used by the project, is System.

The GLU and GLUT (GL is a dependency) installation packages must be installed.

  • libGLU1 (OpenGL utility library)
  • libglut3 (Freely licensed alternative to the GLUT library)

Points of Interest

I hope this tip will help you to test OpenGL on Linux with C# convenient.

References

History

  • 09.03.2018 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)