Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active July 19, 2023 06:02
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mmozeiko/911347b5e3d998621295794e0ba334c4 to your computer and use it in GitHub Desktop.
Save mmozeiko/911347b5e3d998621295794e0ba334c4 to your computer and use it in GitHub Desktop.
setting up and using modern OpenGL 4.5 core context on X11 with EGL
// 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 libx11-dev libgl-dev libegl-dev, then run:
// gcc x11_opengl.c -o x11_opengl -lm -lX11 -lEGL
// important extension functionality used here:
// (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
#include <X11/Xlib.h>
#include <EGL/egl.h>
#include <GL/glcorearb.h>
#include <time.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// replace this with your favorite Assert() implementation
#include <assert.h>
#define Assert(cond) assert(cond)
// 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()
{
Display* dpy = XOpenDisplay(NULL);
if (!dpy)
{
FatalError("Cannot open X display");
}
XSetWindowAttributes attributes =
{
.event_mask = StructureNotifyMask,
};
// create window
int width = 1280;
int height = 720;
Window window = XCreateWindow(
dpy, DefaultRootWindow(dpy),
0, 0, width, height,
0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask,
&attributes);
Assert(window && "Failed to create window");
// uncomment in case you want fixed size window
//XSizeHints* hints = XAllocSizeHints();
//Assert(hints);
//hints->flags |= PMinSize | PMaxSize;
//hints->min_width = hints->max_width = width;
//hints->min_height = hints->max_height = height;
//XSetWMNormalHints(dpy, window, hints);
//XFree(hints);
// set window title
XStoreName(dpy, window, "OpenGL Window");
// subscribe to window close notification
Atom WM_PROTOCOLS = XInternAtom(dpy, "WM_PROTOCOLS", False);
Atom WM_DELETE_WINDOW = XInternAtom(dpy , "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, window, &WM_DELETE_WINDOW, 1);
// initialize EGL
EGLDisplay* display;
{
display = eglGetDisplay((EGLNativeDisplayType)dpy);
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 = eglCreateWindowSurface(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");
// show the window
XMapWindow(dpy, window);
struct timespec c1;
clock_gettime(CLOCK_MONOTONIC, &c1);
float angle = 0;
for (;;)
{
// process all incoming X11 events
if (XPending(dpy))
{
XEvent event;
XNextEvent(dpy, &event);
if (event.type == ClientMessage)
{
if (event.xclient.message_type == WM_PROTOCOLS)
{
Atom protocol = event.xclient.data.l[0];
if (protocol == WM_DELETE_WINDOW)
{
// window closed, exit the for loop
break;
}
}
}
continue;
}
// get current window size
XWindowAttributes attr;
Status status = XGetWindowAttributes(dpy, window, &attr);
Assert(status && "Failed to get window attributes");
width = attr.width;
height = attr.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);
}
}
}
}
@Daweek
Copy link

Daweek commented Aug 13, 2021

Thanks a lot, it helps a lot. I had to remove these lines to make it work on Ubuntu 20.04 x86_64 using NVIDIA 2070 GTX SUPER
//EGL_CONFIG_CAVEAT, EGL_NONE, //EGL_NATIVE_RENDERABLE, EGL_TRUE,

@mmozeiko
Copy link
Author

Thanks! Testing this on nvidia system was on my TODO list. I'll remove those two - they are not really needed.

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