From b929a2bbadf467864796ad4ec90882ce86cfebff Mon Sep 17 00:00:00 2001 From: Erik Reider <35975961+ErikReider@users.noreply.github.com> Date: Sun, 6 Aug 2023 20:48:58 +0200 Subject: feat: add box shadows (#16) --- include/render/fx_renderer/fx_renderer.h | 45 +++++ include/render/fx_renderer/fx_stencilbuffer.h | 20 +++ include/types/fx/shadow_data.h | 17 ++ include/wlr/types/wlr_scene.h | 8 + render/fx_renderer/fx_renderer.c | 188 ++++++++++++++++++++- render/fx_renderer/fx_stencilbuffer.c | 50 ++++++ render/fx_renderer/gles2/shaders/box_shadow.frag | 74 ++++++++ render/fx_renderer/gles2/shaders/meson.build | 2 + render/fx_renderer/gles2/shaders/stencil_mask.frag | 17 ++ render/fx_renderer/meson.build | 1 + tinywl/tinywl.c | 26 +-- types/fx/meson.build | 3 + types/fx/shadow_data.c | 18 ++ types/meson.build | 2 + types/scene/wlr_scene.c | 110 ++++++++++++ 15 files changed, 570 insertions(+), 11 deletions(-) create mode 100644 include/render/fx_renderer/fx_stencilbuffer.h create mode 100644 include/types/fx/shadow_data.h create mode 100644 render/fx_renderer/fx_stencilbuffer.c create mode 100644 render/fx_renderer/gles2/shaders/box_shadow.frag create mode 100644 render/fx_renderer/gles2/shaders/stencil_mask.frag create mode 100644 types/fx/meson.build create mode 100644 types/fx/shadow_data.c diff --git a/include/render/fx_renderer/fx_renderer.h b/include/render/fx_renderer/fx_renderer.h index 2067c04..f569aa9 100644 --- a/include/render/fx_renderer/fx_renderer.h +++ b/include/render/fx_renderer/fx_renderer.h @@ -8,6 +8,8 @@ #include #include #include +#include "render/fx_renderer/fx_stencilbuffer.h" +#include "types/fx/shadow_data.h" enum fx_tex_shader_source { SHADER_SOURCE_TEXTURE_RGBA = 1, @@ -34,9 +36,32 @@ struct tex_shader { GLint radius; }; +struct stencil_mask_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + GLint half_size; + GLint position; + GLint radius; +}; + +struct box_shadow_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + GLint position; + GLint size; + GLint blur_sigma; + GLint corner_radius; +}; + struct fx_renderer { float projection[9]; + struct fx_stencilbuffer stencil_buffer; + struct wlr_addon addon; struct { @@ -52,6 +77,8 @@ struct fx_renderer { struct tex_shader tex_rgba; struct tex_shader tex_rgbx; struct tex_shader tex_ext; + struct box_shadow_shader box_shadow; + struct stencil_mask_shader stencil_mask; } shaders; }; @@ -71,6 +98,19 @@ void fx_renderer_clear(const float color[static 4]); void fx_renderer_scissor(struct wlr_box *box); +// Initialize the stenciling work +void fx_renderer_stencil_mask_init(void); + +// Close the mask +void fx_renderer_stencil_mask_close(bool draw_inside_mask); + +// Finish stenciling and clear the buffer +void fx_renderer_stencil_mask_fini(void); + +void fx_renderer_stencil_enable(void); + +void fx_renderer_stencil_disable(void); + bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], @@ -79,4 +119,9 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, const float color[static 4], const float projection[static 9]); +void fx_render_box_shadow(struct fx_renderer *renderer, + const struct wlr_box *box, const struct wlr_box *stencil_box, + const float matrix[static 9], int corner_radius, + struct shadow_data *shadow_data); + #endif diff --git a/include/render/fx_renderer/fx_stencilbuffer.h b/include/render/fx_renderer/fx_stencilbuffer.h new file mode 100644 index 0000000..6909f96 --- /dev/null +++ b/include/render/fx_renderer/fx_stencilbuffer.h @@ -0,0 +1,20 @@ +#ifndef FX_STENCILBUFFER_H +#define FX_STENCILBUFFER_H + +#include +#include +#include + +struct fx_stencilbuffer { + GLuint rb; + int width; + int height; +}; + +struct fx_stencilbuffer fx_stencilbuffer_create(void); + +void fx_stencilbuffer_init(struct fx_stencilbuffer *stencil_buffer, int width, int height); + +void fx_stencilbuffer_release(struct fx_stencilbuffer *stencil_buffer); + +#endif diff --git a/include/types/fx/shadow_data.h b/include/types/fx/shadow_data.h new file mode 100644 index 0000000..804acfe --- /dev/null +++ b/include/types/fx/shadow_data.h @@ -0,0 +1,17 @@ +#ifndef TYPES_DECORATION_DATA +#define TYPES_DECORATION_DATA + +#include +#include + +struct shadow_data { + bool enabled; + float *color; + float blur_sigma; +}; + +struct shadow_data shadow_data_get_default(void); + +bool scene_buffer_has_shadow(struct shadow_data *data); + +#endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 138e0e2..7b4c002 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -23,6 +23,7 @@ #include #include #include +#include "types/fx/shadow_data.h" struct wlr_output; struct wlr_output_layout; @@ -151,6 +152,7 @@ struct wlr_scene_buffer { float opacity; int corner_radius; + struct shadow_data shadow_data; uint64_t active_outputs; struct wlr_texture *texture; @@ -387,6 +389,12 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer, int radii); +/** +* Sets the shadow of this buffer +*/ +void wlr_scene_buffer_set_shadow_data(struct wlr_scene_buffer *scene_buffer, + struct shadow_data shadow_data); + /** * Calls the buffer's frame_done signal. */ diff --git a/render/fx_renderer/fx_renderer.c b/render/fx_renderer/fx_renderer.c index 014be33..730e314 100644 --- a/render/fx_renderer/fx_renderer.c +++ b/render/fx_renderer/fx_renderer.c @@ -15,12 +15,15 @@ #include #include "render/fx_renderer/fx_renderer.h" +#include "render/fx_renderer/fx_stencilbuffer.h" #include "render/fx_renderer/matrix.h" // shaders #include "common_vert_src.h" #include "quad_frag_src.h" #include "tex_frag_src.h" +#include "stencil_mask_frag_src.h" +#include "box_shadow_frag_src.h" static const GLfloat verts[] = { 1, 0, // top right @@ -120,6 +123,40 @@ static bool link_tex_program(struct tex_shader *shader, return true; } +static bool link_stencil_mask_program(struct stencil_mask_shader *shader) { + GLuint prog; + shader->program = prog = link_program(stencil_mask_frag_src); + if (!shader->program) { + return false; + } + + shader->proj = glGetUniformLocation(prog, "proj"); + shader->color = glGetUniformLocation(prog, "color"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->position = glGetUniformLocation(prog, "position"); + shader->half_size = glGetUniformLocation(prog, "half_size"); + shader->radius = glGetUniformLocation(prog, "radius"); + + return true; +} + +static bool link_box_shadow_program(struct box_shadow_shader *shader) { + GLuint prog; + shader->program = prog = link_program(box_shadow_frag_src); + if (!shader->program) { + return false; + } + shader->proj = glGetUniformLocation(prog, "proj"); + shader->color = glGetUniformLocation(prog, "color"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->position = glGetUniformLocation(prog, "position"); + shader->size = glGetUniformLocation(prog, "size"); + shader->blur_sigma = glGetUniformLocation(prog, "blur_sigma"); + shader->corner_radius = glGetUniformLocation(prog, "corner_radius"); + + return true; +} + static bool check_gl_ext(const char *exts, const char *ext) { size_t extlen = strlen(ext); const char *end = exts + strlen(exts); @@ -191,6 +228,8 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { return NULL; } + renderer->stencil_buffer = fx_stencilbuffer_create(); + // get extensions const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); if (exts_str == NULL) { @@ -226,6 +265,15 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { goto error; } + // stencil mask shader + if (!link_stencil_mask_program(&renderer->shaders.stencil_mask)) { + goto error; + } + // box shadow shader + if (!link_box_shadow_program(&renderer->shaders.box_shadow)) { + goto error; + } + if (!eglMakeCurrent(wlr_egl_get_display(egl), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { wlr_log(WLR_ERROR, "GLES2 RENDERER: Could not unset current EGL"); @@ -240,6 +288,8 @@ error: glDeleteProgram(renderer->shaders.tex_rgba.program); glDeleteProgram(renderer->shaders.tex_rgbx.program); glDeleteProgram(renderer->shaders.tex_ext.program); + glDeleteProgram(renderer->shaders.stencil_mask.program); + glDeleteProgram(renderer->shaders.box_shadow.program); if (!eglMakeCurrent(wlr_egl_get_display(egl), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { @@ -254,10 +304,12 @@ error: } void fx_renderer_fini(struct fx_renderer *renderer) { - // NO OP + fx_stencilbuffer_release(&renderer->stencil_buffer); } void fx_renderer_begin(struct fx_renderer *renderer, int width, int height) { + fx_stencilbuffer_init(&renderer->stencil_buffer, width, height); + glViewport(0, 0, width, height); // refresh projection matrix @@ -282,6 +334,43 @@ void fx_renderer_scissor(struct wlr_box *box) { } } +void fx_renderer_stencil_mask_init(void) { + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + glEnable(GL_STENCIL_TEST); + + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + // Disable writing to color buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); +} + +void fx_renderer_stencil_mask_close(bool draw_inside_mask) { + // Reenable writing to color buffer + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if (draw_inside_mask) { + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + return; + } + glStencilFunc(GL_NOTEQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); +} + +void fx_renderer_stencil_mask_fini(void) { + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + glDisable(GL_STENCIL_TEST); +} + +void fx_renderer_stencil_enable(void) { + glEnable(GL_STENCIL_TEST); +} + +void fx_renderer_stencil_disable(void) { + glDisable(GL_STENCIL_TEST); +} + bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], @@ -410,3 +499,100 @@ void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, glDisableVertexAttribArray(shader.pos_attrib); } + +static void fx_render_stencil_mask(struct fx_renderer *renderer, + const struct wlr_box *box, const float matrix[static 9], + int corner_radius) { + if (box->width == 0 || box->height == 0) { + return; + } + assert(box->width > 0 && box->height > 0); + + // TODO: just pass gl_matrix? + float gl_matrix[9]; + wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); + + // TODO: investigate why matrix is flipped prior to this cmd + // wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix); + + wlr_matrix_transpose(gl_matrix, gl_matrix); + + glEnable(GL_BLEND); + + struct stencil_mask_shader shader = renderer->shaders.stencil_mask; + + glUseProgram(shader.program); + + glUniformMatrix3fv(shader.proj, 1, GL_FALSE, gl_matrix); + + glUniform2f(shader.half_size, box->width * 0.5, box->height * 0.5); + glUniform2f(shader.position, box->x, box->y); + glUniform1f(shader.radius, corner_radius); + + glVertexAttribPointer(shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, + 0, verts); + + glEnableVertexAttribArray(shader.pos_attrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(shader.pos_attrib); +} + +void fx_render_box_shadow(struct fx_renderer *renderer, + const struct wlr_box *box, const struct wlr_box *stencil_box, + const float matrix[static 9], int corner_radius, + struct shadow_data *shadow_data) { + if (box->width == 0 || box->height == 0) { + return; + } + assert(box->width > 0 && box->height > 0); + + float *color = shadow_data->color; + float blur_sigma = shadow_data->blur_sigma; + + float gl_matrix[9]; + wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); + + // TODO: investigate why matrix is flipped prior to this cmd + // wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix); + + wlr_matrix_transpose(gl_matrix, gl_matrix); + + // Init stencil work + fx_renderer_stencil_mask_init(); + // Draw the rounded rect as a mask + fx_render_stencil_mask(renderer, stencil_box, matrix, corner_radius); + fx_renderer_stencil_mask_close(false); + + // blending will practically always be needed (unless we have a madman + // who uses opaque shadows with zero sigma), so just enable it + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + struct box_shadow_shader shader = renderer->shaders.box_shadow; + + glUseProgram(shader.program); + + glUniformMatrix3fv(shader.proj, 1, GL_FALSE, gl_matrix); + glUniform4f(shader.color, color[0], color[1], color[2], color[3]); + glUniform1f(shader.blur_sigma, blur_sigma); + glUniform1f(shader.corner_radius, corner_radius); + + glUniform2f(shader.size, box->width, box->height); + glUniform2f(shader.position, box->x, box->y); + + glVertexAttribPointer(shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, + 0, verts); + + glEnableVertexAttribArray(shader.pos_attrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(shader.pos_attrib); + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + fx_renderer_stencil_mask_fini(); +} diff --git a/render/fx_renderer/fx_stencilbuffer.c b/render/fx_renderer/fx_stencilbuffer.c new file mode 100644 index 0000000..4f57216 --- /dev/null +++ b/render/fx_renderer/fx_stencilbuffer.c @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "include/render/fx_renderer/fx_stencilbuffer.h" + +struct fx_stencilbuffer fx_stencilbuffer_create(void) { + return (struct fx_stencilbuffer) { + .rb = -1, + .width = -1, + .height = -1, + }; +} + +void fx_stencilbuffer_init(struct fx_stencilbuffer *stencil_buffer, int width, int height) { + bool first_alloc = false; + + if (stencil_buffer->rb == (uint32_t) -1) { + glGenRenderbuffers(1, &stencil_buffer->rb); + first_alloc = true; + } + + if (first_alloc || stencil_buffer->width != width || stencil_buffer->height != height) { + glBindRenderbuffer(GL_RENDERBUFFER, stencil_buffer->rb); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); + stencil_buffer->width = width; + stencil_buffer->height = height; + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, + "Stencil buffer incomplete, couldn't create! (FB status: %i)", + status); + return; + } + } + + // Reattach the RenderBuffer to the FrameBuffer + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, stencil_buffer->rb); +} + +void fx_stencilbuffer_release(struct fx_stencilbuffer *stencil_buffer) { + if (stencil_buffer->rb != (uint32_t) -1 && stencil_buffer->rb) { + glDeleteRenderbuffers(1, &stencil_buffer->rb); + } + stencil_buffer->rb = -1; + stencil_buffer->width = -1; + stencil_buffer->height = -1; +} diff --git a/render/fx_renderer/gles2/shaders/box_shadow.frag b/render/fx_renderer/gles2/shaders/box_shadow.frag new file mode 100644 index 0000000..c9b2b91 --- /dev/null +++ b/render/fx_renderer/gles2/shaders/box_shadow.frag @@ -0,0 +1,74 @@ +// Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ + +precision mediump float; +varying vec4 v_color; +varying vec2 v_texcoord; + +uniform vec2 position; +uniform vec2 size; +uniform float blur_sigma; +uniform float corner_radius; + +float gaussian(float x, float sigma) { + const float pi = 3.141592653589793; + return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma); +} + +// approximates the error function, needed for the gaussian integral +vec2 erf(vec2 x) { + vec2 s = sign(x), a = abs(x); + x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; + x *= x; + return s - s / (x * x); +} + +// return the blurred mask along the x dimension +float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) { + float delta = min(halfSize.y - corner - abs(y), 0.0); + float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta)); + vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma)); + return integral.y - integral.x; +} + +// return the mask for the shadow of a box from lower to upper +float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner_radius) { + // Center everything to make the math easier + vec2 center = (lower + upper) * 0.5; + vec2 halfSize = (upper - lower) * 0.5; + point -= center; + + // The signal is only non-zero in a limited range, so don't waste samples + float low = point.y - halfSize.y; + float high = point.y + halfSize.y; + float start = clamp(-3.0 * sigma, low, high); + float end = clamp(3.0 * sigma, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + float step = (end - start) / 4.0; + float y = start + step * 0.5; + float value = 0.0; + for (int i = 0; i < 4; i++) { + value += roundedBoxShadowX(point.x, point.y - y, sigma, corner_radius, halfSize) * gaussian(y, sigma) * step; + y += step; + } + + return value; +} + +// per-pixel "random" number between 0 and 1 +float random() { + return fract(sin(dot(vec2(12.9898, 78.233), gl_FragCoord.xy)) * 43758.5453); +} + +void main() { + float frag_alpha = v_color.a * roundedBoxShadow( + position + blur_sigma, + position + size - blur_sigma, + gl_FragCoord.xy, blur_sigma * 0.5, + corner_radius); + + // dither the alpha to break up color bands + frag_alpha += (random() - 0.5) / 128.0; + + gl_FragColor = vec4(v_color.rgb, frag_alpha); +} diff --git a/render/fx_renderer/gles2/shaders/meson.build b/render/fx_renderer/gles2/shaders/meson.build index ee030a2..42f066e 100644 --- a/render/fx_renderer/gles2/shaders/meson.build +++ b/render/fx_renderer/gles2/shaders/meson.build @@ -4,6 +4,8 @@ shaders = [ 'common.vert', 'quad.frag', 'tex.frag', + 'box_shadow.frag', + 'stencil_mask.frag', ] foreach name : shaders diff --git a/render/fx_renderer/gles2/shaders/stencil_mask.frag b/render/fx_renderer/gles2/shaders/stencil_mask.frag new file mode 100644 index 0000000..ee03307 --- /dev/null +++ b/render/fx_renderer/gles2/shaders/stencil_mask.frag @@ -0,0 +1,17 @@ +precision mediump float; +varying vec2 v_texcoord; + +uniform vec2 half_size; +uniform vec2 position; +uniform float radius; + +void main() { + vec2 q = abs(gl_FragCoord.xy - position - half_size) - half_size + radius; + float dist = min(max(q.x,q.y), 0.0) + length(max(q, 0.0)) - radius; + float smoothedAlpha = 1.0 - smoothstep(-1.0, 0.5, dist); + gl_FragColor = mix(vec4(0.0), vec4(1.0), smoothedAlpha); + + if (gl_FragColor.a < 1.0) { + discard; + } +} diff --git a/render/fx_renderer/meson.build b/render/fx_renderer/meson.build index b4c13a0..394caa3 100644 --- a/render/fx_renderer/meson.build +++ b/render/fx_renderer/meson.build @@ -7,6 +7,7 @@ endif wlr_files += files( 'matrix.c', + 'fx_stencilbuffer.c', 'fx_renderer.c', ) diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index f6e1254..56091e3 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,7 @@ struct tinywl_view { float opacity; int corner_radius; + struct shadow_data shadow_data; }; struct tinywl_keyboard { @@ -597,15 +599,14 @@ static void server_cursor_frame(struct wl_listener *listener, void *data) { } static void output_configure_scene(struct wlr_scene_node *node, - float opacity, int corner_radius) { + struct tinywl_view *view) { if (!node->enabled) { return; } - struct tinywl_view *view = tinywl_view_addon_get(&node->addons, node); - if (view) { - opacity = view->opacity; - corner_radius = view->corner_radius; + struct tinywl_view *_view = tinywl_view_addon_get(&node->addons, node); + if (_view) { + view = _view; } if (node->type == WLR_SCENE_NODE_BUFFER) { @@ -622,21 +623,23 @@ static void output_configure_scene(struct wlr_scene_node *node, xdg_surface = wlr_xdg_surface_from_wlr_surface(scene_surface->surface); } - if (xdg_surface && + if (view && + 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, opacity); + wlr_scene_buffer_set_opacity(buffer, view->opacity); if (!wlr_surface_is_subsurface(xdg_surface->surface)) { - wlr_scene_buffer_set_corner_radius(buffer, corner_radius); + wlr_scene_buffer_set_corner_radius(buffer, view->corner_radius); + wlr_scene_buffer_set_shadow_data(buffer, view->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, opacity, corner_radius); + output_configure_scene(node, view); } } } @@ -650,7 +653,7 @@ 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, 1, 0); + output_configure_scene(&scene_output->scene->tree.node, NULL); /* Render the scene if needed and commit the output */ wlr_scene_output_commit(scene_output); @@ -868,6 +871,9 @@ static void server_new_xdg_surface(struct wl_listener *listener, void *data) { /* Set the scene_nodes decoration data */ view->opacity = 1; view->corner_radius = 20; + view->shadow_data = shadow_data_get_default(); + view->shadow_data.enabled = true; + memcpy(view->shadow_data.color, (float[]) {1.0f, 0.0f, 0.0f, 1.0f}, sizeof(float[4])); /* Listen to the various events it can emit */ view->map.notify = xdg_toplevel_map; diff --git a/types/fx/meson.build b/types/fx/meson.build new file mode 100644 index 0000000..b7f0207 --- /dev/null +++ b/types/fx/meson.build @@ -0,0 +1,3 @@ +wlr_files += files( + 'shadow_data.c' +) diff --git a/types/fx/shadow_data.c b/types/fx/shadow_data.c new file mode 100644 index 0000000..0c2d1d2 --- /dev/null +++ b/types/fx/shadow_data.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include "types/fx/shadow_data.h" +#include "wlr/util/log.h" + +struct shadow_data shadow_data_get_default(void) { + static float default_shadow_color[] = {0.0f, 0.0f, 0.0f, 0.5f}; + return (struct shadow_data) { + .blur_sigma = 20, + .color = default_shadow_color, + .enabled = false, + }; +} + +bool scene_buffer_has_shadow(struct shadow_data *data) { + return data->enabled && data->blur_sigma > 0 && data->color[3] > 0.0; +} diff --git a/types/meson.build b/types/meson.build index fa37bda..b5857d0 100644 --- a/types/meson.build +++ b/types/meson.build @@ -7,3 +7,5 @@ wlr_files += files( 'scene/layer_shell_v1.c', 'buffer/buffer.c', ) + +subdir('fx') diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 873f606..4a32a6b 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include "render/fx_renderer/fx_renderer.h" +#include "types/fx/shadow_data.h" #include "types/wlr_buffer.h" #include "types/wlr_scene.h" #include "util/array.h" @@ -240,6 +242,7 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, return; } + // Buffer is translucent if (scene_buffer->opacity != 1 || scene_buffer->corner_radius > 0) { return; } @@ -398,6 +401,15 @@ static bool scene_node_update_iterator(struct wlr_scene_node *node, pixman_region32_fini(&opaque); } + // Expand the nodes visible region by the shadow size + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + struct shadow_data *data = &buffer->shadow_data; + if (scene_buffer_has_shadow(data)) { + wlr_region_expand(&node->visible, &node->visible, data->blur_sigma); + } + } + update_node_update_outputs(node, data->outputs, NULL); return false; @@ -562,6 +574,7 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, scene_buffer->opacity = 1; scene_buffer->corner_radius = 0; + scene_buffer->shadow_data = shadow_data_get_default(); scene_node_update(&scene_buffer->node, NULL); @@ -773,6 +786,20 @@ void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_shadow_data(struct wlr_scene_buffer *scene_buffer, + struct shadow_data shadow_data) { + struct shadow_data *buff_data = &scene_buffer->shadow_data; + if (buff_data->enabled == shadow_data.enabled && + buff_data->blur_sigma == shadow_data.blur_sigma && + buff_data->color && shadow_data.color) { + return; + } + + memcpy(&scene_buffer->shadow_data, &shadow_data, + sizeof(struct shadow_data)); + scene_node_update(&scene_buffer->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 = @@ -1097,6 +1124,57 @@ static void render_texture(struct fx_renderer *fx_renderer, struct wlr_output *o } } +static void render_box_shadow(struct fx_renderer *fx_renderer, + struct wlr_output *output, pixman_region32_t *surface_damage, + const struct wlr_box *surface_box, int corner_radius, + struct shadow_data *shadow_data) { + // don't damage area behind window since we dont render it anyway + pixman_region32_t inner_region; + pixman_region32_init(&inner_region); + pixman_region32_union_rect(&inner_region, &inner_region, + surface_box->x + corner_radius * 0.5, + surface_box->y + corner_radius * 0.5, + surface_box->width - corner_radius, + surface_box->height - corner_radius); + pixman_region32_intersect(&inner_region, &inner_region, surface_damage); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_subtract(&damage, surface_damage, &inner_region); + if (!pixman_region32_not_empty(&damage)) { + goto damage_finish; + } + + struct wlr_box shadow_box = { + .x = surface_box->x - shadow_data->blur_sigma, + .y = surface_box->y - shadow_data->blur_sigma, + .width = surface_box->width + 2 * shadow_data->blur_sigma, + .height = surface_box->height + 2 * shadow_data->blur_sigma, + }; + float matrix[9]; + wlr_matrix_project_box(matrix, &shadow_box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + output->transform_matrix); + + // ensure the box is updated as per the output orientation + struct wlr_box transformed_box; + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + wlr_box_transform(&transformed_box, &shadow_box, + wlr_output_transform_invert(output->transform), width, height); + + 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_box_shadow(fx_renderer, &transformed_box, surface_box, matrix, + corner_radius, shadow_data); + } + +damage_finish: + pixman_region32_fini(&damage); + pixman_region32_fini(&inner_region); +} + static void scene_node_render(struct fx_renderer *fx_renderer, struct wlr_scene_node *node, struct wlr_scene_output *scene_output, pixman_region32_t *damage) { int x, y; @@ -1148,6 +1226,38 @@ static void scene_node_render(struct fx_renderer *fx_renderer, struct wlr_scene_ wlr_matrix_project_box(matrix, &dst_box, transform, 0.0, output->transform_matrix); + // Some surfaces (mostly GTK 4) decorate their windows with shadows + // which extends the node size past the actual window size. This gets + // the actual surface geometry, mostly ignoring CSD decorations + // but only if we need to. + if (scene_buffer->corner_radius != 0 || + scene_buffer_has_shadow(&scene_buffer->shadow_data)) { + struct wlr_scene_surface *scene_surface = NULL; + if ((scene_surface = wlr_scene_surface_from_buffer(scene_buffer)) && + wlr_surface_is_xdg_surface(scene_surface->surface)) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_wlr_surface(scene_surface->surface); + + struct wlr_box geometry; + wlr_xdg_surface_get_geometry(xdg_surface, &geometry); + dst_box.width = fmin(dst_box.width, geometry.width); + dst_box.height = fmin(dst_box.height, geometry.height); + dst_box.x = fmax(dst_box.x, geometry.x + x); + dst_box.y = fmax(dst_box.y, geometry.y + y); + } + } + + // Shadow + if (scene_buffer_has_shadow(&scene_buffer->shadow_data)) { + // TODO: Compensate for SSD borders here + render_box_shadow(fx_renderer, output, &render_region, &dst_box, + scene_buffer->corner_radius, &scene_buffer->shadow_data); + } + + // Clip the damage to the dst_box before rendering the texture + pixman_region32_intersect_rect(&render_region, &render_region, + dst_box.x, dst_box.y, dst_box.width, dst_box.height); + render_texture(fx_renderer, output, &render_region, texture, &scene_buffer->src_box, &dst_box, matrix, scene_buffer->opacity, scene_buffer->corner_radius); -- cgit v1.2.3