Skip to content

Instantly share code, notes, and snippets.

@JustSlavic
Created May 6, 2026 06:59
Show Gist options
  • Select an option

  • Save JustSlavic/fa9093cbe46782df4641b7d86c37494d to your computer and use it in GitHub Desktop.

Select an option

Save JustSlavic/fa9093cbe46782df4641b7d86c37494d to your computer and use it in GitHub Desktop.
Simple single file Linux X11 OpenGL example
#include <stdio.h>
#include <stdint.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <GL/gl.h>
#include <GL/glx.h>
typedef void glXSwapIntervalEXTType(Display *display, GLXDrawable drawable, int interval);
static glXSwapIntervalEXTType *glXSwapIntervalEXT;
typedef GLXContext glXCreateContextAttribsARBType(Display*, GLXFBConfig, GLXContext, Bool, const int*);
static glXCreateContextAttribsARBType *glXCreateContextAttribsARB;
// Keyboard keycode
#define KEYCODE_ESC 9
typedef int bool;
#define true 1
#define false 0
typedef void glGenBuffersType(GLsizei n, GLuint *buffers);
typedef void glBindBufferType(GLenum target, GLuint buffer);
typedef void glBufferDataType(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
typedef void glGenVertexArraysType(GLsizei n, GLuint *arrays);
typedef void glBindVertexArrayType(GLuint array);
typedef void glVertexAttribPointerType(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
typedef void glEnableVertexAttribArrayType(GLuint index);
typedef GLuint glCreateShaderType(GLenum shaderType);
typedef void glShaderSourceType(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);
typedef void glCompileShaderType(GLuint shader);
typedef GLuint glCreateProgramType(void);
typedef void glAttachShaderType(GLuint program, GLuint shader);
typedef void glDetachShaderType(GLuint program, GLuint shader);
typedef void glLinkProgramType(GLuint program);
typedef void glUseProgramType(GLuint program);
typedef void glGetShaderivType(GLuint shader, GLenum pname, GLint *params);
static glGenBuffersType *glGenBuffers;
static glBindBufferType *glBindBuffer;
static glBufferDataType *glBufferData;
static glGenVertexArraysType *glGenVertexArrays;
static glBindVertexArrayType *glBindVertexArray;
static glVertexAttribPointerType *glVertexAttribPointer;
static glEnableVertexAttribArrayType *glEnableVertexAttribArray;
static glCreateShaderType *glCreateShader;
static glShaderSourceType *glShaderSource;
static glCompileShaderType *glCompileShader;
static glCreateProgramType *glCreateProgram;
static glAttachShaderType *glAttachShader;
static glDetachShaderType *glDetachShader;
static glLinkProgramType *glLinkProgram;
static glUseProgramType *glUseProgram;
static glGetShaderivType *glGetShaderiv;
void initialize_opengl()
{
#define STRINGIFY_(X) #X
#define STRINGIFY(X) STRINGIFY_(X)
#define GLX_GET_PROC_ADDRESS(NAME) \
NAME = (NAME##Type *) glXGetProcAddress((GLubyte const *) STRINGIFY(NAME)); \
GLX_GET_PROC_ADDRESS(glGenBuffers);
GLX_GET_PROC_ADDRESS(glBindBuffer);
GLX_GET_PROC_ADDRESS(glBufferData);
GLX_GET_PROC_ADDRESS(glGenVertexArrays);
GLX_GET_PROC_ADDRESS(glBindVertexArray);
GLX_GET_PROC_ADDRESS(glVertexAttribPointer);
GLX_GET_PROC_ADDRESS(glEnableVertexAttribArray);
GLX_GET_PROC_ADDRESS(glCreateShader);
GLX_GET_PROC_ADDRESS(glShaderSource);
GLX_GET_PROC_ADDRESS(glCompileShader);
GLX_GET_PROC_ADDRESS(glCreateProgram);
GLX_GET_PROC_ADDRESS(glAttachShader);
GLX_GET_PROC_ADDRESS(glDetachShader);
GLX_GET_PROC_ADDRESS(glLinkProgram);
GLX_GET_PROC_ADDRESS(glUseProgram);
GLX_GET_PROC_ADDRESS(glGetShaderiv);
}
// Here should be the function that parses extension string and returns
// an array of c-strings for every supported extension. Then check for equallity
// should be straight-forward.
// For now, consider all needed extensions are present on a dev machine.
bool is_opengl_extension_supported(const char *glx_extensions, const char *extension)
{
printf("GLX extensions string: \"%s\"\n", glx_extensions);
return true;
}
static bool is_running;
typedef struct
{
Window x_window;
Display *x_display;
Colormap x_colormap;
GLXContext glx_context;
} glx_window;
void process_pending_messages(glx_window *glx)
{
XEvent event;
while (XPending(glx->x_display))
{
XNextEvent(glx->x_display, &event);
switch (event.type)
{
case ButtonPress:
case ButtonRelease:
break;
case KeyPress:
case KeyRelease:
{
bool is_down = (event.type == KeyPress);
if (is_down && event.xkey.keycode == KEYCODE_ESC)
{
is_running = false;
}
}
break;
case ClientMessage:
is_running = false;
break;
}
}
}
glx_window glx_create_window(int width, int height)
{
Display *x_display = XOpenDisplay(NULL);
// Get a matching FB config
int visual_attribs[] =
{
GLX_X_RENDERABLE , True,
GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT,
GLX_RENDER_TYPE , GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR,
GLX_RED_SIZE , 8,
GLX_GREEN_SIZE , 8,
GLX_BLUE_SIZE , 8,
GLX_ALPHA_SIZE , 8,
GLX_DEPTH_SIZE , 24,
GLX_STENCIL_SIZE , 8,
GLX_DOUBLEBUFFER , true,
//GLX_SAMPLE_BUFFERS , 1,
//GLX_SAMPLES , 4,
None
};
// FBConfigs were added in GLX version 1.3.
int glx_version_major = 0;
int glx_version_minor = 0;
glXQueryVersion(x_display, &glx_version_major, &glx_version_minor);
printf("GLX version %d.%d\n", glx_version_major, glx_version_minor);
int glx_framebuffer_config_count = 0;
GLXFBConfig* x_framebuffer_configs = glXChooseFBConfig(x_display, DefaultScreen(x_display), visual_attribs, &glx_framebuffer_config_count);
// Pick the FB config/visual with the most samples per pixel.
int desired_num_sample_buffers = 0;
int desired_num_samples = 0;
XVisualInfo *x_visual_info = NULL;
GLXFBConfig glx_framebuffer_config = {};
for (int i = 0; i < glx_framebuffer_config_count; ++i)
{
XVisualInfo *x_vi = glXGetVisualFromFBConfig(x_display, x_framebuffer_configs[i]);
if (x_vi)
{
int sample_buffers, samples;
glXGetFBConfigAttrib(x_display, x_framebuffer_configs[i], GLX_SAMPLE_BUFFERS, &sample_buffers);
glXGetFBConfigAttrib(x_display, x_framebuffer_configs[i], GLX_SAMPLES, &samples);
if ((sample_buffers == desired_num_sample_buffers) && (samples == desired_num_samples))
{
x_visual_info = x_vi;
glx_framebuffer_config = x_framebuffer_configs[i];
}
else
{
XFree(x_vi);
}
}
}
// Be sure to free the FBConfig list allocated by glXChooseFBConfig().
XFree(x_framebuffer_configs);
Colormap x_colormap = XCreateColormap(x_display, RootWindow(x_display, x_visual_info->screen), x_visual_info->visual, AllocNone);
XSetWindowAttributes x_window_attributes;
x_window_attributes.colormap = x_colormap;
x_window_attributes.background_pixmap = 0;
x_window_attributes.border_pixel = 0;
x_window_attributes.event_mask =
ExposureMask |
KeyPressMask |
KeyReleaseMask |
PointerMotionMask |
ButtonPressMask |
StructureNotifyMask |
ButtonReleaseMask;
Window x_window = XCreateWindow(
x_display, RootWindow(x_display, x_visual_info->screen),
0, 0,
width, height,
0,
x_visual_info->depth,
InputOutput,
x_visual_info->visual,
CWBorderPixel|CWColormap|CWEventMask,
&x_window_attributes);
// Done with the visual info data.
XFree(x_visual_info);
XStoreName(x_display, x_window, "Spear");
// Process window close event through event handler so XNextEvent does not fail.
Atom del_window = XInternAtom(x_display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(x_display, x_window, &del_window, 1);
// Display the window
XMapWindow(x_display, x_window);
// Get the default screen's GLX extension list.
const char *glx_extensions = glXQueryExtensionsString(x_display, DefaultScreen(x_display));
GLX_GET_PROC_ADDRESS(glXCreateContextAttribsARB);
GLX_GET_PROC_ADDRESS(glXSwapIntervalEXT);
GLXContext glx_context;
bool is_arb_create_context_supported = is_opengl_extension_supported(glx_extensions, "GLX_ARB_create_context");
if (is_arb_create_context_supported && glXCreateContextAttribsARB)
{
// If it does fail, try to get a GL 4.0 context!
int context_attribs[] =
{
GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
//GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0
};
glx_context = glXCreateContextAttribsARB(x_display, glx_framebuffer_config, 0, True, context_attribs);
// Sync to ensure any errors generated are processed.
XSync(x_display, False);
}
else
{
glx_context = glXCreateNewContext(x_display, glx_framebuffer_config, GLX_RGBA_TYPE, 0, True);
}
glXMakeCurrent(x_display, x_window, glx_context);
glx_window result = {};
result.x_window = x_window;
result.x_display = x_display;
result.x_colormap = x_colormap;
result.glx_context = glx_context;
return result;
}
void glx_swap_interval(glx_window *window, int interval)
{
GLXDrawable x_drawable = glXGetCurrentDrawable();
if (x_drawable)
{
glXSwapIntervalEXT(window->x_display, x_drawable, interval);
}
}
#define GLSL_VERSION(VERSION) "#version " STRINGIFY(VERSION) "\n"
#define GLSL(...) #__VA_ARGS__
char const *vertex_shader_text =
GLSL_VERSION(410)
GLSL(
layout (location = 0) in vec2 vertex_position;
layout (location = 1) in vec3 vertex_color;
out vec4 fragment_color;
void main()
{
fragment_color = vec4(vertex_color, 1.0);
gl_Position = vec4(vertex_position.x, vertex_position.y, 0.0, 1.0);
}
);
char const *fragment_shader_text =
GLSL_VERSION(410)
GLSL(
in vec4 fragment_color;
out vec4 result_color;
void main()
{
result_color = fragment_color;
}
);
int main(int argc, char **argv)
{
int display_width = 800;
int display_height = 600;
glx_window glx = glx_create_window(display_width, display_height);
initialize_opengl();
glClearColor(0.f, 0.f, 0.f, 1.f);
glx_swap_interval(&glx, 1);
uint32_t vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader_id, 1, (char const **) &vertex_shader_text, (int const *) NULL);
glCompileShader(vertex_shader_id);
int successful;
glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &successful);
uint32_t fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader_id, 1, (char const **) &fragment_shader_text, (int const *) NULL);
glCompileShader(fragment_shader_id);
glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &successful);
int shader_id = glCreateProgram();
glAttachShader(shader_id, vertex_shader_id);
glAttachShader(shader_id, fragment_shader_id);
glLinkProgram(shader_id);
float vertices[] =
{
// positions // colors
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f, 1.0f
};
uint32_t vao_id, vbo_id;
glGenBuffers(1, &vbo_id);
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao_id);
glBindVertexArray(vao_id);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (2 * sizeof(float)));
is_running = true;
while (is_running)
{
process_pending_messages(&glx);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glUseProgram(shader_id);
glBindVertexArray(vao_id);
glDrawArrays(GL_TRIANGLES, 0, 6);
glXSwapBuffers(glx.x_display, glx.x_window);
}
glXMakeCurrent(glx.x_display, 0, 0);
glXDestroyContext(glx.x_display, glx.glx_context);
XDestroyWindow(glx.x_display, glx.x_window);
XFreeColormap(glx.x_display, glx.x_colormap);
XCloseDisplay(glx.x_display);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment