diff --git a/data/gala.gresource.xml b/data/gala.gresource.xml
index c12dbbf02..a50d83f0b 100644
--- a/data/gala.gresource.xml
+++ b/data/gala.gresource.xml
@@ -23,7 +23,6 @@
shaders/colorblindness-correction.vert
shaders/monochrome.vert
- shaders/rounded-corners.vert
gala-daemon.css
diff --git a/data/shaders/rounded-corners.vert b/data/shaders/rounded-corners.vert
deleted file mode 100644
index b83a016f9..000000000
--- a/data/shaders/rounded-corners.vert
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2025 elementary, Inc.
- * SPDX-License-Identifier: LGPL-3.0-or-later
- */
-
-// based on shader from mutter
-
-uniform sampler2D tex;
-uniform float clip_radius;
-uniform vec2 actor_size;
-
-float rounded_rect_coverage (vec2 p) {
- float center_left = clip_radius + 1.5;
- float center_right = actor_size.x - clip_radius - 0.55;
- float center_x;
-
- if (p.x < center_left)
- center_x = center_left;
- else if (p.x >= center_right)
- center_x = center_right;
- else
- return 1.0;
-
- float center_top = clip_radius + 1.5;
- float center_bottom = actor_size.y - clip_radius - 0.55;
- float center_y;
-
- if (p.y < center_top)
- center_y = center_top;
- else if (p.y > center_bottom)
- center_y = center_bottom;
- else
- return 1.0;
-
- vec2 delta = p - vec2 (center_x, center_y);
- float dist_squared = dot (delta, delta);
-
- // Fully outside the circle
- float outer_radius = clip_radius + 0.5;
- if (dist_squared > (outer_radius * outer_radius))
- return 0.0;
-
- // Fully inside the circle
- float inner_radius = clip_radius - 0.5;
- if (dist_squared <= (inner_radius * inner_radius))
- return 1.0;
-
- // Only pixels on the edge of the curve need expensive antialiasing
- return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
-}
-
-void main () {
- vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy);
-
- vec2 texture_coord = cogl_tex_coord0_in.xy * actor_size;
- float res = rounded_rect_coverage (texture_coord);
-
- cogl_color_out = sample * cogl_color_in * res;
-}
diff --git a/lib/RoundedCornersEffect.vala b/lib/RoundedCornersEffect.vala
index ba9369d46..f3a9b938a 100644
--- a/lib/RoundedCornersEffect.vala
+++ b/lib/RoundedCornersEffect.vala
@@ -3,74 +3,159 @@
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
-public class Gala.RoundedCornersEffect : Clutter.ShaderEffect {
- private const int CLIP_RADIUS_OFFSET = 3;
+public class Gala.RoundedCornersEffect : Clutter.OffscreenEffect {
+ public int clip_radius { get; construct; }
+ public float monitor_scale { get; construct set; }
- public float clip_radius {
- construct set {
- set_uniform_value ("clip_radius", value + CLIP_RADIUS_OFFSET);
- }
+ private int offset_location;
+ private int actor_size_location;
+ private int full_texture_size_location;
+ private int clip_radius_location;
+
+ public RoundedCornersEffect (int clip_radius, float monitor_scale) {
+ Object (clip_radius: clip_radius, monitor_scale: monitor_scale);
}
- private float _monitor_scale = 1.0f;
- public float monitor_scale {
- get {
- return _monitor_scale;
- }
- construct set {
- _monitor_scale = value;
+ construct {
+ notify["monitor-scale"].connect (queue_repaint);
+ }
+
+ public override Cogl.Pipeline create_pipeline (Cogl.Texture texture) {
+ var snippet = new Cogl.Snippet (
+ Cogl.SnippetHook.FRAGMENT,
+ """
+ uniform sampler2D tex;
+ uniform vec2 offset;
+ uniform vec2 actor_size;
+ uniform vec2 full_texture_size;
+ uniform int clip_radius;
+
+ float rounded_rect_coverage (vec2 p) {
+ float center_left = clip_radius;
+ float center_right = actor_size.x - clip_radius;
+
+ float center_x;
+ if (p.x < center_left) {
+ center_x = center_left;
+ } else if (p.x > center_right) {
+ center_x = center_right;
+ } else {
+ return 1.0;
+ }
+
+ float center_top = clip_radius;
+ float center_bottom = actor_size.y - clip_radius;
- if (actor != null) {
- update_actor_size ();
+ float center_y;
+ if (p.y < center_top) {
+ center_y = center_top;
+ } else if (p.y > center_bottom) {
+ center_y = center_bottom;
+ } else {
+ return 1.0;
+ }
+
+ vec2 delta = p - vec2 (center_x, center_y);
+ float dist_squared = dot (delta, delta);
+
+ // Fully outside the circle
+ float outer_radius = clip_radius + 0.5;
+ if (dist_squared >= (outer_radius * outer_radius)) {
+ return 0.0;
+ }
+
+ // Fully inside the circle
+ float inner_radius = clip_radius - 0.5;
+ if (dist_squared <= (inner_radius * inner_radius)) {
+ return 1.0;
+ }
+
+ // Only pixels on the edge of the curve need expensive antialiasing
+ return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
}
- }
- }
+ """,
+ null
+ );
+ snippet.set_replace (
+ """
+ vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy);
- public RoundedCornersEffect (float clip_radius, float monitor_scale) {
- Object (
-#if HAS_MUTTER48
- shader_type: Cogl.ShaderType.FRAGMENT,
+ vec2 texture_coord = cogl_tex_coord0_in.xy * full_texture_size;
+ if (texture_coord.x < offset.x || texture_coord.x > offset.x + actor_size.x ||
+ texture_coord.y < offset.y || texture_coord.y > offset.y + actor_size.y
+ ) {
+ cogl_color_out = vec4(0, 0, 0, 0);
+ return;
+ }
+
+ texture_coord.x -= offset.x;
+ texture_coord.y -= offset.y;
+ cogl_color_out = sample * cogl_color_in * rounded_rect_coverage (texture_coord);
+ """
+ );
+
+#if HAS_MUTTER47
+ unowned var cogl_context = actor.context.get_backend ().get_cogl_context ();
#else
- shader_type: Clutter.ShaderType.FRAGMENT_SHADER,
+ unowned var cogl_context = Clutter.get_default_backend ().get_cogl_context ();
#endif
- clip_radius: clip_radius,
- monitor_scale: monitor_scale
- );
- }
- construct {
- try {
- var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/rounded-corners.vert", GLib.ResourceLookupFlags.NONE);
- set_shader_source ((string) bytes.get_data ());
- } catch (Error e) {
- critical ("Unable to load rounded-corners.vert: %s", e.message);
- }
+ var pipeline = new Cogl.Pipeline (cogl_context);
+ pipeline.set_layer_texture (0, texture);
+ pipeline.add_snippet (snippet);
+
+ offset_location = pipeline.get_uniform_location ("offset");
+ actor_size_location = pipeline.get_uniform_location ("actor_size");
+ full_texture_size_location = pipeline.get_uniform_location ("full_texture_size");
+ clip_radius_location = pipeline.get_uniform_location ("clip_radius");
+
+ return pipeline;
}
- public override void set_actor (Clutter.Actor? new_actor) {
- if (actor != null) {
- actor.notify["width"].disconnect (update_actor_size);
- actor.notify["height"].disconnect (update_actor_size);
- }
+ public override void paint_target (Clutter.PaintNode node, Clutter.PaintContext paint_context) {
+ float texture_width, texture_height;
+ get_target_size (out texture_width, out texture_height);
- base.set_actor (new_actor);
+ var resource_scale = actor.get_resource_scale ();
- if (actor != null) {
- actor.notify["width"].connect (update_actor_size);
- actor.notify["height"].connect (update_actor_size);
+ var actor_box = actor.get_allocation_box ();
+ actor_box.scale (resource_scale);
+ var effect_box = actor_box.copy ();
+ clutter_actor_box_enlarge_for_effects (ref effect_box);
- update_actor_size ();
- }
+ var offset_x = Math.ceilf ((actor_box.x1 - effect_box.x1) * resource_scale);
+ var offset_y = Math.ceilf ((actor_box.y1 - effect_box.y1) * resource_scale);
+
+ unowned var pipeline = get_pipeline ();
+ pipeline.set_uniform_float (offset_location, 2, 1, { offset_x, offset_y });
+ pipeline.set_uniform_float (actor_size_location, 2, 1, { Math.ceilf (actor_box.get_width ()), Math.ceilf (actor_box.get_height ()) });
+ pipeline.set_uniform_float (full_texture_size_location, 2, 1, { texture_width, texture_height });
+ pipeline.set_uniform_1i (clip_radius_location, Utils.scale_to_int (clip_radius, monitor_scale));
+
+ base.paint_target (node, paint_context);
}
- private void update_actor_size () requires (actor != null) {
- float[] actor_size = {
- actor.width * monitor_scale,
- actor.height * monitor_scale
- };
+ /**
+ * This is the same as mutter's private _clutter_actor_box_enlarge_for_effects function
+ * Mutter basically enlarges the texture a bit to "determine a stable quantized size in pixels
+ * that doesn't vary due to the original box's sub-pixel position."
+ *
+ * We need to account for this in our shader code so this function is reimplemented here.
+ */
+ private void clutter_actor_box_enlarge_for_effects (ref Clutter.ActorBox box) {
+ if (box.get_area () == 0.0) {
+ return;
+ }
+
+ var width = box.x2 - box.x1;
+ var height = box.y2 - box.y1;
+ width = Math.nearbyintf (width);
+ height = Math.nearbyintf (height);
+
+ box.x2 = Math.ceilf (box.x2 + 0.75f);
+ box.y2 = Math.ceilf (box.y2 + 0.75f);
- var actor_size_value = GLib.Value (typeof (Clutter.ShaderFloat));
- Clutter.Value.set_shader_float (actor_size_value, actor_size);
- set_uniform_value ("actor_size", actor_size_value);
+ box.x1 = box.x2 - width - 3;
+ box.y1 = box.y2 - height - 3;
}
}
diff --git a/vapi/mutter-clutter.vapi b/vapi/mutter-clutter.vapi
index b87669553..c3de6c8c6 100644
--- a/vapi/mutter-clutter.vapi
+++ b/vapi/mutter-clutter.vapi
@@ -6690,6 +6690,7 @@ namespace Clutter {
#else
public virtual Cogl.Handle create_texture (float width, float height);
#endif
+ public virtual Cogl.Pipeline create_pipeline (Cogl.Texture texture);
public unowned Cogl.Pipeline? get_pipeline ();
public bool get_target_size (out float width, out float height);
#if HAS_MUTTER46