|
3 | 3 | * SPDX-License-Identifier: LGPL-3.0-or-later |
4 | 4 | */ |
5 | 5 |
|
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; } |
8 | 9 |
|
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); |
13 | 17 | } |
14 | 18 |
|
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; |
22 | 36 |
|
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)); |
25 | 75 | } |
26 | | - } |
27 | | - } |
| 76 | + """, |
| 77 | + null |
| 78 | + ); |
| 79 | + snippet.set_replace ( |
| 80 | + """ |
| 81 | + vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy); |
28 | 82 |
|
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 (); |
33 | 99 | #else |
34 | | - shader_type: Clutter.ShaderType.FRAGMENT_SHADER, |
| 100 | + unowned var cogl_context = Clutter.get_default_backend ().get_cogl_context (); |
35 | 101 | #endif |
36 | | - clip_radius: clip_radius, |
37 | | - monitor_scale: monitor_scale |
38 | | - ); |
39 | | - } |
40 | 102 |
|
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; |
48 | 113 | } |
49 | 114 |
|
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); |
55 | 118 |
|
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); |
57 | 122 |
|
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); |
61 | 128 |
|
62 | | - update_actor_size (); |
63 | | - } |
| 129 | + base.paint_target (node, paint_context); |
64 | 130 | } |
65 | 131 |
|
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); |
71 | 151 |
|
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; |
75 | 154 | } |
76 | 155 | } |
0 commit comments