diff --git a/picom.sample.conf b/picom.sample.conf index 0ed48ab..4e3a206 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -39,6 +39,8 @@ blur-background-exclude = [ ]; # opacity-rule = [ "80:class_g = 'URxvt'" ]; +# max-brightness = 0.66 + # Fading fading = true; # fade-delta = 30; diff --git a/src/backend/backend.c b/src/backend/backend.c index 224c369..a256c9c 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -251,6 +251,13 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { pixman_region32_fini(®_shadow); } + // Set max brightness + if (ps->o.max_brightness < 1.0) { + ps->backend_data->ops->image_op( + ps->backend_data, IMAGE_OP_MAX_BRIGHTNESS, w->win_image, + NULL, ®_visible, &ps->o.max_brightness); + } + // Draw window on target if (!w->invert_color && !w->dim && w->frame_opacity == 1 && w->opacity == 1) { ps->backend_data->ops->compose(ps->backend_data, w->win_image, diff --git a/src/backend/backend.h b/src/backend/backend.h index baa9c77..5a18e0a 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -47,6 +47,8 @@ enum image_operations { // effective size. `reg_op` and `reg_visible` is ignored. `arg` is two integers, // width and height, in that order. IMAGE_OP_RESIZE_TILE, + // Limit how bright image can be + IMAGE_OP_MAX_BRIGHTNESS, }; struct gaussian_blur_args { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index cfb0465..8c015c1 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -183,6 +183,165 @@ static void gl_free_prog_main(gl_win_shader_t *pprogram) { } } +/* + * @brief Implements recursive part of gl_average_texture_color. + * + * @note In order to reduce number of textures which needs to be + * allocated and deleted during this recursive render + * we reuse the same two textures for render source and + * destination simply by alterating between them. + * Unfortunately on first iteration source_texture might + * be read-only. In this case we will select auxilary_texture as + * destination_texture in order not to touch that read-only source + * texture in following render iteration. + * Otherwise we simply will switch source and destination textures + * between each other on each render iteration. + */ +static GLuint _gl_average_texture_color(backend_t *base, GLuint source_texture, + GLuint destination_texture, GLuint auxilary_texture, + GLuint fbo, int width, int height) { + const int max_width = 1; + const int max_height = 1; + const int from_width = next_power_of_two(width); + const int from_height = next_power_of_two(height); + const int to_width = from_width > max_width ? from_width / 2 : from_width; + const int to_height = from_height > max_height ? from_height / 2 : from_height; + + // Prepare coordinates + GLint coord[] = { + // top left + 0, 0, // vertex coord + 0, 0, // texture coord + + // top right + to_width, 0, // vertex coord + 1, 0, // texture coord + + // bottom right + to_width, to_height, // vertex coord + 1, 1, // texture coord + + // bottom left + 0, to_height, // vertex coord + 0, 1, // texture coord + }; + glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord); + + // Prepare framebuffer for new render iteration + glBindTexture(GL_TEXTURE_2D, destination_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, to_width, to_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination_texture, 0); + + // Bind source texture as downscaling shader uniform input + glBindTexture(GL_TEXTURE_2D, source_texture); + + // Render into framebuffer + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); + + // Have we downscaled enough? + GLuint result; + if (to_width > max_width || to_height > max_height) { + GLuint new_source_texture = destination_texture; + GLuint new_destination_texture = auxilary_texture != 0 ? auxilary_texture : source_texture; + result = _gl_average_texture_color(base, new_source_texture, + new_destination_texture, 0, + fbo, to_width, to_height); + } else { + result = destination_texture; + } + + return result; +} + +/* + * @brief Builds a 1x1 texture which has color corresponding to the average of all + * pixels of img by recursively rendering into texture of quorter the size (half + * width and half height). + * Returned texture must be deleted by the caller (use glDeleteTextures). + */ +static GLuint gl_average_texture_color(backend_t *base, struct gl_image *img) { + + struct gl_data *gd = (void *)base; + + // Prepare textures which will be used for destination and source of rendering + // during downscaling. + GLuint textures[2] = {0}; + const int texture_count = sizeof(textures)/sizeof(textures[0]); + glGenTextures(texture_count, textures); + glActiveTexture(GL_TEXTURE0); + for (int i = 0; i < texture_count; i++) { + glBindTexture(GL_TEXTURE_2D, textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, (GLint []){0, 0, 0, 0}); + } + + // Prepare framebuffer used for rendering and bind it + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + // Enable shaders + glUseProgram(gd->brightness_shader.prog); + + // Prepare vertex attributes + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + GLuint bo[2]; + glGenBuffers(2, bo); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + + // Allocate buffers for render input + GLint coord[16] = {0}; + GLuint indices[] = {0, 1, 2, 2, 3, 0}; + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, GL_STATIC_DRAW); + + // Do actual recursive render to 1x1 texture + GLuint result_texture = _gl_average_texture_color(base, img->inner->texture, + textures[0], textures[1], + fbo, img->inner->width, + img->inner->height); + + // Cleanup vertex attributes + glDisableVertexAttribArray(vert_coord_loc); + glDisableVertexAttribArray(vert_in_texcoord_loc); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + // Cleanup shaders + glUseProgram(0); + + // Cleanup framebuffers + glDeleteFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); + + // Cleanup render textures + glBindTexture(GL_TEXTURE_2D, 0); + for (int i = 0; i < texture_count; i++) { + if (result_texture != textures[i]) + glDeleteTextures(1, &textures[i]); + } + + gl_check_err(); + + return result_texture; +} + /** * Render a region with texture data. * @@ -202,7 +361,9 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, return; } - bool dual_texture = false; + GLuint brightness = 0; + if (img->max_brightness < 1.0) + brightness = gl_average_texture_color(base, img); assert(gd->win_shader.prog); glUseProgram(gd->win_shader.prog); @@ -218,17 +379,21 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, if (gd->win_shader.unifm_dim >= 0) { glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); } + if (gd->win_shader.unifm_brightness >= 0) { + glUniform1i(gd->win_shader.unifm_brightness, 1); + } + if (gd->win_shader.unifm_max_brightness >= 0) { + glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness); + } // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", // x, y, width, height, dx, dy, ptex->width, ptex->height, z); // Bind texture + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, brightness); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, img->inner->texture); - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, img->inner->texture); - glActiveTexture(GL_TEXTURE0); - } GLuint vao; glGenVertexArrays(1, &vao); @@ -259,15 +424,10 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - } - glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); + glDeleteTextures(1, &brightness); glUseProgram(0); @@ -597,6 +757,8 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color"); ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim"); + ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness"); + ret->unifm_max_brightness = glGetUniformLocationChecked(ret->prog, "max_brightness"); glUseProgram(ret->prog); int orig_loc = glGetUniformLocation(ret->prog, "orig"); @@ -635,6 +797,10 @@ void gl_resize(struct gl_data *gd, int width, int height) { pml = glGetUniformLocationChecked(gd->present_prog, "projection"); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUseProgram(gd->brightness_shader.prog); + pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glBindTexture(GL_TEXTURE_2D, gd->back_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); @@ -665,6 +831,25 @@ static const char fill_vert[] = GLSL(330, gl_Position = projection * vec4(in_coord, 0, 1); } ); + +static const char interpolating_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); + } +); + +static const char interpolating_vert[] = GLSL(330, + uniform mat4 projection; + layout(location = 0) in vec2 in_coord; + layout(location = 1) in vec2 in_texcoord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + texcoord = in_texcoord; + } +); // clang-format on /// Fill a given region in bound framebuffer. @@ -960,6 +1145,8 @@ const char *win_shader_glsl = GLSL(330, uniform bool invert_color; in vec2 texcoord; uniform sampler2D tex; + uniform sampler2D brightness; + uniform float max_brightness; void main() { vec4 c = texelFetch(tex, ivec2(texcoord), 0); @@ -967,6 +1154,12 @@ const char *win_shader_glsl = GLSL(330, c = vec4(c.aaa - c.rgb, c.a); } c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; + + vec3 rgb_brightness = texture2D(brightness, vec2(0.5, 0.5), 0).rgb; + float brightness = (rgb_brightness[0] + rgb_brightness[1] + rgb_brightness[2]) / 3; + if (brightness > max_brightness) + c.rgb = c.rgb * (max_brightness / brightness); + gl_FragColor = c; } ); @@ -1026,6 +1219,15 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0); glUseProgram(0); + gd->brightness_shader.prog = gl_create_program_from_str(interpolating_vert, interpolating_frag); + if (!gd->brightness_shader.prog) { + log_error("Failed to create the brightness shader"); + return false; + } + glUseProgram(gd->brightness_shader.prog); + glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0); + glUseProgram(0); + // Set up the size of the viewport. We do this last because it expects the blur // textures are already set up. gl_resize(gd, ps->root_width, ps->root_height); @@ -1236,6 +1438,9 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, tex->ewidth = iargs[0]; tex->eheight = iargs[1]; break; + case IMAGE_OP_MAX_BRIGHTNESS: + tex->max_brightness = *(double *)arg; + break; } return true; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 0687513..41fe24f 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -20,8 +20,15 @@ typedef struct { GLint unifm_invert_color; GLint unifm_tex; GLint unifm_dim; + GLint unifm_brightness; + GLint unifm_max_brightness; } gl_win_shader_t; +// Program and uniforms for brightness shader +typedef struct { + GLuint prog; +} gl_brightness_shader_t; + // Program and uniforms for blur shader typedef struct { GLuint prog; @@ -48,6 +55,7 @@ typedef struct gl_image { struct gl_texture *inner; double opacity; double dim; + double max_brightness; int ewidth, eheight; bool has_alpha; bool color_inverted; @@ -60,6 +68,7 @@ struct gl_data { // Height and width of the viewport int height, width; gl_win_shader_t win_shader; + gl_brightness_shader_t brightness_shader; gl_fill_shader_t fill_shader; GLuint back_texture, back_fbo; GLuint present_prog; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index c2e3023..5117e2f 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -390,6 +390,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b log_trace("Binding pixmap %#010x", pixmap); auto wd = ccalloc(1, struct gl_image); + wd->max_brightness = 1; wd->inner = ccalloc(1, struct gl_texture); wd->inner->width = wd->ewidth = r->width; wd->inner->height = wd->eheight = r->height; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 8e7f992..ab4e664 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -458,6 +458,7 @@ static bool image_op(backend_t *base, enum image_operations op, void *image, img->eheight = iargs[1]; break; case IMAGE_OP_APPLY_ALPHA_ALL: assert(false); + case IMAGE_OP_MAX_BRIGHTNESS: assert(false); } pixman_region32_fini(®); return true; diff --git a/src/config.c b/src/config.c index c43ff0f..95c7975 100644 --- a/src/config.c +++ b/src/config.c @@ -549,6 +549,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .inactive_dim_fixed = false, .invert_color_list = NULL, .opacity_rules = NULL, + .max_brightness = 1.0, .use_ewmh_active_win = false, .focus_blacklist = NULL, diff --git a/src/config.h b/src/config.h index a6a9f61..31a232c 100644 --- a/src/config.h +++ b/src/config.h @@ -210,6 +210,8 @@ typedef struct options { c2_lptr_t *invert_color_list; /// Rules to change window opacity. c2_lptr_t *opacity_rules; + /// Limit window brightness + double max_brightness; // === Focus related === /// Whether to try to detect WM windows and mark them as focused. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 902ed91..0f9f201 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -422,6 +422,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } // --use-damage lcfg_lookup_bool(&cfg, "use-damage", &opt->use_damage); + + // --max-brightness + if (config_lookup_float(&cfg, "max-brightness", &opt->max_brightness) && opt->use_damage) { + log_warn("max-brightness requires use-damage = false. Falling back to 1.0"); + opt->max_brightness = 1.0; + } + // --glx-use-gpushader4 if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival) && ival) { log_warn("glx-use-gpushader4 is deprecated since v6, please remove it " diff --git a/src/dbus.c b/src/dbus.c index 3161dbe..b121828 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1035,6 +1035,8 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(inactive_dim, cdbus_reply_double); cdbus_m_opts_get_do(inactive_dim_fixed, cdbus_reply_bool); + cdbus_m_opts_get_do(max_brightness, cdbus_reply_double); + cdbus_m_opts_get_do(use_ewmh_active_win, cdbus_reply_bool); cdbus_m_opts_get_do(detect_transient, cdbus_reply_bool); cdbus_m_opts_get_do(detect_client_leader, cdbus_reply_bool); diff --git a/src/options.c b/src/options.c index c66fbfc..79143bd 100644 --- a/src/options.c +++ b/src/options.c @@ -185,6 +185,11 @@ static void usage(const char *argv0, int ret) { "--inactive-dim-fixed\n" " Use fixed inactive dim value.\n" "\n" + "--max-brightness\n" + " Dims windows which average brightness is above this threshold.\n" + " Requires --no-use-damage.\n" + " Default: 1.0 or no dimming.\n" + "\n" "--detect-transient\n" " Use WM_TRANSIENT_FOR to group windows, and consider windows in\n" " the same group focused at the same time.\n" @@ -410,6 +415,7 @@ static const struct option longopts[] = { {"use-damage", no_argument, NULL, 323}, {"no-use-damage", no_argument, NULL, 324}, {"no-vsync", no_argument, NULL, 325}, + {"max-brightness", required_argument, NULL, 326}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -791,6 +797,11 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 325: opt->vsync = false; break; + + case 326: + opt->max_brightness = atof(optarg); + break; + P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); case 801: opt->print_diagnostics = true; break; @@ -822,6 +833,12 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->shadow_opacity = normalize_d(opt->shadow_opacity); opt->refresh_rate = normalize_i_range(opt->refresh_rate, 0, 300); + opt->max_brightness = normalize_d(opt->max_brightness); + if (opt->max_brightness < 1.0 && opt->use_damage) { + log_warn("--max-brightness requires --no-use-damage. Falling back to 1.0"); + opt->max_brightness = 1.0; + } + // Apply default wintype options that are dependent on global options set_default_winopts(opt, winopt_mask, shadow_enable, fading_enable); diff --git a/src/utils.c b/src/utils.c index d863c36..8a27f39 100644 --- a/src/utils.c +++ b/src/utils.c @@ -32,4 +32,20 @@ void report_allocation_failure(const char *func, const char *file, unsigned int unreachable; } +/// +/// Calculates next closest power of two of 32bit integer n +/// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +/// +int next_power_of_two(int n) +{ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index 1a6bc80..657c0b2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -261,4 +261,12 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) void name##_ref(type *a); \ void name##_unref(type **a); + +/// +/// Calculates next closest power of two of 32bit integer n +/// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +/// +int next_power_of_two(int n); + + // vim: set noet sw=8 ts=8 :