gtk2/gdk/gdkglcontext.c

341 lines
9.1 KiB
C
Raw Normal View History

gdk: Add support for OpenGL This adds the new type GdkGLContext that wraps an OpenGL context for a particular native window. It also adds support for the gdk paint machinery to use OpenGL to draw everything. As soon as anyone creates a GL context for a native window we create a "paint context" for that GdkWindow and switch to using GL for painting it. This commit contains only an implementation for X11 (using GLX). The way painting works is that all client gl contexts draw into offscreen buffers rather than directly to the back buffer, and the way something gets onto the window is by using gdk_cairo_draw_from_gl() to draw part of that buffer onto the draw cairo context. As a fallback (if we're doing redirected drawing or some effect like a cairo_push_group()) we read back the gl buffer into memory and composite using cairo. This means that GL rendering works in all cases, including rendering to a PDF. However, this is not particularly fast. In the *typical* case, where we're drawing directly to the window in the regular paint loop we hit the fast path. The fast path uses opengl to draw the buffer to the window back buffer, either by blitting or texturing. Then we track the region that was drawn, and when the draw ends we paint the normal cairo surface to the window (using texture-from-pixmap in the X11 case, or texture from cairo image otherwise) in the regions where there is no gl painted. There are some complexities wrt layering of gl and cairo areas though: * We track via gdk_window_mark_paint_from_clip() whenever gtk is painting over a region we previously rendered with opengl (flushed_region). This area (needs_blend_region) is blended rather than copied at the end of the frame. * If we're drawing a gl texture with alpha we first copy the current cairo_surface inside the target region to the back buffer before we blend over it. These two operations allow us full stacking of transparent gl and cairo regions.
2014-10-09 08:45:44 +00:00
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext.c: GL context abstraction
*
* Copyright © 2014 Emmanuele Bassi
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:gdkglcontext
* @Title: GdkGLContext
* @Short_description: OpenGL context
*
* #GdkGLContext is an object representing the platform-specific
* OpenGL drawing context.
*
* #GdkGLContexts are created for a #GdkWindow using gdk_window_create_gl_context(), and
* the context will be tied to the native window backing that window, matching the
* GdkVisual of the window.
*
* A #GdkGLContexts normal framebuffer draws directly on to the back buffer of the native
* window backing the #GdkWindow, so its not allowed to draw directly to that, as the
* gdk repaint system is in full control of that. Instead you can create render buffers
* or textures and use gdk_cairo_draw_from_gl() in the draw function of your widget
* to draw them. Then Gdk will handle the integration of your rendering with that of
* other widgets.
*
* Support for #GdkGLContext is platform specific, context creation can fail, returning
* a %NULL context.
*
* A #GdkGLContext has to be made "current" in order to start using
* it, otherwise any OpenGL call will be ignored.
*
* ## Creating a new OpenGL context ##
*
* In order to create a new #GdkGLContext instance you need a
* #GdkWindow, which you typically get during the realize call of a widget.
*
* ## Using a GdkGLContext ##
*
* You will need to make the #GdkGLContext the current context
* before issuing OpenGL calls; the system sends OpenGL commands to
* whichever context is current. It is possible to have multiple
* contexts, so you always need to ensure that the one which you
* want to draw with is the current one before issuing commands:
*
* |[<!-- language="C" -->
* gdk_gl_context_make_current (context);
* ]|
*
* You can now perform your drawing using OpenGL commands.
*
* You can check which #GdkGLContext is the current one by using
* gdk_gl_context_get_current(); you can also unset any #GdkGLContext
* that is currently set by calling gdk_gl_context_clear_current().
*/
#include "config.h"
#include "gdkglcontextprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkvisual.h"
#include "gdkinternals.h"
#include "gdkintl.h"
typedef struct {
GdkWindow *window;
GdkVisual *visual;
} GdkGLContextPrivate;
enum {
PROP_0,
PROP_WINDOW,
PROP_VISUAL,
LAST_PROP
};
static GParamSpec *obj_pspecs[LAST_PROP] = { NULL, };
G_DEFINE_QUARK (gdk-gl-error-quark, gdk_gl_error)
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkGLContext, gdk_gl_context, G_TYPE_OBJECT)
static void
gdk_gl_context_dispose (GObject *gobject)
{
GdkGLContext *context = GDK_GL_CONTEXT (gobject);
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
gdk_display_destroy_gl_context (gdk_window_get_display (priv->window), context);
g_clear_object (&priv->window);
g_clear_object (&priv->visual);
G_OBJECT_CLASS (gdk_gl_context_parent_class)->dispose (gobject);
}
static void
gdk_gl_context_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private ((GdkGLContext *) gobject);
switch (prop_id)
{
case PROP_WINDOW:
{
GdkWindow *window = g_value_get_object (value);
if (window)
g_object_ref (window);
if (priv->window)
g_object_unref (priv->window);
priv->window = window;
}
break;
case PROP_VISUAL:
{
GdkVisual *visual = g_value_get_object (value);
if (visual != NULL)
priv->visual = g_object_ref (visual);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gdk_gl_context_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private ((GdkGLContext *) gobject);
switch (prop_id)
{
case PROP_WINDOW:
g_value_set_object (value, priv->window);
break;
case PROP_VISUAL:
g_value_set_object (value, priv->visual);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gdk_gl_context_class_init (GdkGLContextClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
/**
* GdkGLContext:window:
*
* The #GdkWindow the gl context is bound to.
*
* Since: 3.16
*/
obj_pspecs[PROP_WINDOW] =
g_param_spec_object ("window",
P_("Window"),
P_("The GDK window bound to the GL context"),
GDK_TYPE_WINDOW,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
/**
* GdkGLContext:visual:
*
* The #GdkVisual matching the pixel format used by the context.
*
* Since: 3.16
*/
obj_pspecs[PROP_VISUAL] =
g_param_spec_object ("visual",
P_("Visual"),
P_("The GDK visual used by the GL context"),
GDK_TYPE_VISUAL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
gobject_class->set_property = gdk_gl_context_set_property;
gobject_class->get_property = gdk_gl_context_get_property;
gobject_class->dispose = gdk_gl_context_dispose;
g_object_class_install_properties (gobject_class, LAST_PROP, obj_pspecs);
}
static void
gdk_gl_context_init (GdkGLContext *self)
{
}
/**
* gdk_gl_context_get_visual:
* @context: a #GdkGLContext
*
* Retrieves the #GdkVisual associated with the @context.
*
* Returns: (transfer none): the #GdkVisual
*
* Since: 3.16
*/
GdkVisual *
gdk_gl_context_get_visual (GdkGLContext *context)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
return priv->visual;
}
/*< private >
* gdk_gl_context_flush_buffer:
* @context: a #GdkGLContext
* @painted: The area that has been redrawn this frame
* @damage: The area that we know is actually different from the last frame
*
* Copies the back buffer to the front buffer.
*
* This function may call `glFlush()` implicitly before returning; it
* is not recommended to call `glFlush()` explicitly before calling
* this function.
*
* Since: 3.16
*/
void
gdk_gl_context_flush_buffer (GdkGLContext *context,
cairo_region_t *painted,
cairo_region_t *damage)
{
g_return_if_fail (GDK_IS_GL_CONTEXT (context));
GDK_GL_CONTEXT_GET_CLASS (context)->flush_buffer (context, painted, damage);
}
/**
* gdk_gl_context_make_current:
* @context: a #GdkGLContext
*
* Makes the @context the current one.
*
* Returns: %TRUE if the context is current
*
* Since: 3.16
*/
gboolean
gdk_gl_context_make_current (GdkGLContext *context)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), FALSE);
return gdk_display_make_gl_context_current (gdk_window_get_display (priv->window), context);
}
/**
* gdk_gl_context_get_window:
* @context: a #GdkGLContext
*
* Retrieves the #GdkWindow used by the @context.
*
* Returns: (transfer none): a #GdkWindow or %NULL
*
* Since: 3.16
*/
GdkWindow *
gdk_gl_context_get_window (GdkGLContext *context)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
return priv->window;
}
/**
* gdk_gl_context_clear_current:
*
* Clears the current #GdkGLContext.
*
* Any OpenGL call after this function returns will be ignored
* until gdk_gl_context_make_current() is called.
*
* Since: 3.16
*/
void
gdk_gl_context_clear_current (void)
{
GdkDisplay *display = gdk_display_get_default ();
gdk_display_make_gl_context_current (display, NULL);
}
/**
* gdk_gl_context_get_current:
*
* Retrieves the current #GdkGLContext.
*
* Returns: (transfer none): the current #GdkGLContext, or %NULL
*
* Since: 3.16
*/
GdkGLContext *
gdk_gl_context_get_current (void)
{
GdkDisplay *display = gdk_display_get_default ();
return gdk_display_get_current_gl_context (display);
}