From ec2cd6276d6b137b946a48f4dffaaecd793bff0f Mon Sep 17 00:00:00 2001 From: Richard Grenville Date: Thu, 25 Apr 2013 22:23:35 +0800 Subject: [PATCH] Improvement: --blur-kern - Add blur convolution kernel customization, --blur-kern. The format is a bit tricky so be sure to read the description in `compton -h`. Not much tests received. - GLX backend: Tolerate missing GLSL uniforms for strangely shaped convolution kernel. - Fix a memory leak that blur-background blacklist is not freed. --- common.h | 2 + compton.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++------- opengl.c | 88 +++++++++++++++++-------- 3 files changed, 234 insertions(+), 49 deletions(-) diff --git a/common.h b/common.h index 3058b4d58..6e8c4cf5f 100644 --- a/common.h +++ b/common.h @@ -483,6 +483,8 @@ typedef struct { bool blur_background_fixed; /// Background blur blacklist. A linked list of conditions. c2_lptr_t *blur_background_blacklist; + /// Blur convolution kernel. + XFixed *blur_kern; /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable. double inactive_dim; /// Whether to use fixed inactive dim opacity, instead of deciding diff --git a/compton.c b/compton.c index 00849cb0f..61cd5bcc1 100644 --- a/compton.c +++ b/compton.c @@ -9,6 +9,7 @@ */ #include "compton.h" +#include // === Global constants === @@ -1348,21 +1349,6 @@ win_blur_background(session_t *ps, win *w, Picture tgt_buffer, switch (ps->o.backend) { case BKEND_XRENDER: { - const static int convolution_blur_size = 3; - // Convolution filter parameter (box blur) - // gaussian or binomial filters are definitely superior, yet looks - // like they aren't supported as of xorg-server-1.13.0 - XFixed convolution_blur[] = { - // Must convert to XFixed with XDoubleToFixed() - // Matrix size - XDoubleToFixed(convolution_blur_size), - XDoubleToFixed(convolution_blur_size), - // Matrix - XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), - XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), - XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), - }; - // Directly copying from tgt_buffer does not work, so we create a // Picture in the middle. Picture tmp_picture = win_build_picture(ps, w, NULL); @@ -1370,7 +1356,12 @@ win_blur_background(session_t *ps, win *w, Picture tgt_buffer, if (!tmp_picture) return; - convolution_blur[2 + convolution_blur_size + ((convolution_blur_size - 1) / 2)] = XDoubleToFixed(factor_center); + XFixed *convolution_blur = ps->o.blur_kern; + int kwid = XFixedToDouble((ps->o.blur_kern[0])), + khei = XFixedToDouble((ps->o.blur_kern[1])); + + // Modify the factor of the center pixel + convolution_blur[2 + (khei / 2) * kwid + kwid / 2] = XDoubleToFixed(factor_center); // Minimize the region we try to blur, if the window itself is not // opaque, only the frame is. @@ -1386,7 +1377,7 @@ win_blur_background(session_t *ps, win *w, Picture tgt_buffer, // Copy the content to tmp_picture, then copy back. The filter must // be applied on tgt_buffer, to get the nearby pixels outside the // window. - XRenderSetPictureFilter(ps->dpy, tgt_buffer, XRFILTER_CONVOLUTION, (XFixed *) convolution_blur, sizeof(convolution_blur) / sizeof(XFixed)); + XRenderSetPictureFilter(ps->dpy, tgt_buffer, XRFILTER_CONVOLUTION, convolution_blur, kwid * khei + 2); XRenderComposite(ps->dpy, PictOpSrc, tgt_buffer, None, tmp_picture, x, y, 0, 0, 0, 0, wid, hei); xrfilter_reset(ps, tgt_buffer); XRenderComposite(ps->dpy, PictOpSrc, tmp_picture, None, tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); @@ -1665,10 +1656,18 @@ paint_all(session_t *ps, XserverRegion region, win *t) { XFixesSetPictureClipRegion(ps->dpy, ps->tgt_picture, 0, 0, region); #ifdef MONITOR_REPAINT - XRenderComposite( - ps->dpy, PictOpSrc, ps->black_picture, None, - ps->tgt_picture, 0, 0, 0, 0, 0, 0, - ps->root_width, ps->root_height); + switch (ps->o.backend) { + case BKEND_XRENDER: + XRenderComposite(ps->dpy, PictOpSrc, ps->black_picture, None, + ps->tgt_picture, 0, 0, 0, 0, 0, 0, + ps->root_width, ps->root_height); + break; + case BKEND_GLX: + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + break; + } #endif if (t && t->reg_ignore) { @@ -4234,6 +4233,14 @@ usage(void) { "--blur-background-fixed\n" " Use fixed blur strength instead of adjusting according to window\n" " opacity.\n" + "--blur-kern matrix\n" + " Specify the blur convolution kernel, with the following format:\n" + " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n" + " The element in the center must not be included, it will be forever\n" + " 1.0 or changing based on opacity, depending on whether you have\n" + " --blur-background-fixed.\n" + " A 7x7 Guassian blur kernel looks like:\n" + " --blur-kern '7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003'\n" "--blur-background-exclude condition\n" " Exclude conditions for background blur.\n" "--invert-color-include condition\n" @@ -4476,10 +4483,113 @@ open_config_file(char *cpath, char **ppath) { return NULL; } +/** + * Parse a floating-point number in matrix. + */ +static inline const char * +parse_matrix_readnum(const char *src, double *dest) { + char *pc = NULL; + double val = strtod(src, &pc); + if (!pc || pc == src) { + printf_errf("(\"%s\"): No number found.", src); + return src; + } + + while (*pc && (isspace(*pc) || ',' == *pc)) + ++pc; + + *dest = val; + + return pc; +} + +/** + * Parse a matrix. + */ +static inline XFixed * +parse_matrix(session_t *ps, const char *src) { + int wid = 0, hei = 0; + const char *pc = NULL; + XFixed *matrix = NULL; + + // Get matrix width and height + { + double val = 0.0; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + wid = val; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + hei = val; + } + + // Validate matrix width and height + if (wid <= 0 || hei <= 0) { + printf_errf("(): Invalid matrix width/height."); + goto parse_matrix_err; + } + if (!(wid % 2 && hei % 2)) { + printf_errf("(): Width/height not odd."); + goto parse_matrix_err; + } + if (wid > 16 || hei > 16) { + printf_errf("(): Matrix width/height too large."); + goto parse_matrix_err; + } + + // Allocate memory + matrix = calloc(wid * hei + 2, sizeof(XFixed)); + if (!matrix) { + printf_errf("(): Failed to allocate memory for matrix."); + goto parse_matrix_err; + } + + // Read elements + { + int skip = hei / 2 * wid + wid / 2; + bool hasneg = false; + for (int i = 0; i < wid * hei; ++i) { + // Ignore the center element + if (i == skip) { + matrix[2 + i] = XDoubleToFixed(0); + continue; + } + double val = 0; + if (src == (pc = parse_matrix_readnum(src, &val))) + goto parse_matrix_err; + src = pc; + if (val < 0) hasneg = true; + matrix[2 + i] = XDoubleToFixed(val); + } + if (BKEND_XRENDER == ps->o.backend && hasneg) + printf_errf("(): A convolution kernel with negative values " + "may not work properly under X Render backend."); + } + + // Detect trailing characters + for ( ;*pc; ++pc) + if (!isspace(*pc) && ',' != *pc) { + printf_errf("(): Trailing characters in matrix string."); + goto parse_matrix_err; + } + + // Fill in width and height + matrix[0] = XDoubleToFixed(wid); + matrix[1] = XDoubleToFixed(hei); + + return matrix; + +parse_matrix_err: + free(matrix); + return NULL; +} + /** * Parse a condition list in configuration file. */ -static void +static inline void parse_cfg_condlst(session_t *ps, const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) { config_setting_t *setting = config_lookup(pcfg, name); @@ -4756,6 +4866,7 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { { "glx-no-rebind-pixmap", no_argument, NULL, 298 }, { "glx-swap-method", required_argument, NULL, 299 }, { "fade-exclude", required_argument, NULL, 300 }, + { "blur-kern", required_argument, NULL, 301 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -4979,6 +5090,12 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { // --fade-exclude condlst_add(ps, &ps->o.fade_blacklist, optarg); break; + case 301: + // --blur-kern + free(ps->o.blur_kern); + if (!(ps->o.blur_kern = parse_matrix(ps, optarg))) + exit(1); + break; default: usage(); break; @@ -5042,6 +5159,29 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { ps->o.track_leader = true; } + // Fill default blur kernel + if (ps->o.blur_background && !ps->o.blur_kern) { + const static int convolution_blur_size = 3; + // Convolution filter parameter (box blur) + // gaussian or binomial filters are definitely superior, yet looks + // like they aren't supported as of xorg-server-1.13.0 + const static XFixed convolution_blur[] = { + // Must convert to XFixed with XDoubleToFixed() + // Matrix size + XDoubleToFixed(convolution_blur_size), + XDoubleToFixed(convolution_blur_size), + // Matrix + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1), + }; + ps->o.blur_kern = malloc(sizeof(convolution_blur)); + if (!ps->o.blur_kern) { + printf_errf("(): Failed to allocate memory for convolution kernel."); + exit(1); + } + memcpy(ps->o.blur_kern, &convolution_blur, sizeof(convolution_blur)); + } } /** @@ -5864,6 +6004,7 @@ session_init(session_t *ps_old, int argc, char **argv) { .blur_background_frame = false, .blur_background_fixed = false, .blur_background_blacklist = NULL, + .blur_kern = NULL, .inactive_dim = 0.0, .inactive_dim_fixed = false, .invert_color_list = NULL, @@ -5929,6 +6070,12 @@ session_init(session_t *ps_old, int argc, char **argv) { .glXWaitVideoSyncSGI = NULL, .glXGetSyncValuesOML = NULL, .glXWaitForMscOML = NULL, + +#ifdef CONFIG_VSYNC_OPENGL_GLSL + .glx_prog_blur_unifm_offset_x = -1, + .glx_prog_blur_unifm_offset_y = -1, + .glx_prog_blur_unifm_factor_center = -1, +#endif #endif .xfixes_event = 0, @@ -6277,6 +6424,7 @@ session_destroy(session_t *ps) { free_wincondlst(&ps->o.fade_blacklist); free_wincondlst(&ps->o.focus_blacklist); free_wincondlst(&ps->o.invert_color_list); + free_wincondlst(&ps->o.blur_background_blacklist); #endif // Free tracked atom list @@ -6338,6 +6486,7 @@ session_destroy(session_t *ps) { free(ps->o.display); free(ps->o.logpath); free(ps->o.config_file); + free(ps->o.blur_kern); free(ps->pfds_read); free(ps->pfds_write); free(ps->pfds_except); diff --git a/opengl.c b/opengl.c index eb65e4f4c..50f64f529 100644 --- a/opengl.c +++ b/opengl.c @@ -191,28 +191,60 @@ bool glx_init_blur(session_t *ps) { #ifdef CONFIG_VSYNC_OPENGL_GLSL // Build shader - static const char *FRAG_SHADER_BLUR = - "#version 110\n" - "uniform float offset_x;\n" - "uniform float offset_y;\n" - "uniform float factor_center;\n" - "uniform sampler2D tex_scr;\n" - "\n" - "void main() {\n" - " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x - offset_x, gl_TexCoord[0].y - offset_y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x - offset_x, gl_TexCoord[0].y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x - offset_x, gl_TexCoord[0].y + offset_y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y - offset_y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * factor_center;\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y + offset_y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x + offset_x, gl_TexCoord[0].y - offset_y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x + offset_x, gl_TexCoord[0].y));\n" - " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x + offset_x, gl_TexCoord[0].y + offset_y));\n" - " gl_FragColor = sum / (factor_center + 8.0);\n" - "}\n" - ; - ps->glx_frag_shader_blur = glx_create_shader(GL_FRAGMENT_SHADER, FRAG_SHADER_BLUR); + { + static const char *FRAG_SHADER_BLUR_PREFIX = + "#version 110\n" + "uniform float offset_x;\n" + "uniform float offset_y;\n" + "uniform float factor_center;\n" + "uniform sampler2D 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) * texture2D(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_SUFFIX = + " sum += texture2D(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * factor_center;\n" + " gl_FragColor = sum / (factor_center + float(%.7g));\n" + "}\n"; + int wid = XFixedToDouble(ps->o.blur_kern[0]), hei = XFixedToDouble(ps->o.blur_kern[1]); + int nele = wid * hei - 1; + int len = strlen(FRAG_SHADER_BLUR_PREFIX) + (strlen(FRAG_SHADER_BLUR_ADD) + 42) * nele + + strlen(FRAG_SHADER_BLUR_SUFFIX) + 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; + strcpy(pc, FRAG_SHADER_BLUR_PREFIX); + pc += strlen(FRAG_SHADER_BLUR_PREFIX); + assert(strlen(shader_str) < len); + + double sum = 0.0; + for (int i = 0; i < hei; ++i) { + for (int j = 0; j < wid; ++j) { + if (hei / 2 == i && wid / 2 == j) + continue; + double val = XFixedToDouble(ps->o.blur_kern[2 + i * wid + j]); + sum += val; + sprintf(pc, FRAG_SHADER_BLUR_ADD, val, j - wid / 2, i - hei / 2); + pc += strlen(pc); + assert(strlen(shader_str) < len); + } + } + + sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, sum); + assert(strlen(shader_str) < len); +#ifdef DEBUG_GLX_GLSL + fputs(shader_str, stdout); + fflush(stdout); +#endif + } + ps->glx_frag_shader_blur = glx_create_shader(GL_FRAGMENT_SHADER, shader_str); + } + if (!ps->glx_frag_shader_blur) { printf_errf("(): Failed to create fragment shader."); return false; @@ -227,8 +259,7 @@ glx_init_blur(session_t *ps) { #define P_GET_UNIFM_LOC(name, target) { \ ps->target = glGetUniformLocation(ps->glx_prog_blur, name); \ if (ps->target < 0) { \ - printf_errf("(): Failed to get location of uniform '" name "'."); \ - return false; \ + printf_errf("(): Failed to get location of uniform '" name "'. Might be troublesome."); \ } \ } @@ -759,9 +790,12 @@ glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); #ifdef CONFIG_VSYNC_OPENGL_GLSL glUseProgram(ps->glx_prog_blur); - glUniform1f(ps->glx_prog_blur_unifm_offset_x, 1.0f / width); - glUniform1f(ps->glx_prog_blur_unifm_offset_y, 1.0f / height); - glUniform1f(ps->glx_prog_blur_unifm_factor_center, factor_center); + if (ps->glx_prog_blur_unifm_offset_x >= 0) + glUniform1f(ps->glx_prog_blur_unifm_offset_x, 1.0f / width); + if (ps->glx_prog_blur_unifm_offset_y >= 0) + glUniform1f(ps->glx_prog_blur_unifm_offset_y, 1.0f / height); + if (ps->glx_prog_blur_unifm_factor_center >= 0) + glUniform1f(ps->glx_prog_blur_unifm_factor_center, factor_center); #endif {