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 | |
43 | struct _GskShaderPaintable |
44 | { |
45 | GObject parent_instance; |
46 | |
47 | GskGLShader *shader; |
48 | GBytes *args; |
49 | |
50 | gint64 start_time; |
51 | }; |
52 | |
53 | struct _GskShaderPaintableClass |
54 | { |
55 | GObjectClass parent_class; |
56 | }; |
57 | |
58 | enum { |
59 | PROP_0, |
60 | PROP_SHADER, |
61 | PROP_ARGS, |
62 | |
63 | N_PROPS, |
64 | }; |
65 | |
66 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
67 | |
68 | static void |
69 | gsk_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 | |
80 | static void |
81 | gsk_shader_paintable_paintable_init (GdkPaintableInterface *iface) |
82 | { |
83 | iface->snapshot = gsk_shader_paintable_paintable_snapshot; |
84 | } |
85 | |
86 | G_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 | |
90 | static void |
91 | gsk_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 | |
115 | static void |
116 | gsk_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 | |
139 | static void |
140 | gsk_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 | |
150 | static void |
151 | gsk_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 | |
172 | static void |
173 | gsk_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 | */ |
188 | GdkPaintable * |
189 | gsk_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 | */ |
222 | void |
223 | gsk_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 | */ |
247 | GskGLShader * |
248 | gsk_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 | */ |
267 | void |
268 | gsk_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 | */ |
291 | GBytes * |
292 | gsk_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 | */ |
313 | void |
314 | gsk_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 | |