SimpleGLXWindow Class

 

Introduction

After OpenGL Game Programming - Second Edition was released, it became apparent that the SimpleGLXWindow class that was used in the final chapter wasn't documented in the book! So, for those of you on Linux who'd like a quick breakdown, here it is!

Class Structure

The class structure for SimpleGLXWindow is very similar to the Win32 version. Like the Win32 version it inherits the BOGLGPWindow base class and so implements the same virtual methods. Here's the class definition for reference:

 

class SimpleGLXWindow : public BOGLGPWindow
{
public:
    SimpleGLXWindow(); //default constructor
    virtual ~SimpleGLXWindow();

    bool create(int width, int height, int bpp, bool fullscreen);
    void destroy();
    void processEvents();
    void attachExample(Example* example);

    bool isRunning(); //Is the window running?

    void swapBuffers() { glXSwapBuffers(m_display, m_XWindow); }

    float getElapsedSeconds();

    KeyboardInterface* getKeyboard() const { return m_keyboard; }
    MouseInterface* getMouse() const { return m_mouse; }
private:
    Example* m_example; //A link to the example program
    bool m_isRunning; //Is the window still running?


    Example* getAttachedExample() { return m_example; }

    unsigned int m_lastTime;

    Display* m_display;
    Window m_XWindow;
    GLXContext m_glContext;
    XF86VidModeModeInfo m_XF86DeskMode;
    XSetWindowAttributes m_XSetAttr;
    int m_screenID;

    bool m_isFullscreen;
    unsigned int m_width;
    unsigned int m_height;
    unsigned int m_bpp;

    bool m_GL3Supported;

    KeyboardInterface* m_keyboard;
    MouseInterface* m_mouse;
};

 

 

Window Creation

The most important and complicated part of the window class is the code that creates the OpenGL window. The first thing we do is get a handle on the default display using XOpenDisplay, by passing in a zero (or NULL) this will grab whichever display is set in the DISPLAY environment variable (which is normally the one you are using to view your desktop!).

    m_display = XOpenDisplay(0);  //Open default display
    if (m_display == NULL)
    {
        std::cerr << "Could not open the display" << std::endl;
        return false;
    }
 

So, we've grabbed the default display, and if there was an error we've logged it and returned false to indicate that window creation failed. Next, we get a handle identifying the default screen for the display:

    m_screenID = DefaultScreen(m_display); //Get the default screen id
 

Now, we get a list of the available display modes, and see if one matches what we asked for. If not we bail out with an error:

    XF86VidModeModeInfo **modes;
    if (!XF86VidModeGetAllModeLines(m_display, m_screenID, &modeNum, &modes))
    {
        std::cerr << "Could not query the video modes" << std::endl;
        return false;
    }

    int bestMode = -1;
    for (int i = 0; i < modeNum; i++)
    {
        if ((modes[i]->hdisplay == width) &&
            (modes[i]->vdisplay == height))
        {
            bestMode = i;
        }
    }

    if (bestMode == -1)
    {
        std::cerr << "Could not find a suitable graphics mode" << std::endl;
        return false;
    }
 

After we've stored the best matching display mode, we request for a double buffered window with a 16 bit depthbuffer:

    int doubleBufferedAttribList [] = {
        GLX_RGBA, GLX_DOUBLEBUFFER,
        GLX_RED_SIZE, 4,
        GLX_GREEN_SIZE, 4,
        GLX_BLUE_SIZE, 4,
        GLX_DEPTH_SIZE, 16,
        None
    };

    XVisualInfo* vi = NULL;
    //Attempt to create a double buffered window
    vi = glXChooseVisual(m_display, m_screenID, doubleBufferedAttribList);

    if (vi == NULL)
    {
        std::cerr << "Could not create a double buffered window" << std::endl;
        return false;
    }
 

The next step is to create an OpenGL 2.1 context, so we can in-turn request a GL3 one. These are the same steps we have to take as on Windows:

    //Create a GL 2.1 context
    GLXContext gl2Context = glXCreateContext(m_display, vi, 0, GL_TRUE);

    if (gl2Context == NULL)
    {
        std::cerr << "Could not create a GL 2.1 context, please check your graphics drivers" << std::endl;
        return false;
    }

    //Get a pointer to the GL 3.0 context creation
    PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribs = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((GLubyte*)"glXCreateContextAttribsARB");

    if (glXCreateContextAttribs == NULL)
    {
        std::cerr << "OpenGL 3.0 is not supported, falling back to 2.1" << std::endl;
        m_glContext = gl2Context;
        m_GL3Supported = false;
    }
    else
    {
        //Create a GL 3.0 context

        int attribs[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB, 3,//we want a 3.0 context
            GLX_CONTEXT_MINOR_VERSION_ARB, 0,
            0 //zero indicates the end of the array
        };

        m_glContext = glXCreateContextAttribs(m_display, framebufferConfig, 0, true, &attribs[0]);
        glXDestroyContext(m_display, gl2Context); //We can destroy the GL 2.0 context once the 3.0 one has bene created
        m_GL3Supported = true;
    }
 

If OpenGL 3.0 isn't supported, we set a flag so we can use the fallback 2.1 shaders.

Now we have enough information to actually create the window. Remember we stored the best display mode we could find? We use that below after setting up some window configuration settings:

    Colormap cmap = XCreateColormap(m_display, RootWindow(m_display, vi->screen),vi->visual, AllocNone);
    m_XSetAttr.colormap = cmap;
    m_XSetAttr.border_pixel = 0;
    m_XSetAttr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
                                StructureNotifyMask;

    m_XSetAttr.override_redirect = False;

    unsigned long windowAttributes = CWBorderPixel | CWColormap | CWEventMask;

    if (fullscreen)
    {
        windowAttributes = CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;

        XF86VidModeSwitchToMode(m_display, m_screenID, modes[bestMode]);
        XF86VidModeSetViewPort(m_display, m_screenID, 0, 0);
        m_XSetAttr.override_redirect = True;
    }

    m_XWindow = XCreateWindow(m_display, RootWindow(m_display, vi->screen),
                                  0, 0, width, height, 0, vi->depth, InputOutput, vi->visual,
                                  CWBorderPixel | CWColormap | CWEventMask, &m_XSetAttr);
 

Finally, we set the window title, and if we are fullscreen we grab the cursor:

    if (fullscreen)
    {
        XWarpPointer(m_display, None, m_XWindow, 0, 0, 0, 0, 0, 0);
        XMapRaised(m_display, m_XWindow);
        XGrabKeyboard(m_display, m_XWindow, True, GrabModeAsync, GrabModeAsync, CurrentTime);
        XGrabPointer(m_display, m_XWindow, True, ButtonPressMask,
                     GrabModeAsync, GrabModeAsync, m_XWindow, None, CurrentTime);

        m_isFullscreen = true;
    }
    else
    {
        Atom wmDelete = XInternAtom(m_display, "WM_DELETE_WINDOW", True);
        XSetWMProtocols(m_display, m_XWindow, &wmDelete, 1);
        XSetStandardProperties(m_display, m_XWindow, title.c_str(), None, NULL, NULL, 0, NULL);
        XMapRaised(m_display, m_XWindow);
    }


    XFree(modes);
 

The last line above releases the memory for the display modes that we searched earlier.

Destroying the Window

This is pretty straightforward, here's the code:

void SimpleGLXWindow::destroy()
{
    m_mouse->showCursor(true);
    if (m_glContext)
    {
        glXMakeCurrent(m_display, None, NULL);
        glXDestroyContext(m_display, m_glContext);
        m_glContext = NULL;
    }

    if (m_isFullscreen)
    {
        XF86VidModeSwitchToMode(m_display, m_screenID, &m_XF86DeskMode);
        XF86VidModeSetViewPort(m_display, m_screenID, 0, 0);
    }

    XCloseDisplay(m_display);
}
 

Basically, we make sure that the cursor is visible, then we destroy the OpenGL context and finally switch the display mode back when we are done. We release our handle on the display and the window is completely destroyed.

If you have any further questions, either about this article or the book itself, either let me know on the forums or poke me on the Twitters.

Support this Author