1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-25 14:06:08 -05:00

Merge pull request #851 from yshui/507

Closes #386  #507
This commit is contained in:
Yuxuan Shui 2022-08-21 05:46:56 +01:00 committed by GitHub
commit 3dfc422edf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 845 additions and 208 deletions

View file

@ -54,7 +54,7 @@ jobs:
command: ninja -vC build test
- run:
name: test config file parsing
command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --config tests/configs/parsing_test.conf --no-vsync --diagnostics
command: xvfb-run -s "-screen 0 640x480x24" build/src/picom --experimental-backends --config tests/configs/parsing_test.conf --no-vsync --diagnostics
- run:
name: run testsuite
command: tests/run_tests.sh build/src/picom

View file

@ -236,7 +236,7 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box
Use X Sync fence to sync clients' draw calls, to make sure all draw calls are finished before picom starts drawing. Needed on nvidia-drivers with GLX backend for some users.
*--glx-fshader-win* 'SHADER'::
GLX backend: Use specified GLSL fragment shader for rendering window contents. See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` in the source tree for examples.
GLX backend: Use specified GLSL fragment shader for rendering window contents. See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl` in the source tree for examples. Doesn't work with *--experimental-backends* enabled.
*--force-win-blend*::
Force all windows to be painted with blending. Useful if you have a *--glx-fshader-win* that could turn opaque pixels transparent.
@ -259,6 +259,12 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box
*--transparent-clipping*::
Make transparent windows clip other windows like non-transparent windows do, instead of blending on top of them.
*--window-shader-fg* 'SHADER'::
Specify GLSL fragment shader path for rendering window contents. Only works when *--experimental-backends* is enabled. Shader is searched first relative to the directory the configuration file is in, then in the usual places for a configuration file. See section *SHADER INTERFACE* below for more details on the interface.
*--window-shader-fg-rule* 'SHADER':'CONDITION'::
Specify GLSL fragment shader path for rendering window contents using patterns. Similar to *--opacity-rule*, arguments should be in the format of 'SHADER:CONDITION', e.g. "shader.frag:name = \'window\'". Leading and trailing whitespaces in 'SHADER' will be trimmed. If 'SHADER' is "default", then the default shader will be used for the matching windows. (This also unfortunately means you can't use a shader file named "default"). Only works when *--experimental-backends* is enabled.
FORMAT OF CONDITIONS
--------------------
Some options accept a condition string to match certain windows. A condition string is formed by one or more conditions, joined by logical operators.
@ -346,6 +352,61 @@ This is the old condition format we once used. Support of this format might be r
'PATTERN' is the actual pattern string.
SHADER INTERFACE
----------------
This secion describes the interface of a custom shader, how it is used by picom, and what parameters are passed by picom to the shader. This only applies to the new, experimental backends.
A custom shader is a GLSL fragment shader program, which can be used to override the default way of how a window is rendered. If a custom shader is used, the default picom effects (e.g. dimming, color inversion, etc.) will no longer be automatically applied. It would be the custom shader's responsibility to apply these effects.
The interface between picom and a custom shader is dependent on which backend is being used. The xrender backend doesn't support shader at all. Here we descibes the interface provided by the glx backend.
The shader must defined a function, 'vec4 window_shader()', which would be the entry point of the shader. The returned 'vec4' will be used to set 'gl_FragColor'. A function, 'vec4 default_post_processing(vec4 c)', is provided from applying the default picom effects to input color 'c'.
The following uniform/input variables are made available to the shader:
[source,glsl]
----
in vec2 texcoord; // texture coordinate of the fragment
uniform float opacity; // opacity of the window (0.0 - 1.0)
uniform float dim; // dimming factor of the window (0.0 - 1.0, higher means more dim)
uniform float corner_radius; // corner radius of the window (pixels)
uniform float border_width; // estimated border width of the window (pixels)
uniform bool invert_color; // whether to invert the color of the window
uniform sampler2D tex; // texture of the window
uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
uniform float time; // time in milliseconds, counting from an unspecified starting point
----
The default behavior of picom window rendering can be replicated by the following shader:
[source,glsl]
----
#version 330
in vec2 texcoord; // texture coordinate of the fragment
uniform sampler2D tex; // texture of the window
// Default window post-processing:
// 1) invert color
// 2) opacity / transparency
// 3) max-brightness clamping
// 4) rounded corners
vec4 default_post_processing(vec4 c);
// Default window shader:
// 1) fetch the specified pixel
// 2) apply default post-processing
vec4 window_shader() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
return default_post_processing(c);
}
----
The interface is expected to be mostly stable.
CONFIGURATION FILES
-------------------
picom could read from a configuration file if libconfig support is compiled in. If *--config* is not used, picom will seek for a configuration file in `$XDG_CONFIG_HOME/picom.conf` (`~/.config/picom.conf`, usually), then `$XDG_CONFIG_HOME/picom/picom.conf`, then `$XDG_CONFIG_DIRS/picom.conf` (often `/etc/xdg/picom.conf`), then `$XDG_CONFIG_DIRS/picom/picom.conf`.

View file

@ -321,11 +321,17 @@ use-damage = true;
#
# xrender-sync-fence = false
# GLX backend: Use specified GLSL fragment shader for rendering window contents.
# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl`
# in the source tree for examples.
# GLX backend: Use specified GLSL fragment shader for rendering window
# contents. Read the man page for a detailed explanation of the interface.
#
# glx-fshader-win = ""
# window-shader-fg = "default"
# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
#
# window-shader-fg-rule = [
# "my_shader.frag:window_type != 'dock'"
# ]
# Force all windows to be painted with blending. Useful if you
# have a glx-fshader-win that could turn opaque pixels transparent.

View file

@ -383,6 +383,10 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
ps->backend_data, IMAGE_PROPERTY_BORDER_WIDTH,
w->win_image, &border_width);
}
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CUSTOM_SHADER, w->win_image,
w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
}
if (w->opacity * MAX_ALPHA < 1) {

View file

@ -70,6 +70,9 @@ enum image_properties {
// Border width
// 1 int, default: 0
IMAGE_PROPERTY_BORDER_WIDTH,
// Custom shader for this window.
// 1 pointer to shader struct, default: NULL
IMAGE_PROPERTY_CUSTOM_SHADER,
};
enum image_operations {
@ -77,6 +80,12 @@ enum image_operations {
IMAGE_OP_APPLY_ALPHA,
};
enum shader_attributes {
// Whether the shader needs to be render regardless of whether the window is
// updated.
SHADER_ATTRIBUTE_ANIMATED = 1,
};
struct gaussian_blur_args {
int size;
double deviation;
@ -199,8 +208,24 @@ struct backend_operations {
/// Free resources associated with an image data structure
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
/// Create a shader object from a shader source.
///
/// Optional
void *(*create_shader)(backend_t *backend_data, const char *source)attr_nonnull(1, 2);
/// Free a shader object.
///
/// Required if create_shader is present.
void (*destroy_shader)(backend_t *backend_data, void *shader) attr_nonnull(1, 2);
// =========== Query ===========
/// Get the attributes of a shader.
///
/// Optional, Returns a bitmask of attributes, see `shader_attributes`.
uint64_t (*get_shader_attributes)(backend_t *backend_data, void *shader)
attr_nonnull(1, 2);
/// Return if image is not completely opaque.
///
/// This function is needed because some backend might change the content of the

View file

@ -452,6 +452,7 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break;
case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break;
case IMAGE_PROPERTY_BORDER_WIDTH: tex->border_width = *(int *)arg; break;
case IMAGE_PROPERTY_CUSTOM_SHADER: break;
}
return true;

View file

@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include "backend/backend.h"
@ -73,7 +74,7 @@ GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
{
GLint status = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (GL_FALSE == status) {
if (status == GL_FALSE) {
GLint log_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
@ -93,6 +94,7 @@ end:
glDeleteShader(shader);
shader = 0;
}
gl_check_err();
return shader;
}
@ -105,15 +107,16 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
goto end;
}
for (int i = 0; i < nshaders; ++i)
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) {
if (status == GL_FALSE) {
GLint log_len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
@ -128,59 +131,83 @@ GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
end:
if (program) {
for (int i = 0; i < nshaders; ++i)
for (int i = 0; i < nshaders; ++i) {
glDetachShader(program, shaders[i]);
}
}
if (program && !success) {
glDeleteProgram(program);
program = 0;
}
gl_check_err();
return program;
}
/**
* @brief Create a program from NULL-terminated arrays of vertex and fragment shader
* strings.
*/
GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) {
int vert_count, frag_count;
for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) {
}
for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) {
}
GLuint prog = 0;
auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint);
for (int i = 0; i < vert_count; ++i) {
shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]);
if (shaders[i] == 0) {
goto out;
}
}
for (int i = 0; i < frag_count; ++i) {
shaders[vert_count + i] =
gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]);
if (shaders[vert_count + i] == 0) {
goto out;
}
}
prog = gl_create_program(shaders, vert_count + frag_count);
out:
for (int i = 0; i < vert_count + frag_count; ++i) {
if (shaders[i] != 0) {
glDeleteShader(shaders[i]);
}
}
free(shaders);
gl_check_err();
return prog;
}
/**
* @brief Create a program from vertex and fragment shader strings.
*/
GLuint gl_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;
const char *vert_shaders[2] = {vert_shader_str, NULL};
const char *frag_shaders[2] = {frag_shader_str, NULL};
if (vert_shader_str)
vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str);
if (frag_shader_str)
frag_shader = gl_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;
}
if (count) {
prog = gl_create_program(shaders, count);
}
}
if (vert_shader)
glDeleteShader(vert_shader);
if (frag_shader)
glDeleteShader(frag_shader);
return prog;
return gl_create_program_from_strv(vert_shaders, frag_shaders);
}
static void gl_free_prog_main(gl_win_shader_t *pprogram) {
if (!pprogram)
void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) {
if (!shader) {
return;
}
auto pprogram = (gl_win_shader_t *)shader;
if (pprogram->prog) {
glDeleteProgram(pprogram->prog);
pprogram->prog = 0;
}
gl_check_err();
free(shader);
}
/*
@ -371,35 +398,47 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
brightness = gl_average_texture_color(base, img);
}
assert(gd->win_shader.prog);
glUseProgram(gd->win_shader.prog);
if (gd->win_shader.uniform_opacity >= 0) {
glUniform1f(gd->win_shader.uniform_opacity, (float)img->opacity);
auto win_shader = inner->shader;
if (!win_shader) {
win_shader = gd->default_shader;
}
if (gd->win_shader.uniform_invert_color >= 0) {
glUniform1i(gd->win_shader.uniform_invert_color, img->color_inverted);
assert(win_shader);
assert(win_shader->prog);
glUseProgram(win_shader->prog);
if (win_shader->uniform_opacity >= 0) {
glUniform1f(win_shader->uniform_opacity, (float)img->opacity);
}
if (gd->win_shader.uniform_tex >= 0) {
glUniform1i(gd->win_shader.uniform_tex, 0);
if (win_shader->uniform_invert_color >= 0) {
glUniform1i(win_shader->uniform_invert_color, img->color_inverted);
}
if (gd->win_shader.uniform_dim >= 0) {
glUniform1f(gd->win_shader.uniform_dim, (float)img->dim);
if (win_shader->uniform_tex >= 0) {
glUniform1i(win_shader->uniform_tex, 0);
}
if (gd->win_shader.uniform_brightness >= 0) {
glUniform1i(gd->win_shader.uniform_brightness, 1);
if (win_shader->uniform_dim >= 0) {
glUniform1f(win_shader->uniform_dim, (float)img->dim);
}
if (gd->win_shader.uniform_max_brightness >= 0) {
glUniform1f(gd->win_shader.uniform_max_brightness, (float)img->max_brightness);
if (win_shader->uniform_brightness >= 0) {
glUniform1i(win_shader->uniform_brightness, 1);
}
if (gd->win_shader.uniform_corner_radius >= 0) {
glUniform1f(gd->win_shader.uniform_corner_radius, (float)img->corner_radius);
if (win_shader->uniform_max_brightness >= 0) {
glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness);
}
if (gd->win_shader.uniform_border_width >= 0) {
if (win_shader->uniform_corner_radius >= 0) {
glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius);
}
if (win_shader->uniform_border_width >= 0) {
auto border_width = img->border_width;
if (border_width > img->corner_radius) {
border_width = 0;
}
glUniform1f(gd->win_shader.uniform_border_width, (float)border_width);
glUniform1f(win_shader->uniform_border_width, (float)border_width);
}
if (win_shader->uniform_time >= 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
glUniform1f(win_shader->uniform_time,
(float)ts.tv_sec * 1000.0f + (float)ts.tv_nsec / 1.0e6f);
}
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
@ -883,13 +922,14 @@ const char *vertex_shader = GLSL(330,
/**
* Load a GLSL main program from shader strings.
*/
static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str,
gl_win_shader_t *ret) {
static bool gl_win_shader_from_stringv(const char **vshader_strv,
const char **fshader_strv, gl_win_shader_t *ret) {
// Build program
ret->prog = gl_create_program_from_str(vshader_str, fshader_str);
ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv);
if (!ret->prog) {
log_error("Failed to create GLSL program.");
return -1;
gl_check_err();
return false;
}
// Get uniform addresses
@ -901,6 +941,7 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade
bind_uniform(ret, max_brightness);
bind_uniform(ret, corner_radius);
bind_uniform(ret, border_width);
bind_uniform(ret, time);
gl_check_err();
@ -1545,8 +1586,7 @@ const char *win_shader_glsl = GLSL(330,
return length(max(d, 0.0));
}
void main() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
vec4 default_post_processing(vec4 c) {
vec4 border_color = texture(tex, vec2(0.0, 0.5));
if (invert_color) {
c = vec4(c.aaa - c.rgb, c.a);
@ -1581,7 +1621,23 @@ const char *win_shader_glsl = GLSL(330,
c = (1.0f - factor) * c + factor * border_color;
}
gl_FragColor = c;
return c;
}
vec4 window_shader();
void main() {
gl_FragColor = window_shader();
}
);
const char *win_shader_default = GLSL(330,
in vec2 texcoord;
uniform sampler2D tex;
vec4 default_post_processing(vec4 c);
vec4 window_shader() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
return default_post_processing(c);
}
);
@ -1596,6 +1652,45 @@ const char *present_vertex_shader = GLSL(330,
);
// clang-format on
void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) {
auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t);
const char *vert_shaders[2] = {vertex_shader, NULL};
const char *frag_shaders[3] = {win_shader_glsl, source, NULL};
if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) {
free(win_shader);
return NULL;
}
GLint viewport_dimensions[2];
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
int pml = glGetUniformLocationChecked(win_shader->prog, "projection");
glUseProgram(win_shader->prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
return win_shader;
}
uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) {
auto win_shader = (gl_win_shader_t *)shader;
uint64_t ret = 0;
if (glGetUniformLocation(win_shader->prog, "time") >= 0) {
ret |= SHADER_ATTRIBUTE_ANIMATED;
}
return ret;
}
bool gl_init(struct gl_data *gd, session_t *ps) {
// Initialize GLX data structure
glDisable(GL_DEPTH_TEST);
@ -1637,24 +1732,24 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
// Initialize shaders
gd->default_shader = gl_create_window_shader(NULL, win_shader_default);
if (!gd->default_shader) {
log_error("Failed to create window shaders");
return false;
}
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
// Initialize shaders
gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader);
int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection");
glUseProgram(gd->win_shader.prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag);
gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color");
pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
glUseProgram(gd->fill_shader.prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
@ -1713,13 +1808,16 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
}
void gl_deinit(struct gl_data *gd) {
gl_free_prog_main(&gd->win_shader);
if (gd->logger) {
log_remove_target_tls(gd->logger);
gd->logger = NULL;
}
if (gd->default_shader) {
gl_destroy_window_shader(&gd->base, gd->default_shader);
gd->default_shader = NULL;
}
gl_check_err();
}
@ -1923,6 +2021,18 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
return true;
}
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args) {
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
return default_set_image_property(backend_data, prop, image_data, args);
}
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
inner->shader = args;
return true;
}
enum device_status gl_device_status(backend_t *base) {
auto gd = (struct gl_data *)base;
if (!gd->has_robustness) {

View file

@ -28,6 +28,8 @@ static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
// Program and uniforms for window shader
typedef struct {
UT_hash_handle hh;
uint32_t id;
GLuint prog;
GLint uniform_opacity;
GLint uniform_invert_color;
@ -37,6 +39,7 @@ typedef struct {
GLint uniform_max_brightness;
GLint uniform_corner_radius;
GLint uniform_border_width;
GLint uniform_time;
} gl_win_shader_t;
// Program and uniforms for brightness shader
@ -68,6 +71,7 @@ struct gl_texture {
// Textures for auxiliary uses.
GLuint auxiliary_texture[2];
gl_win_shader_t *shader;
void *user_data;
};
@ -79,7 +83,8 @@ struct gl_data {
bool has_robustness;
// Height and width of the root window
int height, width;
gl_win_shader_t win_shader;
// Hash-table of window shaders
gl_win_shader_t *default_shader;
gl_brightness_shader_t brightness_shader;
gl_fill_shader_t fill_shader;
GLuint back_texture, back_fbo;
@ -103,12 +108,17 @@ typedef struct session session_t;
GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
GLuint gl_create_program(const GLuint *const shaders, int nshaders);
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
void *gl_create_window_shader(backend_t *backend_data, const char *source);
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args);
/**
* @brief Render a region with texture data.
*/
void gl_compose(backend_t *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt,
const region_t *reg_visible);
void gl_compose(backend_t *, void *image_data, int dst_x, int dst_y,
const region_t *reg_tgt, const region_t *reg_visible);
void gl_resize(struct gl_data *, int width, int height);

View file

@ -534,7 +534,7 @@ struct backend_operations glx_ops = {
.release_image = gl_release_image,
.compose = gl_compose,
.image_op = gl_image_op,
.set_image_property = default_set_image_property,
.set_image_property = gl_set_image_property,
.clone_image = default_clone_image,
.blur = gl_blur,
.is_image_transparent = default_is_image_transparent,
@ -547,6 +547,9 @@ struct backend_operations glx_ops = {
.get_blur_size = gl_get_blur_size,
.diagnostics = glx_diagnostics,
.device_status = gl_device_status,
.create_shader = gl_create_window_shader,
.destroy_shader = gl_destroy_window_shader,
.get_shader_attributes = gl_get_shader_attributes,
.max_buffer_age = 5, // Why?
};

View file

@ -1151,11 +1151,16 @@ static void c2_free(c2_ptr_t p) {
/**
* Free a condition tree in c2_lptr_t.
*/
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) {
if (!lp)
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f) {
if (!lp) {
return NULL;
}
c2_lptr_t *pnext = lp->next;
if (f) {
f(lp->data);
}
lp->data = NULL;
c2_free(lp->ptr);
free(lp);

View file

@ -18,9 +18,10 @@ typedef struct _c2_lptr c2_lptr_t;
typedef struct session session_t;
struct managed_win;
typedef void (*c2_userdata_free)(void *);
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp);
c2_lptr_t *c2_free_lptr(c2_lptr_t *lp, c2_userdata_free f);
bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
void **pdata);
@ -34,8 +35,8 @@ void *c2_list_get_data(const c2_lptr_t *condlist);
/**
* Destroy a condition list.
*/
static inline void c2_list_free(c2_lptr_t **pcondlst) {
while ((*pcondlst = c2_free_lptr(*pcondlst))) {
static inline void c2_list_free(c2_lptr_t **pcondlst, c2_userdata_free f) {
while ((*pcondlst = c2_free_lptr(*pcondlst, f))) {
}
*pcondlst = NULL;
}

View file

@ -130,6 +130,14 @@ typedef struct _latom {
struct _latom *next;
} latom_t;
struct shader_info {
char *key;
char *source;
void *backend_shader;
uint64_t attributes;
UT_hash_handle hh;
};
/// Structure containing all necessary data for a session.
typedef struct session {
// === Event handlers ===
@ -150,6 +158,8 @@ typedef struct session {
ev_signal usr1_signal;
/// Signal handler for SIGINT
ev_signal int_signal;
// === Backend related ===
/// backend data
backend_t *backend_data;
/// backend blur context
@ -160,6 +170,8 @@ typedef struct session {
void *file_watch_handle;
/// libev mainloop
struct ev_loop *loop;
/// Shaders
struct shader_info *shaders;
// === Display related ===
/// Whether the X server is grabbed by us

View file

@ -3,13 +3,21 @@
// Copyright (c) 2013 Richard Grenville <pyxlcy@gmail.com>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include <test.h>
#include "c2.h"
#include "common.h"
#include "compiler.h"
@ -23,6 +31,98 @@
#include "config.h"
const char *xdg_config_home(void) {
char *xdgh = getenv("XDG_CONFIG_HOME");
char *home = getenv("HOME");
const char *default_dir = "/.config";
if (!xdgh) {
if (!home) {
return NULL;
}
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
strcpy(xdgh, home);
strcat(xdgh, default_dir);
} else {
xdgh = strdup(xdgh);
}
return xdgh;
}
char **xdg_config_dirs(void) {
char *xdgd = getenv("XDG_CONFIG_DIRS");
size_t count = 0;
if (!xdgd) {
xdgd = "/etc/xdg";
}
for (int i = 0; xdgd[i]; i++) {
if (xdgd[i] == ':') {
count++;
}
}
// Store the string and the result pointers together so they can be
// freed together
char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
auto path = dirs;
for (size_t i = 0; i < count; i++) {
dir_list[i] = path;
path = strchr(path, ':');
*path = '\0';
path++;
}
dir_list[count] = path;
size_t fill = 0;
for (size_t i = 0; i <= count; i++) {
if (dir_list[i][0] == '/') {
dir_list[fill] = dir_list[i];
fill++;
}
}
dir_list[fill] = NULL;
return dir_list;
}
TEST_CASE(xdg_config_dirs) {
auto old_var = getenv("XDG_CONFIG_DIRS");
if (old_var) {
old_var = strdup(old_var);
}
unsetenv("XDG_CONFIG_DIRS");
auto result = xdg_config_dirs();
TEST_STREQUAL(result[0], "/etc/xdg");
TEST_EQUAL(result[1], NULL);
free(result);
setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
result = xdg_config_dirs();
TEST_STREQUAL(result[0], "/etc/xdg");
TEST_STREQUAL(result[1], "/");
TEST_EQUAL(result[2], NULL);
free(result);
setenv("XDG_CONFIG_DIRS", ":", 1);
result = xdg_config_dirs();
TEST_EQUAL(result[0], NULL);
free(result);
if (old_var) {
setenv("XDG_CONFIG_DIRS", old_var, 1);
free(old_var);
}
}
/**
* Parse a long number.
*/
@ -438,6 +538,114 @@ bool parse_rule_opacity(c2_lptr_t **res, const char *src) {
return c2_parse(res, endptr, (void *)val);
}
/// Search for auxiliary file under a base directory
static char *locate_auxiliary_file_at(const char *base, const char *scope, const char *file) {
scoped_charp path = mstrjoin(base, scope);
mstrextend(&path, "/");
mstrextend(&path, file);
if (access(path, O_RDONLY) == 0) {
// Canonicalize path to avoid duplicates
char *abspath = realpath(path, NULL);
return abspath;
}
return NULL;
}
/**
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
* file.
*
* Follows the XDG specification to search for the shader file in configuration locations.
*
* The search order is:
* 1) If an absolute path is given, use it directly.
* 2) Search for the file directly under `include_dir`.
* 3) Search for the file in the XDG configuration directories, under path
* /picom/<scope>/
*/
char *locate_auxiliary_file(const char *scope, const char *path, const char *include_dir) {
if (!path || strlen(path) == 0) {
return NULL;
}
// Filename is absolute path, so try to load from there
if (path[0] == '/') {
if (access(path, O_RDONLY) == 0) {
return realpath(path, NULL);
}
}
// First try to load file from the include directory (i.e. relative to the
// config file)
if (include_dir && strlen(include_dir)) {
char *ret = locate_auxiliary_file_at(include_dir, "", path);
if (ret) {
return ret;
}
}
// Fall back to searching in user config directory
scoped_charp picom_scope = mstrjoin("/picom/", scope);
scoped_charp config_home = (char *)xdg_config_home();
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
if (ret) {
return ret;
}
// Fall back to searching in system config directory
auto config_dirs = xdg_config_dirs();
for (int i = 0; config_dirs[i]; i++) {
ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
if (ret) {
free(config_dirs);
return ret;
}
}
free(config_dirs);
return ret;
}
/**
* Parse a list of window shader rules.
*/
bool parse_rule_window_shader(c2_lptr_t **res, const char *src, const char *include_dir) {
if (!src) {
return false;
}
// Find custom shader terminator
const char *endptr = strchr(src, ':');
if (!endptr) {
log_error("Custom shader terminator not found: %s", src);
return false;
}
// Parse and create custom shader
scoped_charp untrimed_shader_source = strdup(src);
if (!untrimed_shader_source) {
return false;
}
auto source_end = strchr(untrimed_shader_source, ':');
*source_end = '\0';
size_t length;
char *tmp = (char *)trim_both(untrimed_shader_source, &length);
tmp[length] = '\0';
char *shader_source = NULL;
if (strcasecmp(tmp, "default") != 0) {
shader_source = locate_auxiliary_file("shaders", tmp, include_dir);
if (!shader_source) {
log_error("Custom shader file \"%s\" not found for rule: %s", tmp, src);
free(shader_source);
return false;
}
}
return c2_parse(res, ++endptr, (void *)shader_source);
}
/**
* Add a pattern to a condition linked list.
*/
@ -565,6 +773,8 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
.blur_background_blacklist = NULL,
.blur_kerns = NULL,
.blur_kernel_count = 0,
.window_shader_fg = NULL,
.window_shader_fg_rules = NULL,
.inactive_dim = 0.0,
.inactive_dim_fixed = false,
.invert_color_list = NULL,

View file

@ -15,6 +15,8 @@
#include <xcb/xcb.h>
#include <xcb/xfixes.h>
#include "uthash_extra.h"
#ifdef CONFIG_LIBCONFIG
#include <libconfig.h>
#endif
@ -206,6 +208,10 @@ typedef struct options {
struct conv **blur_kerns;
/// Number of convolution kernels
int blur_kernel_count;
/// Custom fragment shader for painting windows
char *window_shader_fg;
/// Rules to change custom fragment shader for painting windows.
c2_lptr_t *window_shader_fg_rules;
/// 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
@ -255,6 +261,9 @@ bool must_use parse_int(const char *, int *);
struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count);
bool must_use parse_geometry(session_t *, const char *, region_t *);
bool must_use parse_rule_opacity(c2_lptr_t **, const char *);
bool must_use parse_rule_window_shader(c2_lptr_t **, const char *, const char *);
char *must_use locate_auxiliary_file(const char *scope, const char *path,
const char *include_dir);
enum blur_method must_use parse_blur_method(const char *src);
/**
@ -263,6 +272,9 @@ enum blur_method must_use parse_blur_method(const char *src);
bool condlst_add(c2_lptr_t **, const char *);
#ifdef CONFIG_LIBCONFIG
const char *xdg_config_home(void);
char **xdg_config_dirs(void);
/// Parse a configuration file
/// Returns the actually config_file name used, allocated on heap
/// Outputs:

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2012-2014 Richard Grenville <pyxlcy@gmail.com>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -37,98 +38,6 @@ static inline int lcfg_lookup_bool(const config_t *config, const char *path, boo
return ret;
}
const char *xdg_config_home(void) {
char *xdgh = getenv("XDG_CONFIG_HOME");
char *home = getenv("HOME");
const char *default_dir = "/.config";
if (!xdgh) {
if (!home) {
return NULL;
}
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
strcpy(xdgh, home);
strcat(xdgh, default_dir);
} else {
xdgh = strdup(xdgh);
}
return xdgh;
}
char **xdg_config_dirs(void) {
char *xdgd = getenv("XDG_CONFIG_DIRS");
size_t count = 0;
if (!xdgd) {
xdgd = "/etc/xdg";
}
for (int i = 0; xdgd[i]; i++) {
if (xdgd[i] == ':') {
count++;
}
}
// Store the string and the result pointers together so they can be
// freed together
char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
auto path = dirs;
for (size_t i = 0; i < count; i++) {
dir_list[i] = path;
path = strchr(path, ':');
*path = '\0';
path++;
}
dir_list[count] = path;
size_t fill = 0;
for (size_t i = 0; i <= count; i++) {
if (dir_list[i][0] == '/') {
dir_list[fill] = dir_list[i];
fill++;
}
}
dir_list[fill] = NULL;
return dir_list;
}
TEST_CASE(xdg_config_dirs) {
auto old_var = getenv("XDG_CONFIG_DIRS");
if (old_var) {
old_var = strdup(old_var);
}
unsetenv("XDG_CONFIG_DIRS");
auto result = xdg_config_dirs();
TEST_STREQUAL(result[0], "/etc/xdg");
TEST_EQUAL(result[1], NULL);
free(result);
setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
result = xdg_config_dirs();
TEST_STREQUAL(result[0], "/etc/xdg");
TEST_STREQUAL(result[1], "/");
TEST_EQUAL(result[2], NULL);
free(result);
setenv("XDG_CONFIG_DIRS", ":", 1);
result = xdg_config_dirs();
TEST_EQUAL(result[0], NULL);
free(result);
if (old_var) {
setenv("XDG_CONFIG_DIRS", old_var, 1);
free(old_var);
}
}
/// Search for config file under a base directory
FILE *open_config_file_at(const char *base, char **out_path) {
static const char *config_paths[] = {"/picom.conf", "/picom/picom.conf",
@ -251,6 +160,36 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) {
}
}
/**
* Parse a window shader rule list in configuration file.
*/
static inline void parse_cfg_condlst_shader(options_t *opt, const config_t *pcfg,
const char *name, const char *include_dir) {
config_setting_t *setting = config_lookup(pcfg, name);
if (setting) {
// Parse an array of options
if (config_setting_is_array(setting)) {
int i = config_setting_length(setting);
while (i--) {
if (!parse_rule_window_shader(
&opt->window_shader_fg_rules,
config_setting_get_string_elem(setting, i),
include_dir)) {
exit(1);
}
}
}
// Treat it as a single pattern if it's a string
else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
if (!parse_rule_window_shader(&opt->window_shader_fg_rules,
config_setting_get_string(setting),
include_dir)) {
exit(1);
}
}
}
}
static inline void parse_wintype_config(const config_t *cfg, const char *member_name,
win_option_t *o, win_option_mask_t *mask) {
char *str = mstrjoin("wintypes.", member_name);
@ -602,6 +541,16 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
opt->max_brightness = 1.0;
}
// --window-shader-fg
if (config_lookup_string(&cfg, "window-shader-fg", &sval)) {
opt->window_shader_fg =
locate_auxiliary_file("shaders", sval, config_get_include_dir(&cfg));
}
// --window-shader-fg-rule
parse_cfg_condlst_shader(opt, &cfg, "window-shader-fg-rule",
config_get_include_dir(&cfg));
// --glx-use-gpushader4
if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival)) {
log_error("glx-use-gpushader4 has been removed, please remove it "

View file

@ -346,7 +346,18 @@ static void usage(const char *argv0, int ret) {
"\n"
"--transparent-clipping\n"
" Make transparent windows clip other windows like non-transparent windows\n"
" do, instead of blending on top of them\n";
" do, instead of blending on top of them\n"
"\n"
"--window-shader-fg shader\n"
" Specify GLSL fragment shader path for rendering window contents. Only\n"
" works when `--experimental-backends` is enabled.\n"
"\n"
"--window-shader-fg-rule shader:condition\n"
" Specify GLSL fragment shader path for rendering window contents using\n"
" patterns. Pattern should be in the format of `SHADER_PATH:PATTERN`,\n"
" similar to `--opacity-rule`. `SHADER_PATH` can be \"default\", in which\n"
" case the default shader will be used. Only works when\n"
" `--experimental-backends` is enabled.\n";
FILE *f = (ret ? stderr : stdout);
fprintf(f, usage_text, argv0);
#undef WARNING_DISABLED
@ -442,6 +453,8 @@ static const struct option longopts[] = {
{"corner-radius", required_argument, NULL, 333},
{"rounded-corners-exclude", required_argument, NULL, 334},
{"clip-shadow-above", required_argument, NULL, 335},
{"window-shader-fg", required_argument, NULL, 336},
{"window-shader-fg-rule", required_argument, NULL, 337},
{"experimental-backends", no_argument, NULL, 733},
{"monitor-repaint", no_argument, NULL, 800},
{"diagnostics", no_argument, NULL, 801},
@ -792,6 +805,24 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
case 317:
opt->glx_fshader_win_str = strdup(optarg);
break;
case 336: {
// --window-shader-fg
scoped_charp cwd = getcwd(NULL, 0);
opt->window_shader_fg =
locate_auxiliary_file("shaders", optarg, cwd);
if (!opt->window_shader_fg) {
exit(1);
}
break;
}
case 337: {
// --window-shader-fg-rule
scoped_charp cwd = getcwd(NULL, 0);
if (!parse_rule_window_shader(&opt->window_shader_fg_rules, optarg, cwd)) {
exit(1);
}
break;
}
case 321: {
enum log_level tmp_level = string_to_log_level(optarg);
if (tmp_level == LOG_LEVEL_INVALID) {
@ -803,13 +834,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
}
P_CASEBOOL(319, no_x_selection);
P_CASEBOOL(323, use_damage);
case 324:
opt->use_damage = false;
break;
case 325:
opt->vsync = false;
break;
case 324: opt->use_damage = false; break;
case 325: opt->vsync = false; break;
case 326:
opt->max_brightness = atof(optarg);
break;
@ -850,7 +876,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
break;
P_CASEBOOL(733, experimental_backends);
P_CASEBOOL(800, monitor_repaint);
case 801: opt->print_diagnostics = true; break;
case 801:
opt->print_diagnostics = true;
break;
P_CASEBOOL(802, debug_mode);
P_CASEBOOL(803, no_ewmh_fullscreen);
default: usage(argv[0], 1); break;
@ -895,6 +923,25 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
return false;
}
if (opt->glx_fshader_win_str && opt->experimental_backends) {
log_warn("--glx-fshader-win has been replaced by "
"\"--window-shader-fg\" for the experimental backends.");
}
if (opt->window_shader_fg || opt->window_shader_fg_rules) {
if (!opt->experimental_backends || opt->backend != BKEND_GLX) {
log_warn("The new window shader interface only works with the "
"experimental glx backend.%s",
(opt->backend == BKEND_GLX) ? " You may want to use "
"\"--glx-fshader-win\" "
"instead on the legacy "
"glx backend."
: "");
opt->window_shader_fg = NULL;
c2_list_free(&opt->window_shader_fg_rules, free);
}
}
// Range checking and option assignments
opt->fade_delta = max2(opt->fade_delta, 1);
opt->shadow_radius = max2(opt->shadow_radius, 0);

View file

@ -431,6 +431,14 @@ static void destroy_backend(session_t *ps) {
free_paint(ps, &w->paint);
}
HASH_ITER2(ps->shaders, shader) {
if (shader->backend_shader != NULL) {
ps->backend_data->ops->destroy_shader(ps->backend_data,
shader->backend_shader);
shader->backend_shader = NULL;
}
}
if (ps->backend_data && ps->root_image) {
ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
ps->root_image = NULL;
@ -499,10 +507,29 @@ static bool initialize_backend(session_t *ps) {
if (!initialize_blur(ps)) {
log_fatal("Failed to prepare for background blur, aborting...");
ps->backend_data->ops->deinit(ps->backend_data);
ps->backend_data = NULL;
quit(ps);
return false;
goto err;
}
// Create shaders
HASH_ITER2(ps->shaders, shader) {
assert(shader->backend_shader == NULL);
shader->backend_shader = ps->backend_data->ops->create_shader(
ps->backend_data, shader->source);
if (shader->backend_shader == NULL) {
log_warn("Failed to create shader for shader file %s, "
"this shader will not be used",
shader->key);
} else {
if (ps->backend_data->ops->get_shader_attributes) {
shader->attributes =
ps->backend_data->ops->get_shader_attributes(
ps->backend_data, shader->backend_shader);
} else {
shader->attributes = 0;
}
log_debug("Shader %s has attributes %ld", shader->key,
shader->attributes);
}
}
// window_stack shouldn't include window that's
@ -525,6 +552,11 @@ static bool initialize_backend(session_t *ps) {
// The old backends binds pixmap lazily, nothing to do here
return true;
err:
ps->backend_data->ops->deinit(ps->backend_data);
ps->backend_data = NULL;
quit(ps);
return false;
}
/// Handle configure event of the root window
@ -618,11 +650,13 @@ static void handle_root_flags(session_t *ps) {
}
}
static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
static struct managed_win *
paint_preprocess(session_t *ps, bool *fade_running, bool *animation) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered
struct managed_win *bottom = NULL;
*fade_running = false;
*animation = false;
// Fading step calculation
long long steps = 0L;
@ -637,7 +671,9 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
}
ps->fade_time += steps * ps->o.fade_delta;
// First, let's process fading
// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it.
win_stack_foreach_managed_safe(w, &ps->window_stack) {
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
@ -648,6 +684,11 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) {
add_damage_from_win(ps, w);
}
if (w->fg_shader && (w->fg_shader->attributes & SHADER_ATTRIBUTE_ANIMATED)) {
add_damage_from_win(ps, w);
*animation = true;
}
// Run fading
if (run_fade(ps, &w, steps)) {
*fade_running = true;
@ -1432,8 +1473,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
* screen is not redirected. its sole purpose should be to decide whether the
* screen should be redirected. */
bool fade_running = false;
bool animation = false;
bool was_redirected = ps->redirected;
auto bottom = paint_preprocess(ps, &fade_running);
auto bottom = paint_preprocess(ps, &fade_running, &animation);
ps->tmout_unredir_hit = false;
if (!was_redirected && ps->redirected) {
@ -1482,7 +1524,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
// TODO(yshui) Investigate how big the X critical section needs to be. There are
// suggestions that rendering should be in the critical section as well.
ps->redraw_needed = false;
ps->redraw_needed = animation;
}
static void draw_callback(EV_P_ ev_idle *w, int revents) {
@ -1490,8 +1532,9 @@ static void draw_callback(EV_P_ ev_idle *w, int revents) {
draw_callback_impl(EV_A_ ps, revents);
// Don't do painting non-stop unless we are in benchmark mode
if (!ps->o.benchmark) {
// Don't do painting non-stop unless we are in benchmark mode, or if
// draw_callback_impl thinks we should continue painting.
if (!ps->o.benchmark && !ps->redraw_needed) {
ev_idle_stop(EV_A_ & ps->draw_idle);
}
}
@ -1526,6 +1569,62 @@ static void config_file_change_cb(void *_ps) {
reset_enable(ps->loop, NULL, 0);
}
static bool load_shader_source(session_t *ps, const char *path) {
if (!path) {
// Using the default shader.
return false;
}
log_info("Loading shader source from %s", path);
struct shader_info *shader = NULL;
HASH_FIND_STR(ps->shaders, path, shader);
if (shader) {
log_debug("Shader already loaded, reusing");
return false;
}
shader = ccalloc(1, struct shader_info);
shader->key = strdup(path);
HASH_ADD_KEYPTR(hh, ps->shaders, shader->key, strlen(shader->key), shader);
FILE *f = fopen(path, "r");
if (!f) {
log_error("Failed to open custom shader file: %s", path);
goto err;
}
struct stat statbuf;
if (fstat(fileno(f), &statbuf) < 0) {
log_error("Failed to access custom shader file: %s", path);
goto err;
}
auto num_bytes = (size_t)statbuf.st_size;
shader->source = ccalloc(num_bytes + 1, char);
auto read_bytes = fread(shader->source, sizeof(char), num_bytes, f);
if (read_bytes < num_bytes || ferror(f)) {
// This is a difficult to hit error case, review thoroughly.
log_error("Failed to read custom shader at %s. (read %lu bytes, expected "
"%lu bytes)",
path, read_bytes, num_bytes);
goto err;
}
return false;
err:
HASH_DEL(ps->shaders, shader);
if (f) {
fclose(f);
}
free(shader->source);
free(shader->key);
free(shader);
return true;
}
static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) {
return load_shader_source(data, c2_list_get_data(cond));
}
/**
* Initialize a session.
*
@ -1753,6 +1852,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
return NULL;
}
if (ps->o.window_shader_fg) {
log_debug("Default window shader: \"%s\"", ps->o.window_shader_fg);
}
if (ps->o.logpath) {
auto l = file_logger_new(ps->o.logpath);
if (l) {
@ -1802,6 +1905,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
c2_list_postprocess(ps, ps->o.fade_blacklist) &&
c2_list_postprocess(ps, ps->o.blur_background_blacklist) &&
c2_list_postprocess(ps, ps->o.invert_color_list) &&
c2_list_postprocess(ps, ps->o.window_shader_fg_rules) &&
c2_list_postprocess(ps, ps->o.opacity_rules) &&
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
c2_list_postprocess(ps, ps->o.focus_blacklist))) {
@ -1809,6 +1913,22 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
"might not work");
}
// Load shader source file specified in the shader rules
if (c2_list_foreach(ps->o.window_shader_fg_rules, load_shader_source_for_condition, ps)) {
log_error("Failed to load shader source file for some of the window "
"shader rules");
}
if (load_shader_source(ps, ps->o.window_shader_fg)) {
log_error("Failed to load window shader source file");
}
if (log_get_level_tls() <= LOG_LEVEL_DEBUG) {
HASH_ITER2(ps->shaders, shader) {
log_debug("Shader %s:", shader->key);
log_debug("%s", shader->source);
}
}
ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
sum_kernel_preprocess(ps->gaussian_map);
@ -2164,16 +2284,17 @@ static void session_destroy(session_t *ps) {
list_init_head(&ps->window_stack);
// Free blacklists
c2_list_free(&ps->o.shadow_blacklist);
c2_list_free(&ps->o.shadow_clip_list);
c2_list_free(&ps->o.fade_blacklist);
c2_list_free(&ps->o.focus_blacklist);
c2_list_free(&ps->o.invert_color_list);
c2_list_free(&ps->o.blur_background_blacklist);
c2_list_free(&ps->o.opacity_rules);
c2_list_free(&ps->o.paint_blacklist);
c2_list_free(&ps->o.unredir_if_possible_blacklist);
c2_list_free(&ps->o.rounded_corners_blacklist);
c2_list_free(&ps->o.shadow_blacklist, NULL);
c2_list_free(&ps->o.shadow_clip_list, NULL);
c2_list_free(&ps->o.fade_blacklist, NULL);
c2_list_free(&ps->o.focus_blacklist, NULL);
c2_list_free(&ps->o.invert_color_list, NULL);
c2_list_free(&ps->o.blur_background_blacklist, NULL);
c2_list_free(&ps->o.opacity_rules, NULL);
c2_list_free(&ps->o.paint_blacklist, NULL);
c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL);
c2_list_free(&ps->o.rounded_corners_blacklist, NULL);
c2_list_free(&ps->o.window_shader_fg_rules, free);
// Free tracked atom list
{
@ -2224,6 +2345,17 @@ static void session_destroy(session_t *ps) {
free(ps->o.glx_fshader_win_str);
free_xinerama_info(ps);
// Release custom window shaders
free(ps->o.window_shader_fg);
struct shader_info *shader, *tmp;
HASH_ITER(hh, ps->shaders, shader, tmp) {
HASH_DEL(ps->shaders, shader);
assert(shader->backend_shader == NULL);
free(shader->source);
free(shader->key);
free(shader);
}
#ifdef CONFIG_VSYNC_DRM
// Close file opened for DRM VSync
if (ps->drm_fd >= 0) {

View file

@ -1052,6 +1052,19 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou
add_damage_from_win(ps, w);
}
static void
win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shader_new) {
if (w->fg_shader == shader_new) {
return;
}
w->fg_shader = shader_new;
// A different shader might change how the window is drawn, these changes should
// be rare however, so this should be fine.
add_damage_from_win(ps, w);
}
/**
* Determine if a window should have background blurred.
*/
@ -1098,6 +1111,28 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
}
}
/**
* Determine custom window shader to use for a window.
*/
static void win_determine_fg_shader(session_t *ps, struct managed_win *w) {
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
auto shader_new = ps->o.window_shader_fg;
void *val = NULL;
if (c2_match(ps, w, ps->o.window_shader_fg_rules, &val)) {
shader_new = val;
}
struct shader_info *shader = NULL;
if (shader_new) {
HASH_FIND_STR(ps->shaders, shader_new, shader);
}
win_set_fg_shader(ps, w, shader);
}
/**
* Update window opacity according to opacity rules.
*/
@ -1134,6 +1169,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
win_determine_invert_color(ps, w);
win_determine_blur_background(ps, w);
win_determine_rounded_corners(ps, w);
win_determine_fg_shader(ps, w);
w->mode = win_calc_mode(w);
log_debug("Window mode changed to %d", w->mode);
win_update_opacity_rule(ps, w);
@ -1463,6 +1499,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
.prev_trans = NULL,
.shadow = false,
.clip_shadow_above = false,
.fg_shader = NULL,
.xinerama_scr = -1,
.mode = WMODE_TRANS,
.ever_damaged = false,

View file

@ -209,7 +209,8 @@ struct managed_win {
/// Previous window opacity.
double opacity_target_old;
/// true if window (or client window, for broken window managers
/// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity prop
/// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity
/// prop
bool has_opacity_prop;
/// Cached value of opacity window attribute.
opacity_t opacity_prop;
@ -270,6 +271,9 @@ struct managed_win {
/// Whether to blur window background.
bool blur_background;
/// The custom window shader to use when rendering.
struct shader_info *fg_shader;
#ifdef CONFIG_OPENGL
/// Textures and FBO background blur use.
glx_blur_cache_t glx_blur_cache;

View file

@ -417,3 +417,10 @@ wintypes:
popup_menu = { opacity = 0.8; }
dropdown_menu = { opacity = 0.8; }
};
window-shader-fg-rule =
[
"shader.frag:name = 'test'",
" shader.frag :name = 'a'",
"default:name = 'b'"
]

View file

@ -0,0 +1 @@
vec4 window_shader() {}