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..0d2b56bc1 100644 --- a/lib/RoundedCornersEffect.vala +++ b/lib/RoundedCornersEffect.vala @@ -3,48 +3,99 @@ * 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.Effect { + public float 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 float _monitor_scale = 1.0f; - public float monitor_scale { - get { - return _monitor_scale; - } - construct set { - _monitor_scale = value; + private Cogl.Framebuffer round_framebuffer; + private Cogl.Pipeline round_pipeline; + private Cogl.Texture round_texture; + private int round_clip_radius_location; + private int round_actor_size_location; - if (actor != null) { - update_actor_size (); - } - } + public RoundedCornersEffect (float clip_radius, float monitor_scale) { + Object (clip_radius: clip_radius, monitor_scale: monitor_scale); } - public RoundedCornersEffect (float clip_radius, float monitor_scale) { - Object ( -#if HAS_MUTTER48 - shader_type: Cogl.ShaderType.FRAGMENT, + construct { +#if HAS_MUTTER47 + unowned var ctx = actor.context.get_backend ().get_cogl_context (); #else - shader_type: Clutter.ShaderType.FRAGMENT_SHADER, + unowned var ctx = Clutter.get_default_backend ().get_cogl_context (); #endif - clip_radius: clip_radius, - monitor_scale: monitor_scale + + round_pipeline = new Cogl.Pipeline (ctx); + round_pipeline.set_layer_null_texture (0); + round_pipeline.set_layer_filters (0, Cogl.PipelineFilter.LINEAR, Cogl.PipelineFilter.LINEAR); + round_pipeline.set_layer_wrap_mode (0, Cogl.PipelineWrapMode.CLAMP_TO_EDGE); + round_pipeline.add_snippet ( + new Cogl.Snippet ( + Cogl.SnippetHook.FRAGMENT, + """ + uniform sampler2D tex; + uniform vec2 actor_size; + uniform float 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; + + 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)); + } + """, + + """ + 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; + """ + ) ); - } - 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); - } + round_clip_radius_location = round_pipeline.get_uniform_location ("clip_radius"); + round_actor_size_location = round_pipeline.get_uniform_location ("actor_size"); + + update_clip_radius (); + update_actor_size (); + + notify["monitor-scale"].connect (update_clip_radius); } public override void set_actor (Clutter.Actor? new_actor) { @@ -58,19 +109,102 @@ public class Gala.RoundedCornersEffect : Clutter.ShaderEffect { if (actor != null) { actor.notify["width"].connect (update_actor_size); actor.notify["height"].connect (update_actor_size); - update_actor_size (); } } - private void update_actor_size () requires (actor != null) { + private void update_clip_radius () { + float[] _clip_radius = { clip_radius * monitor_scale }; + round_pipeline.set_uniform_float (round_clip_radius_location, 1, 1, _clip_radius); + } + + private void update_actor_size () { + if (actor == null) { + return; + } + float[] actor_size = { - actor.width * monitor_scale, - actor.height * monitor_scale + actor.width, + actor.height }; - 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); + round_pipeline.set_uniform_float (round_actor_size_location, 2, 1, actor_size); + } + + private void setup_projection_matrix (Cogl.Framebuffer framebuffer, float width, float height) { + Graphene.Matrix projection = {}; + projection.init_translate ({ -width / 2.0f, -height / 2.0f, 0.0f }); + projection.scale (2.0f / width, -2.0f / height, 1.0f); + + framebuffer.set_projection_matrix (projection); + } + + private bool update_rounded_fbo (int width, int height) { +#if HAS_MUTTER47 + unowned var ctx = actor.context.get_backend ().get_cogl_context (); +#else + unowned var ctx = Clutter.get_default_backend ().get_cogl_context (); +#endif + +#if HAS_MUTTER46 + round_texture = new Cogl.Texture2D.with_size (ctx, width, height); +#else + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height); + try { + round_texture = new Cogl.Texture2D.from_data (ctx, width, height, Cogl.PixelFormat.BGRA_8888_PRE, surface.get_stride (), surface.get_data ()); + } catch (Error e) { + critical ("BackgroundBlurEffect: Couldn't create round_texture: %s", e.message); + return false; + } +#endif + + round_pipeline.set_layer_texture (0, round_texture); + round_framebuffer = new Cogl.Offscreen.with_texture (round_texture); + + setup_projection_matrix (round_framebuffer, width, height); + + return true; + } + + private bool update_framebuffers (Clutter.PaintContext paint_context) { + if (actor == null) { + return false; + } + + var width = (int) actor.width; + var height = (int) actor.height; + + if (width <= 0 || height <= 0) { + warning ("RoundedCornersEffect: Couldn't update framebuffers, incorrect size"); + return false; + } + + return update_rounded_fbo (width, height); + } + + private Clutter.PaintNode create_round_nodes (Clutter.PaintNode node) { + var round_node = new Clutter.LayerNode.to_framebuffer (round_framebuffer, round_pipeline); + round_node.add_rectangle ({ 0.0f, 0.0f, actor.width, actor.height }); + + node.add_child (round_node); + + return round_node; + } + + private void add_actor_node (Clutter.PaintNode node) { + var actor_node = new Clutter.ActorNode (actor, -1); + node.add_child (actor_node); + } + + public override void paint_node (Clutter.PaintNode node, Clutter.PaintContext paint_context, Clutter.EffectPaintFlags flags) { + /* Failing to create or update the offscreen framebuffers prevents + * the entire effect to be applied. + */ + if (!update_framebuffers (paint_context)) { + add_actor_node (node); + return; + } + + add_actor_node (create_round_nodes (node)); } } diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index c1f2f91dd..15cecc215 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -129,6 +129,7 @@ public class Gala.WindowClone : ActorTarget, RootTarget { active_shape = new ActiveShape (); active_shape.opacity = 0; + bind_property ("monitor-scale", active_shape, "monitor-scale", SYNC_CREATE); clone_container = new Clutter.Actor () { pivot_point = { 0.5f, 0.5f } @@ -624,8 +625,17 @@ public class Gala.WindowClone : ActorTarget, RootTarget { private const int BORDER_RADIUS = 16; private const double COLOR_OPACITY = 0.8; + public float monitor_scale { + set { + rounded_corners_effect.monitor_scale = value; + } + } + + private RoundedCornersEffect rounded_corners_effect; + construct { - add_effect (new RoundedCornersEffect (BORDER_RADIUS, 1.0f)); + rounded_corners_effect = new RoundedCornersEffect (BORDER_RADIUS, 1.0f); + add_effect (rounded_corners_effect); } public void update_color () { diff --git a/src/Widgets/WindowSwitcher/WindowSwitcher.vala b/src/Widgets/WindowSwitcher/WindowSwitcher.vala index f084b1d8b..808b23fae 100644 --- a/src/Widgets/WindowSwitcher/WindowSwitcher.vala +++ b/src/Widgets/WindowSwitcher/WindowSwitcher.vala @@ -127,6 +127,10 @@ public class Gala.WindowSwitcher : CanvasActor, GestureTarget, RootTarget { var margin = Utils.scale_to_int (WRAPPER_PADDING, scaling_factor); + foreach (unowned var icon in (GLib.List) container.get_children ()) { + icon.scale_factor = scaling_factor; + } + container.margin_left = margin; container.margin_right = margin; container.margin_bottom = margin;