Files
tracy/examples/opengl/triangle/platform/platform_x11.cpp
2026-06-10 14:14:54 -07:00

150 lines
4.8 KiB
C++

// platform_x11.cpp — Linux/X11 backend (GLX)
//
// Creates a GLX 3.3 Core Profile OpenGL context.
// GLEW must be initialized by the caller (initGL) after platformInit() returns.
//
// Dependencies: libX11, libGL, libGLEW
//
// Compile flags:
// g++ -std=c++17 spinning_triangle.cpp platform/platform_x11.cpp \
// -lX11 -lGL -lGLEW -o gl_spinning_triangle
#include <X11/Xlib.h>
#include <GL/glx.h>
#include <cstdio>
#include <time.h>
#include "platform.h"
// GLX_ARB_create_context token values (from <GL/glxext.h>)
#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
# define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
# define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
# define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
# define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#endif
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
static Display* sDpy = nullptr;
static Window sWin = 0;
static GLXContext sCtx = nullptr;
static Atom sWmDelete = 0;
static bool sRunning = false;
static struct timespec sStart = {};
static void (*sRenderCb)() = nullptr;
static void (*sShutdownCb)() = nullptr;
bool platformInit(int width, int height, const char* title) {
sDpy = XOpenDisplay(nullptr);
if (!sDpy) { fprintf(stderr, "Cannot open X display\n"); return false; }
const int fbAttribs[] = {
GLX_X_RENDERABLE, True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DOUBLEBUFFER, True,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
None
};
int fbCount = 0;
GLXFBConfig* fbc = glXChooseFBConfig(sDpy, DefaultScreen(sDpy), fbAttribs, &fbCount);
if (!fbc || fbCount == 0) { fprintf(stderr, "No suitable GLXFBConfig\n"); return false; }
XVisualInfo* vi = glXGetVisualFromFBConfig(sDpy, fbc[0]);
XSetWindowAttributes swa = {};
swa.colormap = XCreateColormap(sDpy, DefaultRootWindow(sDpy), vi->visual, AllocNone);
swa.event_mask = ExposureMask | KeyPressMask;
sWin = XCreateWindow(sDpy, DefaultRootWindow(sDpy),
200, 200, width, height, 0, vi->depth, InputOutput, vi->visual,
CWColormap | CWEventMask, &swa);
XFree(vi);
XStoreName(sDpy, sWin, title);
XMapWindow(sDpy, sWin);
sWmDelete = XInternAtom(sDpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(sDpy, sWin, &sWmDelete, 1);
auto glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
if (!glXCreateContextAttribsARB) {
fprintf(stderr, "glXCreateContextAttribsARB not found\n");
XFree(fbc);
return false;
}
const int ctxAttribs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 3,
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
None
};
sCtx = glXCreateContextAttribsARB(sDpy, fbc[0], nullptr, True, ctxAttribs);
XFree(fbc);
if (!sCtx) { fprintf(stderr, "Failed to create GLX context\n"); return false; }
glXMakeCurrent(sDpy, sWin, sCtx);
clock_gettime(CLOCK_MONOTONIC, &sStart);
return true;
}
bool platformInitGL() {
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
return false;
}
return true;
}
double platformGetTime() {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (double)(now.tv_sec - sStart.tv_sec) + (double)(now.tv_nsec - sStart.tv_nsec) * 1e-9;
}
void platformSwapBuffers() { glXSwapBuffers(sDpy, sWin); }
void platformRunLoop(void (*render)(), void (*shutdown)()) {
static const long kFrameNs = 1000000000L / 60;
sRenderCb = render;
sShutdownCb = shutdown;
sRunning = true;
while (sRunning) {
struct timespec frameStart;
clock_gettime(CLOCK_MONOTONIC, &frameStart);
while (XPending(sDpy)) {
XEvent ev;
XNextEvent(sDpy, &ev);
if (ev.type == KeyPress) { sRunning = false; break; }
if (ev.type == ClientMessage && (Atom)ev.xclient.data.l[0] == sWmDelete) {
sRunning = false;
break;
}
}
if (sRunning) render();
struct timespec frameEnd;
clock_gettime(CLOCK_MONOTONIC, &frameEnd);
long elapsed = (frameEnd.tv_sec - frameStart.tv_sec) * 1000000000L
+ (frameEnd.tv_nsec - frameStart.tv_nsec);
long remaining = kFrameNs - elapsed;
if (remaining > 0) {
struct timespec ts = { 0, remaining };
nanosleep(&ts, nullptr);
}
}
shutdown();
glXMakeCurrent(sDpy, None, nullptr);
glXDestroyContext(sDpy, sCtx);
XDestroyWindow(sDpy, sWin);
XCloseDisplay(sDpy);
}