Skip to content

Commit e5c27db

Browse files
committed
Rewrite RoundedCornersEffect
1 parent 3005f2d commit e5c27db

File tree

2 files changed

+132
-52
lines changed

2 files changed

+132
-52
lines changed

lib/RoundedCornersEffect.vala

Lines changed: 131 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,153 @@
33
* SPDX-License-Identifier: LGPL-3.0-or-later
44
*/
55

6-
public class Gala.RoundedCornersEffect : Clutter.ShaderEffect {
7-
private const int CLIP_RADIUS_OFFSET = 3;
6+
public class Gala.RoundedCornersEffect : Clutter.OffscreenEffect {
7+
public float clip_radius { get; construct; }
8+
public float monitor_scale { get; construct set; }
89

9-
public float clip_radius {
10-
construct set {
11-
set_uniform_value ("clip_radius", value + CLIP_RADIUS_OFFSET);
12-
}
10+
private int offset_location;
11+
private int actor_size_location;
12+
private int full_texture_size_location;
13+
private int clip_radius_location;
14+
15+
public RoundedCornersEffect (float clip_radius, float monitor_scale) {
16+
Object (clip_radius: clip_radius, monitor_scale: monitor_scale);
1317
}
1418

15-
private float _monitor_scale = 1.0f;
16-
public float monitor_scale {
17-
get {
18-
return _monitor_scale;
19-
}
20-
construct set {
21-
_monitor_scale = value;
19+
construct {
20+
notify["monitor-scale"].connect (queue_repaint);
21+
}
22+
23+
public override Cogl.Pipeline create_pipeline (Cogl.Texture texture) {
24+
var snippet = new Cogl.Snippet (
25+
Cogl.SnippetHook.FRAGMENT,
26+
"""
27+
uniform sampler2D tex;
28+
uniform vec2 offset;
29+
uniform vec2 actor_size;
30+
uniform vec2 full_texture_size;
31+
uniform float clip_radius;
32+
33+
float rounded_rect_coverage (vec2 p) {
34+
float center_left = clip_radius;
35+
float center_right = actor_size.x - clip_radius;
2236
23-
if (actor != null) {
24-
update_actor_size ();
37+
float center_x;
38+
if (p.x < center_left) {
39+
center_x = center_left;
40+
} else if (p.x > center_right) {
41+
center_x = center_right;
42+
} else {
43+
return 1.0;
44+
}
45+
46+
float center_top = clip_radius;
47+
float center_bottom = actor_size.y - clip_radius;
48+
49+
float center_y;
50+
if (p.y < center_top) {
51+
center_y = center_top;
52+
} else if (p.y > center_bottom) {
53+
center_y = center_bottom;
54+
} else {
55+
return 1.0;
56+
}
57+
58+
vec2 delta = p - vec2 (center_x, center_y);
59+
float dist_squared = dot (delta, delta);
60+
61+
// Fully outside the circle
62+
float outer_radius = clip_radius + 0.5;
63+
if (dist_squared >= (outer_radius * outer_radius)) {
64+
return 0.0;
65+
}
66+
67+
// Fully inside the circle
68+
float inner_radius = clip_radius - 0.5;
69+
if (dist_squared <= (inner_radius * inner_radius)) {
70+
return 1.0;
71+
}
72+
73+
// Only pixels on the edge of the curve need expensive antialiasing
74+
return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
2575
}
26-
}
27-
}
76+
""",
77+
null
78+
);
79+
snippet.set_replace (
80+
"""
81+
vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy);
2882
29-
public RoundedCornersEffect (float clip_radius, float monitor_scale) {
30-
Object (
31-
#if HAS_MUTTER48
32-
shader_type: Cogl.ShaderType.FRAGMENT,
83+
vec2 texture_coord = cogl_tex_coord0_in.xy * full_texture_size;
84+
if (texture_coord.x < offset.x || texture_coord.x > offset.x + actor_size.x ||
85+
texture_coord.y < offset.y || texture_coord.y > offset.y + actor_size.y
86+
) {
87+
cogl_color_out = sample * cogl_color_in;
88+
return;
89+
}
90+
91+
texture_coord.x -= offset.x;
92+
texture_coord.y -= offset.y;
93+
cogl_color_out = sample * cogl_color_in * rounded_rect_coverage (texture_coord);
94+
"""
95+
);
96+
97+
#if HAS_MUTTER47
98+
unowned var cogl_context = actor.context.get_backend ().get_cogl_context ();
3399
#else
34-
shader_type: Clutter.ShaderType.FRAGMENT_SHADER,
100+
unowned var cogl_context = Clutter.get_default_backend ().get_cogl_context ();
35101
#endif
36-
clip_radius: clip_radius,
37-
monitor_scale: monitor_scale
38-
);
39-
}
40102

41-
construct {
42-
try {
43-
var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/rounded-corners.vert", GLib.ResourceLookupFlags.NONE);
44-
set_shader_source ((string) bytes.get_data ());
45-
} catch (Error e) {
46-
critical ("Unable to load rounded-corners.vert: %s", e.message);
47-
}
103+
var pipeline = new Cogl.Pipeline (cogl_context);
104+
pipeline.set_layer_texture (0, texture);
105+
pipeline.add_snippet (snippet);
106+
107+
offset_location = pipeline.get_uniform_location ("offset");
108+
actor_size_location = pipeline.get_uniform_location ("actor_size");
109+
full_texture_size_location = pipeline.get_uniform_location ("full_texture_size");
110+
clip_radius_location = pipeline.get_uniform_location ("clip_radius");
111+
112+
return pipeline;
48113
}
49114

50-
public override void set_actor (Clutter.Actor? new_actor) {
51-
if (actor != null) {
52-
actor.notify["width"].disconnect (update_actor_size);
53-
actor.notify["height"].disconnect (update_actor_size);
54-
}
115+
public override void paint_target (Clutter.PaintNode node, Clutter.PaintContext paint_context) {
116+
float texture_width, texture_height;
117+
get_target_size (out texture_width, out texture_height);
55118

56-
base.set_actor (new_actor);
119+
var actor_box = actor.get_allocation_box ();
120+
var effect_box = actor_box.copy ();
121+
clutter_actor_box_enlarge_for_effects (ref effect_box);
57122

58-
if (actor != null) {
59-
actor.notify["width"].connect (update_actor_size);
60-
actor.notify["height"].connect (update_actor_size);
123+
unowned var pipeline = get_pipeline ();
124+
pipeline.set_uniform_float (offset_location, 2, 1, { actor_box.x1 - effect_box.x1, actor_box.y1 - effect_box.y1 });
125+
pipeline.set_uniform_float (actor_size_location, 2, 1, { actor.width, actor.height });
126+
pipeline.set_uniform_float (full_texture_size_location, 2, 1, { texture_width, texture_height });
127+
pipeline.set_uniform_1f (clip_radius_location, clip_radius * monitor_scale);
61128

62-
update_actor_size ();
63-
}
129+
base.paint_target (node, paint_context);
64130
}
65131

66-
private void update_actor_size () requires (actor != null) {
67-
float[] actor_size = {
68-
actor.width * monitor_scale,
69-
actor.height * monitor_scale
70-
};
132+
/**
133+
* This is the same as mutter's private _clutter_actor_box_enlarge_for_effects function
134+
* Mutter basically enlarges the texture a bit to "determine a stable quantized size in pixels
135+
* that doesn't vary due to the original box's sub-pixel position."
136+
*
137+
* We need to account for this in our shader code so this function is reimplemented here.
138+
*/
139+
private void clutter_actor_box_enlarge_for_effects (ref Clutter.ActorBox box) {
140+
if (box.get_area () == 0.0) {
141+
return;
142+
}
143+
144+
var width = box.x2 - box.x1;
145+
var height = box.y2 - box.y1;
146+
width = Math.nearbyintf (width);
147+
height = Math.nearbyintf (height);
148+
149+
box.x2 = Math.ceilf (box.x2 + 0.75f);
150+
box.y2 = Math.ceilf (box.y2 + 0.75f);
71151

72-
var actor_size_value = GLib.Value (typeof (Clutter.ShaderFloat));
73-
Clutter.Value.set_shader_float (actor_size_value, actor_size);
74-
set_uniform_value ("actor_size", actor_size_value);
152+
box.x1 = box.x2 - width - 3;
153+
box.y1 = box.y2 - height - 3;
75154
}
76155
}

vapi/mutter-clutter.vapi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6690,6 +6690,7 @@ namespace Clutter {
66906690
#else
66916691
public virtual Cogl.Handle create_texture (float width, float height);
66926692
#endif
6693+
public virtual Cogl.Pipeline create_pipeline (Cogl.Texture texture);
66936694
public unowned Cogl.Pipeline? get_pipeline ();
66946695
public bool get_target_size (out float width, out float height);
66956696
#if HAS_MUTTER46

0 commit comments

Comments
 (0)