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