1#include "gtkshaderbin.h"
2
3typedef struct {
4 GskGLShader *shader;
5 GtkStateFlags state;
6 GtkStateFlags state_mask;
7 float extra_border;
8 gboolean compiled;
9 gboolean compiled_ok;
10} ShaderInfo;
11
12struct _GtkShaderBin
13{
14 GtkWidget parent_instance;
15 GtkWidget *child;
16 ShaderInfo *active_shader;
17 GPtrArray *shaders;
18 guint tick_id;
19 float time;
20 float mouse_x, mouse_y;
21 gint64 first_frame_time;
22};
23
24struct _GtkShaderBinClass
25{
26 GtkWidgetClass parent_class;
27};
28
29G_DEFINE_TYPE (GtkShaderBin, gtk_shader_bin, GTK_TYPE_WIDGET)
30
31static void
32shader_info_free (ShaderInfo *info)
33{
34 g_object_unref (object: info->shader);
35 g_free (mem: info);
36}
37
38static void
39gtk_shader_bin_finalize (GObject *object)
40{
41 GtkShaderBin *self = GTK_SHADER_BIN (ptr: object);
42
43 g_ptr_array_free (array: self->shaders, TRUE);
44
45 G_OBJECT_CLASS (gtk_shader_bin_parent_class)->finalize (object);
46}
47
48static void
49gtk_shader_bin_dispose (GObject *object)
50{
51 GtkShaderBin *self = GTK_SHADER_BIN (ptr: object);
52
53 g_clear_pointer (&self->child, gtk_widget_unparent);
54
55 G_OBJECT_CLASS (gtk_shader_bin_parent_class)->dispose (object);
56}
57
58static gboolean
59gtk_shader_bin_tick (GtkWidget *widget,
60 GdkFrameClock *frame_clock,
61 gpointer unused)
62{
63 GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget);
64 gint64 frame_time;
65
66 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
67 if (self->first_frame_time == 0)
68 self->first_frame_time = frame_time;
69 self->time = (frame_time - self->first_frame_time) / (float)G_USEC_PER_SEC;
70
71 gtk_widget_queue_draw (widget);
72
73 return G_SOURCE_CONTINUE;
74}
75
76static void
77motion_cb (GtkEventControllerMotion *controller,
78 double x,
79 double y,
80 GtkShaderBin *self)
81{
82 self->mouse_x = x;
83 self->mouse_y = y;
84}
85
86static void
87gtk_shader_bin_init (GtkShaderBin *self)
88{
89 GtkEventController *controller;
90 self->shaders = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)shader_info_free);
91
92 controller = gtk_event_controller_motion_new ();
93 g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
94 gtk_widget_add_controller (GTK_WIDGET (self), controller);
95}
96
97void
98gtk_shader_bin_update_active_shader (GtkShaderBin *self)
99{
100 GtkStateFlags new_state = gtk_widget_get_state_flags (GTK_WIDGET (self));
101 ShaderInfo *new_shader = NULL;
102
103 for (int i = 0; i < self->shaders->len; i++)
104 {
105 ShaderInfo *info = g_ptr_array_index (self->shaders, i);
106
107 if ((info->state_mask & new_state) == info->state)
108 {
109 new_shader = info;
110 break;
111 }
112 }
113
114 if (self->active_shader == new_shader)
115 return;
116
117 self->active_shader = new_shader;
118 self->first_frame_time = 0;
119
120 if (self->active_shader)
121 {
122 if (self->tick_id == 0)
123 self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
124 callback: gtk_shader_bin_tick,
125 NULL, NULL);
126 }
127 else
128 {
129 if (self->tick_id != 0)
130 {
131 gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: self->tick_id);
132 self->tick_id = 0;
133 }
134 }
135
136 gtk_widget_queue_draw (GTK_WIDGET (self));
137}
138
139static void
140gtk_shader_bin_state_flags_changed (GtkWidget *widget,
141 GtkStateFlags previous_state_flags)
142{
143 GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget);
144
145 gtk_shader_bin_update_active_shader (self);
146}
147
148void
149gtk_shader_bin_add_shader (GtkShaderBin *self,
150 GskGLShader *shader,
151 GtkStateFlags state,
152 GtkStateFlags state_mask,
153 float extra_border)
154{
155 ShaderInfo *info = g_new0 (ShaderInfo, 1);
156 info->shader = g_object_ref (shader);
157 info->state = state;
158 info->state_mask = state_mask;
159 info->extra_border = extra_border;
160
161 g_ptr_array_add (array: self->shaders, data: info);
162
163 gtk_shader_bin_update_active_shader (self);
164}
165
166void
167gtk_shader_bin_set_child (GtkShaderBin *self,
168 GtkWidget *child)
169{
170
171 if (self->child == child)
172 return;
173
174 g_clear_pointer (&self->child, gtk_widget_unparent);
175
176 if (child)
177 {
178 self->child = child;
179 gtk_widget_set_parent (widget: child, GTK_WIDGET (self));
180 }
181}
182
183GtkWidget *
184gtk_shader_bin_get_child (GtkShaderBin *self)
185{
186 return self->child;
187}
188
189static void
190gtk_shader_bin_snapshot (GtkWidget *widget,
191 GtkSnapshot *snapshot)
192{
193 GtkShaderBin *self = GTK_SHADER_BIN (ptr: widget);
194 int width, height;
195
196 width = gtk_widget_get_width (widget);
197 height = gtk_widget_get_height (widget);
198
199 if (self->active_shader)
200 {
201 if (!self->active_shader->compiled)
202 {
203 GtkNative *native = gtk_widget_get_native (widget);
204 GskRenderer *renderer = gtk_native_get_renderer (self: native);
205 GError *error = NULL;
206
207 self->active_shader->compiled = TRUE;
208 self->active_shader->compiled_ok =
209 gsk_gl_shader_compile (shader: self->active_shader->shader,
210 renderer, error: &error);
211 if (!self->active_shader->compiled_ok)
212 {
213 g_warning ("GtkShaderBin failed to compile shader: %s", error->message);
214 g_error_free (error);
215 }
216 }
217
218 if (self->active_shader->compiled_ok)
219 {
220 float border = self->active_shader->extra_border;
221 graphene_vec2_t mouse;
222 graphene_vec2_init (v: &mouse, x: self->mouse_x + border, y: self->mouse_y + border);
223 gtk_snapshot_push_gl_shader (snapshot, shader: self->active_shader->shader,
224 bounds: &GRAPHENE_RECT_INIT(-border, -border, width+2*border, height+2*border),
225 take_args: gsk_gl_shader_format_args (shader: self->active_shader->shader,
226 "u_time", self->time,
227 "u_mouse", &mouse,
228 NULL));
229 gtk_widget_snapshot_child (widget, child: self->child, snapshot);
230 gtk_snapshot_gl_shader_pop_texture (snapshot);
231 gtk_snapshot_pop (snapshot);
232
233 return;
234 }
235 }
236
237 /* Non-shader fallback */
238 gtk_widget_snapshot_child (widget, child: self->child, snapshot);
239}
240
241static void
242gtk_shader_bin_class_init (GtkShaderBinClass *class)
243{
244 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
245 GObjectClass *object_class = G_OBJECT_CLASS (class);
246
247 object_class->finalize = gtk_shader_bin_finalize;
248 object_class->dispose = gtk_shader_bin_dispose;
249
250 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
251
252 widget_class->snapshot = gtk_shader_bin_snapshot;
253 widget_class->state_flags_changed = gtk_shader_bin_state_flags_changed;
254}
255
256GtkWidget *
257gtk_shader_bin_new (void)
258{
259 GtkShaderBin *self;
260
261 self = g_object_new (GTK_TYPE_SHADER_BIN, NULL);
262
263 return GTK_WIDGET (self);
264}
265

source code of gtk/demos/gtk-demo/gtkshaderbin.c