From d8db673fb7fd66253206821c998a3ef06449d906 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 8 Oct 2023 06:47:19 +0200 Subject: [PATCH] gpu: Add a box shadow shader Code was inspired mainly by https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ and https://pcwalton.github.io/_posts/2015-12-21-drawing-css-box-shadows-in-webrender.html So far the results aren't cached, that's the task of future commits. --- gsk/gpu/gskgpuboxshadowop.c | 91 +++++++++++++++++ gsk/gpu/gskgpuboxshadowopprivate.h | 23 +++++ gsk/gpu/gskgpunodeprocessor.c | 47 ++++++--- gsk/gpu/shaders/common.glsl | 1 + gsk/gpu/shaders/gskgpuboxshadow.glsl | 145 +++++++++++++++++++++++++++ gsk/gpu/shaders/meson.build | 1 + gsk/meson.build | 1 + 7 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 gsk/gpu/gskgpuboxshadowop.c create mode 100644 gsk/gpu/gskgpuboxshadowopprivate.h create mode 100644 gsk/gpu/shaders/gskgpuboxshadow.glsl diff --git a/gsk/gpu/gskgpuboxshadowop.c b/gsk/gpu/gskgpuboxshadowop.c new file mode 100644 index 0000000000..2a8fbac76d --- /dev/null +++ b/gsk/gpu/gskgpuboxshadowop.c @@ -0,0 +1,91 @@ +#include "config.h" + +#include "gskgpuboxshadowopprivate.h" + +#include "gskgpuframeprivate.h" +#include "gskgpuprintprivate.h" +#include "gskgpushaderopprivate.h" +#include "gskrectprivate.h" +#include "gsk/gskroundedrectprivate.h" + +#include "gpu/shaders/gskgpuboxshadowinstance.h" + +typedef struct _GskGpuBoxShadowOp GskGpuBoxShadowOp; + +struct _GskGpuBoxShadowOp +{ + GskGpuShaderOp op; +}; + +static void +gsk_gpu_box_shadow_op_print (GskGpuOp *op, + GskGpuFrame *frame, + GString *string, + guint indent) +{ + GskGpuShaderOp *shader = (GskGpuShaderOp *) op; + GskGpuBoxshadowInstance *instance; + + instance = (GskGpuBoxshadowInstance *) gsk_gpu_frame_get_vertex_data (frame, shader->vertex_offset); + + gsk_gpu_print_op (string, indent, instance->inset ? "inset-shadow" : "outset-shadow"); + gsk_gpu_print_rounded_rect (string, instance->outline); + gsk_gpu_print_rgba (string, instance->color); + g_string_append_printf (string, "%g %g %g %g ", + instance->shadow_offset[0], instance->shadow_offset[1], + instance->blur_radius, instance->shadow_spread); + gsk_gpu_print_newline (string); +} + +static const GskGpuShaderOpClass GSK_GPU_BOX_SHADOW_OP_CLASS = { + { + GSK_GPU_OP_SIZE (GskGpuBoxShadowOp), + GSK_GPU_STAGE_SHADER, + gsk_gpu_shader_op_finish, + gsk_gpu_box_shadow_op_print, +#ifdef GDK_RENDERING_VULKAN + gsk_gpu_shader_op_vk_command, +#endif + gsk_gpu_shader_op_gl_command + }, + "gskgpuboxshadow", + sizeof (GskGpuBoxshadowInstance), +#ifdef GDK_RENDERING_VULKAN + &gsk_gpu_boxshadow_info, +#endif + gsk_gpu_boxshadow_setup_vao +}; + +void +gsk_gpu_box_shadow_op (GskGpuFrame *frame, + GskGpuShaderClip clip, + gboolean inset, + const graphene_rect_t *bounds, + const GskRoundedRect *outline, + const graphene_point_t *shadow_offset, + float spread, + float blur_radius, + const graphene_point_t *offset, + const GdkRGBA *color) +{ + GskGpuBoxshadowInstance *instance; + + /* Use border shader for no blurring */ + g_return_if_fail (blur_radius > 0.0f); + + gsk_gpu_shader_op_alloc (frame, + &GSK_GPU_BOX_SHADOW_OP_CLASS, + clip, + NULL, + &instance); + + gsk_gpu_rect_to_float (bounds, offset, instance->bounds); + gsk_rounded_rect_to_float (outline, offset, instance->outline); + gsk_gpu_rgba_to_float (color, instance->color); + instance->shadow_offset[0] = shadow_offset->x; + instance->shadow_offset[1] = shadow_offset->y; + instance->shadow_spread = spread; + instance->blur_radius = blur_radius; + instance->inset = inset ? 1 : 0; +} + diff --git a/gsk/gpu/gskgpuboxshadowopprivate.h b/gsk/gpu/gskgpuboxshadowopprivate.h new file mode 100644 index 0000000000..9d6050f038 --- /dev/null +++ b/gsk/gpu/gskgpuboxshadowopprivate.h @@ -0,0 +1,23 @@ +#pragma once + +#include "gskgputypesprivate.h" +#include "gsktypes.h" + +#include + +G_BEGIN_DECLS + +void gsk_gpu_box_shadow_op (GskGpuFrame *frame, + GskGpuShaderClip clip, + gboolean inset, + const graphene_rect_t *bounds, + const GskRoundedRect *outline, + const graphene_point_t *shadow_offset, + float spread, + float blur_radius, + const graphene_point_t *offset, + const GdkRGBA *color); + + +G_END_DECLS + diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index edf58fc479..db047c7e64 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -3,6 +3,7 @@ #include "gskgpunodeprocessorprivate.h" #include "gskgpuborderopprivate.h" +#include "gskgpuboxshadowopprivate.h" #include "gskgpubluropprivate.h" #include "gskgpuclearopprivate.h" #include "gskgpuclipprivate.h" @@ -1195,18 +1196,27 @@ gsk_gpu_node_processor_add_inset_shadow_node (GskGpuNodeProcessor *self, gsk_inset_shadow_node_get_dy (node)), (float[4]) { spread, spread, spread, spread }, (GdkRGBA[4]) { *color, *color, *color, *color }); - return; } - - GSK_DEBUG (FALLBACK, "No blurring for inset shadows"); - gsk_gpu_node_processor_add_fallback_node (self, node); + else + { + gsk_gpu_box_shadow_op (self->frame, + gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds), + TRUE, + &node->bounds, + gsk_inset_shadow_node_get_outline (node), + &GRAPHENE_POINT_INIT (gsk_inset_shadow_node_get_dx (node), + gsk_inset_shadow_node_get_dy (node)), + spread, + blur_radius, + &self->offset, + color); + } } static void gsk_gpu_node_processor_add_outset_shadow_node (GskGpuNodeProcessor *self, GskRenderNode *node) { - GskRoundedRect outline; const GdkRGBA *color; float spread, blur_radius, dx, dy; @@ -1216,12 +1226,14 @@ gsk_gpu_node_processor_add_outset_shadow_node (GskGpuNodeProcessor *self, dx = gsk_outset_shadow_node_get_dx (node); dy = gsk_outset_shadow_node_get_dy (node); - gsk_rounded_rect_init_copy (&outline, gsk_outset_shadow_node_get_outline (node)); - gsk_rounded_rect_shrink (&outline, -spread, -spread, -spread, -spread); - graphene_rect_offset (&outline.bounds, dx, dy); - if (blur_radius == 0) { + GskRoundedRect outline; + + gsk_rounded_rect_init_copy (&outline, gsk_outset_shadow_node_get_outline (node)); + gsk_rounded_rect_shrink (&outline, -spread, -spread, -spread, -spread); + graphene_rect_offset (&outline.bounds, dx, dy); + gsk_gpu_border_op (self->frame, gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds), &outline, @@ -1229,11 +1241,20 @@ gsk_gpu_node_processor_add_outset_shadow_node (GskGpuNodeProcessor *self, &GRAPHENE_POINT_INIT (-dx, -dy), (float[4]) { spread, spread, spread, spread }, (GdkRGBA[4]) { *color, *color, *color, *color }); - return; } - - GSK_DEBUG (FALLBACK, "No blurring for outset shadows"); - gsk_gpu_node_processor_add_fallback_node (self, node); + else + { + gsk_gpu_box_shadow_op (self->frame, + gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds), + FALSE, + &node->bounds, + gsk_outset_shadow_node_get_outline (node), + &GRAPHENE_POINT_INIT (dx, dy), + spread, + blur_radius, + &self->offset, + color); + } } static gboolean diff --git a/gsk/gpu/shaders/common.glsl b/gsk/gpu/shaders/common.glsl index 68ac8676b0..ce1bae5374 100644 --- a/gsk/gpu/shaders/common.glsl +++ b/gsk/gpu/shaders/common.glsl @@ -18,6 +18,7 @@ void main_clip_rounded (void); #include "roundedrect.glsl" #define PI 3.1415926535897932384626433832795 +#define SQRT1_2 1.4142135623730951 Rect rect_clip (Rect r) diff --git a/gsk/gpu/shaders/gskgpuboxshadow.glsl b/gsk/gpu/shaders/gskgpuboxshadow.glsl new file mode 100644 index 0000000000..03c2becf3c --- /dev/null +++ b/gsk/gpu/shaders/gskgpuboxshadow.glsl @@ -0,0 +1,145 @@ +#include "common.glsl" + +/* blur radius (aka in_blur_direction) 0 is NOT supported and MUST be caught before */ + +PASS(0) vec2 _pos; +PASS_FLAT(1) RoundedRect _shadow_outline; +PASS_FLAT(4) RoundedRect _clip_outline; +PASS_FLAT(7) vec4 _color; +PASS_FLAT(8) vec2 _sigma; +PASS_FLAT(9) uint _inset; + +#ifdef GSK_VERTEX_SHADER + +IN(0) mat3x4 in_outline; +IN(3) vec4 in_bounds; +IN(4) vec4 in_color; +IN(5) vec2 in_shadow_offset; +IN(6) float in_shadow_spread; +IN(7) float in_blur_radius; +IN(8) uint in_inset; + +void +run (out vec2 pos) +{ + RoundedRect outline = rounded_rect_from_gsk (in_outline); + + pos = rect_get_position (rect_from_gsk (in_bounds)); + + _pos = pos; + _clip_outline = outline; + vec2 spread = GSK_GLOBAL_SCALE * in_shadow_spread; + if (in_inset == 0u) + spread = -spread; + outline = rounded_rect_shrink (outline, spread.yxyx); + rounded_rect_offset (outline, GSK_GLOBAL_SCALE * in_shadow_offset); + _shadow_outline = outline; + _color = in_color; + _sigma = GSK_GLOBAL_SCALE * 0.5 * in_blur_radius; + _inset = in_inset; +} + +#endif + + +#ifdef GSK_FRAGMENT_SHADER + +/* A standard gaussian function, used for weighting samples */ +float +gauss (float x, + float sigma) +{ + float sigma_2 = sigma * sigma; + return 1.0 / sqrt (2.0 * PI * sigma_2) * exp (-(x * x) / (2.0 * sigma_2)); +} + +/* This 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); +} + +float +erf_range (vec2 x, + float sigma) +{ + vec2 from_to = 0.5 - 0.5 * erf (x / (sigma * SQRT1_2)); + return from_to.y - from_to.x; +} + +float +ellipse_x (vec2 ellipse, + float y) +{ + float y_scaled = y / ellipse.y; + return ellipse.x * sqrt (1.0 - y_scaled * y_scaled); +} + +float +blur_rect (Rect r, + vec2 pos) +{ + return erf_range (r.bounds.xz - pos.x, _sigma.x) * erf_range (r.bounds.yw - pos.y, _sigma.y); +} + +float +blur_corner (vec2 p, + vec2 r) +{ + if (min (r.x, r.y) <= 0.0) + return 0.0; + + float result = 0.0; + float step = 1.0; + for (float y = 0.5 * step; y <= r.y; y += step) + { + float x = r.x - ellipse_x (r, r.y - y); + result -= gauss (p.y - y, _sigma.y) * erf_range (vec2 (- p.x, x - p.x), _sigma.x); + } + return result; +} + +float +blur_rounded_rect (RoundedRect r, + vec2 p) +{ + float result = blur_rect (Rect (r.bounds), _pos); + + result -= blur_corner (p - r.bounds.xy, vec2 (r.corner_widths[TOP_LEFT], r.corner_heights[TOP_LEFT])); + result -= blur_corner (vec2 (r.bounds.z - p.x, p.y - r.bounds.y), vec2 (r.corner_widths[TOP_RIGHT], r.corner_heights[TOP_RIGHT])); + result -= blur_corner (r.bounds.zw - p, vec2 (r.corner_widths[BOTTOM_RIGHT], r.corner_heights[BOTTOM_RIGHT])); + result -= blur_corner (vec2 (p.x - r.bounds.x, r.bounds.w - p.y), vec2 (r.corner_widths[BOTTOM_LEFT], r.corner_heights[BOTTOM_LEFT])); + + return result; +} + + +void +run (out vec4 color, + out vec2 position) +{ + float clip_alpha = rounded_rect_coverage (_clip_outline, _pos); + + if (_inset == 0u) + clip_alpha = 1.0 - clip_alpha; + + if (clip_alpha == 0.0) + { + color = vec4 (0.0); + position = _pos; + return; + } + + float blur_alpha = blur_rounded_rect (_shadow_outline, _pos); + if (_inset == 1u) + blur_alpha = 1.0 - blur_alpha; + + color = clip_alpha * _color * blur_alpha; + position = _pos; +} + +#endif diff --git a/gsk/gpu/shaders/meson.build b/gsk/gpu/shaders/meson.build index bab7c28437..618bf70994 100644 --- a/gsk/gpu/shaders/meson.build +++ b/gsk/gpu/shaders/meson.build @@ -14,6 +14,7 @@ gsk_private_gpu_include_shaders = files([ gsk_private_gpu_shaders = files([ 'gskgpublur.glsl', 'gskgpuborder.glsl', + 'gskgpuboxshadow.glsl', 'gskgpucolor.glsl', 'gskgpucolorize.glsl', 'gskgpuroundedcolor.glsl', diff --git a/gsk/meson.build b/gsk/meson.build index 9aeb32ba24..a1d0acabfd 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -75,6 +75,7 @@ gsk_private_sources = files([ 'gpu/gskgpublitop.c', 'gpu/gskgpublurop.c', 'gpu/gskgpuborderop.c', + 'gpu/gskgpuboxshadowop.c', 'gpu/gskgpubuffer.c', 'gpu/gskgpubufferwriter.c', 'gpu/gskgpuclearop.c',