summaryrefslogtreecommitdiffstats
path: root/twin/compton-tde/opengl.c
diff options
context:
space:
mode:
Diffstat (limited to 'twin/compton-tde/opengl.c')
-rw-r--r--twin/compton-tde/opengl.c1941
1 files changed, 1941 insertions, 0 deletions
diff --git a/twin/compton-tde/opengl.c b/twin/compton-tde/opengl.c
new file mode 100644
index 000000000..2b9ff6286
--- /dev/null
+++ b/twin/compton-tde/opengl.c
@@ -0,0 +1,1941 @@
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>
+ * See LICENSE for more information.
+ *
+ */
+
+#include "opengl.h"
+
+#ifdef CONFIG_GLX_SYNC
+void
+xr_glx_sync(session_t *ps, Drawable d, XSyncFence *pfence) {
+ if (*pfence) {
+ // GLsync sync = ps->psglx->glFenceSyncProc(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ GLsync sync = ps->psglx->glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, *pfence, 0);
+ /* GLenum ret = ps->psglx->glClientWaitSyncProc(sync, GL_SYNC_FLUSH_COMMANDS_BIT,
+ 1000);
+ assert(GL_CONDITION_SATISFIED == ret); */
+ XSyncTriggerFence(ps->dpy, *pfence);
+ XFlush(ps->dpy);
+ ps->psglx->glWaitSyncProc(sync, 0, GL_TIMEOUT_IGNORED);
+ // ps->psglx->glDeleteSyncProc(sync);
+ // XSyncResetFence(ps->dpy, *pfence);
+ }
+ glx_check_err(ps);
+}
+#endif
+
+static inline GLXFBConfig
+get_fbconfig_from_visualinfo(session_t *ps, const XVisualInfo *visualinfo) {
+ int nelements = 0;
+ GLXFBConfig *fbconfigs = glXGetFBConfigs(ps->dpy, visualinfo->screen,
+ &nelements);
+ for (int i = 0; i < nelements; ++i) {
+ int visual_id = 0;
+ if (Success == glXGetFBConfigAttrib(ps->dpy, fbconfigs[i], GLX_VISUAL_ID, &visual_id)
+ && visual_id == visualinfo->visualid)
+ return fbconfigs[i];
+ }
+
+ return NULL;
+}
+
+#ifdef DEBUG_GLX_DEBUG_CONTEXT
+static void
+glx_debug_msg_callback(GLenum source, GLenum type,
+ GLuint id, GLenum severity, GLsizei length, const GLchar *message,
+ GLvoid *userParam) {
+ printf_dbgf("(): source 0x%04X, type 0x%04X, id %u, severity 0x%0X, \"%s\"\n",
+ source, type, id, severity, message);
+}
+#endif
+
+/**
+ * Initialize OpenGL.
+ */
+bool
+glx_init(session_t *ps, bool need_render) {
+ bool success = false;
+ XVisualInfo *pvis = NULL;
+
+ // Check for GLX extension
+ if (!ps->glx_exists) {
+ if (glXQueryExtension(ps->dpy, &ps->glx_event, &ps->glx_error))
+ ps->glx_exists = true;
+ else {
+ printf_errf("(): No GLX extension.");
+ goto glx_init_end;
+ }
+ }
+
+ // Get XVisualInfo
+ pvis = get_visualinfo_from_visual(ps, ps->vis);
+ if (!pvis) {
+ printf_errf("(): Failed to acquire XVisualInfo for current visual.");
+ goto glx_init_end;
+ }
+
+ // Ensure the visual is double-buffered
+ if (need_render) {
+ int value = 0;
+ if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
+ printf_errf("(): Root visual is not a GL visual.");
+ goto glx_init_end;
+ }
+
+ if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value)
+ || !value) {
+ printf_errf("(): Root visual is not a double buffered GL visual.");
+ goto glx_init_end;
+ }
+ }
+
+ // Ensure GLX_EXT_texture_from_pixmap exists
+ if (need_render && !glx_hasglxext(ps, "GLX_EXT_texture_from_pixmap"))
+ goto glx_init_end;
+
+ // Initialize GLX data structure
+ if (!ps->psglx) {
+ static const glx_session_t CGLX_SESSION_DEF = CGLX_SESSION_INIT;
+ ps->psglx = cmalloc(1, glx_session_t);
+ memcpy(ps->psglx, &CGLX_SESSION_DEF, sizeof(glx_session_t));
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ ppass->unifm_factor_center = -1;
+ ppass->unifm_offset_x = -1;
+ ppass->unifm_offset_y = -1;
+ }
+#endif
+ }
+
+ glx_session_t *psglx = ps->psglx;
+
+ if (!psglx->context) {
+ // Get GLX context
+#ifndef DEBUG_GLX_DEBUG_CONTEXT
+ psglx->context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE);
+#else
+ {
+ GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis);
+ if (!fbconfig) {
+ printf_errf("(): Failed to get GLXFBConfig for root visual %#lx.",
+ pvis->visualid);
+ goto glx_init_end;
+ }
+
+ f_glXCreateContextAttribsARB p_glXCreateContextAttribsARB =
+ (f_glXCreateContextAttribsARB)
+ glXGetProcAddress((const GLubyte *) "glXCreateContextAttribsARB");
+ if (!p_glXCreateContextAttribsARB) {
+ printf_errf("(): Failed to get glXCreateContextAttribsARB().");
+ goto glx_init_end;
+ }
+
+ static const int attrib_list[] = {
+ GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB,
+ None
+ };
+ psglx->context = p_glXCreateContextAttribsARB(ps->dpy, fbconfig, NULL,
+ GL_TRUE, attrib_list);
+ }
+#endif
+
+ if (!psglx->context) {
+ printf_errf("(): Failed to get GLX context.");
+ goto glx_init_end;
+ }
+
+ // Attach GLX context
+ if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), psglx->context)) {
+ printf_errf("(): Failed to attach GLX context.");
+ goto glx_init_end;
+ }
+
+#ifdef DEBUG_GLX_DEBUG_CONTEXT
+ {
+ f_DebugMessageCallback p_DebugMessageCallback =
+ (f_DebugMessageCallback)
+ glXGetProcAddress((const GLubyte *) "glDebugMessageCallback");
+ if (!p_DebugMessageCallback) {
+ printf_errf("(): Failed to get glDebugMessageCallback(0.");
+ goto glx_init_end;
+ }
+ p_DebugMessageCallback(glx_debug_msg_callback, ps);
+ }
+#endif
+
+ }
+
+ // 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 && !ps->o.glx_no_stencil) {
+ 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)
+ psglx->has_texture_non_power_of_two = glx_hasglext(ps,
+ "GL_ARB_texture_non_power_of_two");
+
+ // Acquire function addresses
+ if (need_render) {
+#ifdef DEBUG_GLX_MARK
+ psglx->glStringMarkerGREMEDY = (f_StringMarkerGREMEDY)
+ glXGetProcAddress((const GLubyte *) "glStringMarkerGREMEDY");
+ psglx->glFrameTerminatorGREMEDY = (f_FrameTerminatorGREMEDY)
+ glXGetProcAddress((const GLubyte *) "glFrameTerminatorGREMEDY");
+#endif
+
+ psglx->glXBindTexImageProc = (f_BindTexImageEXT)
+ glXGetProcAddress((const GLubyte *) "glXBindTexImageEXT");
+ psglx->glXReleaseTexImageProc = (f_ReleaseTexImageEXT)
+ glXGetProcAddress((const GLubyte *) "glXReleaseTexImageEXT");
+ if (!psglx->glXBindTexImageProc || !psglx->glXReleaseTexImageProc) {
+ printf_errf("(): Failed to acquire glXBindTexImageEXT() / glXReleaseTexImageEXT().");
+ goto glx_init_end;
+ }
+
+ if (ps->o.glx_use_copysubbuffermesa) {
+ psglx->glXCopySubBufferProc = (f_CopySubBuffer)
+ glXGetProcAddress((const GLubyte *) "glXCopySubBufferMESA");
+ if (!psglx->glXCopySubBufferProc) {
+ printf_errf("(): Failed to acquire glXCopySubBufferMESA().");
+ goto glx_init_end;
+ }
+ }
+
+#ifdef CONFIG_GLX_SYNC
+ psglx->glFenceSyncProc = (f_FenceSync)
+ glXGetProcAddress((const GLubyte *) "glFenceSync");
+ psglx->glIsSyncProc = (f_IsSync)
+ glXGetProcAddress((const GLubyte *) "glIsSync");
+ psglx->glDeleteSyncProc = (f_DeleteSync)
+ glXGetProcAddress((const GLubyte *) "glDeleteSync");
+ psglx->glClientWaitSyncProc = (f_ClientWaitSync)
+ glXGetProcAddress((const GLubyte *) "glClientWaitSync");
+ psglx->glWaitSyncProc = (f_WaitSync)
+ glXGetProcAddress((const GLubyte *) "glWaitSync");
+ psglx->glImportSyncEXT = (f_ImportSyncEXT)
+ glXGetProcAddress((const GLubyte *) "glImportSyncEXT");
+ if (!psglx->glFenceSyncProc || !psglx->glIsSyncProc || !psglx->glDeleteSyncProc
+ || !psglx->glClientWaitSyncProc || !psglx->glWaitSyncProc
+ || !psglx->glImportSyncEXT) {
+ printf_errf("(): Failed to acquire GLX sync functions.");
+ goto glx_init_end;
+ }
+#endif
+ }
+
+ // Acquire FBConfigs
+ if (need_render && !glx_update_fbconfig(ps))
+ goto glx_init_end;
+
+ // Render preparations
+ if (need_render) {
+ glx_on_root_change(ps);
+
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glDisable(GL_BLEND);
+
+ if (!ps->o.glx_no_stencil) {
+ // Initialize stencil buffer
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glDisable(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));
+ }
+
+ success = true;
+
+glx_init_end:
+ cxfree(pvis);
+
+ if (!success)
+ glx_destroy(ps);
+
+ return success;
+}
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+
+static void
+glx_free_prog_main(session_t *ps, glx_prog_main_t *pprogram) {
+ if (!pprogram)
+ return;
+ if (pprogram->prog) {
+ glDeleteProgram(pprogram->prog);
+ pprogram->prog = 0;
+ }
+ pprogram->unifm_opacity = -1;
+ pprogram->unifm_invert_color = -1;
+ pprogram->unifm_tex = -1;
+}
+
+#endif
+
+/**
+ * Destroy GLX related resources.
+ */
+void
+glx_destroy(session_t *ps) {
+ if (!ps->psglx)
+ return;
+
+ // Free all GLX resources of windows
+ for (win *w = ps->list; w; w = w->next)
+ free_win_res_glx(ps, w);
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ // Free GLSL shaders/programs
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ if (ppass->frag_shader)
+ glDeleteShader(ppass->frag_shader);
+ if (ppass->prog)
+ glDeleteProgram(ppass->prog);
+ }
+
+ glx_free_prog_main(ps, &ps->o.glx_prog_win);
+
+ glx_check_err(ps);
+#endif
+
+ // Free FBConfigs
+ for (int i = 0; i <= OPENGL_MAX_DEPTH; ++i) {
+ free(ps->psglx->fbconfigs[i]);
+ ps->psglx->fbconfigs[i] = NULL;
+ }
+
+ // Destroy GLX context
+ if (ps->psglx->context) {
+ glXDestroyContext(ps->dpy, ps->psglx->context);
+ ps->psglx->context = NULL;
+ }
+
+ free(ps->psglx);
+ ps->psglx = NULL;
+}
+
+/**
+ * Reinitialize GLX.
+ */
+bool
+glx_reinit(session_t *ps, bool need_render) {
+ // Reinitialize VSync as well
+ vsync_deinit(ps);
+
+ glx_destroy(ps);
+ if (!glx_init(ps, need_render)) {
+ printf_errf("(): Failed to initialize GLX.");
+ return false;
+ }
+
+ if (!vsync_init(ps)) {
+ printf_errf("(): Failed to initialize VSync.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to run on root window size change.
+ */
+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, 0, ps->root_height, -1000.0, 1000.0);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
+/**
+ * Initialize GLX blur filter.
+ */
+bool
+glx_init_blur(session_t *ps) {
+ assert(ps->o.blur_kerns[0]);
+
+ // Allocate PBO if more than one blur kernel is present
+ if (ps->o.blur_kerns[1]) {
+#ifdef CONFIG_VSYNC_OPENGL_FBO
+ // Try to generate a framebuffer
+ GLuint fbo = 0;
+ glGenFramebuffers(1, &fbo);
+ if (!fbo) {
+ printf_errf("(): Failed to generate Framebuffer. Cannot do "
+ "multi-pass blur with GLX backend.");
+ return false;
+ }
+ glDeleteFramebuffers(1, &fbo);
+#else
+ printf_errf("(): FBO support not compiled in. Cannot do multi-pass blur "
+ "with GLX backend.");
+ return false;
+#endif
+ }
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ {
+ char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ static const char *FRAG_SHADER_BLUR_PREFIX =
+ "#version 110\n"
+ "%s"
+ "uniform float offset_x;\n"
+ "uniform float offset_y;\n"
+ "uniform float factor_center;\n"
+ "uniform %s tex_scr;\n"
+ "\n"
+ "void main() {\n"
+ " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);\n";
+ static const char *FRAG_SHADER_BLUR_ADD =
+ " sum += float(%.7g) * %s(tex_scr, vec2(gl_TexCoord[0].x + offset_x * float(%d), gl_TexCoord[0].y + offset_y * float(%d)));\n";
+ static const char *FRAG_SHADER_BLUR_ADD_GPUSHADER4 =
+ " sum += float(%.7g) * %sOffset(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y), ivec2(%d, %d));\n";
+ static const char *FRAG_SHADER_BLUR_SUFFIX =
+ " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * factor_center;\n"
+ " gl_FragColor = sum / (factor_center + float(%.7g));\n"
+ "}\n";
+
+ const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two;
+ const char *sampler_type = (use_texture_rect ?
+ "sampler2DRect": "sampler2D");
+ const char *texture_func = (use_texture_rect ?
+ "texture2DRect": "texture2D");
+ const char *shader_add = FRAG_SHADER_BLUR_ADD;
+ char *extension = mstrcpy("");
+ if (use_texture_rect)
+ mstrextend(&extension, "#extension GL_ARB_texture_rectangle : require\n");
+ if (ps->o.glx_use_gpushader4) {
+ mstrextend(&extension, "#extension GL_EXT_gpu_shader4 : require\n");
+ shader_add = FRAG_SHADER_BLUR_ADD_GPUSHADER4;
+ }
+
+ for (int i = 0; i < MAX_BLUR_PASS && ps->o.blur_kerns[i]; ++i) {
+ XFixed *kern = ps->o.blur_kerns[i];
+ if (!kern)
+ break;
+
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+
+ // Build shader
+ {
+ int wid = XFixedToDouble(kern[0]), hei = XFixedToDouble(kern[1]);
+ int nele = wid * hei - 1;
+ int len = strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) + strlen(extension) + (strlen(shader_add) + strlen(texture_func) + 42) * nele + strlen(FRAG_SHADER_BLUR_SUFFIX) + strlen(texture_func) + 12 + 1;
+ char *shader_str = calloc(len, sizeof(char));
+ if (!shader_str) {
+ printf_errf("(): Failed to allocate %d bytes for shader string.", len);
+ return false;
+ }
+ {
+ char *pc = shader_str;
+ sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type);
+ pc += strlen(pc);
+ assert(strlen(shader_str) < len);
+
+ double sum = 0.0;
+ for (int j = 0; j < hei; ++j) {
+ for (int k = 0; k < wid; ++k) {
+ if (hei / 2 == j && wid / 2 == k)
+ continue;
+ double val = XFixedToDouble(kern[2 + j * wid + k]);
+ if (0.0 == val)
+ continue;
+ sum += val;
+ sprintf(pc, shader_add, val, texture_func, k - wid / 2, j - hei / 2);
+ pc += strlen(pc);
+ assert(strlen(shader_str) < len);
+ }
+ }
+
+ sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum);
+ assert(strlen(shader_str) < len);
+ }
+ ppass->frag_shader = glx_create_shader(GL_FRAGMENT_SHADER, shader_str);
+ free(shader_str);
+ }
+
+ if (!ppass->frag_shader) {
+ printf_errf("(): Failed to create fragment shader %d.", i);
+ return false;
+ }
+
+ // Build program
+ ppass->prog = glx_create_program(&ppass->frag_shader, 1);
+ if (!ppass->prog) {
+ printf_errf("(): Failed to create GLSL program.");
+ return false;
+ }
+
+ // Get uniform addresses
+#define P_GET_UNIFM_LOC(name, target) { \
+ ppass->target = glGetUniformLocation(ppass->prog, name); \
+ if (ppass->target < 0) { \
+ printf_errf("(): Failed to get location of %d-th uniform '" name "'. Might be troublesome.", i); \
+ } \
+ }
+
+ P_GET_UNIFM_LOC("factor_center", unifm_factor_center);
+ if (!ps->o.glx_use_gpushader4) {
+ P_GET_UNIFM_LOC("offset_x", unifm_offset_x);
+ P_GET_UNIFM_LOC("offset_y", unifm_offset_y);
+ }
+
+#undef P_GET_UNIFM_LOC
+ }
+ free(extension);
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+ }
+
+
+ glx_check_err(ps);
+
+ return true;
+#else
+ printf_errf("(): GLSL support not compiled in. Cannot do blur with GLX backend.");
+ return false;
+#endif
+}
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+
+/**
+ * Load a GLSL main program from shader strings.
+ */
+bool
+glx_load_prog_main(session_t *ps,
+ const char *vshader_str, const char *fshader_str,
+ glx_prog_main_t *pprogram) {
+ assert(pprogram);
+
+ // Build program
+ pprogram->prog = glx_create_program_from_str(vshader_str, fshader_str);
+ if (!pprogram->prog) {
+ printf_errf("(): Failed to create GLSL program.");
+ return false;
+ }
+
+ // Get uniform addresses
+#define P_GET_UNIFM_LOC(name, target) { \
+ pprogram->target = glGetUniformLocation(pprogram->prog, name); \
+ if (pprogram->target < 0) { \
+ printf_errf("(): Failed to get location of uniform '" name "'. Might be troublesome."); \
+ } \
+ }
+ P_GET_UNIFM_LOC("opacity", unifm_opacity);
+ P_GET_UNIFM_LOC("invert_color", unifm_invert_color);
+ P_GET_UNIFM_LOC("tex", unifm_tex);
+#undef P_GET_UNIFM_LOC
+
+ glx_check_err(ps);
+
+ return true;
+}
+
+#endif
+
+/**
+ * @brief Update the FBConfig of given depth.
+ */
+static inline void
+glx_update_fbconfig_bydepth(session_t *ps, int depth, glx_fbconfig_t *pfbcfg) {
+ // Make sure the depth is sane
+ if (depth < 0 || depth > OPENGL_MAX_DEPTH)
+ return;
+
+ // Compare new FBConfig with current one
+ if (glx_cmp_fbconfig(ps, ps->psglx->fbconfigs[depth], pfbcfg) < 0) {
+#ifdef DEBUG_GLX
+ printf_dbgf("(%d): %#x overrides %#x, target %#x.\n", depth, (unsigned) pfbcfg->cfg, (ps->psglx->fbconfigs[depth] ? (unsigned) ps->psglx->fbconfigs[depth]->cfg: 0), pfbcfg->texture_tgts);
+#endif
+ if (!ps->psglx->fbconfigs[depth]) {
+ ps->psglx->fbconfigs[depth] = malloc(sizeof(glx_fbconfig_t));
+ allocchk(ps->psglx->fbconfigs[depth]);
+ }
+ (*ps->psglx->fbconfigs[depth]) = *pfbcfg;
+ }
+}
+
+/**
+ * Get GLX FBConfigs for all depths.
+ */
+static bool
+glx_update_fbconfig(session_t *ps) {
+ // Acquire all FBConfigs and loop through them
+ int nele = 0;
+ GLXFBConfig* pfbcfgs = glXGetFBConfigs(ps->dpy, ps->scr, &nele);
+
+ for (GLXFBConfig *pcur = pfbcfgs; pcur < pfbcfgs + nele; pcur++) {
+ glx_fbconfig_t fbinfo = {
+ .cfg = *pcur,
+ .texture_fmt = 0,
+ .texture_tgts = 0,
+ .y_inverted = false,
+ };
+ int id = (int) (pcur - pfbcfgs);
+ int depth = 0, depth_alpha = 0, val = 0;
+
+ // Skip over multi-sampled visuals
+ // http://people.freedesktop.org/~glisse/0001-glx-do-not-use-multisample-visual-config-for-front-o.patch
+#ifdef GLX_SAMPLES
+ if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_SAMPLES, &val)
+ && val > 1)
+ continue;
+#endif
+
+ if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BUFFER_SIZE, &depth)
+ || Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_ALPHA_SIZE, &depth_alpha)) {
+ printf_errf("(): Failed to retrieve buffer size and alpha size of FBConfig %d.", id);
+ continue;
+ }
+ if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &fbinfo.texture_tgts)) {
+ printf_errf("(): Failed to retrieve BIND_TO_TEXTURE_TARGETS_EXT of FBConfig %d.", id);
+ continue;
+ }
+
+ int visualdepth = 0;
+ {
+ XVisualInfo *pvi = glXGetVisualFromFBConfig(ps->dpy, *pcur);
+ if (!pvi) {
+ // On nvidia-drivers-325.08 this happens slightly too often...
+ // printf_errf("(): Failed to retrieve X Visual of FBConfig %d.", id);
+ continue;
+ }
+ visualdepth = pvi->depth;
+ cxfree(pvi);
+ }
+
+ bool rgb = false;
+ bool rgba = false;
+
+ if (depth >= 32 && depth_alpha && Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_RGBA_EXT, &val) && val)
+ rgba = true;
+
+ if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BIND_TO_TEXTURE_RGB_EXT, &val) && val)
+ rgb = true;
+
+ if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_Y_INVERTED_EXT, &val))
+ fbinfo.y_inverted = val;
+
+ {
+ int tgtdpt = depth - depth_alpha;
+ if (tgtdpt == visualdepth && tgtdpt < 32 && rgb) {
+ fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGB_EXT;
+ glx_update_fbconfig_bydepth(ps, tgtdpt, &fbinfo);
+ }
+ }
+
+ if (depth == visualdepth && rgba) {
+ fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGBA_EXT;
+ glx_update_fbconfig_bydepth(ps, depth, &fbinfo);
+ }
+ }
+
+ cxfree(pfbcfgs);
+
+ // Sanity checks
+ if (!ps->psglx->fbconfigs[ps->depth]) {
+ printf_errf("(): No FBConfig found for default depth %d.", ps->depth);
+ return false;
+ }
+
+ if (!ps->psglx->fbconfigs[32]) {
+ printf_errf("(): No FBConfig found for depth 32. Expect crazy things.");
+ }
+
+#ifdef DEBUG_GLX
+ printf_dbgf("(): %d-bit: %#3x, 32-bit: %#3x\n",
+ ps->depth, (int) ps->psglx->fbconfigs[ps->depth]->cfg,
+ (int) ps->psglx->fbconfigs[32]->cfg);
+#endif
+
+ return true;
+}
+
+static inline int
+glx_cmp_fbconfig_cmpattr(session_t *ps,
+ const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b,
+ int attr) {
+ int attr_a = 0, attr_b = 0;
+
+ // TODO: Error checking
+ glXGetFBConfigAttrib(ps->dpy, pfbc_a->cfg, attr, &attr_a);
+ glXGetFBConfigAttrib(ps->dpy, pfbc_b->cfg, attr, &attr_b);
+
+ return attr_a - attr_b;
+}
+
+/**
+ * Compare two GLX FBConfig's to find the preferred one.
+ */
+static int
+glx_cmp_fbconfig(session_t *ps,
+ const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b) {
+ int result = 0;
+
+ if (!pfbc_a)
+ return -1;
+ if (!pfbc_b)
+ return 1;
+
+#define P_CMPATTR_LT(attr) { if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) return -result; }
+#define P_CMPATTR_GT(attr) { if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) return result; }
+
+ P_CMPATTR_LT(GLX_BIND_TO_TEXTURE_RGBA_EXT);
+ P_CMPATTR_LT(GLX_DOUBLEBUFFER);
+ P_CMPATTR_LT(GLX_STENCIL_SIZE);
+ P_CMPATTR_LT(GLX_DEPTH_SIZE);
+ P_CMPATTR_GT(GLX_BIND_TO_MIPMAP_TEXTURE_EXT);
+
+ return 0;
+}
+
+/**
+ * Bind a X pixmap to an OpenGL texture.
+ */
+bool
+glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, Pixmap pixmap,
+ unsigned width, unsigned height, unsigned depth) {
+ if (!pixmap) {
+ printf_errf("(%#010lx): Binding to an empty pixmap. This can't work.",
+ pixmap);
+ return false;
+ }
+
+ glx_texture_t *ptex = *pptex;
+ bool need_release = true;
+
+ // Allocate structure
+ if (!ptex) {
+ static const glx_texture_t GLX_TEX_DEF = {
+ .texture = 0,
+ .glpixmap = 0,
+ .pixmap = 0,
+ .target = 0,
+ .width = 0,
+ .height = 0,
+ .depth = 0,
+ .y_inverted = false,
+ };
+
+ ptex = malloc(sizeof(glx_texture_t));
+ allocchk(ptex);
+ memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t));
+ *pptex = ptex;
+ }
+
+ // Release pixmap if parameters are inconsistent
+ if (ptex->texture && ptex->pixmap != pixmap) {
+ glx_release_pixmap(ps, ptex);
+ }
+
+ // Create GLX pixmap
+ if (!ptex->glpixmap) {
+ need_release = false;
+
+ // Retrieve pixmap parameters, if they aren't provided
+ if (!(width && height && depth)) {
+ Window rroot = None;
+ int rx = 0, ry = 0;
+ unsigned rbdwid = 0;
+ if (!XGetGeometry(ps->dpy, pixmap, &rroot, &rx, &ry,
+ &width, &height, &rbdwid, &depth)) {
+ printf_errf("(%#010lx): Failed to query Pixmap info.", pixmap);
+ return false;
+ }
+ if (depth > OPENGL_MAX_DEPTH) {
+ printf_errf("(%d): Requested depth higher than %d.", depth,
+ OPENGL_MAX_DEPTH);
+ return false;
+ }
+ }
+
+ const glx_fbconfig_t *pcfg = ps->psglx->fbconfigs[depth];
+ if (!pcfg) {
+ printf_errf("(%d): Couldn't find FBConfig with requested depth.", depth);
+ return false;
+ }
+
+ // Determine texture target, copied from compiz
+ // The assumption we made here is the target never changes based on any
+ // pixmap-specific parameters, and this may change in the future
+ GLenum tex_tgt = 0;
+ if (GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts
+ && ps->psglx->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, tgt %#x, rgba %d\n", depth, tex_tgt,
+ (GLX_TEXTURE_FORMAT_RGBA_EXT == pcfg->texture_fmt));
+#endif
+
+ GLint attrs[] = {
+ GLX_TEXTURE_FORMAT_EXT,
+ pcfg->texture_fmt,
+ GLX_TEXTURE_TARGET_EXT,
+ tex_tgt,
+ 0,
+ };
+
+ ptex->glpixmap = glXCreatePixmap(ps->dpy, pcfg->cfg, pixmap, attrs);
+ ptex->pixmap = pixmap;
+ ptex->target = (GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D:
+ GL_TEXTURE_RECTANGLE);
+ ptex->width = width;
+ ptex->height = height;
+ ptex->depth = depth;
+ ptex->y_inverted = pcfg->y_inverted;
+ }
+ if (!ptex->glpixmap) {
+ printf_errf("(): Failed to allocate GLX pixmap.");
+ return false;
+ }
+
+ glEnable(ptex->target);
+
+ // Create texture
+ if (!ptex->texture) {
+ need_release = false;
+
+ GLuint texture = 0;
+ glGenTextures(1, &texture);
+ glBindTexture(ptex->target, texture);
+
+ glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glBindTexture(ptex->target, 0);
+
+ ptex->texture = texture;
+ }
+ if (!ptex->texture) {
+ printf_errf("(): Failed to allocate texture.");
+ return false;
+ }
+
+ glBindTexture(ptex->target, ptex->texture);
+
+ // The specification requires rebinding whenever the content changes...
+ // We can't follow this, too slow.
+ if (need_release)
+ ps->psglx->glXReleaseTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
+
+ ps->psglx->glXBindTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
+
+ // Cleanup
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+
+ glx_check_err(ps);
+
+ return true;
+}
+
+/**
+ * @brief Release binding of a texture.
+ */
+void
+glx_release_pixmap(session_t *ps, glx_texture_t *ptex) {
+ // Release binding
+ if (ptex->glpixmap && ptex->texture) {
+ glBindTexture(ptex->target, ptex->texture);
+ ps->psglx->glXReleaseTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
+ glBindTexture(ptex->target, 0);
+ }
+
+ // Free GLX Pixmap
+ if (ptex->glpixmap) {
+ glXDestroyPixmap(ps->dpy, ptex->glpixmap);
+ ptex->glpixmap = 0;
+ }
+
+ glx_check_err(ps);
+}
+
+/**
+ * Preprocess function before start painting.
+ */
+void
+glx_paint_pre(session_t *ps, XserverRegion *preg) {
+ ps->psglx->z = 0.0;
+ // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // Get buffer age
+ bool trace_damage = (ps->o.glx_swap_method < 0 || ps->o.glx_swap_method > 1);
+
+ // Trace raw damage regions
+ XserverRegion newdamage = None;
+ if (trace_damage && *preg)
+ newdamage = copy_region(ps, *preg);
+
+ // OpenGL doesn't support partial repaint without GLX_MESA_copy_sub_buffer,
+ // we could redraw the whole screen or copy unmodified pixels from
+ // front buffer with --glx-copy-from-front.
+ if (ps->o.glx_use_copysubbuffermesa || !*preg) {
+ }
+ else {
+ int buffer_age = ps->o.glx_swap_method;
+
+ // Getting buffer age
+ {
+ // Query GLX_EXT_buffer_age for buffer age
+ if (SWAPM_BUFFER_AGE == buffer_age) {
+ unsigned val = 0;
+ glXQueryDrawable(ps->dpy, get_tgt_window(ps),
+ GLX_BACK_BUFFER_AGE_EXT, &val);
+ buffer_age = val;
+ }
+
+ // Buffer age too high
+ if (buffer_age > CGLX_MAX_BUFFER_AGE + 1)
+ buffer_age = 0;
+
+ // Make sure buffer age >= 0
+ buffer_age = max_i(buffer_age, 0);
+
+ // Check if we have we have empty regions
+ if (buffer_age > 1) {
+ for (int i = 0; i < buffer_age - 1; ++i)
+ if (!ps->all_damage_last[i]) { buffer_age = 0; break; }
+ }
+ }
+
+ // Do nothing for buffer_age 1 (copy)
+ if (1 != buffer_age) {
+ // Copy pixels
+ if (ps->o.glx_copy_from_front) {
+ // Determine copy area
+ XserverRegion reg_copy = XFixesCreateRegion(ps->dpy, NULL, 0);
+ if (!buffer_age) {
+ XFixesSubtractRegion(ps->dpy, reg_copy, ps->screen_reg, *preg);
+ }
+ else {
+ for (int i = 0; i < buffer_age - 1; ++i)
+ XFixesUnionRegion(ps->dpy, reg_copy, reg_copy,
+ ps->all_damage_last[i]);
+ XFixesSubtractRegion(ps->dpy, reg_copy, reg_copy, *preg);
+ }
+
+ // Actually copy pixels
+ {
+ GLfloat raster_pos[4];
+ GLfloat curx = 0.0f, cury = 0.0f;
+ glGetFloatv(GL_CURRENT_RASTER_POSITION, raster_pos);
+ glReadBuffer(GL_FRONT);
+ glRasterPos2f(0.0, 0.0);
+ {
+ int nrects = 0;
+ XRectangle *rects = XFixesFetchRegion(ps->dpy, reg_copy, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ const int x = rects[i].x;
+ const int y = ps->root_height - rects[i].y - rects[i].height;
+ // Kwin patch says glRasterPos2f() causes artifacts on bottom
+ // screen edge with some drivers
+ glBitmap(0, 0, 0, 0, x - curx, y - cury, NULL);
+ curx = x;
+ cury = y;
+ glCopyPixels(x, y, rects[i].width, rects[i].height, GL_COLOR);
+ }
+ cxfree(rects);
+ }
+ glReadBuffer(GL_BACK);
+ glRasterPos4fv(raster_pos);
+ }
+
+ free_region(ps, &reg_copy);
+ }
+
+ // Determine paint area
+ if (ps->o.glx_copy_from_front) { }
+ else if (buffer_age) {
+ for (int i = 0; i < buffer_age - 1; ++i)
+ XFixesUnionRegion(ps->dpy, *preg, *preg, ps->all_damage_last[i]);
+ }
+ else {
+ free_region(ps, preg);
+ }
+ }
+ }
+
+ if (trace_damage) {
+ free_region(ps, &ps->all_damage_last[CGLX_MAX_BUFFER_AGE - 1]);
+ memmove(ps->all_damage_last + 1, ps->all_damage_last,
+ (CGLX_MAX_BUFFER_AGE - 1) * sizeof(XserverRegion));
+ ps->all_damage_last[0] = newdamage;
+ }
+
+ glx_set_clip(ps, *preg, NULL);
+
+#ifdef DEBUG_GLX_PAINTREG
+ glx_render_color(ps, 0, 0, ps->root_width, ps->root_height, 0, *preg, NULL);
+#endif
+
+ glx_check_err(ps);
+}
+
+/**
+ * Set clipping region on the target window.
+ */
+void
+glx_set_clip(session_t *ps, XserverRegion reg, const reg_data_t *pcache_reg) {
+ // Quit if we aren't using stencils
+ if (ps->o.glx_no_stencil)
+ return;
+
+ static XRectangle rect_blank = { .x = 0, .y = 0, .width = 0, .height = 0 };
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+
+ if (!reg)
+ return;
+
+ int nrects = 0;
+ XRectangle *rects_free = NULL;
+ const XRectangle *rects = NULL;
+ if (pcache_reg) {
+ rects = pcache_reg->rects;
+ nrects = pcache_reg->nrects;
+ }
+ if (!rects) {
+ nrects = 0;
+ rects = rects_free = XFixesFetchRegion(ps->dpy, reg, &nrects);
+ }
+ // Use one empty rectangle if the region is empty
+ if (!nrects) {
+ cxfree(rects_free);
+ rects_free = NULL;
+ nrects = 1;
+ rects = &rect_blank;
+ }
+
+ assert(nrects);
+ if (1 == nrects) {
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(rects[0].x, ps->root_height - rects[0].y - rects[0].height,
+ rects[0].width, rects[0].height);
+ }
+ else {
+ glEnable(GL_STENCIL_TEST);
+ glClear(GL_STENCIL_BUFFER_BIT);
+
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ glDepthMask(GL_FALSE);
+ glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
+
+ glBegin(GL_QUADS);
+
+ for (int i = 0; i < nrects; ++i) {
+ GLint rx = rects[i].x;
+ GLint ry = ps->root_height - 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: %d, %d, %d, %d\n", i, rx, ry, rxe, rye);
+#endif
+
+ glVertex3i(rx, ry, z);
+ glVertex3i(rxe, ry, z);
+ glVertex3i(rxe, rye, z);
+ glVertex3i(rx, rye, z);
+ }
+
+ glEnd();
+
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ // glDepthMask(GL_TRUE);
+ }
+
+ cxfree(rects_free);
+
+ glx_check_err(ps);
+}
+
+#define P_PAINTREG_START() \
+ XserverRegion reg_new = None; \
+ XRectangle rec_all = { .x = dx, .y = dy, .width = width, .height = height }; \
+ XRectangle *rects = &rec_all; \
+ int nrects = 1; \
+ \
+ if (ps->o.glx_no_stencil && reg_tgt) { \
+ if (pcache_reg) { \
+ rects = pcache_reg->rects; \
+ nrects = pcache_reg->nrects; \
+ } \
+ else { \
+ reg_new = XFixesCreateRegion(ps->dpy, &rec_all, 1); \
+ XFixesIntersectRegion(ps->dpy, reg_new, reg_new, reg_tgt); \
+ \
+ nrects = 0; \
+ rects = XFixesFetchRegion(ps->dpy, reg_new, &nrects); \
+ } \
+ } \
+ glBegin(GL_QUADS); \
+ \
+ for (int ri = 0; ri < nrects; ++ri) { \
+ XRectangle crect; \
+ rect_crop(&crect, &rects[ri], &rec_all); \
+ \
+ if (!crect.width || !crect.height) \
+ continue; \
+
+#define P_PAINTREG_END() \
+ } \
+ glEnd(); \
+ \
+ if (rects && rects != &rec_all && !(pcache_reg && pcache_reg->rects == rects)) \
+ cxfree(rects); \
+ free_region(ps, &reg_new); \
+
+static inline GLuint
+glx_gen_texture(session_t *ps, GLenum tex_tgt, int width, int height) {
+ GLuint tex = 0;
+ glGenTextures(1, &tex);
+ if (!tex) return 0;
+ glEnable(tex_tgt);
+ glBindTexture(tex_tgt, tex);
+ glTexParameteri(tex_tgt, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(tex_tgt, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(tex_tgt, 0, GL_RGB, width, height, 0, GL_RGB,
+ GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(tex_tgt, 0);
+
+ return tex;
+}
+
+static inline void
+glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int basex, int basey,
+ int dx, int dy, int width, int height) {
+ if (width > 0 && height > 0)
+ glCopyTexSubImage2D(tex_tgt, 0, dx - basex, dy - basey,
+ dx, ps->root_height - dy - height, width, height);
+}
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+/**
+ * Blur contents in a particular region.
+ */
+bool
+glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
+ GLfloat factor_center,
+ XserverRegion reg_tgt, const reg_data_t *pcache_reg,
+ glx_blur_cache_t *pbc) {
+ assert(ps->psglx->blur_passes[0].prog);
+ const bool more_passes = ps->psglx->blur_passes[1].prog;
+ const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST);
+ const bool have_stencil = glIsEnabled(GL_STENCIL_TEST);
+ bool ret = false;
+
+ // Calculate copy region size
+ glx_blur_cache_t ibc = { .width = 0, .height = 0 };
+ if (!pbc)
+ pbc = &ibc;
+
+ int mdx = dx, mdy = dy, mwidth = width, mheight = height;
+#ifdef DEBUG_GLX
+ printf_dbgf("(): %d, %d, %d, %d\n", mdx, mdy, mwidth, mheight);
+#endif
+
+ /*
+ if (ps->o.resize_damage > 0) {
+ int inc_x = 0, inc_y = 0;
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ XFixed *kern = ps->o.blur_kerns[i];
+ if (!kern) break;
+ inc_x += XFixedToDouble(kern[0]) / 2;
+ inc_y += XFixedToDouble(kern[1]) / 2;
+ }
+ inc_x = min_i(ps->o.resize_damage, inc_x);
+ inc_y = min_i(ps->o.resize_damage, inc_y);
+
+ mdx = max_i(dx - inc_x, 0);
+ mdy = max_i(dy - inc_y, 0);
+ int mdx2 = min_i(dx + width + inc_x, ps->root_width),
+ mdy2 = min_i(dy + height + inc_y, ps->root_height);
+ mwidth = mdx2 - mdx;
+ mheight = mdy2 - mdy;
+ }
+ */
+
+ GLenum tex_tgt = GL_TEXTURE_RECTANGLE;
+ if (ps->psglx->has_texture_non_power_of_two)
+ tex_tgt = GL_TEXTURE_2D;
+
+ // Free textures if size inconsistency discovered
+ if (mwidth != pbc->width || mheight != pbc->height)
+ free_glx_bc_resize(ps, pbc);
+
+ // Generate FBO and textures if needed
+ if (!pbc->textures[0])
+ pbc->textures[0] = glx_gen_texture(ps, tex_tgt, mwidth, mheight);
+ GLuint tex_scr = pbc->textures[0];
+ if (more_passes && !pbc->textures[1])
+ pbc->textures[1] = glx_gen_texture(ps, tex_tgt, mwidth, mheight);
+ pbc->width = mwidth;
+ pbc->height = mheight;
+ GLuint tex_scr2 = pbc->textures[1];
+#ifdef CONFIG_VSYNC_OPENGL_FBO
+ if (more_passes && !pbc->fbo)
+ glGenFramebuffers(1, &pbc->fbo);
+ const GLuint fbo = pbc->fbo;
+#endif
+
+ if (!tex_scr || (more_passes && !tex_scr2)) {
+ printf_errf("(): Failed to allocate texture.");
+ goto glx_blur_dst_end;
+ }
+#ifdef CONFIG_VSYNC_OPENGL_FBO
+ if (more_passes && !fbo) {
+ printf_errf("(): Failed to allocate framebuffer.");
+ goto glx_blur_dst_end;
+ }
+#endif
+
+ // Read destination pixels into a texture
+ glEnable(tex_tgt);
+ glBindTexture(tex_tgt, tex_scr);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight);
+ /*
+ if (tex_scr2) {
+ glBindTexture(tex_tgt, tex_scr2);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, dx - mdx);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy + height,
+ mwidth, mdy + mheight - dy - height);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy, dx - mdx, height);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, dx + width, dy,
+ mdx + mwidth - dx - width, height);
+ } */
+
+ // Texture scaling factor
+ GLfloat texfac_x = 1.0f, texfac_y = 1.0f;
+ if (GL_TEXTURE_2D == tex_tgt) {
+ texfac_x /= mwidth;
+ texfac_y /= mheight;
+ }
+
+ // Paint it back
+ if (more_passes) {
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ }
+
+ bool last_pass = false;
+ for (int i = 0; !last_pass; ++i) {
+ last_pass = !ps->psglx->blur_passes[i + 1].prog;
+ assert(i < MAX_BLUR_PASS - 1);
+ const glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ assert(ppass->prog);
+
+ assert(tex_scr);
+ glBindTexture(tex_tgt, tex_scr);
+
+#ifdef CONFIG_VSYNC_OPENGL_FBO
+ if (!last_pass) {
+ static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 };
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, tex_scr2, 0);
+ glDrawBuffers(1, DRAWBUFS);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER)
+ != GL_FRAMEBUFFER_COMPLETE) {
+ printf_errf("(): Framebuffer attachment failed.");
+ goto glx_blur_dst_end;
+ }
+ }
+ else {
+ static const GLenum DRAWBUFS[2] = { GL_BACK };
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glDrawBuffers(1, DRAWBUFS);
+ if (have_scissors)
+ glEnable(GL_SCISSOR_TEST);
+ if (have_stencil)
+ glEnable(GL_STENCIL_TEST);
+ }
+#endif
+
+ // Color negation for testing...
+ // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+ // glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
+ // glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR);
+
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glUseProgram(ppass->prog);
+ if (ppass->unifm_offset_x >= 0)
+ glUniform1f(ppass->unifm_offset_x, texfac_x);
+ if (ppass->unifm_offset_y >= 0)
+ glUniform1f(ppass->unifm_offset_y, texfac_y);
+ if (ppass->unifm_factor_center >= 0)
+ glUniform1f(ppass->unifm_factor_center, factor_center);
+
+ {
+ P_PAINTREG_START();
+ {
+ const GLfloat rx = (crect.x - mdx) * texfac_x;
+ const GLfloat ry = (mheight - (crect.y - mdy)) * texfac_y;
+ const GLfloat rxe = rx + crect.width * texfac_x;
+ const GLfloat rye = ry - crect.height * texfac_y;
+ GLfloat rdx = crect.x - mdx;
+ GLfloat rdy = mheight - crect.y + mdy;
+ GLfloat rdxe = rdx + crect.width;
+ GLfloat rdye = rdy - crect.height;
+
+ if (last_pass) {
+ rdx = crect.x;
+ rdy = ps->root_height - crect.y;
+ rdxe = rdx + crect.width;
+ rdye = rdy - crect.height;
+ }
+
+#ifdef DEBUG_GLX
+ printf_dbgf("(): %f, %f, %f, %f -> %f, %f, %f, %f\n", rx, ry, rxe, rye, rdx, rdy, rdxe, rdye);
+#endif
+
+ glTexCoord2f(rx, ry);
+ glVertex3f(rdx, rdy, z);
+
+ glTexCoord2f(rxe, ry);
+ glVertex3f(rdxe, rdy, z);
+
+ glTexCoord2f(rxe, rye);
+ glVertex3f(rdxe, rdye, z);
+
+ glTexCoord2f(rx, rye);
+ glVertex3f(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+
+ glUseProgram(0);
+
+ // Swap tex_scr and tex_scr2
+ {
+ GLuint tmp = tex_scr2;
+ tex_scr2 = tex_scr;
+ tex_scr = tmp;
+ }
+ }
+
+ ret = true;
+
+glx_blur_dst_end:
+#ifdef CONFIG_VSYNC_OPENGL_FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+#endif
+ glBindTexture(tex_tgt, 0);
+ glDisable(tex_tgt);
+ if (have_scissors)
+ glEnable(GL_SCISSOR_TEST);
+ if (have_stencil)
+ glEnable(GL_STENCIL_TEST);
+
+ if (&ibc == pbc) {
+ free_glx_bc(ps, pbc);
+ }
+
+ glx_check_err(ps);
+
+ return ret;
+}
+#endif
+
+bool
+glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z,
+ GLfloat factor, XserverRegion reg_tgt, const reg_data_t *pcache_reg) {
+ // It's possible to dim in glx_render(), but it would be over-complicated
+ // considering all those mess in color negation and modulation
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.0f, 0.0f, 0.0f, factor);
+
+ {
+ P_PAINTREG_START();
+ {
+ GLint rdx = crect.x;
+ GLint rdy = ps->root_height - crect.y;
+ GLint rdxe = rdx + crect.width;
+ GLint rdye = rdy - crect.height;
+
+ glVertex3i(rdx, rdy, z);
+ glVertex3i(rdxe, rdy, z);
+ glVertex3i(rdxe, rdye, z);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+
+ glEnd();
+
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+ glDisable(GL_BLEND);
+
+ glx_check_err(ps);
+
+ return true;
+}
+
+/**
+ * @brief Render a region with texture data.
+ */
+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,
+ double opacity, bool argb, bool neg,
+ XserverRegion reg_tgt, const reg_data_t *pcache_reg
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ , const glx_prog_main_t *pprogram
+#endif
+ ) {
+ if (!ptex || !ptex->texture) {
+ printf_errf("(): Missing texture.");
+ return false;
+ }
+
+#ifdef DEBUG_GLX_PAINTREG
+ glx_render_dots(ps, dx, dy, width, height, z, reg_tgt, pcache_reg);
+ return true;
+#endif
+
+ argb = argb || (GLX_TEXTURE_FORMAT_RGBA_EXT ==
+ ps->psglx->fbconfigs[ptex->depth]->texture_fmt);
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ const bool has_prog = pprogram && pprogram->prog;
+#endif
+ bool dual_texture = false;
+
+ // It's required by legacy versions of OpenGL to enable texture target
+ // before specifying environment. Thanks to madsy for telling me.
+ glEnable(ptex->target);
+
+ // Enable blending if needed
+ if (opacity < 1.0 || argb) {
+
+ glEnable(GL_BLEND);
+
+ // Needed for handling opacity of ARGB texture
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ // This is all weird, but X Render is using premultiplied ARGB format, and
+ // we need to use those things to correct it. Thanks to derhass for help.
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(opacity, opacity, opacity, opacity);
+ }
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ if (!has_prog)
+#endif
+ {
+ // Color negation
+ if (neg) {
+ // Simple color negation
+ if (!glIsEnabled(GL_BLEND)) {
+ glEnable(GL_COLOR_LOGIC_OP);
+ glLogicOp(GL_COPY_INVERTED);
+ }
+ // ARGB texture color negation
+ else if (argb) {
+ dual_texture = true;
+
+ // Use two texture stages because the calculation is too complicated,
+ // thanks to madsy for providing code
+ // Texture stage 0
+ glActiveTexture(GL_TEXTURE0);
+
+ // Negation for premultiplied color: color = A - C
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+ // Pass texture alpha through
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+
+ // Texture stage 1
+ glActiveTexture(GL_TEXTURE1);
+ glEnable(ptex->target);
+ glBindTexture(ptex->target, ptex->texture);
+
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+ glActiveTexture(GL_TEXTURE0);
+ }
+ // RGB blend color negation
+ else {
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+ }
+ }
+ }
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ else {
+ // Programmable path
+ assert(pprogram->prog);
+ glUseProgram(pprogram->prog);
+ if (pprogram->unifm_opacity >= 0)
+ glUniform1f(pprogram->unifm_opacity, opacity);
+ if (pprogram->unifm_invert_color >= 0)
+ glUniform1i(pprogram->unifm_invert_color, neg);
+ if (pprogram->unifm_tex >= 0)
+ glUniform1i(pprogram->unifm_tex, 0);
+ }
+#endif
+
+#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);
+#endif
+
+ // Bind texture
+ glBindTexture(ptex->target, ptex->texture);
+ if (dual_texture) {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(ptex->target, ptex->texture);
+ glActiveTexture(GL_TEXTURE0);
+ }
+
+ // Painting
+ {
+ P_PAINTREG_START();
+ {
+ GLfloat rx = (double) (crect.x - dx + x);
+ GLfloat ry = (double) (crect.y - dy + y);
+ GLfloat rxe = rx + (double) crect.width;
+ GLfloat rye = ry + (double) crect.height;
+ // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] [0-1]
+ // Thanks to amonakov for pointing out!
+ if (GL_TEXTURE_2D == ptex->target) {
+ rx = rx / ptex->width;
+ ry = ry / ptex->height;
+ rxe = rxe / ptex->width;
+ rye = rye / ptex->height;
+ }
+ GLint rdx = crect.x;
+ GLint rdy = ps->root_height - crect.y;
+ GLint rdxe = rdx + crect.width;
+ GLint rdye = rdy - crect.height;
+
+ // Invert Y if needed, this may not work as expected, though. I don't
+ // have such a FBConfig to test with.
+ if (!ptex->y_inverted) {
+ ry = 1.0 - ry;
+ rye = 1.0 - rye;
+ }
+
+#ifdef DEBUG_GLX
+ printf_dbgf("(): Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d\n", ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye);
+#endif
+
+#define P_TEXCOORD(cx, cy) { \
+ if (dual_texture) { \
+ glMultiTexCoord2f(GL_TEXTURE0, cx, cy); \
+ glMultiTexCoord2f(GL_TEXTURE1, cx, cy); \
+ } \
+ else glTexCoord2f(cx, cy); \
+}
+ P_TEXCOORD(rx, ry);
+ glVertex3i(rdx, rdy, z);
+
+ P_TEXCOORD(rxe, ry);
+ glVertex3i(rdxe, rdy, z);
+
+ P_TEXCOORD(rxe, rye);
+ glVertex3i(rdxe, rdye, z);
+
+ P_TEXCOORD(rx, rye);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+
+ // Cleanup
+ glBindTexture(ptex->target, 0);
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glDisable(GL_BLEND);
+ glDisable(GL_COLOR_LOGIC_OP);
+ glDisable(ptex->target);
+
+ if (dual_texture) {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+ glActiveTexture(GL_TEXTURE0);
+ }
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ if (has_prog)
+ glUseProgram(0);
+#endif
+
+ glx_check_err(ps);
+
+ return true;
+}
+
+/**
+ * @brief Render a region with a specified color.
+ */
+bool
+glx_render_specified_color(session_t *ps, int color, int dx, int dy, int width, int height, int z,
+ XserverRegion reg_tgt, const reg_data_t *pcache_reg) {
+
+ glColor4f(color,
+ color,
+ color,
+ 1.0f
+ );
+
+ {
+ P_PAINTREG_START();
+ {
+ GLint rdx = crect.x;
+ GLint rdy = ps->root_height - crect.y;
+ GLint rdxe = rdx + crect.width;
+ GLint rdye = rdy - crect.height;
+
+ glVertex3i(rdx, rdy, z);
+ glVertex3i(rdxe, rdy, z);
+ glVertex3i(rdxe, rdye, z);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ glx_check_err(ps);
+
+ return true;
+}
+
+/**
+ * Render a region with color.
+ */
+static void
+glx_render_color(session_t *ps, int dx, int dy, int width, int height, int z,
+ XserverRegion reg_tgt, const reg_data_t *pcache_reg) {
+ static int color = 0;
+
+ color = color % (3 * 3 * 3 - 1) + 1;
+ glColor4f(1.0 / 3.0 * (color / (3 * 3)),
+ 1.0 / 3.0 * (color % (3 * 3) / 3),
+ 1.0 / 3.0 * (color % 3),
+ 1.0f
+ );
+ z -= 0.2;
+
+ {
+ P_PAINTREG_START();
+ {
+ GLint rdx = crect.x;
+ GLint rdy = ps->root_height - crect.y;
+ GLint rdxe = rdx + crect.width;
+ GLint rdye = rdy - crect.height;
+
+ glVertex3i(rdx, rdy, z);
+ glVertex3i(rdxe, rdy, z);
+ glVertex3i(rdxe, rdye, z);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ glx_check_err(ps);
+}
+
+/**
+ * Render a region with dots.
+ */
+static void
+glx_render_dots(session_t *ps, int dx, int dy, int width, int height, int z,
+ XserverRegion reg_tgt, const reg_data_t *pcache_reg) {
+ glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
+ z -= 0.1;
+
+ {
+ P_PAINTREG_START();
+ {
+ static const GLint BLK_WID = 5, BLK_HEI = 5;
+
+ glEnd();
+ glPointSize(1.0);
+ glBegin(GL_POINTS);
+
+ GLint rdx = crect.x;
+ GLint rdy = ps->root_height - crect.y;
+ GLint rdxe = rdx + crect.width;
+ GLint rdye = rdy - crect.height;
+ rdx = (rdx) / BLK_WID * BLK_WID;
+ rdy = (rdy) / BLK_HEI * BLK_HEI;
+ rdxe = (rdxe) / BLK_WID * BLK_WID;
+ rdye = (rdye) / BLK_HEI * BLK_HEI;
+
+ for (GLint cdx = rdx; cdx < rdxe; cdx += BLK_WID)
+ for (GLint cdy = rdy; cdy > rdye; cdy -= BLK_HEI)
+ glVertex3i(cdx + BLK_WID / 2, cdy - BLK_HEI / 2, z);
+ }
+ P_PAINTREG_END();
+ }
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ glx_check_err(ps);
+}
+
+/**
+ * Swap buffer with glXCopySubBufferMESA().
+ */
+void
+glx_swap_copysubbuffermesa(session_t *ps, XserverRegion reg) {
+ int nrects = 0;
+ XRectangle *rects = XFixesFetchRegion(ps->dpy, reg, &nrects);
+
+ if (1 == nrects && rect_is_fullscreen(ps, rects[0].x, rects[0].y,
+ rects[0].width, rects[0].height)) {
+ glXSwapBuffers(ps->dpy, get_tgt_window(ps));
+ }
+ else {
+ glx_set_clip(ps, None, NULL);
+ for (int i = 0; i < nrects; ++i) {
+ const int x = rects[i].x;
+ const int y = ps->root_height - rects[i].y - rects[i].height;
+ const int wid = rects[i].width;
+ const int hei = rects[i].height;
+
+#ifdef DEBUG_GLX
+ printf_dbgf("(): %d, %d, %d, %d\n", x, y, wid, hei);
+#endif
+ ps->psglx->glXCopySubBufferProc(ps->dpy, get_tgt_window(ps), x, y, wid, hei);
+ }
+ }
+
+ glx_check_err(ps);
+
+ cxfree(rects);
+}
+
+/**
+ * @brief Get tightly packed RGB888 data from GL front buffer.
+ *
+ * Don't expect any sort of decent performance.
+ *
+ * @returns tightly packed RGB888 data of the size of the screen,
+ * to be freed with `free()`
+ */
+unsigned char *
+glx_take_screenshot(session_t *ps, int *out_length) {
+ int length = 3 * ps->root_width * ps->root_height;
+ GLint unpack_align_old = 0;
+ glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_align_old);
+ assert(unpack_align_old > 0);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ unsigned char *buf = cmalloc(length, unsigned char);
+ glReadBuffer(GL_FRONT);
+ glReadPixels(0, 0, ps->root_width, ps->root_height, GL_RGB,
+ GL_UNSIGNED_BYTE, buf);
+ glReadBuffer(GL_BACK);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_align_old);
+ if (out_length)
+ *out_length = sizeof(unsigned char) * length;
+ return buf;
+}
+
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+GLuint
+glx_create_shader(GLenum shader_type, const char *shader_str) {
+#ifdef DEBUG_GLX_GLSL
+ printf("glx_create_shader(): ===\n%s\n===\n", shader_str);
+ fflush(stdout);
+#endif
+
+ bool success = false;
+ GLuint shader = glCreateShader(shader_type);
+ if (!shader) {
+ printf_errf("(): Failed to create shader with type %#x.", 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(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) {
+ for (int i = 0; i < nshaders; ++i)
+ glDetachShader(program, shaders[i]);
+ }
+ if (program && !success) {
+ glDeleteProgram(program);
+ program = 0;
+ }
+
+ return program;
+}
+
+/**
+ * @brief Create a program from vertex and fragment shader strings.
+ */
+GLuint
+glx_create_program_from_str(const char *vert_shader_str,
+ const char *frag_shader_str) {
+ GLuint vert_shader = 0;
+ GLuint frag_shader = 0;
+ GLuint prog = 0;
+
+ if (vert_shader_str)
+ vert_shader = glx_create_shader(GL_VERTEX_SHADER, vert_shader_str);
+ if (frag_shader_str)
+ frag_shader = glx_create_shader(GL_FRAGMENT_SHADER, frag_shader_str);
+
+ {
+ GLuint shaders[2];
+ int count = 0;
+ if (vert_shader)
+ shaders[count++] = vert_shader;
+ if (frag_shader)
+ shaders[count++] = frag_shader;
+ assert(count <= sizeof(shaders) / sizeof(shaders[0]));
+ if (count)
+ prog = glx_create_program(shaders, count);
+ }
+
+ if (vert_shader)
+ glDeleteShader(vert_shader);
+ if (frag_shader)
+ glDeleteShader(frag_shader);
+
+ return prog;
+}
+#endif
+