1/* GTK - The GIMP Toolkit
2 *
3 * gtkglarea.c: A GL drawing area
4 *
5 * Copyright © 2014 Emmanuele Bassi
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "config.h"
22
23#include "config.h"
24#include "gtkglarea.h"
25#include "gtkintl.h"
26#include "gtkmarshalers.h"
27#include "gtkmarshalers.h"
28#include "gtkprivate.h"
29#include "gtkrender.h"
30#include "gtksnapshot.h"
31#include "gtknative.h"
32#include "gtkwidgetprivate.h"
33
34#include <epoxy/gl.h>
35
36/**
37 * GtkGLArea:
38 *
39 * `GtkGLArea` is a widget that allows drawing with OpenGL.
40 *
41 * ![An example GtkGLArea](glarea.png)
42 *
43 * `GtkGLArea` sets up its own [class@Gdk.GLContext], and creates a custom
44 * GL framebuffer that the widget will do GL rendering onto. It also ensures
45 * that this framebuffer is the default GL rendering target when rendering.
46 *
47 * In order to draw, you have to connect to the [signal@Gtk.GLArea::render]
48 * signal, or subclass `GtkGLArea` and override the GtkGLAreaClass.render
49 * virtual function.
50 *
51 * The `GtkGLArea` widget ensures that the `GdkGLContext` is associated with
52 * the widget's drawing area, and it is kept updated when the size and
53 * position of the drawing area changes.
54 *
55 * ## Drawing with GtkGLArea
56 *
57 * The simplest way to draw using OpenGL commands in a `GtkGLArea` is to
58 * create a widget instance and connect to the [signal@Gtk.GLArea::render] signal:
59 *
60 * The `render()` function will be called when the `GtkGLArea` is ready
61 * for you to draw its content:
62 *
63 * ```c
64 * static gboolean
65 * render (GtkGLArea *area, GdkGLContext *context)
66 * {
67 * // inside this function it's safe to use GL; the given
68 * // GdkGLContext has been made current to the drawable
69 * // surface used by the `GtkGLArea` and the viewport has
70 * // already been set to be the size of the allocation
71 *
72 * // we can start by clearing the buffer
73 * glClearColor (0, 0, 0, 0);
74 * glClear (GL_COLOR_BUFFER_BIT);
75 *
76 * // draw your object
77 * // draw_an_object ();
78 *
79 * // we completed our drawing; the draw commands will be
80 * // flushed at the end of the signal emission chain, and
81 * // the buffers will be drawn on the window
82 * return TRUE;
83 * }
84 *
85 * void setup_glarea (void)
86 * {
87 * // create a GtkGLArea instance
88 * GtkWidget *gl_area = gtk_gl_area_new ();
89 *
90 * // connect to the "render" signal
91 * g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
92 * }
93 * ```
94 *
95 * If you need to initialize OpenGL state, e.g. buffer objects or
96 * shaders, you should use the [signal@Gtk.Widget::realize] signal;
97 * you can use the [signal@Gtk.Widget::unrealize] signal to clean up.
98 * Since the `GdkGLContext` creation and initialization may fail, you
99 * will need to check for errors, using [method@Gtk.GLArea.get_error].
100 *
101 * An example of how to safely initialize the GL state is:
102 *
103 * ```c
104 * static void
105 * on_realize (GtkGLarea *area)
106 * {
107 * // We need to make the context current if we want to
108 * // call GL API
109 * gtk_gl_area_make_current (area);
110 *
111 * // If there were errors during the initialization or
112 * // when trying to make the context current, this
113 * // function will return a GError for you to catch
114 * if (gtk_gl_area_get_error (area) != NULL)
115 * return;
116 *
117 * // You can also use gtk_gl_area_set_error() in order
118 * // to show eventual initialization errors on the
119 * // GtkGLArea widget itself
120 * GError *internal_error = NULL;
121 * init_buffer_objects (&error);
122 * if (error != NULL)
123 * {
124 * gtk_gl_area_set_error (area, error);
125 * g_error_free (error);
126 * return;
127 * }
128 *
129 * init_shaders (&error);
130 * if (error != NULL)
131 * {
132 * gtk_gl_area_set_error (area, error);
133 * g_error_free (error);
134 * return;
135 * }
136 * }
137 * ```
138 *
139 * If you need to change the options for creating the `GdkGLContext`
140 * you should use the [signal@Gtk.GLArea::create-context] signal.
141 */
142
143typedef struct {
144 guint id;
145 int width;
146 int height;
147 GdkTexture *holder;
148} Texture;
149
150typedef struct {
151 GdkGLContext *context;
152 GError *error;
153
154 gboolean have_buffers;
155
156 int required_gl_version;
157
158 guint frame_buffer;
159 guint depth_stencil_buffer;
160 Texture *texture;
161 GList *textures;
162
163 gboolean has_depth_buffer;
164 gboolean has_stencil_buffer;
165
166 gboolean needs_resize;
167 gboolean needs_render;
168 gboolean auto_render;
169 gboolean use_es;
170} GtkGLAreaPrivate;
171
172enum {
173 PROP_0,
174
175 PROP_CONTEXT,
176 PROP_HAS_DEPTH_BUFFER,
177 PROP_HAS_STENCIL_BUFFER,
178 PROP_USE_ES,
179
180 PROP_AUTO_RENDER,
181
182 LAST_PROP
183};
184
185static GParamSpec *obj_props[LAST_PROP] = { NULL, };
186
187enum {
188 RENDER,
189 RESIZE,
190 CREATE_CONTEXT,
191
192 LAST_SIGNAL
193};
194
195static void gtk_gl_area_allocate_buffers (GtkGLArea *area);
196static void gtk_gl_area_allocate_texture (GtkGLArea *area);
197
198static guint area_signals[LAST_SIGNAL] = { 0, };
199
200G_DEFINE_TYPE_WITH_PRIVATE (GtkGLArea, gtk_gl_area, GTK_TYPE_WIDGET)
201
202static void
203gtk_gl_area_set_property (GObject *gobject,
204 guint prop_id,
205 const GValue *value,
206 GParamSpec *pspec)
207{
208 GtkGLArea *self = GTK_GL_AREA (gobject);
209
210 switch (prop_id)
211 {
212 case PROP_AUTO_RENDER:
213 gtk_gl_area_set_auto_render (area: self, auto_render: g_value_get_boolean (value));
214 break;
215
216 case PROP_HAS_DEPTH_BUFFER:
217 gtk_gl_area_set_has_depth_buffer (area: self, has_depth_buffer: g_value_get_boolean (value));
218 break;
219
220 case PROP_HAS_STENCIL_BUFFER:
221 gtk_gl_area_set_has_stencil_buffer (area: self, has_stencil_buffer: g_value_get_boolean (value));
222 break;
223
224 case PROP_USE_ES:
225 gtk_gl_area_set_use_es (area: self, use_es: g_value_get_boolean (value));
226 break;
227
228 default:
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
230 }
231}
232
233static void
234gtk_gl_area_get_property (GObject *gobject,
235 guint prop_id,
236 GValue *value,
237 GParamSpec *pspec)
238{
239 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (GTK_GL_AREA (gobject));
240
241 switch (prop_id)
242 {
243 case PROP_AUTO_RENDER:
244 g_value_set_boolean (value, v_boolean: priv->auto_render);
245 break;
246
247 case PROP_HAS_DEPTH_BUFFER:
248 g_value_set_boolean (value, v_boolean: priv->has_depth_buffer);
249 break;
250
251 case PROP_HAS_STENCIL_BUFFER:
252 g_value_set_boolean (value, v_boolean: priv->has_stencil_buffer);
253 break;
254
255 case PROP_CONTEXT:
256 g_value_set_object (value, v_object: priv->context);
257 break;
258
259 case PROP_USE_ES:
260 g_value_set_boolean (value, v_boolean: priv->use_es);
261 break;
262
263 default:
264 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
265 }
266}
267
268static void
269gtk_gl_area_realize (GtkWidget *widget)
270{
271 GtkGLArea *area = GTK_GL_AREA (widget);
272 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
273
274 GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->realize (widget);
275
276 g_clear_error (err: &priv->error);
277 priv->context = NULL;
278 g_signal_emit (instance: area, signal_id: area_signals[CREATE_CONTEXT], detail: 0, &priv->context);
279
280 /* In case the signal failed, but did not set an error */
281 if (priv->context == NULL && priv->error == NULL)
282 g_set_error_literal (err: &priv->error, GDK_GL_ERROR,
283 code: GDK_GL_ERROR_NOT_AVAILABLE,
284 _("OpenGL context creation failed"));
285
286 priv->needs_resize = TRUE;
287}
288
289static void
290gtk_gl_area_notify (GObject *object,
291 GParamSpec *pspec)
292{
293 if (strcmp (s1: pspec->name, s2: "scale-factor") == 0)
294 {
295 GtkGLArea *area = GTK_GL_AREA (object);
296 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
297
298 priv->needs_resize = TRUE;
299 }
300
301 if (G_OBJECT_CLASS (gtk_gl_area_parent_class)->notify)
302 G_OBJECT_CLASS (gtk_gl_area_parent_class)->notify (object, pspec);
303}
304
305static GdkGLContext *
306gtk_gl_area_real_create_context (GtkGLArea *area)
307{
308 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
309 GtkWidget *widget = GTK_WIDGET (area);
310 GError *error = NULL;
311 GdkGLContext *context;
312
313 context = gdk_surface_create_gl_context (surface: gtk_native_get_surface (self: gtk_widget_get_native (widget)), error: &error);
314 if (error != NULL)
315 {
316 gtk_gl_area_set_error (area, error);
317 g_clear_object (&context);
318 g_clear_error (err: &error);
319 return NULL;
320 }
321
322 gdk_gl_context_set_allowed_apis (self: context, apis: priv->use_es ? GDK_GL_API_GLES : GDK_GL_API_GL);
323 gdk_gl_context_set_required_version (context,
324 major: priv->required_gl_version / 10,
325 minor: priv->required_gl_version % 10);
326
327 gdk_gl_context_realize (context, error: &error);
328 if (error != NULL)
329 {
330 gtk_gl_area_set_error (area, error);
331 g_clear_object (&context);
332 g_clear_error (err: &error);
333 return NULL;
334 }
335
336 return context;
337}
338
339static void
340gtk_gl_area_resize (GtkGLArea *area, int width, int height)
341{
342 glViewport (0, 0, width, height);
343}
344
345/*
346 * Creates all the buffer objects needed for rendering the scene
347 */
348static void
349gtk_gl_area_ensure_buffers (GtkGLArea *area)
350{
351 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
352 GtkWidget *widget = GTK_WIDGET (area);
353
354 gtk_widget_realize (widget);
355
356 if (priv->context == NULL)
357 return;
358
359 if (priv->have_buffers)
360 return;
361
362 priv->have_buffers = TRUE;
363
364 glGenFramebuffers (1, &priv->frame_buffer);
365
366 if ((priv->has_depth_buffer || priv->has_stencil_buffer))
367 {
368 if (priv->depth_stencil_buffer == 0)
369 glGenRenderbuffers (1, &priv->depth_stencil_buffer);
370 }
371 else if (priv->depth_stencil_buffer != 0)
372 {
373 /* Delete old depth/stencil buffer */
374 glDeleteRenderbuffers (1, &priv->depth_stencil_buffer);
375 priv->depth_stencil_buffer = 0;
376 }
377
378 gtk_gl_area_allocate_buffers (area);
379}
380
381static void
382delete_one_texture (gpointer data)
383{
384 Texture *texture = data;
385
386 if (texture->holder)
387 gdk_gl_texture_release (GDK_GL_TEXTURE (texture->holder));
388
389 if (texture->id != 0)
390 {
391 glDeleteTextures (1, &texture->id);
392 texture->id = 0;
393 }
394
395 g_free (mem: texture);
396}
397
398static void
399gtk_gl_area_ensure_texture (GtkGLArea *area)
400{
401 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
402 GtkWidget *widget = GTK_WIDGET (area);
403
404 gtk_widget_realize (widget);
405
406 if (priv->context == NULL)
407 return;
408
409 if (priv->texture == NULL)
410 {
411 GList *l, *link;
412
413 l = priv->textures;
414 while (l)
415 {
416 Texture *texture = l->data;
417 link = l;
418 l = l->next;
419
420 if (texture->holder)
421 continue;
422
423 priv->textures = g_list_delete_link (list: priv->textures, link_: link);
424
425 if (priv->texture == NULL)
426 priv->texture = texture;
427 else
428 delete_one_texture (data: texture);
429 }
430 }
431
432 if (priv->texture == NULL)
433 {
434 priv->texture = g_new (Texture, 1);
435
436 priv->texture->width = 0;
437 priv->texture->height = 0;
438 priv->texture->holder = NULL;
439
440 glGenTextures (1, &priv->texture->id);
441 }
442
443 gtk_gl_area_allocate_texture (area);
444}
445
446/*
447 * Allocates space of the right type and size for all the buffers
448 */
449static void
450gtk_gl_area_allocate_buffers (GtkGLArea *area)
451{
452 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
453 GtkWidget *widget = GTK_WIDGET (area);
454 int scale, width, height;
455
456 if (priv->context == NULL)
457 return;
458
459 scale = gtk_widget_get_scale_factor (widget);
460 width = gtk_widget_get_width (widget) * scale;
461 height = gtk_widget_get_height (widget) * scale;
462
463 if (priv->has_depth_buffer || priv->has_stencil_buffer)
464 {
465 glBindRenderbuffer (GL_RENDERBUFFER, priv->depth_stencil_buffer);
466 if (priv->has_stencil_buffer)
467 glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
468 else
469 glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
470 }
471
472 priv->needs_render = TRUE;
473}
474
475static void
476gtk_gl_area_allocate_texture (GtkGLArea *area)
477{
478 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
479 GtkWidget *widget = GTK_WIDGET (area);
480 int scale, width, height;
481
482 if (priv->context == NULL)
483 return;
484
485 if (priv->texture == NULL)
486 return;
487
488 g_assert (priv->texture->holder == NULL);
489
490 scale = gtk_widget_get_scale_factor (widget);
491 width = gtk_widget_get_width (widget) * scale;
492 height = gtk_widget_get_height (widget) * scale;
493
494 if (priv->texture->width != width ||
495 priv->texture->height != height)
496 {
497 glBindTexture (GL_TEXTURE_2D, priv->texture->id);
498 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
499 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
500 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
501 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
502
503 if (gdk_gl_context_get_use_es (context: priv->context))
504 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
505 else
506 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
507
508 priv->texture->width = width;
509 priv->texture->height = height;
510 }
511}
512
513/**
514 * gtk_gl_area_attach_buffers:
515 * @area: a `GtkGLArea`
516 *
517 * Binds buffers to the framebuffer.
518 *
519 * Ensures that the @area framebuffer object is made the current draw
520 * and read target, and that all the required buffers for the @area
521 * are created and bound to the framebuffer.
522 *
523 * This function is automatically called before emitting the
524 * [signal@Gtk.GLArea::render] signal, and doesn't normally need to be
525 * called by application code.
526 */
527void
528gtk_gl_area_attach_buffers (GtkGLArea *area)
529{
530 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
531
532 g_return_if_fail (GTK_IS_GL_AREA (area));
533
534 if (priv->context == NULL)
535 return;
536
537 gtk_gl_area_make_current (area);
538
539 if (priv->texture == NULL)
540 gtk_gl_area_ensure_texture (area);
541 else if (priv->needs_resize)
542 gtk_gl_area_allocate_texture (area);
543
544 if (!priv->have_buffers)
545 gtk_gl_area_ensure_buffers (area);
546 else if (priv->needs_resize)
547 gtk_gl_area_allocate_buffers (area);
548
549 glBindFramebuffer (GL_FRAMEBUFFER, priv->frame_buffer);
550
551 if (priv->texture != NULL)
552 glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
553 GL_TEXTURE_2D, priv->texture->id, 0);
554
555 if (priv->depth_stencil_buffer)
556 {
557 if (priv->has_depth_buffer)
558 glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
559 GL_RENDERBUFFER, priv->depth_stencil_buffer);
560 if (priv->has_stencil_buffer)
561 glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
562 GL_RENDERBUFFER, priv->depth_stencil_buffer);
563 }
564}
565
566static void
567gtk_gl_area_delete_buffers (GtkGLArea *area)
568{
569 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
570
571 if (priv->context == NULL)
572 return;
573
574 priv->have_buffers = FALSE;
575
576 if (priv->depth_stencil_buffer != 0)
577 {
578 glDeleteRenderbuffers (1, &priv->depth_stencil_buffer);
579 priv->depth_stencil_buffer = 0;
580 }
581
582 if (priv->frame_buffer != 0)
583 {
584 glBindFramebuffer (GL_FRAMEBUFFER, 0);
585 glDeleteFramebuffers (1, &priv->frame_buffer);
586 priv->frame_buffer = 0;
587 }
588}
589
590static void
591gtk_gl_area_delete_textures (GtkGLArea *area)
592{
593 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
594
595 if (priv->texture)
596 {
597 delete_one_texture (data: priv->texture);
598 priv->texture = NULL;
599 }
600
601 /* FIXME: we need to explicitly release all outstanding
602 * textures here, otherwise release_texture will get called
603 * later and access freed memory.
604 */
605 g_list_free_full (list: priv->textures, free_func: delete_one_texture);
606 priv->textures = NULL;
607}
608
609static void
610gtk_gl_area_unrealize (GtkWidget *widget)
611{
612 GtkGLArea *area = GTK_GL_AREA (widget);
613 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
614
615 if (priv->context != NULL)
616 {
617 gtk_gl_area_make_current (area);
618 gtk_gl_area_delete_buffers (area);
619 gtk_gl_area_delete_textures (area);
620
621 /* Make sure to unset the context if current */
622 if (priv->context == gdk_gl_context_get_current ())
623 gdk_gl_context_clear_current ();
624 }
625
626 g_clear_object (&priv->context);
627 g_clear_error (err: &priv->error);
628
629 GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unrealize (widget);
630}
631
632static void
633gtk_gl_area_size_allocate (GtkWidget *widget,
634 int width,
635 int height,
636 int baseline)
637{
638 GtkGLArea *area = GTK_GL_AREA (widget);
639 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
640
641 GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->size_allocate (widget, width, height, baseline);
642
643 if (gtk_widget_get_realized (widget))
644 priv->needs_resize = TRUE;
645}
646
647static void
648gtk_gl_area_draw_error_screen (GtkGLArea *area,
649 GtkSnapshot *snapshot,
650 int width,
651 int height)
652{
653 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
654 PangoLayout *layout;
655 int layout_height;
656
657 layout = gtk_widget_create_pango_layout (GTK_WIDGET (area),
658 text: priv->error->message);
659 pango_layout_set_width (layout, width: width * PANGO_SCALE);
660 pango_layout_set_alignment (layout, alignment: PANGO_ALIGN_CENTER);
661 pango_layout_get_pixel_size (layout, NULL, height: &layout_height);
662
663 gtk_snapshot_render_layout (snapshot,
664 context: gtk_widget_get_style_context (GTK_WIDGET (area)),
665 x: 0, y: (height - layout_height) / 2,
666 layout);
667
668 g_object_unref (object: layout);
669}
670
671static void
672release_texture (gpointer data)
673{
674 Texture *texture = data;
675 texture->holder = NULL;
676}
677
678static void
679gtk_gl_area_snapshot (GtkWidget *widget,
680 GtkSnapshot *snapshot)
681{
682 GtkGLArea *area = GTK_GL_AREA (widget);
683 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
684 gboolean unused;
685 int w, h, scale;
686 GLenum status;
687
688 scale = gtk_widget_get_scale_factor (widget);
689 w = gtk_widget_get_width (widget) * scale;
690 h = gtk_widget_get_height (widget) * scale;
691
692 if (w == 0 || h == 0)
693 return;
694
695 if (priv->error != NULL)
696 {
697 gtk_gl_area_draw_error_screen (area,
698 snapshot,
699 width: gtk_widget_get_width (widget),
700 height: gtk_widget_get_height (widget));
701 return;
702 }
703
704 if (priv->context == NULL)
705 return;
706
707 gtk_gl_area_make_current (area);
708
709 gtk_gl_area_attach_buffers (area);
710
711 if (priv->has_depth_buffer)
712 glEnable (GL_DEPTH_TEST);
713 else
714 glDisable (GL_DEPTH_TEST);
715
716 status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
717 if (status == GL_FRAMEBUFFER_COMPLETE)
718 {
719 Texture *texture;
720
721 if (priv->needs_render || priv->auto_render)
722 {
723 if (priv->needs_resize)
724 {
725 g_signal_emit (instance: area, signal_id: area_signals[RESIZE], detail: 0, w, h, NULL);
726 priv->needs_resize = FALSE;
727 }
728
729 g_signal_emit (instance: area, signal_id: area_signals[RENDER], detail: 0, priv->context, &unused);
730 }
731
732 priv->needs_render = FALSE;
733
734 texture = priv->texture;
735 priv->texture = NULL;
736 priv->textures = g_list_prepend (list: priv->textures, data: texture);
737
738 texture->holder = gdk_gl_texture_new (context: priv->context,
739 id: texture->id,
740 width: texture->width,
741 height: texture->height,
742 destroy: release_texture, data: texture);
743
744 /* Our texture is rendered by OpenGL, so it is upside down,
745 * compared to what GSK expects, so flip it back.
746 */
747 gtk_snapshot_save (snapshot);
748 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (0, gtk_widget_get_height (widget)));
749 gtk_snapshot_scale (snapshot, factor_x: 1, factor_y: -1);
750 gtk_snapshot_append_texture (snapshot,
751 texture: texture->holder,
752 bounds: &GRAPHENE_RECT_INIT (0, 0,
753 gtk_widget_get_width (widget),
754 gtk_widget_get_height (widget)));
755 gtk_snapshot_restore (snapshot);
756
757 g_object_unref (object: texture->holder);
758 }
759 else
760 {
761 g_warning ("fb setup not supported (%x)", status);
762 }
763}
764
765static gboolean
766create_context_accumulator (GSignalInvocationHint *ihint,
767 GValue *return_accu,
768 const GValue *handler_return,
769 gpointer data)
770{
771 g_value_copy (src_value: handler_return, dest_value: return_accu);
772
773 /* stop after the first handler returning a valid object */
774 return g_value_get_object (value: handler_return) == NULL;
775}
776
777static void
778gtk_gl_area_class_init (GtkGLAreaClass *klass)
779{
780 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
781 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
782
783 klass->resize = gtk_gl_area_resize;
784 klass->create_context = gtk_gl_area_real_create_context;
785
786 widget_class->realize = gtk_gl_area_realize;
787 widget_class->unrealize = gtk_gl_area_unrealize;
788 widget_class->size_allocate = gtk_gl_area_size_allocate;
789 widget_class->snapshot = gtk_gl_area_snapshot;
790
791 /**
792 * GtkGLArea:context:
793 *
794 * The `GdkGLContext` used by the `GtkGLArea` widget.
795 *
796 * The `GtkGLArea` widget is responsible for creating the `GdkGLContext`
797 * instance. If you need to render with other kinds of buffers (stencil,
798 * depth, etc), use render buffers.
799 */
800 obj_props[PROP_CONTEXT] =
801 g_param_spec_object (name: "context",
802 P_("Context"),
803 P_("The GL context"),
804 GDK_TYPE_GL_CONTEXT,
805 flags: G_PARAM_READABLE |
806 G_PARAM_STATIC_STRINGS);
807
808 /**
809 * GtkGLArea:auto-render: (attributes org.gtk.Property.get=gtk_gl_area_get_auto_render org.gtk.Property.set=gtk_gl_area_set_auto_render)
810 *
811 * If set to %TRUE the ::render signal will be emitted every time
812 * the widget draws.
813 *
814 * This is the default and is useful if drawing the widget is faster.
815 *
816 * If set to %FALSE the data from previous rendering is kept around and will
817 * be used for drawing the widget the next time, unless the window is resized.
818 * In order to force a rendering [method@Gtk.GLArea.queue_render] must be called.
819 * This mode is useful when the scene changes seldom, but takes a long time
820 * to redraw.
821 */
822 obj_props[PROP_AUTO_RENDER] =
823 g_param_spec_boolean (name: "auto-render",
824 P_("Auto render"),
825 P_("Whether the GtkGLArea renders on each redraw"),
826 TRUE,
827 GTK_PARAM_READWRITE |
828 G_PARAM_STATIC_STRINGS |
829 G_PARAM_EXPLICIT_NOTIFY);
830
831 /**
832 * GtkGLArea:has-depth-buffer: (attributes org.gtk.Property.get=gtk_gl_area_get_has_depth_buffer org.gtk.Property.set=gtk_gl_area_set_has_depth_buffer)
833 *
834 * If set to %TRUE the widget will allocate and enable a depth buffer for the
835 * target framebuffer.
836 *
837 * Setting this property will enable GL's depth testing as a side effect. If
838 * you don't need depth testing, you should call `glDisable(GL_DEPTH_TEST)`
839 * in your `GtkGLArea::render` handler.
840 */
841 obj_props[PROP_HAS_DEPTH_BUFFER] =
842 g_param_spec_boolean (name: "has-depth-buffer",
843 P_("Has depth buffer"),
844 P_("Whether a depth buffer is allocated"),
845 FALSE,
846 GTK_PARAM_READWRITE |
847 G_PARAM_STATIC_STRINGS |
848 G_PARAM_EXPLICIT_NOTIFY);
849
850 /**
851 * GtkGLArea:has-stencil-buffer: (attributes org.gtk.Property.get=gtk_gl_area_get_has_stencil_buffer org.gtk.Property.set=gtk_gl_area_set_has_stencil_buffer)
852 *
853 * If set to %TRUE the widget will allocate and enable a stencil buffer for the
854 * target framebuffer.
855 */
856 obj_props[PROP_HAS_STENCIL_BUFFER] =
857 g_param_spec_boolean (name: "has-stencil-buffer",
858 P_("Has stencil buffer"),
859 P_("Whether a stencil buffer is allocated"),
860 FALSE,
861 GTK_PARAM_READWRITE |
862 G_PARAM_STATIC_STRINGS |
863 G_PARAM_EXPLICIT_NOTIFY);
864
865 /**
866 * GtkGLArea:use-es: (attributes org.gtk.Property.get=gtk_gl_area_get_use_es org.gtk.Property.set=gtk_gl_area_set_use_es)
867 *
868 * If set to %TRUE the widget will try to create a `GdkGLContext` using
869 * OpenGL ES instead of OpenGL.
870 */
871 obj_props[PROP_USE_ES] =
872 g_param_spec_boolean (name: "use-es",
873 P_("Use OpenGL ES"),
874 P_("Whether the context uses OpenGL or OpenGL ES"),
875 FALSE,
876 GTK_PARAM_READWRITE |
877 G_PARAM_STATIC_STRINGS |
878 G_PARAM_EXPLICIT_NOTIFY);
879
880 gobject_class->set_property = gtk_gl_area_set_property;
881 gobject_class->get_property = gtk_gl_area_get_property;
882 gobject_class->notify = gtk_gl_area_notify;
883
884 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: obj_props);
885
886 /**
887 * GtkGLArea::render:
888 * @area: the `GtkGLArea` that emitted the signal
889 * @context: the `GdkGLContext` used by @area
890 *
891 * Emitted every time the contents of the `GtkGLArea` should be redrawn.
892 *
893 * The @context is bound to the @area prior to emitting this function,
894 * and the buffers are painted to the window once the emission terminates.
895 *
896 * Returns: %TRUE to stop other handlers from being invoked for the event.
897 * %FALSE to propagate the event further.
898 */
899 area_signals[RENDER] =
900 g_signal_new (I_("render"),
901 G_TYPE_FROM_CLASS (gobject_class),
902 signal_flags: G_SIGNAL_RUN_LAST,
903 G_STRUCT_OFFSET (GtkGLAreaClass, render),
904 accumulator: _gtk_boolean_handled_accumulator, NULL,
905 c_marshaller: _gtk_marshal_BOOLEAN__OBJECT,
906 G_TYPE_BOOLEAN, n_params: 1,
907 GDK_TYPE_GL_CONTEXT);
908 g_signal_set_va_marshaller (signal_id: area_signals[RENDER],
909 G_TYPE_FROM_CLASS (klass),
910 va_marshaller: _gtk_marshal_BOOLEAN__OBJECTv);
911
912 /**
913 * GtkGLArea::resize:
914 * @area: the `GtkGLArea` that emitted the signal
915 * @width: the width of the viewport
916 * @height: the height of the viewport
917 *
918 * Emitted once when the widget is realized, and then each time the widget
919 * is changed while realized.
920 *
921 * This is useful in order to keep GL state up to date with the widget size,
922 * like for instance camera properties which may depend on the width/height
923 * ratio.
924 *
925 * The GL context for the area is guaranteed to be current when this signal
926 * is emitted.
927 *
928 * The default handler sets up the GL viewport.
929 */
930 area_signals[RESIZE] =
931 g_signal_new (I_("resize"),
932 G_TYPE_FROM_CLASS (klass),
933 signal_flags: G_SIGNAL_RUN_LAST,
934 G_STRUCT_OFFSET (GtkGLAreaClass, resize),
935 NULL, NULL,
936 c_marshaller: _gtk_marshal_VOID__INT_INT,
937 G_TYPE_NONE, n_params: 2, G_TYPE_INT, G_TYPE_INT);
938 g_signal_set_va_marshaller (signal_id: area_signals[RESIZE],
939 G_TYPE_FROM_CLASS (klass),
940 va_marshaller: _gtk_marshal_VOID__INT_INTv);
941
942 /**
943 * GtkGLArea::create-context:
944 * @area: the `GtkGLArea` that emitted the signal
945 * @error: (nullable): location to store error information on failure
946 *
947 * Emitted when the widget is being realized.
948 *
949 * This allows you to override how the GL context is created.
950 * This is useful when you want to reuse an existing GL context,
951 * or if you want to try creating different kinds of GL options.
952 *
953 * If context creation fails then the signal handler can use
954 * [method@Gtk.GLArea.set_error] to register a more detailed error
955 * of how the construction failed.
956 *
957 * Returns: (transfer full): a newly created `GdkGLContext`;
958 * the `GtkGLArea` widget will take ownership of the returned value.
959 */
960 area_signals[CREATE_CONTEXT] =
961 g_signal_new (I_("create-context"),
962 G_TYPE_FROM_CLASS (klass),
963 signal_flags: G_SIGNAL_RUN_LAST,
964 G_STRUCT_OFFSET (GtkGLAreaClass, create_context),
965 accumulator: create_context_accumulator, NULL,
966 c_marshaller: _gtk_marshal_OBJECT__VOID,
967 GDK_TYPE_GL_CONTEXT, n_params: 0);
968 g_signal_set_va_marshaller (signal_id: area_signals[CREATE_CONTEXT],
969 G_TYPE_FROM_CLASS (klass),
970 va_marshaller: _gtk_marshal_OBJECT__VOIDv);
971}
972
973static void
974gtk_gl_area_init (GtkGLArea *area)
975{
976 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
977
978 priv->auto_render = TRUE;
979 priv->needs_render = TRUE;
980 priv->required_gl_version = 0;
981}
982
983/**
984 * gtk_gl_area_new:
985 *
986 * Creates a new `GtkGLArea` widget.
987 *
988 * Returns: a new `GtkGLArea`
989 */
990GtkWidget *
991gtk_gl_area_new (void)
992{
993 return g_object_new (GTK_TYPE_GL_AREA, NULL);
994}
995
996/**
997 * gtk_gl_area_set_error:
998 * @area: a `GtkGLArea`
999 * @error: (nullable): a new `GError`, or %NULL to unset the error
1000 *
1001 * Sets an error on the area which will be shown instead of the
1002 * GL rendering.
1003 *
1004 * This is useful in the [signal@Gtk.GLArea::create-context]
1005 * signal if GL context creation fails.
1006 */
1007void
1008gtk_gl_area_set_error (GtkGLArea *area,
1009 const GError *error)
1010{
1011 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1012
1013 g_return_if_fail (GTK_IS_GL_AREA (area));
1014
1015 g_clear_error (err: &priv->error);
1016 if (error)
1017 priv->error = g_error_copy (error);
1018}
1019
1020/**
1021 * gtk_gl_area_get_error:
1022 * @area: a `GtkGLArea`
1023 *
1024 * Gets the current error set on the @area.
1025 *
1026 * Returns: (nullable) (transfer none): the `GError`
1027 */
1028GError *
1029gtk_gl_area_get_error (GtkGLArea *area)
1030{
1031 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1032
1033 g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);
1034
1035 return priv->error;
1036}
1037
1038/**
1039 * gtk_gl_area_set_use_es: (attributes org.gtk.Method.set_property=use-es)
1040 * @area: a `GtkGLArea`
1041 * @use_es: whether to use OpenGL or OpenGL ES
1042 *
1043 * Sets whether the @area should create an OpenGL or an OpenGL ES context.
1044 *
1045 * You should check the capabilities of the `GdkGLContext` before drawing
1046 * with either API.
1047 */
1048void
1049gtk_gl_area_set_use_es (GtkGLArea *area,
1050 gboolean use_es)
1051{
1052 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1053
1054 g_return_if_fail (GTK_IS_GL_AREA (area));
1055 g_return_if_fail (!gtk_widget_get_realized (GTK_WIDGET (area)));
1056
1057 use_es = !!use_es;
1058
1059 if (priv->use_es != use_es)
1060 {
1061 priv->use_es = use_es;
1062
1063 g_object_notify_by_pspec (G_OBJECT (area), pspec: obj_props[PROP_USE_ES]);
1064 }
1065}
1066
1067/**
1068 * gtk_gl_area_get_use_es: (attributes org.gtk.Method.get_property=use-es)
1069 * @area: a `GtkGLArea`
1070 *
1071 * Returns whether the `GtkGLArea` should use OpenGL ES.
1072 *
1073 * See [method@Gtk.GLArea.set_use_es].
1074 *
1075 * Returns: %TRUE if the `GtkGLArea` should create an OpenGL ES context
1076 * and %FALSE otherwise
1077 */
1078gboolean
1079gtk_gl_area_get_use_es (GtkGLArea *area)
1080{
1081 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1082
1083 g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);
1084
1085 return priv->use_es;
1086}
1087
1088/**
1089 * gtk_gl_area_set_required_version:
1090 * @area: a `GtkGLArea`
1091 * @major: the major version
1092 * @minor: the minor version
1093 *
1094 * Sets the required version of OpenGL to be used when creating
1095 * the context for the widget.
1096 *
1097 * This function must be called before the area has been realized.
1098 */
1099void
1100gtk_gl_area_set_required_version (GtkGLArea *area,
1101 int major,
1102 int minor)
1103{
1104 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1105
1106 g_return_if_fail (GTK_IS_GL_AREA (area));
1107 g_return_if_fail (!gtk_widget_get_realized (GTK_WIDGET (area)));
1108
1109 priv->required_gl_version = major * 10 + minor;
1110}
1111
1112/**
1113 * gtk_gl_area_get_required_version:
1114 * @area: a `GtkGLArea`
1115 * @major: (out): return location for the required major version
1116 * @minor: (out): return location for the required minor version
1117 *
1118 * Retrieves the required version of OpenGL.
1119 *
1120 * See [method@Gtk.GLArea.set_required_version].
1121 */
1122void
1123gtk_gl_area_get_required_version (GtkGLArea *area,
1124 int *major,
1125 int *minor)
1126{
1127 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1128
1129 g_return_if_fail (GTK_IS_GL_AREA (area));
1130
1131 if (major != NULL)
1132 *major = priv->required_gl_version / 10;
1133 if (minor != NULL)
1134 *minor = priv->required_gl_version % 10;
1135}
1136
1137/**
1138 * gtk_gl_area_get_has_depth_buffer: (attributes org.gtk.Method.get_property=has-depth-buffer)
1139 * @area: a `GtkGLArea`
1140 *
1141 * Returns whether the area has a depth buffer.
1142 *
1143 * Returns: %TRUE if the @area has a depth buffer, %FALSE otherwise
1144 */
1145gboolean
1146gtk_gl_area_get_has_depth_buffer (GtkGLArea *area)
1147{
1148 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1149
1150 g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);
1151
1152 return priv->has_depth_buffer;
1153}
1154
1155/**
1156 * gtk_gl_area_set_has_depth_buffer: (attributes org.gtk.Method.set_property=has-depth-buffer)
1157 * @area: a `GtkGLArea`
1158 * @has_depth_buffer: %TRUE to add a depth buffer
1159 *
1160 * Sets whether the `GtkGLArea` should use a depth buffer.
1161 *
1162 * If @has_depth_buffer is %TRUE the widget will allocate and
1163 * enable a depth buffer for the target framebuffer. Otherwise
1164 * there will be none.
1165 */
1166void
1167gtk_gl_area_set_has_depth_buffer (GtkGLArea *area,
1168 gboolean has_depth_buffer)
1169{
1170 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1171
1172 g_return_if_fail (GTK_IS_GL_AREA (area));
1173
1174 has_depth_buffer = !!has_depth_buffer;
1175
1176 if (priv->has_depth_buffer != has_depth_buffer)
1177 {
1178 priv->has_depth_buffer = has_depth_buffer;
1179
1180 g_object_notify (G_OBJECT (area), property_name: "has-depth-buffer");
1181
1182 priv->have_buffers = FALSE;
1183 }
1184}
1185
1186/**
1187 * gtk_gl_area_get_has_stencil_buffer: (attributes org.gtk.Method.get_property=has-stencil-buffer)
1188 * @area: a `GtkGLArea`
1189 *
1190 * Returns whether the area has a stencil buffer.
1191 *
1192 * Returns: %TRUE if the @area has a stencil buffer, %FALSE otherwise
1193 */
1194gboolean
1195gtk_gl_area_get_has_stencil_buffer (GtkGLArea *area)
1196{
1197 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1198
1199 g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);
1200
1201 return priv->has_stencil_buffer;
1202}
1203
1204/**
1205 * gtk_gl_area_set_has_stencil_buffer: (attributes org.gtk.Method.set_property=has-stencil-buffer)
1206 * @area: a `GtkGLArea`
1207 * @has_stencil_buffer: %TRUE to add a stencil buffer
1208 *
1209 * Sets whether the `GtkGLArea` should use a stencil buffer.
1210 *
1211 * If @has_stencil_buffer is %TRUE the widget will allocate and
1212 * enable a stencil buffer for the target framebuffer. Otherwise
1213 * there will be none.
1214 */
1215void
1216gtk_gl_area_set_has_stencil_buffer (GtkGLArea *area,
1217 gboolean has_stencil_buffer)
1218{
1219 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1220
1221 g_return_if_fail (GTK_IS_GL_AREA (area));
1222
1223 has_stencil_buffer = !!has_stencil_buffer;
1224
1225 if (priv->has_stencil_buffer != has_stencil_buffer)
1226 {
1227 priv->has_stencil_buffer = has_stencil_buffer;
1228
1229 g_object_notify (G_OBJECT (area), property_name: "has-stencil-buffer");
1230
1231 priv->have_buffers = FALSE;
1232 }
1233}
1234
1235/**
1236 * gtk_gl_area_queue_render:
1237 * @area: a `GtkGLArea`
1238 *
1239 * Marks the currently rendered data (if any) as invalid, and queues
1240 * a redraw of the widget.
1241 *
1242 * This ensures that the [signal@Gtk.GLArea::render] signal
1243 * is emitted during the draw.
1244 *
1245 * This is only needed when [method@Gtk.GLArea.set_auto_render] has
1246 * been called with a %FALSE value. The default behaviour is to
1247 * emit [signal@Gtk.GLArea::render] on each draw.
1248 */
1249void
1250gtk_gl_area_queue_render (GtkGLArea *area)
1251{
1252 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1253
1254 g_return_if_fail (GTK_IS_GL_AREA (area));
1255
1256 priv->needs_render = TRUE;
1257
1258 gtk_widget_queue_draw (GTK_WIDGET (area));
1259}
1260
1261
1262/**
1263 * gtk_gl_area_get_auto_render: (attributes org.gtk.Method.get_property=auto-render)
1264 * @area: a `GtkGLArea`
1265 *
1266 * Returns whether the area is in auto render mode or not.
1267 *
1268 * Returns: %TRUE if the @area is auto rendering, %FALSE otherwise
1269 */
1270gboolean
1271gtk_gl_area_get_auto_render (GtkGLArea *area)
1272{
1273 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1274
1275 g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);
1276
1277 return priv->auto_render;
1278}
1279
1280/**
1281 * gtk_gl_area_set_auto_render: (attributes org.gtk.Method.set_property=auto-render)
1282 * @area: a `GtkGLArea`
1283 * @auto_render: a boolean
1284 *
1285 * Sets whether the `GtkGLArea` is in auto render mode.
1286 *
1287 * If @auto_render is %TRUE the [signal@Gtk.GLArea::render] signal will
1288 * be emitted every time the widget draws. This is the default and is
1289 * useful if drawing the widget is faster.
1290 *
1291 * If @auto_render is %FALSE the data from previous rendering is kept
1292 * around and will be used for drawing the widget the next time,
1293 * unless the window is resized. In order to force a rendering
1294 * [method@Gtk.GLArea.queue_render] must be called. This mode is
1295 * useful when the scene changes seldom, but takes a long time to redraw.
1296 */
1297void
1298gtk_gl_area_set_auto_render (GtkGLArea *area,
1299 gboolean auto_render)
1300{
1301 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1302
1303 g_return_if_fail (GTK_IS_GL_AREA (area));
1304
1305 auto_render = !!auto_render;
1306
1307 if (priv->auto_render != auto_render)
1308 {
1309 priv->auto_render = auto_render;
1310
1311 g_object_notify (G_OBJECT (area), property_name: "auto-render");
1312
1313 if (auto_render)
1314 gtk_widget_queue_draw (GTK_WIDGET (area));
1315 }
1316}
1317
1318/**
1319 * gtk_gl_area_get_context:
1320 * @area: a `GtkGLArea`
1321 *
1322 * Retrieves the `GdkGLContext` used by @area.
1323 *
1324 * Returns: (transfer none) (nullable): the `GdkGLContext`
1325 */
1326GdkGLContext *
1327gtk_gl_area_get_context (GtkGLArea *area)
1328{
1329 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1330
1331 g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);
1332
1333 return priv->context;
1334}
1335
1336/**
1337 * gtk_gl_area_make_current:
1338 * @area: a `GtkGLArea`
1339 *
1340 * Ensures that the `GdkGLContext` used by @area is associated with
1341 * the `GtkGLArea`.
1342 *
1343 * This function is automatically called before emitting the
1344 * [signal@Gtk.GLArea::render] signal, and doesn't normally need
1345 * to be called by application code.
1346 */
1347void
1348gtk_gl_area_make_current (GtkGLArea *area)
1349{
1350 GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self: area);
1351
1352 g_return_if_fail (GTK_IS_GL_AREA (area));
1353 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (area)));
1354
1355 if (priv->context != NULL)
1356 gdk_gl_context_make_current (context: priv->context);
1357}
1358

source code of gtk/gtk/gtkglarea.c