Created
July 28, 2022 04:18
-
-
Save liviaerxin/5676b614f4d9c170f8ed40be2bc9fc9c to your computer and use it in GitHub Desktop.
Create X11 window rendering an animation using OpenGL
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
/** | |
* @file XOpenGLRenderAnimation.cpp | |
* @author your name ([email protected]) | |
* @brief | |
* @version 0.1 | |
* @date 2022-07-27 | |
* | |
* @copyright Copyright (c) 2022 | |
* https://github.com/gamedevtech/X11OpenGLWindow | |
clang++ XOpenGLRenderAnimation.cpp -o XOpenGLRenderAnimation \ | |
-I/opt/X11/include \ | |
-L/opt/X11/lib -lX11 -lGL | |
*/ | |
#include <cstring> | |
#include <iostream> | |
#include <math.h> | |
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
#include <X11/keysymdef.h> | |
#include <GL/gl.h> | |
#include <GL/glx.h> | |
#include <sys/time.h> | |
#include <unistd.h> | |
#define WINDOW_WIDTH 800 | |
#define WINDOW_HEIGHT 600 | |
#define FPS 30 | |
#define TEST_LOCAL | |
struct Point2D { | |
float x; | |
float y; | |
}; | |
extern bool Initialize(int w, int h); | |
extern bool Update(float deltaTime); | |
extern void Render(double prevTime, double currentTime); | |
extern void Resize(int w, int h); | |
extern void Shutdown(); | |
extern void Rotate(Point2D &p, double angle); | |
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, | |
GLXContext, Bool, | |
const int *); | |
#define SKIP_TICKS (1000 / FPS) | |
static double GetMilliseconds() { | |
static timeval s_tTimeVal; | |
gettimeofday(&s_tTimeVal, NULL); | |
double time = s_tTimeVal.tv_sec * 1000.0; // sec to ms | |
time += s_tTimeVal.tv_usec / 1000.0; // us to ms | |
return time; | |
} | |
static bool isExtensionSupported(const char *extList, const char *extension) { | |
return strstr(extList, extension) != 0; | |
} | |
int main(int argc, char **argv) { | |
Display *display; | |
Window window; | |
Screen *screen; | |
int screenId; | |
XEvent ev; | |
// Open the display | |
display = XOpenDisplay(NULL); | |
if (display == NULL) { | |
std::cout << "Could not open display\n"; | |
return 1; | |
} | |
screen = DefaultScreenOfDisplay(display); | |
screenId = DefaultScreen(display); | |
// Check GLX version | |
GLint majorGLX, minorGLX = 0; | |
glXQueryVersion(display, &majorGLX, &minorGLX); | |
if (majorGLX <= 1 && minorGLX < 2) { | |
std::cout << "GLX 1.2 or greater is required.\n"; | |
XCloseDisplay(display); | |
return 1; | |
} | |
GLint glxAttribs[] = {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, | |
None}; | |
int fbcount; | |
GLXFBConfig *fbc = glXChooseFBConfig(display, screenId, glxAttribs, &fbcount); | |
if (fbc == 0) { | |
std::cout << "Failed to retrieve framebuffer.\n"; | |
XCloseDisplay(display); | |
return 1; | |
} | |
// Pick the FB config/visual with the most samples per pixel | |
int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999; | |
for (int i = 0; i < fbcount; ++i) { | |
XVisualInfo *vi = glXGetVisualFromFBConfig(display, fbc[i]); | |
if (vi != 0) { | |
int samp_buf, samples; | |
glXGetFBConfigAttrib(display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); | |
glXGetFBConfigAttrib(display, fbc[i], GLX_SAMPLES, &samples); | |
if (best_fbc < 0 || (samp_buf && samples > best_num_samp)) { | |
best_fbc = i; | |
best_num_samp = samples; | |
} | |
if (worst_fbc < 0 || !samp_buf || samples < worst_num_samp) | |
worst_fbc = i; | |
worst_num_samp = samples; | |
} | |
XFree(vi); | |
} | |
GLXFBConfig bestFbc = fbc[best_fbc]; | |
XFree(fbc); // Make sure to free this! | |
XVisualInfo *visual = glXGetVisualFromFBConfig(display, bestFbc); | |
if (visual == 0) { | |
std::cout << "Could not create correct visual window.\n"; | |
XCloseDisplay(display); | |
return 1; | |
} | |
if (screenId != visual->screen) { | |
std::cout << "screenId(" << screenId << ") does not match visual->screen(" | |
<< visual->screen << ").\n"; | |
XCloseDisplay(display); | |
return 1; | |
} | |
// Open the window | |
XSetWindowAttributes windowAttribs; | |
windowAttribs.border_pixel = BlackPixel(display, screenId); | |
windowAttribs.background_pixel = WhitePixel(display, screenId); | |
windowAttribs.override_redirect = True; | |
windowAttribs.colormap = XCreateColormap( | |
display, RootWindow(display, screenId), visual->visual, AllocNone); | |
windowAttribs.event_mask = ExposureMask; | |
window = XCreateWindow( | |
display, RootWindow(display, screenId), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, | |
0, visual->depth, InputOutput, visual->visual, | |
CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &windowAttribs); | |
// Redirect Close | |
Atom atomWmDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False); | |
XSetWMProtocols(display, window, &atomWmDeleteWindow, 1); | |
// Create GLX OpenGL context | |
glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; | |
glXCreateContextAttribsARB = | |
(glXCreateContextAttribsARBProc)glXGetProcAddressARB( | |
(const GLubyte *)"glXCreateContextAttribsARB"); | |
int context_attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB, | |
3, | |
GLX_CONTEXT_MINOR_VERSION_ARB, | |
2, | |
GLX_CONTEXT_FLAGS_ARB, | |
GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, | |
None}; | |
GLXContext context = 0; | |
const char *glxExts = glXQueryExtensionsString(display, screenId); | |
if (!isExtensionSupported(glxExts, "GLX_ARB_create_context")) { | |
std::cout << "GLX_ARB_create_context not supported\n"; | |
context = glXCreateNewContext(display, bestFbc, GLX_RGBA_TYPE, 0, True); | |
} else { | |
context = | |
glXCreateContextAttribsARB(display, bestFbc, 0, true, context_attribs); | |
} | |
XSync(display, False); | |
// Verifying that context is a direct context | |
if (!glXIsDirect(display, context)) { | |
std::cout << "Indirect GLX rendering context obtained\n"; | |
} else { | |
std::cout << "Direct GLX rendering context obtained\n"; | |
} | |
glXMakeCurrent(display, window, context); | |
std::cout << "GL Renderer: " << glGetString(GL_RENDERER) << "\n"; | |
std::cout << "GL Version: " << glGetString(GL_VERSION) << "\n"; | |
std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) | |
<< "\n"; | |
if (!Initialize(WINDOW_WIDTH, WINDOW_HEIGHT)) { | |
glXDestroyContext(display, context); | |
XFree(visual); | |
XFreeColormap(display, windowAttribs.colormap); | |
XDestroyWindow(display, window); | |
XCloseDisplay(display); | |
return 1; | |
} | |
// Show the window | |
XClearWindow(display, window); | |
XMapRaised(display, window); | |
double prevTime = GetMilliseconds(); | |
double currentTime = GetMilliseconds(); | |
double deltaTime = 0.0; | |
timeval time; | |
long sleepTime = 0; | |
gettimeofday(&time, NULL); | |
long nextGameTick = (time.tv_sec * 1000) + (time.tv_usec / 1000); | |
// Enter message loop | |
while (true) { | |
if (XPending(display) > 0) { | |
XNextEvent(display, &ev); | |
if (ev.type == Expose) { | |
XWindowAttributes attribs; | |
XGetWindowAttributes(display, window, &attribs); | |
Resize(attribs.width, attribs.height); | |
} | |
if (ev.type == ClientMessage) { | |
if (ev.xclient.data.l[0] == atomWmDeleteWindow) { | |
std::cout << "Shutting Down From ClientMessage\n"; | |
break; | |
} | |
} else if (ev.type == DestroyNotify) { | |
std::cout << "Shutting Down From DestroyNotify\n"; | |
break; | |
} | |
} | |
prevTime = currentTime; | |
currentTime = GetMilliseconds(); | |
deltaTime = double(currentTime - prevTime) * 0.001; | |
if (!Update((float)deltaTime)) { | |
break; | |
} | |
Render(prevTime, currentTime); | |
// Present frame | |
glXSwapBuffers(display, window); | |
// Limit Framerate | |
gettimeofday(&time, NULL); | |
nextGameTick += SKIP_TICKS; | |
sleepTime = nextGameTick - ((time.tv_sec * 1000) + (time.tv_usec / 1000)); | |
usleep((unsigned int)(sleepTime / 1000)); | |
} | |
std::cout << "Shutting Down\n"; | |
Shutdown(); | |
// Cleanup GLX | |
glXDestroyContext(display, context); | |
// Cleanup X11 | |
XFree(visual); | |
XFreeColormap(display, windowAttribs.colormap); | |
XDestroyWindow(display, window); | |
XCloseDisplay(display); | |
return 0; | |
} | |
#ifdef TEST_LOCAL | |
bool Initialize(int w, int h) { | |
glClearColor(0.5f, 0.6f, 0.7f, 1.0f); | |
glViewport(0, 0, w, h); | |
return true; | |
} | |
bool Update(float deltaTime) { return true; } | |
struct Point2D p1 = {0.0f, -0.5f}; | |
struct Point2D p2 = {-0.5f, 0.5f}; | |
struct Point2D p3 = {0.5f, 0.5f}; | |
// float x2 = -1.0f, y2 = 1.0f; | |
// float x3 = 1.0f, y3 = 1.0f; | |
float speed = M_PI / 60; // radian/per second | |
void Render(double prevTime, double currentTime) { | |
double deltaTime = double(currentTime - prevTime) * 0.001; | |
Rotate(p1, deltaTime * speed); | |
Rotate(p2, deltaTime * speed); | |
Rotate(p3, deltaTime * speed); | |
glClear(GL_COLOR_BUFFER_BIT); | |
glBegin(GL_TRIANGLES); | |
glColor3f(1.0f, 0.0f, 0.0f); | |
glVertex3f(p1.x, p1.y, 0.0f); | |
glColor3f(0.0f, 1.0f, 0.0f); | |
glVertex3f(p2.x, p2.y, 0.0f); | |
glColor3f(0.0f, 0.0f, 1.0f); | |
glVertex3f(p3.x, p3.y, 0.0f); | |
glEnd(); | |
} | |
void Resize(int w, int h) { glViewport(0, 0, w, h); } | |
void Shutdown() {} | |
void Rotate(Point2D &p, double angle) { | |
float x1 = p.x * cos(angle) - p.y * sin(angle); | |
float y1 = p.x * sin(angle) + p.y * cos(angle); | |
p.x = x1; | |
p.y = y1; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment