diff --git a/Makefile b/Makefile index 4d5e90a..7a0d57a 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,9 @@ ifeq "$(NO_VSYNC_OPENGL)" "" # -lGL must precede some other libraries, or it segfaults on FreeBSD (#74) LIBS := -lGL $(LIBS) OBJS += opengl.o + # ifeq "$(NO_VSYNC_OPENGL_GLSL)" "" + # CFG += -DCONFIG_VSYNC_OPENGL_GLSL + # endif endif # ==== D-Bus ==== diff --git a/compton.sample.conf b/compton.sample.conf index 2043c66..a5fc78d 100644 --- a/compton.sample.conf +++ b/compton.sample.conf @@ -34,6 +34,7 @@ fade-out-step = 0.03; # no-fading-openclose = true; # Other +backend = "xrender" mark-wmwin-focused = true; mark-ovredir-focused = true; use-ewmh-active-win = false; diff --git a/src/common.h b/src/common.h index 30c09e3..f36edf4 100644 --- a/src/common.h +++ b/src/common.h @@ -89,6 +89,10 @@ // libGL #ifdef CONFIG_VSYNC_OPENGL +#ifdef CONFIG_VSYNC_OPENGL_GLSL +#define GL_GLEXT_PROTOTYPES +#endif + #include #endif @@ -263,6 +267,7 @@ typedef enum { enum backend { BKEND_XRENDER, BKEND_GLX, + NUM_BKEND, }; typedef struct _glx_texture glx_texture_t; @@ -594,6 +599,8 @@ typedef struct { // === OpenGL related === /// GLX context. GLXContext glx_context; + /// Whether we have GL_ARB_texture_non_power_of_two. + bool glx_has_texture_non_power_of_two; /// Pointer to glXGetVideoSyncSGI function. f_GetVideoSync glXGetVideoSyncSGI; /// Pointer to glXWaitVideoSyncSGI function. @@ -874,6 +881,7 @@ typedef enum { extern const char * const WINTYPES[NUM_WINTYPES]; extern const char * const VSYNC_STRS[NUM_VSYNC]; +extern const char * const BACKEND_STRS[NUM_BKEND]; extern session_t *ps_g; // == Debugging code == @@ -1252,6 +1260,20 @@ parse_vsync(session_t *ps, const char *str) { return false; } +/** + * Parse a backend option argument. + */ +static inline bool +parse_backend(session_t *ps, const char *str) { + for (enum backend i = 0; i < (sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0])); ++i) + if (!strcasecmp(str, BACKEND_STRS[i])) { + ps->o.backend = i; + return true; + } + printf_errf("(\"%s\"): Invalid backend argument.", str); + return false; +} + timeout_t * timeout_insert(session_t *ps, time_ms_t interval, bool (*callback)(session_t *ps, timeout_t *ptmout), void *data); @@ -1542,6 +1564,9 @@ glx_destroy(session_t *ps); void glx_on_root_change(session_t *ps); +bool +glx_init_blur(session_t *ps); + bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, Pixmap pixmap, int width, int height, int depth); @@ -1554,6 +1579,9 @@ glx_tex_binded(const glx_texture_t *ptex) { return ptex && ptex->glpixmap && ptex->texture; } +void +glx_set_clip(session_t *ps, XserverRegion reg); + bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, int width, int height, int z, diff --git a/src/compton.c b/src/compton.c index f8c33d2..df39a36 100644 --- a/src/compton.c +++ b/src/compton.c @@ -40,6 +40,12 @@ const char * const VSYNC_STRS[NUM_VSYNC] = { "opengl-swc", // VSYNC_OPENGL_SWC }; +/// Names of backends. +const char * const BACKEND_STRS[NUM_BKEND] = { + "xrender", // BKEND_XRENDER + "glx", // BKEND_GLX +}; + /// Function pointers to init VSync modes. static bool (* const (VSYNC_FUNCS_INIT[NUM_VSYNC]))(session_t *ps) = { [VSYNC_DRM ] = vsync_drm_init, @@ -1628,8 +1634,7 @@ paint_all(session_t *ps, XserverRegion region, win *t) { reg_paint = region; } - XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, reg_paint); - + set_tgt_clip(ps, reg_paint); paint_root(ps, reg_paint); // Create temporary regions for use during painting @@ -1666,8 +1671,7 @@ paint_all(session_t *ps, XserverRegion region, win *t) { // Detect if the region is empty before painting if (region == reg_paint || !is_region_empty(ps, reg_paint)) { - XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, - reg_paint); + set_tgt_clip(ps, reg_paint); win_paint_shadow(ps, w, reg_paint); } @@ -1694,7 +1698,7 @@ paint_all(session_t *ps, XserverRegion region, win *t) { } if (!is_region_empty(ps, reg_paint)) { - XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, reg_paint); + set_tgt_clip(ps, reg_paint); // Blur window background if ((ps->o.blur_background && WMODE_SOLID != w->mode) || (ps->o.blur_background_frame && w->frame_opacity)) { @@ -1713,7 +1717,7 @@ paint_all(session_t *ps, XserverRegion region, win *t) { // Do this as early as possible if (!ps->o.dbe) - XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, None); + set_tgt_clip(ps, None); if (ps->o.vsync) { // Make sure all previous requests are processed to achieve best @@ -4044,7 +4048,7 @@ usage(void) { " opengl-oml = Try to VSync with OML_sync_control OpenGL extension.\n" " Only work on some drivers. Experimental." WARNING"\n" " opengl-swc = Try to VSync with SGI_swap_control OpenGL extension.\n" - " Only work on some drivers. Works only with OpenGL backend.\n" + " Only work on some drivers. Works only with GLX backend.\n" " Does not actually control paint timing, only buffer swap is\n" " affected, so it doesn't have the effect of --sw-opti unlike\n" " other methods. Experimental." WARNING "\n" @@ -4098,8 +4102,8 @@ usage(void) { "--invert-color-include condition\n" " Specify a list of conditions of windows that should be painted with\n" " inverted color. Resource-hogging, and is not well tested.\n" - "--opengl\n" - " Enable the highly experimental OpenGL backend." WARNING "\n" + "--backend backend\n" + " Choose backend. Possible choices are xrender and glx" WARNING ".\n" #undef WARNING #ifndef CONFIG_DBUS #define WARNING WARNING_DISABLED @@ -4450,6 +4454,9 @@ parse_config(session_t *ps, struct options_tmp *pcfgtmp) { // --vsync if (config_lookup_string(&cfg, "vsync", &sval) && !parse_vsync(ps, sval)) exit(1); + // --backend + if (config_lookup_string(&cfg, "backend", &sval) && !parse_backend(ps, sval)) + exit(1); // --alpha-step config_lookup_float(&cfg, "alpha-step", &ps->o.alpha_step); // --dbe @@ -4552,6 +4559,7 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { { "logpath", required_argument, NULL, 287 }, { "invert-color-include", required_argument, NULL, 288 }, { "opengl", no_argument, NULL, 289 }, + { "backend", required_argument, NULL, 290 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -4809,6 +4817,11 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { // --opengl ps->o.backend = BKEND_GLX; break; + case 290: + // --backend + if (!parse_backend(ps, optarg)) + exit(1); + break; default: usage(); break; @@ -5115,7 +5128,7 @@ vsync_opengl_swc_init(session_t *ps) { if (BKEND_GLX != ps->o.backend) { printf_errf("(): I'm afraid glXSwapIntervalSGI wouldn't help if you are " - "not using OpenGL backend. You could try, nonetheless."); + "not using GLX backend. You could try, nonetheless."); } // Get video sync functions @@ -5283,11 +5296,13 @@ init_filters(session_t *ps) { } break; } +#ifdef CONFIG_VSYNC_OPENGL case BKEND_GLX: { - printf_errf("(): OpenGL blur is not implemented yet."); - return false; + if (!glx_init_blur(ps)) + return false; } +#endif } } @@ -5709,6 +5724,7 @@ session_init(session_t *ps_old, int argc, char **argv) { #ifdef CONFIG_VSYNC_OPENGL .glx_context = None, + .glx_has_texture_non_power_of_two = false, .glXGetVideoSyncSGI = NULL, .glXWaitVideoSyncSGI = NULL, .glXGetSyncValuesOML = NULL, @@ -5862,6 +5878,14 @@ session_init(session_t *ps_old, int argc, char **argv) { ps->o.dbe = false; } + // Start listening to events on root earlier to catch all possible + // root geometry changes + XSelectInput(ps->dpy, ps->root, + SubstructureNotifyMask + | ExposureMask + | StructureNotifyMask + | PropertyChangeMask); + ps->root_width = DisplayWidth(ps->dpy, ps->scr); ps->root_height = DisplayHeight(ps->dpy, ps->scr); @@ -5874,7 +5898,7 @@ session_init(session_t *ps_old, int argc, char **argv) { // Initialize DBE if (ps->o.dbe && BKEND_GLX == ps->o.backend) { - printf_errf("(): DBE couldn't be used on OpenGL backend."); + printf_errf("(): DBE couldn't be used on GLX backend."); ps->o.dbe = false; } @@ -5887,7 +5911,7 @@ session_init(session_t *ps_old, int argc, char **argv) { if (!glx_init(ps, true)) exit(1); #else - printf_errfq(1, "(): OpenGL backend support not compiled in."); + printf_errfq(1, "(): GLX backend support not compiled in."); #endif } @@ -5926,6 +5950,7 @@ session_init(session_t *ps_old, int argc, char **argv) { } } + // Initialize filters, must be preceded by OpenGL context creation if (!init_filters(ps)) exit(1); @@ -5947,12 +5972,6 @@ session_init(session_t *ps_old, int argc, char **argv) { redir_start(ps); - XSelectInput(ps->dpy, ps->root, - SubstructureNotifyMask - | ExposureMask - | StructureNotifyMask - | PropertyChangeMask); - { Window root_return, parent_return; Window *children; diff --git a/src/compton.h b/src/compton.h index 0479b3d..49ffcb8 100644 --- a/src/compton.h +++ b/src/compton.h @@ -176,7 +176,7 @@ paint_isvalid(session_t *ps, const paint_t *ppaint) { return true; } /** - * Bind texture in paint_t if we are using OpenGL backend. + * Bind texture in paint_t if we are using GLX backend. */ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int hei, int depth) { @@ -542,6 +542,20 @@ win_render(session_t *ps, win *w, int x, int y, int wid, int hei, double opacity pict, (w ? w->paint.ptex: ps->root_tile_paint.ptex), reg_paint); } +static inline void +set_tgt_clip(session_t *ps, XserverRegion reg) { + switch (ps->o.backend) { + case BKEND_XRENDER: + XFixesSetPictureClipRegion(ps->dpy, ps->tgt_buffer, 0, 0, reg); + break; +#ifdef CONFIG_VSYNC_OPENGL + case BKEND_GLX: + glx_set_clip(ps, reg); + break; +#endif + } +} + static void paint_all(session_t *ps, XserverRegion region, win *t); diff --git a/src/dbus.c b/src/dbus.c index 4dd6143..25b37fd 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -876,6 +876,11 @@ cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_reply_string(ps, msg, VSYNC_STRS[ps->o.vsync]); return true; } + if (!strcmp("backend", target)) { + assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0])); + cdbus_reply_string(ps, msg, BACKEND_STRS[ps->o.backend]); + return true; + } cdbus_m_opts_get_do(dbe, cdbus_reply_bool); cdbus_m_opts_get_do(vsync_aggressive, cdbus_reply_bool); diff --git a/src/opengl.c b/src/opengl.c index 16e99de..2ec0b33 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -51,7 +51,7 @@ glx_init(session_t *ps, bool need_render) { } // Ensure GLX_EXT_texture_from_pixmap exists - if (need_render && !glx_hasext(ps, "GLX_EXT_texture_from_pixmap")) + if (need_render && !glx_hasglxext(ps, "GLX_EXT_texture_from_pixmap")) goto glx_init_end; // Get GLX context @@ -68,6 +68,24 @@ glx_init(session_t *ps, bool need_render) { goto glx_init_end; } + // Ensure we have a stencil buffer. X Fixes does not guarantee rectangles + // in regions don't overlap, so we must use stencil buffer to make sure + // we don't paint a region for more than one time, I think? + if (need_render) { + GLint val = 0; + glGetIntegerv(GL_STENCIL_BITS, &val); + if (!val) { + printf_errf("(): Target window doesn't have stencil buffer."); + goto glx_init_end; + } + } + + // Check GL_ARB_texture_non_power_of_two, requires a GLX context and + // must precede FBConfig fetching + if (need_render) + ps->glx_has_texture_non_power_of_two = glx_hasglext(ps, + "GL_ARB_texture_non_power_of_two"); + // Acquire function addresses if (need_render) { ps->glXBindTexImageProc = (f_BindTexImageEXT) @@ -85,24 +103,23 @@ glx_init(session_t *ps, bool need_render) { goto glx_init_end; if (need_render) { - // Adjust viewport - glViewport(0, 0, ps->root_width, ps->root_height); + glx_on_root_change(ps); - // Initialize settings, copied from dcompmgr - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, ps->root_width, ps->root_height, 0, -100.0, 100.0); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); // glEnable(GL_DEPTH_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); + // Initialize stencil buffer + glClear(GL_STENCIL_BUFFER_BIT); + glEnable(GL_STENCIL_TEST); + glStencilMask(0x1); + glStencilFunc(GL_EQUAL, 0x1, 0x1); + // Clear screen glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glXSwapBuffers(ps->dpy, get_tgt_window(ps)); + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // glXSwapBuffers(ps->dpy, get_tgt_window(ps)); } success = true; @@ -141,6 +158,34 @@ glx_destroy(session_t *ps) { void glx_on_root_change(session_t *ps) { glViewport(0, 0, ps->root_width, ps->root_height); + + // Initialize matrix, copied from dcompmgr + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, ps->root_width, ps->root_height, 0, -100.0, 100.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +/** + * Initialize GLX blur filter. + */ +bool +glx_init_blur(session_t *ps) { + printf_errf("(): Blur on GLX backend isn't implemented yet, sorry."); + return false; + +// #ifdef CONFIG_VSYNC_OPENGL_GLSL +// static const char *FRAG_SHADER_BLUR = ""; +// GLuint frag_shader = glx_create_shader(GL_FRAGMENT_SHADER, FRAG_SHADER_BLUR); +// if (!frag_shader) { +// printf_errf("(): Failed to create fragment shader for blurring."); +// return false; +// } +// #else +// printf_errf("(): GLSL support not compiled in. Cannot do blur with GLX backend."); +// return false; +// #endif } /** @@ -346,15 +391,28 @@ glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, Pixmap pixmap, if (!ptex->glpixmap) { need_release = false; + // Determine texture target, copied from compiz + GLint tex_tgt = 0; + if (GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts + && ps->glx_has_texture_non_power_of_two) + tex_tgt = GLX_TEXTURE_2D_EXT; + else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & pcfg->texture_tgts) + tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; + else if (!(GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts)) + tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; + else + tex_tgt = GLX_TEXTURE_2D_EXT; + #ifdef DEBUG_GLX - printf_dbgf("(): depth %d rgba %d\n", depth, (GLX_TEXTURE_FORMAT_RGBA_EXT == pcfg->texture_fmt)); + printf_dbgf("(): depth %d, tgt %#x, rgba %d\n", depth, tex_tgt, + (GLX_TEXTURE_FORMAT_RGBA_EXT == pcfg->texture_fmt)); #endif - int attrs[] = { + GLint attrs[] = { GLX_TEXTURE_FORMAT_EXT, pcfg->texture_fmt, - // GLX_TEXTURE_TARGET_EXT, - // , + GLX_TEXTURE_TARGET_EXT, + tex_tgt, 0, }; @@ -403,6 +461,51 @@ glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { } } +/** + * Set clipping region on the target window. + */ +void +glx_set_clip(session_t *ps, XserverRegion reg) { + glClear(GL_STENCIL_BUFFER_BIT); + + if (reg) { + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDepthMask(GL_FALSE); + glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); + + int nrects = 0; + XRectangle *rects = XFixesFetchRegion(ps->dpy, reg, &nrects); + + glBegin(GL_QUADS); + + for (int i = 0; i < nrects; ++i) { + GLint rx = rects[i].x; + GLint ry = rects[i].y; + GLint rxe = rx + rects[i].width; + GLint rye = ry + rects[i].height; + GLint z = 0; + +#ifdef DEBUG_GLX + printf_dbgf("(): Rect %d: %f, %f, %f, %f\n", i, rx, ry, rxe, rye); +#endif + + glVertex3i(rx, ry, z); + glVertex3i(rxe, ry, z); + glVertex3i(rxe, rye, z); + glVertex3i(rx, rye, z); + } + + glEnd(); + + if (rects) + XFree(rects); + + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + } +} + /** * @brief Render a region with texture data. */ @@ -440,11 +543,6 @@ glx_render(session_t *ps, const glx_texture_t *ptex, } } - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, ptex->texture); - - glBegin(GL_QUADS); - { XserverRegion reg_new = None; @@ -459,9 +557,10 @@ glx_render(session_t *ps, const glx_texture_t *ptex, int nrects = 1; #ifdef DEBUG_GLX - printf_dbgf("(): Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d \n", x, y, width, height, dx, dy, ptex->width, ptex->height, z); + printf_dbgf("(): Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", x, y, width, height, dx, dy, ptex->width, ptex->height, z); #endif + /* if (reg_tgt) { reg_new = XFixesCreateRegion(ps->dpy, &rec_all, 1); XFixesIntersectRegion(ps->dpy, reg_new, reg_new, reg_tgt); @@ -469,6 +568,12 @@ glx_render(session_t *ps, const glx_texture_t *ptex, nrects = 0; rects = XFixesFetchRegion(ps->dpy, reg_new, &nrects); } + */ + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ptex->texture); + + glBegin(GL_QUADS); for (int i = 0; i < nrects; ++i) { GLfloat rx = (double) (rects[i].x - dx + x) / ptex->width; @@ -504,11 +609,12 @@ glx_render(session_t *ps, const glx_texture_t *ptex, glVertex3i(rdx, rdye, z); } + glEnd(); + if (rects && rects != &rec_all) XFree(rects); free_region(ps, ®_new); } - glEnd(); // Cleanup glBindTexture(GL_TEXTURE_2D, 0); @@ -519,3 +625,85 @@ glx_render(session_t *ps, const glx_texture_t *ptex, return true; } + +#ifdef CONFIG_VSYNC_OPENGL_GLSL +GLuint +glx_create_shader(GLenum shader_type, const char *shader_str) { + bool success = false; + GLuint shader = glCreateShader(shader_type); + if (!shader) { + printf_errf("(): Failed to create shader with type %d.", shader_type); + goto glx_create_shader_end; + } + glShaderSource(shader, 1, &shader_str, NULL); + glCompileShader(shader); + + // Get shader status + { + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetShaderInfoLog(shader, log_len, NULL, log); + printf_errf("(): Failed to compile shader with type %d: %s", + shader_type, log); + } + goto glx_create_shader_end; + } + } + + success = true; + +glx_create_shader_end: + if (shader && !success) { + glDeleteShader(shader); + shader = 0; + } + + return shader; +} + +GLuint +glx_create_program(GLenum shader_type, const GLuint * const shaders, + int nshaders) { + bool success = false; + GLuint program = glCreateProgram(); + if (!program) { + printf_errf("(): Failed to create program."); + goto glx_create_program_end; + } + + for (int i = 0; i < nshaders; ++i) + glAttachShader(program, shaders[i]); + glLinkProgram(program); + + // Get program status + { + GLint status = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetProgramInfoLog(program, log_len, NULL, log); + printf_errf("(): Failed to link program: %s", log); + } + goto glx_create_program_end; + } + } + success = true; + +glx_create_program_end: + if (program && !success) { + glDeleteProgram(program); + program = 0; + } + + return program; +} +#endif + diff --git a/src/opengl.h b/src/opengl.h index b48b441..7f6dae6 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -12,22 +12,62 @@ #include +/** + * Check if a word is in string. + */ +static inline bool +wd_is_in_str(const char *haystick, const char *needle) { + if (!haystick) + return false; + + assert(*needle); + + const char *pos = haystick - 1; + while ((pos = strstr(pos + 1, needle))) { + // Continue if it isn't a word boundary + if (((pos - haystick) && !isspace(*(pos - 1))) + || (strlen(pos) > strlen(needle) && !isspace(pos[strlen(needle)]))) + continue; + return true; + } + + return false; +} + /** * Check if a GLX extension exists. */ static inline bool -glx_hasext(session_t *ps, const char *ext) { +glx_hasglxext(session_t *ps, const char *ext) { const char *glx_exts = glXQueryExtensionsString(ps->dpy, ps->scr); - const char *pos = strstr(glx_exts, ext); - // Make sure the extension string is matched as a whole word - if (!pos - || ((pos - glx_exts) && !isspace(*(pos - 1))) - || (strlen(pos) > strlen(ext) && !isspace(pos[strlen(ext)]))) { - printf_errf("(): Missing OpenGL extension %s.", ext); + if (!glx_exts) { + printf_errf("(): Failed get GLX extension list."); return false; } - return true; + bool found = wd_is_in_str(glx_exts, ext); + if (!found) + printf_errf("(): Missing GLX extension %s.", ext); + + return found; +} + +/** + * Check if a GLX extension exists. + */ +static inline bool +glx_hasglext(session_t *ps, const char *ext) { + const char *gl_exts = (const char *) glGetString(GL_EXTENSIONS); + if (!gl_exts) { + printf_errf("(): Failed get GL extension list."); + return false; + } + + bool found = wd_is_in_str(gl_exts, ext); + if (!found) + printf_errf("(): Missing GL extension %s.", ext); + + return found; } static inline XVisualInfo *