From 1be21a33d939e9ce3569493608b1fec3d5ce0677 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 7 May 2023 01:49:29 +0200 Subject: [PATCH] vulkan: Rewrite rounded rectangle to use SDF distance We can use this to properly compute distance in scaled situations. We also now compute coverage with (imperfect) antialiasing. --- gsk/vulkan/resources/clip.frag.glsl | 2 +- gsk/vulkan/resources/ellipse.glsl | 38 ++++++++++++ gsk/vulkan/resources/meson.build | 1 + gsk/vulkan/resources/rounded-rect.frag.glsl | 17 ++++++ gsk/vulkan/resources/rounded-rect.glsl | 64 ++++++++------------- 5 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 gsk/vulkan/resources/ellipse.glsl create mode 100644 gsk/vulkan/resources/rounded-rect.frag.glsl diff --git a/gsk/vulkan/resources/clip.frag.glsl b/gsk/vulkan/resources/clip.frag.glsl index 472dc6f58c..4f4755c5bb 100644 --- a/gsk/vulkan/resources/clip.frag.glsl +++ b/gsk/vulkan/resources/clip.frag.glsl @@ -1,5 +1,5 @@ #include "constants.glsl" -#include "rounded-rect.glsl" +#include "rounded-rect.frag.glsl" #ifndef _CLIP_ #define _CLIP_ diff --git a/gsk/vulkan/resources/ellipse.glsl b/gsk/vulkan/resources/ellipse.glsl new file mode 100644 index 0000000000..08986de731 --- /dev/null +++ b/gsk/vulkan/resources/ellipse.glsl @@ -0,0 +1,38 @@ +#ifndef _ELLIPSE_ +#define _ELLIPSE_ + +struct Ellipse +{ + vec2 center; + vec2 radius; +}; + +float +ellipse_distance (Ellipse r, vec2 p) +{ + vec2 e = r.radius; + p = p - r.center; + + if (e.x == e.y) + return length (p) - e.x; + + /* from https://www.shadertoy.com/view/tt3yz7 */ + vec2 pAbs = abs(p); + vec2 ei = 1.0 / e; + vec2 e2 = e*e; + vec2 ve = ei * vec2(e2.x - e2.y, e2.y - e2.x); + + vec2 t = vec2(0.70710678118654752, 0.70710678118654752); + for (int i = 0; i < 3; i++) { + vec2 v = ve*t*t*t; + vec2 u = normalize(pAbs - v) * length(t * e - v); + vec2 w = ei * (v + u); + t = normalize(clamp(w, 0.0, 1.0)); + } + + vec2 nearestAbs = t * e; + float dist = length(pAbs - nearestAbs); + return dot(pAbs, pAbs) < dot(nearestAbs, nearestAbs) ? -dist : dist; +} + +#endif diff --git a/gsk/vulkan/resources/meson.build b/gsk/vulkan/resources/meson.build index 802f391b4d..2bcbd03ba3 100644 --- a/gsk/vulkan/resources/meson.build +++ b/gsk/vulkan/resources/meson.build @@ -6,6 +6,7 @@ gsk_private_vulkan_include_shaders = [ 'rect.frag.glsl', 'rect.vert.glsl', 'rounded-rect.glsl', + 'rounded-rect.frag.glsl', ] gsk_private_vulkan_fragment_shaders = [ diff --git a/gsk/vulkan/resources/rounded-rect.frag.glsl b/gsk/vulkan/resources/rounded-rect.frag.glsl new file mode 100644 index 0000000000..fe62dab565 --- /dev/null +++ b/gsk/vulkan/resources/rounded-rect.frag.glsl @@ -0,0 +1,17 @@ +#ifndef _ROUNDED_RECT_FRAG_ +#define _ROUNDED_RECT_FRAG_ + +#include "rounded-rect.glsl" + +float +rounded_rect_coverage (RoundedRect r, vec2 p) +{ + vec2 fw = abs (fwidth (p)); + float distance_scale = max (fw.x, fw.y); + float distance = rounded_rect_distance (r, p) / distance_scale; + float coverage = 0.5 - distance; + + return clamp (coverage, 0, 1); +} + +#endif diff --git a/gsk/vulkan/resources/rounded-rect.glsl b/gsk/vulkan/resources/rounded-rect.glsl index e0c2ffc6f9..5ee5dfd2fa 100644 --- a/gsk/vulkan/resources/rounded-rect.glsl +++ b/gsk/vulkan/resources/rounded-rect.glsl @@ -1,6 +1,9 @@ #ifndef _ROUNDED_RECT_ #define _ROUNDED_RECT_ +#include "ellipse.glsl" +#include "rect.glsl" + struct RoundedRect { vec4 bounds; @@ -9,51 +12,34 @@ struct RoundedRect }; float -ellipsis_dist (vec2 p, vec2 radius) +rounded_rect_distance (RoundedRect r, vec2 p) { - vec2 p0 = p / radius; - vec2 p1 = 2.0 * p0 / radius; - - return (dot(p0, p0) - 1.0) / length (p1); -} + Rect bounds = Rect(vec4(r.bounds)); -float -ellipsis_coverage (vec2 point, vec2 center, vec2 radius) -{ - float d = ellipsis_dist (point - center, radius); - return clamp (0.5 - d, 0.0, 1.0); -} - -float -rounded_rect_coverage (RoundedRect r, vec2 p) -{ - if (p.x < r.bounds.x || p.y < r.bounds.y || - p.x >= r.bounds.z || p.y >= r.bounds.w) - return 0.0; + float bounds_distance = rect_distance (bounds, p); - vec2 rad_tl = vec2(r.corner_widths.x, r.corner_heights.x); - vec2 rad_tr = vec2(r.corner_widths.y, r.corner_heights.y); - vec2 rad_br = vec2(r.corner_widths.z, r.corner_heights.z); - vec2 rad_bl = vec2(r.corner_widths.w, r.corner_heights.w); - - vec2 ref_tl = r.bounds.xy + vec2( r.corner_widths.x, r.corner_heights.x); - vec2 ref_tr = r.bounds.zy + vec2(-r.corner_widths.y, r.corner_heights.y); - vec2 ref_br = r.bounds.zw + vec2(-r.corner_widths.z, -r.corner_heights.z); - vec2 ref_bl = r.bounds.xw + vec2( r.corner_widths.w, -r.corner_heights.w); + Ellipse tl = Ellipse (r.bounds.xy + vec2( r.corner_widths.x, r.corner_heights.x), + vec2(r.corner_widths.x, r.corner_heights.x)); + Ellipse tr = Ellipse (r.bounds.zy + vec2(-r.corner_widths.y, r.corner_heights.y), + vec2(r.corner_widths.y, r.corner_heights.y)); + Ellipse br = Ellipse (r.bounds.zw + vec2(-r.corner_widths.z, -r.corner_heights.z), + vec2(r.corner_widths.z, r.corner_heights.z)); + Ellipse bl = Ellipse (r.bounds.xw + vec2( r.corner_widths.w, -r.corner_heights.w), + vec2(r.corner_widths.w, r.corner_heights.w)); - float d_tl = ellipsis_coverage(p, ref_tl, rad_tl); - float d_tr = ellipsis_coverage(p, ref_tr, rad_tr); - float d_br = ellipsis_coverage(p, ref_br, rad_br); - float d_bl = ellipsis_coverage(p, ref_bl, rad_bl); + vec4 distances = vec4(ellipse_distance (tl, p), + ellipse_distance (tr, p), + ellipse_distance (br, p), + ellipse_distance (bl, p)); - vec4 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl); + bvec4 is_out = bvec4(p.x < tl.center.x && p.y < tl.center.y, + p.x > tr.center.x && p.y < tr.center.y, + p.x > br.center.x && p.y > br.center.y, + p.x < bl.center.x && p.y > bl.center.y); + distances = mix (vec4(bounds_distance), distances, is_out); - bvec4 is_out = bvec4(p.x < ref_tl.x && p.y < ref_tl.y, - p.x > ref_tr.x && p.y < ref_tr.y, - p.x > ref_br.x && p.y > ref_br.y, - p.x < ref_bl.x && p.y > ref_bl.y); - - return 1.0 - dot(vec4(is_out), corner_coverages); + vec2 max2 = max (distances.xy, distances.zw); + return max (max2.x, max2.y); } RoundedRect