From 3d1182f0d87ab61cd88cd62eb0e042525d3eddf9 Mon Sep 17 00:00:00 2001 From: lenemter Date: Thu, 19 Feb 2026 20:37:36 +0300 Subject: [PATCH 1/4] Introduce Gala.SimpleShaderEffect --- lib/SimpleShaderEffect.vala | 142 ++++++++++++++++++++++++++++++++++++ lib/meson.build | 1 + 2 files changed, 143 insertions(+) create mode 100644 lib/SimpleShaderEffect.vala diff --git a/lib/SimpleShaderEffect.vala b/lib/SimpleShaderEffect.vala new file mode 100644 index 000000000..accf7f8f6 --- /dev/null +++ b/lib/SimpleShaderEffect.vala @@ -0,0 +1,142 @@ +/* + * Copyright 2026 elementary, Inc. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * {@link Clutter.Effect} implementation to apply shaders quickly and easily. + * The main difference between Gala.SimpleShaderEffect and Clutter.ShaderEffect is that + * we don't use {@link Clutter.OffscreenEffect} that enlarges the texture for fractional scaling which + * can produce some graphical glitches. + */ +public abstract class Gala.SimpleShaderEffect : Clutter.Effect { + /** + * Fallback shader that outputs the original content. + */ + public const string FALLBACK_SHADER = "uniform sampler2D tex; void main () { cogl_color_out = texture2D (tex, cogl_tex_coord0_in.xy); }"; + + private Cogl.Program program; + private Cogl.Pipeline pipeline; + private Cogl.Framebuffer framebuffer; + private Cogl.Texture texture; + private int texture_width; + private int texture_height; + + protected SimpleShaderEffect (string shader_source) { + unowned var ctx = Clutter.get_default_backend ().get_cogl_context (); + + var shader = Cogl.Shader.create (FRAGMENT); + shader.source (shader_source); + + program = Cogl.Program.create (); + program.attach_shader (shader); + program.link (); + + pipeline = new Cogl.Pipeline (ctx); + pipeline.set_user_program (program); + } + + private bool update_framebuffer () { + var actor_box = actor.get_allocation_box (); + var new_width = (int) actor_box.get_width (); + var new_height = (int) actor_box.get_height (); + + if (new_width <= 0 || new_height <= 0) { + warning ("SimpleShaderEffect: Couldn't update framebuffers, incorrect size"); + return false; + } + + if (texture_width == new_width && texture_height == new_height && framebuffer != null) { + return true; + } + + unowned var ctx = Clutter.get_default_backend ().get_cogl_context (); + +#if HAS_MUTTER46 + texture = new Cogl.Texture2D.with_size (ctx, new_width, new_height); +#else + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, new_width, new_height); + try { + texture = new Cogl.Texture2D.from_data (ctx, new_width, new_height, Cogl.PixelFormat.BGRA_8888_PRE, surface.get_stride (), surface.get_data ()); + } catch (Error e) { + critical ("SimpleShaderEffect: Couldn't create texture: %s", e.message); + return false; + } +#endif + + pipeline.set_layer_texture (0, texture); + framebuffer = new Cogl.Offscreen.with_texture (texture); + + Graphene.Matrix projection = {}; + projection.init_translate ({ -new_width / 2.0f, -new_height / 2.0f, 0.0f }); + projection.scale (2.0f / new_width, -2.0f / new_height, 1.0f); + + framebuffer.set_projection_matrix (projection); + + texture_width = new_width; + texture_height = new_height; + + return true; + } + + public override void paint_node (Clutter.PaintNode node, Clutter.PaintContext paint_context, Clutter.EffectPaintFlags flags) { + var actor_node = new Clutter.ActorNode (actor, 255); + + if (BYPASS_EFFECT in flags || !update_framebuffer ()) { + node.add_child (actor_node); + return; + } + + var layer_node = new Clutter.LayerNode.to_framebuffer (framebuffer, pipeline); + layer_node.add_rectangle ({0, 0, texture_width, texture_height}); + layer_node.add_child (actor_node); + + node.add_child (layer_node); + } + + private bool get_and_validate_uniform_location (string uniform, out int uniform_location) { + uniform_location = program.get_uniform_location (uniform); + + if (uniform_location == -1) { + warning ("Can't update uniform '%s'", uniform); + return false; + } + + return true; + } + + protected void set_uniform_1f (string uniform, float value) { + int uniform_location; + if (get_and_validate_uniform_location (uniform, out uniform_location)) { + program.set_uniform_1f (uniform_location, value); + } + } + + protected void set_uniform_1i (string uniform, int value) { + int uniform_location; + if (get_and_validate_uniform_location (uniform, out uniform_location)) { + program.set_uniform_1i (uniform_location, value); + } + } + + protected void set_uniform_float (string uniform, int n_components, float[] value) { + int uniform_location; + if (get_and_validate_uniform_location (uniform, out uniform_location)) { + program.set_uniform_float (uniform_location, n_components, value); + } + } + + protected void set_uniform_int (string uniform, int n_components, int[] value) { + int uniform_location; + if (get_and_validate_uniform_location (uniform, out uniform_location)) { + program.set_uniform_int (uniform_location, n_components, value); + } + } + + protected void set_uniform_matrix (string uniform, int dimensions, bool transpose, float[] value) { + int uniform_location; + if (get_and_validate_uniform_location (uniform, out uniform_location)) { + program.set_uniform_matrix (uniform_location, dimensions, transpose, value); + } + } +} diff --git a/lib/meson.build b/lib/meson.build index 737db46f1..8b1703ae7 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -22,6 +22,7 @@ gala_lib_sources = files( 'Plugin.vala', 'RoundedCornersEffect.vala', 'ShadowEffect.vala', + 'SimpleShaderEffect.vala', 'Text.vala', 'Utils.vala', 'WindowIcon.vala', From 31a3ec83a75bdd6890113231e835cef1f50b585b Mon Sep 17 00:00:00 2001 From: lenemter Date: Fri, 20 Feb 2026 19:02:59 +0300 Subject: [PATCH 2/4] MonochromeEffect: use SimpleShaderEffect --- src/ColorFilters/MonochromeEffect.vala | 29 +++++++++++--------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ColorFilters/MonochromeEffect.vala b/src/ColorFilters/MonochromeEffect.vala index c5b8034d7..87b1f0b25 100644 --- a/src/ColorFilters/MonochromeEffect.vala +++ b/src/ColorFilters/MonochromeEffect.vala @@ -1,23 +1,23 @@ /* - * Copyright 2023 elementary, Inc. + * Copyright 2023-2026 elementary, Inc. * SPDX-License-Identifier: GPL-3.0-or-later */ -public class Gala.MonochromeEffect : Clutter.ShaderEffect { +public class Gala.MonochromeEffect : SimpleShaderEffect { public const string EFFECT_NAME = "monochrome-filter"; private double _strength; public double strength { get { return _strength; } - construct set { + set { _strength = value; - set_uniform_value ("STRENGTH", value); + set_uniform_1f ("STRENGTH", (float) value); queue_repaint (); } } public bool pause_for_screenshot { set { - set_uniform_value ("PAUSE_FOR_SCREENSHOT", (int) value); + set_uniform_1i ("PAUSE_FOR_SCREENSHOT", (int) value); queue_repaint (); } } @@ -28,22 +28,17 @@ public class Gala.MonochromeEffect : Clutter.ShaderEffect { public Clutter.Actor? transition_actor { get; set; default = null; } public MonochromeEffect (double strength) { - Object ( -#if HAS_MUTTER48 - shader_type: Cogl.ShaderType.FRAGMENT, -#else - shader_type: Clutter.ShaderType.FRAGMENT_SHADER, -#endif - strength: strength - ); - + string shader_source; try { - var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/monochrome.frag", GLib.ResourceLookupFlags.NONE); - set_shader_source ((string) bytes.get_data ()); + var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/monochrome.frag", NONE); + shader_source = (string) bytes.get_data (); } catch (Error e) { - critical ("Unable to load monochrome.frag: %s", e.message); + warning ("Unable to load monochrome.frag: %s", e.message); + shader_source = FALLBACK_SHADER; } + base (shader_source); + this.strength = strength; pause_for_screenshot = false; } } From 6f27e19d08c2e696c3f058a590a5cc1d3ff872d8 Mon Sep 17 00:00:00 2001 From: lenemter Date: Fri, 20 Feb 2026 19:34:34 +0300 Subject: [PATCH 3/4] ColorblindnessCorrectionEffect: use SimpleShaderEffect --- .../ColorblindnessCorrectionEffect.vala | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ColorFilters/ColorblindnessCorrectionEffect.vala b/src/ColorFilters/ColorblindnessCorrectionEffect.vala index 4cb5416f4..f589ce9dc 100644 --- a/src/ColorFilters/ColorblindnessCorrectionEffect.vala +++ b/src/ColorFilters/ColorblindnessCorrectionEffect.vala @@ -1,9 +1,9 @@ /* - * Copyright 2023 elementary, Inc. + * Copyright 2023-2026 elementary, Inc. * SPDX-License-Identifier: GPL-3.0-or-later */ -public class Gala.ColorblindnessCorrectionEffect : Clutter.ShaderEffect { +public class Gala.ColorblindnessCorrectionEffect : SimpleShaderEffect { public const string EFFECT_NAME = "colorblindness-correction-filter"; private int _mode; @@ -11,7 +11,7 @@ public class Gala.ColorblindnessCorrectionEffect : Clutter.ShaderEffect { get { return _mode; } construct set { _mode = value; - set_uniform_value ("COLORBLIND_MODE", _mode); + set_uniform_1i ("COLORBLIND_MODE", _mode); } } private double _strength; @@ -19,13 +19,13 @@ public class Gala.ColorblindnessCorrectionEffect : Clutter.ShaderEffect { get { return _strength; } construct set { _strength = value; - set_uniform_value ("STRENGTH", value); + set_uniform_1f ("STRENGTH", (float) value); queue_repaint (); } } public bool pause_for_screenshot { set { - set_uniform_value ("PAUSE_FOR_SCREENSHOT", (int) value); + set_uniform_1i ("PAUSE_FOR_SCREENSHOT", (int) value); queue_repaint (); } } @@ -36,23 +36,18 @@ public class Gala.ColorblindnessCorrectionEffect : Clutter.ShaderEffect { public Clutter.Actor? transition_actor { get; set; default = null; } public ColorblindnessCorrectionEffect (int mode, double strength) { - Object ( -#if HAS_MUTTER48 - shader_type: Cogl.ShaderType.FRAGMENT, -#else - shader_type: Clutter.ShaderType.FRAGMENT_SHADER, -#endif - mode: mode, - strength: strength - ); - + string shader_source; try { var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/colorblindness-correction.frag", GLib.ResourceLookupFlags.NONE); - set_shader_source ((string) bytes.get_data ()); + shader_source = (string) bytes.get_data (); } catch (Error e) { - critical ("Unable to load colorblindness-correction.frag: %s", e.message); + warning ("Unable to load colorblindness-correction.frag: %s", e.message); + shader_source = FALLBACK_SHADER; } + base (shader_source); + this.mode = mode; + this.strength = strength; pause_for_screenshot = false; } } From 57e1e2170bd63f6ab95cdfe4b05881a7cac8ac48 Mon Sep 17 00:00:00 2001 From: lenemter Date: Fri, 20 Feb 2026 19:37:03 +0300 Subject: [PATCH 4/4] Update metainfo --- data/gala.metainfo.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/data/gala.metainfo.xml.in b/data/gala.metainfo.xml.in index d5763d941..359d4af3d 100644 --- a/data/gala.metainfo.xml.in +++ b/data/gala.metainfo.xml.in @@ -36,6 +36,7 @@ Pinch gestures are switching between workspaces instead of zooming + Dock background blinks when Greyscale filter is enabled