Skip to content

Instantly share code, notes, and snippets.

@valignatev
Last active July 19, 2026 23:57
Show Gist options
  • Select an option

  • Save valignatev/60fdd91fefabd131a0e53fe2e3ef0ec7 to your computer and use it in GitHub Desktop.

Select an option

Save valignatev/60fdd91fefabd131a0e53fe2e3ef0ec7 to your computer and use it in GitHub Desktop.
setting up and using modern OpenGL 4.5 core context on X11 with EGL and xcb, without xlib or glx
// This is @mmozeiko's libx + EGL example ported to xcb + EGL
// Original code: https://gist.github.com/mmozeiko/911347b5e3d998621295794e0ba334c4
// It requires EGL_EXT_platform_xcb extension, so if your system is circa the end of 2021 or newer you should be fine.
// Also, I don't know if it works on nvidia. LMK if you test it
// example how to set up OpenGL core context on X11 with EGL
// and use basic functionality of OpenGL 4.5 version
// to compile on Ubuntu first install following packages: build-essential libxcb1-dev libgl-dev libegl-dev, then run:
// gcc xcb_opengl.c -o xcb_opengl -lm -lxcb -lEGL
// On Arch that would be (I'm not sure if it's the full list): base-devel libxcb libglvnd
// important extension functionality used here:
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_platform_xcb.txt
// (4.3) KHR_debug: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt
// (4.5) ARB_direct_state_access: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_direct_state_access.txt
// (4.1) ARB_separate_shader_objects: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_separate_shader_objects.txt
// (4.2) ARB_shading_language_420pack: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_420pack.txt
// (4.3) ARB_explicit_uniform_location: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_explicit_uniform_location.txt
// #define EGL_EGLEXT_PROTOTYPES
#include <xcb/xcb.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/glcorearb.h>
#include <time.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
// replace this with your favorite Assert() implementation
#include <assert.h>
#define Assert(cond) assert(cond)
// This is needed to get EGL native display from xcb connection
// eglGetDisplay(EGL_DEFAULT_DISPLAY) and eglGetDisplay(conn) work on my maching (Intel graphics),
// but this makes EGL guess what kind of pointer you pass to it, so I guess might not work in some cases.
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
// eglCreateWindowSurface works on Intel and AMD mesa drivers, but seems like eglCreatePlatformWindowSurfaceEXT
// is more portable and can work better on Nvidia???
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
// make sure you use functions that are valid for selected GL version (specified when context is created)
#define GL_FUNCTIONS(X) \
X(PFNGLENABLEPROC, glEnable ) \
X(PFNGLDISABLEPROC, glDisable ) \
X(PFNGLBLENDFUNCPROC, glBlendFunc ) \
X(PFNGLVIEWPORTPROC, glViewport ) \
X(PFNGLCLEARCOLORPROC, glClearColor ) \
X(PFNGLCLEARPROC, glClear ) \
X(PFNGLDRAWARRAYSPROC, glDrawArrays ) \
X(PFNGLCREATEBUFFERSPROC, glCreateBuffers ) \
X(PFNGLNAMEDBUFFERSTORAGEPROC, glNamedBufferStorage ) \
X(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray ) \
X(PFNGLCREATEVERTEXARRAYSPROC, glCreateVertexArrays ) \
X(PFNGLVERTEXARRAYATTRIBBINDINGPROC, glVertexArrayAttribBinding ) \
X(PFNGLVERTEXARRAYVERTEXBUFFERPROC, glVertexArrayVertexBuffer ) \
X(PFNGLVERTEXARRAYATTRIBFORMATPROC, glVertexArrayAttribFormat ) \
X(PFNGLENABLEVERTEXARRAYATTRIBPROC, glEnableVertexArrayAttrib ) \
X(PFNGLCREATESHADERPROGRAMVPROC, glCreateShaderProgramv ) \
X(PFNGLGETPROGRAMIVPROC, glGetProgramiv ) \
X(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog ) \
X(PFNGLGENPROGRAMPIPELINESPROC, glGenProgramPipelines ) \
X(PFNGLUSEPROGRAMSTAGESPROC, glUseProgramStages ) \
X(PFNGLBINDPROGRAMPIPELINEPROC, glBindProgramPipeline ) \
X(PFNGLPROGRAMUNIFORMMATRIX2FVPROC, glProgramUniformMatrix2fv ) \
X(PFNGLBINDTEXTUREUNITPROC, glBindTextureUnit ) \
X(PFNGLCREATETEXTURESPROC, glCreateTextures ) \
X(PFNGLTEXTUREPARAMETERIPROC, glTextureParameteri ) \
X(PFNGLTEXTURESTORAGE2DPROC, glTextureStorage2D ) \
X(PFNGLTEXTURESUBIMAGE2DPROC, glTextureSubImage2D ) \
X(PFNGLDEBUGMESSAGECALLBACKPROC, glDebugMessageCallback )
#define X(type, name) static type name;
GL_FUNCTIONS(X)
#undef X
#define STR2(x) #x
#define STR(x) STR2(x)
static void FatalError(const char* message)
{
fprintf(stderr, "%s\n", message);
char cmd[1024];
snprintf(cmd, sizeof(cmd), "zenity --error --no-wrap --text=\"%s\"", message);
system(cmd);
exit(0);
}
#ifndef NDEBUG
static void APIENTRY DebugCallback(
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_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM)
{
assert(!"OpenGL API usage error! Use debugger to examine call stack!");
}
}
#endif
int main()
{
xcb_connection_t *conn;
conn = xcb_connect(NULL, NULL);
if (!conn || xcb_connection_has_error(conn))
{
fprintf(stderr, "Couldn't connect to X server: error %d\n",
conn ? xcb_connection_has_error(conn) : -1);
return 1;
}
xcb_generic_error_t *error;
xcb_void_cookie_t cookie;
xcb_screen_t *screen =
xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
xcb_window_t window = xcb_generate_id(conn);
uint32_t attributes[3] = {
0,
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
XCB_GRAVITY_STATIC
};
int width = 1280;
int height = 720;
cookie = xcb_create_window_checked(conn, XCB_COPY_FROM_PARENT,
window, screen->root,
0, 0, width, height, 1,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
screen->root_visual,
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_BIT_GRAVITY,
attributes);
if ((error = xcb_request_check(conn, cookie)) != NULL) {
fprintf(stderr, "Couldn't create X window: error %d\n", error);
return 1;
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window,
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
13, "OpenGL Window");
xcb_intern_atom_cookie_t protocols_cookie = xcb_intern_atom(conn, 1, 12, "WM_PROTOCOLS");
xcb_intern_atom_cookie_t delete_cookie = xcb_intern_atom(conn, 0, 16, "WM_DELETE_WINDOW");
xcb_intern_atom_reply_t* wm_protocols = xcb_intern_atom_reply(conn, protocols_cookie, 0);
xcb_intern_atom_reply_t* delete_window = xcb_intern_atom_reply(conn, delete_cookie, 0);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, (*wm_protocols).atom, 4, 32, 1, &(*delete_window).atom);
GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
// initialize EGL
EGLDisplay display;
{
display = GetPlatformDisplayEXT(EGL_PLATFORM_XCB_EXT, conn,
(const EGLint[]){
EGL_PLATFORM_XCB_SCREEN_EXT,
0, // This is a screenp that you can get from 2nd argument of xcb_connect
EGL_NONE,
});
Assert(display != EGL_NO_DISPLAY && "Failed to get EGL display");
EGLint major, minor;
if (!eglInitialize(display, &major, &minor))
{
FatalError("Cannot initialize EGL display");
}
if (major < 1 || (major == 1 && minor < 5))
{
FatalError("EGL version 1.5 or higher required");
}
}
// choose OpenGL API for EGL, by default it uses OpenGL ES
EGLBoolean ok = eglBindAPI(EGL_OPENGL_API);
Assert(ok && "Failed to select OpenGL API for EGL");
// choose EGL configuration
EGLConfig config;
{
EGLint attr[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_CONFORMANT, EGL_OPENGL_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
// uncomment for multisampled framebuffer
//EGL_SAMPLE_BUFFERS, 1,
//EGL_SAMPLES, 4, // 4x MSAA
EGL_NONE,
};
EGLint count;
if (!eglChooseConfig(display, attr, &config, 1, &count) || count != 1)
{
FatalError("Cannot choose EGL config");
}
}
// create EGL surface
EGLSurface* surface;
{
EGLint attr[] =
{
EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_LINEAR, // or use EGL_GL_COLORSPACE_SRGB for sRGB framebuffer
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
EGL_NONE,
};
surface = CreatePlatformWindowSurfaceEXT(display, config, &window, attr);
if (surface == EGL_NO_SURFACE)
{
FatalError("Cannot create EGL surface");
}
}
// create EGL context
EGLContext* context;
{
EGLint attr[] =
{
EGL_CONTEXT_MAJOR_VERSION, 4,
EGL_CONTEXT_MINOR_VERSION, 5,
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
#ifndef NDEBUG
// ask for debug context for non "Release" builds
// this is so we can enable debug callback
EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE,
#endif
EGL_NONE,
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, attr);
if (context == EGL_NO_CONTEXT)
{
FatalError("Cannot create EGL context, OpenGL 4.5 not supported?");
}
}
ok = eglMakeCurrent(display, surface, surface, context);
Assert(ok && "Failed to make context current");
// load OpenGL functions
#define X(type, name) name = (type)eglGetProcAddress(#name); Assert(name);
GL_FUNCTIONS(X)
#undef X
#ifndef NDEBUG
// enable debug callback
glDebugMessageCallback(&DebugCallback, NULL);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#endif
struct Vertex
{
float position[2];
float uv[2];
float color[3];
};
// vertex buffer containing triangle vertices
GLuint vbo;
{
struct Vertex data[] =
{
{ { -0.00f, +0.75f }, { 25.0f, 50.0f }, { 1, 0, 0 } },
{ { +0.75f, -0.50f }, { 0.0f, 0.0f }, { 0, 1, 0 } },
{ { -0.75f, -0.50f }, { 50.0f, 0.0f }, { 0, 0, 1 } },
};
glCreateBuffers(1, &vbo);
glNamedBufferStorage(vbo, sizeof(data), data, 0);
}
// vertex input
GLuint vao;
{
glCreateVertexArrays(1, &vao);
GLint vbuf_index = 0;
glVertexArrayVertexBuffer(vao, vbuf_index, vbo, 0, sizeof(struct Vertex));
GLint a_pos = 0;
glVertexArrayAttribFormat(vao, a_pos, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex, position));
glVertexArrayAttribBinding(vao, a_pos, vbuf_index);
glEnableVertexArrayAttrib(vao, a_pos);
GLint a_uv = 1;
glVertexArrayAttribFormat(vao, a_uv, 2, GL_FLOAT, GL_FALSE, offsetof(struct Vertex, uv));
glVertexArrayAttribBinding(vao, a_uv, vbuf_index);
glEnableVertexArrayAttrib(vao, a_uv);
GLint a_color = 2;
glVertexArrayAttribFormat(vao, a_color, 3, GL_FLOAT, GL_FALSE, offsetof(struct Vertex, color));
glVertexArrayAttribBinding(vao, a_color, vbuf_index);
glEnableVertexArrayAttrib(vao, a_color);
}
// checkerboard texture, with 50% transparency on black colors
GLuint texture;
{
unsigned int pixels[] =
{
0x80000000, 0xffffffff,
0xffffffff, 0x80000000,
};
glCreateTextures(GL_TEXTURE_2D, 1, &texture);
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLsizei width = 2;
GLsizei height = 2;
glTextureStorage2D(texture, 1, GL_RGBA8, width, height);
glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
// fragment & vertex shaders for drawing triangle
GLuint pipeline, vshader, fshader;
{
const char* glsl_vshader =
"#version 450 core \n"
"#line " STR(__LINE__) " \n\n" // actual line number in this file for nicer error messages
" \n"
"layout (location=0) in vec2 a_pos; \n" // position attribute index 0
"layout (location=1) in vec2 a_uv; \n" // uv attribute index 1
"layout (location=2) in vec3 a_color; \n" // color attribute index 2
" \n"
"layout (location=0) \n" // (from ARB_explicit_uniform_location)
"uniform mat2 u_matrix; \n" // matrix uniform location 0
" \n"
"out gl_PerVertex { vec4 gl_Position; }; \n" // required because of ARB_separate_shader_objects
"out vec2 uv; \n"
"out vec4 color; \n"
" \n"
"void main() \n"
"{ \n"
" vec2 pos = u_matrix * a_pos; \n"
" gl_Position = vec4(pos, 0, 1); \n"
" uv = a_uv; \n"
" color = vec4(a_color, 1); \n"
"} \n"
;
const char* glsl_fshader =
"#version 450 core \n"
"#line " STR(__LINE__) " \n\n" // actual line number in this file for nicer error messages
" \n"
"in vec2 uv; \n"
"in vec4 color; \n"
" \n"
"layout (binding=0) \n" // (from ARB_shading_language_420pack)
"uniform sampler2D s_texture; \n" // texture unit binding 0
" \n"
"layout (location=0) \n"
"out vec4 o_color; \n" // output fragment data location 0
" \n"
"void main() \n"
"{ \n"
" o_color = color * texture(s_texture, uv); \n"
"} \n"
;
vshader = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader);
fshader = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader);
GLint linked;
glGetProgramiv(vshader, GL_LINK_STATUS, &linked);
if (!linked)
{
char message[1024];
glGetProgramInfoLog(vshader, sizeof(message), NULL, message);
fprintf(stderr, "%s\n", message);
Assert(!"Failed to create vertex shader!");
}
glGetProgramiv(fshader, GL_LINK_STATUS, &linked);
if (!linked)
{
char message[1024];
glGetProgramInfoLog(fshader, sizeof(message), NULL, message);
fprintf(stderr, "%s\n", message);
Assert(!"Failed to create fragment shader!");
}
glGenProgramPipelines(1, &pipeline);
glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vshader);
glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fshader);
}
// setup global GL state
{
// enable alpha blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// disble depth testing
glDisable(GL_DEPTH_TEST);
// disable culling
glDisable(GL_CULL_FACE);
}
// use 0 to disable vsync
int vsync = 1;
ok = eglSwapInterval(display, vsync);
Assert(ok && "Failed to set vsync for EGL");
cookie = xcb_map_window_checked(conn, window);
if ((error = xcb_request_check(conn, cookie)) != NULL) {
free(error);
return -1;
}
struct timespec c1;
clock_gettime(CLOCK_MONOTONIC, &c1);
float angle = 0;
for (;;)
{
// process all incoming X11 events
xcb_generic_event_t *event;
event = xcb_poll_for_event(conn);
if (event) {
if (((*event).response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
if((*(xcb_client_message_event_t*)event).data.data32[0] == (*delete_window).atom) {
// Window closed, exit
break;
}
continue;
}
}
free(event);
xcb_get_geometry_cookie_t geom_cookie;
xcb_get_geometry_reply_t *geometry;
geom_cookie = xcb_get_geometry(conn, window);
geometry = xcb_get_geometry_reply(conn, geom_cookie, NULL);
width = geometry->width;
height = geometry->height;
struct timespec c2;
clock_gettime(CLOCK_MONOTONIC, &c2);
float delta = (float)(c2.tv_sec - c1.tv_sec) + 1e-9f * (c2.tv_nsec - c1.tv_nsec);
c1 = c2;
// render only if window size is non-zero
if (width != 0 && height != 0)
{
// setup output size covering all client area of window
glViewport(0, 0, width, height);
// clear screen
glClearColor(0.392f, 0.584f, 0.929f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// setup rotation matrix in uniform
{
angle += delta * 2.0f * (float)M_PI / 20.0f; // full rotation in 20 seconds
angle = fmodf(angle, 2.0f * (float)M_PI);
float aspect = (float)height / width;
float matrix[] =
{
cosf(angle) * aspect, -sinf(angle),
sinf(angle) * aspect, cosf(angle),
};
GLint u_matrix = 0;
glProgramUniformMatrix2fv(vshader, u_matrix, 1, GL_FALSE, matrix);
}
// activate shaders for next draw call
glBindProgramPipeline(pipeline);
// provide vertex input
glBindVertexArray(vao);
// bind texture to texture unit
GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code
glBindTextureUnit(s_texture, texture);
// draw 3 vertices as triangle
glDrawArrays(GL_TRIANGLES, 0, 3);
// swap the buffers to show output
if (!eglSwapBuffers(display, surface))
{
FatalError("Failed to swap OpenGL buffers!");
}
}
else
{
// window is minimized, cannot vsync - instead sleep a bit
if (vsync)
{
usleep(10 * 1000);
}
}
}
}
@valignatev

Copy link
Copy Markdown
Author

Thanks! This probably means that I will have to use libx as X11 layer going forward. Very sadge. Another L for nvidia.

@valignatev

Copy link
Copy Markdown
Author

Actually, seems like Nvidia has this as an external lib? https://github.com/NVIDIA/egl-x11

So maybe you don't have this installed and that's why it falls back to software renderer. Idk. I've also just updated the gist to use eglcreatePlatformWindowSurface instead of eglCreateWindowSurface, maybe it's more portable. Anyway, feel free to ignore this if you don't feel like being my guinea pig, and regardless, thanks a lot for reports!

@xBZZZZ

xBZZZZ commented Apr 27, 2026

Copy link
Copy Markdown

So maybe you don't have this installed and that's why it falls back to software renderer.

https://developer.download.nvidia.com/compute/cuda/repos/opensuse15/x86_64/libnvidia-egl-x111-1.0.5-150700.1.1.x86_64.rpm was installed since beginning

does https://github.com/NVIDIA/egl-x11 do anything if not using wayland?

  • This is an EGL platform library for the NVIDIA driver to support XWayland via xlib (using EGL_KHR_platform_x11) or xcb (using EGL_EXT_platform_xcb).

I've also just updated the gist to use eglcreatePlatformWindowSurface instead of eglCreateWindowSurface, maybe it's more portable.

nothing changed, still getting software renderer and same stderr

@valignatev

valignatev commented Apr 27, 2026

Copy link
Copy Markdown
Author

do anything if not using wayland

I sure hoped that it did! Oh well..... I'll probably have to use libx to get EGL context and then use xcb for everything else. Fucking nvidia man
Seems like glfw and sdl both get EGL context through libx too. They probably know something :D
Anyway, thanks!

@valignatev

Copy link
Copy Markdown
Author

Btw there are examples of getting EGL context by mixing xcb window and xlib display connection, like https://github.com/linyaa-kiwi/gl-examples/blob/f92d48314daf505e9ac65e8c96b23cad41613ca8/src/x11-egl/create-x-egl-gles2-surface.cpp
And it is spec-compliant, but I, of course, don't have ways to test it on Nvidia, so just leaving it here either for future self, or for anyone else who wants to test different variations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment