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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class SimpleGLXWindow : public BOGLGPWindow
{
public :
SimpleGLXWindow();
virtual ~SimpleGLXWindow();
bool create( int width, int height, int bpp, bool fullscreen);
void destroy();
void processEvents();
void attachExample(Example* example);
bool isRunning();
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;
bool m_isRunning;
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!).
1 2 3 4 5 6 | m_display = XOpenDisplay(0);
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:
1 | m_screenID = DefaultScreen(m_display);
|
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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;
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 ;
}
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
{
int attribs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
0
};
m_glContext = glXCreateContextAttribs(m_display, framebufferConfig, 0, true , &attribs[0]);
glXDestroyContext(m_display, gl2Context);
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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
|