vulkan: Rewrite rounded rectangle to use SDF distance
authorBenjamin Otte <otte@redhat.com>
Sat, 6 May 2023 23:49:29 +0000 (01:49 +0200)
committerBenjamin Otte <otte@redhat.com>
Sun, 4 Jun 2023 17:42:00 +0000 (19:42 +0200)
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
gsk/vulkan/resources/ellipse.glsl [new file with mode: 0644]
gsk/vulkan/resources/meson.build
gsk/vulkan/resources/rounded-rect.frag.glsl [new file with mode: 0644]
gsk/vulkan/resources/rounded-rect.glsl

index 472dc6f58c9ef3e3780cbedaf3aee427ba646dd3..4f4755c5bbb57e9608391cd4ba0b9b6194dece60 100644 (file)
@@ -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 (file)
index 0000000..08986de
--- /dev/null
@@ -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
index 802f391b4dba0ad29c753bdd0a9c5edcfc70ca78..2bcbd03ba323f8f2903e34f178c17c8a37b31774 100644 (file)
@@ -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 (file)
index 0000000..fe62dab
--- /dev/null
@@ -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
index e0c2ffc6f905d7f4afc3b41880ac5a12a6dbe8d8..5ee5dfd2fac596b58427eaf16dc5fd5b11f5b0b3 100644 (file)
@@ -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);
-}
-
-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);
-}
+  Rect bounds = Rect(vec4(r.bounds));
 
-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);
-
-  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 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl);
-
-  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);
+  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));
+
+ vec4 distances = vec4(ellipse_distance (tl, p),
+                       ellipse_distance (tr, p),
+                       ellipse_distance (br, p),
+                       ellipse_distance (bl, p));
+
+  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);
+
+  vec2 max2 = max (distances.xy, distances.zw);
+  return max (max2.x, max2.y);
 }
 
 RoundedRect