// example how to set up modern OpenGL context with fallback to legacy context // compile: gcc xlib_opengl.c -lX11 -lGL -o xlib_opengl // set to 0 to create resizable window #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 // do you need depth buffer? #define WINDOW_DEPTH 1 // do you need stencil buffer? #define WINDOW_STENCIL 0 // use sRGB for color buffer #define WINDOW_SRGB 1 // do you need multisampling? // to disable set to 0, to enable set to 2, 4, 8, 16, ... #define WINDOW_MSAA 4 // do you need vsync? #define WINDOW_VSYNC 1 // keep this enabled when debugging #define USE_DEBUG_MODE 1 // replace this with your favorite assert() implementation #include #include #include #include #include #include "glext.h" // download from https://www.opengl.org/registry/api/GL/glext.h #include "glxext.h" // download from https://www.opengl.org/registry/api/GL/glxext.h // https://www.opengl.org/registry/specs/ARB/glx_create_context.txt // https://cgit.freedesktop.org/mesa/mesa/plain/docs/specs/MESA_swap_control.spec // https://www.opengl.org/registry/specs/SGI/swap_control.txt // https://www.opengl.org/registry/specs/EXT/swap_control.txt // https://www.opengl.org/registry/specs/EXT/glx_swap_control_tear.txt // https://www.opengl.org/registry/specs/ARB/framebuffer_sRGB.txt // https://www.opengl.org/registry/specs/ARB/multisample.txt // https://www.opengl.org/registry/specs/ARB/debug_output.txt #if USE_DEBUG_MODE #define GL_CHECK(x) do \ { \ x; \ GLenum err = glGetError(); \ assert(err == GL_NO_ERROR); \ } while (0) #else #define GL_CHECK(x) x #endif // called after context is set up (only once) // for example, load GL extensions here & set up GL state static void RenderInit(void) { GL_CHECK( glClearColor(100.f/255.f, 149.f/255.f, 237.f/255.f, 1.f) ); } // called before render is destroyed static void RenderDone(void) { } // called when window is resized static void RenderResize(unsigned width, unsigned height) { GL_CHECK( glViewport(0, 0, width, height) ); } // called every frame before swapping buffers static void RenderFrame(void) { GL_CHECK( glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) ); // use legacy GL for drawing to display a triangle glBegin(GL_TRIANGLES); glColor3f(1.f, 0.f, 0.f); glVertex2f(0.f, 0.5f); glColor3f(0.f, 1.f, 0.f); glVertex2f(0.5f, -0.5f); glColor3f(0.f, 0.f, 1.f); glVertex2f(-0.5f, -0.5f); GL_CHECK( glEnd() ); } #if USE_DEBUG_MODE static void APIENTRY OpenGLDebugCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void* user) { fprintf(stderr, "%s\n", message); if (severity >= GL_DEBUG_SEVERITY_LOW_ARB && severity <= GL_DEBUG_SEVERITY_HIGH_ARB) { assert(0); } } #endif static int StringsAreEqual(const char* src, const char* dst, size_t dstlen) { while (*src && dstlen-- && *dst) { if (*src++ != *dst++) { return 0; } } return (dstlen && *src == *dst) || (!dstlen && *src == 0); } int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "XOpenDisplay failed!\n"); } else { int glx_ARB_create_context = 0; int glx_ARB_multisample = 0; int glx_ARB_framebuffer_sRGB = 0; int glx_SGI_swap_control = 0; int glx_MESA_swap_control = 0; int glx_EXT_swap_control = 0; int glx_EXT_swap_control_tear = 0; const char* glx = glXQueryExtensionsString(display, DefaultScreen(display)); if (!glx) { fprintf(stderr, "glXQueryExtensionsString failed!\n"); } else { const char* start = glx; for (;;) { while (*glx != 0 && *glx != ' ') { glx++; } size_t length = glx - start; if (StringsAreEqual("GLX_ARB_create_context", start, length)) { glx_ARB_create_context = 1; } else if (StringsAreEqual("GLX_ARB_multisample", start, length)) { glx_ARB_multisample = 1; } else if (StringsAreEqual("GLX_ARB_framebuffer_sRGB", start, length)) { glx_ARB_framebuffer_sRGB = 1; } else if (StringsAreEqual("GLX_SGI_swap_control", start, length)) { glx_SGI_swap_control = 1; } else if (StringsAreEqual("GLX_MESA_swap_control", start, length)) { glx_MESA_swap_control = 1; } else if (StringsAreEqual("GLX_EXT_swap_control", start, length)) { glx_EXT_swap_control = 1; } else if (StringsAreEqual("GLX_EXT_swap_control_tear", start, length)) { glx_EXT_swap_control_tear = 1; } if (*glx == 0) { break; } glx++; start = glx; } } int attr[32]; int* p = attr; *p++ = GLX_X_VISUAL_TYPE; *p++ = GLX_TRUE_COLOR; *p++ = GLX_DOUBLEBUFFER; *p++ = True; *p++ = GLX_RED_SIZE; *p++ = 8; *p++ = GLX_GREEN_SIZE; *p++ = 8; *p++ = GLX_BLUE_SIZE; *p++ = 8; *p++ = GLX_DEPTH_SIZE; *p++ = WINDOW_DEPTH ? 24 : 0; *p++ = GLX_STENCIL_SIZE; *p++ = WINDOW_STENCIL ? 8 : 0; if (WINDOW_SRGB && glx_ARB_framebuffer_sRGB) { *p++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB; *p++ = True; } if (WINDOW_MSAA && glx_ARB_multisample) { *p++ = GLX_SAMPLE_BUFFERS_ARB; *p++ = 1; *p++ = GLX_SAMPLES_ARB; *p++ = WINDOW_MSAA; } *p++ = 0; int count; GLXFBConfig* config = glXChooseFBConfig(display, DefaultScreen(display), attr, &count); if (!config || count == 0) { fprintf(stderr, "glXChooseFBConfig failed!\n"); } else { XVisualInfo* info = glXGetVisualFromFBConfig(display, config[0]); if (!info) { fprintf(stderr, "glXGetVisualFromFBConfig failed!\n"); } else { Window root = DefaultRootWindow(display); Colormap cmap = XCreateColormap(display, root, info->visual, AllocNone); if (!cmap) { fprintf(stderr, "XCreateColormap failed!\n"); } else { XSetWindowAttributes swa; swa.colormap = cmap; swa.event_mask = StructureNotifyMask; Window window = XCreateWindow( display, root, 0, 0, WINDOW_WIDTH ? WINDOW_WIDTH : 800, WINDOW_HEIGHT ? WINDOW_HEIGHT : 450, 0, info->depth, InputOutput, info->visual, CWColormap | CWEventMask, &swa); if (!window) { fprintf(stderr, "XCreateWindow failed!\n"); } else { if (WINDOW_WIDTH && WINDOW_HEIGHT) { XSizeHints* hints = XAllocSizeHints(); if (!hints) { fprintf(stderr, "XAllocSizeHints failed!\n"); } else { hints->flags |= PMinSize | PMaxSize; hints->min_width = hints->max_width = WINDOW_WIDTH; hints->min_height = hints->max_height = WINDOW_HEIGHT; XSetWMNormalHints(display, window, hints); XFree(hints); } } Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1); XStoreName(display, window, "OpenGL window"); XMapWindow(display, window); GLXContext ctx = NULL; if (glx_ARB_create_context) { int cattr[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 0, #if USE_DEBUG_MODE GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, #endif GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, None, }; PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB( (const GLubyte*)"glXCreateContextAttribsARB"); ctx = glXCreateContextAttribsARB(display, config[0], 0, True, cattr); if (!ctx) { fprintf(stderr, "Failed to create modern OpenGL context, trying legacy context!\n"); } } if (!ctx) { ctx = glXCreateContext(display, info, NULL, GL_TRUE); } if (!ctx) { fprintf(stderr, "glXCreateContext failed!\n"); } else { if (!glXMakeCurrent(display, window, ctx)) { fprintf(stderr, "glXMakeCurrent failed!\n"); } else { if (WINDOW_VSYNC) { if (glx_EXT_swap_control) { PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB( (const GLubyte*)"glXSwapIntervalEXT"); glXSwapIntervalEXT(display, window, glx_EXT_swap_control_tear ? -1 : 1); } else if (glx_MESA_swap_control) { PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddressARB( (const GLubyte*)"glXSwapIntervalMESA"); if (glXSwapIntervalMESA(1)) { fprintf(stderr, "glXSwapIntervalMESA failed!\n"); } } else if (glx_SGI_swap_control) { PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB( (const GLubyte*)"glXSwapIntervalSGI"); if (glXSwapIntervalSGI(1)) { fprintf(stderr, "glXSwapIntervalSGI failed!\n"); } } } #if USE_DEBUG_MODE const GLubyte* ext; GL_CHECK( ext = glGetString(GL_EXTENSIONS) ); if (ext) { const GLubyte* start = ext; for (;;) { while (*ext != 0 && *ext != ' ') { ext++; } size_t length = ext - start; if (StringsAreEqual("GL_ARB_debug_output", (const char*)start, length)) { PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC)glXGetProcAddressARB((const GLubyte*)"glDebugMessageCallbackARB"); GL_CHECK( glDebugMessageCallbackARB(OpenGLDebugCallback, NULL) ); GL_CHECK( glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) ); break; } if (*ext == 0) { break; } ext++; start = ext; } } #endif if (WINDOW_MSAA && glx_ARB_multisample) { GL_CHECK( glEnable(GL_MULTISAMPLE_ARB) ); } RenderInit(); for (;;) { if (XPending(display)) { XEvent event; XNextEvent(display, &event); if (event.type == ConfigureNotify) { RenderResize(event.xconfigure.width, event.xconfigure.height); } else if (event.type == ClientMessage) { if ((Atom)event.xclient.data.l[0] == WM_DELETE_WINDOW) { break; } } continue; } RenderFrame(); glXSwapBuffers(display, window); } RenderDone(); glXMakeCurrent(display, None, NULL); } glXDestroyContext(display, ctx); } XDestroyWindow(display, window); } XFreeColormap(display, cmap); } XFree(info); } XFree(config); } XCloseDisplay(display); } return 0; }