summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/render/fx_renderer/fx_renderer.h45
-rw-r--r--include/render/fx_renderer/fx_stencilbuffer.h20
-rw-r--r--include/types/fx/shadow_data.h17
-rw-r--r--include/wlr/types/wlr_scene.h8
-rw-r--r--render/fx_renderer/fx_renderer.c188
-rw-r--r--render/fx_renderer/fx_stencilbuffer.c50
-rw-r--r--render/fx_renderer/gles2/shaders/box_shadow.frag74
-rw-r--r--render/fx_renderer/gles2/shaders/meson.build2
-rw-r--r--render/fx_renderer/gles2/shaders/stencil_mask.frag17
-rw-r--r--render/fx_renderer/meson.build1
-rw-r--r--tinywl/tinywl.c26
-rw-r--r--types/fx/meson.build3
-rw-r--r--types/fx/shadow_data.c18
-rw-r--r--types/meson.build2
-rw-r--r--types/scene/wlr_scene.c110
15 files changed, 570 insertions, 11 deletions
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 <wlr/render/wlr_texture.h>
#include <wlr/util/addon.h>
#include <wlr/util/box.h>
+#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 <GLES2/gl2.h>
+#include <stdbool.h>
+#include <wlr/render/wlr_texture.h>
+
+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 <stdbool.h>
+#include <wlr/util/addon.h>
+
+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 <wayland-server-core.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_damage_ring.h>
+#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;
@@ -388,6 +390,12 @@ 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.
*/
void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer,
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 <wlr/util/log.h>
#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 <assert.h>
+#include <wlr/render/gles2.h>
+#include <wlr/util/log.h>
+
+#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 <stdlib.h>
#include <stdio.h>
#include <time.h>
+#include <types/fx/shadow_data.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wayland-util.h>
@@ -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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#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 <string.h>
#include <wlr/backend.h>
#include <wlr/render/gles2.h>
+#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_damage_ring.h>
#include <wlr/types/wlr_matrix.h>
@@ -13,6 +14,7 @@
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#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);