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.
This commit is contained in:
Richard Grenville 2013-04-25 22:23:35 +08:00
parent 4a4f857cc2
commit 2e6fb0203d
4 changed files with 235 additions and 50 deletions

View File

@ -80,7 +80,7 @@ else
export LD_ALTEXEC = /usr/bin/ld.gold
OBJS += backtrace-symbols.o
LIBS += -lbfd
CFLAGS += -ggdb
CFLAGS += -ggdb -Wshadow
# CFLAGS += -Weverything -Wno-disabled-macro-expansion -Wno-padded -Wno-gnu
endif

View File

@ -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

View File

@ -9,6 +9,7 @@
*/
#include "compton.h"
#include <ctype.h>
// === 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);

View File

@ -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
{