gitlab_snippets/mmozeiko/xlib_opengl.c

434 lines
16 KiB
C

// 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 <assert.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glx.h>
#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;
}