From f86ae3eced5255f8757ef70bed22c3703439e74f Mon Sep 17 00:00:00 2001 From: Erik Reider <35975961+ErikReider@users.noreply.github.com> Date: Tue, 27 Feb 2024 20:05:00 +0100 Subject: [PATCH] Add wlr_scene blur impl --- include/scenefx/types/fx/blur_data.h | 2 + include/scenefx/types/fx/shadow_data.h | 4 +- include/scenefx/types/wlr_scene.h | 42 +++- render/fx_renderer/fx_pass.c | 1 + tinywl/tinywl.c | 78 +++----- types/fx/blur_data.c | 4 + types/scene/wlr_scene.c | 257 +++++++++++++++++++++++-- 7 files changed, 323 insertions(+), 65 deletions(-) diff --git a/include/scenefx/types/fx/blur_data.h b/include/scenefx/types/fx/blur_data.h index e6a7c29..5afda75 100644 --- a/include/scenefx/types/fx/blur_data.h +++ b/include/scenefx/types/fx/blur_data.h @@ -15,6 +15,8 @@ struct blur_data { struct blur_data blur_data_get_default(void); +bool scene_buffer_should_blur(bool backdrop_blur, struct blur_data *blur_data); + bool blur_data_should_parameters_blur_effects(struct blur_data *blur_data); bool blur_data_cmp(struct blur_data *a, struct blur_data *b); diff --git a/include/scenefx/types/fx/shadow_data.h b/include/scenefx/types/fx/shadow_data.h index d96a084..3d985c1 100644 --- a/include/scenefx/types/fx/shadow_data.h +++ b/include/scenefx/types/fx/shadow_data.h @@ -1,5 +1,5 @@ -#ifndef TYPES_DECORATION_DATA -#define TYPES_DECORATION_DATA +#ifndef TYPES_SHADOW_DATA_H +#define TYPES_SHADOW_DATA_H #include #include diff --git a/include/scenefx/types/wlr_scene.h b/include/scenefx/types/wlr_scene.h index 5925931..3a16985 100644 --- a/include/scenefx/types/wlr_scene.h +++ b/include/scenefx/types/wlr_scene.h @@ -20,7 +20,6 @@ */ #include -#include "scenefx/types/fx/shadow_data.h" #include #include #include @@ -29,6 +28,9 @@ #include #include +#include "scenefx/types/fx/shadow_data.h" +#include "scenefx/types/fx/blur_data.h" + struct wlr_output; struct wlr_output_layout; struct wlr_output_layout_output; @@ -111,6 +113,8 @@ struct wlr_scene { enum wlr_scene_debug_damage_option debug_damage_option; bool direct_scanout; bool calculate_visibility; + + struct blur_data blur_data; }; /** A scene-graph node displaying a single surface. */ @@ -176,10 +180,13 @@ struct wlr_scene_buffer { */ struct wlr_scene_output *primary_output; - float opacity; int corner_radius; struct shadow_data shadow_data; + bool backdrop_blur; + bool backdrop_blur_optimized; + bool backdrop_blur_ignore_transparent; + float opacity; enum wlr_scale_filter_mode filter_mode; struct wlr_fbox src_box; int dst_width, dst_height; @@ -314,6 +321,9 @@ struct wlr_scene *wlr_scene_create(void); void wlr_scene_set_presentation(struct wlr_scene *scene, struct wlr_presentation *presentation); +/** Sets the global blur parameters */ +void wlr_scene_set_blur_data(struct wlr_scene *scene, struct blur_data blur_data); + /** * Handles linux_dmabuf_v1 feedback for all surfaces in the scene. * @@ -462,6 +472,34 @@ void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_shadow_data(struct wlr_scene_buffer *scene_buffer, struct shadow_data shadow_data); +/** +* Sets the whether or not the buffer should render backdrop blur +*/ +void wlr_scene_buffer_set_backdrop_blur(struct wlr_scene_buffer *scene_buffer, + bool enabled); + +/** +* Sets the whether the backdrop blur should use optimized blur or not +*/ +void wlr_scene_buffer_set_backdrop_blur_optimized(struct wlr_scene_buffer *scene_buffer, + bool enabled); + +/** +* Sets the whether the backdrop blur should not render in fully transparent +* segments. +*/ +void wlr_scene_buffer_set_backdrop_blur_ignore_transparent( + struct wlr_scene_buffer *scene_buffer, bool enabled); + +/** + * Tells the renderer to re-render the optimized blur. Very expensive so should + * only be called when needed. + * + * An example use would be to call this when a "static" node changes, like a + * wallpaper. + */ +void wlr_scene_optimized_blur_mark_dirty(struct wlr_scene *scene); + /** * Calls the buffer's frame_done signal. */ diff --git a/render/fx_renderer/fx_pass.c b/render/fx_renderer/fx_pass.c index f12eda5..2e585fd 100644 --- a/render/fx_renderer/fx_pass.c +++ b/render/fx_renderer/fx_pass.c @@ -676,6 +676,7 @@ void fx_render_pass_add_blur(struct fx_gles_render_pass *pass, struct fx_framebuffer *buffer = renderer->optimized_blur_buffer; if (!buffer || !fx_options->use_optimized_blur) { + return; pixman_region32_translate(&translucent_region, dst_box.x, dst_box.y); pixman_region32_intersect(&translucent_region, &translucent_region, options->clip); diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 994574f..32488f6 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -557,50 +557,6 @@ static void server_cursor_frame(struct wl_listener *listener, void *data) { wlr_seat_pointer_notify_frame(server->seat); } -static void output_configure_scene(struct wlr_scene_node *node, - struct tinywl_toplevel *toplevel) { - if (!node->enabled) { - return; - } - - struct tinywl_toplevel *_toplevel = node->data; - if (_toplevel) { - toplevel = _toplevel; - } - - if (node->type == WLR_SCENE_NODE_BUFFER) { - struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - - struct wlr_scene_surface * scene_surface = - wlr_scene_surface_try_from_buffer(buffer); - if (!scene_surface) { - return; - } - - struct wlr_xdg_surface *xdg_surface = - wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); - - if (toplevel && - xdg_surface && - xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - // TODO: Be able to set whole decoration_data instead of calling - // each individually? - wlr_scene_buffer_set_opacity(buffer, toplevel->opacity); - - if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { - wlr_scene_buffer_set_corner_radius(buffer, toplevel->corner_radius); - wlr_scene_buffer_set_shadow_data(buffer, toplevel->shadow_data); - } - } - } else if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *tree = wl_container_of(node, tree, node); - struct wlr_scene_node *node; - wl_list_for_each(node, &tree->children, link) { - output_configure_scene(node, toplevel); - } - } -} - static void output_frame(struct wl_listener *listener, void *data) { /* This function is called every time an output is ready to display a frame, * generally at the output's refresh rate (e.g. 60Hz). */ @@ -610,8 +566,6 @@ static void output_frame(struct wl_listener *listener, void *data) { struct wlr_scene_output *scene_output = wlr_scene_get_scene_output( scene, output->wlr_output); - output_configure_scene(&scene_output->scene->tree.node, NULL); - /* Render the scene if needed and commit the output */ wlr_scene_output_commit(scene_output, NULL); @@ -703,10 +657,42 @@ static void server_new_output(struct wl_listener *listener, void *data) { wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); } +static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, + int sy, void *user_data) { + struct tinywl_toplevel *toplevel = user_data; + + struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) { + return; + } + + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); + if (toplevel && + xdg_surface && + xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + // TODO: Be able to set whole decoration_data instead of calling + // each individually? + wlr_scene_buffer_set_opacity(buffer, toplevel->opacity); + + if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { + wlr_scene_buffer_set_corner_radius(buffer, toplevel->corner_radius); + wlr_scene_buffer_set_shadow_data(buffer, toplevel->shadow_data); + + wlr_scene_buffer_set_backdrop_blur(buffer, true); + wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); + wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, true); + } + } +} + static void xdg_toplevel_map(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, map); + wlr_scene_node_for_each_buffer(&toplevel->scene_tree->node, + iter_xdg_scene_buffers, toplevel); + wl_list_insert(&toplevel->server->toplevels, &toplevel->link); focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface); diff --git a/types/fx/blur_data.c b/types/fx/blur_data.c index 3aa618b..0458cea 100644 --- a/types/fx/blur_data.c +++ b/types/fx/blur_data.c @@ -11,6 +11,10 @@ struct blur_data blur_data_get_default(void) { }; } +bool scene_buffer_should_blur(bool backdrop_blur, struct blur_data *blur_data) { + return backdrop_blur && blur_data->radius > 0 && blur_data->num_passes > 0; +} + bool blur_data_should_parameters_blur_effects(struct blur_data *blur_data) { return blur_data->brightness != 1.0f || blur_data->saturation != 1.0f diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 0545e70..5755d1e 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -16,8 +16,10 @@ #include #include +#include "render/fx_renderer/fx_renderer.h" #include "render/pass.h" #include "scenefx/types/fx/shadow_data.h" +#include "scenefx/types/fx/blur_data.h" #include "types/wlr_buffer.h" #include "types/wlr_output.h" #include "types/wlr_scene.h" @@ -176,6 +178,8 @@ struct wlr_scene *wlr_scene_create(void) { scene->direct_scanout = !env_parse_bool("WLR_SCENE_DISABLE_DIRECT_SCANOUT"); scene->calculate_visibility = !env_parse_bool("WLR_SCENE_DISABLE_VISIBILITY"); + scene->blur_data = blur_data_get_default(); + return scene; } @@ -644,8 +648,12 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, wl_signal_init(&scene_buffer->events.frame_done); pixman_region32_init(&scene_buffer->opaque_region); scene_buffer->opacity = 1; + scene_buffer->corner_radius = 0; scene_buffer->shadow_data = shadow_data_get_default(); + scene_buffer->backdrop_blur = false; + scene_buffer->backdrop_blur_optimized = false; + scene_buffer->backdrop_blur_ignore_transparent = true; scene_node_update(&scene_buffer->node, NULL); @@ -893,6 +901,45 @@ void wlr_scene_buffer_set_shadow_data(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_backdrop_blur(struct wlr_scene_buffer *scene_buffer, + bool enabled) { + if (scene_buffer->backdrop_blur != enabled) { + scene_buffer->backdrop_blur = enabled; + wlr_scene_optimized_blur_mark_dirty(scene_node_get_root(&scene_buffer->node)); + } +} + +void wlr_scene_buffer_set_backdrop_blur_optimized(struct wlr_scene_buffer *scene_buffer, + bool enabled) { + if (scene_buffer->backdrop_blur_optimized != enabled) { + scene_buffer->backdrop_blur_optimized = enabled; + wlr_scene_optimized_blur_mark_dirty(scene_node_get_root(&scene_buffer->node)); + } +} + +void wlr_scene_buffer_set_backdrop_blur_ignore_transparent( + struct wlr_scene_buffer *scene_buffer, bool enabled) { + if (scene_buffer->backdrop_blur_ignore_transparent != enabled) { + scene_buffer->backdrop_blur_ignore_transparent = enabled; + wlr_scene_optimized_blur_mark_dirty(scene_node_get_root(&scene_buffer->node)); + } +} + +static void output_optimized_blur_mark_dirty(struct wlr_output *output) { + struct fx_renderer *renderer = fx_get_renderer(output->renderer); + renderer->blur_buffer_dirty = true; +} + +void wlr_scene_optimized_blur_mark_dirty(struct wlr_scene *scene) { + // Mark the blur buffers as dirty + struct wlr_scene_output *current_output; + wl_list_for_each(current_output, &scene->outputs, link) { + struct wlr_output *output = current_output->output; + output_optimized_blur_mark_dirty(output); + } + scene_node_update(&scene->tree.node, NULL); +} + static struct wlr_texture *scene_buffer_get_texture( struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { struct wlr_client_buffer *client_buffer = @@ -1174,7 +1221,8 @@ static void clip_xdg(struct wlr_scene_node *node, // Don't clip if the scene buffer doesn't have any effects if (!scene_buffer || (scene_buffer->corner_radius == 0 && - !scene_buffer_has_shadow(&scene_buffer->shadow_data))) { + !scene_buffer_has_shadow(&scene_buffer->shadow_data) && + !scene_buffer->backdrop_blur)) { return; } @@ -1282,20 +1330,6 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_output_transform_invert(scene_buffer->transform); transform = wlr_output_transform_compose(transform, data->transform); - // Shadow - if (scene_buffer_has_shadow(&scene_buffer->shadow_data)) { - struct fx_render_rect_options shadow_options = { - .base = { - .box = xdg_box, - .clip = &render_region, // Render with the original extended clip region - }, - .scale = data->scale, - }; - fx_render_pass_add_box_shadow(data->render_pass, &shadow_options, - scene_buffer->corner_radius * data->scale, - &scene_buffer->shadow_data); - } - struct fx_render_texture_options tex_options = { .base = (struct wlr_render_texture_options){ .texture = texture, @@ -1311,8 +1345,70 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .scale = data->scale, .clip_box = &xdg_box, .corner_radius = scene_buffer->corner_radius * data->scale, + .discard_transparent = false, }; + // Blur + struct wlr_scene *scene = data->output->scene; + if (scene_buffer_should_blur(scene_buffer->backdrop_blur, &scene->blur_data)) { + pixman_region32_t opaque_region; + pixman_region32_init(&opaque_region); + + bool has_alpha = false; + if (scene_buffer->opacity < 1.0) { + has_alpha = true; + pixman_region32_union_rect(&opaque_region, &opaque_region, 0, 0, 0, 0); + } else { + struct wlr_scene_surface *scene_surface + = wlr_scene_surface_try_from_buffer(scene_buffer); + has_alpha = !scene_surface->surface->opaque; + pixman_region32_copy(&opaque_region, &scene_surface->surface->opaque_region); + } + + if (has_alpha) { + struct wlr_output *output = data->output->output; + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + struct wlr_box monitor_box = { 0, 0, width, height }; + wlr_box_transform(&monitor_box, &monitor_box, + wlr_output_transform_invert(output->transform), + monitor_box.width, monitor_box.height); + struct fx_render_blur_pass_options blur_options = { + .tex_options = tex_options, + .opaque_region = &scene_buffer->opaque_region, + .use_optimized_blur = scene_buffer->backdrop_blur_optimized, + .output = output, + .monitor_box = monitor_box, + .blur_data = &scene->blur_data, + .ignore_transparent = scene_buffer->backdrop_blur_ignore_transparent, + }; + // Re-render the optimized blur buffer when needed + if (data->render_pass->buffer->renderer->blur_buffer_dirty + && scene_buffer->backdrop_blur_optimized) { + printf("RERENDER!\n"); + fx_render_pass_add_optimized_blur(data->render_pass, &blur_options); + } + // Render the actual blur behind the surface + fx_render_pass_add_blur(data->render_pass, &blur_options); + + } + pixman_region32_fini(&opaque_region); + } + + // Shadow + if (scene_buffer_has_shadow(&scene_buffer->shadow_data)) { + struct fx_render_rect_options shadow_options = { + .base = { + .box = xdg_box, + .clip = &render_region, // Render with the original extended clip region + }, + .scale = data->scale, + }; + fx_render_pass_add_box_shadow(data->render_pass, &shadow_options, + scene_buffer->corner_radius * data->scale, + &scene_buffer->shadow_data); + } + fx_render_pass_add_texture(data->render_pass, &tex_options); struct wlr_scene_output_sample_event sample_event = { @@ -1345,6 +1441,22 @@ void wlr_scene_set_presentation(struct wlr_scene *scene, wl_signal_add(&presentation->events.destroy, &scene->presentation_destroy); } +void wlr_scene_set_blur_data(struct wlr_scene *scene, struct blur_data blur_data) { + struct blur_data *buff_data = &scene->blur_data; + if (blur_data_cmp(buff_data, &blur_data)) { + return; + } + + memcpy(&scene->blur_data, &blur_data, + sizeof(struct blur_data)); + + wlr_scene_optimized_blur_mark_dirty(scene); +} + +static bool wlr_scene_should_blur(struct wlr_scene *scene) { + return scene->blur_data.radius > 0 && scene->blur_data.num_passes > 0; +} + static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener, void *data) { struct wlr_scene *scene = @@ -1393,6 +1505,9 @@ static void scene_output_update_geometry(struct wlr_scene_output *scene_output, wlr_damage_ring_add_whole(&scene_output->damage_ring); wlr_output_schedule_frame(scene_output->output); + // Re-render the optimized blur when the output is changed + output_optimized_blur_mark_dirty(scene_output->output); + scene_node_output_update(&scene_output->scene->tree.node, &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL); } @@ -1734,6 +1849,54 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, return true; } +struct blur_info { + bool has_blur; + bool has_optimized; +}; + +static struct blur_info workspace_get_blur_info(int list_len, + struct render_list_entry *list_data, struct wlr_scene_output *scene_output, + pixman_region32_t *blur_region) { + if (!wlr_scene_should_blur(scene_output->scene)) { + return (struct blur_info) { + .has_blur = false, + .has_optimized = false, + }; + } + + bool has_optimized = false; + for (int i = list_len - 1; i >= 0; i--) { + struct wlr_scene_node *node = list_data[i].node; + switch (node->type) { + case WLR_SCENE_NODE_BUFFER:; + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + assert(scene_buffer->buffer); + if (scene_buffer->backdrop_blur && scene_surface && + !scene_surface->surface->opaque) { + int x, y; + wlr_scene_node_coords(node, &x, &y); + pixman_region32_union_rect(blur_region, blur_region, + (x - scene_output->x) * scene_output->output->scale, + (y - scene_output->y) * scene_output->output->scale, + scene_buffer->dst_width * scene_output->output->scale, + scene_buffer->dst_height * scene_output->output->scale); + if (scene_buffer->backdrop_blur_optimized) { + has_optimized = true; + } + } + break; + default: + break; + } + } + return (struct blur_info) { + .has_blur = pixman_region32_not_empty(blur_region), + .has_optimized = has_optimized, + }; +} + bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, const struct wlr_scene_output_state_options *options) { if (!scene_output->output->needs_frame && !pixman_region32_not_empty( @@ -1919,6 +2082,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } + struct fx_renderer *renderer = fx_get_renderer(output->renderer); struct fx_gles_render_pass *render_pass = fx_renderer_begin_buffer_pass(output->renderer, buffer, output, &(struct wlr_buffer_pass_options) { @@ -1935,6 +2099,61 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_damage_ring_get_buffer_damage(&scene_output->damage_ring, buffer_age, &render_data.damage); + // Blur artifact prevention + pixman_region32_t *damage = &render_data.damage; + pixman_region32_t blur_region; + pixman_region32_init(&blur_region); + struct blur_info blur_info = + workspace_get_blur_info(list_len, list_data, scene_output, &blur_region); + // Expand the damage to compensate for blur + if (blur_info.has_blur) { + // Skip the blur artifact prevention if damaging the whole viewport + if (renderer->blur_buffer_dirty) { + // Needs to be extended before clearing + pixman_region32_union_rect(&render_data.damage, &render_data.damage, + 0, 0, output->width, output->height); + } else { + // copy the surrounding content where the blur would display artifacts + // and draw it above the artifacts + struct blur_data *blur_data = &scene_output->scene->blur_data; + int output_width, output_height; + wlr_output_transformed_resolution(output, &output_width, &output_height); + + // ensure that the damage isn't expanding past the output's size + int32_t damage_width = damage->extents.x2 - damage->extents.x1; + int32_t damage_height = damage->extents.y2 - damage->extents.y1; + if (damage_width > output_width || damage_height > output_height) { + pixman_region32_intersect_rect(damage, damage, + 0, 0, output_width, output_height); + } else { + // Expand the original damage to compensate for surrounding + // blurred views to avoid sharp edges between damage regions + wlr_region_expand(damage, damage, blur_data_calc_size(blur_data)); + } + + pixman_region32_t extended_damage; + pixman_region32_init(&extended_damage); + pixman_region32_intersect(&extended_damage, damage, &blur_region); + // Expand the region to compensate for blur artifacts + wlr_region_expand(&extended_damage, &extended_damage, blur_data_calc_size(blur_data)); + // Limit to the monitors viewport + pixman_region32_intersect_rect(&extended_damage, &extended_damage, + 0, 0, output_width, output_height); + + // capture the padding pixels around the blur where artifacts will be drawn + pixman_region32_subtract(&renderer->blur_padding_region, + &extended_damage, damage); + // Combine into the surface damage (we need to redraw the padding area as well) + pixman_region32_union(damage, damage, &extended_damage); + pixman_region32_fini(&extended_damage); + + // Capture the padding pixels before blur for later use + fx_renderer_read_to_buffer(render_pass, &renderer->blur_padding_region, + renderer->blur_saved_pixels_buffer, render_pass->buffer); + } + } + pixman_region32_fini(&blur_region); + pixman_region32_t background; pixman_region32_init(&background); pixman_region32_copy(&background, &render_data.damage); @@ -1997,6 +2216,14 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } } + // Not needed if we damaged the whole viewport + if (!renderer->blur_buffer_dirty) { + // TODO: Investigate blitting instead + // Render the saved pixels over the blur artifacts + fx_renderer_read_to_buffer(render_pass, &renderer->blur_padding_region, + render_pass->buffer, renderer->blur_saved_pixels_buffer); + } + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { struct highlight_region *damage; wl_list_for_each(damage, &scene_output->damage_highlight_regions, link) {