Created
May 6, 2026 06:59
-
-
Save JustSlavic/fa9093cbe46782df4641b7d86c37494d to your computer and use it in GitHub Desktop.
Simple single file Linux X11 OpenGL example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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