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.
This commit is contained in:
Benjamin Otte 2023-10-08 06:47:19 +02:00
parent 268ad54c6a
commit d8db673fb7
7 changed files with 296 additions and 13 deletions

View File

@ -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;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "gskgputypesprivate.h"
#include "gsktypes.h"
#include <graphene.h>
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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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',

View File

@ -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',