diff --git a/README.md b/README.md index d34ca907..d42a65c2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ Sway is an incredible window manager, and certainly one of the most well establi - `shadows_on_csd enable|disable` (**Note**: The shadow might not fit some windows) - `shadow_blur_radius ` - `shadow_color ex, #0000007F` ++ Window blur: *ONLY ON SWAYFX-GIT, NOT YET RELEASED* + - `blur enable|disable` + - `blur_xray enable|disable` + - `blur_passes ` + - `blur_radius ` + Dim unfocused windows: - `default_dim_inactive ` - `for_window [CRITERIA_HERE] dim_inactive ` diff --git a/config.in b/config.in index 8053f03a..11c11ec5 100644 --- a/config.in +++ b/config.in @@ -24,6 +24,12 @@ set $menu dmenu_path | dmenu | xargs swaymsg exec -- # window corner radius in px corner_radius 10 +# Window background blur +blur off +blur_xray off +blur_passes 2 +blur_radius 5 + shadows off shadows_on_csd off shadow_blur_radius 20 diff --git a/include/sway/commands.h b/include/sway/commands.h index 91b1fc58..b895d5f2 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -109,6 +109,10 @@ sway_cmd cmd_bindcode; sway_cmd cmd_bindgesture; sway_cmd cmd_bindswitch; sway_cmd cmd_bindsym; +sway_cmd cmd_blur; +sway_cmd cmd_blur_passes; +sway_cmd cmd_blur_radius; +sway_cmd cmd_blur_xray; sway_cmd cmd_border; sway_cmd cmd_client_noop; sway_cmd cmd_client_focused; diff --git a/include/sway/config.h b/include/sway/config.h index 2dc4b52d..cabc9cf5 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -470,23 +470,33 @@ enum xwayland_mode { XWAYLAND_MODE_IMMEDIATE, }; +struct blur_parameters { + int num_passes; + int radius; +}; + /** * The configuration struct. The result of loading a config file. */ struct sway_config { - // SwayFX config options int corner_radius; bool smart_corner_radius; + float default_dim_inactive; - // dim_inactive colors struct { float unfocused[4]; float urgent[4]; } dim_inactive_colors; + bool shadow_enabled; bool shadows_on_csd_enabled; int shadow_blur_sigma; float shadow_color[4]; + + bool blur_enabled; + bool blur_xray; + struct blur_parameters blur_params; + bool titlebar_separator; bool scratchpad_minimize; diff --git a/include/sway/desktop/fx_renderer/fx_framebuffer.h b/include/sway/desktop/fx_renderer/fx_framebuffer.h new file mode 100644 index 00000000..965c1def --- /dev/null +++ b/include/sway/desktop/fx_renderer/fx_framebuffer.h @@ -0,0 +1,23 @@ +#ifndef FX_FRAMEBUFFER_H +#define FX_FRAMEBUFFER_H + +#include +#include +#include + +#include "sway/desktop/fx_renderer/fx_texture.h" + +struct fx_framebuffer { + struct fx_texture texture; + GLuint fb; +}; + +void fx_framebuffer_bind(struct fx_framebuffer *buffer, GLsizei width, GLsizei height); + +void fx_framebuffer_create(struct wlr_output *output, struct fx_framebuffer *buffer, + bool bind); + +void fx_framebuffer_release(struct fx_framebuffer *buffer); + + +#endif diff --git a/include/sway/desktop/fx_renderer/fx_renderer.h b/include/sway/desktop/fx_renderer/fx_renderer.h index a48a00e1..37887d30 100644 --- a/include/sway/desktop/fx_renderer/fx_renderer.h +++ b/include/sway/desktop/fx_renderer/fx_renderer.h @@ -5,6 +5,9 @@ #include #include +#include "sway/desktop/fx_renderer/fx_framebuffer.h" +#include "sway/desktop/fx_renderer/fx_texture.h" + enum corner_location { ALL, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, NONE }; enum fx_tex_shader_source { @@ -24,8 +27,9 @@ struct decoration_data { float saturation; int corner_radius; float dim; - float* dim_color; + float *dim_color; bool has_titlebar; + bool blur; }; struct gles2_tex_shader { @@ -54,13 +58,34 @@ struct rounded_quad_shader { GLint radius; }; +struct blur_shader { + GLuint program; + GLint proj; + GLint tex; + GLint pos_attrib; + GLint tex_attrib; + GLint radius; + GLint halfpixel; +}; + struct fx_renderer { struct wlr_egl *egl; float projection[9]; + struct sway_output *sway_output; + GLuint stencil_buffer_id; + struct fx_framebuffer wlr_buffer; // Just the framebuffer used by wlroots + struct fx_framebuffer main_buffer; // The main FB used for rendering + struct fx_framebuffer blur_buffer; // Contains the blurred background for tiled windows + // Blur swaps between the two effects buffers everytime it scales the image + struct fx_framebuffer effects_buffer; // Buffer used for effects + struct fx_framebuffer effects_buffer_swapped; // Swap buffer used for effects + + bool blur_buffer_dirty; + struct { bool OES_egl_image_external; } exts; @@ -83,6 +108,9 @@ struct fx_renderer { struct rounded_quad_shader rounded_tl_quad; struct rounded_quad_shader rounded_tr_quad; + struct blur_shader blur1; + struct blur_shader blur2; + struct { GLuint program; GLint proj; @@ -117,19 +145,21 @@ struct fx_renderer { struct fx_renderer *fx_renderer_create(struct wlr_egl *egl); -void fx_renderer_begin(struct fx_renderer *renderer, uint32_t width, uint32_t height); +void fx_renderer_fini(struct fx_renderer *renderer); -void fx_renderer_end(); +void fx_renderer_begin(struct fx_renderer *renderer, struct sway_output *output); + +void fx_renderer_end(struct fx_renderer *renderer); void fx_renderer_clear(const float color[static 4]); void fx_renderer_scissor(struct wlr_box *box); -bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, +bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct fx_texture *fx_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data); -bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, +bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct fx_texture *fx_texture, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data); void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, @@ -146,4 +176,8 @@ void fx_render_border_corner(struct fx_renderer *renderer, const struct wlr_box void fx_render_box_shadow(struct fx_renderer *renderer, const struct wlr_box *box, const float color[static 4], const float projection[static 9], int radius, float blur_sigma); +void fx_render_blur(struct fx_renderer *renderer, struct sway_output *output, + const float matrix[static 9], struct fx_framebuffer **buffer, + struct blur_shader *shader, const struct wlr_box *box, int blur_radius); + #endif diff --git a/include/sway/desktop/fx_renderer/fx_texture.h b/include/sway/desktop/fx_renderer/fx_texture.h new file mode 100644 index 00000000..0c375913 --- /dev/null +++ b/include/sway/desktop/fx_renderer/fx_texture.h @@ -0,0 +1,18 @@ +#ifndef FX_TEXTURE_H +#define FX_TEXTURE_H + +#include +#include +#include + +struct fx_texture { + GLuint target; + GLuint id; + bool has_alpha; + int width; + int height; +}; + +struct fx_texture fx_texture_from_wlr_texture(struct wlr_texture* tex); + +#endif diff --git a/include/sway/output.h b/include/sway/output.h index a6bec10a..65f7ca1a 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -24,6 +24,8 @@ struct sway_output { struct sway_server *server; struct wl_list link; + struct fx_renderer *renderer; + struct wl_list layers[4]; // sway_layer_surface::link struct wlr_box usable_area; diff --git a/include/sway/server.h b/include/sway/server.h index 0c2eccf3..96c3623f 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -37,7 +37,6 @@ struct sway_server { // secondary headless backend used for creating virtual outputs on-the-fly struct wlr_backend *headless_backend; struct wlr_renderer *wlr_renderer; - struct fx_renderer *renderer; struct wlr_allocator *allocator; struct wlr_compositor *compositor; diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 1d8e5a36..3cd668f9 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -115,6 +115,8 @@ struct sway_container { bool shadow_enabled; + bool blur_enabled; + float saturation; float alpha; diff --git a/sway/commands.c b/sway/commands.c index fbe17039..8e2d8f89 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -49,6 +49,10 @@ static const struct cmd_handler handlers[] = { { "bindgesture", cmd_bindgesture }, { "bindswitch", cmd_bindswitch }, { "bindsym", cmd_bindsym }, + { "blur", cmd_blur }, + { "blur_passes", cmd_blur_passes }, + { "blur_radius", cmd_blur_radius }, + { "blur_xray", cmd_blur_xray }, { "client.background", cmd_client_noop }, { "client.focused", cmd_client_focused }, { "client.focused_inactive", cmd_client_focused_inactive }, diff --git a/sway/commands/blur.c b/sway/commands/blur.c new file mode 100644 index 00000000..15dd985d --- /dev/null +++ b/sway/commands/blur.c @@ -0,0 +1,31 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "util.h" + +struct cmd_results *cmd_blur(int argc, char **argv) { + struct cmd_results *error = checkarg(argc, "blur", EXPECTED_AT_LEAST, 1); + + if (error) { + return error; + } + + struct sway_container *con = config->handler_context.container; + + bool result = parse_boolean(argv[0], config->blur_enabled); + if (con == NULL) { + config->blur_enabled = result; + } else { + con->blur_enabled = result; + } + + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { + if (output->renderer) { + output->renderer->blur_buffer_dirty = true; + output_damage_whole(output); + } + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/commands/blur_passes.c b/sway/commands/blur_passes.c new file mode 100644 index 00000000..0868a568 --- /dev/null +++ b/sway/commands/blur_passes.c @@ -0,0 +1,28 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" + +struct cmd_results *cmd_blur_passes(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "blur_passes", EXPECTED_EQUAL_TO, 1))) { + return error; + } + + char *inv; + int value = strtol(argv[0], &inv, 10); + if (*inv != '\0' || value < 0 || value > 10) { + return cmd_results_new(CMD_FAILURE, "Invalid size specified"); + } + + config->blur_params.num_passes = value; + + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { + if (output->renderer) { + output->renderer->blur_buffer_dirty = true; + output_damage_whole(output); + } + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/commands/blur_radius.c b/sway/commands/blur_radius.c new file mode 100644 index 00000000..f6e7d4ed --- /dev/null +++ b/sway/commands/blur_radius.c @@ -0,0 +1,28 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" + +struct cmd_results *cmd_blur_radius(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "blur_radius", EXPECTED_AT_LEAST, 1))) { + return error; + } + + char *inv; + int value = strtol(argv[0], &inv, 10); + if (*inv != '\0' || value < 0 || value > 10) { + return cmd_results_new(CMD_FAILURE, "Invalid size specified"); + } + + config->blur_params.radius = value; + + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { + if (output->renderer) { + output->renderer->blur_buffer_dirty = true; + output_damage_whole(output); + } + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/commands/blur_xray.c b/sway/commands/blur_xray.c new file mode 100644 index 00000000..045566d0 --- /dev/null +++ b/sway/commands/blur_xray.c @@ -0,0 +1,25 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "util.h" + +struct cmd_results *cmd_blur_xray(int argc, char **argv) { + struct cmd_results *error = checkarg(argc, "blur_xray", EXPECTED_AT_LEAST, 1); + + if (error) { + return error; + } + + bool result = parse_boolean(argv[0], config->blur_xray); + config->blur_xray = result; + + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { + if (output->renderer) { + output->renderer->blur_buffer_dirty = true; + output_damage_whole(output); + } + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/config.c b/sway/config.c index 24022762..85e53679 100644 --- a/sway/config.c +++ b/sway/config.c @@ -336,13 +336,21 @@ static void config_defaults(struct sway_config *config) { // SwayFX defaults config->corner_radius = 0; config->smart_corner_radius = true; + config->default_dim_inactive = 0.0f; color_to_rgba(config->dim_inactive_colors.unfocused, 0x000000FF); color_to_rgba(config->dim_inactive_colors.urgent, 0x900000FF); + config->shadow_enabled = false; config->shadows_on_csd_enabled = false; config->shadow_blur_sigma = 20.0f; color_to_rgba(config->shadow_color, 0x0000007F); + + config->blur_enabled = false; + config->blur_xray = false; + config->blur_params.num_passes = 2; + config->blur_params.radius = 5; + config->titlebar_separator = true; config->scratchpad_minimize = true; diff --git a/sway/desktop/fx_renderer/fx_framebuffer.c b/sway/desktop/fx_renderer/fx_framebuffer.c new file mode 100644 index 00000000..db6f8928 --- /dev/null +++ b/sway/desktop/fx_renderer/fx_framebuffer.c @@ -0,0 +1,70 @@ +#include "log.h" +#include "sway/desktop/fx_renderer/fx_framebuffer.h" + +void fx_framebuffer_bind(struct fx_framebuffer *buffer, GLsizei width, GLsizei height) { + glBindFramebuffer(GL_FRAMEBUFFER, buffer->fb); + glViewport(0, 0, width, height); +} + +void fx_framebuffer_create(struct wlr_output *output, struct fx_framebuffer *buffer, bool bind) { + bool firstAlloc = false; + + // Create a new framebuffer + if (buffer->fb == (uint32_t) -1) { + glGenFramebuffers(1, &buffer->fb); + firstAlloc = true; + } + + if (buffer->texture.id == 0) { + firstAlloc = true; + glGenTextures(1, &buffer->texture.id); + glBindTexture(GL_TEXTURE_2D, buffer->texture.id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + if (firstAlloc || buffer->texture.width != width || buffer->texture.height != height) { + glBindTexture(GL_TEXTURE_2D, buffer->texture.id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, buffer->fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + buffer->texture.id, 0); + buffer->texture.target = GL_TEXTURE_2D; + buffer->texture.has_alpha = false; + buffer->texture.width = width; + buffer->texture.height = height; + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + sway_log(SWAY_ERROR, "Framebuffer incomplete, couldn't create! (FB status: %i)", status); + return; + } + sway_log(SWAY_DEBUG, "Framebuffer created, status %i", status); + } + + // Bind the default framebuffer + glBindTexture(GL_TEXTURE_2D, 0); + if (bind) { + fx_framebuffer_bind(buffer, width, height); + } +} + +void fx_framebuffer_release(struct fx_framebuffer *buffer) { + if (buffer->fb != (uint32_t) -1 && buffer->fb) { + glDeleteFramebuffers(1, &buffer->fb); + } + buffer->fb= -1; + + if (buffer->texture.id) { + glDeleteTextures(1, &buffer->texture.id); + } + buffer->texture.id = 0; + buffer->texture.width = -1; + buffer->texture.height = -1; +} diff --git a/sway/desktop/fx_renderer/fx_renderer.c b/sway/desktop/fx_renderer/fx_renderer.c index 36c5bb61..715c5b15 100644 --- a/sway/desktop/fx_renderer/fx_renderer.c +++ b/sway/desktop/fx_renderer/fx_renderer.c @@ -19,6 +19,8 @@ #include "sway/server.h" // shaders +#include "blur1_frag_src.h" +#include "blur2_frag_src.h" #include "box_shadow_frag_src.h" #include "common_vert_src.h" #include "corner_frag_src.h" @@ -33,6 +35,33 @@ static const GLfloat verts[] = { 0, 1, // bottom left }; +static void create_stencil_buffer(struct wlr_output* output, GLuint *buffer_id) { + if (*buffer_id != (uint32_t) -1) { + return; + } + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + glGenRenderbuffers(1, buffer_id); + glBindRenderbuffer(GL_RENDERBUFFER, *buffer_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *buffer_id); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + sway_log(SWAY_ERROR, "Stencilbuffer incomplete, couldn't create! (FB status: %i)", status); + return; + } + sway_log(SWAY_DEBUG, "Stencilbuffer created, status %i", status); +} + +static void release_stencil_buffer(GLuint *buffer_id) { + if (*buffer_id != (uint32_t)-1 && buffer_id) { + glDeleteRenderbuffers(1, buffer_id); + } + *buffer_id = -1; +} + static GLuint compile_shader(GLuint type, const GLchar *src) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &src, NULL); @@ -179,6 +208,15 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { // TODO: needed? renderer->egl = egl; + renderer->main_buffer.fb = -1; + + renderer->blur_buffer.fb = -1; + renderer->effects_buffer.fb = -1; + renderer->effects_buffer_swapped.fb = -1; + renderer->stencil_buffer_id = -1; + + renderer->blur_buffer_dirty = true; + // get extensions const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); if (exts_str == NULL) { @@ -258,6 +296,32 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { renderer->shaders.box_shadow.blur_sigma = glGetUniformLocation(prog, "blur_sigma"); renderer->shaders.box_shadow.corner_radius = glGetUniformLocation(prog, "corner_radius"); + // Blur 1 + prog = link_program(blur1_frag_src); + renderer->shaders.blur1.program = prog; + if (!renderer->shaders.blur1.program) { + goto error; + } + renderer->shaders.blur1.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.blur1.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.blur1.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.blur1.tex_attrib = glGetAttribLocation(prog, "texcoord"); + renderer->shaders.blur1.radius = glGetUniformLocation(prog, "radius"); + renderer->shaders.blur1.halfpixel = glGetUniformLocation(prog, "halfpixel"); + + // Blur 2 + prog = link_program(blur2_frag_src); + renderer->shaders.blur2.program = prog; + if (!renderer->shaders.blur2.program) { + goto error; + } + renderer->shaders.blur2.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.blur2.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.blur2.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.blur2.tex_attrib = glGetAttribLocation(prog, "texcoord"); + renderer->shaders.blur2.radius = glGetUniformLocation(prog, "radius"); + renderer->shaders.blur2.halfpixel = glGetUniformLocation(prog, "halfpixel"); + // fragment shaders if (!link_tex_program(renderer, &renderer->shaders.tex_rgba, SHADER_SOURCE_TEXTURE_RGBA)) { @@ -289,6 +353,8 @@ error: glDeleteProgram(renderer->shaders.rounded_tr_quad.program); glDeleteProgram(renderer->shaders.corner.program); glDeleteProgram(renderer->shaders.box_shadow.program); + glDeleteProgram(renderer->shaders.blur1.program); + glDeleteProgram(renderer->shaders.blur2.program); glDeleteProgram(renderer->shaders.tex_rgba.program); glDeleteProgram(renderer->shaders.tex_rgbx.program); glDeleteProgram(renderer->shaders.tex_ext.program); @@ -305,27 +371,53 @@ error: return NULL; } -void fx_renderer_begin(struct fx_renderer *renderer, uint32_t width, uint32_t height) { - // Create and render the stencil buffer - if (renderer->stencil_buffer_id == 0) { - glGenRenderbuffers(1, &renderer->stencil_buffer_id); - } - glBindRenderbuffer(GL_RENDERBUFFER, renderer->stencil_buffer_id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, renderer->stencil_buffer_id); +void fx_renderer_fini(struct fx_renderer *renderer) { + fx_framebuffer_release(&renderer->main_buffer); + fx_framebuffer_release(&renderer->blur_buffer); + fx_framebuffer_release(&renderer->effects_buffer); + fx_framebuffer_release(&renderer->effects_buffer_swapped); + release_stencil_buffer(&renderer->stencil_buffer_id); +} - glViewport(0, 0, width, height); +void fx_renderer_begin(struct fx_renderer *renderer, struct sway_output *sway_output) { + struct wlr_output *output = sway_output->wlr_output; + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + renderer->sway_output = sway_output; + // Store the wlr framebuffer + GLint wlr_fb = -1; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &wlr_fb); + if (wlr_fb < 0) { + sway_log(SWAY_ERROR, "Failed to get wlr framebuffer!"); + abort(); + } + renderer->wlr_buffer.fb = wlr_fb; + + // Create the main framebuffer + fx_framebuffer_create(output, &renderer->main_buffer, true); + // Create the stencil buffer and attach it to our main_buffer + create_stencil_buffer(output, &renderer->stencil_buffer_id); + + // Create a new blur/effects framebuffers + fx_framebuffer_create(output, &renderer->effects_buffer, false); + fx_framebuffer_create(output, &renderer->effects_buffer_swapped, false); // refresh projection matrix matrix_projection(renderer->projection, width, height, WL_OUTPUT_TRANSFORM_FLIPPED_180); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Bind to our main framebuffer + fx_framebuffer_bind(&renderer->main_buffer, width, height); } -void fx_renderer_end() { - // TODO +void fx_renderer_end(struct fx_renderer *renderer) { + // Release the main buffer + fx_framebuffer_release(&renderer->main_buffer); + release_stencil_buffer(&renderer->stencil_buffer_id); } void fx_renderer_clear(const float color[static 4]) { @@ -335,26 +427,23 @@ void fx_renderer_clear(const float color[static 4]) { } void fx_renderer_scissor(struct wlr_box *box) { - if (box) { - glScissor(box->x, box->y, box->width, box->height); - glEnable(GL_SCISSOR_TEST); - } else { - glDisable(GL_SCISSOR_TEST); - } + if (box) { + glScissor(box->x, box->y, box->width, box->height); + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } } -bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, +bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct fx_texture *fx_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data) { - assert(wlr_texture_is_gles2(wlr_texture)); - struct wlr_gles2_texture_attribs texture_attrs; - wlr_gles2_texture_get_attribs(wlr_texture, &texture_attrs); struct gles2_tex_shader *shader = NULL; - switch (texture_attrs.target) { + switch (fx_texture->target) { case GL_TEXTURE_2D: - if (texture_attrs.has_alpha) { + if (fx_texture->has_alpha) { shader = &renderer->shaders.tex_rgba; } else { shader = &renderer->shaders.tex_rgbx; @@ -382,16 +471,18 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t wlr_matrix_transpose(gl_matrix, gl_matrix); // if there's no opacity or rounded corners we don't need to blend - if (!texture_attrs.has_alpha && deco_data.alpha == 1.0 && !deco_data.corner_radius) { + if (!fx_texture->has_alpha && deco_data.alpha == 1.0 && !deco_data.corner_radius) { glDisable(GL_BLEND); } else { glEnable(GL_BLEND); } - glActiveTexture(GL_TEXTURE0); - glBindTexture(texture_attrs.target, texture_attrs.tex); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glTexParameteri(texture_attrs.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glActiveTexture(GL_TEXTURE0); + glBindTexture(fx_texture->target, fx_texture->id); + + glTexParameteri(fx_texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(shader->program); @@ -408,10 +499,10 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t glUniform1f(shader->saturation, deco_data.saturation); glUniform1f(shader->radius, deco_data.corner_radius); - const GLfloat x1 = src_box->x / wlr_texture->width; - const GLfloat y1 = src_box->y / wlr_texture->height; - const GLfloat x2 = (src_box->x + src_box->width) / wlr_texture->width; - const GLfloat y2 = (src_box->y + src_box->height) / wlr_texture->height; + const GLfloat x1 = src_box->x / fx_texture->width; + const GLfloat y1 = src_box->y / fx_texture->height; + const GLfloat x2 = (src_box->x + src_box->width) / fx_texture->width; + const GLfloat y2 = (src_box->y + src_box->height) / fx_texture->height; const GLfloat texcoord[] = { x2, y1, // top right x1, y1, // top left @@ -430,21 +521,21 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t glDisableVertexAttribArray(shader->pos_attrib); glDisableVertexAttribArray(shader->tex_attrib); - glBindTexture(texture_attrs.target, 0); + glBindTexture(fx_texture->target, 0); return true; } -bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, +bool fx_render_texture_with_matrix(struct fx_renderer *renderer, struct fx_texture *texture, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data) { struct wlr_fbox src_box = { .x = 0, .y = 0, - .width = wlr_texture->width, - .height = wlr_texture->height, + .width = texture->width, + .height = texture->height, }; - return fx_render_subtexture_with_matrix(renderer, wlr_texture, &src_box, + return fx_render_subtexture_with_matrix(renderer, texture, &src_box, dst_box, matrix, deco_data); } @@ -669,3 +760,46 @@ void fx_render_box_shadow(struct fx_renderer *renderer, const struct wlr_box *bo glClear(GL_STENCIL_BUFFER_BIT); glDisable(GL_STENCIL_TEST); } + +void fx_render_blur(struct fx_renderer *renderer, struct sway_output *output, + const float matrix[static 9], struct fx_framebuffer **buffer, + struct blur_shader *shader, const struct wlr_box *box, int blur_radius) { + glDisable(GL_BLEND); + glDisable(GL_STENCIL_TEST); + + glActiveTexture(GL_TEXTURE0); + + glBindTexture((*buffer)->texture.target, (*buffer)->texture.id); + + glTexParameteri((*buffer)->texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glUseProgram(shader->program); + + // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set + // to GL_FALSE + float gl_matrix[9]; + wlr_matrix_transpose(gl_matrix, matrix); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, gl_matrix); + + glUniform1i(shader->tex, 0); + glUniform1f(shader->radius, blur_radius); + + int width, height; + wlr_output_transformed_resolution(output->wlr_output, &width, &height); + if (shader == &renderer->shaders.blur1) { + glUniform2f(shader->halfpixel, 0.5f / (width / 2.0f), 0.5f / (height / 2.0f)); + } else { + glUniform2f(shader->halfpixel, 0.5f / (width * 2.0f), 0.5f / (height * 2.0f)); + } + + glVertexAttribPointer(shader->pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(shader->tex_attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); + + glEnableVertexAttribArray(shader->pos_attrib); + glEnableVertexAttribArray(shader->tex_attrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(shader->pos_attrib); + glDisableVertexAttribArray(shader->tex_attrib); +} diff --git a/sway/desktop/fx_renderer/fx_texture.c b/sway/desktop/fx_renderer/fx_texture.c new file mode 100644 index 00000000..60aa9a26 --- /dev/null +++ b/sway/desktop/fx_renderer/fx_texture.c @@ -0,0 +1,19 @@ +#include +#include + +#include "sway/desktop/fx_renderer/fx_texture.h" + +struct fx_texture fx_texture_from_wlr_texture(struct wlr_texture* texture) { + assert(wlr_texture_is_gles2(texture)); + + struct wlr_gles2_texture_attribs texture_attrs; + wlr_gles2_texture_get_attribs(texture, &texture_attrs); + + return (struct fx_texture) { + .target = texture_attrs.target, + .id = texture_attrs.tex, + .has_alpha = texture_attrs.has_alpha, + .width = texture->width, + .height = texture->height, + }; +} diff --git a/sway/desktop/fx_renderer/shaders/blur1.frag b/sway/desktop/fx_renderer/shaders/blur1.frag new file mode 100644 index 00000000..e7cb1be8 --- /dev/null +++ b/sway/desktop/fx_renderer/shaders/blur1.frag @@ -0,0 +1,18 @@ +precision mediump float; +varying mediump vec2 v_texcoord; +uniform sampler2D tex; + +uniform float radius; +uniform vec2 halfpixel; + +void main() { + vec2 uv = v_texcoord * 2.0; + + vec4 sum = texture2D(tex, uv) * 4.0; + sum += texture2D(tex, uv - halfpixel.xy * radius); + sum += texture2D(tex, uv + halfpixel.xy * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); + + gl_FragColor = sum / 8.0; +} diff --git a/sway/desktop/fx_renderer/shaders/blur2.frag b/sway/desktop/fx_renderer/shaders/blur2.frag new file mode 100644 index 00000000..3755a294 --- /dev/null +++ b/sway/desktop/fx_renderer/shaders/blur2.frag @@ -0,0 +1,22 @@ +precision mediump float; +varying mediump vec2 v_texcoord; +uniform sampler2D tex; + +uniform float radius; +uniform vec2 halfpixel; + +void main() { + vec2 uv = v_texcoord / 2.0; + + vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); + + sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); + sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; + + gl_FragColor = sum / 12.0; +} diff --git a/sway/desktop/fx_renderer/shaders/meson.build b/sway/desktop/fx_renderer/shaders/meson.build index 6c0dd1d3..83119963 100644 --- a/sway/desktop/fx_renderer/shaders/meson.build +++ b/sway/desktop/fx_renderer/shaders/meson.build @@ -1,6 +1,8 @@ embed = find_program('./embed.sh', native: true) shaders = [ + 'blur1.frag', + 'blur2.frag', 'box_shadow.frag', 'common.vert', 'corner.frag', diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index 6e3cc0e2..a8356ad7 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c @@ -290,6 +290,12 @@ static void handle_surface_commit(struct wl_listener *listener, void *data) { struct sway_output *output = wlr_output->data; struct wlr_box old_extent = layer->extent; + // Rerender the static blur on change + if (layer->layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND + || layer->layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { + output->renderer->blur_buffer_dirty = true; + } + bool layer_changed = false; if (layer_surface->current.committed != 0 || layer->mapped != layer_surface->mapped) { @@ -366,6 +372,13 @@ static void handle_destroy(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = sway_layer->layer_surface->output; sway_assert(wlr_output, "wlr_layer_surface_v1 has null output"); struct sway_output *output = wlr_output->data; + + // Rerender the static blur + if (sway_layer->layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND + || sway_layer->layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { + output->renderer->blur_buffer_dirty = true; + } + arrange_layers(output); transaction_commit_dirty(); wl_list_remove(&sway_layer->output_destroy.link); diff --git a/sway/desktop/output.c b/sway/desktop/output.c index a7269b7b..45c2f1c4 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -745,12 +746,11 @@ static void damage_child_views_iterator(struct sway_container *con, void output_damage_whole_container(struct sway_output *output, struct sway_container *con) { // Pad the box by 1px, because the width is a double and might be a fraction - int shadow_sigma = con->shadow_enabled ? config->shadow_blur_sigma : 0; struct wlr_box box = { - .x = con->current.x - output->lx - 1 - shadow_sigma, - .y = con->current.y - output->ly - 1 - shadow_sigma, - .width = con->current.width + 2 + shadow_sigma * 2, - .height = con->current.height + 2 + shadow_sigma * 2, + .x = con->current.x - output->lx - 1, + .y = con->current.y - output->ly - 1, + .width = con->current.width + 2, + .height = con->current.height + 2, }; scale_box(&box, output->wlr_output->scale); if (wlr_damage_ring_add_box(&output->damage_ring, &box)) { @@ -798,6 +798,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) { output_disable(output); } + fx_renderer_fini(output->renderer); + output_begin_destroy(output); wl_list_remove(&output->link); @@ -939,6 +941,14 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->server = server; wlr_damage_ring_init(&output->damage_ring); + // Init FX Renderer + struct wlr_egl *egl = wlr_gles2_renderer_get_egl(server->wlr_renderer); + output->renderer = fx_renderer_create(egl); + if (!output->renderer) { + sway_log(SWAY_ERROR, "Failed to create fx_renderer"); + abort(); + } + wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->destroy.notify = handle_destroy; wl_signal_add(&wlr_output->events.commit, &output->commit); @@ -963,6 +973,10 @@ void handle_new_output(struct wl_listener *listener, void *data) { transaction_commit_dirty(); + // From sway upstream (fixes damage_ring bounds being INT_MAX) + int width, height; + wlr_output_transformed_resolution(output->wlr_output, &width, &height); + wlr_damage_ring_set_bounds(&output->damage_ring, width, height); update_output_manager_config(server); } diff --git a/sway/desktop/render.c b/sway/desktop/render.c index a89cbf6b..d2b366b5 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -19,6 +19,7 @@ #include "config.h" #include "log.h" #include "sway/config.h" +#include "sway/desktop/fx_renderer/fx_framebuffer.h" #include "sway/desktop/fx_renderer/fx_renderer.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" @@ -37,6 +38,13 @@ struct render_data { struct decoration_data deco_data; }; +struct workspace_effect_info { + bool container_wants_blur; + bool container_wants_shadow; + bool should_render_optimized_blur; + int expanded_size; +}; + struct decoration_data get_undecorated_decoration_data() { return (struct decoration_data) { .alpha = 1.0f, @@ -45,9 +53,18 @@ struct decoration_data get_undecorated_decoration_data() { .corner_radius = 0, .saturation = 1.0f, .has_titlebar = false, + .blur = false, }; } +int get_blur_size() { + return pow(2, config->blur_params.num_passes) * config->blur_params.radius; +} + +bool should_parameters_blur() { + return config->blur_params.radius > 0 && config->blur_params.num_passes > 0; +} + /** * Apply scale to a width or height. * @@ -67,7 +84,7 @@ static int scale_length(int length, int offset, float scale) { static void scissor_output(struct wlr_output *wlr_output, pixman_box32_t *rect) { struct sway_output *output = wlr_output->data; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; assert(renderer); struct wlr_box box = { @@ -80,30 +97,22 @@ static void scissor_output(struct wlr_output *wlr_output, int ow, oh; wlr_output_transformed_resolution(wlr_output, &ow, &oh); - enum wl_output_transform transform = - wlr_output_transform_invert(wlr_output->transform); + enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); wlr_box_transform(&box, &box, transform, ow, oh); fx_renderer_scissor(&box); } static void set_scale_filter(struct wlr_output *wlr_output, - struct wlr_texture *texture, enum scale_filter_mode scale_filter) { - if (!wlr_texture_is_gles2(texture)) { - return; - } - - struct wlr_gles2_texture_attribs attribs; - wlr_gles2_texture_get_attribs(texture, &attribs); - - glBindTexture(attribs.target, attribs.tex); + struct fx_texture *texture, enum scale_filter_mode scale_filter) { + glBindTexture(texture->target, texture->id); switch (scale_filter) { case SCALE_FILTER_LINEAR: - glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; case SCALE_FILTER_NEAREST: - glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; case SCALE_FILTER_DEFAULT: case SCALE_FILTER_SMART: @@ -120,12 +129,19 @@ pixman_region32_t create_damage(const struct wlr_box damage_box, pixman_region32 return damage; } +struct wlr_box get_monitor_box(struct wlr_output *output) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + struct wlr_box monitor_box = { 0, 0, width, height }; + return monitor_box; +} + static void render_texture(struct wlr_output *wlr_output, - pixman_region32_t *output_damage, struct wlr_texture *texture, + pixman_region32_t *output_damage, struct fx_texture *texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data) { struct sway_output *output = wlr_output->data; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; pixman_region32_t damage = create_damage(*dst_box, output_damage); bool damaged = pixman_region32_not_empty(&damage); @@ -150,6 +166,170 @@ damage_finish: pixman_region32_fini(&damage); } +/* Renders the blur for each damaged rect and swaps the buffer */ +void render_blur_segments(struct fx_renderer *renderer, struct sway_output *output, + const float matrix[static 9], pixman_region32_t* damage, + struct fx_framebuffer **buffer, struct blur_shader* shader, + const struct wlr_box *box, int blur_radius) { + int width, height; + wlr_output_transformed_resolution(output->wlr_output, &width, &height); + + if (*buffer == &renderer->effects_buffer) { + fx_framebuffer_bind(&renderer->effects_buffer_swapped, width, height); + } else { + fx_framebuffer_bind(&renderer->effects_buffer, width, height); + } + + if (pixman_region32_not_empty(damage)) { + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); + for (int i = 0; i < nrects; ++i) { + const pixman_box32_t box = rects[i]; + struct wlr_box new_box = { box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1 }; + fx_renderer_scissor(&new_box); + fx_render_blur(renderer, output, matrix, buffer, shader, &new_box, blur_radius); + } + } + + if (*buffer != &renderer->effects_buffer) { + *buffer = &renderer->effects_buffer; + } else { + *buffer = &renderer->effects_buffer_swapped; + } +} + +/** Blurs the main_buffer content and returns the blurred framebuffer */ +struct fx_framebuffer *get_main_buffer_blur(struct fx_renderer *renderer, struct sway_output *output, + pixman_region32_t *original_damage, const float box_matrix[static 9], const struct wlr_box *box) { + struct wlr_box monitor_box = get_monitor_box(output->wlr_output); + + const enum wl_output_transform transform = wlr_output_transform_invert(output->wlr_output->transform); + float matrix[9]; + wlr_matrix_project_box(matrix, &monitor_box, transform, 0, output->wlr_output->transform_matrix); + + float gl_matrix[9]; + wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_copy(&damage, original_damage); + wlr_region_transform(&damage, &damage, wlr_output_transform_invert(output->wlr_output->transform), + monitor_box.width, monitor_box.height); + wlr_region_expand(&damage, &damage, get_blur_size()); + + // Initially blur main_buffer content into the effects_buffers + struct fx_framebuffer *current_buffer = &renderer->main_buffer; + + // Bind to blur framebuffer + fx_framebuffer_bind(&renderer->effects_buffer, monitor_box.width, monitor_box.height); + glBindTexture(renderer->main_buffer.texture.target, renderer->main_buffer.texture.id); + + // damage region will be scaled, make a temp + pixman_region32_t tempDamage; + pixman_region32_init(&tempDamage); + // When DOWNscaling, we make the region twice as small because it's the TARGET + wlr_region_scale(&tempDamage, &damage, 0.5f); + + int blur_radius = config->blur_params.radius; + int blur_passes = config->blur_params.num_passes; + + // First pass + render_blur_segments(renderer, output, gl_matrix, &tempDamage, ¤t_buffer, + &renderer->shaders.blur1, box, blur_radius); + + // Downscale + for (int i = 1; i < blur_passes; ++i) { + wlr_region_scale(&tempDamage, &damage, 1.0f / (1 << (i + 1))); + render_blur_segments(renderer, output, gl_matrix, &tempDamage, ¤t_buffer, + &renderer->shaders.blur1, box, blur_radius); + } + + // Upscale + for (int i = blur_passes - 1; i >= 0; --i) { + // when upsampling we make the region twice as big + wlr_region_scale(&tempDamage, &damage, 1.0f / (1 << i)); + render_blur_segments(renderer, output, gl_matrix, &tempDamage, ¤t_buffer, + &renderer->shaders.blur2, box, blur_radius); + } + + pixman_region32_fini(&tempDamage); + pixman_region32_fini(&damage); + + // Bind back to the default buffer + fx_framebuffer_bind(&renderer->main_buffer, monitor_box.width, monitor_box.height); + + return current_buffer; +} + +void render_blur(bool optimized, struct sway_output *output, + pixman_region32_t *output_damage, const struct wlr_fbox *src_box, + const struct wlr_box *dst_box, pixman_region32_t *opaque_region, + int surface_width, int surface_height, int32_t surface_scale, + int corner_radius) { + struct wlr_output *wlr_output = output->wlr_output; + struct fx_renderer *renderer = output->renderer; + + // Check if damage is inside of box rect + pixman_region32_t damage = create_damage(*dst_box, output_damage); + + pixman_region32_t inverse_opaque; + pixman_region32_init(&inverse_opaque); + + if (!pixman_region32_not_empty(&damage)) { + goto damage_finish; + } + + /* + * Capture the back_buffer and blur it + */ + + pixman_box32_t surface_box = { + .x1 = 0, + .y1 = 0, + .x2 = dst_box->width / wlr_output->scale, + .y2 = dst_box->height / wlr_output->scale, + }; + + wlr_region_scale(&inverse_opaque, &inverse_opaque, wlr_output->scale); + + // Gets the non opaque region + pixman_region32_copy(&inverse_opaque, opaque_region); + pixman_region32_inverse(&inverse_opaque, &inverse_opaque, &surface_box); + pixman_region32_intersect_rect(&inverse_opaque, &inverse_opaque, 0, 0, + surface_width * surface_scale, + surface_height * surface_scale); + if (!pixman_region32_not_empty(&inverse_opaque)) { + goto damage_finish; + } + + wlr_region_scale(&inverse_opaque, &inverse_opaque, wlr_output->scale); + + struct fx_framebuffer *buffer = &renderer->blur_buffer; + if (!buffer->texture.id || (!optimized && !config->blur_xray)) { + pixman_region32_translate(&inverse_opaque, dst_box->x, dst_box->y); + pixman_region32_intersect(&inverse_opaque, &inverse_opaque, &damage); + + // Render the blur into its own buffer + buffer = get_main_buffer_blur(renderer, output, &inverse_opaque, + wlr_output->transform_matrix, dst_box); + } + + // Draw the blurred texture + struct wlr_box monitor_box = get_monitor_box(wlr_output); + enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); + float matrix[9]; + wlr_matrix_project_box(matrix, &monitor_box, transform, 0.0, wlr_output->transform_matrix); + + struct decoration_data deco_data = get_undecorated_decoration_data(); + deco_data.corner_radius = corner_radius; + render_texture(wlr_output, &damage, &buffer->texture, src_box, dst_box, matrix, deco_data); + +damage_finish: + pixman_region32_fini(&damage); + pixman_region32_fini(&inverse_opaque); +} + + static void render_surface_iterator(struct sway_output *output, struct sway_view *view, struct wlr_surface *surface, struct wlr_box *_box, void *_data) { @@ -162,17 +342,12 @@ static void render_surface_iterator(struct sway_output *output, return; } - struct wlr_fbox src_box; - wlr_surface_get_buffer_source_box(surface, &src_box); - struct wlr_box proj_box = *_box; scale_box(&proj_box, wlr_output->scale); float matrix[9]; - enum wl_output_transform transform = - wlr_output_transform_invert(surface->current.transform); - wlr_matrix_project_box(matrix, &proj_box, transform, 0.0, - wlr_output->transform_matrix); + enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, &proj_box, transform, 0.0, wlr_output->transform_matrix); struct wlr_box dst_box = *_box; struct wlr_box *clip_box = data->clip_box; @@ -185,9 +360,40 @@ static void render_surface_iterator(struct sway_output *output, scale_box(&dst_box, wlr_output->scale); - data->deco_data.corner_radius *= wlr_output->scale; - render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, - matrix, data->deco_data); + struct decoration_data deco_data = data->deco_data; + deco_data.corner_radius *= wlr_output->scale; + + // render blur (view->surface == surface excludes blurring subsurfaces) + if (deco_data.blur && should_parameters_blur() && view->surface == surface) { + pixman_region32_t opaque_region; + pixman_region32_init(&opaque_region); + + bool has_alpha = false; + if (deco_data.alpha < 1.0) { + has_alpha = true; + pixman_region32_union_rect(&opaque_region, &opaque_region, 0, 0, 0, 0); + } else { + has_alpha = !surface->opaque; + pixman_region32_copy(&opaque_region, &surface->opaque_region); + } + + if (has_alpha) { + int width, height; + wlr_output_transformed_resolution(wlr_output, &width, &height); + struct wlr_fbox blur_src_box = { 0, 0, width, height }; + bool is_floating = container_is_floating(view->container); + render_blur(!is_floating, output, output_damage, &blur_src_box, &dst_box, &opaque_region, + surface->current.width, surface->current.height, surface->current.scale, deco_data.corner_radius); + } + + pixman_region32_fini(&opaque_region); + } + + struct wlr_fbox src_box; + wlr_surface_get_buffer_source_box(surface, &src_box); + struct fx_texture fx_texture = fx_texture_from_wlr_texture(texture); + render_texture(wlr_output, output_damage, &fx_texture, &src_box, &dst_box, + matrix, deco_data); wlr_presentation_surface_sampled_on_output(server.presentation, surface, wlr_output); @@ -235,13 +441,73 @@ static void render_drag_icons(struct sway_output *output, render_surface_iterator, &data); } +void render_whole_output(struct fx_renderer *renderer, pixman_region32_t *original_damage, + struct fx_texture *texture) { + struct wlr_output *output = renderer->sway_output->wlr_output; + struct wlr_box monitor_box = get_monitor_box(output); + + enum wl_output_transform transform = wlr_output_transform_invert(output->transform); + float matrix[9]; + wlr_matrix_project_box(matrix, &monitor_box, transform, 0.0, output->transform_matrix); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, monitor_box.x, monitor_box.y, + monitor_box.width, monitor_box.height); + pixman_region32_intersect(&damage, &damage, original_damage); + bool damaged = pixman_region32_not_empty(&damage); + if (!damaged) { + goto damage_finish; + } + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(output, &rects[i]); + fx_render_texture_with_matrix(renderer, texture, &monitor_box, matrix, get_undecorated_decoration_data()); + } + +damage_finish: + pixman_region32_fini(&damage); +} + +void render_monitor_blur(struct sway_output *output, pixman_region32_t *damage) { + struct wlr_output *wlr_output = output->wlr_output; + struct fx_renderer *renderer = output->renderer; + + struct wlr_box monitor_box = get_monitor_box(wlr_output); + pixman_region32_t fake_damage; + pixman_region32_init_rect(&fake_damage, 0, 0, monitor_box.width, monitor_box.height); + + // Render the blur + struct fx_framebuffer *buffer = get_main_buffer_blur(renderer, output, &fake_damage, + wlr_output->transform_matrix, &monitor_box); + + // Render the newly blurred content into the blur_buffer + fx_framebuffer_create(wlr_output, &renderer->blur_buffer, true); + // Clear the damaged region of the blur_buffer + float clear_color[] = { 0, 0, 0, 0 }; + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(wlr_output, &rects[i]); + fx_renderer_clear(clear_color); + } + render_whole_output(renderer, &fake_damage, &buffer->texture); + fx_framebuffer_bind(&renderer->main_buffer, monitor_box.width, monitor_box.height); + + pixman_region32_fini(&fake_damage); + + renderer->blur_buffer_dirty = false; +} + // _box.x and .y are expected to be layout-local // _box.width and .height are expected to be output-buffer-local void render_rect(struct sway_output *output, pixman_region32_t *output_damage, const struct wlr_box *_box, float color[static 4]) { struct wlr_output *wlr_output = output->wlr_output; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; struct wlr_box box; memcpy(&box, _box, sizeof(struct wlr_box)); @@ -269,7 +535,7 @@ void render_rounded_rect(struct sway_output *output, pixman_region32_t *output_d const struct wlr_box *_box, float color[static 4], int corner_radius, enum corner_location corner_location) { struct wlr_output *wlr_output = output->wlr_output; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; struct wlr_box box; memcpy(&box, _box, sizeof(struct wlr_box)); @@ -300,7 +566,7 @@ void render_border_corner(struct sway_output *output, pixman_region32_t *output_ const struct wlr_box *_box, const float color[static 4], int corner_radius, int border_thickness, enum corner_location corner_location) { struct wlr_output *wlr_output = output->wlr_output; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; struct wlr_box box; memcpy(&box, _box, sizeof(struct wlr_box)); @@ -331,7 +597,7 @@ void render_box_shadow(struct sway_output *output, pixman_region32_t *output_dam const struct wlr_box *_box, const float color[static 4], float blur_sigma, float corner_radius) { struct wlr_output *wlr_output = output->wlr_output; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; struct wlr_box box; memcpy(&box, _box, sizeof(struct wlr_box)); @@ -448,8 +714,7 @@ static void render_saved_view(struct sway_view *view, struct sway_output *output float matrix[9]; enum wl_output_transform transform = wlr_output_transform_invert(saved_buf->transform); - wlr_matrix_project_box(matrix, &proj_box, transform, 0, - wlr_output->transform_matrix); + wlr_matrix_project_box(matrix, &proj_box, transform, 0, wlr_output->transform_matrix); struct sway_container_state state = view->container->current; dst_box.x = state.x - output->lx; @@ -465,7 +730,30 @@ static void render_saved_view(struct sway_view *view, struct sway_output *output scale_box(&dst_box, wlr_output->scale); deco_data.corner_radius *= wlr_output->scale; - render_texture(wlr_output, damage, saved_buf->buffer->texture, + + // render blur + if (deco_data.blur && should_parameters_blur()) { + struct wlr_gles2_texture_attribs attribs; + wlr_gles2_texture_get_attribs(saved_buf->buffer->texture, &attribs); + + if (deco_data.alpha < 1.0 || attribs.has_alpha) { + pixman_region32_t opaque_region; + pixman_region32_init(&opaque_region); + pixman_region32_union_rect(&opaque_region, &opaque_region, 0, 0, 0, 0); + + struct wlr_box monitor_box = get_monitor_box(wlr_output); + // TODO: contribute wlroots function to allow creating an fbox from a box? + struct wlr_fbox src_box = { monitor_box.x, monitor_box.y, monitor_box.width, monitor_box.height }; + bool is_floating = container_is_floating(view->container); + render_blur(!is_floating, output, damage, &src_box, &dst_box, &opaque_region, + saved_buf->width, saved_buf->height, 1, deco_data.corner_radius); + + pixman_region32_fini(&opaque_region); + } + } + + struct fx_texture fx_texture = fx_texture_from_wlr_texture(saved_buf->buffer->texture); + render_texture(wlr_output, damage, &fx_texture, &saved_buf->source_box, &dst_box, matrix, deco_data); } @@ -481,6 +769,9 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage, struct sway_container *con, struct border_colors *colors, struct decoration_data deco_data) { struct sway_view *view = con->view; + struct sway_container_state *state = &con->current; + + // render view if (!wl_list_empty(&view->saved_buffers)) { render_saved_view(view, output, damage, deco_data); } else if (view->surface) { @@ -768,7 +1059,8 @@ static void render_titlebar(struct sway_output *output, if (ob_inner_width < texture_box.width) { texture_box.width = ob_inner_width; } - render_texture(output->wlr_output, output_damage, marks_texture, + struct fx_texture fx_texture = fx_texture_from_wlr_texture(marks_texture); + render_texture(output->wlr_output, output_damage, &fx_texture, NULL, &texture_box, matrix, deco_data); // Padding above @@ -844,7 +1136,8 @@ static void render_titlebar(struct sway_output *output, texture_box.width = ob_inner_width - ob_marks_width; } - render_texture(output->wlr_output, output_damage, title_texture, + struct fx_texture fx_texture = fx_texture_from_wlr_texture(title_texture); + render_texture(output->wlr_output, output_damage, &fx_texture, NULL, &texture_box, matrix, deco_data); // Padding above @@ -1052,6 +1345,7 @@ static void render_containers_linear(struct sway_output *output, .corner_radius = corner_radius, .saturation = child->saturation, .has_titlebar = has_titlebar, + .blur = child->blur_enabled, }; render_view(output, damage, child, colors, deco_data); if (has_titlebar) { @@ -1159,6 +1453,7 @@ static void render_containers_tabbed(struct sway_output *output, .corner_radius = current->corner_radius, .saturation = current->saturation, .has_titlebar = true, + .blur = current->blur_enabled, }; render_view(output, damage, current, current_colors, deco_data); } else { @@ -1233,6 +1528,7 @@ static void render_containers_stacked(struct sway_output *output, .saturation = current->saturation, .corner_radius = current->corner_radius, .has_titlebar = true, + .blur = current->blur_enabled, }; render_view(output, damage, current, current_colors, deco_data); } else { @@ -1333,6 +1629,7 @@ static void render_floating_container(struct sway_output *soutput, .saturation = con->saturation, .corner_radius = con->corner_radius, .has_titlebar = has_titlebar, + .blur = con->blur_enabled, }; render_view(soutput, damage, con, colors, deco_data); if (has_titlebar) { @@ -1375,10 +1672,82 @@ static void render_seatops(struct sway_output *output, } } +struct find_effect_iter_data { + struct workspace_effect_info *effect_info; + bool blur_buffer_dirty; +}; + +static bool find_con_effect_iterator(struct sway_container *con, void* _data) { + struct sway_view *view = con->view; + struct find_effect_iter_data *data = _data; + struct workspace_effect_info *effect_info = data->effect_info; + + if (!view) { + return false; + } + + if (con->blur_enabled && !view->surface->opaque) { + effect_info->container_wants_blur = true; + + bool is_floating = container_is_floating(con); + // Check if we should render optimized blur + if (data->blur_buffer_dirty + // Only test floating windows when xray is enabled + && (!is_floating || (is_floating && config->blur_xray))) { + effect_info->should_render_optimized_blur = true; + } + } + if (con->shadow_enabled) { + effect_info->container_wants_shadow = true; + } + + // Stop the iteration if all of the effects have been found. + // Ensures that no effect is skipped if returning early + return effect_info->container_wants_blur + && effect_info->container_wants_shadow + && effect_info->should_render_optimized_blur; +} + +static struct workspace_effect_info get_workspace_effect_info(struct sway_output *sway_output) { + struct fx_renderer *renderer = sway_output->renderer; + struct sway_workspace *workspace = sway_output->current.active_workspace; + + struct workspace_effect_info effect_info = { + .container_wants_blur = false, + .container_wants_shadow = false, + .should_render_optimized_blur = false, + .expanded_size = 0 + }; + + if (!workspace_is_visible(workspace)) { + return effect_info; + } + + // Iterate through the workspace containers and check if any effects are requested + struct find_effect_iter_data iter_data = { + .effect_info = &effect_info, + .blur_buffer_dirty = renderer->blur_buffer_dirty + }; + workspace_find_container(workspace, find_con_effect_iterator, &iter_data); + + // Set the expanded damage region + bool shadow_enabled = effect_info.container_wants_shadow || config->shadow_enabled; + int shadow_sigma = shadow_enabled ? config->shadow_blur_sigma : 0; + bool blur_enabled = effect_info.container_wants_blur || config->blur_enabled; + int blur_size = blur_enabled ? get_blur_size() : 0; + // +1 as a margin of error + effect_info.expanded_size = MAX(shadow_sigma, blur_size) + 1; + + return effect_info; +} + void output_render(struct sway_output *output, struct timespec *when, pixman_region32_t *damage) { struct wlr_output *wlr_output = output->wlr_output; - struct fx_renderer *renderer = output->server->renderer; + struct fx_renderer *renderer = output->renderer; + + int width, height; + wlr_output_transformed_resolution(wlr_output, &width, &height); struct sway_workspace *workspace = output->current.active_workspace; if (workspace == NULL) { @@ -1390,23 +1759,56 @@ void output_render(struct sway_output *output, struct timespec *when, fullscreen_con = workspace->current.fullscreen; } - fx_renderer_begin(renderer, wlr_output->width, wlr_output->height); - if (debug.damage == DAMAGE_RERENDER) { - int width, height; - wlr_output_transformed_resolution(wlr_output, &width, &height); pixman_region32_union_rect(damage, damage, 0, 0, width, height); } - if (!pixman_region32_not_empty(damage)) { - // Output isn't damaged but needs buffer swap - goto renderer_end; + bool has_blur = false; + bool blur_optimize_should_render = false; + bool damage_not_empty = pixman_region32_not_empty(damage); + pixman_region32_t extended_damage; + pixman_region32_init(&extended_damage); + if (!fullscreen_con && !server.session_lock.locked && damage_not_empty) { + // Check if there are any windows to blur + struct workspace_effect_info effect_info = get_workspace_effect_info(output); + has_blur = effect_info.container_wants_blur || config->blur_enabled; + if (effect_info.should_render_optimized_blur) { + blur_optimize_should_render = true; + // Damage the whole output + pixman_region32_union_rect(damage, damage, 0, 0, width, height); + } + + // Extend the damaged region + int expanded_size = effect_info.expanded_size; + if (expanded_size > 0) { + int32_t damage_width = damage->extents.x2 - damage->extents.x1; + int32_t damage_height = damage->extents.y2 - damage->extents.y1; + // Limit the damage extent to the size of the monitor to prevent overflow + if (damage_width > width || damage_height > height) { + pixman_region32_intersect_rect(damage, damage, 0, 0, width, height); + } + + wlr_region_expand(damage, damage, expanded_size); + pixman_region32_copy(&extended_damage, damage); + wlr_region_expand(damage, damage, expanded_size); + } else { + pixman_region32_copy(&extended_damage, damage); + } + } else { + pixman_region32_copy(&extended_damage, damage); } - if (debug.damage == DAMAGE_HIGHLIGHT) { + if (debug.damage == DAMAGE_HIGHLIGHT && damage_not_empty) { fx_renderer_clear((float[]){1, 1, 0, 1}); } + fx_renderer_begin(renderer, output); + + if (!damage_not_empty) { + // Output isn't damaged but needs buffer swap + goto renderer_end; + } + if (server.session_lock.locked) { float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; if (server.session_lock.lock == NULL) { @@ -1494,6 +1896,11 @@ void output_render(struct sway_output *output, struct timespec *when, render_layer_toplevel(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); + bool blur_enabled = has_blur && should_parameters_blur(); + if (blur_enabled && blur_optimize_should_render && renderer->blur_buffer_dirty) { + render_monitor_blur(output, damage); + } + render_workspace(output, damage, workspace, workspace->current.focused); render_floating(output, damage); #if HAVE_XWAYLAND @@ -1524,6 +1931,7 @@ void output_render(struct sway_output *output, struct timespec *when, .corner_radius = 0, .saturation = focus->saturation, .has_titlebar = false, + .blur = false, }; render_view_popups(focus->view, output, damage, deco_data); } @@ -1536,29 +1944,45 @@ render_overlay: render_drag_icons(output, damage, &root->drag_icons); renderer_end: + // Draw the contents of our buffer into the wlr buffer + fx_framebuffer_bind(&renderer->wlr_buffer, width, height); + float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + if (pixman_region32_not_empty(&extended_damage)) { + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&extended_damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(wlr_output, &rects[i]); + fx_renderer_clear(clear_color); + } + } + render_whole_output(renderer, &extended_damage, &renderer->main_buffer.texture); fx_renderer_scissor(NULL); + fx_renderer_end(renderer); + + // Draw the software cursors wlr_renderer_begin(output->server->wlr_renderer, wlr_output->width, wlr_output->height); wlr_output_render_software_cursors(wlr_output, damage); wlr_renderer_end(output->server->wlr_renderer); - fx_renderer_end(); - - int width, height; - wlr_output_transformed_resolution(wlr_output, &width, &height); + fx_renderer_scissor(NULL); pixman_region32_t frame_damage; pixman_region32_init(&frame_damage); - enum wl_output_transform transform = - wlr_output_transform_invert(wlr_output->transform); - wlr_region_transform(&frame_damage, &output->damage_ring.current, - transform, width, height); + enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); + /* + * Extend the frame damage by the blur size to properly calc damage for the + * next buffer swap. Thanks Emersion for your excellent damage tracking blog-post! + */ + wlr_region_transform(&frame_damage, &extended_damage, transform, width, height); - if (debug.damage != DAMAGE_DEFAULT) { + if (debug.damage != DAMAGE_DEFAULT || blur_optimize_should_render) { pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, wlr_output->width, wlr_output->height); } wlr_output_set_damage(wlr_output, &frame_damage); + + pixman_region32_fini(&extended_damage); pixman_region32_fini(&frame_damage); if (!wlr_output_commit(wlr_output)) { diff --git a/sway/meson.build b/sway/meson.build index 7eef011b..54e41072 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -14,7 +14,9 @@ sway_sources = files( 'xdg_decoration.c', 'desktop/desktop.c', + 'desktop/fx_renderer/fx_framebuffer.c', 'desktop/fx_renderer/fx_renderer.c', + 'desktop/fx_renderer/fx_texture.c', 'desktop/fx_renderer/matrix.c', 'desktop/idle_inhibit_v1.c', 'desktop/layer_shell.c', @@ -48,6 +50,10 @@ sway_sources = files( 'commands/assign.c', 'commands/bar.c', 'commands/bind.c', + 'commands/blur.c', + 'commands/blur_passes.c', + 'commands/blur_radius.c', + 'commands/blur_xray.c', 'commands/border.c', 'commands/client.c', 'commands/corner_radius.c', diff --git a/sway/server.c b/sway/server.c index c4d005b6..262b7788 100644 --- a/sway/server.c +++ b/sway/server.c @@ -86,12 +86,6 @@ bool server_init(struct sway_server *server) { sway_log(SWAY_ERROR, "Failed to create wlr_renderer"); return false; } - struct wlr_egl *egl = wlr_gles2_renderer_get_egl(server->wlr_renderer); - server->renderer = fx_renderer_create(egl); - if (!server->renderer) { - sway_log(SWAY_ERROR, "Failed to create fx_renderer"); - return false; - } wlr_renderer_init_wl_shm(server->wlr_renderer, server->wl_display); diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 31edb038..2288346b 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -686,6 +686,21 @@ The default colors are: *shadow_color* The shadow color. Default color: #0000007F +*blur* enable|disable + Sets whether blur should be drawn. Can also be set per window with + *for_window*. + +*blur_xray* enable|disable + Sets whether blur should only display the background. + +*blur_passes* + Adjusts the blur passes of windows between 0 (disabled) and 10 + while 2 is the default value. + +*blur_radius* + Adjusts the blur radius of windows between 0 (disabled) and 10 + while 5 is the default value. + *default_border* normal|none|pixel [] Set default border style for new tiled windows. diff --git a/sway/tree/container.c b/sway/tree/container.c index f256bafc..2e61ff5c 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -43,6 +43,7 @@ struct sway_container *container_create(struct sway_view *view) { c->saturation = 1.0f; c->dim = config->default_dim_inactive; c->shadow_enabled = config->shadow_enabled; + c->blur_enabled = config->blur_enabled; c->corner_radius = config->corner_radius; if (!view) {