gtkglarea.c 39.7 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 26 27 28
/* 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"
#include "gtkstylecontext.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
29 30
#include "gtkrender.h"

Alexander Larsson's avatar
Alexander Larsson committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
#include <epoxy/gl.h>

/**
 * SECTION:gtkglarea
 * @Title: GtkGLArea
 * @Short_description: A widget for custom drawing with OpenGL
 *
 * #GtkGLArea is a widget that allows drawing with OpenGL.
 *
 * #GtkGLArea sets up its own #GdkGLContext for the window it creates, 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.
 *
 * In order to draw, you have to connect to the #GtkGLArea::render signal,
 * or subclass #GtkGLArea and override the @GtkGLAreaClass.render() virtual
 * function.
 *
 * The #GtkGLArea widget ensures that the #GdkGLContext is associated with
 * the widget's drawing area, and it is kept updated when the size and
 * position of the drawing area changes.
 *
 * ## Drawing with GtkGLArea ##
 *
 * The simplest way to draw using OpenGL commands in a #GtkGLArea is to
 * create a widget instance and connect to the #GtkGLArea::render signal:
 *
 * |[<!-- language="C" -->
 *   // 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);
 * ]|
 *
 * The `render()` function will be called when the #GtkGLArea is ready
 * for you to draw its content:
 *
 * |[<!-- language="C" -->
 *   static gboolean
 *   render (GtkGLArea *area, GdkGLContext *context)
 *   {
 *     // inside this function it's safe to use GL; the given
 *     // #GdkGLContext has been made current to the drawable
 *     // 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;
 *   }
 * ]|
 *
92
 * If you need to initialize OpenGL state, e.g. buffer objects or
93
 * shaders, you should use the #GtkWidget::realize signal; you
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
 * can use the #GtkWidget::unrealize signal to clean up. Since the
 * #GdkGLContext creation and initialization may fail, you will
 * need to check for errors, using gtk_gl_area_get_error(). An example
 * of how to safely initialize the GL state is:
 *
 * |[<!-- language="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
 *     // function will return a #GError for you to catch
 *     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);
 *         return;
 *       }
 *
 *     init_shaders (&error);
 *     if (error != NULL)
 *       {
 *         gtk_gl_area_set_error (area, error);
 *         g_error_free (error);
 *         return;
 *       }
 *   }
 * ]|
Alexander Larsson's avatar
Alexander Larsson committed
134
 *
135 136
 * If you need to change the options for creating the #GdkGLContext
 * you should use the #GtkGLArea::create-context signal.
Alexander Larsson's avatar
Alexander Larsson committed
137 138 139 140
 */

typedef struct {
  GdkGLContext *context;
141
  GdkWindow *event_window;
142
  GError *error;
143 144 145

  gboolean have_buffers;

146 147
  int required_gl_version;

148 149 150 151 152
  guint frame_buffer;
  guint render_buffer;
  guint texture;
  guint depth_stencil_buffer;

Alexander Larsson's avatar
Alexander Larsson committed
153 154
  gboolean has_alpha;
  gboolean has_depth_buffer;
155 156
  gboolean has_stencil_buffer;

157
  gboolean needs_resize;
158 159
  gboolean needs_render;
  gboolean auto_render;
160
  gboolean use_es;
Alexander Larsson's avatar
Alexander Larsson committed
161 162 163 164 165 166 167 168
} GtkGLAreaPrivate;

enum {
  PROP_0,

  PROP_CONTEXT,
  PROP_HAS_ALPHA,
  PROP_HAS_DEPTH_BUFFER,
169
  PROP_HAS_STENCIL_BUFFER,
170
  PROP_USE_ES,
Alexander Larsson's avatar
Alexander Larsson committed
171

172
  PROP_AUTO_RENDER,
173

Alexander Larsson's avatar
Alexander Larsson committed
174 175 176 177 178 179 180
  LAST_PROP
};

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

enum {
  RENDER,
181
  RESIZE,
182
  CREATE_CONTEXT,
Alexander Larsson's avatar
Alexander Larsson committed
183 184 185 186

  LAST_SIGNAL
};

187
static void gtk_gl_area_allocate_buffers (GtkGLArea *area);
188

Alexander Larsson's avatar
Alexander Larsson committed
189 190 191 192 193 194 195
static guint area_signals[LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE_WITH_PRIVATE (GtkGLArea, gtk_gl_area, GTK_TYPE_WIDGET)

static void
gtk_gl_area_dispose (GObject *gobject)
{
196 197
  GtkGLArea *area = GTK_GL_AREA (gobject);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
198 199 200 201 202 203 204 205 206 207 208 209

  g_clear_object (&priv->context);

  G_OBJECT_CLASS (gtk_gl_area_parent_class)->dispose (gobject);
}

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

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

Alexander Larsson's avatar
Alexander Larsson committed
218
    case PROP_HAS_ALPHA:
219
      gtk_gl_area_set_has_alpha (self, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
220 221 222
      break;

    case PROP_HAS_DEPTH_BUFFER:
223
      gtk_gl_area_set_has_depth_buffer (self, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
224 225
      break;

226
    case PROP_HAS_STENCIL_BUFFER:
227
      gtk_gl_area_set_has_stencil_buffer (self, g_value_get_boolean (value));
228 229
      break;

230 231 232 233
    case PROP_USE_ES:
      gtk_gl_area_set_use_es (self, g_value_get_boolean (value));
      break;

Alexander Larsson's avatar
Alexander Larsson committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    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)
    {
249 250 251 252
    case PROP_AUTO_RENDER:
      g_value_set_boolean (value, priv->auto_render);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
253 254 255 256 257 258 259 260
    case PROP_HAS_ALPHA:
      g_value_set_boolean (value, priv->has_alpha);
      break;

    case PROP_HAS_DEPTH_BUFFER:
      g_value_set_boolean (value, priv->has_depth_buffer);
      break;

261 262 263 264
    case PROP_HAS_STENCIL_BUFFER:
      g_value_set_boolean (value, priv->has_stencil_buffer);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
265 266 267 268
    case PROP_CONTEXT:
      g_value_set_object (value, priv->context);
      break;

269 270 271 272
    case PROP_USE_ES:
      g_value_set_boolean (value, priv->use_es);
      break;

Alexander Larsson's avatar
Alexander Larsson committed
273 274 275 276 277 278 279 280
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_gl_area_realize (GtkWidget *widget)
{
281 282
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
283 284 285
  GtkAllocation allocation;
  GdkWindowAttr attributes;
  gint attributes_mask;
Alexander Larsson's avatar
Alexander Larsson committed
286 287 288

  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->realize (widget);

289 290
  gtk_widget_get_allocation (widget, &allocation);

291 292 293 294 295 296
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
  attributes.wclass = GDK_INPUT_ONLY;
297
  attributes.event_mask = gtk_widget_get_events (widget);
298 299 300 301

  attributes_mask = GDK_WA_X | GDK_WA_Y;

  priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
302
                                       &attributes, attributes_mask);
303 304
  gtk_widget_register_window (widget, priv->event_window);

305 306 307 308 309 310 311 312 313
  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"));
314 315 316 317

  priv->needs_resize = TRUE;
}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
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);
}

334 335 336 337 338 339 340 341
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;

342
  context = gdk_window_create_gl_context (gtk_widget_get_window (widget), &error);
343
  if (error != NULL)
344 345 346
    {
      gtk_gl_area_set_error (area, error);
      g_clear_object (&context);
347
      g_clear_error (&error);
348 349 350
      return NULL;
    }

351
  gdk_gl_context_set_use_es (context, priv->use_es);
352 353 354
  gdk_gl_context_set_required_version (context,
                                       priv->required_gl_version / 10,
                                       priv->required_gl_version % 10);
355

356
  gdk_gl_context_realize (context, &error);
357
  if (error != NULL)
358 359 360
    {
      gtk_gl_area_set_error (area, error);
      g_clear_object (&context);
361
      g_clear_error (&error);
362 363
      return NULL;
    }
364 365 366 367

  return context;
}

368 369 370 371
static void
gtk_gl_area_resize (GtkGLArea *area, int width, int height)
{
  glViewport (0, 0, width, height);
372 373 374 375 376 377
}

/*
 * Creates all the buffer objects needed for rendering the scene
 */
static void
378
gtk_gl_area_ensure_buffers (GtkGLArea *area)
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
{
  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;

  glGenFramebuffersEXT (1, &priv->frame_buffer);

  if (priv->has_alpha)
396 397 398 399 400 401 402 403 404 405 406 407
    {
      /* For alpha we use textures as that is required for blending to work */
      if (priv->texture == 0)
        glGenTextures (1, &priv->texture);

      /* Delete old render buffer if any */
      if (priv->render_buffer != 0)
        {
          glDeleteRenderbuffersEXT(1, &priv->render_buffer);
          priv->render_buffer = 0;
        }
    }
408
  else
409
    {
410
    /* For non-alpha we use render buffers so we can blit instead of texture the result */
411 412
      if (priv->render_buffer == 0)
        glGenRenderbuffersEXT (1, &priv->render_buffer);
413

414 415 416 417 418 419 420
      /* Delete old texture if any */
      if (priv->texture != 0)
        {
          glDeleteTextures(1, &priv->texture);
          priv->texture = 0;
        }
    }
421

422 423 424 425 426 427 428 429 430 431 432 433 434
  if ((priv->has_depth_buffer || priv->has_stencil_buffer))
    {
      if (priv->depth_stencil_buffer == 0)
        glGenRenderbuffersEXT (1, &priv->depth_stencil_buffer);
    }
  else if (priv->depth_stencil_buffer != 0)
    {
      /* Delete old depth/stencil buffer */
      glDeleteRenderbuffersEXT (1, &priv->depth_stencil_buffer);
      priv->depth_stencil_buffer = 0;
    }

  gtk_gl_area_allocate_buffers (area);
435 436 437 438 439 440
}

/*
 * Allocates space of the right type and size for all the buffers
 */
static void
441
gtk_gl_area_allocate_buffers (GtkGLArea *area)
442 443
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
444 445
  GtkWidget *widget = GTK_WIDGET (area);
  int scale, width, height;
446 447 448 449

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

450 451 452 453
  scale = gtk_widget_get_scale_factor (widget);
  width = gtk_widget_get_allocated_width (widget) * scale;
  height = gtk_widget_get_allocated_height (widget) * scale;

454 455 456 457 458 459 460
  if (priv->texture)
    {
      glBindTexture (GL_TEXTURE_2D, priv->texture);
      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);
461 462 463 464 465

      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);
466 467 468 469
    }

  if (priv->render_buffer)
    {
470 471
      glBindRenderbuffer (GL_RENDERBUFFER, priv->render_buffer);
      glRenderbufferStorage (GL_RENDERBUFFER, GL_RGB8, width, height);
472 473 474
    }

  if (priv->has_depth_buffer || priv->has_stencil_buffer)
Alexander Larsson's avatar
Alexander Larsson committed
475
    {
476
      glBindRenderbuffer (GL_RENDERBUFFER, priv->depth_stencil_buffer);
477
      if (priv->has_stencil_buffer)
478
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
479
      else
480
        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
Alexander Larsson's avatar
Alexander Larsson committed
481
    }
482 483

  priv->needs_render = TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
484 485
}

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
/**
 * gtk_gl_area_attach_buffers:
 * @area: a #GtkGLArea
 *
 * Ensures that the @area framebuffer object is made the current draw
 * and read target, and that all the required buffers for the @area
 * are created and bound to the frambuffer.
 *
 * This function is automatically called before emitting the
 * #GtkGLArea::render signal, and doesn't normally need to be called
 * by application code.
 *
 * Since: 3.16
 */
void
gtk_gl_area_attach_buffers (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

505 506 507 508 509
  g_return_if_fail (GTK_IS_GL_AREA (area));

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

510 511 512
  gtk_gl_area_make_current (area);

  if (!priv->have_buffers)
513 514 515
    gtk_gl_area_ensure_buffers (area);
  else if (priv->needs_resize)
    gtk_gl_area_allocate_buffers (area);
516 517 518 519 520

  glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, priv->frame_buffer);

  if (priv->texture)
    glFramebufferTexture2D (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
521
                            GL_TEXTURE_2D, priv->texture, 0);
522 523
  else if (priv->render_buffer)
    glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
524
                                  GL_RENDERBUFFER_EXT, priv->render_buffer);
525 526 527 528

  if (priv->depth_stencil_buffer)
    {
      if (priv->has_depth_buffer)
529 530
        glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                      GL_RENDERBUFFER_EXT, priv->depth_stencil_buffer);
531
      if (priv->has_stencil_buffer)
532 533
        glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
                                      GL_RENDERBUFFER_EXT, priv->depth_stencil_buffer);
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
    }
}

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->render_buffer != 0)
    {
549
      glDeleteRenderbuffersEXT (1, &priv->render_buffer);
550 551 552 553 554 555 556 557 558 559 560
      priv->render_buffer = 0;
    }

  if (priv->texture != 0)
    {
      glDeleteTextures(1, &priv->texture);
      priv->texture = 0;
    }

  if (priv->depth_stencil_buffer != 0)
    {
561 562 563
      glDeleteRenderbuffersEXT (1, &priv->depth_stencil_buffer);
      priv->depth_stencil_buffer = 0;
    }
564

565 566
  if (priv->frame_buffer != 0)
    {
567 568 569 570 571 572
      glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
      glDeleteFramebuffersEXT (1, &priv->frame_buffer);
      priv->frame_buffer = 0;
    }
}

Alexander Larsson's avatar
Alexander Larsson committed
573 574 575
static void
gtk_gl_area_unrealize (GtkWidget *widget)
{
576 577
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
578 579 580

  if (priv->context != NULL)
    {
581
      if (priv->have_buffers)
582 583 584 585
        {
          gtk_gl_area_make_current (area);
          gtk_gl_area_delete_buffers (area);
        }
Alexander Larsson's avatar
Alexander Larsson committed
586

587
      /* Make sure to unset the context if current */
588 589
      if (priv->context == gdk_gl_context_get_current ())
        gdk_gl_context_clear_current ();
Alexander Larsson's avatar
Alexander Larsson committed
590 591
    }

592
  g_clear_object (&priv->context);
593 594
  g_clear_error (&priv->error);

595 596 597 598 599 600 601
  if (priv->event_window != NULL)
    {
      gtk_widget_unregister_window (widget, priv->event_window);
      gdk_window_destroy (priv->event_window);
      priv->event_window = NULL;
    }

Alexander Larsson's avatar
Alexander Larsson committed
602 603 604
  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unrealize (widget);
}

605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
static void
gtk_gl_area_map (GtkWidget *widget)
{
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  if (priv->event_window != NULL)
    gdk_window_show (priv->event_window);

  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->map (widget);
}

static void
gtk_gl_area_unmap (GtkWidget *widget)
{
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  if (priv->event_window != NULL)
    gdk_window_hide (priv->event_window);

  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unmap (widget);
}

Alexander Larsson's avatar
Alexander Larsson committed
629 630 631 632
static void
gtk_gl_area_size_allocate (GtkWidget     *widget,
                           GtkAllocation *allocation)
{
633 634 635
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

Alexander Larsson's avatar
Alexander Larsson committed
636
  GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->size_allocate (widget, allocation);
637

638 639 640
  if (gtk_widget_get_realized (widget))
    {
      if (priv->event_window != NULL)
641 642 643 644 645
        gdk_window_move_resize (priv->event_window,
                                allocation->x,
                                allocation->y,
                                allocation->width,
                                allocation->height);
646 647 648

      priv->needs_resize = TRUE;
    }
Alexander Larsson's avatar
Alexander Larsson committed
649 650
}

651
static void
652
gtk_gl_area_draw_error_screen (GtkGLArea *area,
653 654 655 656
                               cairo_t   *cr,
                               gint       width,
                               gint       height)
{
657
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
658 659 660
  PangoLayout *layout;
  int layout_height;

661
  layout = gtk_widget_create_pango_layout (GTK_WIDGET (area),
662 663 664 665
                                           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);
666
  gtk_render_layout (gtk_widget_get_style_context (GTK_WIDGET (area)),
667 668 669 670 671 672 673
                     cr,
                     0, (height - layout_height) / 2,
                     layout);

  g_object_unref (layout);
}

Alexander Larsson's avatar
Alexander Larsson committed
674 675 676 677
static gboolean
gtk_gl_area_draw (GtkWidget *widget,
                  cairo_t   *cr)
{
678 679
  GtkGLArea *area = GTK_GL_AREA (widget);
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
Alexander Larsson's avatar
Alexander Larsson committed
680 681 682 683
  gboolean unused;
  int w, h, scale;
  GLenum status;

684
  if (priv->error != NULL)
685
    {
686
      gtk_gl_area_draw_error_screen (area,
687 688 689 690 691
                                     cr,
                                     gtk_widget_get_allocated_width (widget),
                                     gtk_widget_get_allocated_height (widget));
      return FALSE;
    }
Alexander Larsson's avatar
Alexander Larsson committed
692

693 694 695
  if (priv->context == NULL)
    return FALSE;

696 697 698 699 700 701 702 703
  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
704 705 706 707

  scale = gtk_widget_get_scale_factor (widget);
  w = gtk_widget_get_allocated_width (widget) * scale;
  h = gtk_widget_get_allocated_height (widget) * scale;
708

709 710
  status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
  if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
Alexander Larsson's avatar
Alexander Larsson committed
711
    {
712
      if (priv->needs_render || priv->auto_render)
713 714 715 716 717 718
        {
          if (priv->needs_resize)
            {
              g_signal_emit (area, area_signals[RESIZE], 0, w, h, NULL);
              priv->needs_resize = FALSE;
            }
719

720 721
          g_signal_emit (area, area_signals[RENDER], 0, priv->context, &unused);
        }
722

723
      priv->needs_render = FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
724 725 726

      gdk_cairo_draw_from_gl (cr,
                              gtk_widget_get_window (widget),
727 728
                              priv->texture ? priv->texture : priv->render_buffer,
                              priv->texture ? GL_TEXTURE : GL_RENDERBUFFER,
Alexander Larsson's avatar
Alexander Larsson committed
729
                              scale, 0, 0, w, h);
730
      gtk_gl_area_make_current (area);
Alexander Larsson's avatar
Alexander Larsson committed
731 732 733
    }
  else
    {
734
      g_warning ("fb setup not supported");
Alexander Larsson's avatar
Alexander Larsson committed
735 736 737 738 739
    }

  return TRUE;
}

740 741 742 743 744 745 746 747 748 749 750 751
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
752 753 754 755 756 757
static void
gtk_gl_area_class_init (GtkGLAreaClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

758
  klass->resize = gtk_gl_area_resize;
759
  klass->create_context = gtk_gl_area_real_create_context;
760

Alexander Larsson's avatar
Alexander Larsson committed
761 762
  widget_class->realize = gtk_gl_area_realize;
  widget_class->unrealize = gtk_gl_area_unrealize;
763 764
  widget_class->map = gtk_gl_area_map;
  widget_class->unmap = gtk_gl_area_unmap;
Alexander Larsson's avatar
Alexander Larsson committed
765 766 767 768 769 770 771 772 773 774 775
  widget_class->size_allocate = gtk_gl_area_size_allocate;
  widget_class->draw = gtk_gl_area_draw;

  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DRAWING_AREA);

  /**
   * GtkGLArea:context:
   *
   * The #GdkGLContext used by the #GtkGLArea widget.
   *
   * The #GtkGLArea widget is responsible for creating the #GdkGLContext
776 777
   * instance. If you need to render with other kinds of buffers (stencil,
   * depth, etc), use render buffers.
Alexander Larsson's avatar
Alexander Larsson committed
778 779 780 781 782 783 784 785 786 787 788
   *
   * Since: 3.16
   */
  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);

789 790 791
  /**
   * GtkGLArea:auto-render:
   *
792 793 794
   * If set to %TRUE the #GtkGLArea::render signal will be emitted every time
   * the widget draws. This is the default and is useful if drawing the widget
   * is faster.
795
   *
796 797 798 799 800
   * 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.
   * In order to force a rendering gtk_gl_area_queue_render() must be called.
   * This mode is useful when the scene changes seldomly, but takes a long time
   * to redraw.
801 802 803 804 805 806
   *
   * Since: 3.16
   */
  obj_props[PROP_AUTO_RENDER] =
    g_param_spec_boolean ("auto-render",
                          P_("Auto render"),
Matthias Clasen's avatar
Matthias Clasen committed
807
                          P_("Whether the GtkGLArea renders on each redraw"),
808
                          TRUE,
809 810 811
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
812

Alexander Larsson's avatar
Alexander Larsson committed
813 814 815
  /**
   * GtkGLArea:has-alpha:
   *
816 817 818
   * If set to %TRUE the buffer allocated by the widget will have an alpha channel
   * component, and when rendering to the window the result will be composited over
   * whatever is below the widget.
Alexander Larsson's avatar
Alexander Larsson committed
819
   *
820 821
   * If set to %FALSE there will be no alpha channel, and the buffer will fully
   * replace anything below the widget.
Alexander Larsson's avatar
Alexander Larsson committed
822 823 824 825 826 827
   *
   * Since: 3.16
   */
  obj_props[PROP_HAS_ALPHA] =
    g_param_spec_boolean ("has-alpha",
                          P_("Has alpha"),
Matthias Clasen's avatar
Matthias Clasen committed
828
                          P_("Whether the color buffer has an alpha component"),
Alexander Larsson's avatar
Alexander Larsson committed
829
                          FALSE,
830 831 832
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
833 834 835 836

  /**
   * GtkGLArea:has-depth-buffer:
   *
837 838
   * If set to %TRUE the widget will allocate and enable a depth buffer for the
   * target framebuffer.
Alexander Larsson's avatar
Alexander Larsson committed
839 840 841 842 843 844 845 846
   *
   * Since: 3.16
   */
  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,
847 848 849
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
850

851 852 853
  /**
   * GtkGLArea:has-stencil-buffer:
   *
854 855
   * If set to %TRUE the widget will allocate and enable a stencil buffer for the
   * target framebuffer.
856 857 858 859 860 861 862 863
   *
   * Since: 3.16
   */
  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,
864 865 866
                          GTK_PARAM_READWRITE |
                          G_PARAM_STATIC_STRINGS |
                          G_PARAM_EXPLICIT_NOTIFY);
867

868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
  /**
   * GtkGLArea:use-es:
   *
   * If set to %TRUE the widget will try to create a #GdkGLContext using
   * OpenGL ES instead of OpenGL.
   *
   * See also: gdk_gl_context_set_use_es()
   *
   * Since: 3.22
   */
  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
887 888 889
  gobject_class->set_property = gtk_gl_area_set_property;
  gobject_class->get_property = gtk_gl_area_get_property;
  gobject_class->dispose = gtk_gl_area_dispose;
890
  gobject_class->notify = gtk_gl_area_notify;
Alexander Larsson's avatar
Alexander Larsson committed
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911

  g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);

  /**
   * GtkGLArea::render:
   * @area: the #GtkGLArea that emitted the signal
   * @context: the #GdkGLContext used by @area
   *
   * The ::render signal is emitted every time the contents
   * of the #GtkGLArea should be redrawn.
   *
   * 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.
   *
   * Since: 3.16
   */
  area_signals[RENDER] =
    g_signal_new (I_("render"),
912 913 914 915 916 917 918
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkGLAreaClass, render),
                  _gtk_boolean_handled_accumulator, NULL,
                  NULL,
                  G_TYPE_BOOLEAN, 1,
                  GDK_TYPE_GL_CONTEXT);
919 920

  /**
921
   * GtkGLArea::resize:
922
   * @area: the #GtkGLArea that emitted the signal
923 924
   * @width: the width of the viewport
   * @height: the height of the viewport
925
   *
926
   * The ::resize signal is emitted once when the widget is realized, and
927 928 929 930
   * 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.
   *
931 932
   * The GL context for the area is guaranteed to be current when this signal
   * is emitted.
933
   *
934
   * The default handler sets up the GL viewport.
935 936 937 938
   *
   * Since: 3.16
   */
  area_signals[RESIZE] =
939
    g_signal_new (I_("resize"),
940 941 942 943 944 945 946
                  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);

947 948 949 950 951 952 953 954 955
  /**
   * GtkGLArea::create-context:
   * @area: the #GtkGLArea that emitted the signal
   * @error: (allow-none): location to store error information on failure
   *
   * The ::create-context signal is emitted when the widget is being
   * realized, and 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
Emmanuele Bassi's avatar
Emmanuele Bassi committed
956
   * options.
957 958 959 960 961
   *
   * If context creation fails then the signal handler can use
   * gtk_gl_area_set_error() to register a more detailed error
   * of how the construction failed.
   *
962 963
   * Returns: (transfer full): a newly created #GdkGLContext;
   *     the #GtkGLArea widget will take ownership of the returned value.
964 965 966 967
   *
   * Since: 3.16
   */
  area_signals[CREATE_CONTEXT] =
968
    g_signal_new (I_("create-context"),
969 970 971 972 973 974
                  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);
Alexander Larsson's avatar
Alexander Larsson committed
975 976 977
}

static void
978
gtk_gl_area_init (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
979
{
980 981 982 983 984 985 986
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  gtk_widget_set_has_window (GTK_WIDGET (area), FALSE);
  gtk_widget_set_app_paintable (GTK_WIDGET (area), TRUE);

  priv->auto_render = TRUE;
  priv->needs_render = TRUE;
987
  priv->required_gl_version = 0;
Alexander Larsson's avatar
Alexander Larsson committed
988 989 990 991 992 993 994
}

/**
 * gtk_gl_area_new:
 *
 * Creates a new #GtkGLArea widget.
 *
995
 * Returns: a new #GtkGLArea
Alexander Larsson's avatar
Alexander Larsson committed
996 997 998 999 1000 1001
 *
 * Since: 3.16
 */
GtkWidget *
gtk_gl_area_new (void)
{
1002
  return g_object_new (GTK_TYPE_GL_AREA, NULL);
Alexander Larsson's avatar
Alexander Larsson committed
1003 1004
}

1005 1006 1007 1008 1009 1010
/**
 * gtk_gl_area_set_error:
 * @area: a #GtkGLArea
 * @error: (allow-none): a new #GError, or %NULL to unset the error
 *
 * Sets an error on the area which will be shown instead of the
1011 1012
 * GL rendering. This is useful in the #GtkGLArea::create-context
 * signal if GL context creation fails.
1013 1014 1015 1016
 *
 * Since: 3.16
 */
void
1017
gtk_gl_area_set_error (GtkGLArea    *area,
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
                       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:
 * @area: a #GtkGLArea
 *
 * Gets the current error set on the @area.
 *
1035
 * Returns: (nullable) (transfer none): the #GError or %NULL
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
 *
 * Since: 3.16
 */
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;
}

1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
/**
 * gtk_gl_area_set_use_es:
 * @area: a #GtkGLArea
 * @use_es: whether to use OpenGL or OpenGL ES
 *
 * Sets whether the @area should create an OpenGL or an OpenGL ES context.
 *
 * You should check the capabilities of the #GdkGLContext before drawing
 * with either API.
 *
 * Since: 3.22
 */
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]);
    }
}

/**
 * gtk_gl_area_get_use_es:
 * @area: a #GtkGLArea
 *
 * Retrieves the value set by gtk_gl_area_set_use_es().
 *
 * Returns: %TRUE if the #GtkGLArea should create an OpenGL ES context
 *   and %FALSE otherwise
 *
 * Since: 3.22
 */
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;
}

1101 1102 1103
/**
 * gtk_gl_area_set_required_version:
 * @area: a #GtkGLArea
1104 1105
 * @major: the major version
 * @minor: the minor version
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
 *
 * Sets the required version of OpenGL to be used when creating the context
 * for the widget.
 *
 * This function must be called before the area has been realized.
 *
 * Since: 3.16
 */
void
gtk_gl_area_set_required_version (GtkGLArea *area,
1116 1117
                                  gint       major,
                                  gint       minor)
1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
{
  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:
 * @area: a #GtkGLArea
 * @major: (out): return location for the required major version
 * @minor: (out): return location for the required minor version
 *
 * Retrieves the required version of OpenGL set
 * using gtk_gl_area_set_required_version().
 *
 * Since: 3.16
 */
void
gtk_gl_area_get_required_version (GtkGLArea *area,
1140 1141
                                  gint      *major,
                                  gint      *minor)
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
{
  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
1153 1154 1155 1156
/**
 * gtk_gl_area_get_has_alpha:
 * @area: a #GtkGLArea
 *
1157 1158 1159
 * Returns whether the area has an alpha component.
 *
 * Returns: %TRUE if the @area has an alpha component, %FALSE otherwise
Alexander Larsson's avatar
Alexander Larsson committed
1160 1161 1162 1163
 *
 * Since: 3.16
 */
gboolean
1164
gtk_gl_area_get_has_alpha (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);

  return priv->has_alpha;
}

/**
 * gtk_gl_area_set_has_alpha:
 * @area: a #GtkGLArea
1176
 * @has_alpha: %TRUE to add an alpha component
Alexander Larsson's avatar
Alexander Larsson committed
1177
 *
1178 1179 1180
 * If @has_alpha is %TRUE the buffer allocated by the widget will have
 * an alpha channel component, and when rendering to the window the
 * result will be composited over whatever is below the widget.
Alexander Larsson's avatar
Alexander Larsson committed
1181
 *
1182 1183
 * If @has_alpha is %FALSE there will be no alpha channel, and the
 * buffer will fully replace anything below the widget.
Alexander Larsson's avatar
Alexander Larsson committed
1184 1185 1186 1187
 *
 * Since: 3.16
 */
void
1188 1189
gtk_gl_area_set_has_alpha (GtkGLArea *area,
                           gboolean   has_alpha)
Alexander Larsson's avatar
Alexander Larsson committed
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  has_alpha = !!has_alpha;

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

      g_object_notify (G_OBJECT (area), "has-alpha");
1202

1203
      gtk_gl_area_delete_buffers (area);
Alexander Larsson's avatar
Alexander Larsson committed
1204 1205 1206 1207 1208 1209 1210
    }
}

/**
 * gtk_gl_area_get_has_depth_buffer:
 * @area: a #GtkGLArea
 *
1211 1212 1213
 * 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
1214 1215 1216 1217
 *
 * Since: 3.16
 */
gboolean
1218
gtk_gl_area_get_has_depth_buffer (GtkGLArea *area)
Alexander Larsson's avatar
Alexander Larsson committed
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
{
  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;
}

/**
 * gtk_gl_area_set_has_depth_buffer:
 * @area: a #GtkGLArea
1230
 * @has_depth_buffer: %TRUE to add a depth buffer
Alexander Larsson's avatar
Alexander Larsson committed
1231
 *
1232 1233 1234
 * 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
1235 1236 1237 1238
 *
 * Since: 3.16
 */
void
1239 1240
gtk_gl_area_set_has_depth_buffer (GtkGLArea *area,
                                  gboolean   has_depth_buffer)
Alexander Larsson's avatar
Alexander Larsson committed
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
{
  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");
1253

1254
      priv->have_buffers = FALSE;
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290
    }
}

/**
 * gtk_gl_area_get_has_stencil_buffer:
 * @area: a #GtkGLArea
 *
 * Returns whether the area has a stencil buffer.
 *
 * Returns: %TRUE if the @area has a stencil buffer, %FALSE otherwise
 *
 * Since: 3.16
 */
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;
}

/**
 * gtk_gl_area_set_has_stencil_buffer:
 * @area: a #GtkGLArea
 * @has_stencil_buffer: %TRUE to add a stencil buffer
 *
 * If @has_stencil_buffer is %TRUE the widget will allocate and
 * enable a stencil buffer for the target framebuffer. Otherwise
 * there will be none.
 *
 * Since: 3.16
 */
void
gtk_gl_area_set_has_stencil_buffer (GtkGLArea *area,
1291
                                    gboolean   has_stencil_buffer)
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
{
  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");

1305
      priv->have_buffers = FALSE;
1306 1307 1308 1309 1310 1311 1312
    }
}

/**
 * gtk_gl_area_queue_render:
 * @area: a #GtkGLArea
 *
1313 1314
 * Marks the currently rendered data (if any) as invalid, and queues
 * a redraw of the widget, ensuring that the #GtkGLArea::render signal
1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362
 * is emitted during the draw.
 *
 * This is only needed when the gtk_gl_area_set_auto_render() has
 * been called with a %FALSE value. The default behaviour is to
 * emit #GtkGLArea::render on each draw.
 *
 * Since: 3.16
 */
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));
}


/**
 * gtk_gl_area_get_auto_render:
 * @area: a #GtkGLArea
 *
 * Returns whether the area is in auto render mode or not.
 *
 * Returns: %TRUE if the @area is auto rendering, %FALSE otherwise
 *
 * Since: 3.16
 */
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;
}

/**
 * gtk_gl_area_set_auto_render:
 * @area: a #GtkGLArea
 * @auto_render: a boolean
 *
 * If @auto_render is %TRUE the #GtkGLArea::render signal will be
 * emitted every time the widget draws. This is the default and is
1363
 * useful if drawing the widget is faster.
1364 1365 1366 1367 1368
 *
 * 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
 * gtk_gl_area_queue_render() must be called. This mode is useful when
1369
 * the scene changes seldomly, but takes a long time to redraw.
1370 1371 1372 1373 1374
 *
 * Since: 3.16
 */
void
gtk_gl_area_set_auto_render (GtkGLArea *area,
1375
                             gboolean   auto_render)
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_if_fail (GTK_IS_GL_AREA (area));

  auto_render = !!auto_render;

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

      g_object_notify (G_OBJECT (area), "auto-render");

      if (auto_render)
1390
        gtk_widget_queue_draw (GTK_WIDGET (area));
Alexander Larsson's avatar
Alexander Larsson committed
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421
    }
}

/**
 * gtk_gl_area_get_context:
 * @area: a #GtkGLArea
 *
 * Retrieves the #GdkGLContext used by @area.
 *
 * Returns: (transfer none): the #GdkGLContext
 *
 * Since: 3.16
 */
GdkGLContext *
gtk_gl_area_get_context (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);

  g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);

  return priv->context;
}

/**
 * gtk_gl_area_make_current:
 * @area: a #GtkGLArea
 *
 * Ensures that the #GdkGLContext used by @area is associated with
 * the #GtkGLArea.
 *
 * This function is automatically called before emitting the
1422 1423
 * #GtkGLArea::render signal, and doesn't normally need to be called
 * by application code.
Alexander Larsson's avatar
Alexander Larsson committed
1424 1425 1426
 *
 * Since: 3.16
 */
1427
void
Alexander Larsson's avatar
Alexander Larsson committed
1428 1429 1430 1431 1432
gtk_gl_area_make_current (GtkGLArea *area)
{
  GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
  GtkWidget *widget;

1433
  g_return_if_fail (GTK_IS_GL_AREA (area));
Alexander Larsson's avatar
Alexander Larsson committed
1434 1435 1436

  widget = GTK_WIDGET (area);

1437
  g_return_if_fail (gtk_widget_get_realized (widget));
Alexander Larsson's avatar
Alexander Larsson committed
1438

1439
  if (priv->context != NULL)
1440
    gdk_gl_context_make_current (priv->context);
Alexander Larsson's avatar
Alexander Larsson committed
1441
}