Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/gala.metainfo.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
</description>
<issues>
<issue url="https://github.com/elementary/gala/issues/2767">Pinch gestures are switching between workspaces instead of zooming</issue>
<issue url="https://github.com/elementary/dock/issues/530">Dock background blinks when Greyscale filter is enabled</issue>
</issues>
</release>

Expand Down
142 changes: 142 additions & 0 deletions lib/SimpleShaderEffect.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2026 elementary, Inc. <https://elementary.io>
* 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ig there must be a reason why they do this could you maybe explain why we don't need it in this case?

* 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);
}
}
}
1 change: 1 addition & 0 deletions lib/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ gala_lib_sources = files(
'Plugin.vala',
'RoundedCornersEffect.vala',
'ShadowEffect.vala',
'SimpleShaderEffect.vala',
'Text.vala',
'Utils.vala',
'WindowIcon.vala',
Expand Down
29 changes: 12 additions & 17 deletions src/ColorFilters/ColorblindnessCorrectionEffect.vala
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
/*
* Copyright 2023 elementary, Inc. <https://elementary.io>
* Copyright 2023-2026 elementary, Inc. <https://elementary.io>
* 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;
public int mode {
get { return _mode; }
construct set {
_mode = value;
set_uniform_value ("COLORBLIND_MODE", _mode);
set_uniform_1i ("COLORBLIND_MODE", _mode);
}
}
private double _strength;
public double strength {
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 ();
}
}
Expand All @@ -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;
}
}
29 changes: 12 additions & 17 deletions src/ColorFilters/MonochromeEffect.vala
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
/*
* Copyright 2023 elementary, Inc. <https://elementary.io>
* Copyright 2023-2026 elementary, Inc. <https://elementary.io>
* 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 ();
}
}
Expand All @@ -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;
}
}
Loading