Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Writing a Platform and GUI Toolkit Independent OpenGL Class

4.92/5 (33 votes)
1 Nov 2010CPOL13 min read 102.5K   7.5K  
Article showing how to write OS and GUI toolkit portable OpenGL view class

Introduction

Most of the basic OpenGL setup code that I have come across is demonstrated either using a windowing-system-neutral approach using glut or specific to some GUI toolkit like MFC, GTK+ or Qt. For instance, the glut examples show how to create a ready-to-use window with glutCreateWindow or MFC examples show how to setup an OpenGL context in a CView derived class. GTK+ uses some extensions like gtkglarea or gtkglext and Qt has its own QGLWidget which hides all the OpenGL setup implementations from the user. Good enough if you want to stick to one toolkit or operating system with your code. But imagine a situation where you want to change the toolkit or the OS, say from MFC to Motif! The OpenGL view class that you have written has to go through a lot of change! So why don't we have a platform and toolkit neutral class which can simply render into any window created on any platform?

Background

In this article, I would like to demonstrate how to create a platform and toolkit independent OpenGL view class. For that, we need to first understand the primary requirement for setting up an OpenGL rendering on any window.

OpenGL rendering on any toolkit's window boils down to the underlying windowing system supported on a particular OS. That is, the Device Context on Microsoft Windows and the Display of the X Window System on UNIX/Linux. Although I haven't tested the code on MacOSX, since MacOSX also uses the X Window System as its windowing system, this article is also applicable for MacOSX.

The reader must understand that this article is not about teaching OpenGL or any specific toolkit. This article is intended for those who already have a good understanding of OpenGL and at least one GUI toolkit. Details of creating DLLs or UNIX/Linux shared objects are also out of scope of this article. I am trying to show how to write OpenGL code that is portable to not only different OSs but also to other GUI toolkits. Though language portability can be achieved by writing suitable wrappers on the code shown here, we are focusing on writing the code in C/C++.

Using the Code

First, we need to prepare the window so that it should accept its pixel rendering instructions from the OpenGL API. Then, we must handle the OpenGL drawing commands. Next, we must also handle the effects of resizing the window on OpenGL rendering. So essentially, the three prime methods of our class will be for setting up the window, rendering the OpenGL stuff and resizing the window.

For getting hold of the windowing system, the key things are HWND (Handle to the Window) and HDC (Handle to the Device Context) on Microsoft Windows and Display (X Display is somewhat equivalent to MSWin Device Context though the theory of X Windows is a bit different from MSWin and not in the scope of this article) and Window (equivalent to HWND) on X Windows. If we can access these things from any toolkit, we can pretty much enable our OpenGL view class to render onto any toolkit's window. And trust me, any good toolkit should have functions or methods to provide you the window handle and the Display. The window handle is nothing but HWND or Window.

So let us look at the code now. We would be writing conditional compilation preprocessor statements to make our class portable on both Windows and UNIX/Linux.

Since our class would be in a shared library, we would be creating a DLL (GLView.dll) on Windows and a shared object file (libGLView.so) on Linux. We need to export the class symbols on Windows and hence would be using the __declspec extensions. Linux does not need that and hence we mask it using preprocessor directives.

C++
#ifdef GLVIEW_EXPORTS
    #ifdef WIN32
        #define GLVIEW_API __declspec(dllexport)
    #else
        #define GLVIEW_API
    #endif
#else
    #ifdef WIN32
        #define GLVIEW_API __declspec(dllimport)
    #else
        #define GLVIEW_API
    #endif
#endif

Then we include the necessary headers, again using preprocessor directives to make platform specific includes.

C++
#ifdef WIN32
#include <windows.h>     // required by OpenGL and must be included before gl.h
#include <TCHAR.H>     // for unicode support
#endif

#include <GL/gl.h>
#include <GL/glu.h>
#ifndef WIN32         // X Windows only
#include <GL/glx.h>
#endif

And now our class declaration...
Note, the methods and data members are also guarded by preprocessor directives.

C++
class GLVIEW_API CGLView 
{
public:
    CGLView(void);
#ifdef WIN32
    void SetWindow(HWND ihWnd);
#else
    void SetWindow(Display* pDisp, const Window& wnd);
#endif
    bool SetupGLContext(bool iSetPixelFormat);
    void Resize(unsigned short iWidth, unsigned short iHeight);
    void RenderScene(void);
    void Refresh( void);
private:
#ifdef WIN32
    int SetPixelFormat(HDC hdc);
#endif
    void Setup3DEnvironment();
    void DrawGradientBackground();
    void InitDisplayLists();
    void DoAntialiasing();
private:
#ifdef WIN32
    HWND m_hWnd;
    HDC m_hDC;
    HGLRC m_hGLRC;
#else
    Display* m_pXDisplay;
    int m_iXScreen;
    Window m_iXWindow;
    GLXContext m_hGLContext;
    XVisualInfo *m_hVisual;
    Colormap m_ColMap;
#endif
    // other private members hereafter...
};

Let us have a look at the private data members. The WIN32 members are of type HWND, HDC and HGLRC. HGLRC is the handle to the GL Rendering Context provided by Windows to make the window OpenGL-aware. HDC can be obtained using Windows API function GetDC() and HGLRC can created by Windows specific GL API function wglCreateContext() once HWND is obtained.

The X Windows members are of type Display, Window and GLXContext. GLXContext is similar to HGLRC which again can be created using X Window specific GL API function glXCreateContext() once Display and Window is obtained.
Remember, creation of the OpenGL Rendering Context is the key entry point to begin rendering to any window.

Now, let us look at the important methods of the class. The constructor essentially does only the initialization of the private variables.
This first important method of the class is SetWindow(). This method has a different signature on Linux than on Windows. The difference is that on Windows, it takes the HWND as the argument whereas Display and Window as arguments on Linux. Note, though I am referring as Linux, it's applicable to any OS that has X Windows as its windowing system. Let us look at the implementation of the SetWindow() method for both systems.

C++
#ifdef WIN32
void CGLView::SetWindow(HWND ihWnd)
{
    m_hWnd = ihWnd;
}
#else
void CGLView::SetWindow(Display* pDisp, const Window& wnd)
{
    m_pXDisplay = pDisp;
    m_iXWindow = wnd;
}
#endif

The method simply sets the arguments to the respective private data members.

The next method is SetupGLContext. This method is actually responsible to setup the OpenGL context by calling the appropriate platform based API functions discussed earlier. This method has code discretized by preprocessor directives.

C++
bool CGLView::SetupGLContext(bool iSetPixelFormat)
{
#ifdef WIN32
    if(m_hWnd == NULL)
    {
        return false;
    }
    m_hDC = ::GetDC(m_hWnd);
    if(iSetPixelFormat)
    {
        SetPixelFormat(m_hDC);
    } 
    if (m_hGLRC = ::wglCreateContext(m_hDC))
    { 
        // try to make it the thread's current rendering context 
        if(false == wglMakeCurrent(m_hDC, m_hGLRC))
        {
            MessageBox(m_hWnd, _T("Failed wglMakeCurrent"), 
                    _T("Error!"), MB_ICONERROR);
            return false;
        }
        else
        {
            glClearColor(0.0, 0.0, 0.0, 1.0);
            InitDisplayLists();
        }
    }
    return true;
#else
    int attrListSgl[] =
    { GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4,
    GLX_DEPTH_SIZE, 16, None };
    int attrListDbl[] =
    { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4,
    GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, None };
    m_iXScreen = DefaultScreen(m_pXDisplay);
    m_hVisual = glXChooseVisual(m_pXDisplay, m_iXScreen, attrListDbl);
    if (NULL == m_hVisual)
    {
        m_hVisual = glXChooseVisual(m_pXDisplay, m_iXScreen, attrListSgl);
        cout << "Singlebuffered : true" << endl;
        if(NULL == m_hVisual)
        {
            cerr << "Could not get suitable XVisualInfo\n" << endl;
            return false;
        }
    }
    else
    {
        cout << "Doublebuffered : true\n" << endl;
    }
    // Create the rendering context
    m_hGLContext = glXCreateContext(m_pXDisplay, m_hVisual, 0, GL_TRUE);
    // Create a colormap
    m_ColMap = XCreateColormap
            (m_pXDisplay, RootWindow(m_pXDisplay, m_hVisual->screen),
            m_hVisual->visual, AllocNone);
    // Make the rendering context current, perform initialization, then
    // deselect it
    if(False == glXMakeCurrent(m_pXDisplay, m_iXWindow, m_hGLContext))
    {
        return false;
    }
    else
    {
        glClearColor(0.0, 0.0, 0.0, 1.0);
        InitDisplayLists();
    }
    glXMakeCurrent(m_pXDisplay, None, NULL);
    return true;
#endif
}

Note the additional method call (SetPixelFormat()), within SetupGLContext(), for Windows platform. This method is purposefully based on the boolean value coming in as the argument to SetupGLContext(). This is done so that GL-ready widgets from some toolkits can be accomodated, that is, resetting of PixelFormat is not required for already set widgets. The developer using the GLView class should discretely pass the boolean value to SetupGLContext based on what kind of widget he is using to fit the GLView class onto.
Once the GLContext is created, it is made current and the window ready for receiving OpenGL commands by calling wglMakeCurrent() and glXMakeCurrent() on Windows and X respectively.

The Resize() method is responsible to remap the view-port and the viewing volume to the window after it is resized. It does the common OpenGL view-port and matrix transformations stuff on both platforms except for discretely calling the xxxMakeCurrent() methods.

C++
void CGLView::Resize(unsigned short iWidth, unsigned short iHeight)
{
    GLdouble modelMatrix[16];
    GLdouble projMatrix[16];
    GLint viewport[4];
    winH = (GLdouble)iHeight;
    winW = (GLdouble)iWidth;
    // setup viewport, projection etc.:
    /* Prevent a divide by zero*/
    if(iHeight == 0)
    iHeight = 1;
#ifdef WIN32
    wglMakeCurrent(m_hDC, m_hGLRC);
#else
    glXMakeCurrent(m_pXDisplay, m_iXWindow, m_hGLContext);
#endif
    glViewport (0, 0, iWidth, iHeight);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    if (iWidth <= iHeight)
        glOrtho (-nRange, nRange, -nRange*iHeight/iWidth, nRange*iHeight/iWidth, 
                -nRange*10000, nRange*10000);
    else
        glOrtho (-nRange*iWidth/iHeight, nRange*iWidth/iHeight, -nRange, nRange, 
                -nRange*10000, nRange*10000);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    ...
#ifdef WIN32
    wglMakeCurrent(NULL,NULL);
#else
    glXMakeCurrent(m_pXDisplay, None, NULL);
#endif
}

The RenderScene() method is responsible to show the OpenGL primitives drawn by the programmer onto the OpenGL-ready window. This method too does mostly the common OpenGL stuff except for the xxxMakeCurrent() methods and swapping the display buffers.

C++
void CGLView::RenderScene(void)
{
#ifdef WIN32
    wglMakeCurrent(m_hDC, m_hGLRC);
#else
    glXMakeCurrent(m_pXDisplay, m_iXWindow, m_hGLContext);
#endif
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    DrawGradientBackground(); 
    DoAntialiasing();
    Setup3DEnvironment(); 
    glPushMatrix();
    {
        //Draw stuff here
        glRotatef(-45.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(-45.0f, 0.0f, 0.0f, 1.0f);
        glColor4ub(125, 255, 255, 255);
        drawTorus(30, 20, 50, 25, false);
    }
    glPopMatrix();
#ifdef WIN32
    SwapBuffers(m_hDC);
    wglMakeCurrent(NULL, NULL);
#else
    glXSwapBuffers(m_pXDisplay, m_iXWindow);
    glXMakeCurrent(m_pXDisplay, None, NULL);
#endif
}

The Refresh() method is responsible for repainting the window and thus contains platform specific code.

C++
void CGLView::Refresh( void)
{
#ifdef WIN32
    ::InvalidateRect(m_hWnd, NULL, FALSE);
#else
    // setup event data
    XWindowAttributes winattr;
    Status st = XGetWindowAttributes(m_pXDisplay, m_iXWindow, &winattr);
    XExposeEvent ev = 
        { Expose, 0, 1, m_pXDisplay, m_iXWindow, 0, 0, 
            winattr.width, winattr.height, 0 };
    // send event to display connection
    XSendEvent(m_pXDisplay, m_iXWindow, False, ExposureMask, (XEvent *) &ev);
    XFlush(m_pXDisplay);
#endif
}

The other methods are up to the developer to enhance the GLView class and add functionality like view zooming, panning, etc. To keep the class brief for this tutorial, I have not added many such functionalities of a typical OpenGL View class.

Build a dynamic or a static library and use it as common API for any toolkit or any platform.

Now let us take a look at using our base code library in different toolkits across Windows and Linux.

WINDOWS

To begin with, let's see how to use it in the most commonly used toolkit on Windows -- its own MFC.
In an application with the Document/View architecture, the CView derived class is the one that hosts the visualization. We use that class to embed our GLView object. Then we obtain the HWND from the CView derived class and setup our GLView class.
Note the call to the GetSafeWnd() method to get the HWND in the OnInitialUpdate() method.
The client area rectangle is obtained using the GetClientRect() method and then its dimensions are used to call the Resize() method to initially resize the GLView to fit to the client area of the window.

The OnDraw() method is tapped to call the RenderScene() method of our GLView and OnSize() method to handle the resizing of the GLView.

C++
CGLViewMFCMDIAppView::CGLViewMFCMDIAppView()
{
    // TODO: add construction code here
    m_pGLView = new CGLView();
}
void CGLViewMFCMDIAppView::OnInitialUpdate()
{
    CView::OnInitialUpdate();
    if(m_pGLView)
    {
        m_pGLView->SetWindow(GetSafeHwnd());
        m_pGLView->SetupGLContext(true);
        CRect rect;
        GetClientRect(&rect);
        m_pGLView->Resize(rect.Width(), rect.Height());
    }
}
void CGLViewMFCMDIAppView::OnDraw(CDC* /*pDC*/)
{
    CGLViewMFCMDIAppDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
    if(m_pGLView)
    {
        m_pGLView->RenderScene();
    }
}
void CGLViewMFCMDIAppView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    if(m_pGLView)
    {
        m_pGLView->Resize(cx, cy);
    }
}

MultiGL/MFCMDI.jpg

Now let us have a look at how we can use the GLView in an MFC dialog based application. Since we just need the HWND, we can use our class to render into almost any window or control. But mind you, you can't just render by getting the HWND alone if the paint event of the control is already handled by the control class. In that case, you just need to subclass the control and handle its paint event. The most suitable control in an MFC dialog is the picture control. Just place a Picture Control on the MFC dialog that you desire to render the OpenGL view. Create a variable for the control and you are all ready to use it in the dialog code to get the HWND and render the OpenGL view onto it. The suitable methods in the dialog class are OnInitDialog and OnPaint for creating the view, setting the window and rendering.

C++
BOOL CGLViewMFCDlgAppDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    // Add "About..." menu item to system menu.
    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);
    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }
    // Set the icon for this dialog. The framework does this automatically
    // when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE); // Set big icon
    SetIcon(m_hIcon, FALSE); // Set small icon
    // TODO: Add extra initialization here
    if(m_pGLView)
    {
        m_pGLView->SetWindow(m_cGLFrame.GetSafeHwnd());
        m_pGLView->SetupGLContext(true);
        CRect rect;
        m_cGLFrame.GetClientRect(&rect);
        m_pGLView->Resize(rect.Width(), rect.Height());
    }
    return TRUE; // return TRUE unless you set the focus to a control
}
void CGLViewMFCDlgAppDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting
        SendMessage(WM_ICONERASEBKGND, 
                reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;
        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
        if(m_pGLView)
        {
            m_pGLView->RenderScene();
        }
    }
}

Resizing of most dialogs does not happen from the application logic point of view unless your application has specific needs. In that case, for MFC dialogs you need to write additional code so that the control resizes with the dialog and then call the GLView::Resize() method. Subclassing of controls may be required in this case.

MultiGL/MFCDlg.jpg

UNIX/Linux

Now let us have a look at using our GLView class on UNIX/Linux. The most commonly used GUI toolkit on UNIX is Motif though Linux applications are mostly written in GTK and Qt which are cross platform. However, when different flavours of UNIX comes to one's mind and writing a native application on X, Motif is still the commercially preferred toolkit. It is also one of the oldest and most mature ones. So let us have a look at the code in Motif.

The most suitable widget on Motif is the DrawingArea widget of the widget class xmDrawingAreaWidgetClass. Since most Motif applications are written in plain C style, we have a static global variable of our GLView class. In the main function, we create a DrawingArea widget and then add the callbacks for the Expose event (equivalent to paint event on Windows) and the Resize event.

C++
drawing_area=XtCreateManagedWidget("drawing_area", xmDrawingAreaWidgetClass, form,al,ac);
XtAddCallback (drawing_area, XmNexposeCallback,
(XtCallbackProc)exposeCB, NULL);
XtAddCallback (drawing_area, XmNresizeCallback,
(XtCallbackProc)resizeCB, NULL);

Then we use our GLView's methods to set the window after retrieving the Display and Window from the DrawingArea widget.

C++
pGLView = new CGLView();
if(pGLView == NULL)
{
    cout << "Failed to create CGLView!" << endl;
    exit(0);
}
else
{
    Arg args[5];
    int n = 0;
    Dimension width, height;
    XtSetArg (args[n], XmNwidth, &width); n++;
    XtSetArg (args[n], XmNheight, &height); n++;
    XtGetValues (drawing_area, args, n);
    pGLView->SetWindow(XtDisplay(drawing_area), XtWindow(drawing_area));
    pGLView->SetupGLContext(true);
    pGLView->Resize(width, height);
}

Then we just implement the callback methods for Expose and Resize events.

C++
void exposeCB(Widget w,
    int client_data,
    XmDrawingAreaCallbackStruct *cd)
{
    if(pGLView != NULL)
    {
        pGLView->RenderScene();
    }
}
void resizeCB(Widget w,
    int client_data,
    XmDrawingAreaCallbackStruct *cd)
{
    Arg args[5];
    int n = 0;
    Dimension width, height;
    XtSetArg (args[n], XmNwidth, &width); n++;
    XtSetArg (args[n], XmNheight, &height); n++;
    XtGetValues (w, args, n);
    if(pGLView != NULL)
    {
        pGLView->Resize(width, height);
    }
}

That's it! Our GLView is rendering on the native Motif window!

MultiGL/motif.jpg

Cross Platform

GTK+ and Qt are some of the leading GUI toolkits available for writing cross platform GUI applications with the "code once build anywhere" way. One important thing that we must remember while using such toolkits is the fact that we are using them in the first place to have the code built on other platforms without having the need to modify the code. We must also understand that these toolkits have some platform specific functions and classes for us to handle platform specific code just like in our case where we need the HWND on Windows and Window on X. So let us look into how to use these toolkits along with their platform specific functions so that we can write the application on any one platform and make it suitable to be built directly on multiple platforms.

GTK+

We will be using the GtkDrawingArea widget to render our GLView and we wiil be handling the realize, configure and expose events to create our GLView, setup the window and resize it respectively. We will also be using the platform specific header inclusions. Note the inclusion of gdkwin32.h on Windows and gdkx.h on X. The code is pretty much self explanatory and you can easily see how the HWND is obtained for Windows and the Display and Window on X. One notable thing is the macro GTK_WIDGET_UNSET_FLAGS (widget, GTK_DOUBLE_BUFFERED) that we use to disable the built-in double buffering supported on the GtkDrawingArea widget that causes flickering of our GLView.

C++
#ifdef WIN32
#include <gdk/gdkwin32.h>
#else
#include <gdk/gdkx.h>
#endif

void
UpdateDrawingArea (GtkWidget * widget)
{
    GtkWidget *drw = lookup_widget (GTK_WIDGET (widget), "drawingarea1");
    gdk_window_invalidate_rect (GTK_WIDGET (drw)->window,
        &GTK_WIDGET (drw)->allocation, FALSE);
}
void
on_drawingarea1_realize (GtkWidget * widget, gpointer user_data)
{
    // Unset Double Buffering on DrawingArea to 
    // eliminate flickering of glview
    GTK_WIDGET_UNSET_FLAGS (widget, GTK_DOUBLE_BUFFERED);
    pGLView = new CGLView ();
    if (pGLView)
    {
        GdkWindow *gdkwin = GTK_WIDGET (widget)->window;
#ifdef WIN32
        glong hWnd;
        hWnd = (glong) gdk_win32_drawable_get_handle (gdkwin); // get HWND
        pGLView->SetWindow ((HWND) hWnd);
#else
        Display* pDisplay = gdk_x11_drawable_get_xdisplay(gdkwin);
        Window window = (Window) gdk_x11_drawable_get_xid(gdkwin);
        pGLView->SetWindow (pDisplay, window);
#endif
        pGLView->SetupGLContext (TRUE);
        gint w, h;
        gtk_widget_get_size_request (widget, &w, &h);
        pGLView->Resize (w, h);
    }
}
gboolean
on_drawingarea1_configure_event (GtkWidget * widget,
        GdkEventConfigure * event,
        gpointer user_data)
{
    if (pGLView)
    {
        pGLView->Resize (event->width, event->height);
    }
    return FALSE;
}
gboolean
on_drawingarea1_expose_event (GtkWidget * widget,
    GdkEventExpose * event, gpointer user_data)
{
    if (pGLView)
    {
        pGLView->RenderScene ();
        UpdateDrawingArea (GTK_WIDGET (widget));
    }
    return FALSE;
}

MultiGL/GTK.jpg

Qt

Qt is an object oriented C++ cross platform GUI toolkit that is becoming increasingly popular equally for desktop and mobile applications. Qt provides a rich set of widget classes and also a fully loaded OpenGL widget. However, if we have a powerful GLView class fully loaded with all functionality, we can easily port our code from other toolkits to Qt without having to rewrite our OpenGL code using the QGLWidget. To use our GLView in a Qt widget, we write a class derived from QWidget and override its paintEvent() and resizeEvent() methods. Since Qt provides a powerful Paint Engine that obscures our GLView, we need to disable it and also ask the QWidget to directly render to the screen. We disable the Paint Engine by overriding the paintEngine() method to return 0 from it and rendering to the screen directly is done by setting the widget attribute Qt::WA_PaintOnScreen. The constructor is the place where we create our GLView instance and set the window. the method winId() gets the HWND and Window on Windows and X respectively. On X, to get the Display, the QWidget class provides a method x11Info() that gets the QX11Info class object which in turn provides a static method display().

C++
#ifndef WIN32
#include <QX11Info>
#endif
GLFrame::GLFrame(QWidget *parent)
    : QWidget(parent)
{
    m_pGLView = new CGLView();
    if(m_pGLView)
    {
        setAttribute(Qt::WA_PaintOnScreen);
#ifdef WIN32
        m_pGLView->SetWindow((HWND)winId());
#else
        Display* pDisp = x11Info().display();
        Window wnd = winId();
        m_pGLView->SetWindow(pDisp, wnd);
#endif
        m_pGLView->SetupGLContext(true);
        m_pGLView->Resize(width(), height());
    }
}
QPaintEngine * GLFrame::paintEngine () const
{
    return 0;
}
void GLFrame::paintEvent(QPaintEvent* /*event*/)
{
    if(m_pGLView)
    {
        m_pGLView->RenderScene();
    }
}
void GLFrame::resizeEvent(QResizeEvent* event)
{
    if(m_pGLView)
    {
        QSize size = event->size();
        m_pGLView->Resize(size.width(), size.height());
    }
}

MultiGL/qt.jpg

We can very well use our GLView to render onto the QGLwidget too! Just override the initializeGL(), paintGL() and resizeGL() methods. No need to disable the double buffering or ask the widget to directly render to the screen. But then, the boolean argument to SetupGLContext() should be set to false so that it does not reset the pixel format already setup by the QGLWidget.

We have seen how to create an OpenGL view class that can be seamlessly used across different toolkits and platforms.

I hope you enjoyed this article and it makes a lot of sense to make the OpenGL component a flexible one in the software development world where OpenGL is becoming increasingly popular and strong.

The source code for the base code and projects on Windows and Linux has been provided.
Windows project is a VS2008 solution that encompasses the GLView DLL project, MFC MDI and Dialog examples, GTK and Qt examples. You need to have the windows version of GTK+ (I used version 2.12.9) and Qt (I used 4.6.3)

The Linux projects require Lesstif (Open Source Motif clone). I believe OpenMotif should also work. And of course, GTK+ and Qt if you want to build those examples too. The build is done using Autotools and is required to be installed.

The project folder contains a readme.txt file which explains how to setup, build and run the examples. It also has two convenience scripts to build and run the library and samples.

Points of Interest

I have successully used this paradigm also to create a COM component for the GLView class which not only works for different toolkits but also with different scripting languages like Tcl/Tk (using Tcom), Python (using comtypes) and VBScript. It also works for Windows Forms in C# and VB.NET using .NET/COM interoperability. But since it is a COM component, its use is restricted to the Windows platform only.

There are better ways to make a fully blown object oriented GLView class using the right Design Patterns, etc. But here, I am focusing on how to write the class in such a way that once you write the code, you need not make any changes to the files and can just compile it on any platform and make it available to any toolkit.

The beauty of this paradigm is that, you have already seen, setting up and rendering a basic OpenGL view requires only 4-5 lines of extra code on any toolkit.

History

  • Version 1.0

License

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