gtkglarea.c 38.4 KB
Newer Older
Alexander Larsson's avatar
Alexander Larsson committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* GTK - The GIMP Toolkit
 *
 * gtkglarea.c: A GL drawing area
 *
 * Copyright © 2014  Emmanuele Bassi
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "config.h"
#include "gtkglarea.h"
#include "gtkintl.h"
26
#include "gtkmarshalers.h"
Alexander Larsson's avatar
Alexander Larsson committed
27
28
#include "gtkmarshalers.h"
#include "gtkprivate.h"
29
#include "gtkrender.h"
30
#include "gtksnapshot.h"
31
#include "gtknative.h"
32
#include "gtkwidgetprivate.h"
33

Alexander Larsson's avatar
Alexander Larsson committed
34
35
36
#include <epoxy/gl.h>

/**
37
 * GtkGLArea:
Alexander Larsson's avatar
Alexander Larsson committed
38
 *
Matthias Clasen's avatar
Matthias Clasen committed
39
 * `GtkGLArea` is a widget that allows drawing with OpenGL.
Alexander Larsson's avatar
Alexander Larsson committed
40
 *
Matthias Clasen's avatar
Matthias Clasen committed
41
 * ![An example GtkGLArea](glarea.png)
Alexander Larsson's avatar
Alexander Larsson committed
42
 *
Matthias Clasen's avatar
Matthias Clasen committed
43
44
45
 * `GtkGLArea` sets up its own [class@Gdk.GLContext], and creates a custom
 * GL framebuffer that the widget will do GL rendering onto. It also ensures
 * that this framebuffer is the default GL rendering target when rendering.
Alexander Larsson's avatar
Alexander Larsson committed
46
 *
Matthias Clasen's avatar
Matthias Clasen committed
47
48
49
50
51
 * In order to draw, you have to connect to the [signal@Gtk.GLArea::render]
 * signal, or subclass `GtkGLArea` and override the GtkGLAreaClass.render
 * virtual function.
 *
 * The `GtkGLArea` widget ensures that the `GdkGLContext` is associated with
Alexander Larsson's avatar
Alexander Larsson committed
52
53
54
 * the widget's drawing area, and it is kept updated when the size and
 * position of the drawing area changes.
 *
Matthias Clasen's avatar
Matthias Clasen committed
55
 * ## Drawing with GtkGLArea
Alexander Larsson's avatar
Alexander Larsson committed
56
 *
Matthias Clasen's avatar
Matthias Clasen committed
57
58
 * The simplest way to draw using OpenGL commands in a `GtkGLArea` is to
 * create a widget instance and connect to the [signal@Gtk.GLArea::render] signal:
Alexander Larsson's avatar
Alexander Larsson committed
59
 *
Matthias Clasen's avatar
Matthias Clasen committed
60
 * The `render()` function will be called when the `GtkGLArea` is ready
Alexander Larsson's avatar
Alexander Larsson committed
61
62
 * for you to draw its content:
 *
Matthias Clasen's avatar
Matthias Clasen committed
63
64
65
66
67
 * ```c
 * static gboolean
 * render (GtkGLArea *area, GdkGLContext *context)
 * {
 *   // inside this function it's safe to use GL; the given
Matthias Clasen's avatar
Matthias Clasen committed
68
 *   // GdkGLContext has been made current to the drawable
Matthias Clasen's avatar
Matthias Clasen committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
 *   // surface used by the `GtkGLArea` and the viewport has
 *   // already been set to be the size of the allocation
 *
 *   // we can start by clearing the buffer
 *   glClearColor (0, 0, 0, 0);
 *   glClear (GL_COLOR_BUFFER_BIT);
 *
 *   // draw your object
 *   // draw_an_object ();
 *
 *   // we completed our drawing; the draw commands will be
 *   // flushed at the end of the signal emission chain, and
 *   // the buffers will be drawn on the window
 *   return TRUE;
 * }
 *
 * void setup_glarea (void)
 * {
 *   // create a GtkGLArea instance
 *   GtkWidget *gl_area = gtk_gl_area_new ();
 *
 *   // connect to the "render" signal
 *   g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
 * }
 * ```
Alexander Larsson's avatar
Alexander Larsson committed
94
 *
Emmanuele Bassi's avatar
Emmanuele Bassi committed
95
 * If you need to initialize OpenGL state, e.g. buffer objects or
Matthias Clasen's avatar
Matthias Clasen committed
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
 * shaders, you should use the [signal@Gtk.Widget::realize] signal;
 * you can use the [signal@Gtk.Widget::unrealize] signal to clean up.
 * Since the `GdkGLContext` creation and initialization may fail, you
 * will need to check for errors, using [method@Gtk.GLArea.get_error].
 *
 * An example of how to safely initialize the GL state is:
 *
 * ```c
 * static void
 * on_realize (GtkGLarea *area)
 * {
 *   // We need to make the context current if we want to
 *   // call GL API
 *   gtk_gl_area_make_current (area);
 *
 *   // If there were errors during the initialization or
 *   // when trying to make the context current, this
Matthias Clasen's avatar
Matthias Clasen committed
113
 *   // function will return a GError for you to catch
Matthias Clasen's avatar
Matthias Clasen committed
114
115
116
117
118
119
120
121
122
123
124
125
 *   if (gtk_gl_area_get_error (area) != NULL)
 *     return;
 *
 *   // You can also use gtk_gl_area_set_error() in order
 *   // to show eventual initialization errors on the
 *   // GtkGLArea widget itself
 *   GError *internal_error = NULL;
 *   init_buffer_objects (&error);
 *   if (error != NULL)
 *     {
 *       gtk_gl_area_set_error (area, error);
 *       g_error_free (error);
126
 *       return;
Matthias Clasen's avatar
Matthias Clasen committed
127
 *     }
128
 *
Matthias Clasen's avatar
Matthias Clasen committed
129
130
131
132
133
134
135
136
137
138
139
140
 *   init_shaders (&error);
 *   if (error != NULL)
 *     {
 *       gtk_gl_area_set_error (area, error);
 *       g_error_free (error);
 *       return;
 *     }
 * }
 * ```
 *
 * If you need to change the options for creating the `GdkGLContext`
 * you should use the [signal@Gtk.GLArea::create-context] signal.
Alexander Larsson's avatar
Alexander Larsson committed
141
142
 */

143
144
145
146
147
148
149
typedef struct {
  guint id;
  int width;
  int height;
  GdkTexture *holder;
} Texture;

Alexander Larsson's avatar
Alexander Larsson committed
150
151
typedef struct {
  GdkGLContext *context;
152
  GError *error;
Alexander Larsson's avatar
Alexander Larsson committed
153
154
155

  gboolean have_buffers;

156
157
  int required_gl_version;

Alexander Larsson's avatar
Alexander Larsson committed
158
159
  guint frame_buffer;
  guint depth_stencil_buffer;
160
161
  Texture *texture;
  GList *textures;
Alexander Larsson's avatar
Alexander Larsson committed
162

Alexander Larsson's avatar
Alexander Larsson committed
163
  gboolean has_depth_buffer;
Alexander Larsson's avatar
Alexander Larsson committed
164
165
  gboolean has_stencil_buffer;

166
  gboolean needs_resize;
Alexander Larsson's avatar
Alexander Larsson committed
167
168
  gboolean needs_render;
  gboolean auto_render;
169
  gboolean use_es;
Alexander Larsson's avatar
Alexander Larsson committed
170
171
172
173
174
175
176
} GtkGLAreaPrivate;

enum {
  PROP_0,

  PROP_CONTEXT,
  PROP_HAS_DEPTH_BUFFER,
Alexander Larsson's avatar
Alexander Larsson committed
177
  PROP_HAS_STENCIL_BUFFER,
178
  PROP_USE_ES,
Alexander Larsson's avatar
Alexander Larsson committed
179

Alexander Larsson's avatar
Alexander Larsson committed
180
  PROP_AUTO_RENDER,
181

Alexander Larsson's avatar
Alexander Larsson committed
182
183
184
185
186
187
188
  LAST_PROP
};

static GParamSpec *obj_props[LAST_PROP] = { NULL, };

enum {
  RENDER,
189
  RESIZE,
190
  CREATE_CONTEXT,
Alexander Larsson's avatar
Alexander Larsson committed
191
192
193
194

  LAST_SIGNAL
};

195
static void gtk_gl_area_allocate_buffers (GtkGLArea *area);
196
static void gtk_gl_area_allocate_texture (GtkGLArea *area);
Alexander Larsson's avatar
Alexander Larsson committed
197

Alexander Larsson's avatar
Alexander Larsson committed
198
199
200
201
202
203
204
205
206
207
static guint area_signals[LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE_WITH_PRIVATE (GtkGLArea, gtk_gl_area, GTK_TYPE_WIDGET)

static void
gtk_gl_area_set_property (GObject      *gobject,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
208
209
  GtkGLArea *self = GTK_GL_AREA (gobject);

Alexander Larsson's avatar
Alexander Larsson committed
210
211
  switch (prop_id)
    {
Alexander Larsson's avatar
Alexander Larsson committed
212
    case PROP_AUTO_RENDER:
213
      gtk_gl_area_set_auto_render (self, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
214
215
      break;

Alexander Larsson's avatar
Alexander Larsson committed
216
    case PROP_HAS_DEPTH_BUFFER:
217
      gtk_gl_area_set_has_depth_buffer (self, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
218
219
      break;

Alexander Larsson's avatar
Alexander Larsson committed
220
    case PROP_HAS_STENCIL_BUFFER:
221
      gtk_gl_area_set_has_stencil_buffer (self, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
222
223
      break;

224
225
226
227
    case PROP_USE_ES:
      gtk_gl_area_set_use_es (self, g_value_get_boolean (value));
      break;

Alexander Larsson's avatar
Alexander Larsson committed
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_gl_area_get_property (GObject    *gobject,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (GTK_GL_AREA (gobject));

  switch (prop_id)
    {
Alexander Larsson's avatar
Alexander Larsson committed
243
244
245
246
    case PROP_AUTO_RENDER:
      g_value_set_boolean (value, priv->auto_render);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
247
248
249
250
    case PROP_HAS_DEPTH_BUFFER:
      g_value_set_boolean (value, priv->has_depth_buffer);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
251
252
253
254
    case PROP_HAS_STENCIL_BUFFER:
      g_value_set_boolean (value, priv->has_stencil_buffer);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
255
256
257
258
    case PROP_CONTEXT:
      g_value_set_object (value, priv->context);
      break;

259
260
261
262
    case PROP_USE_ES:
      g_value_set_boolean (value, priv->use_es);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
263
264
265
266
267
268
269
270
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_gl_area_realize (GtkWidget *widget)
{
271
272
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
273
274
275

  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->realize (widget);

276
277
278
279
280
281
282
283
284
  g_clear_error (&priv->error);
  priv->context = NULL;
  g_signal_emit (area, area_signals[CREATE_CONTEXT], 0, &priv->context);

  /* In case the signal failed, but did not set an error */
  if (priv->context == NULL && priv->error == NULL)
    g_set_error_literal (&priv->error, GDK_GL_ERROR,
                         GDK_GL_ERROR_NOT_AVAILABLE,
                         _("OpenGL context creation failed"));
285
286
287
288

  priv->needs_resize = TRUE;
}

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
static void
gtk_gl_area_notify (GObject    *object,
                    GParamSpec *pspec)
{
  if (strcmp (pspec->name, "scale-factor") == 0)
    {
      GtkGLArea *area = GTK_GL_AREA (object);
      GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

      priv->needs_resize = TRUE;
    }

  if (G_OBJECT_CLASS (gtk_gl_area_parent_class)->notify)
    G_OBJECT_CLASS (gtk_gl_area_parent_class)->notify (object, pspec);
}

305
306
307
308
309
310
311
312
static GdkGLContext *
gtk_gl_area_real_create_context (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
  GtkWidget *widget = GTK_WIDGET (area);
  GError *error = NULL;
  GdkGLContext *context;

313
  context = gdk_surface_create_gl_context (gtk_native_get_surface (gtk_widget_get_native (widget)), &error);
314
  if (error != NULL)
315
316
317
    {
      gtk_gl_area_set_error (area, error);
      g_clear_object (&context);
318
      g_clear_error (&error);
319
320
321
      return NULL;
    }

322
  gdk_gl_context_set_use_es (context, priv->use_es);
323
324
325
  gdk_gl_context_set_required_version (context,
                                       priv->required_gl_version / 10,
                                       priv->required_gl_version % 10);
326

327
  gdk_gl_context_realize (context, &error);
328
  if (error != NULL)
329
330
331
    {
      gtk_gl_area_set_error (area, error);
      g_clear_object (&context);
332
      g_clear_error (&error);
333
334
      return NULL;
    }
335
336
337
338

  return context;
}

339
340
341
342
static void
gtk_gl_area_resize (GtkGLArea *area, int width, int height)
{
  glViewport (0, 0, width, height);
Alexander Larsson's avatar
Alexander Larsson committed
343
344
345
346
347
348
}

/*
 * Creates all the buffer objects needed for rendering the scene
 */
static void
349
gtk_gl_area_ensure_buffers (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
350
351
352
353
354
355
356
357
358
359
360
361
362
363
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
  GtkWidget *widget = GTK_WIDGET (area);

  gtk_widget_realize (widget);

  if (priv->context == NULL)
    return;

  if (priv->have_buffers)
    return;

  priv->have_buffers = TRUE;

364
  glGenFramebuffers (1, &priv->frame_buffer);
Alexander Larsson's avatar
Alexander Larsson committed
365

366
367
368
  if ((priv->has_depth_buffer || priv->has_stencil_buffer))
    {
      if (priv->depth_stencil_buffer == 0)
369
        glGenRenderbuffers (1, &priv->depth_stencil_buffer);
370
371
372
373
    }
  else if (priv->depth_stencil_buffer != 0)
    {
      /* Delete old depth/stencil buffer */
374
      glDeleteRenderbuffers (1, &priv->depth_stencil_buffer);
375
376
377
378
      priv->depth_stencil_buffer = 0;
    }

  gtk_gl_area_allocate_buffers (area);
Alexander Larsson's avatar
Alexander Larsson committed
379
380
}

381
382
383
384
385
static void
delete_one_texture (gpointer data)
{
  Texture *texture = data;

386
  if (texture->holder)
387
    gdk_gl_texture_release (GDK_GL_TEXTURE (texture->holder));
388

389
390
391
392
393
  if (texture->id != 0)
    {
      glDeleteTextures (1, &texture->id);
      texture->id = 0;
    }
394

395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
  g_free (texture);
}

static void
gtk_gl_area_ensure_texture (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
  GtkWidget *widget = GTK_WIDGET (area);

  gtk_widget_realize (widget);

  if (priv->context == NULL)
    return;

  if (priv->texture == NULL)
    {
      GList *l, *link;

      l = priv->textures;
      while (l)
        {
          Texture *texture = l->data;
          link = l;
          l = l->next;

          if (texture->holder)
            continue;

          priv->textures = g_list_delete_link (priv->textures, link);

          if (priv->texture == NULL)
            priv->texture = texture;
          else
            delete_one_texture (texture);
        }
    }

  if (priv->texture == NULL)
    {
      priv->texture = g_new (Texture, 1);

      priv->texture->width = 0;
      priv->texture->height = 0;
      priv->texture->holder = NULL;

      glGenTextures (1, &priv->texture->id);
    }

  gtk_gl_area_allocate_texture (area);
}

Alexander Larsson's avatar
Alexander Larsson committed
446
447
448
449
/*
 * Allocates space of the right type and size for all the buffers
 */
static void
450
gtk_gl_area_allocate_buffers (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
451
452
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
453
454
  GtkWidget *widget = GTK_WIDGET (area);
  int scale, width, height;
Alexander Larsson's avatar
Alexander Larsson committed
455
456
457
458

  if (priv->context == NULL)
    return;

459
  scale = gtk_widget_get_scale_factor (widget);
460
461
  width = gtk_widget_get_width (widget) * scale;
  height = gtk_widget_get_height (widget) * scale;
462

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
  if (priv->has_depth_buffer || priv->has_stencil_buffer)
    {
      glBindRenderbuffer (GL_RENDERBUFFER, priv->depth_stencil_buffer);
      if (priv->has_stencil_buffer)
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
      else
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
    }

  priv->needs_render = TRUE;
}

static void
gtk_gl_area_allocate_texture (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
  GtkWidget *widget = GTK_WIDGET (area);
  int scale, width, height;

  if (priv->context == NULL)
    return;

  if (priv->texture == NULL)
    return;

  g_assert (priv->texture->holder == NULL);

  scale = gtk_widget_get_scale_factor (widget);
  width = gtk_widget_get_width (widget) * scale;
  height = gtk_widget_get_height (widget) * scale;

  if (priv->texture->width != width ||
      priv->texture->height != height)
Alexander Larsson's avatar
Alexander Larsson committed
496
    {
497
      glBindTexture (GL_TEXTURE_2D, priv->texture->id);
Alexander Larsson's avatar
Alexander Larsson committed
498
499
500
501
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
502
503
504
505
506

      if (gdk_gl_context_get_use_es (priv->context))
        glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
      else
        glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
Alexander Larsson's avatar
Alexander Larsson committed
507

508
509
      priv->texture->width = width;
      priv->texture->height = height;
Alexander Larsson's avatar
Alexander Larsson committed
510
511
512
    }
}

Alexander Larsson's avatar
Alexander Larsson committed
513
514
/**
 * gtk_gl_area_attach_buffers:
Matthias Clasen's avatar
Matthias Clasen committed
515
516
517
 * @area: a `GtkGLArea`
 *
 * Binds buffers to the framebuffer.
Alexander Larsson's avatar
Alexander Larsson committed
518
519
520
 *
 * Ensures that the @area framebuffer object is made the current draw
 * and read target, and that all the required buffers for the @area
521
 * are created and bound to the framebuffer.
Alexander Larsson's avatar
Alexander Larsson committed
522
523
 *
 * This function is automatically called before emitting the
Matthias Clasen's avatar
Matthias Clasen committed
524
525
 * [signal@Gtk.GLArea::render] signal, and doesn't normally need to be
 * called by application code.
Alexander Larsson's avatar
Alexander Larsson committed
526
527
528
529
530
531
 */
void
gtk_gl_area_attach_buffers (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

532
533
534
535
536
  g_return_if_fail (GTK_IS_GL_AREA (area));

  if (priv->context == NULL)
    return;

Alexander Larsson's avatar
Alexander Larsson committed
537
538
  gtk_gl_area_make_current (area);

539
540
541
542
543
  if (priv->texture == NULL)
    gtk_gl_area_ensure_texture (area);
  else if (priv->needs_resize)
    gtk_gl_area_allocate_texture (area);

Alexander Larsson's avatar
Alexander Larsson committed
544
  if (!priv->have_buffers)
545
546
547
    gtk_gl_area_ensure_buffers (area);
  else if (priv->needs_resize)
    gtk_gl_area_allocate_buffers (area);
Alexander Larsson's avatar
Alexander Larsson committed
548

549
  glBindFramebuffer (GL_FRAMEBUFFER, priv->frame_buffer);
Alexander Larsson's avatar
Alexander Larsson committed
550

551
  if (priv->texture != NULL)
552
    glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
553
                            GL_TEXTURE_2D, priv->texture->id, 0);
Alexander Larsson's avatar
Alexander Larsson committed
554
555
556
557

  if (priv->depth_stencil_buffer)
    {
      if (priv->has_depth_buffer)
558
559
        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                   GL_RENDERBUFFER, priv->depth_stencil_buffer);
Alexander Larsson's avatar
Alexander Larsson committed
560
      if (priv->has_stencil_buffer)
561
562
        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                   GL_RENDERBUFFER, priv->depth_stencil_buffer);
Alexander Larsson's avatar
Alexander Larsson committed
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
    }
}

static void
gtk_gl_area_delete_buffers (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  if (priv->context == NULL)
    return;

  priv->have_buffers = FALSE;

  if (priv->depth_stencil_buffer != 0)
    {
578
      glDeleteRenderbuffers (1, &priv->depth_stencil_buffer);
579
580
      priv->depth_stencil_buffer = 0;
    }
Alexander Larsson's avatar
Alexander Larsson committed
581

582
583
  if (priv->frame_buffer != 0)
    {
584
585
      glBindFramebuffer (GL_FRAMEBUFFER, 0);
      glDeleteFramebuffers (1, &priv->frame_buffer);
Alexander Larsson's avatar
Alexander Larsson committed
586
587
588
589
      priv->frame_buffer = 0;
    }
}

590
591
592
593
594
static void
gtk_gl_area_delete_textures (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

Matthias Clasen's avatar
Matthias Clasen committed
595
596
597
598
599
  if (priv->texture)
    {
      delete_one_texture (priv->texture);
      priv->texture = NULL;
    }
600
601
602
603
604
605
606
607
608

  /* FIXME: we need to explicitly release all outstanding
   * textures here, otherwise release_texture will get called
   * later and access freed memory.
   */
  g_list_free_full (priv->textures, delete_one_texture);
  priv->textures = NULL;
}

Alexander Larsson's avatar
Alexander Larsson committed
609
610
611
static void
gtk_gl_area_unrealize (GtkWidget *widget)
{
Alexander Larsson's avatar
Alexander Larsson committed
612
613
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
614
615
616

  if (priv->context != NULL)
    {
617
618
619
      gtk_gl_area_make_current (area);
      gtk_gl_area_delete_buffers (area);
      gtk_gl_area_delete_textures (area);
620

621
      /* Make sure to unset the context if current */
622
623
      if (priv->context == gdk_gl_context_get_current ())
        gdk_gl_context_clear_current ();
Alexander Larsson's avatar
Alexander Larsson committed
624
625
    }

626
  g_clear_object (&priv->context);
627
628
  g_clear_error (&priv->error);

Alexander Larsson's avatar
Alexander Larsson committed
629
630
631
632
  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unrealize (widget);
}

static void
633
634
635
636
gtk_gl_area_size_allocate (GtkWidget *widget,
                           int        width,
                           int        height,
                           int        baseline)
Alexander Larsson's avatar
Alexander Larsson committed
637
{
Alexander Larsson's avatar
Alexander Larsson committed
638
639
640
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

641
  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->size_allocate (widget, width, height, baseline);
Alexander Larsson's avatar
Alexander Larsson committed
642

643
  if (gtk_widget_get_realized (widget))
644
    priv->needs_resize = TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
645
646
}

647
static void
648
649
gtk_gl_area_draw_error_screen (GtkGLArea   *area,
                               GtkSnapshot *snapshot,
Benjamin Otte's avatar
Benjamin Otte committed
650
651
                               int          width,
                               int          height)
652
{
Alexander Larsson's avatar
Alexander Larsson committed
653
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
654
655
656
  PangoLayout *layout;
  int layout_height;

Alexander Larsson's avatar
Alexander Larsson committed
657
  layout = gtk_widget_create_pango_layout (GTK_WIDGET (area),
658
659
660
661
                                           priv->error->message);
  pango_layout_set_width (layout, width * PANGO_SCALE);
  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
  pango_layout_get_pixel_size (layout, NULL, &layout_height);
662
663
664
665
666

  gtk_snapshot_render_layout (snapshot,
                              gtk_widget_get_style_context (GTK_WIDGET (area)),
                              0, (height - layout_height) / 2,
                              layout);
667
668
669
670

  g_object_unref (layout);
}

671
672
673
674
675
676
677
static void
release_texture (gpointer data)
{
  Texture *texture = data;
  texture->holder = NULL;
}

678
679
680
static void
gtk_gl_area_snapshot (GtkWidget   *widget,
                      GtkSnapshot *snapshot)
Alexander Larsson's avatar
Alexander Larsson committed
681
{
Alexander Larsson's avatar
Alexander Larsson committed
682
683
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
684
685
686
687
  gboolean unused;
  int w, h, scale;
  GLenum status;

Matthias Clasen's avatar
Matthias Clasen committed
688
689
690
691
692
693
694
  scale = gtk_widget_get_scale_factor (widget);
  w = gtk_widget_get_width (widget) * scale;
  h = gtk_widget_get_height (widget) * scale;

  if (w == 0 || h == 0)
    return;

695
  if (priv->error != NULL)
696
    {
Alexander Larsson's avatar
Alexander Larsson committed
697
      gtk_gl_area_draw_error_screen (area,
698
                                     snapshot,
699
700
                                     gtk_widget_get_width (widget),
                                     gtk_widget_get_height (widget));
701
      return;
702
    }
Alexander Larsson's avatar
Alexander Larsson committed
703

704
  if (priv->context == NULL)
705
    return;
706

Alexander Larsson's avatar
Alexander Larsson committed
707
708
709
710
711
712
713
714
  gtk_gl_area_make_current (area);

  gtk_gl_area_attach_buffers (area);

 if (priv->has_depth_buffer)
   glEnable (GL_DEPTH_TEST);
 else
   glDisable (GL_DEPTH_TEST);
Alexander Larsson's avatar
Alexander Larsson committed
715

716
717
  status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
  if (status == GL_FRAMEBUFFER_COMPLETE)
Alexander Larsson's avatar
Alexander Larsson committed
718
    {
719
      Texture *texture;
720

Alexander Larsson's avatar
Alexander Larsson committed
721
      if (priv->needs_render || priv->auto_render)
Matthias Clasen's avatar
Matthias Clasen committed
722
723
724
725
726
727
        {
          if (priv->needs_resize)
            {
              g_signal_emit (area, area_signals[RESIZE], 0, w, h, NULL);
              priv->needs_resize = FALSE;
            }
728

Matthias Clasen's avatar
Matthias Clasen committed
729
730
          g_signal_emit (area, area_signals[RENDER], 0, priv->context, &unused);
        }
731

Alexander Larsson's avatar
Alexander Larsson committed
732
      priv->needs_render = FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
733

734
735
736
737
      texture = priv->texture;
      priv->texture = NULL;
      priv->textures = g_list_prepend (priv->textures, texture);

Benjamin Otte's avatar
Benjamin Otte committed
738
739
740
741
742
      texture->holder = gdk_gl_texture_new (priv->context,
                                            texture->id,
                                            texture->width,
                                            texture->height,
                                            release_texture, texture);
743

Matthias Clasen's avatar
Matthias Clasen committed
744
745
746
747
748
749
      /* Our texture is rendered by OpenGL, so it is upside down,
       * compared to what GSK expects, so flip it back.
       */
      gtk_snapshot_save (snapshot);
      gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, gtk_widget_get_height (widget)));
      gtk_snapshot_scale (snapshot, 1, -1);
750
      gtk_snapshot_append_texture (snapshot,
751
                                   texture->holder,
752
753
                                   &GRAPHENE_RECT_INIT (0, 0,
                                                        gtk_widget_get_width (widget),
Benjamin Otte's avatar
Benjamin Otte committed
754
                                                        gtk_widget_get_height (widget)));
Matthias Clasen's avatar
Matthias Clasen committed
755
      gtk_snapshot_restore (snapshot);
756

757
      g_object_unref (texture->holder);
Alexander Larsson's avatar
Alexander Larsson committed
758
759
760
    }
  else
    {
Matthias Clasen's avatar
Matthias Clasen committed
761
      g_warning ("fb setup not supported (%x)", status);
Alexander Larsson's avatar
Alexander Larsson committed
762
763
764
    }
}

765
766
767
768
769
770
771
772
773
774
775
776
static gboolean
create_context_accumulator (GSignalInvocationHint *ihint,
                            GValue *return_accu,
                            const GValue *handler_return,
                            gpointer data)
{
  g_value_copy (handler_return, return_accu);

  /* stop after the first handler returning a valid object */
  return g_value_get_object (handler_return) == NULL;
}

Alexander Larsson's avatar
Alexander Larsson committed
777
778
779
780
781
782
static void
gtk_gl_area_class_init (GtkGLAreaClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

783
  klass->resize = gtk_gl_area_resize;
784
  klass->create_context = gtk_gl_area_real_create_context;
785

Alexander Larsson's avatar
Alexander Larsson committed
786
787
788
  widget_class->realize = gtk_gl_area_realize;
  widget_class->unrealize = gtk_gl_area_unrealize;
  widget_class->size_allocate = gtk_gl_area_size_allocate;
789
  widget_class->snapshot = gtk_gl_area_snapshot;
Alexander Larsson's avatar
Alexander Larsson committed
790
791
792
793

  /**
   * GtkGLArea:context:
   *
Matthias Clasen's avatar
Matthias Clasen committed
794
   * The `GdkGLContext` used by the `GtkGLArea` widget.
Alexander Larsson's avatar
Alexander Larsson committed
795
   *
Matthias Clasen's avatar
Matthias Clasen committed
796
   * The `GtkGLArea` widget is responsible for creating the `GdkGLContext`
797
798
   * instance. If you need to render with other kinds of buffers (stencil,
   * depth, etc), use render buffers.
Alexander Larsson's avatar
Alexander Larsson committed
799
800
801
802
803
804
805
806
807
   */
  obj_props[PROP_CONTEXT] =
    g_param_spec_object ("context",
                         P_("Context"),
                         P_("The GL context"),
                         GDK_TYPE_GL_CONTEXT,
                         G_PARAM_READABLE |
                         G_PARAM_STATIC_STRINGS);

Alexander Larsson's avatar
Alexander Larsson committed
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)
Alexander Larsson's avatar
Alexander Larsson committed
810
   *
Matthias Clasen's avatar
Matthias Clasen committed
811
812
813
814
   * If set to %TRUE the ::render signal will be emitted every time
   * the widget draws.
   *
   * This is the default and is useful if drawing the widget is faster.
Alexander Larsson's avatar
Alexander Larsson committed
815
   *
Matthias Clasen's avatar
Matthias Clasen committed
816
817
   * If set to %FALSE the data from previous rendering is kept around and will
   * be used for drawing the widget the next time, unless the window is resized.
Matthias Clasen's avatar
Matthias Clasen committed
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
Matthias Clasen's avatar
Matthias Clasen committed
820
   * to redraw.
Alexander Larsson's avatar
Alexander Larsson committed
821
822
823
824
   */
  obj_props[PROP_AUTO_RENDER] =
    g_param_spec_boolean ("auto-render",
                          P_("Auto render"),
Matthias Clasen's avatar
Matthias Clasen committed
825
                          P_("Whether the GtkGLArea renders on each redraw"),
Alexander Larsson's avatar
Alexander Larsson committed
826
                          TRUE,
827
828
829
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
830

Alexander Larsson's avatar
Alexander Larsson committed
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)
Alexander Larsson's avatar
Alexander Larsson committed
833
   *
Matthias Clasen's avatar
Matthias Clasen committed
834
835
   * If set to %TRUE the widget will allocate and enable a depth buffer for the
   * target framebuffer.
Alexander Larsson's avatar
Alexander Larsson committed
836
837
838
839
840
841
   */
  obj_props[PROP_HAS_DEPTH_BUFFER] =
    g_param_spec_boolean ("has-depth-buffer",
                          P_("Has depth buffer"),
                          P_("Whether a depth buffer is allocated"),
                          FALSE,
842
843
844
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
845

Alexander Larsson's avatar
Alexander Larsson committed
846
  /**
847
   * 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)
Alexander Larsson's avatar
Alexander Larsson committed
848
   *
Matthias Clasen's avatar
Matthias Clasen committed
849
850
   * If set to %TRUE the widget will allocate and enable a stencil buffer for the
   * target framebuffer.
Alexander Larsson's avatar
Alexander Larsson committed
851
852
853
854
855
856
   */
  obj_props[PROP_HAS_STENCIL_BUFFER] =
    g_param_spec_boolean ("has-stencil-buffer",
                          P_("Has stencil buffer"),
                          P_("Whether a stencil buffer is allocated"),
                          FALSE,
857
858
859
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
860

861
  /**
862
   * GtkGLArea:use-es: (attributes org.gtk.Property.get=gtk_gl_area_get_use_es org.gtk.Property.set=gtk_gl_area_set_use_es)
863
   *
Matthias Clasen's avatar
Matthias Clasen committed
864
   * If set to %TRUE the widget will try to create a `GdkGLContext` using
865
866
867
868
869
870
871
872
873
874
875
   * OpenGL ES instead of OpenGL.
   */
  obj_props[PROP_USE_ES] =
    g_param_spec_boolean ("use-es",
                          P_("Use OpenGL ES"),
                          P_("Whether the context uses OpenGL or OpenGL ES"),
                          FALSE,
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);

Alexander Larsson's avatar
Alexander Larsson committed
876
877
  gobject_class->set_property = gtk_gl_area_set_property;
  gobject_class->get_property = gtk_gl_area_get_property;
878
  gobject_class->notify = gtk_gl_area_notify;
Alexander Larsson's avatar
Alexander Larsson committed
879
880
881
882
883

  g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);

  /**
   * GtkGLArea::render:
Matthias Clasen's avatar
Matthias Clasen committed
884
885
   * @area: the `GtkGLArea` that emitted the signal
   * @context: the `GdkGLContext` used by @area
Alexander Larsson's avatar
Alexander Larsson committed
886
   *
Matthias Clasen's avatar
Matthias Clasen committed
887
   * Emitted every time the contents of the `GtkGLArea` should be redrawn.
Alexander Larsson's avatar
Alexander Larsson committed
888
889
890
891
892
893
894
895
896
   *
   * The @context is bound to the @area prior to emitting this function,
   * and the buffers are painted to the window once the emission terminates.
   *
   * Returns: %TRUE to stop other handlers from being invoked for the event.
   *   %FALSE to propagate the event further.
   */
  area_signals[RENDER] =
    g_signal_new (I_("render"),
Matthias Clasen's avatar
Matthias Clasen committed
897
898
899
900
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkGLAreaClass, render),
                  _gtk_boolean_handled_accumulator, NULL,
901
                  _gtk_marshal_BOOLEAN__OBJECT,
Matthias Clasen's avatar
Matthias Clasen committed
902
903
                  G_TYPE_BOOLEAN, 1,
                  GDK_TYPE_GL_CONTEXT);
904
905
906
  g_signal_set_va_marshaller (area_signals[RENDER],
                              G_TYPE_FROM_CLASS (klass),
                              _gtk_marshal_BOOLEAN__OBJECTv);
907
908

  /**
909
   * GtkGLArea::resize:
Matthias Clasen's avatar
Matthias Clasen committed
910
   * @area: the `GtkGLArea` that emitted the signal
911
912
   * @width: the width of the viewport
   * @height: the height of the viewport
913
   *
Matthias Clasen's avatar
Matthias Clasen committed
914
915
916
917
918
919
   * Emitted once when the widget is realized, and then each time the widget
   * is changed while realized.
   *
   * This is useful in order to keep GL state up to date with the widget size,
   * like for instance camera properties which may depend on the width/height
   * ratio.
920
   *
Matthias Clasen's avatar
Matthias Clasen committed
921
922
   * The GL context for the area is guaranteed to be current when this signal
   * is emitted.
923
   *
Matthias Clasen's avatar
Matthias Clasen committed
924
   * The default handler sets up the GL viewport.
925
926
   */
  area_signals[RESIZE] =
927
    g_signal_new (I_("resize"),
928
929
930
931
932
933
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkGLAreaClass, resize),
                  NULL, NULL,
                  _gtk_marshal_VOID__INT_INT,
                  G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
934
935
936
  g_signal_set_va_marshaller (area_signals[RESIZE],
                              G_TYPE_FROM_CLASS (klass),
                              _gtk_marshal_VOID__INT_INTv);
937

938
939
  /**
   * GtkGLArea::create-context:
Matthias Clasen's avatar
Matthias Clasen committed
940
   * @area: the `GtkGLArea` that emitted the signal
941
   * @error: (nullable): location to store error information on failure
942
   *
Matthias Clasen's avatar
Matthias Clasen committed
943
944
945
946
947
   * Emitted when the widget is being realized.
   *
   * This allows you to override how the GL context is created.
   * This is useful when you want to reuse an existing GL context,
   * or if you want to try creating different kinds of GL options.
948
949
   *
   * If context creation fails then the signal handler can use
Matthias Clasen's avatar
Matthias Clasen committed
950
   * [method@Gtk.GLArea.set_error] to register a more detailed error
951
952
   * of how the construction failed.
   *
Matthias Clasen's avatar
Matthias Clasen committed
953
954
   * Returns: (transfer full): a newly created `GdkGLContext`;
   *     the `GtkGLArea` widget will take ownership of the returned value.
955
956
   */
  area_signals[CREATE_CONTEXT] =
957
    g_signal_new (I_("create-context"),
958
959
960
961
962
963
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkGLAreaClass, create_context),
                  create_context_accumulator, NULL,
                  _gtk_marshal_OBJECT__VOID,
                  GDK_TYPE_GL_CONTEXT, 0);
964
965
966
  g_signal_set_va_marshaller (area_signals[CREATE_CONTEXT],
                              G_TYPE_FROM_CLASS (klass),
                              _gtk_marshal_OBJECT__VOIDv);
Alexander Larsson's avatar
Alexander Larsson committed
967
968
969
}

static void
Alexander Larsson's avatar
Alexander Larsson committed
970
gtk_gl_area_init (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
971
{
Alexander Larsson's avatar
Alexander Larsson committed
972
973
974
975
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  priv->auto_render = TRUE;
  priv->needs_render = TRUE;
976
  priv->required_gl_version = 0;
Alexander Larsson's avatar
Alexander Larsson committed
977
978
979
980
981
}

/**
 * gtk_gl_area_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
982
 * Creates a new `GtkGLArea` widget.
Alexander Larsson's avatar
Alexander Larsson committed
983
 *
Matthias Clasen's avatar
Matthias Clasen committed
984
 * Returns: a new `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
985
986
987
988
 */
GtkWidget *
gtk_gl_area_new (void)
{
Matthias Clasen's avatar
Matthias Clasen committed
989
  return g_object_new (GTK_TYPE_GL_AREA, NULL);
Alexander Larsson's avatar
Alexander Larsson committed
990
991
}

992
993
/**
 * gtk_gl_area_set_error:
Matthias Clasen's avatar
Matthias Clasen committed
994
 * @area: a `GtkGLArea`
995
 * @error: (nullable): a new `GError`, or %NULL to unset the error
996
997
 *
 * Sets an error on the area which will be shown instead of the
Matthias Clasen's avatar
Matthias Clasen committed
998
999
1000
 * GL rendering.
 *
 * This is useful in the [signal@Gtk.GLArea::create-context]
Matthias Clasen's avatar
Matthias Clasen committed
1001
 * signal if GL context creation fails.
1002
1003
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
1004
gtk_gl_area_set_error (GtkGLArea    *area,
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
                       const GError *error)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  g_clear_error (&priv->error);
  if (error)
    priv->error = g_error_copy (error);
}

/**
 * gtk_gl_area_get_error:
Matthias Clasen's avatar
Matthias Clasen committed
1018
 * @area: a `GtkGLArea`
1019
1020
1021
 *
 * Gets the current error set on the @area.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1022
 * Returns: (nullable) (transfer none): the `GError`
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
 */
GError *
gtk_gl_area_get_error (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);

  return priv->error;
}

1034
/**
1035
 * gtk_gl_area_set_use_es: (attributes org.gtk.Method.set_property=use-es)
Matthias Clasen's avatar
Matthias Clasen committed
1036
 * @area: a `GtkGLArea`
1037
1038
1039
1040
 * @use_es: whether to use OpenGL or OpenGL ES
 *
 * Sets whether the @area should create an OpenGL or an OpenGL ES context.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1041
 * You should check the capabilities of the `GdkGLContext` before drawing
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
 * with either API.
 */
void
gtk_gl_area_set_use_es (GtkGLArea *area,
                        gboolean   use_es)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));
  g_return_if_fail (!gtk_widget_get_realized (GTK_WIDGET (area)));

  use_es = !!use_es;

  if (priv->use_es != use_es)
    {
      priv->use_es = use_es;

      g_object_notify_by_pspec (G_OBJECT (area), obj_props[PROP_USE_ES]);
    }
}

/**
1064
 * gtk_gl_area_get_use_es: (attributes org.gtk.Method.get_property=use-es)
Matthias Clasen's avatar
Matthias Clasen committed
1065
 * @area: a `GtkGLArea`
1066
 *
Matthias Clasen's avatar
Matthias Clasen committed
1067
 * Returns whether the `GtkGLArea` should use OpenGL ES.
1068
 *
Matthias Clasen's avatar
Matthias Clasen committed
1069
1070
1071
 * See [method@Gtk.GLArea.set_use_es].
 *
 * Returns: %TRUE if the `GtkGLArea` should create an OpenGL ES context
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
 *   and %FALSE otherwise
 */
gboolean
gtk_gl_area_get_use_es (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);

  return priv->use_es;
}

1084
1085
/**
 * gtk_gl_area_set_required_version:
Matthias Clasen's avatar
Matthias Clasen committed
1086
 * @area: a `GtkGLArea`
Matthias Clasen's avatar
Matthias Clasen committed
1087
1088
 * @major: the major version
 * @minor: the minor version
1089
 *
Matthias Clasen's avatar
Matthias Clasen committed
1090
1091
 * Sets the required version of OpenGL to be used when creating
 * the context for the widget.
1092
1093
1094
1095
1096
 *
 * This function must be called before the area has been realized.
 */
void
gtk_gl_area_set_required_version (GtkGLArea *area,
Benjamin Otte's avatar
Benjamin Otte committed
1097
1098
                                  int        major,
                                  int        minor)
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));
  g_return_if_fail (!gtk_widget_get_realized (GTK_WIDGET (area)));

  priv->required_gl_version = major * 10 + minor;
}

/**
 * gtk_gl_area_get_required_version:
Matthias Clasen's avatar
Matthias Clasen committed
1110
 * @area: a `GtkGLArea`
1111
1112
1113
 * @major: (out): return location for the required major version
 * @minor: (out): return location for the required minor version
 *
Matthias Clasen's avatar
Matthias Clasen committed
1114
1115
1116
 * Retrieves the required version of OpenGL.
 *
 * See [method@Gtk.GLArea.set_required_version].
1117
1118
1119
 */
void
gtk_gl_area_get_required_version (GtkGLArea *area,
Benjamin Otte's avatar
Benjamin Otte committed
1120
1121
                                  int       *major,
                                  int       *minor)
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  if (major != NULL)
    *major = priv->required_gl_version / 10;
  if (minor != NULL)
    *minor = priv->required_gl_version % 10;
}

Alexander Larsson's avatar
Alexander Larsson committed
1133
/**
1134
 * gtk_gl_area_get_has_depth_buffer: (attributes org.gtk.Method.get_property=has-depth-buffer)
Matthias Clasen's avatar
Matthias Clasen committed
1135
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1136
 *
Matthias Clasen's avatar
Matthias Clasen committed
1137
1138
1139
 * Returns whether the area has a depth buffer.
 *
 * Returns: %TRUE if the @area has a depth buffer, %FALSE otherwise
Alexander Larsson's avatar
Alexander Larsson committed
1140
1141
 */
gboolean
Matthias Clasen's avatar
Matthias Clasen committed
1142
gtk_gl_area_get_has_depth_buffer (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
1143
1144
1145
1146
1147
1148
1149
1150
1151
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);

  return priv->has_depth_buffer;
}

/**
1152
 * gtk_gl_area_set_has_depth_buffer: (attributes org.gtk.Method.set_property=has-depth-buffer)
Matthias Clasen's avatar
Matthias Clasen committed
1153
 * @area: a `GtkGLArea`
Matthias Clasen's avatar
Matthias Clasen committed
1154
 * @has_depth_buffer: %TRUE to add a depth buffer
Alexander Larsson's avatar
Alexander Larsson committed
1155
 *
Matthias Clasen's avatar
Matthias Clasen committed
1156
1157
 * Sets whether the `GtkGLArea` should use a depth buffer.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1158
1159
1160
 * If @has_depth_buffer is %TRUE the widget will allocate and
 * enable a depth buffer for the target framebuffer. Otherwise
 * there will be none.
Alexander Larsson's avatar
Alexander Larsson committed
1161
1162
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
1163
1164
gtk_gl_area_set_has_depth_buffer (GtkGLArea *area,
                                  gboolean   has_depth_buffer)
Alexander Larsson's avatar
Alexander Larsson committed
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  has_depth_buffer = !!has_depth_buffer;

  if (priv->has_depth_buffer != has_depth_buffer)
    {
      priv->has_depth_buffer = has_depth_buffer;

      g_object_notify (G_OBJECT (area), "has-depth-buffer");
Alexander Larsson's avatar
Alexander Larsson committed
1177

1178
      priv->have_buffers = FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
1179
1180
1181
1182
    }
}

/**
1183
 * gtk_gl_area_get_has_stencil_buffer: (attributes org.gtk.Method.get_property=has-stencil-buffer)
Matthias Clasen's avatar
Matthias Clasen committed
1184
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
 *
 * Returns whether the area has a stencil buffer.
 *
 * Returns: %TRUE if the @area has a stencil buffer, %FALSE otherwise
 */
gboolean
gtk_gl_area_get_has_stencil_buffer (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);

  return priv->has_stencil_buffer;
}

/**
1201
 * gtk_gl_area_set_has_stencil_buffer: (attributes org.gtk.Method.set_property=has-stencil-buffer)
Matthias Clasen's avatar
Matthias Clasen committed
1202
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1203
1204
 * @has_stencil_buffer: %TRUE to add a stencil buffer
 *
Matthias Clasen's avatar
Matthias Clasen committed
1205
1206
 * Sets whether the `GtkGLArea` should use a stencil buffer.
 *
Alexander Larsson's avatar
Alexander Larsson committed
1207
1208
1209
1210
1211
1212
 * If @has_stencil_buffer is %TRUE the widget will allocate and
 * enable a stencil buffer for the target framebuffer. Otherwise
 * there will be none.
 */
void
gtk_gl_area_set_has_stencil_buffer (GtkGLArea *area,
Matthias Clasen's avatar
Matthias Clasen committed
1213
                                    gboolean   has_stencil_buffer)
Alexander Larsson's avatar
Alexander Larsson committed
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  has_stencil_buffer = !!has_stencil_buffer;

  if (priv->has_stencil_buffer != has_stencil_buffer)
    {
      priv->has_stencil_buffer = has_stencil_buffer;

      g_object_notify (G_OBJECT (area), "has-stencil-buffer");

1227
      priv->have_buffers = FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
1228
1229
1230
1231
1232
    }
}

/**
 * gtk_gl_area_queue_render:
Matthias Clasen's avatar
Matthias Clasen committed
1233
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1234
 *
Matthias Clasen's avatar
Matthias Clasen committed
1235
 * Marks the currently rendered data (if any) as invalid, and queues
Matthias Clasen's avatar
Matthias Clasen committed
1236
1237
1238
 * a redraw of the widget.
 *
 * This ensures that the [signal@Gtk.GLArea::render] signal
Alexander Larsson's avatar
Alexander Larsson committed
1239
1240
 * is emitted during the draw.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1241
 * This is only needed when [method@Gtk.GLArea.set_auto_render] has
Alexander Larsson's avatar
Alexander Larsson committed
1242
 * been called with a %FALSE value. The default behaviour is to
Matthias Clasen's avatar
Matthias Clasen committed
1243
 * emit [signal@Gtk.GLArea::render] on each draw.
Alexander Larsson's avatar
Alexander Larsson committed
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
 */
void
gtk_gl_area_queue_render (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  priv->needs_render = TRUE;

  gtk_widget_queue_draw (GTK_WIDGET (area));
}


/**
1259
 * gtk_gl_area_get_auto_render: (attributes org.gtk.Method.get_property=auto-render)
Matthias Clasen's avatar
Matthias Clasen committed
1260
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
 *
 * Returns whether the area is in auto render mode or not.
 *
 * Returns: %TRUE if the @area is auto rendering, %FALSE otherwise
 */
gboolean
gtk_gl_area_get_auto_render (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);

  return priv->auto_render;
}

/**
1277
 * gtk_gl_area_set_auto_render: (attributes org.gtk.Method.set_property=auto-render)
Matthias Clasen's avatar
Matthias Clasen committed
1278
 * @area: a `GtkGLArea`
Alexander Larsson's avatar
Alexander Larsson committed
1279
1280
 * @auto_render: a boolean
 *
Matthias Clasen's avatar
Matthias Clasen committed
1281
1282
1283
1284
 * Sets whether the `GtkGLArea` is in auto render mode.
 *
 * If @auto_render is %TRUE the [signal@Gtk.GLArea::render] signal will
 * be emitted every time the widget draws. This is the default and is
Matthias Clasen's avatar
Matthias Clasen committed
1285
 * useful if drawing the widget is faster.
Alexander Larsson's avatar
Alexander Larsson committed
1286
1287
1288
1289
 *
 * If @auto_render is %FALSE the data from previous rendering is kept
 * around and will be used for drawing the widget the next time,
 * unless the window is resized. In order to force a rendering
Matthias Clasen's avatar
Matthias Clasen committed
1290
1291
 * [method@Gtk.GLArea.queue_render] must be called. This mode is
 * useful when the scene changes seldom, but takes a long time to redraw.
Alexander Larsson's avatar
Alexander Larsson committed
1292
1293
1294
 */
void
gtk_gl_area_set_auto_render (GtkGLArea *area,
Matthias Clasen's avatar
Matthias Clasen committed
1295
                             gboolean   auto_render)
Alexander Larsson's avatar
Alexander Larsson committed
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);