While it's possible to draw pixels to an X11 window with built-in Xlib functions, they are quite slow. If you're curious, feel free to look it up, but I'm going to skip straight to creating an OpenGL context in the window. OpenGL is a very featureful API for programming graphics processors, but we'll be using it sparingly. For now, we'll just draw the classic, "Hello Triangle."
Build with:
gcc main.c -lX11 -lGL
int gl_attributes [] = {
GLX_RGBA,
GLX_DOUBLEBUFFER,
GLX_RED_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_DEPTH_SIZE, 24,
0
};
const auto visual = glXChooseVisual (display, screen, gl_attributes); assert (visual);
const auto gl_context = glXCreateContext (display, visual, 0, true); assert (gl_context);
OpenGL requires a "context," which is your link into OpenGL, holding all your rendering information and associated with every OpenGL command you run. glXCreateContext takes a NULL-terminated list of attributes, some of which are single values to toggle on and off, and others which are an identifier followed by a value. Check the OpenGL documentation for all possible values. glXCreateContext does not guarantee which version your context will be. If you want check that your program has access to features beyond OpenGL 1.0, see this tutorial to check your context version, and this tutorial to request a specific version and profile. These days, since OpenGL isn't getting new versions, all modern systems will give you a 4.6 compatibility context (except MacOS, which offers 2.1 legacy, 3.2 core, or 4.1 core). "Compatibility" means old, deprecated APIs are supported. We get the visual for our X window with GLX, which is the X11 extension for OpenGL.
{ const auto result = glXMakeCurrent (display, xwindow, gl_context); assert (result); }
After creating a context, but before using it, you must call this function. This assigns the window as the drawable surface of the context, and sets it as the currently active context for this thread.
printf ("OpenGL version: %s\n", (const char*)glGetString (GL_VERSION));
Check what version your profile prints as. You should expect something like, "OpenGL version: 4.6 (Compatibility Profile)". At time of writing, all modern OpenGL drivers give you a compatibility profile which has support for old OpenGL APIs, but if you have problems, make sure you're getting a compatibility profile version.
static void OnResize () {
glViewport (0, 0, window.w, window.h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
}
I've created this function to reset some properties whenever the window is resized. When you first call glXMakeCurrent, the viewport is set to the area of the window, but it doesn't automatically update after that. It's a bit empty for now, but here's where you'll usually set up your projection matrix, which tells OpenGL how to mathematically translate from rendering coordinates to screen coordinates.
glClearColor (0, 0, 0, 0);
You can tell OGL what value to use for clearing - try changing these values.
glClear (GL_COLOR_BUFFER_BIT);
glBegin (GL_TRIANGLES);
glColor3f (1, 0, 0);
glVertex3f (-1, -1, 0);
glColor3f (0, 1, 0);
glVertex3f (1, -1, 0);
glColor3f (0, 0, 1);
glVertex3f (0, 1, 0);
glEnd ();
glXSwapBuffers (display, xwindow);
This is a simple rendering pass. The drawing surface is cleared (based on the clear color set before), and we pass in some color and vertex information to draw a triangle. The identity projection matrix maps the viewport to horizontal and vertical axes of -1 to 1, so this triangle stretches to the corners and top-middle of the window. You'll see that OpenGL blends colors between the vertices.
When creating the context, we specified, "GLX_DOUBLEBUFFER," which means we want 2 rendering buffers. One is displayed while the other is drawn into, then, when drawing is complete, we swap the buffers wholesale. This prevents the user seeing the individual drawing operations and only shows them complete frames. glXSwapBuffers is the function used to... swap the buffers.
If you'd like to learn more about legacy OpenGL, I recommend the book, "OpenGL Superbible 4th Edition," which covers version 2.1 quite well.