diff --git a/include/sway/desktop/fx_renderer.h b/include/sway/desktop/fx_renderer.h index c5bae260..3620a5b2 100644 --- a/include/sway/desktop/fx_renderer.h +++ b/include/sway/desktop/fx_renderer.h @@ -84,6 +84,17 @@ struct fx_renderer { GLint half_thickness; } corner; + struct { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + GLint position; + GLint size; + GLint blur_sigma; + GLint alpha; + } box_shadow; + struct gles2_tex_shader tex_rgba; struct gles2_tex_shader tex_rgbx; struct gles2_tex_shader tex_ext; @@ -118,4 +129,7 @@ void fx_render_border_corner(struct fx_renderer *renderer, const struct wlr_box const float color[static 4], const float projection[static 9], enum corner_location corner_location, int radius, int border_thickness); +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); + #endif diff --git a/sway/desktop/fx_renderer.c b/sway/desktop/fx_renderer.c index 3b4279fe..f584dd0c 100644 --- a/sway/desktop/fx_renderer.c +++ b/sway/desktop/fx_renderer.c @@ -24,6 +24,7 @@ #include "quad_round_tl_frag_src.h" #include "quad_round_tr_frag_src.h" #include "corner_frag_src.h" +#include "box_shadow_frag_src.h" #include "tex_rgba_frag_src.h" #include "tex_rgbx_frag_src.h" #include "tex_external_frag_src.h" @@ -43,6 +44,7 @@ static GLuint compile_shader(GLuint type, const GLchar *src) { GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok == GL_FALSE) { + sway_log(SWAY_ERROR, "Failed to compile shader"); glDeleteShader(shader); shader = 0; } @@ -75,6 +77,7 @@ static GLuint link_program(const GLchar *vert_src, const GLchar *frag_src) { GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) { + sway_log(SWAY_ERROR, "Failed to link shader"); glDeleteProgram(prog); goto error; } @@ -228,6 +231,20 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { renderer->shaders.corner.half_size = glGetUniformLocation(prog, "half_size"); renderer->shaders.corner.half_thickness = glGetUniformLocation(prog, "half_thickness"); + // box shadow shader + prog = link_program(common_vert_src, box_shadow_frag_src); + renderer->shaders.box_shadow.program = prog; + if (!renderer->shaders.box_shadow.program) { + goto error; + } + renderer->shaders.box_shadow.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.box_shadow.color = glGetUniformLocation(prog, "color"); + renderer->shaders.box_shadow.pos_attrib = glGetUniformLocation(prog, "pos"); + renderer->shaders.box_shadow.position = glGetUniformLocation(prog, "position"); + renderer->shaders.box_shadow.size = glGetUniformLocation(prog, "size"); + renderer->shaders.box_shadow.blur_sigma = glGetUniformLocation(prog, "blur_sigma"); + renderer->shaders.box_shadow.alpha = glGetUniformLocation(prog, "alpha"); + // fragment shaders prog = link_program(common_vert_src, tex_rgba_frag_src); if (!init_frag_shader(&renderer->shaders.tex_rgba, prog)) { @@ -253,6 +270,7 @@ error: glDeleteProgram(renderer->shaders.rounded_tl_quad.program); glDeleteProgram(renderer->shaders.rounded_tr_quad.program); glDeleteProgram(renderer->shaders.corner.program); + glDeleteProgram(renderer->shaders.box_shadow.program); glDeleteProgram(renderer->shaders.tex_rgba.program); glDeleteProgram(renderer->shaders.tex_rgbx.program); glDeleteProgram(renderer->shaders.tex_ext.program); @@ -543,3 +561,45 @@ void fx_render_border_corner(struct fx_renderer *renderer, const struct wlr_box glDisableVertexAttribArray(renderer->shaders.corner.pos_attrib); } + +// TODO: alpha input arg +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) { + if (box->width == 0 || box->height == 0) { + return; + } + assert(box->width > 0 && box->height > 0); + float matrix[9]; + wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0, projection); + + 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); + + // 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); + + glUseProgram(renderer->shaders.box_shadow.program); + + glUniformMatrix3fv(renderer->shaders.box_shadow.proj, 1, GL_FALSE, gl_matrix); + glUniform3f(renderer->shaders.box_shadow.color, color[0], color[1], color[2]); + glUniform1f(renderer->shaders.box_shadow.alpha, color[3]); + glUniform1f(renderer->shaders.box_shadow.blur_sigma, blur_sigma); + + glUniform2f(renderer->shaders.box_shadow.size, box->width, box->height); + glUniform2f(renderer->shaders.box_shadow.position, box->x, box->y); + + glVertexAttribPointer(renderer->shaders.box_shadow.pos_attrib, 2, GL_FLOAT, GL_FALSE, + 0, verts); + + glEnableVertexAttribArray(renderer->shaders.box_shadow.pos_attrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(renderer->shaders.box_shadow.pos_attrib); +} diff --git a/sway/desktop/render.c b/sway/desktop/render.c index 7a985def..60e51108 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -328,6 +328,39 @@ damage_finish: pixman_region32_fini(&damage); } +// _box.x and .y are expected to be layout-local +// _box.width and .height are expected to be output-buffer-local +void render_box_shadow(struct sway_output *output, pixman_region32_t *output_damage, + const struct wlr_box *_box, const float color[static 4]) { + struct wlr_output *wlr_output = output->wlr_output; + struct fx_renderer *renderer = output->server->renderer; + + struct wlr_box box; + memcpy(&box, _box, sizeof(struct wlr_box)); + box.x -= output->lx * wlr_output->scale; + box.y -= output->ly * wlr_output->scale; + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, box.x, box.y, + box.width, box.height); + pixman_region32_intersect(&damage, &damage, output_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(wlr_output, &rects[i]); + fx_render_box_shadow(renderer, &box, color, wlr_output->transform_matrix, 0, 0.3); + } + +damage_finish: + pixman_region32_fini(&damage); +} + void premultiply_alpha(float color[4], float opacity) { color[3] *= opacity; color[0] *= color[3]; @@ -965,6 +998,12 @@ static void render_containers_linear(struct sway_output *output, } else if (state->border == B_PIXEL) { render_top_border(output, damage, state, colors, deco_data.alpha, deco_data.corner_radius); } + + // render shadow + const float color[4] = {0,0,0,0.7}; + struct wlr_box box = {state->x, state->y, state->width, state->height}; + scale_box(&box, output->wlr_output->scale); + render_box_shadow(output, damage, &box, color); } else { render_container(output, damage, child, parent->focused || child->current.focused); diff --git a/sway/desktop/shaders/box_shadow.frag b/sway/desktop/shaders/box_shadow.frag new file mode 100644 index 00000000..2ea2c2dd --- /dev/null +++ b/sway/desktop/shaders/box_shadow.frag @@ -0,0 +1,39 @@ +// Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ + +precision mediump float; +varying vec2 v_texcoord; + +uniform vec3 color; +uniform vec2 position; +uniform vec2 size; +uniform float blur_sigma; +uniform float alpha; + +// approximates the error function, needed for the gaussian integral +vec4 erf(vec4 x) { + vec4 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 mask for the shadow of a box from lower to upper +float box_shadow(vec2 lower, vec2 upper, vec2 point, float sigma) { + vec4 query = vec4(point - lower, upper - point); + vec4 integral = 0.5 + 0.5 * erf(query * (sqrt(0.5) / sigma)); + return (integral.z - integral.x) * (integral.w - integral.y); +} + +// 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 = alpha * box_shadow(position, position + size, gl_FragCoord.xy, blur_sigma); + + // dither the alpha to break up color bands + frag_alpha += (random() - 0.5) / 128.0; + + gl_FragColor = vec4(color, frag_alpha); +} diff --git a/sway/desktop/shaders/meson.build b/sway/desktop/shaders/meson.build index 08efb967..ce2439de 100644 --- a/sway/desktop/shaders/meson.build +++ b/sway/desktop/shaders/meson.build @@ -7,6 +7,7 @@ shaders = [ 'quad_round_tl.frag', 'quad_round_tr.frag', 'corner.frag', + 'box_shadow.frag', 'tex_rgba.frag', 'tex_rgbx.frag', 'tex_external.frag',