1/*
2 * Copyright © 2020 Red Hat, Inc
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Matthias Clasen <mclasen@redhat.com>
18 */
19
20#include "config.h"
21
22#include <gtk/gtk.h>
23#include "gskshaderpaintable.h"
24
25/**
26 * GskShaderPaintable:
27 *
28 * `GskShaderPaintable` is an implementation of the `GdkPaintable` interface
29 * that uses a `GskGLShader` to create pixels.
30 *
31 * You can set the uniform data that the shader needs for rendering
32 * using gsk_shader_paintable_set_args(). This function can
33 * be called repeatedly to change the uniform data for the next
34 * snapshot.
35 *
36 * Commonly, time is passed to shaders as a float uniform containing
37 * the elapsed time in seconds. The convenience API
38 * gsk_shader_paintable_update_time() can be called from a `GtkTickCallback`
39 * to update the time based on the frame time of the frame clock.
40 */
41
42
43struct _GskShaderPaintable
44{
45 GObject parent_instance;
46
47 GskGLShader *shader;
48 GBytes *args;
49
50 gint64 start_time;
51};
52
53struct _GskShaderPaintableClass
54{
55 GObjectClass parent_class;
56};
57
58enum {
59 PROP_0,
60 PROP_SHADER,
61 PROP_ARGS,
62
63 N_PROPS,
64};
65
66static GParamSpec *properties[N_PROPS] = { NULL, };
67
68static void
69gsk_shader_paintable_paintable_snapshot (GdkPaintable *paintable,
70 GdkSnapshot *snapshot,
71 double width,
72 double height)
73{
74 GskShaderPaintable *self = GSK_SHADER_PAINTABLE (ptr: paintable);
75
76 gtk_snapshot_push_gl_shader (snapshot, shader: self->shader, bounds: &GRAPHENE_RECT_INIT(0, 0, width, height), take_args: g_bytes_ref (bytes: self->args));
77 gtk_snapshot_pop (snapshot);
78}
79
80static void
81gsk_shader_paintable_paintable_init (GdkPaintableInterface *iface)
82{
83 iface->snapshot = gsk_shader_paintable_paintable_snapshot;
84}
85
86G_DEFINE_TYPE_EXTENDED (GskShaderPaintable, gsk_shader_paintable, G_TYPE_OBJECT, 0,
87 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
88 gsk_shader_paintable_paintable_init))
89
90static void
91gsk_shader_paintable_set_property (GObject *object,
92 guint prop_id,
93 const GValue *value,
94 GParamSpec *pspec)
95
96{
97 GskShaderPaintable *self = GSK_SHADER_PAINTABLE (ptr: object);
98
99 switch (prop_id)
100 {
101 case PROP_SHADER:
102 gsk_shader_paintable_set_shader (self, shader: g_value_get_object (value));
103 break;
104
105 case PROP_ARGS:
106 gsk_shader_paintable_set_args (self, data: g_value_get_boxed (value));
107 break;
108
109 default:
110 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
111 break;
112 }
113}
114
115static void
116gsk_shader_paintable_get_property (GObject *object,
117 guint prop_id,
118 GValue *value,
119 GParamSpec *pspec)
120{
121 GskShaderPaintable *self = GSK_SHADER_PAINTABLE (ptr: object);
122
123 switch (prop_id)
124 {
125 case PROP_SHADER:
126 g_value_set_object (value, v_object: self->shader);
127 break;
128
129 case PROP_ARGS:
130 g_value_set_boxed (value, v_boxed: self->args);
131 break;
132
133 default:
134 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
135 break;
136 }
137}
138
139static void
140gsk_shader_paintable_finalize (GObject *object)
141{
142 GskShaderPaintable *self = GSK_SHADER_PAINTABLE (ptr: object);
143
144 g_clear_pointer (&self->args, g_bytes_unref);
145 g_clear_object (&self->shader);
146
147 G_OBJECT_CLASS (gsk_shader_paintable_parent_class)->finalize (object);
148}
149
150static void
151gsk_shader_paintable_class_init (GskShaderPaintableClass *klass)
152{
153 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
154
155 gobject_class->get_property = gsk_shader_paintable_get_property;
156 gobject_class->set_property = gsk_shader_paintable_set_property;
157 gobject_class->finalize = gsk_shader_paintable_finalize;
158
159 properties[PROP_SHADER] =
160 g_param_spec_object (name: "shader", nick: "Shader", blurb: "The shader",
161 GSK_TYPE_GL_SHADER,
162 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
163
164 properties[PROP_ARGS] =
165 g_param_spec_boxed (name: "args", nick: "Arguments", blurb: "The uniform arguments",
166 G_TYPE_BYTES,
167 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
168
169 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
170}
171
172static void
173gsk_shader_paintable_init (GskShaderPaintable *self)
174{
175}
176
177/**
178 * gsk_shader_paintable_new:
179 * @shader: (transfer full) (nullable): the shader to use
180 * @data: (transfer full) (nullable): uniform data
181 *
182 * Creates a paintable that uses the @shader to create
183 * pixels. The shader must not require input textures.
184 * If @data is %NULL, all uniform values are set to zero.
185 *
186 * Returns: (transfer full): a new `GskShaderPaintable`
187 */
188GdkPaintable *
189gsk_shader_paintable_new (GskGLShader *shader,
190 GBytes *data)
191{
192 GdkPaintable *ret;
193
194 g_return_val_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader), NULL);
195
196 if (shader && !data)
197 {
198 int size = gsk_gl_shader_get_args_size (shader);
199 data = g_bytes_new_take (g_new0 (guchar, size), size);
200 }
201
202 ret = g_object_new (GSK_TYPE_SHADER_PAINTABLE,
203 first_property_name: "shader", shader,
204 "args", data,
205 NULL);
206
207 g_clear_object (&shader);
208 g_clear_pointer (&data, g_bytes_unref);
209
210 return ret;
211}
212
213/**
214 * gsk_shader_paintable_set_shader:
215 * @self: a `GskShaderPaintable`
216 * @shader: the `GskGLShader` to use
217 *
218 * Sets the shader that the paintable will use
219 * to create pixels. The shader must not require
220 * input textures.
221 */
222void
223gsk_shader_paintable_set_shader (GskShaderPaintable *self,
224 GskGLShader *shader)
225{
226 g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self));
227 g_return_if_fail (shader == NULL || GSK_IS_GL_SHADER (shader));
228 g_return_if_fail (shader == NULL || gsk_gl_shader_get_n_textures (shader) == 0);
229
230 if (!g_set_object (&self->shader, shader))
231 return;
232
233 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SHADER]);
234 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: self));
235
236 g_clear_pointer (&self->args, g_bytes_unref);
237}
238
239/**
240 * gsk_shader_paintable_get_shader:
241 * @self: a `GskShaderPaintable`
242 *
243 * Returns the shader that the paintable is using.
244 *
245 * Returns: (transfer none): the `GskGLShader` that is used
246 */
247GskGLShader *
248gsk_shader_paintable_get_shader (GskShaderPaintable *self)
249{
250 g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL);
251
252 return self->shader;
253}
254
255/**
256 * gsk_shader_paintable_set_args:
257 * @self: a `GskShaderPaintable`
258 * @data: Data block with uniform data for the shader
259 *
260 * Sets the uniform data that will be passed to the
261 * shader when rendering. The @data will typically
262 * be produced by a `GskUniformDataBuilder`.
263 *
264 * Note that the @data should be considered immutable
265 * after it has been passed to this function.
266 */
267void
268gsk_shader_paintable_set_args (GskShaderPaintable *self,
269 GBytes *data)
270{
271 g_return_if_fail (GSK_IS_SHADER_PAINTABLE (self));
272 g_return_if_fail (data == NULL || g_bytes_get_size (data) == gsk_gl_shader_get_args_size (self->shader));
273
274 g_clear_pointer (&self->args, g_bytes_unref);
275 if (data)
276 self->args = g_bytes_ref (bytes: data);
277
278 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ARGS]);
279 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: self));
280}
281
282/**
283 * gsk_shader_paintable_get_args:
284 * @self: a `GskShaderPaintable`
285 *
286 * Returns the uniform data set with
287 * gsk_shader_paintable_get_args().
288 *
289 * Returns: (transfer none): the uniform data
290 */
291GBytes *
292gsk_shader_paintable_get_args (GskShaderPaintable *self)
293{
294 g_return_val_if_fail (GSK_IS_SHADER_PAINTABLE (self), NULL);
295
296 return self->args;
297}
298
299/**
300 * gsk_shader_paintable_update_time:
301 * @self: a `GskShaderPaintable`
302 * @time_idx: the index of the uniform for time in seconds as float
303 * @frame_time: the current frame time, as returned by `GdkFrameClock`
304 *
305 * This function is a convenience wrapper for
306 * gsk_shader_paintable_set_args() that leaves all
307 * uniform values unchanged, except for the uniform with
308 * index @time_idx, which will be set to the elapsed time
309 * in seconds, since the first call to this function.
310 *
311 * This function is usually called from a `GtkTickCallback`.
312 */
313void
314gsk_shader_paintable_update_time (GskShaderPaintable *self,
315 int time_idx,
316 gint64 frame_time)
317{
318 GskShaderArgsBuilder *builder;
319 GBytes *args;
320 float time;
321
322 if (self->start_time == 0)
323 self->start_time = frame_time;
324
325 time = (frame_time - self->start_time) / (float)G_TIME_SPAN_SECOND;
326
327 builder = gsk_shader_args_builder_new (shader: self->shader, initial_values: self->args);
328 gsk_shader_args_builder_set_float (builder, idx: time_idx, value: time);
329 args = gsk_shader_args_builder_free_to_args (builder);
330
331 gsk_shader_paintable_set_args (self, data: args);
332
333 g_bytes_unref (bytes: args);
334}
335

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