Compare commits

...

26 Commits

Author SHA1 Message Date
Matthias Clasen
b12148ef62 Add GdkGLProfile and GdkGLError to the docs 2014-10-12 00:20:34 -04:00
Matthias Clasen
26ad6184a4 Pedantic formatting fix 2014-10-12 00:12:48 -04:00
Matthias Clasen
909b2fe589 Drop a reference to GtkGLArea::create-context
The signal no longer exists.
2014-10-11 23:55:53 -04:00
Matthias Clasen
29d23aaa96 Nitpicky documentation changes 2014-10-11 23:35:52 -04:00
Matthias Clasen
ba1fe1689b Nitpicky documentation fixes 2014-10-11 23:32:19 -04:00
Matthias Clasen
d0fe5ce023 Correct another Since tag 2014-10-11 23:20:53 -04:00
Matthias Clasen
35afc38e29 Trivial formatting fixes 2014-10-11 23:17:34 -04:00
Matthias Clasen
258060f158 Correct copyright year 2014-10-11 23:04:40 -04:00
Matthias Clasen
1dd4989bbc Correct Since tags 2014-10-11 22:57:31 -04:00
Alexander Larsson
e500a156e0 gl: Make gdk_gl_context_make_current() return void
Its not really reasonable to handle failures to make_current, it
basically only happens if you pass invalid arguments to it, and
thats not something we trap on similar things on the X drawing side.

If GL is not supported that should be handled by the context creation
failing, and anything going wrong after that is essentially a critical
(or an async X error).
2014-10-09 17:24:21 +02:00
Alexander Larsson
c080e25dd6 X11 GdkGLContext: Remove spurious XSync
This XSync doesn't seem to be necessary. Remove it until otherwise
proven.
2014-10-09 16:22:56 +02:00
Alexander Larsson
519bea5185 Fix warning 2014-10-09 16:22:47 +02:00
Alexander Larsson
b24d58a5f6 gl: Make all user GdkGLContexts not attached to any window
We make user facing gl contexts not attached to a surface if possible,
or attached to dummy surfaces. This means nothing can accidentally
read/write to the toplevel back buffer.
2014-10-09 16:09:05 +02:00
Alexander Larsson
d4979fb82a Add glarea demo to gtk3-demo 2014-10-09 11:33:44 +02:00
Alexander Larsson
46c6bca189 Add tests/testglblending
This test stacks GL and cairo rendering in various
orders to test the layer handling.
2014-10-09 11:33:44 +02:00
Alexander Larsson
a3a4384047 Add tests/gdkgears
A more complex test modeled on the traditional glxgears.
2014-10-09 11:33:40 +02:00
Alexander Larsson
6f3d00c0fd Add tests/testglarea
This is a very simple test of GtkGLArea.
2014-10-09 11:33:40 +02:00
Alexander Larsson
ea50477aa7 Add GtkGLArea widget 2014-10-09 11:33:37 +02:00
Alexander Larsson
8bc17f1e5e wayland: Add OpenGL support
This uses EGL to implement GdkGLContext for wayland.
2014-10-09 11:06:48 +02:00
Alexander Larsson
224404cb2d wayland: Set GdkVisual::bits_per_rgb to 8
This was 0 before.
2014-10-09 11:04:51 +02:00
Alexander Larsson
5b649ea4fe Add GDK_ALWAYS_USE_GL debug hack
If this is set we always use GL to render each window, even
if there are no GL widgets in the window.
2014-10-09 11:03:47 +02:00
Alexander Larsson
1cc57bd8af 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 11:03:22 +02:00
Alexander Larsson
946ab24cf7 Add gdk_window_mark_paint_from_clip and call from widget drawing
This is a new function that gets called every time we're drawing
some area in the Gtk paint machinery. It is a no-op right now, but
it will be required later to keep track of what areas which
we previously rendered with GL was overwritten with cairo contents.
2014-10-09 10:18:02 +02:00
Alexander Larsson
91f99ed807 Change the way the update area is tracked during paint
First of all we track the current update area during an
update in window->active_update_area. This will be used later
in end_paint to know the damaged area.

Secondly we keep track of old update areas for the last 2
frames. This will later allow us to reuse old framebuffer
contents in double or tripple buffer setups, only painting
what has changed since then.
2014-10-09 10:09:51 +02:00
Alexander Larsson
76ab606cef Add internal gdk_cairo_region_from_clip helper
This extract the current cairo clip region if possible and
returns it as a cairo_region_T. It will be needed by later code.
2014-10-09 09:58:36 +02:00
Emmanuele Bassi
5881b5e453 build: Require libepoxy
If we want to use OpenGL in GDK then we have two choices; either:

  - find the GL headers on each platform
  - do extension discovery
  - implement all the crazy dlopen()/dlsym() dispatch tables

*or* use libepoxy, which shields us from all this madness and provides a
decent layer for GL clients to use, without creating its own namespace.

Epoxy is also used by other projects, like Xorg and piglit, and it's
portable to all the platforms GDK cares about.

https://bugzilla.gnome.org/show_bug.cgi?id=119189
2014-10-09 09:37:59 +02:00
54 changed files with 5124 additions and 59 deletions

View File

@@ -48,6 +48,7 @@ m4_define([cairo_required_version], [1.12.0])
m4_define([gdk_pixbuf_required_version], [2.30.0])
m4_define([introspection_required_version], [1.39.0])
m4_define([wayland_required_version], [1.5.91])
m4_define([epoxy_required_version], [1.0])
GLIB_REQUIRED_VERSION=glib_required_version
PANGO_REQUIRED_VERSION=pango_required_version
ATK_REQUIRED_VERSION=atk_required_version
@@ -426,7 +427,7 @@ fi
PKG_PROG_PKG_CONFIG
WAYLAND_DEPENDENCIES="wayland-client >= wayland_required_version xkbcommon >= 0.2.0 wayland-cursor >= wayland_required_version"
WAYLAND_DEPENDENCIES="wayland-client >= wayland_required_version xkbcommon >= 0.2.0 wayland-cursor >= wayland_required_version wayland-egl >= wayland_required_version"
if test "$enable_wayland_backend" = "maybe" ; then
AC_PATH_PROG([WAYLAND_SCANNER],[wayland-scanner],[no])
PKG_CHECK_EXISTS($WAYLAND_DEPENDENCIES, [have_wayland_deps=yes], [have_wayland_deps=no])
@@ -1326,7 +1327,7 @@ CFLAGS="$saved_cflags"
LDFLAGS="$saved_ldflags"
GDK_PACKAGES="$PANGO_PACKAGES gdk-pixbuf-2.0 >= gdk_pixbuf_required_version cairo >= cairo_required_version cairo-gobject >= cairo_required_version"
GDK_PRIVATE_PACKAGES="$GDK_GIO_PACKAGE $X_PACKAGES $WAYLAND_PACKAGES $cairo_backends"
GDK_PRIVATE_PACKAGES="$GDK_GIO_PACKAGE $X_PACKAGES $WAYLAND_PACKAGES $cairo_backends epoxy >= epoxy_required_version"
if test "x$enable_x11_backend" = xyes; then
GDK_PRIVATE_PACKAGES="$GDK_PRIVATE_PACKAGES pangoft2"
fi

View File

@@ -25,6 +25,7 @@ demos = \
event_axes.c \
expander.c \
gestures.c \
glarea.c \
headerbar.c \
hypertext.c \
iconview.c \

View File

@@ -95,6 +95,7 @@
<file>expander.c</file>
<file>flowbox.c</file>
<file>gestures.c</file>
<file>glarea.c</file>
<file>headerbar.c</file>
<file>hypertext.c</file>
<file>iconview.c</file>

184
demos/gtk-demo/glarea.c Normal file
View File

@@ -0,0 +1,184 @@
/* OpenGL Area
*
* GtkGLArea is a widget that allows custom drawing using OpenGL calls.
*/
#include <gtk/gtk.h>
#include <epoxy/gl.h>
static GtkWidget *demo_window = NULL;
/* the GtkGLArea widget */
static GtkWidget *gl_area = NULL;
enum {
X_AXIS,
Y_AXIS,
Z_AXIS,
N_AXIS
};
/* Rotation angles on each axis */
static float rotation_angles[N_AXIS] = { 0.0 };
/* The object we are drawing */
static void
draw_triangle (void)
{
glColor3f (1.0f, 0.85f, 0.35f);
glBegin (GL_TRIANGLES);
{
glVertex3f ( 0.0, 0.6, 0.0);
glVertex3f (-0.2, -0.3, 0.0);
glVertex3f ( 0.2, -0.3, 0.0);
}
glEnd ();
}
/* The main rendering callback */
static gboolean
render (GtkGLArea *area,
GdkGLContext *context)
{
glClearColor (0.5, 0.5, 0.5, 1.0);
glClear (GL_COLOR_BUFFER_BIT);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glRotatef (rotation_angles[X_AXIS], 1, 0, 0);
glRotatef (rotation_angles[Y_AXIS], 0, 1, 0);
glRotatef (rotation_angles[Z_AXIS], 0, 0, 1);
draw_triangle ();
glFlush ();
return TRUE;
}
static void
on_axis_value_change (GtkAdjustment *adjustment,
gpointer data)
{
int axis = GPOINTER_TO_INT (data);
g_assert (axis >= 0 && axis < N_AXIS);
/* Update the rotation angle */
rotation_angles[axis] = gtk_adjustment_get_value (adjustment);
/* Update the contents of the GL drawing area */
gtk_widget_queue_draw (gl_area);
}
static GtkWidget *
create_axis_slider (int axis)
{
GtkWidget *box, *label, *slider;
GtkAdjustment *adj;
const char *text;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
switch (axis)
{
case X_AXIS:
text = "X axis";
break;
case Y_AXIS:
text = "Y axis";
break;
case Z_AXIS:
text = "Z axis";
break;
default:
g_assert_not_reached ();
}
label = gtk_label_new (text);
gtk_container_add (GTK_CONTAINER (box), label);
gtk_widget_show (label);
adj = gtk_adjustment_new (0.0, 0.0, 360.0, 1.0, 12.0, 0.0);
g_signal_connect (adj, "value-changed",
G_CALLBACK (on_axis_value_change),
GINT_TO_POINTER (axis));
slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj);
gtk_container_add (GTK_CONTAINER (box), slider);
gtk_widget_set_hexpand (slider, TRUE);
gtk_widget_show (slider);
gtk_widget_show (box);
return box;
}
static void
close_window (GtkWidget *widget)
{
/* Reset the state */
demo_window = NULL;
gl_area = NULL;
rotation_angles[X_AXIS] = 0.0;
rotation_angles[Y_AXIS] = 0.0;
rotation_angles[Z_AXIS] = 0.0;
}
GtkWidget *
create_glarea_window (GtkWidget *do_widget)
{
GtkWidget *window, *box, *button, *controls;
int i;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "GtkGLArea - Golden Triangle");
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_box_set_spacing (GTK_BOX (box), 6);
gtk_container_add (GTK_CONTAINER (window), box);
gl_area = gtk_gl_area_new ();
gtk_widget_set_hexpand (gl_area, TRUE);
gtk_widget_set_vexpand (gl_area, TRUE);
gtk_container_add (GTK_CONTAINER (box), gl_area);
/* the main "draw" call for GtkGLArea */
g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
controls = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_container_add (GTK_CONTAINER (box), controls);
gtk_widget_set_hexpand (controls, TRUE);
for (i = 0; i < N_AXIS; i++)
gtk_container_add (GTK_CONTAINER (controls), create_axis_slider (i));
button = gtk_button_new_with_label ("Quit");
gtk_widget_set_hexpand (button, TRUE);
gtk_container_add (GTK_CONTAINER (box), button);
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
return window;
}
GtkWidget*
do_glarea (GtkWidget *do_widget)
{
if (demo_window == NULL)
demo_window = create_glarea_window (do_widget);
if (!gtk_widget_get_visible (demo_window))
gtk_widget_show_all (demo_window);
else
gtk_widget_destroy (demo_window);
return demo_window;
}

View File

@@ -32,6 +32,7 @@
<xi:include href="xml/windows.xml" />
<xi:include href="xml/gdkframeclock.xml" />
<xi:include href="xml/gdkframetimings.xml" />
<xi:include href="xml/gdkglcontext.xml" />
<xi:include href="xml/events.xml" />
<xi:include href="xml/event_structs.xml" />
<xi:include href="xml/keys.xml" />

View File

@@ -394,6 +394,8 @@ gdk_window_constrain_size
gdk_window_beep
gdk_window_get_scale_factor
gdk_window_set_opaque_region
gdk_window_create_gl_context
gdk_window_mark_paint_from_clip
<SUBSECTION>
gdk_window_get_clip_region
@@ -617,6 +619,7 @@ gdk_cairo_rectangle
gdk_cairo_region
gdk_cairo_region_create_from_surface
gdk_cairo_surface_create_from_pixbuf
gdk_cairo_draw_from_gl
</SECTION>
<SECTION>
@@ -1004,6 +1007,7 @@ gdk_x11_display_error_trap_pop
gdk_x11_display_error_trap_pop_ignored
gdk_x11_display_set_cursor_theme
gdk_x11_display_set_window_scale
gdk_x11_display_get_glx_version
gdk_x11_register_standard_event_type
gdk_x11_screen_get_screen_number
gdk_x11_screen_get_xscreen
@@ -1123,6 +1127,11 @@ GDK_X11_KEYMAP_CLASS
GDK_IS_X11_KEYMAP
GDK_IS_X11_KEYMAP_CLASS
GDK_X11_KEYMAP_GET_CLASS
GDK_TYPE_X11_GL_CONTEXT
GDK_X11_GL_CONTEXT
GDK_X11_GL_CONTEXT_CLASS
GDK_IS_X11_GL_CONTEXT
GDK_IS_X11_GL_CONTEXT_CLASS
GDK_TYPE_X11_SCREEN
GDK_X11_SCREEN
GDK_X11_SCREEN_CLASS
@@ -1273,3 +1282,28 @@ gdk_frame_timings_get_predicted_presentation_time
<SUBSECTION Private>
gdk_frame_get_type
</SECTION>
<SECTION>
<FILE>gdkglcontext</FILE>
GdkGLContext
gdk_gl_context_get_visual
gdk_gl_context_get_window
gdk_gl_context_make_current
gdk_gl_context_clear_current
gdk_gl_context_get_current
GDK_GL_ERROR
GDK_TYPE_GL_ERROR
GDK_TYPE_GL_PROFILE
GdkGLProfile
GdkGLError
<SUBSECTION Standard>
GDK_GL_CONTEXT
GDK_IS_GL_CONTEXT
GDK_TYPE_GL_CONTEXT
GDK_GL_CONTEXT_CLASS
GDK_GL_CONTEXT_GET_CLASS
GDK_IS_GL_CONTEXT_CLASS
gdk_gl_context_get_type
gdk_gl_error_quark
</SECTION>

View File

@@ -8,6 +8,7 @@ gdk_display_get_type
gdk_display_manager_get_type
gdk_drag_context_get_type
gdk_frame_clock_get_type
gdk_gl_context_get_type
gdk_keymap_get_type
gdk_screen_get_type
gdk_visual_get_type

View File

@@ -242,6 +242,7 @@
<xi:include href="xml/gtkadjustment.xml" />
<xi:include href="xml/gtkcalendar.xml" />
<xi:include href="xml/gtkdrawingarea.xml" />
<xi:include href="xml/gtkglarea.xml" />
<xi:include href="xml/gtkeventbox.xml" />
<xi:include href="xml/gtkhandlebox.xml" />
<xi:include href="xml/gtkimcontextsimple.xml" />

View File

@@ -8166,7 +8166,6 @@ GTK_GESTURE_ZOOM_GET_CLASS
gtk_gesture_zoom_get_type
</SECTION>
<SECTION>
<FILE>gtksidebar</FILE>
GtkSidebar
@@ -8185,3 +8184,25 @@ GTK_SIDEBAR_GET_CLASS
GtkSidebarPrivate
gtk_sidebar_get_type
</SECTION>
<SECTION>
<FILE>gtkglarea</FILE>
GtkGLArea
GtkGLAreaClass
gtk_gl_area_new
gtk_gl_area_get_context
<SUBSECTION>
gtk_gl_area_set_has_alpha
gtk_gl_area_get_has_alpha
gtk_gl_area_set_has_depth_buffer
gtk_gl_area_get_has_depth_buffer
gtk_gl_area_make_current
<SUBSECTION Standard>
GTK_TYPE_GL_AREA
GTK_GL_AREA
GTK_GL_AREA_CLASS
GTK_IS_GL_AREA
GTK_IS_GL_AREA_CLASS
<SUBSECTION Private>
gtk_gl_area_get_type
</SECTION>

View File

@@ -71,6 +71,7 @@ gdk_public_h_sources = \
gdkdnd.h \
gdkevents.h \
gdkframetimings.h \
gdkglcontext.h \
gdkkeys.h \
gdkkeysyms.h \
gdkkeysyms-compat.h \
@@ -107,6 +108,7 @@ gdk_private_headers = \
gdkdndprivate.h \
gdkframeclockidle.h \
gdkframeclockprivate.h \
gdkglcontextprivate.h \
gdkscreenprivate.h \
gdkinternals.h \
gdkintl.h \
@@ -131,6 +133,8 @@ gdk_c_sources = \
gdkdnd.c \
gdkevents.c \
gdkframetimings.c \
gdkgl.c \
gdkglcontext.c \
gdkglobals.c \
gdkkeys.c \
gdkkeyuni.c \

View File

@@ -148,7 +148,8 @@ static const GDebugKey gdk_debug_keys[] = {
{"draw", GDK_DEBUG_DRAW},
{"eventloop", GDK_DEBUG_EVENTLOOP},
{"frames", GDK_DEBUG_FRAMES},
{"settings", GDK_DEBUG_SETTINGS}
{"settings", GDK_DEBUG_SETTINGS},
{"opengl", GDK_DEBUG_OPENGL},
};
static gboolean

View File

@@ -41,6 +41,7 @@
#include <gdk/gdkevents.h>
#include <gdk/gdkframeclock.h>
#include <gdk/gdkframetimings.h>
#include <gdk/gdkglcontext.h>
#include <gdk/gdkkeys.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkmain.h>

View File

@@ -525,3 +525,38 @@ gdk_cairo_region_create_from_surface (cairo_surface_t *surface)
return region;
}
cairo_region_t *
gdk_cairo_region_from_clip (cairo_t *cr)
{
cairo_rectangle_list_t *rectangles;
cairo_region_t *region;
int i;
rectangles = cairo_copy_clip_rectangle_list (cr);
if (rectangles->status != CAIRO_STATUS_SUCCESS)
return NULL;
region = cairo_region_create ();
for (i = 0; i < rectangles->num_rectangles; i++)
{
cairo_rectangle_int_t clip_rect;
cairo_rectangle_t *rect;
rect = &rectangles->rectangles[i];
/* Here we assume clip rects are ints for direct targets, which
is true for cairo */
clip_rect.x = (int)rect->x;
clip_rect.y = (int)rect->y;
clip_rect.width = (int)rect->width;
clip_rect.height = (int)rect->height;
cairo_region_union_rectangle (region, &clip_rect);
}
cairo_rectangle_list_destroy (rectangles);
return region;
}

View File

@@ -70,6 +70,17 @@ GDK_AVAILABLE_IN_3_10
cairo_surface_t * gdk_cairo_surface_create_from_pixbuf (const GdkPixbuf *pixbuf,
int scale,
GdkWindow *for_window);
GDK_AVAILABLE_IN_3_16
void gdk_cairo_draw_from_gl (cairo_t *cr,
GdkWindow *window,
int source,
int source_type,
int buffer_scale,
int x,
int y,
int width,
int height);
G_END_DECLS

View File

@@ -2225,3 +2225,66 @@ gdk_error_trap_pop (void)
{
return gdk_error_trap_pop_internal (TRUE);
}
/*< private >
* gdk_display_destroy_gl_context:
* @display: a #GdkDisplay
* @context: a #GdkGLContext
*
* Destroys the platform-specific parts of the @context.
*
* The @context instance is still valid, though inert, after
* this functionr returns.
*/
void
gdk_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context)
{
GdkGLContext *current = gdk_display_get_current_gl_context (display);
if (current == context)
g_object_set_data (G_OBJECT (display), "-gdk-gl-current-context", NULL);
GDK_DISPLAY_GET_CLASS (display)->destroy_gl_context (display, context);
}
/*< private >
* gdk_display_make_gl_context_current:
* @display: a #GdkDisplay
* @context: (optional): a #GdkGLContext, or %NULL
*
* Makes the given @context the current GL context, or unsets
* the current GL context if @context is %NULL.
*/
void
gdk_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context)
{
GdkGLContext *current = gdk_display_get_current_gl_context (display);
if (current == context)
return;
if (context == NULL)
g_object_set_data (G_OBJECT (display), "-gdk-gl-current-context", NULL);
else
g_object_set_data_full (G_OBJECT (display), "-gdk-gl-current-context",
g_object_ref (context),
(GDestroyNotify) g_object_unref);
GDK_DISPLAY_GET_CLASS (display)->make_gl_context_current (display, context);
}
/*< private >
* gdk_display_get_current_gl_context:
* @display: a #GdkDisplay
*
* Retrieves the current #GdkGLContext associated with @display.
*
* Returns: (transfer none): the current #GdkGLContext or %NULL
*/
GdkGLContext *
gdk_display_get_current_gl_context (GdkDisplay *display)
{
return g_object_get_data (G_OBJECT (display), "-gdk-gl-current-context");
}

View File

@@ -225,6 +225,11 @@ struct _GdkDisplayClass
gchar * (*utf8_to_string_target) (GdkDisplay *display,
const gchar *text);
void (*make_gl_context_current) (GdkDisplay *display,
GdkGLContext *context);
void (*destroy_gl_context) (GdkDisplay *display,
GdkGLContext *context);
/* Signals */
void (*opened) (GdkDisplay *display);
void (*closed) (GdkDisplay *display,
@@ -303,6 +308,12 @@ void _gdk_display_create_window_impl (GdkDisplay *display
gint attributes_mask);
GdkWindow * _gdk_display_create_window (GdkDisplay *display);
void gdk_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context);
void gdk_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context);
GdkGLContext * gdk_display_get_current_gl_context (GdkDisplay *display);
G_END_DECLS
#endif /* __GDK_DISPLAY_PRIVATE_H__ */

461
gdk/gdkgl.c Normal file
View File

@@ -0,0 +1,461 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2014 Red Hat, Inc.
*
* 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 "gdkcairo.h"
#include "gdkglcontextprivate.h"
#include "gdkinternals.h"
#include <epoxy/gl.h>
#include <math.h>
static cairo_user_data_key_t direct_key;
void
gdk_cairo_surface_mark_as_direct (cairo_surface_t *surface,
GdkWindow *window)
{
cairo_surface_set_user_data (surface, &direct_key,
g_object_ref (window), g_object_unref);
}
/* x,y,width,height describes a rectangle in the gl render buffer
coordinate space, and its top left corner is drawn at the current
position according to the cairo translation. */
/**
* gdk_cairo_draw_from_gl:
* @cr: a cairo context
* @window: The window we're rendering for (not necessarily into)
* @source: The GL ID of the source buffer
* @source_type: The type of the @source
* @buffer_scale: The scale-factor that the @source buffer is allocated for
* @x: The source x position in @source to start copying from in GL coordinates
* @y: The source y position in @source to start copying from in GL coordinates
* @width: The width of the region to draw
* @height: The height of the region to draw
*
* This is the main way to draw GL content in GTK+. It takes a render buffer ID
* (@source_type == #GL_RENDERBUFFER) or a texture id (@source_type == #GL_TEXTURE)
* and draws it onto @cr with an OVER operation, respecting the current clip.
*
* This will work for *all* cairo_t, as long as @window is realized, but the
* fallback implementation that reads back the pixels from the buffer may be
* used in the general case. In the case of direct drawing to a window with
* no special effects applied to @cr it will however use a more efficient
* approach.
*
* For #GL_RENDERBUFFER the code will always fall back to software for buffers
* with alpha components, so make sure you use #GL_TEXTURE if using alpha.
*
* Since: 3.16
*/
void
gdk_cairo_draw_from_gl (cairo_t *cr,
GdkWindow *window,
int source,
int source_type,
int buffer_scale,
int x,
int y,
int width,
int height)
{
GdkGLContext *context;
cairo_surface_t *image;
cairo_matrix_t matrix;
int dx, dy, window_scale;
gboolean trivial_transform;
cairo_surface_t *group_target;
GdkWindow *direct_window, *impl_window;
GLuint framebuffer;
GLint alpha_size = 0;
cairo_region_t *clip_region;
impl_window = window->impl_window;
window_scale = gdk_window_get_scale_factor (impl_window);
context = gdk_window_get_paint_gl_context (window, NULL);
if (context == NULL)
{
g_warning ("gdk_cairo_draw_gl_render_buffer failed - no paint context");
return;
}
clip_region = gdk_cairo_region_from_clip (cr);
gdk_gl_context_make_current (context);
glGenFramebuffersEXT (1, &framebuffer);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, framebuffer);
if (source_type == GL_RENDERBUFFER)
{
glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_ALPHA_SIZE, &alpha_size);
glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, source);
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, source);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
}
else if (source_type == GL_TEXTURE)
{
glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &alpha_size);
glBindTexture (GL_TEXTURE_2D, source);
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);
glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, source, 0);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
}
else
{
g_warning ("Unsupported gl source type %d\n", source_type);
return;
}
group_target = cairo_get_group_target (cr);
direct_window = cairo_surface_get_user_data (group_target, &direct_key);
cairo_get_matrix (cr, &matrix);
dx = matrix.x0;
dy = matrix.y0;
/* Trivial == integer-only translation */
trivial_transform =
(double)dx == matrix.x0 && (double)dy == matrix.y0 &&
matrix.xx == 1.0 && matrix.xy == 0.0 &&
matrix.yx == 0.0 && matrix.yy == 1.0;
/* For direct paint of non-alpha renderbuffer, we can
just do a bitblit */
if (source_type == GL_RENDERBUFFER &&
alpha_size == 0 &&
direct_window != NULL &&
direct_window->current_paint.use_gl &&
trivial_transform &&
clip_region != NULL)
{
int window_height;
int i;
/* Translate to impl coords */
cairo_region_translate (clip_region, dx, dy);
glEnable (GL_SCISSOR_TEST);
window_height = gdk_window_get_height (impl_window);
glDrawBuffer (GL_BACK);
#define FLIP_Y(_y) (window_height*window_scale - (_y))
for (i = 0; i < cairo_region_num_rectangles (clip_region); i++)
{
cairo_rectangle_int_t clip_rect, dest;
cairo_region_get_rectangle (clip_region, i, &clip_rect);
clip_rect.x *= window_scale;
clip_rect.y *= window_scale;
clip_rect.width *= window_scale;
clip_rect.height *= window_scale;
glScissor (clip_rect.x, FLIP_Y (clip_rect.y + clip_rect.height),
clip_rect.width, clip_rect.height);
dest.x = dx * window_scale;
dest.y = dy * window_scale;
dest.width = width * window_scale / buffer_scale;
dest.height = height * window_scale / buffer_scale;
if (gdk_rectangle_intersect (&clip_rect, &dest, &dest))
{
int clipped_src_x = x + (dest.x - dx * window_scale);
int clipped_src_y = y + (height - dest.height - (dest.y - dy * window_scale));
glBlitFramebufferEXT(clipped_src_x, clipped_src_y,
(clipped_src_x + dest.width), (clipped_src_y + dest.height),
dest.x, FLIP_Y(dest.y + dest.height),
dest.x + dest.width, FLIP_Y(dest.y),
GL_COLOR_BUFFER_BIT, GL_NEAREST);
if (impl_window->current_paint.flushed_region)
{
cairo_rectangle_int_t flushed_rect;
flushed_rect.x = dest.x / window_scale;
flushed_rect.y = dest.y / window_scale;
flushed_rect.width = (dest.x + dest.width + window_scale - 1) / window_scale - flushed_rect.x;
flushed_rect.height = (dest.y + dest.height + window_scale - 1) / window_scale - flushed_rect.y;
cairo_region_union_rectangle (impl_window->current_paint.flushed_region,
&flushed_rect);
cairo_region_subtract_rectangle (impl_window->current_paint.need_blend_region,
&flushed_rect);
}
}
}
glDisable (GL_SCISSOR_TEST);
#undef FLIP_Y
}
/* For direct paint of alpha or non-alpha textures we can use texturing */
else if (source_type == GL_TEXTURE &&
direct_window != NULL &&
direct_window->current_paint.use_gl &&
trivial_transform &&
clip_region != NULL)
{
int window_height;
GLint texture_width;
GLint texture_height;
int i;
/* Translate to impl coords */
cairo_region_translate (clip_region, dx, dy);
glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texture_width);
glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texture_height);
if (alpha_size != 0)
{
cairo_region_t *opaque_region, *blend_region;
opaque_region = cairo_region_copy (clip_region);
cairo_region_subtract (opaque_region, impl_window->current_paint.flushed_region);
cairo_region_subtract (opaque_region, impl_window->current_paint.need_blend_region);
if (!cairo_region_is_empty (opaque_region))
gdk_gl_texture_from_surface (impl_window->current_paint.surface,
opaque_region);
blend_region = cairo_region_copy (clip_region);
cairo_region_intersect (blend_region, impl_window->current_paint.need_blend_region);
glEnable (GL_BLEND);
if (!cairo_region_is_empty (blend_region))
gdk_gl_texture_from_surface (impl_window->current_paint.surface,
blend_region);
cairo_region_destroy (opaque_region);
cairo_region_destroy (blend_region);
}
glEnable (GL_SCISSOR_TEST);
glEnable (GL_TEXTURE_2D);
window_height = gdk_window_get_height (impl_window);
#define FLIP_Y(_y) (window_height*window_scale - (_y))
for (i = 0; i < cairo_region_num_rectangles (clip_region); i++)
{
cairo_rectangle_int_t clip_rect, dest;
cairo_region_get_rectangle (clip_region, i, &clip_rect);
clip_rect.x *= window_scale;
clip_rect.y *= window_scale;
clip_rect.width *= window_scale;
clip_rect.height *= window_scale;
glScissor (clip_rect.x, FLIP_Y (clip_rect.y + clip_rect.height),
clip_rect.width, clip_rect.height);
dest.x = dx * window_scale;
dest.y = dy * window_scale;
dest.width = width * window_scale / buffer_scale;
dest.height = height * window_scale / buffer_scale;
if (gdk_rectangle_intersect (&clip_rect, &dest, &dest))
{
int clipped_src_x = x + (dest.x - dx * window_scale);
int clipped_src_y = y + (height - dest.height - (dest.y - dy * window_scale));
glBegin (GL_QUADS);
glTexCoord2f (clipped_src_x / (float)texture_width, clipped_src_y / (float)texture_height);
glVertex2f (dest.x, FLIP_Y(dest.y + dest.height));
glTexCoord2f ((clipped_src_x + dest.width) / (float)texture_width, clipped_src_y / (float)texture_height);
glVertex2f (dest.x + dest.width, FLIP_Y(dest.y + dest.height));
glTexCoord2f ((clipped_src_x + dest.width) / (float)texture_width, (clipped_src_y + dest.height) / (float)texture_height);
glVertex2f (dest.x + dest.width, FLIP_Y(dest.y));
glTexCoord2f (clipped_src_x / (float)texture_width, (clipped_src_y + dest.height) / (float)texture_height);
glVertex2f (dest.x, FLIP_Y(dest.y));
glEnd();
if (impl_window->current_paint.flushed_region)
{
cairo_rectangle_int_t flushed_rect;
flushed_rect.x = dest.x / window_scale;
flushed_rect.y = dest.y / window_scale;
flushed_rect.width = (dest.x + dest.width + window_scale - 1) / window_scale - flushed_rect.x;
flushed_rect.height = (dest.y + dest.height + window_scale - 1) / window_scale - flushed_rect.y;
cairo_region_union_rectangle (impl_window->current_paint.flushed_region,
&flushed_rect);
cairo_region_subtract_rectangle (impl_window->current_paint.need_blend_region,
&flushed_rect);
}
}
}
if (alpha_size != 0)
glDisable (GL_BLEND);
glDisable (GL_TEXTURE_2D);
glDisable (GL_SCISSOR_TEST);
#undef FLIP_Y
}
else
{
/* Software fallback */
/* TODO: avoid reading back non-required data due to dest clip */
image = cairo_surface_create_similar_image (cairo_get_target (cr),
(alpha_size == 0) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
width, height);
#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
cairo_surface_set_device_scale (image, buffer_scale, buffer_scale);
#endif
glPixelStorei (GL_PACK_ALIGNMENT, 4);
glPixelStorei (GL_PACK_ROW_LENGTH, cairo_image_surface_get_stride (image) / 4);
glReadPixels (x, y, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
cairo_image_surface_get_data (image));
glPixelStorei (GL_PACK_ROW_LENGTH, 0);
cairo_surface_mark_dirty (image);
/* Invert due to opengl having different origin */
cairo_scale (cr, 1, -1);
cairo_translate (cr, 0, -height / buffer_scale);
cairo_set_source_surface (cr, image, 0, 0);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_paint (cr);
cairo_surface_destroy (image);
}
glDrawBuffer (GL_BACK);
glReadBuffer(GL_BACK);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
glDeleteFramebuffersEXT (1, &framebuffer);
if (clip_region)
cairo_region_destroy (clip_region);
}
void
gdk_gl_texture_from_surface (cairo_surface_t *surface,
cairo_region_t *region)
{
GdkGLContext *current;
cairo_surface_t *image;
double device_x_offset, device_y_offset;
cairo_rectangle_int_t rect, e;
int n_rects, i;
GdkWindow *window;
int window_height;
unsigned int texture_id;
int window_scale;
double sx, sy;
current = gdk_gl_context_get_current ();
if (current &&
GDK_GL_CONTEXT_GET_CLASS (current)->texture_from_surface &&
GDK_GL_CONTEXT_GET_CLASS (current)->texture_from_surface (current, surface, region))
return;
/* Software fallback */
window = gdk_gl_context_get_window (gdk_gl_context_get_current ());
window_scale = gdk_window_get_scale_factor (window);
window_height = gdk_window_get_height (window);
sx = sy = 1;
#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
cairo_surface_get_device_scale (window->current_paint.surface, &sx, &sy);
#endif
cairo_surface_get_device_offset (surface,
&device_x_offset, &device_y_offset);
glGenTextures (1, &texture_id);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, texture_id);
glEnable (GL_TEXTURE_RECTANGLE_ARB);
n_rects = cairo_region_num_rectangles (region);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rect);
glScissor (rect.x * window_scale, (window_height - rect.y - rect.height) * window_scale,
rect.width * window_scale, rect.height * window_scale);
e = rect;
e.x *= sx;
e.y *= sy;
e.x += (int)device_x_offset;
e.y += (int)device_y_offset;
e.width *= sx;
e.height *= sy;
image = cairo_surface_map_to_image (surface, &e);
glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
glPixelStorei (GL_UNPACK_ROW_LENGTH, cairo_image_surface_get_stride (image)/4);
glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, 4, e.width, e.height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
cairo_image_surface_get_data (image));
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
cairo_surface_unmap_image (surface, image);
#define FLIP_Y(_y) (window_height - (_y))
glBegin (GL_QUADS);
glTexCoord2f (0.0f * sx, rect.height * sy);
glVertex2f (rect.x * window_scale, FLIP_Y(rect.y + rect.height) * window_scale);
glTexCoord2f (rect.width * sx, rect.height * sy);
glVertex2f ((rect.x + rect.width) * window_scale, FLIP_Y(rect.y + rect.height) * window_scale);
glTexCoord2f (rect.width * sx, 0.0f * sy);
glVertex2f ((rect.x + rect.width) * window_scale, FLIP_Y(rect.y) * window_scale);
glTexCoord2f (0.0f * sx, 0.0f * sy);
glVertex2f (rect.x * window_scale, FLIP_Y(rect.y) * window_scale);
glEnd();
}
glDisable (GL_TEXTURE_RECTANGLE_ARB);
glDeleteTextures (1, &texture_id);
}

339
gdk/gdkglcontext.c Normal file
View File

@@ -0,0 +1,339 @@
/* 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 match
* the #GdkVisual of the window.
*
* A #GdkGLContext is not tied to any particular normal framebuffer.
* For instance, it cannot draw to the #GdkWindow back buffer. The GDK
* repaint system is in full control of the painting to 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 %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.
*
* Since: 3.16
*/
void
gdk_gl_context_make_current (GdkGLContext *context)
{
GdkGLContextPrivate *priv = gdk_gl_context_get_instance_private (context);
g_return_if_fail (GDK_IS_GL_CONTEXT (context));
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);
}

59
gdk/gdkglcontext.h Normal file
View File

@@ -0,0 +1,59 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext.h: 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/>.
*/
#ifndef __GDK_GL_CONTEXT_H__
#define __GDK_GL_CONTEXT_H__
#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdk.h> can be included directly."
#endif
#include <gdk/gdkversionmacros.h>
#include <gdk/gdktypes.h>
G_BEGIN_DECLS
#define GDK_TYPE_GL_CONTEXT (gdk_gl_context_get_type ())
#define GDK_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_GL_CONTEXT, GdkGLContext))
#define GDK_IS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_GL_CONTEXT))
#define GDK_GL_ERROR (gdk_gl_error_quark ())
GDK_AVAILABLE_IN_3_16
GQuark gdk_gl_error_quark (void);
GDK_AVAILABLE_IN_3_16
GType gdk_gl_context_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_3_16
GdkVisual * gdk_gl_context_get_visual (GdkGLContext *context);
GDK_AVAILABLE_IN_3_16
GdkWindow * gdk_gl_context_get_window (GdkGLContext *context);
GDK_AVAILABLE_IN_3_16
void gdk_gl_context_make_current (GdkGLContext *context);
GDK_AVAILABLE_IN_3_16
GdkGLContext * gdk_gl_context_get_current (void);
GDK_AVAILABLE_IN_3_16
void gdk_gl_context_clear_current (void);
G_END_DECLS
#endif /* __GDK_GL_CONTEXT_H__ */

58
gdk/gdkglcontextprivate.h Normal file
View File

@@ -0,0 +1,58 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontextprivate.h: 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/>.
*/
#ifndef __GDK_GL_CONTEXT_PRIVATE_H__
#define __GDK_GL_CONTEXT_PRIVATE_H__
#include "gdkglcontext.h"
G_BEGIN_DECLS
#define GDK_GL_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_GL_CONTEXT, GdkGLContextClass))
#define GDK_IS_GL_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_GL_CONTEXT))
#define GDK_GL_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_GL_CONTEXT, GdkGLContextClass))
typedef struct _GdkGLContextClass GdkGLContextClass;
struct _GdkGLContext
{
GObject parent_instance;
};
struct _GdkGLContextClass
{
GObjectClass parent_class;
void (* update) (GdkGLContext *context);
void (* flush_buffer) (GdkGLContext *context,
cairo_region_t *painted,
cairo_region_t *damage);
gboolean (* texture_from_surface) (GdkGLContext *context,
cairo_surface_t *surface,
cairo_region_t *region);
};
void gdk_gl_context_flush_buffer (GdkGLContext *context,
cairo_region_t *painted,
cairo_region_t *damage);
G_END_DECLS
#endif /* __GDK_GL_CONTEXT_PRIVATE_H__ */

View File

@@ -84,7 +84,8 @@ typedef enum {
GDK_DEBUG_DRAW = 1 << 9,
GDK_DEBUG_EVENTLOOP = 1 << 10,
GDK_DEBUG_FRAMES = 1 << 11,
GDK_DEBUG_SETTINGS = 1 << 12
GDK_DEBUG_SETTINGS = 1 << 12,
GDK_DEBUG_OPENGL = 1 << 13
} GdkDebugFlag;
typedef enum {
@@ -208,11 +209,27 @@ struct _GdkWindow
struct {
cairo_region_t *region;
cairo_surface_t *surface;
/* Areas of region that have been copied to the back buffer already */
cairo_region_t *flushed_region;
/* Areas of region that have been copied to the back buffer but
needs furter blending of surface data. These two regions are
always non-intersecting. */
cairo_region_t *need_blend_region;
gboolean surface_needs_composite;
gboolean use_gl;
} current_paint;
GdkGLContext *gl_paint_context;
cairo_region_t *update_area;
guint update_freeze_count;
/* This is the update_area that was in effect when the current expose
started. It may be smaller than the expose area if we'e painting
more than we have to, but it represents the "true" damage. */
cairo_region_t *active_update_area;
/* We store the old expose areas to support buffer-age optimizations */
cairo_region_t *old_updated_area[2];
GdkWindowState state;
@@ -319,8 +336,14 @@ void _gdk_windowing_event_data_free (GdkEvent *event);
void _gdk_set_window_state (GdkWindow *window,
GdkWindowState new_state);
gboolean _gdk_cairo_surface_extents (cairo_surface_t *surface,
GdkRectangle *extents);
gboolean _gdk_cairo_surface_extents (cairo_surface_t *surface,
GdkRectangle *extents);
void gdk_gl_texture_from_surface (cairo_surface_t *surface,
cairo_region_t *region);
void gdk_cairo_surface_mark_as_direct (cairo_surface_t *surface,
GdkWindow *window);
cairo_region_t *gdk_cairo_region_from_clip (cairo_t *cr);
/*************************************
* Interfaces used by windowing code *
@@ -334,6 +357,9 @@ void _gdk_window_destroy (GdkWindow *window,
void _gdk_window_clear_update_area (GdkWindow *window);
void _gdk_window_update_size (GdkWindow *window);
gboolean _gdk_window_update_viewable (GdkWindow *window);
GdkGLContext * gdk_window_get_paint_gl_context (GdkWindow *window,
GError **error);
void _gdk_window_process_updates_recurse (GdkWindow *window,
cairo_region_t *expose_region);

View File

@@ -128,6 +128,8 @@ typedef struct _GdkWindow GdkWindow;
typedef struct _GdkKeymap GdkKeymap;
typedef struct _GdkAppLaunchContext GdkAppLaunchContext;
typedef struct _GdkGLContext GdkGLContext;
/**
* GdkByteOrder:
* @GDK_LSB_FIRST: The values are stored with the least-significant byte
@@ -429,8 +431,26 @@ struct _GdkPoint
gint y;
};
/**
* GdkGLProfile:
* @GDK_GL_PROFILE_DEFAULT: ...
* @GDK_GL_PROFILE_LEGACY: ...
* @GDK_GL_PROFILE_3_2_CORE: ...
*
* ...
*/
typedef enum {
GDK_GL_PROFILE_DEFAULT,
GDK_GL_PROFILE_LEGACY,
GDK_GL_PROFILE_3_2_CORE
} GdkGLProfile;
typedef enum {
GDK_GL_ERROR_NOT_AVAILABLE,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
GDK_GL_ERROR_UNSUPPORTED_PROFILE
} GdkGLError;
G_END_DECLS
#endif /* __GDK_TYPES_H__ */

View File

@@ -39,9 +39,12 @@
#include "gdkmarshalers.h"
#include "gdkframeclockidle.h"
#include "gdkwindowimpl.h"
#include "gdkglcontextprivate.h"
#include <math.h>
#include <epoxy/gl.h>
/* for the use of round() */
#include "fallback-c89.c"
@@ -185,6 +188,11 @@ static cairo_surface_t *gdk_window_ref_impl_surface (GdkWindow *window);
static void gdk_window_set_frame_clock (GdkWindow *window,
GdkFrameClock *clock);
static void draw_ugly_color (GdkWindow *window,
const cairo_region_t *region,
int color);
static guint signals[LAST_SIGNAL] = { 0 };
static gpointer parent_class = NULL;
@@ -279,11 +287,16 @@ create_surface_accumulator (GSignalInvocationHint *ihint,
static GQuark quark_pointer_window = 0;
static gboolean always_use_gl = FALSE;
static void
gdk_window_class_init (GdkWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
if (g_getenv ("GDK_ALWAYS_USE_GL"))
always_use_gl = TRUE;
parent_class = g_type_class_peek_parent (klass);
object_class->finalize = gdk_window_finalize;
@@ -1024,9 +1037,35 @@ recompute_visible_regions (GdkWindow *private,
recalculate_children);
}
static void
gdk_window_clear_old_updated_area (GdkWindow *window)
{
int i;
for (i = 0; i < 2; i++)
{
if (window->old_updated_area[i])
{
cairo_region_destroy (window->old_updated_area[i]);
window->old_updated_area[i] = NULL;
}
}
}
static void
gdk_window_append_old_updated_area (GdkWindow *window,
cairo_region_t *region)
{
if (window->old_updated_area[1])
cairo_region_destroy (window->old_updated_area[1]);
window->old_updated_area[1] = window->old_updated_area[0];
window->old_updated_area[0] = cairo_region_reference (region);
}
void
_gdk_window_update_size (GdkWindow *window)
{
gdk_window_clear_old_updated_area (window);
recompute_visible_regions (window, FALSE);
}
@@ -1384,6 +1423,18 @@ gdk_window_new (GdkWindow *parent,
g_signal_connect (device_manager, "device-removed",
G_CALLBACK (device_removed_cb), window);
if (always_use_gl)
{
GError *error = NULL;
if (gdk_window_get_paint_gl_context (window, &error) == NULL)
{
g_warning ("Unable to force GL enabled: %s\n", error->message);
g_error_free (error);
}
}
return window;
}
@@ -1835,6 +1886,12 @@ gdk_window_free_current_paint (GdkWindow *window)
cairo_region_destroy (window->current_paint.region);
window->current_paint.region = NULL;
cairo_region_destroy (window->current_paint.flushed_region);
window->current_paint.flushed_region = NULL;
cairo_region_destroy (window->current_paint.need_blend_region);
window->current_paint.need_blend_region = NULL;
window->current_paint.surface_needs_composite = FALSE;
}
@@ -2662,6 +2719,58 @@ gdk_window_ref_impl_surface (GdkWindow *window)
return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->ref_cairo_surface (gdk_window_get_impl_window (window));
}
GdkGLContext *
gdk_window_get_paint_gl_context (GdkWindow *window, GError **error)
{
if (window->impl_window->gl_paint_context == NULL)
window->impl_window->gl_paint_context =
GDK_WINDOW_IMPL_GET_CLASS (window->impl)->create_gl_context (window,
TRUE,
GDK_GL_PROFILE_DEFAULT,
NULL,
error);
return window->impl_window->gl_paint_context;
}
/**
* gdk_window_create_gl_context:
* @window: a #GdkWindow
* @profile: the GL profile the context should target
* @error: return location for an error
*
* Creates a new #GdkGLContext matching the
* framebuffer format to the visual of the #GdkWindow. The context
* is disconnected from any particular window or surface.
*
* If the creation of the #GdkGLContext failed, @error will be set.
*
* Returns: (transfer full): the newly created #GdkGLContext, or
* %NULL on error
*
* Since: 3.16
**/
GdkGLContext *
gdk_window_create_gl_context (GdkWindow *window,
GdkGLProfile profile,
GError **error)
{
GdkGLContext *paint_context;
g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
paint_context = gdk_window_get_paint_gl_context (window, error);
if (paint_context == NULL)
return NULL;
return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->create_gl_context (window,
FALSE,
profile,
paint_context,
error);
}
/**
* gdk_window_begin_paint_rect:
* @window: a #GdkWindow
@@ -2738,6 +2847,7 @@ gdk_window_begin_paint_region (GdkWindow *window,
GdkWindowImplClass *impl_class;
double sx, sy;
gboolean needs_surface;
cairo_content_t surface_content;
g_return_if_fail (GDK_IS_WINDOW (window));
@@ -2759,14 +2869,59 @@ gdk_window_begin_paint_region (GdkWindow *window,
needs_surface = impl_class->begin_paint_region (window, region);
window->current_paint.region = cairo_region_copy (region);
cairo_region_intersect (window->current_paint.region, window->clip_region);
cairo_region_get_extents (window->current_paint.region, &clip_box);
window->current_paint.flushed_region = cairo_region_create ();
window->current_paint.need_blend_region = cairo_region_create ();
surface_content = gdk_window_get_content (window);
window->current_paint.use_gl = window->impl_window->gl_paint_context != NULL;
if (window->current_paint.use_gl)
{
GdkGLContext *context;
int ww = gdk_window_get_width (window) * gdk_window_get_scale_factor (window);
int wh = gdk_window_get_height (window) * gdk_window_get_scale_factor (window);
context = gdk_window_get_paint_gl_context (window, NULL);
if (context == NULL)
{
g_warning ("gl rendering failed, context: %p", context);
window->current_paint.use_gl = FALSE;
}
else
{
gdk_gl_context_make_current (context);
/* With gl we always need a surface to combine the gl
drawing with the native drawing. */
needs_surface = TRUE;
/* Also, we need the surface to include alpha */
surface_content = CAIRO_CONTENT_COLOR_ALPHA;
/* Initial setup */
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glDisable (GL_DEPTH_TEST);
glDisable(GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glViewport (0, 0, ww, wh);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0.0f, ww, 0.0f, wh, -1.0f, 1.0f);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
}
}
if (needs_surface)
{
window->current_paint.surface = gdk_window_create_similar_surface (window,
gdk_window_get_content (window),
surface_content,
MAX (clip_box.width, 1),
MAX (clip_box.height, 1));
sx = sy = 1;
@@ -2774,6 +2929,7 @@ gdk_window_begin_paint_region (GdkWindow *window,
cairo_surface_get_device_scale (window->current_paint.surface, &sx, &sy);
#endif
cairo_surface_set_device_offset (window->current_paint.surface, -clip_box.x*sx, -clip_box.y*sy);
gdk_cairo_surface_mark_as_direct (window->current_paint.surface, window);
window->current_paint.surface_needs_composite = TRUE;
}
@@ -2787,18 +2943,87 @@ gdk_window_begin_paint_region (GdkWindow *window,
gdk_window_clear_backing_region (window);
}
/**
* gdk_window_mark_paint_from_clip:
* @window: a #GdkWindow
* @cr: a #cairo_t
*
* If you call this during a paint (e.g. between gdk_window_begin_paint_region()
* and gdk_window_end_paint() then GDK will mark the current clip region of the
* window as being drawn. This is required when mixing GL rendering via
* gdk_cairo_draw_from_gl() and cairo rendering, as otherwise GDK has no way
* of knowing when something paints over the GL-drawn regions.
*
* This is typically called automatically by GTK+ and you don't need
* to care about this.
*
* Since: 3.16
**/
void
gdk_window_mark_paint_from_clip (GdkWindow *window,
cairo_t *cr)
{
cairo_region_t *clip_region;
GdkWindow *impl_window = window->impl_window;
if (impl_window->current_paint.surface == NULL ||
cairo_get_target (cr) != impl_window->current_paint.surface)
return;
if (cairo_region_is_empty (impl_window->current_paint.flushed_region))
return;
/* This here seems a bit weird, but basically, we're taking the current
clip and applying also the flushed region, and the result is that the
new clip is the intersection of these. This is the area where the newly
drawn region overlaps a previosly flushed area, which is an area of the
double buffer surface that need to be blended OVER the back buffer rather
than SRCed. */
cairo_save (cr);
/* We set the identity matrix here so we get and apply regions in native
window coordinates. */
cairo_identity_matrix (cr);
gdk_cairo_region (cr, impl_window->current_paint.flushed_region);
cairo_clip (cr);
clip_region = gdk_cairo_region_from_clip (cr);
if (clip_region == NULL)
{
/* Failed to represent clip as region, mark all as requiring
blend */
cairo_region_union (impl_window->current_paint.need_blend_region,
impl_window->current_paint.flushed_region);
cairo_region_destroy (impl_window->current_paint.flushed_region);
impl_window->current_paint.flushed_region = cairo_region_create ();
}
else
{
cairo_region_subtract (impl_window->current_paint.flushed_region, clip_region);
cairo_region_union (impl_window->current_paint.need_blend_region, clip_region);
}
/* Clear the area on the double buffer surface to transparent so we
can start drawing from scratch the area "above" the flushed
region */
cairo_set_source_rgba (cr, 0, 0, 0, 0);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
cairo_restore (cr);
}
/**
* gdk_window_end_paint:
* @window: a #GdkWindow
*
* Indicates that the backing store created by the most recent call to
* gdk_window_begin_paint_region() should be copied onscreen and
* Indicates that the backing store created by the most recent call
* to gdk_window_begin_paint_region() should be copied onscreen and
* deleted, leaving the next-most-recent backing store or no backing
* store at all as the active paint region. See
* gdk_window_begin_paint_region() for full details. It is an error to
* call this function without a matching
* gdk_window_begin_paint_region() first.
* gdk_window_begin_paint_region() for full details.
*
* It is an error to call this function without a matching
* gdk_window_begin_paint_region() first.
**/
void
gdk_window_end_paint (GdkWindow *window)
@@ -2806,7 +3031,6 @@ gdk_window_end_paint (GdkWindow *window)
GdkWindow *composited;
GdkWindowImplClass *impl_class;
GdkRectangle clip_box = { 0, };
cairo_region_t *full_clip;
cairo_t *cr;
g_return_if_fail (GDK_IS_WINDOW (window));
@@ -2826,41 +3050,56 @@ gdk_window_end_paint (GdkWindow *window)
if (impl_class->end_paint)
impl_class->end_paint (window);
if (window->current_paint.surface_needs_composite)
{
cairo_surface_t *surface;
gboolean skip_alpha_blending;
cairo_region_get_extents (window->current_paint.region, &clip_box);
full_clip = cairo_region_copy (window->clip_region);
cairo_region_intersect (full_clip, window->current_paint.region);
surface = gdk_window_ref_impl_surface (window);
cr = cairo_create (surface);
cairo_surface_destroy (surface);
cairo_set_source_surface (cr, window->current_paint.surface, 0, 0);
gdk_cairo_region (cr, full_clip);
cairo_clip (cr);
/* We can skip alpha blending for a fast composite case
* if we have an impl window or we're a fully opaque window. */
skip_alpha_blending = (gdk_window_has_impl (window) ||
window->alpha == 255);
if (skip_alpha_blending)
if (window->current_paint.use_gl)
{
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
cairo_region_t *opaque_region = cairo_region_copy (window->current_paint.region);
cairo_region_subtract (opaque_region, window->current_paint.flushed_region);
cairo_region_subtract (opaque_region, window->current_paint.need_blend_region);
gdk_gl_context_make_current (window->gl_paint_context);
if (!cairo_region_is_empty (opaque_region))
gdk_gl_texture_from_surface (window->current_paint.surface,
opaque_region);
if (!cairo_region_is_empty (window->current_paint.need_blend_region))
{
glEnable(GL_BLEND);
gdk_gl_texture_from_surface (window->current_paint.surface,
window->current_paint.need_blend_region);
glDisable(GL_BLEND);
}
cairo_region_destroy (opaque_region);
gdk_gl_context_flush_buffer (window->gl_paint_context,
window->current_paint.region,
window->active_update_area);
if (epoxy_has_gl_extension ("GL_GREMEDY_frame_terminator"))
glFrameTerminatorGREMEDY();
}
else
{
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_paint_with_alpha (cr, window->alpha / 255.0);
}
surface = gdk_window_ref_impl_surface (window);
cr = cairo_create (surface);
cairo_surface_destroy (surface);
cairo_destroy (cr);
cairo_region_destroy (full_clip);
cairo_set_source_surface (cr, window->current_paint.surface, 0, 0);
gdk_cairo_region (cr, window->current_paint.region);
cairo_clip (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
cairo_destroy (cr);
}
}
gdk_window_free_current_paint (window);
@@ -3375,18 +3614,33 @@ gdk_window_process_updates_internal (GdkWindow *window)
*/
if (window->update_area)
{
cairo_region_t *update_area = window->update_area;
g_assert (window->active_update_area == NULL); /* No reentrancy */
window->active_update_area = window->update_area;
window->update_area = NULL;
if (gdk_window_is_viewable (window))
{
cairo_region_t *expose_region;
expose_region = cairo_region_copy (window->active_update_area);
/* Sometimes we can't just paint only the new area, as the windowing system
requires more to be repainted. For instance, with opengl you typically
repaint all of each frame each time and then swap the buffer, although
there are extensions that allow us to reuse part of an old frame */
if (GDK_WINDOW_IMPL_GET_CLASS (window->impl)->invalidate_for_new_frame)
GDK_WINDOW_IMPL_GET_CLASS (window->impl)->invalidate_for_new_frame (window, expose_region);
/* Clip to part visible in impl window */
cairo_region_intersect (update_area, window->clip_region);
cairo_region_intersect (expose_region, window->clip_region);
if (debug_updates)
{
cairo_region_t *swap_region = cairo_region_copy (expose_region);
cairo_region_subtract (swap_region, window->active_update_area);
draw_ugly_color (window, swap_region, 1);
/* Make sure we see the red invalid area before redrawing. */
gdk_display_sync (gdk_window_get_display (window));
g_usleep (70000);
@@ -3395,14 +3649,15 @@ gdk_window_process_updates_internal (GdkWindow *window)
impl_class = GDK_WINDOW_IMPL_GET_CLASS (window->impl);
if (impl_class->queue_antiexpose)
impl_class->queue_antiexpose (window, update_area);
impl_class->queue_antiexpose (window, expose_region);
expose_region = cairo_region_copy (update_area);
impl_class->process_updates_recurse (window, expose_region);
cairo_region_destroy (expose_region);
}
cairo_region_destroy (update_area);
gdk_window_append_old_updated_area (window, window->active_update_area);
}
cairo_region_destroy (window->active_update_area);
window->active_update_area = NULL;
}
window->in_update = FALSE;
@@ -3705,13 +3960,17 @@ gdk_window_set_invalidate_handler (GdkWindow *window,
static void
draw_ugly_color (GdkWindow *window,
const cairo_region_t *region)
const cairo_region_t *region,
int color)
{
cairo_t *cr;
cr = gdk_cairo_create (window);
/* Draw ugly color all over the newly-invalid region */
cairo_set_source_rgb (cr, 50000/65535., 10000/65535., 10000/65535.);
if (color == 0)
cairo_set_source_rgb (cr, 50000/65535., 10000/65535., 10000/65535.);
else
cairo_set_source_rgb (cr, 10000/65535., 50000/65535., 10000/65535.);
gdk_cairo_region (cr, region);
cairo_fill (cr);
@@ -3807,7 +4066,7 @@ gdk_window_invalidate_maybe_recurse_full (GdkWindow *window,
invalidate_impl_subwindows (window, region, child_func, user_data, 0, 0);
if (debug_updates)
draw_ugly_color (window, visible_region);
draw_ugly_color (window, visible_region, 0);
while (window != NULL &&
!cairo_region_is_empty (visible_region))
@@ -5046,6 +5305,7 @@ gdk_window_hide (GdkWindow *window)
impl_class->hide (window);
}
gdk_window_clear_old_updated_area (window);
recompute_visible_regions (window, FALSE);
/* all decendants became non-visible, we need to send visibility notify */
@@ -5105,6 +5365,7 @@ gdk_window_withdraw (GdkWindow *window)
}
recompute_visible_regions (window, FALSE);
gdk_window_clear_old_updated_area (window);
}
}

View File

@@ -739,6 +739,9 @@ cairo_region_t *gdk_window_get_visible_region(GdkWindow *window);
GDK_AVAILABLE_IN_ALL
void gdk_window_begin_paint_rect (GdkWindow *window,
const GdkRectangle *rectangle);
GDK_AVAILABLE_IN_3_16
void gdk_window_mark_paint_from_clip (GdkWindow *window,
cairo_t *cr);
GDK_AVAILABLE_IN_ALL
void gdk_window_begin_paint_region (GdkWindow *window,
const cairo_region_t *region);
@@ -1108,6 +1111,12 @@ GDK_AVAILABLE_IN_3_14
gboolean gdk_window_show_window_menu (GdkWindow *window,
GdkEvent *event);
GDK_AVAILABLE_IN_3_16
GdkGLContext * gdk_window_create_gl_context (GdkWindow *window,
GdkGLProfile profile,
GError **error);
G_END_DECLS
#endif /* __GDK_WINDOW_H__ */

View File

@@ -289,6 +289,13 @@ struct _GdkWindowImplClass
gint bottom);
gboolean (* show_window_menu) (GdkWindow *window,
GdkEvent *event);
GdkGLContext *(*create_gl_context) (GdkWindow *window,
gboolean attached,
GdkGLProfile profile,
GdkGLContext *share,
GError **error);
void (*invalidate_for_new_frame)(GdkWindow *window,
cairo_region_t *update_area);
};
/* Interface Functions */

View File

@@ -39,7 +39,9 @@ libgdk_wayland_la_SOURCES = \
gdkdisplay-wayland.h \
gdkdnd-wayland.c \
gdkeventsource.c \
gdkkeys-wayland.c \
gdkkeys-wayland.c \
gdkglcontext-wayland.c \
gdkglcontext-wayland.h \
gdkscreen-wayland.c \
gdkselection-wayland.c \
gdkwindow-wayland.c \
@@ -54,6 +56,7 @@ libgdkinclude_HEADERS = \
libgdkwaylandinclude_HEADERS = \
gdkwaylanddevice.h \
gdkwaylanddisplay.h \
gdkwaylandglcontext.h \
gdkwaylandselection.h \
gdkwaylandwindow.h

View File

@@ -34,6 +34,7 @@
#include "gdkdevicemanager.h"
#include "gdkkeysprivate.h"
#include "gdkprivate-wayland.h"
#include "gdkglcontext-wayland.h"
/**
* SECTION:wayland_interaction
@@ -540,6 +541,9 @@ gdk_wayland_display_class_init (GdkWaylandDisplayClass * class)
display_class->convert_selection = _gdk_wayland_display_convert_selection;
display_class->text_property_to_utf8_list = _gdk_wayland_display_text_property_to_utf8_list;
display_class->utf8_to_string_target = _gdk_wayland_display_utf8_to_string_target;
display_class->destroy_gl_context = gdk_wayland_display_destroy_gl_context;
display_class->make_gl_context_current = gdk_wayland_display_make_gl_context_current;
}
static void

View File

@@ -26,6 +26,7 @@
#include <stdint.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <wayland-egl.h>
#include <gdk/wayland/gtk-shell-client-protocol.h>
#include <gdk/wayland/xdg-shell-client-protocol.h>
@@ -37,6 +38,8 @@
#include "gdkdisplayprivate.h"
#include <epoxy/egl.h>
G_BEGIN_DECLS
typedef struct _GdkWaylandSelection GdkWaylandSelection;
@@ -76,6 +79,17 @@ struct _GdkWaylandDisplay
struct xkb_context *xkb_context;
GdkWaylandSelection *selection;
/* egl info */
EGLDisplay egl_display;
int egl_major_version;
int egl_minor_version;
guint have_egl : 1;
guint have_egl_khr_create_context : 1;
guint have_egl_buffer_age : 1;
guint have_egl_swap_buffers_with_damage : 1;
guint have_egl_surfaceless_context : 1;
};
struct _GdkWaylandDisplayClass

View File

@@ -0,0 +1,427 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-wayland.c: Wayland specific OpenGL wrappers
*
* Copyright © 2014 Emmanuele Bassi
* Copyright © 2014 Alexander Larsson
*
* 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/>.
*/
#include "config.h"
#include "gdkglcontext-wayland.h"
#include "gdkdisplay-wayland.h"
#include "gdkwaylanddisplay.h"
#include "gdkwaylandglcontext.h"
#include "gdkwaylandwindow.h"
#include "gdkprivate-wayland.h"
#include "gdkinternals.h"
#include "gdkintl.h"
G_DEFINE_TYPE (GdkWaylandGLContext, gdk_wayland_gl_context, GDK_TYPE_GL_CONTEXT)
static void
gdk_wayland_gl_context_update (GdkGLContext *context)
{
GdkWindow *window = gdk_gl_context_get_window (context);
int width, height;
gdk_gl_context_make_current (context);
width = gdk_window_get_width (window);
height = gdk_window_get_height (window);
GDK_NOTE (OPENGL, g_print ("Updating GL viewport size to { %d, %d } for window %p (context: %p)\n",
width, height,
window, context));
glViewport (0, 0, width, height);
}
void
gdk_wayland_window_invalidate_for_new_frame (GdkWindow *window,
cairo_region_t *update_area)
{
cairo_rectangle_int_t window_rect;
GdkDisplay *display = gdk_window_get_display (window);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandGLContext *context_wayland;
int buffer_age;
gboolean invalidate_all;
EGLSurface egl_surface;
/* Minimal update is ok if we're not drawing with gl */
if (window->gl_paint_context == NULL)
return;
context_wayland = GDK_WAYLAND_GL_CONTEXT (window->gl_paint_context);
buffer_age = 0;
egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window,
context_wayland->egl_config);
if (display_wayland->have_egl_buffer_age)
{
gdk_gl_context_make_current (window->gl_paint_context);
eglQuerySurface (display_wayland->egl_display, egl_surface,
EGL_BUFFER_AGE_EXT, &buffer_age);
}
invalidate_all = FALSE;
if (buffer_age == 0 || buffer_age >= 4)
invalidate_all = TRUE;
else
{
if (buffer_age >= 2)
{
if (window->old_updated_area[0])
cairo_region_union (update_area, window->old_updated_area[0]);
else
invalidate_all = TRUE;
}
if (buffer_age >= 3)
{
if (window->old_updated_area[1])
cairo_region_union (update_area, window->old_updated_area[1]);
else
invalidate_all = TRUE;
}
}
if (invalidate_all)
{
window_rect.x = 0;
window_rect.y = 0;
window_rect.width = gdk_window_get_width (window);
window_rect.height = gdk_window_get_height (window);
/* If nothing else is known, repaint everything so that the back
buffer is fully up-to-date for the swapbuffer */
cairo_region_union_rectangle (update_area, &window_rect);
}
}
static void
gdk_wayland_gl_context_flush_buffer (GdkGLContext *context,
cairo_region_t *painted,
cairo_region_t *damage)
{
GdkWindow *window = gdk_gl_context_get_window (context);
GdkDisplay *display = gdk_window_get_display (window);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandGLContext *context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
EGLSurface egl_surface;
gdk_gl_context_make_current (context);
egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window,
context_wayland->egl_config);
// TODO: Use eglSwapBuffersWithDamageEXT if available
if (display_wayland->have_egl_swap_buffers_with_damage)
{
int i, j, n_rects = cairo_region_num_rectangles (damage);
EGLint *rects = g_new (EGLint, n_rects * 4);
cairo_rectangle_int_t rect;
int window_height = gdk_window_get_height (window);
for (i = 0, j = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (damage, i, &rect);
rects[j++] = rect.x;
rects[j++] = window_height - rect.height - rect.y;
rects[j++] = rect.width;
rects[j++] = rect.height;
}
eglSwapBuffersWithDamageEXT (display_wayland->egl_display, egl_surface, rects, n_rects);
g_free (rects);
}
else
eglSwapBuffers (display_wayland->egl_display, egl_surface);
}
static void
gdk_wayland_gl_context_class_init (GdkWaylandGLContextClass *klass)
{
GdkGLContextClass *context_class = GDK_GL_CONTEXT_CLASS (klass);
context_class->update = gdk_wayland_gl_context_update;
context_class->flush_buffer = gdk_wayland_gl_context_flush_buffer;
}
static void
gdk_wayland_gl_context_init (GdkWaylandGLContext *self)
{
}
gboolean
gdk_wayland_display_init_gl (GdkDisplay *display)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
EGLint major, minor;
EGLDisplay *dpy;
if (display_wayland->have_egl)
return TRUE;
dpy = eglGetDisplay ((EGLNativeDisplayType)display_wayland->wl_display);
if (dpy == NULL)
return FALSE;
if (!eglInitialize (dpy, &major, &minor))
return FALSE;
if (!eglBindAPI (EGL_OPENGL_API))
return FALSE;
display_wayland->egl_display = dpy;
display_wayland->egl_major_version = major;
display_wayland->egl_minor_version = minor;
display_wayland->have_egl = TRUE;
display_wayland->have_egl_khr_create_context =
epoxy_has_egl_extension (dpy, "EGL_KHR_create_context");
display_wayland->have_egl_buffer_age =
epoxy_has_egl_extension (dpy, "EGL_EXT_buffer_age");
display_wayland->have_egl_swap_buffers_with_damage =
epoxy_has_egl_extension (dpy, "EGL_EXT_swap_buffers_with_damage");
display_wayland->have_egl_surfaceless_context =
epoxy_has_egl_extension (dpy, "EGL_KHR_surfaceless_context");
GDK_NOTE (OPENGL,
g_print ("EGL API version %d.%d found\n"
" - Vendor: %s\n"
" - Version: %s\n"
" - Client APIs: %s\n"
" - Extensions:\n"
"\t%s\n"
,
display_wayland->egl_major_version,
display_wayland->egl_minor_version,
eglQueryString (dpy, EGL_VENDOR),
eglQueryString(dpy, EGL_VERSION),
eglQueryString(dpy, EGL_CLIENT_APIS),
eglQueryString(dpy, EGL_EXTENSIONS)));
return TRUE;
}
#define MAX_EGL_ATTRS 30
static gboolean
find_eglconfig_for_window (GdkWindow *window,
EGLConfig *egl_config_out,
GError **error)
{
GdkDisplay *display = gdk_window_get_display (window);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkVisual *visual = gdk_window_get_visual (window);
EGLint attrs[MAX_EGL_ATTRS];
EGLint count;
EGLConfig *configs;
gboolean use_rgba;
int i = 0;
attrs[i++] = EGL_SURFACE_TYPE;
attrs[i++] = EGL_WINDOW_BIT;
attrs[i++] = EGL_COLOR_BUFFER_TYPE;
attrs[i++] = EGL_RGB_BUFFER;
attrs[i++] = EGL_RED_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);
attrs[i++] = EGL_GREEN_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);
attrs[i++] = EGL_BLUE_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);
use_rgba = (visual == gdk_screen_get_rgba_visual (gdk_display_get_default_screen (display)));
if (use_rgba)
{
attrs[i++] = EGL_ALPHA_SIZE;
attrs[i++] = 1;
}
else
{
attrs[i++] = EGL_ALPHA_SIZE;
attrs[i++] = 0;
}
attrs[i++] = EGL_NONE;
g_assert (i < MAX_EGL_ATTRS);
if (!eglChooseConfig (display_wayland->egl_display, attrs, NULL, 0, &count) || count < 1)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given pixel format"));
return FALSE;
}
configs = g_new (EGLConfig, count);
if (!eglChooseConfig (display_wayland->egl_display, attrs, configs, count, &count) || count < 1)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given pixel format"));
return FALSE;
}
/* Pick first valid configuration i guess? */
if (egl_config_out != NULL)
*egl_config_out = configs[0];
g_free (configs);
return TRUE;
}
GdkGLContext *
gdk_wayland_window_create_gl_context (GdkWindow *window,
gboolean attached,
GdkGLProfile profile,
GdkGLContext *share,
GError **error)
{
GdkDisplay *display = gdk_window_get_display (window);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandGLContext *context;
EGLContext ctx;
EGLConfig config;
int i;
EGLint context_attribs[3];
if (!gdk_wayland_display_init_gl (display))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GL implementation is available"));
return NULL;
}
if (profile == GDK_GL_PROFILE_3_2_CORE &&
!display_wayland->have_egl_khr_create_context)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_PROFILE,
_("3.2 core GL profile is not available on EGL implementation"));
return NULL;
}
if (!find_eglconfig_for_window (window, &config, error))
return NULL;
i = 0;
if (profile == GDK_GL_PROFILE_3_2_CORE)
{
context_attribs[i++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
context_attribs[i++] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
}
context_attribs[i++] = EGL_NONE;
ctx = eglCreateContext (display_wayland->egl_display,
config,
share ? GDK_WAYLAND_GL_CONTEXT (share)->egl_context : EGL_NO_CONTEXT,
context_attribs);
if (ctx == NULL)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
return NULL;
}
GDK_NOTE (OPENGL,
g_print ("Created EGL context[%p]\n", ctx));
context = g_object_new (GDK_WAYLAND_TYPE_GL_CONTEXT,
"window", window,
"visual", gdk_window_get_visual (window),
NULL);
context->egl_config = config;
context->egl_context = ctx;
context->is_attached = attached;
return GDK_GL_CONTEXT (context);
}
void
gdk_wayland_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandGLContext *context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
/* TODO: Unset as current if current? */
if (context_wayland->egl_context != NULL)
{
if (eglGetCurrentContext () == context_wayland->egl_context)
eglMakeCurrent(display_wayland->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
GDK_NOTE (OPENGL, g_print ("Destroying EGL context\n"));
eglDestroyContext (display_wayland->egl_display,
context_wayland->egl_context);
context_wayland->egl_context = NULL;
}
}
void
gdk_wayland_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkWaylandGLContext *context_wayland;
GdkWindow *window;
EGLSurface egl_surface;
if (context == NULL)
{
eglMakeCurrent(display_wayland->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
return;
}
context_wayland = GDK_WAYLAND_GL_CONTEXT (context);
window = gdk_gl_context_get_window (context);
if (context_wayland->is_attached)
egl_surface = gdk_wayland_window_get_egl_surface (window->impl_window, context_wayland->egl_config);
else
{
if (display_wayland->have_egl_surfaceless_context)
egl_surface = EGL_NO_SURFACE;
else
egl_surface = gdk_wayland_window_get_dummy_egl_surface (window->impl_window,
context_wayland->egl_config);
}
if (!eglMakeCurrent (display_wayland->egl_display, egl_surface,
egl_surface, context_wayland->egl_context))
g_critical ("eglMakeCurrent failed");
}

View File

@@ -0,0 +1,65 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-wayland.h: Private Wayland specific OpenGL wrappers
*
* Copyright © 2014 Emmanuele Bassi
* Copyright © 2014 Red Hat, Int
*
* 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/>.
*/
#ifndef __GDK_WAYLAND_GL_CONTEXT__
#define __GDK_WAYLAND_GL_CONTEXT__
#include "gdkglcontextprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkvisual.h"
#include "gdkwindow.h"
#include "gdkinternals.h"
#include "gdkmain.h"
#include <epoxy/egl.h>
G_BEGIN_DECLS
struct _GdkWaylandGLContext
{
GdkGLContext parent_instance;
EGLContext egl_context;
EGLConfig egl_config;
gboolean is_attached;
};
struct _GdkWaylandGLContextClass
{
GdkGLContextClass parent_class;
};
gboolean gdk_wayland_display_init_gl (GdkDisplay *display);
GdkGLContext * gdk_wayland_window_create_gl_context (GdkWindow *window,
gboolean attach,
GdkGLProfile profile,
GdkGLContext *share,
GError **error);
void gdk_wayland_window_invalidate_for_new_frame (GdkWindow *window,
cairo_region_t *update_area);
void gdk_wayland_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context);
void gdk_wayland_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context);
G_END_DECLS
#endif /* __GDK_WAYLAND_GL_CONTEXT__ */

View File

@@ -237,5 +237,9 @@ struct wl_data_source * gdk_wayland_selection_get_data_source (GdkWindow *owner,
GdkAtom selection);
void gdk_wayland_selection_unset_data_source (GdkAtom selection);
EGLSurface gdk_wayland_window_get_egl_surface (GdkWindow *window,
EGLConfig config);
EGLSurface gdk_wayland_window_get_dummy_egl_surface (GdkWindow *window,
EGLConfig config);
#endif /* __GDK_PRIVATE_WAYLAND_H__ */

View File

@@ -911,6 +911,7 @@ gdk_wayland_visual_new (GdkScreen *screen)
visual->screen = GDK_SCREEN (screen);
visual->type = GDK_VISUAL_TRUE_COLOR;
visual->depth = 32;
visual->bits_per_rgb = 8;
return visual;
}

View File

@@ -33,6 +33,7 @@
#include <gdk/wayland/gdkwaylanddisplay.h>
#include <gdk/wayland/gdkwaylandselection.h>
#include <gdk/wayland/gdkwaylandwindow.h>
#include <gdk/wayland/gdkwaylandglcontext.h>
#undef __GDKWAYLAND_H_INSIDE__

View File

@@ -0,0 +1,45 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-wayland.c: Wayland specific OpenGL wrappers
*
* Copyright © 2014 Emmanuele Bassi
* Copyright © 2014 Red Hat, Inc
*
* 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/>.
*/
#ifndef __GDK_WAYLAND_GL_CONTEXT_H__
#define __GDK_WAYLAND_GL_CONTEXT_H__
#if !defined (__GDKWAYLAND_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdkwayland.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GDK_WAYLAND_TYPE_GL_CONTEXT (gdk_wayland_gl_context_get_type ())
#define GDK_WAYLAND_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_WAYLAND_TYPE_GL_CONTEXT, GdkWaylandGLContext))
#define GDK_WAYLAND_IS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_WAYLAND_TYPE_GL_CONTEXT))
typedef struct _GdkWaylandGLContext GdkWaylandGLContext;
typedef struct _GdkWaylandGLContextClass GdkWaylandGLContextClass;
GDK_AVAILABLE_IN_3_16
GType gdk_wayland_gl_context_get_type (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GDK_WAYLAND_GL_CONTEXT_H__ */

View File

@@ -26,6 +26,7 @@
#include "gdkwindow.h"
#include "gdkwindowimpl.h"
#include "gdkdisplay-wayland.h"
#include "gdkglcontext-wayland.h"
#include "gdkframeclockprivate.h"
#include "gdkprivate-wayland.h"
#include "gdkinternals.h"
@@ -97,6 +98,12 @@ struct _GdkWindowImplWayland
struct wl_subsurface *subsurface;
struct wl_egl_window *egl_window;
EGLSurface egl_surface;
struct wl_egl_window *dummy_egl_window;
EGLSurface dummy_egl_surface;
unsigned int mapped : 1;
unsigned int use_custom_surface : 1;
unsigned int pending_commit : 1;
@@ -170,6 +177,9 @@ gdk_wayland_window_update_size (GdkWindow *window,
window->width = width;
window->height = height;
if (impl->egl_window)
wl_egl_window_resize (impl->egl_window, width, height, 0, 0);
area.x = 0;
area.y = 0;
area.width = window->width;
@@ -582,15 +592,18 @@ gdk_window_impl_wayland_end_paint (GdkWindow *window)
cairo_rectangle_int_t rect;
int i, n;
gdk_wayland_window_attach_image (window);
n = cairo_region_num_rectangles (window->current_paint.region);
for (i = 0; i < n; i++)
if (!window->current_paint.use_gl)
{
cairo_region_get_rectangle (window->current_paint.region, i, &rect);
wl_surface_damage (impl->surface,
rect.x, rect.y, rect.width, rect.height);
impl->pending_commit = TRUE;
gdk_wayland_window_attach_image (window);
n = cairo_region_num_rectangles (window->current_paint.region);
for (i = 0; i < n; i++)
{
cairo_region_get_rectangle (window->current_paint.region, i, &rect);
wl_surface_damage (impl->surface,
rect.x, rect.y, rect.width, rect.height);
impl->pending_commit = TRUE;
}
}
}
@@ -1156,10 +1169,35 @@ gdk_wayland_window_show (GdkWindow *window,
static void
gdk_wayland_window_hide_surface (GdkWindow *window)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (window));
GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
if (impl->surface)
{
if (impl->dummy_egl_surface)
{
eglDestroySurface(display_wayland->egl_display, impl->dummy_egl_surface);
impl->dummy_egl_surface = NULL;
}
if (impl->dummy_egl_window)
{
wl_egl_window_destroy (impl->dummy_egl_window);
impl->dummy_egl_window = NULL;
}
if (impl->egl_surface)
{
eglDestroySurface(display_wayland->egl_display, impl->egl_surface);
impl->egl_surface = NULL;
}
if (impl->egl_window)
{
wl_egl_window_destroy (impl->egl_window);
impl->egl_window = NULL;
}
if (impl->xdg_surface)
{
xdg_surface_destroy (impl->xdg_surface);
@@ -2128,6 +2166,8 @@ _gdk_window_impl_wayland_class_init (GdkWindowImplWaylandClass *klass)
impl_class->set_opaque_region = gdk_wayland_window_set_opaque_region;
impl_class->set_shadow_width = gdk_wayland_window_set_shadow_width;
impl_class->show_window_menu = gdk_wayland_window_show_window_menu;
impl_class->create_gl_context = gdk_wayland_window_create_gl_context;
impl_class->invalidate_for_new_frame = gdk_wayland_window_invalidate_for_new_frame;
}
void
@@ -2169,6 +2209,75 @@ gdk_wayland_window_get_wl_surface (GdkWindow *window)
return impl->surface;
}
static struct wl_egl_window *
gdk_wayland_window_get_wl_egl_window (GdkWindow *window)
{
GdkWindowImplWayland *impl;
g_return_val_if_fail (GDK_IS_WAYLAND_WINDOW (window), NULL);
impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
if (impl->egl_window == NULL)
{
impl->egl_window =
wl_egl_window_create(impl->surface,
impl->wrapper->width,
impl->wrapper->height);
}
return impl->egl_window;
}
EGLSurface
gdk_wayland_window_get_egl_surface (GdkWindow *window,
EGLConfig config)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (window));
GdkWindowImplWayland *impl;
struct wl_egl_window *egl_window;
g_return_val_if_fail (GDK_IS_WAYLAND_WINDOW (window), NULL);
impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
if (impl->egl_surface == NULL)
{
egl_window = gdk_wayland_window_get_wl_egl_window (window);
impl->egl_surface =
eglCreateWindowSurface (display_wayland->egl_display,
config, egl_window, NULL);
}
return impl->egl_surface;
}
EGLSurface
gdk_wayland_window_get_dummy_egl_surface (GdkWindow *window,
EGLConfig config)
{
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (window));
GdkWindowImplWayland *impl;
g_return_val_if_fail (GDK_IS_WAYLAND_WINDOW (window), NULL);
impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
if (impl->dummy_egl_surface == NULL)
{
impl->dummy_egl_window =
wl_egl_window_create (impl->surface, 1, 1);
impl->dummy_egl_surface =
eglCreateWindowSurface (display_wayland->egl_display,
config, impl->dummy_egl_window, NULL);
}
return impl->dummy_egl_surface;
}
/**
* gdk_wayland_window_set_use_custom_surface:
* @window: (type GdkWaylandWindow): a #GdkWindow

View File

@@ -39,6 +39,8 @@ libgdk_x11_la_SOURCES = \
gdkeventtranslator.c \
gdkeventtranslator.h \
gdkgeometry-x11.c \
gdkglcontext-x11.c \
gdkglcontext-x11.h \
gdkkeys-x11.c \
gdkmain-x11.c \
gdkproperty-x11.c \
@@ -71,6 +73,7 @@ libgdkx11include_HEADERS = \
gdkx11display.h \
gdkx11displaymanager.h \
gdkx11dnd.h \
gdkx11glcontext.h \
gdkx11keys.h \
gdkx11property.h \
gdkx11screen.h \

View File

@@ -37,6 +37,7 @@
#include "gdkdisplay-x11.h"
#include "gdkprivate-x11.h"
#include "gdkscreen-x11.h"
#include "gdkglcontext-x11.h"
#include <glib.h>
#include <glib/gprintf.h>
@@ -2903,5 +2904,8 @@ gdk_x11_display_class_init (GdkX11DisplayClass * class)
display_class->text_property_to_utf8_list = _gdk_x11_display_text_property_to_utf8_list;
display_class->utf8_to_string_target = _gdk_x11_display_utf8_to_string_target;
display_class->destroy_gl_context = gdk_x11_display_destroy_gl_context;
display_class->make_gl_context_current = gdk_x11_display_make_gl_context_current;
_gdk_x11_windowing_init ();
}

View File

@@ -124,6 +124,21 @@ struct _GdkX11Display
GSList *error_traps;
gint wm_moveresize_button;
/* GLX information */
gint glx_version;
gint glx_error_base;
gint glx_event_base;
guint have_glx : 1;
/* GLX extensions we check */
guint has_glx_swap_interval : 1;
guint has_glx_create_context : 1;
guint has_glx_texture_from_pixmap : 1;
guint has_glx_video_sync : 1;
guint has_glx_buffer_age : 1;
guint has_glx_sync_control : 1;
};
struct _GdkX11DisplayClass

906
gdk/x11/gdkglcontext-x11.c Normal file
View File

@@ -0,0 +1,906 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-x11.c: X11 specific OpenGL wrappers
*
* 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/>.
*/
#include "config.h"
#include "gdkglcontext-x11.h"
#include "gdkdisplay-x11.h"
#include "gdkscreen-x11.h"
#include "gdkx11display.h"
#include "gdkx11glcontext.h"
#include "gdkx11screen.h"
#include "gdkx11window.h"
#include "gdkx11visual.h"
#include "gdkinternals.h"
#include "gdkintl.h"
#include <cairo/cairo-xlib.h>
#include <GL/glx.h>
G_DEFINE_TYPE (GdkX11GLContext, gdk_x11_gl_context, GDK_TYPE_GL_CONTEXT)
typedef struct {
GdkDisplay *display;
GLXDrawable glx_drawable;
Window dummy_xwin;
GLXWindow dummy_glx;
guint32 last_frame_counter;
} DrawableInfo;
static void
drawable_info_free (gpointer data_)
{
DrawableInfo *data = data_;
Display *dpy;
gdk_x11_display_error_trap_push (data->display);
dpy = gdk_x11_display_get_xdisplay (data->display);
if (data->glx_drawable)
glXDestroyWindow (dpy, data->glx_drawable);
if (data->dummy_glx)
glXDestroyWindow (dpy, data->dummy_glx);
if (data->dummy_xwin)
XDestroyWindow (dpy, data->dummy_xwin);
gdk_x11_display_error_trap_pop_ignored (data->display);
g_slice_free (DrawableInfo, data);
}
static DrawableInfo *
get_glx_drawable_info (GdkWindow *window)
{
return g_object_get_data (G_OBJECT (window), "-gdk-x11-window-glx-info");
}
static void
set_glx_drawable_info (GdkWindow *window,
DrawableInfo *info)
{
g_object_set_data_full (G_OBJECT (window), "-gdk-x11-window-glx-info",
info,
drawable_info_free);
}
static void
gdk_x11_gl_context_update (GdkGLContext *context)
{
GdkWindow *window = gdk_gl_context_get_window (context);
int width, height;
gdk_gl_context_make_current (context);
width = gdk_window_get_width (window);
height = gdk_window_get_height (window);
GDK_NOTE (OPENGL, g_print ("Updating GL viewport size to { %d, %d } for window %lu (context: %p)\n",
width, height,
(unsigned long) gdk_x11_window_get_xid (window),
context));
glViewport (0, 0, width, height);
}
static void
maybe_wait_for_vblank (GdkDisplay *display,
GLXDrawable drawable)
{
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy = gdk_x11_display_get_xdisplay (display);
if (display_x11->has_glx_sync_control)
{
gint64 ust, msc, sbc;
glXGetSyncValuesOML (dpy, drawable, &ust, &msc, &sbc);
glXWaitForMscOML (dpy, drawable,
0, 2, (msc + 1) % 2,
&ust, &msc, &sbc);
}
else if (display_x11->has_glx_video_sync)
{
guint32 current_count;
glXGetVideoSyncSGI (&current_count);
glXWaitVideoSyncSGI (2, (current_count + 1) % 2, &current_count);
}
}
void
gdk_x11_window_invalidate_for_new_frame (GdkWindow *window,
cairo_region_t *update_area)
{
cairo_rectangle_int_t window_rect;
GdkDisplay *display = gdk_window_get_display (window);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkX11GLContext *context_x11;
unsigned int buffer_age;
gboolean invalidate_all;
/* Minimal update is ok if we're not drawing with gl */
if (window->gl_paint_context == NULL)
return;
context_x11 = GDK_X11_GL_CONTEXT (window->gl_paint_context);
buffer_age = 0;
if (display_x11->has_glx_buffer_age)
{
gdk_gl_context_make_current (window->gl_paint_context);
glXQueryDrawable(dpy, context_x11->drawable,
GLX_BACK_BUFFER_AGE_EXT, &buffer_age);
}
invalidate_all = FALSE;
if (buffer_age == 0 || buffer_age >= 4)
invalidate_all = TRUE;
else
{
if (buffer_age >= 2)
{
if (window->old_updated_area[0])
cairo_region_union (update_area, window->old_updated_area[0]);
else
invalidate_all = TRUE;
}
if (buffer_age >= 3)
{
if (window->old_updated_area[1])
cairo_region_union (update_area, window->old_updated_area[1]);
else
invalidate_all = TRUE;
}
}
if (invalidate_all)
{
window_rect.x = 0;
window_rect.y = 0;
window_rect.width = gdk_window_get_width (window);
window_rect.height = gdk_window_get_height (window);
/* If nothing else is known, repaint everything so that the back
buffer is fully up-to-date for the swapbuffer */
cairo_region_union_rectangle (update_area, &window_rect);
}
}
static void
gdk_x11_gl_context_flush_buffer (GdkGLContext *context,
cairo_region_t *painted,
cairo_region_t *damage)
{
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (context);
GdkWindow *window = gdk_gl_context_get_window (context);
GdkDisplay *display = gdk_window_get_display (window);
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
DrawableInfo *info;
GLXDrawable drawable;
gdk_gl_context_make_current (context);
info = get_glx_drawable_info (window);
drawable = context_x11->drawable;
GDK_NOTE (OPENGL,
g_print ("Flushing GLX buffers for drawable %lu (window: %lu), frame sync: %s\n",
(unsigned long) drawable,
(unsigned long) gdk_x11_window_get_xid (window),
context_x11->do_frame_sync ? "yes" : "no"));
/* if we are going to wait for the vertical refresh manually
* we need to flush pending redraws, and we also need to wait
* for that to finish, otherwise we are going to tear.
*
* obviously, this condition should not be hit if we have
* GLX_SGI_swap_control, and we ask the driver to do the right
* thing.
*/
if (context_x11->do_frame_sync)
{
guint32 end_frame_counter = 0;
gboolean has_counter = display_x11->has_glx_video_sync;
gboolean can_wait = display_x11->has_glx_video_sync || display_x11->has_glx_sync_control;
if (display_x11->has_glx_video_sync)
glXGetVideoSyncSGI (&end_frame_counter);
if (context_x11->do_frame_sync && !display_x11->has_glx_swap_interval)
{
glFinish ();
if (has_counter && can_wait)
{
guint32 last_counter = info != NULL ? info->last_frame_counter : 0;
if (last_counter == end_frame_counter)
maybe_wait_for_vblank (display, drawable);
}
else if (can_wait)
maybe_wait_for_vblank (display, drawable);
}
}
glXSwapBuffers (dpy, drawable);
if (context_x11->do_frame_sync && info != NULL && display_x11->has_glx_video_sync)
glXGetVideoSyncSGI (&info->last_frame_counter);
}
typedef struct {
Display *display;
GLXDrawable drawable;
gboolean y_inverted;
} GdkGLXPixmap;
static void
glx_pixmap_destroy (void *data)
{
GdkGLXPixmap *glx_pixmap = data;
glXDestroyPixmap (glx_pixmap->display, glx_pixmap->drawable);
g_slice_free (GdkGLXPixmap, glx_pixmap);
}
static GdkGLXPixmap *
glx_pixmap_get (cairo_surface_t *surface)
{
Display *display = cairo_xlib_surface_get_display (surface);
Screen *screen = cairo_xlib_surface_get_screen (surface);
Visual *visual = cairo_xlib_surface_get_visual (surface);;
GdkGLXPixmap *glx_pixmap;
GLXFBConfig *fbconfigs;
int nfbconfigs;
XVisualInfo *visinfo;
int i, value;
gboolean y_inverted;
const int pixmap_attributes[] = {
GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_RECTANGLE_EXT,
GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGBA_EXT,
None
};
y_inverted = FALSE;
fbconfigs = glXGetFBConfigs (display, XScreenNumberOfScreen (screen), &nfbconfigs);
for (i = 0; i < nfbconfigs; i++)
{
visinfo = glXGetVisualFromFBConfig (display, fbconfigs[i]);
if (!visinfo || visinfo->visualid != XVisualIDFromVisual (visual))
continue;
glXGetFBConfigAttrib (display, fbconfigs[i], GLX_DRAWABLE_TYPE, &value);
if (!(value & GLX_PIXMAP_BIT))
continue;
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_TARGETS_EXT,
&value);
if (!(value & GLX_TEXTURE_RECTANGLE_BIT_EXT))
continue;
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_RGBA_EXT,
&value);
if (value == FALSE)
{
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_RGB_EXT,
&value);
if (value == FALSE)
continue;
}
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_Y_INVERTED_EXT,
&value);
if (value == TRUE)
y_inverted = TRUE;
break;
}
if (i == nfbconfigs)
return NULL;
glx_pixmap = g_slice_new0 (GdkGLXPixmap);
glx_pixmap->y_inverted = y_inverted;
glx_pixmap->display = display;
glx_pixmap->drawable = glXCreatePixmap (display, fbconfigs[i],
cairo_xlib_surface_get_drawable (surface),
pixmap_attributes);
return glx_pixmap;
}
static gboolean
gdk_x11_gl_context_texture_from_surface (GdkGLContext *context,
cairo_surface_t *surface,
cairo_region_t *region)
{
GdkGLXPixmap *glx_pixmap;
double device_x_offset, device_y_offset;
cairo_rectangle_int_t rect;
int n_rects, i;
GdkWindow *window;
int window_height;
int window_scale;
unsigned int texture_id;
double sx, sy;
if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_XLIB)
return FALSE;
glx_pixmap = glx_pixmap_get (surface);
if (glx_pixmap == NULL)
return FALSE;
window = gdk_gl_context_get_window (gdk_gl_context_get_current ());
window_scale = gdk_window_get_scale_factor (window);
window_height = gdk_window_get_height (window);
sx = sy = 1;
#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
cairo_surface_get_device_scale (window->current_paint.surface, &sx, &sy);
#endif
cairo_surface_get_device_offset (surface,
&device_x_offset, &device_y_offset);
/* Ensure all the X stuff are synced before we read it back via texture-from-pixmap */
glXWaitX();
glGenTextures (1, &texture_id);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, texture_id);
glEnable (GL_TEXTURE_RECTANGLE_ARB);
glXBindTexImageEXT (glx_pixmap->display, glx_pixmap->drawable,
GLX_FRONT_LEFT_EXT, NULL);
n_rects = cairo_region_num_rectangles (region);
for (i = 0; i < n_rects; i++)
{
int src_x, src_y, src_height, src_width;
cairo_region_get_rectangle (region, i, &rect);
glScissor (rect.x * window_scale, (window_height - rect.y - rect.height) * window_scale,
rect.width * window_scale, rect.height * window_scale);
src_x = rect.x * sx + device_x_offset;
src_y = rect.y * sy + device_y_offset;
src_width = rect.width * sx;
src_height = rect.height * sy;
#define FLIP_Y(_y) (window_height - (_y))
glBegin (GL_QUADS);
glTexCoord2f (src_x, src_y + src_height);
glVertex2f (rect.x * window_scale, FLIP_Y(rect.y + rect.height) * window_scale);
glTexCoord2f (src_x + src_width, src_y + src_height);
glVertex2f ((rect.x + rect.width) * window_scale, FLIP_Y(rect.y + rect.height) * window_scale);
glTexCoord2f (src_x + src_width, src_y);
glVertex2f ((rect.x + rect.width) * window_scale, FLIP_Y(rect.y) * window_scale);
glTexCoord2f (src_x, src_y);
glVertex2f (rect.x * window_scale, FLIP_Y(rect.y) * window_scale);
glEnd();
}
glXReleaseTexImageEXT (glx_pixmap->display, glx_pixmap->drawable,
GLX_FRONT_LEFT_EXT);
glDisable (GL_TEXTURE_RECTANGLE_ARB);
glDeleteTextures (1, &texture_id);
glx_pixmap_destroy(glx_pixmap);
return TRUE;
}
static void
gdk_x11_gl_context_class_init (GdkX11GLContextClass *klass)
{
GdkGLContextClass *context_class = GDK_GL_CONTEXT_CLASS (klass);
context_class->update = gdk_x11_gl_context_update;
context_class->flush_buffer = gdk_x11_gl_context_flush_buffer;
context_class->texture_from_surface = gdk_x11_gl_context_texture_from_surface;
}
static void
gdk_x11_gl_context_init (GdkX11GLContext *self)
{
}
gboolean
gdk_x11_display_init_gl (GdkDisplay *display)
{
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
GdkScreen *screen;
Display *dpy;
int error_base, event_base;
int screen_num;
if (display_x11->have_glx)
return TRUE;
dpy = gdk_x11_display_get_xdisplay (display);
if (!glXQueryExtension (dpy, &error_base, &event_base))
return FALSE;
screen = gdk_display_get_default_screen (display);
screen_num = GDK_X11_SCREEN (screen)->screen_num;
display_x11->have_glx = TRUE;
display_x11->glx_version = epoxy_glx_version (dpy, screen_num);
display_x11->glx_error_base = error_base;
display_x11->glx_event_base = event_base;
display_x11->has_glx_create_context =
epoxy_has_glx_extension (dpy, screen_num, "GLX_ARB_create_context_profile");
display_x11->has_glx_swap_interval =
epoxy_has_glx_extension (dpy, screen_num, "GLX_SGI_swap_control");
display_x11->has_glx_texture_from_pixmap =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_texture_from_pixmap");
display_x11->has_glx_video_sync =
epoxy_has_glx_extension (dpy, screen_num, "GLX_SGI_video_sync");
display_x11->has_glx_buffer_age =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_buffer_age");
display_x11->has_glx_sync_control =
epoxy_has_glx_extension (dpy, screen_num, "GLX_OML_sync_control");
GDK_NOTE (OPENGL,
g_print ("GLX version %d.%d found\n"
" - Vendor: %s\n"
" - Checked extensions:\n"
"\t* GLX_ARB_create_context_profile: %s\n"
"\t* GLX_SGI_swap_control: %s\n"
"\t* GLX_EXT_texture_from_pixmap: %s\n"
"\t* GLX_SGI_video_sync: %s\n"
"\t* GLX_EXT_buffer_age: %s\n"
"\t* GLX_OML_sync_control: %s\n",
display_x11->glx_version / 10,
display_x11->glx_version % 10,
glXGetClientString (dpy, GLX_VENDOR),
display_x11->has_glx_create_context ? "yes" : "no",
display_x11->has_glx_swap_interval ? "yes" : "no",
display_x11->has_glx_texture_from_pixmap ? "yes" : "no",
display_x11->has_glx_video_sync ? "yes" : "no",
display_x11->has_glx_buffer_age ? "yes" : "no",
display_x11->has_glx_sync_control ? "yes" : "no"));
return TRUE;
}
#define MAX_GLX_ATTRS 30
static gboolean
find_fbconfig_for_visual (GdkDisplay *display,
GdkVisual *visual,
GLXFBConfig *fb_config_out,
XVisualInfo **visinfo_out,
GError **error)
{
static int attrs[MAX_GLX_ATTRS];
Display *dpy = gdk_x11_display_get_xdisplay (display);
GLXFBConfig *configs;
int n_configs, i;
gboolean use_rgba;
gboolean retval = FALSE;
i = 0;
attrs[i++] = GLX_DRAWABLE_TYPE;
attrs[i++] = GLX_WINDOW_BIT;
attrs[i++] = GLX_RENDER_TYPE;
attrs[i++] = GLX_RGBA_BIT;
attrs[i++] = GLX_DOUBLEBUFFER;
attrs[i++] = GL_TRUE;
attrs[i++] = GLX_RED_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);
attrs[i++] = GLX_GREEN_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);;
attrs[i++] = GLX_BLUE_SIZE;
attrs[i++] = gdk_visual_get_bits_per_rgb (visual);;
use_rgba = (visual == gdk_screen_get_rgba_visual (gdk_display_get_default_screen (display)));
if (use_rgba)
{
attrs[i++] = GLX_ALPHA_SIZE;
attrs[i++] = 1;
}
else
{
attrs[i++] = GLX_ALPHA_SIZE;
attrs[i++] = GLX_DONT_CARE;
}
attrs[i++] = None;
g_assert (i < MAX_GLX_ATTRS);
configs = glXChooseFBConfig (dpy, DefaultScreen (dpy), attrs, &n_configs);
if (configs == NULL || n_configs == 0)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given pixel format"));
return FALSE;
}
/* if we don't care about an alpha channel, then the first
* valid configuration is the one we give back
*/
if (!use_rgba)
{
if (fb_config_out != NULL)
*fb_config_out = configs[0];
if (visinfo_out != NULL)
*visinfo_out = glXGetVisualFromFBConfig (dpy, configs[0]);
retval = TRUE;
goto out;
}
for (i = 0; i < n_configs; i++)
{
XVisualInfo *visinfo;
unsigned long mask;
visinfo = glXGetVisualFromFBConfig (dpy, configs[i]);
if (visinfo == NULL)
continue;
mask = visinfo->red_mask | visinfo->green_mask | visinfo->blue_mask;
if (visinfo->depth == 32 && mask != 0xffffffff)
{
if (fb_config_out != NULL)
*fb_config_out = configs[i];
if (visinfo_out != NULL)
*visinfo_out = visinfo;
retval = TRUE;
goto out;
}
XFree (visinfo);
}
g_set_error (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given RGBA pixel format"));
out:
XFree (configs);
return retval;
}
static GLXContext
create_gl3_context (GdkDisplay *display,
GLXFBConfig config,
GdkGLContext *share)
{
static const int attrib_list[] = {
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MAJOR_VERSION_ARB, 2,
None,
};
GdkX11GLContext *context_x11 = NULL;
if (share != NULL)
context_x11 = GDK_X11_GL_CONTEXT (share);
return glXCreateContextAttribsARB (gdk_x11_display_get_xdisplay (display),
config,
context_x11 != NULL ? context_x11->glx_context : NULL,
True,
attrib_list);
}
static GLXContext
create_gl_context (GdkDisplay *display,
GLXFBConfig config,
GdkGLContext *share)
{
GdkX11GLContext *context_x11 = NULL;
if (share != NULL)
context_x11 = GDK_X11_GL_CONTEXT (share);
return glXCreateNewContext (gdk_x11_display_get_xdisplay (display),
config,
GLX_RGBA_TYPE,
context_x11 != NULL ? context_x11->glx_context : NULL,
True);
}
GdkGLContext *
gdk_x11_window_create_gl_context (GdkWindow *window,
gboolean attached,
GdkGLProfile profile,
GdkGLContext *share,
GError **error)
{
GdkDisplay *display;
GdkX11GLContext *context;
GdkVisual *visual;
GdkVisual *gdk_visual;
GLXFBConfig config;
GLXContext glx_context;
GLXWindow drawable;
gboolean is_direct;
XVisualInfo *xvisinfo;
Display *dpy;
DrawableInfo *info;
display = gdk_window_get_display (window);
if (!gdk_x11_display_init_gl (display))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GL implementation is available"));
return NULL;
}
if (profile == GDK_GL_PROFILE_3_2_CORE &&
!GDK_X11_DISPLAY (display)->has_glx_create_context)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_PROFILE,
_("The GLX_ARB_create_context_profile extension "
"needed to create 3.2 core profiles is not "
"available"));
return NULL;
}
visual = gdk_window_get_visual (window);
if (!find_fbconfig_for_visual (display, visual, &config, &xvisinfo, error))
return NULL;
dpy = gdk_x11_display_get_xdisplay (display);
/* we check for the GLX_ARB_create_context_profile extension
* while validating the PixelFormat.
*/
if (profile == GDK_GL_PROFILE_3_2_CORE)
glx_context = create_gl3_context (display, config, share);
else
{
/* GDK_GL_PROFILE_DEFAULT is currently
* equivalent to the LEGACY profile
*/
glx_context = create_gl_context (display, config, share);
}
if (glx_context == NULL)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
return NULL;
}
is_direct = glXIsDirect (dpy, glx_context);
info = get_glx_drawable_info (window->impl_window);
if (info == NULL)
{
XSetWindowAttributes attrs;
unsigned long mask;
gdk_x11_display_error_trap_push (display);
info = g_slice_new0 (DrawableInfo);
info->display = display;
info->last_frame_counter = 0;
attrs.override_redirect = True;
attrs.colormap = XCreateColormap (dpy, DefaultRootWindow (dpy), xvisinfo->visual, AllocNone);
attrs.border_pixel = 0;
mask = CWOverrideRedirect | CWColormap | CWBorderPixel;
info->dummy_xwin = XCreateWindow (dpy, DefaultRootWindow (dpy),
-100, -100, 1, 1,
0,
xvisinfo->depth,
CopyFromParent,
xvisinfo->visual,
mask,
&attrs);
XMapWindow(dpy, info->dummy_xwin);
if (GDK_X11_DISPLAY (display)->glx_version >= 13)
{
info->glx_drawable = glXCreateWindow (dpy, config,
gdk_x11_window_get_xid (window->impl_window),
NULL);
info->dummy_glx = glXCreateWindow (dpy, config, info->dummy_xwin, NULL);
}
if (gdk_x11_display_error_trap_pop (display))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
drawable_info_free (info);
glXDestroyContext (dpy, glx_context);
return NULL;
}
set_glx_drawable_info (window->impl_window, info);
}
gdk_visual = gdk_x11_screen_lookup_visual (gdk_display_get_default_screen (display),
xvisinfo->visualid);
XFree (xvisinfo);
if (attached)
drawable = info->glx_drawable ? info->glx_drawable : gdk_x11_window_get_xid (window->impl_window);
else
drawable = info->dummy_glx ? info->dummy_glx : info->dummy_xwin;
GDK_NOTE (OPENGL,
g_print ("Created GLX context[%p], %s\n",
glx_context,
is_direct ? "direct" : "indirect"));
context = g_object_new (GDK_X11_TYPE_GL_CONTEXT,
"window", window,
"visual", gdk_visual,
NULL);
context->profile = profile;
context->glx_config = config;
context->glx_context = glx_context;
context->drawable = drawable;
context->is_attached = attached;
context->is_direct = is_direct;
return GDK_GL_CONTEXT (context);
}
void
gdk_x11_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context)
{
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
if (context_x11->glx_context != NULL)
{
if (glXGetCurrentContext () == context_x11->glx_context)
glXMakeContextCurrent (dpy, None, None, NULL);
GDK_NOTE (OPENGL, g_print ("Destroying GLX context\n"));
glXDestroyContext (dpy, context_x11->glx_context);
context_x11->glx_context = NULL;
}
}
void
gdk_x11_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context)
{
GdkX11GLContext *context_x11;
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkWindow *window;
GdkScreen *screen;
gboolean do_frame_sync = FALSE;
if (context == NULL)
{
glXMakeContextCurrent (dpy, None, None, NULL);
return;
}
context_x11 = GDK_X11_GL_CONTEXT (context);
window = gdk_gl_context_get_window (context);
// If the WM is compositing there is no particular need to delay
// the swap when drawing on the offscreen, rendering to the screen
// happens later anyway, and its up to the compositor to sync that
// to the vblank.
screen = gdk_window_get_screen (window);
do_frame_sync = ! gdk_screen_is_composited (screen);
context_x11->do_frame_sync = do_frame_sync;
GDK_NOTE (OPENGL,
g_print ("Making GLX context current to drawable %lu\n",
(unsigned long) context_x11->drawable));
glXMakeContextCurrent (dpy, context_x11->drawable, context_x11->drawable,
context_x11->glx_context);
if (context_x11->is_attached && GDK_X11_DISPLAY (display)->has_glx_swap_interval)
{
if (context_x11->do_frame_sync)
glXSwapIntervalSGI (1);
else
glXSwapIntervalSGI (0);
}
}
/**
* gdk_x11_display_get_glx_version:
* @display: a #GdkDisplay
* @major: (out): return location for the GLX major version
* @minor: (out): return location for the GLX minor version
*
* Retrieves the version of the GLX implementation.
*
* Returns: %TRUE if GLX is available
*
* Since: 3.16
*/
gboolean
gdk_x11_display_get_glx_version (GdkDisplay *display,
gint *major,
gint *minor)
{
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
if (!GDK_IS_X11_DISPLAY (display))
return FALSE;
if (!gdk_x11_display_init_gl (display))
return FALSE;
if (major != NULL)
*major = GDK_X11_DISPLAY (display)->glx_version / 10;
if (minor != NULL)
*minor = GDK_X11_DISPLAY (display)->glx_version % 10;
return TRUE;
}

View File

@@ -0,0 +1,74 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-x11.h: Private X11 specific OpenGL wrappers
*
* 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/>.
*/
#ifndef __GDK_X11_GL_CONTEXT__
#define __GDK_X11_GL_CONTEXT__
#include <X11/X.h>
#include <X11/Xlib.h>
#include <epoxy/gl.h>
#include <epoxy/glx.h>
#include "gdkglcontextprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkvisual.h"
#include "gdkwindow.h"
#include "gdkinternals.h"
#include "gdkmain.h"
G_BEGIN_DECLS
struct _GdkX11GLContext
{
GdkGLContext parent_instance;
GdkGLProfile profile;
GLXContext glx_context;
GLXFBConfig glx_config;
GLXDrawable drawable;
guint is_attached : 1;
guint is_direct : 1;
guint do_frame_sync : 1;
};
struct _GdkX11GLContextClass
{
GdkGLContextClass parent_class;
};
gboolean gdk_x11_display_init_gl (GdkDisplay *display);
GdkGLContext * gdk_x11_window_create_gl_context (GdkWindow *window,
gboolean attached,
GdkGLProfile profile,
GdkGLContext *share,
GError **error);
void gdk_x11_window_invalidate_for_new_frame (GdkWindow *window,
cairo_region_t *update_area);
void gdk_x11_display_destroy_gl_context (GdkDisplay *display,
GdkGLContext *context);
void gdk_x11_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context);
G_END_DECLS
#endif /* __GDK_X11_GL_CONTEXT__ */

View File

@@ -36,6 +36,7 @@
#include "gdkasync.h"
#include "gdkeventsource.h"
#include "gdkdisplay-x11.h"
#include "gdkglcontext-x11.h"
#include "gdkprivate-x11.h"
#include <stdlib.h>
@@ -5730,4 +5731,6 @@ gdk_window_impl_x11_class_init (GdkWindowImplX11Class *klass)
impl_class->set_opaque_region = gdk_x11_window_set_opaque_region;
impl_class->set_shadow_width = gdk_x11_window_set_shadow_width;
impl_class->show_window_menu = gdk_x11_window_show_window_menu;
impl_class->create_gl_context = gdk_x11_window_create_gl_context;
impl_class->invalidate_for_new_frame = gdk_x11_window_invalidate_for_new_frame;
}

View File

@@ -43,6 +43,7 @@
#include <gdk/x11/gdkx11display.h>
#include <gdk/x11/gdkx11displaymanager.h>
#include <gdk/x11/gdkx11dnd.h>
#include <gdk/x11/gdkx11glcontext.h>
#include <gdk/x11/gdkx11keys.h>
#include <gdk/x11/gdkx11property.h>
#include <gdk/x11/gdkx11screen.h>

49
gdk/x11/gdkx11glcontext.h Normal file
View File

@@ -0,0 +1,49 @@
/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-x11.c: X11 specific OpenGL wrappers
*
* 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/>.
*/
#ifndef __GDK_X11_GL_CONTEXT_H__
#define __GDK_X11_GL_CONTEXT_H__
#if !defined (__GDKX_H_INSIDE__) && !defined (GDK_COMPILATION)
#error "Only <gdk/gdkx.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GDK_X11_TYPE_GL_CONTEXT (gdk_x11_gl_context_get_type ())
#define GDK_X11_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_X11_TYPE_GL_CONTEXT, GdkX11GLContext))
#define GDK_X11_IS_GL_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_X11_TYPE_GL_CONTEXT))
typedef struct _GdkX11GLContext GdkX11GLContext;
typedef struct _GdkX11GLContextClass GdkX11GLContextClass;
GDK_AVAILABLE_IN_3_16
GType gdk_x11_gl_context_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_3_16
gboolean gdk_x11_display_get_glx_version (GdkDisplay *display,
gint *major,
gint *minor);
G_END_DECLS
#endif /* __GDK_X11_GL_CONTEXT_H__ */

View File

@@ -363,6 +363,7 @@ gtk_public_h_sources = \
gtkgesturesingle.h \
gtkgestureswipe.h \
gtkgesturezoom.h \
gtkglarea.h \
gtkgrid.h \
gtkheaderbar.h \
gtkicontheme.h \
@@ -937,6 +938,7 @@ gtk_base_c_sources = \
gtkgesturesingle.c \
gtkgestureswipe.c \
gtkgesturezoom.c \
gtkglarea.c \
gtkgrid.c \
gtkheaderbar.c \
gtkhsla.c \

View File

@@ -114,6 +114,7 @@
#include <gtk/gtkgesturesingle.h>
#include <gtk/gtkgestureswipe.h>
#include <gtk/gtkgesturezoom.h>
#include <gtk/gtkglarea.h>
#include <gtk/gtkgrid.h>
#include <gtk/gtkheaderbar.h>
#include <gtk/gtkicontheme.h>

629
gtk/gtkglarea.c Normal file
View File

@@ -0,0 +1,629 @@
/* 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"
#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;
* }
* ]|
*
* The `draw_an_object()` function draws a 2D, gold-colored
* triangle:
*
* |[<!-- language="C" -->
* static void
* draw_an_object (void)
* {
* // set the color
* glColor3f (1.0f, 0.85f, 0.35f);
*
* // draw our triangle
* glBegin (GL_TRIANGLES);
* {
* glVertex3f ( 0.0f, 0.6f, 0.0f);
* glVertex3f (-0.2f, -0.3f, 0.0f);
* glVertex3f ( 0.2f, -0.3f, 0.0f);
* }
* glEnd ();
* }
* ]|
*
* This is an extremely simple example; in a real-world application you
* would probably replace the immediate mode drawing with persistent
* geometry primitives, like a Vertex Buffer Object, and only redraw what
* changed in your scene.
*
*/
typedef struct {
GdkGLContext *context;
GLuint framebuffer;
gboolean has_alpha;
gboolean has_depth_buffer;
} GtkGLAreaPrivate;
enum {
PROP_0,
PROP_CONTEXT,
PROP_HAS_ALPHA,
PROP_HAS_DEPTH_BUFFER,
LAST_PROP
};
static GParamSpec *obj_props[LAST_PROP] = { NULL, };
enum {
RENDER,
LAST_SIGNAL
};
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)
{
GtkGLArea *self = GTK_GL_AREA (gobject);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
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)
{
switch (prop_id)
{
case PROP_HAS_ALPHA:
gtk_gl_area_set_has_alpha (GTK_GL_AREA(gobject),
g_value_get_boolean (value));
break;
case PROP_HAS_DEPTH_BUFFER:
gtk_gl_area_set_has_depth_buffer (GTK_GL_AREA(gobject),
g_value_get_boolean (value));
break;
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)
{
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;
case PROP_CONTEXT:
g_value_set_object (value, priv->context);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_gl_area_realize (GtkWidget *widget)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private ((GtkGLArea *) widget);
GdkWindow *window;
GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->realize (widget);
window = gtk_widget_get_window (widget);
priv->context = gdk_window_create_gl_context (window,
GDK_GL_PROFILE_DEFAULT,
NULL);
if (priv->context != NULL)
{
gdk_gl_context_make_current (priv->context);
glGenFramebuffersEXT (1, &priv->framebuffer);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, priv->framebuffer);
}
}
static void
gtk_gl_area_unrealize (GtkWidget *widget)
{
GtkGLArea *self = GTK_GL_AREA (widget);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
if (priv->context != NULL)
{
if (priv->framebuffer != 0)
{
gtk_gl_area_make_current (self);
/* Bind 0, which means render to back buffer, as a result, fb is unbound */
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
glDeleteFramebuffersEXT (1, &priv->framebuffer);
priv->framebuffer = 0;
}
else
g_warning ("can't free framebuffer");
gdk_gl_context_clear_current ();
}
GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unrealize (widget);
}
static void
gtk_gl_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->size_allocate (widget, allocation);
}
static gboolean
gtk_gl_area_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkGLArea *self = GTK_GL_AREA (widget);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
gboolean unused;
int w, h, scale;
GLuint color_rb = 0, depth_rb = 0, color_tex = 0;
GLenum status;
if (priv->context == NULL)
return FALSE;
gtk_gl_area_make_current (self);
scale = gtk_widget_get_scale_factor (widget);
w = gtk_widget_get_allocated_width (widget) * scale;
h = gtk_widget_get_allocated_height (widget) * scale;
if (priv->has_alpha)
{
/* For alpha we use textures as that is required for blending to work */
glGenTextures (1, &color_tex);
glBindTexture (GL_TEXTURE_2D, color_tex);
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);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, color_tex, 0);
}
else
{
/* For non-alpha we use render buffers so we can blit instead of texture the result */
glGenRenderbuffersEXT (1, &color_rb);
glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, color_rb);
glRenderbufferStorageEXT (GL_RENDERBUFFER_EXT, GL_RGB8, w, h);
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, color_rb);
}
if (priv->has_depth_buffer)
{
glGenRenderbuffersEXT (1, &depth_rb);
glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, depth_rb);
/* TODO: Pick actual requested depth */
glRenderbufferStorageEXT (GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, w, h);
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, depth_rb);
glEnable (GL_DEPTH_TEST);
}
else
glDisable (GL_DEPTH_TEST);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
{
glViewport(0, 0, w, h);
g_signal_emit (self, area_signals[RENDER], 0, priv->context, &unused);
gdk_cairo_draw_from_gl (cr,
gtk_widget_get_window (widget),
color_tex ? color_tex : color_rb,
color_tex ? GL_TEXTURE : GL_RENDERBUFFER,
scale, 0, 0, w, h);
gtk_gl_area_make_current (self);
}
else
{
g_print ("fb setup not supported\n");
}
if (color_tex != 0)
glDeleteTextures(1, &color_tex);
if (color_rb != 0)
glDeleteRenderbuffersEXT(1, &color_rb);
if (depth_rb != 0)
glDeleteRenderbuffersEXT(1, &depth_rb);
return TRUE;
}
static void
gtk_gl_area_screen_changed (GtkWidget *widget,
GdkScreen *old_screen)
{
GtkGLArea *self = GTK_GL_AREA (widget);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
/* this will cause the context to be recreated on realize */
g_clear_object (&priv->context);
}
static void
gtk_gl_area_class_init (GtkGLAreaClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
widget_class->screen_changed = gtk_gl_area_screen_changed;
widget_class->realize = gtk_gl_area_realize;
widget_class->unrealize = gtk_gl_area_unrealize;
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
* instance. If you need to render with other kinds of buffers (stencil,
* depth, etc), use render buffers.
*
* 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);
/**
* GtkGLArea:has-alpha:
*
* 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.
*
* If set to %FALSE there will be no alpha channel, and the buffer will fully replace anything
* below the widget.
*
* Since: 3.16
*/
obj_props[PROP_HAS_ALPHA] =
g_param_spec_boolean ("has-alpha",
P_("Has alpha"),
P_("Whether the gl area color buffer has an alpha component"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGLArea:has-depth-buffer:
*
* If set to %TRUE the widget will allocate and enable a depth buffer for the target
* framebuffer.
*
* 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,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
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;
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"),
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);
}
static void
gtk_gl_area_init (GtkGLArea *self)
{
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE);
}
/**
* gtk_gl_area_new:
*
* Creates a new #GtkGLArea widget.
*
* Returns: (transfer full): the newly created #GtkGLArea
*
* Since: 3.16
*/
GtkWidget *
gtk_gl_area_new (void)
{
return g_object_new (GTK_TYPE_GL_AREA, NULL);
}
/**
* gtk_gl_area_get_has_alpha:
* @area: a #GtkGLArea
*
* Returns whether the area has an alpha component.
*
* Returns: %TRUE if the @area has an alpha component, %FALSE otherwise
*
* Since: 3.16
*/
gboolean
gtk_gl_area_get_has_alpha (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_alpha;
}
/**
* gtk_gl_area_set_has_alpha:
* @area: a #GtkGLArea
* @has_alpha: %TRUE to add an alpha component
*
* 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.
*
* If @has_alpha is %FALSE there will be no alpha channel, and the
* buffer will fully replace anything below the widget.
*
* Since: 3.16
*/
void
gtk_gl_area_set_has_alpha (GtkGLArea *area,
gboolean has_alpha)
{
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");
}
}
/**
* gtk_gl_area_get_has_depth_buffer:
* @area: a #GtkGLArea
*
* Returns whether the area has a depth buffer.
*
* Returns: %TRUE if the @area has a depth buffer, %FALSE otherwise
*
* Since: 3.16
*/
gboolean
gtk_gl_area_get_has_depth_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_depth_buffer;
}
/**
* gtk_gl_area_set_has_depth_buffer:
* @area: a #GtkGLArea
* @has_depth_buffer: %TRUE to add a depth buffer
*
* If @has_depth_buffer is %TRUE the widget will allocate and
* enable a depth buffer for the target framebuffer. Otherwise
* there will be none.
*
* Since: 3.16
*/
void
gtk_gl_area_set_has_depth_buffer (GtkGLArea *area,
gboolean has_depth_buffer)
{
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");
}
}
/**
* 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
* #GtkGLArea::render signal, and should not be called by
* application code.
*
* Since: 3.16
*/
void
gtk_gl_area_make_current (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
GtkWidget *widget;
g_return_if_fail (GTK_IS_GL_AREA (area));
widget = GTK_WIDGET (area);
g_return_if_fail (gtk_widget_get_realized (widget));
if (priv->context)
gdk_gl_context_make_current (priv->context);
}

105
gtk/gtkglarea.h Normal file
View File

@@ -0,0 +1,105 @@
/* GTK - The GIMP Toolkit
*
* gtkglarea.h: 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/>.
*/
#ifndef __GTK_GL_AREA_H__
#define __GTK_GL_AREA_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_GL_AREA (gtk_gl_area_get_type ())
#define GTK_GL_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_GL_AREA, GtkGLArea))
#define GTK_IS_GL_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_GL_AREA))
#define GTK_GL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_GL_AREA, GtkGLAreaClass))
#define GTK_IS_GL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_GL_AREA))
#define GTK_GL_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_GL_AREA, GtkGLAreaClass))
typedef struct _GtkGLArea GtkGLArea;
typedef struct _GtkGLAreaClass GtkGLAreaClass;
/**
* GtkGLArea:
*
* A #GtkWidget used for drawing with OpenGL.
*
* Since: 3.16
*/
struct _GtkGLArea
{
/*< private >*/
GtkWidget parent_instance;
};
/**
* GtkGLAreaClass:
* @render: class closure for the #GtkGLArea::render signal
*
* The `GtkGLAreaClass` structure contains only private data.
*
* Since: 3.16
*/
struct _GtkGLAreaClass
{
/*< private >*/
GtkWidgetClass parent_class;
/*< public >*/
gboolean (* render) (GtkGLArea *area,
GdkGLContext *context);
/*< private >*/
gpointer _padding[6];
};
GDK_AVAILABLE_IN_3_16
GType gtk_gl_area_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_3_16
GtkWidget * gtk_gl_area_new (void);
GDK_AVAILABLE_IN_3_16
gboolean gtk_gl_area_get_has_alpha (GtkGLArea *area);
GDK_AVAILABLE_IN_3_16
void gtk_gl_area_set_has_alpha (GtkGLArea *area,
gboolean has_alpha);
GDK_AVAILABLE_IN_3_16
gboolean gtk_gl_area_get_has_depth_buffer (GtkGLArea *area);
GDK_AVAILABLE_IN_3_16
void gtk_gl_area_set_has_depth_buffer (GtkGLArea *area,
gboolean has_depth_buffer);
GDK_AVAILABLE_IN_3_16
GdkGLContext * gtk_gl_area_get_context (GtkGLArea *area);
GDK_AVAILABLE_IN_3_16
void gtk_gl_area_make_current (GtkGLArea *area);
G_END_DECLS
#endif /* __GTK_GL_AREA_H__ */

View File

@@ -6948,6 +6948,8 @@ _gtk_widget_draw_internal (GtkWidget *widget,
{
gboolean result;
gdk_window_mark_paint_from_clip (window, cr);
g_signal_emit (widget, widget_signals[DRAW],
0, cr,
&result);

View File

@@ -68,6 +68,8 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testfullscreen \
testgeometry \
testgiconpixbuf \
testglarea \
testglblending \
testgrid \
testgtk \
testheaderbar \
@@ -152,6 +154,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testactionbar \
testwindowsize \
testpopover \
gdkgears \
$(NULL)
if USE_X11
@@ -499,6 +502,16 @@ testtitlebar_SOURCES = testtitlebar.c
testwindowsize_SOURCES = testwindowsize.c
gdkgears_SOURCES = \
gdkgears.c \
gtkgears.c \
gtkgears.h
testglblending_SOURCES = \
testglblending.c \
gtkgears.c \
gtkgears.h
EXTRA_DIST += \
gradient1.png \
testgtk.1 \

238
tests/gdkgears.c Normal file
View File

@@ -0,0 +1,238 @@
#include <stdlib.h>
#include <gtk/gtk.h>
#include "gtkgears.h"
/************************************************************************
* DEMO CODE *
************************************************************************/
static void
toggle_alpha (GtkWidget *checkbutton,
GtkWidget *gears)
{
gtk_gl_area_set_has_alpha (GTK_GL_AREA (gears),
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(checkbutton)));
}
static void
toggle_overlay (GtkWidget *checkbutton,
GtkWidget *revealer)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (revealer),
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(checkbutton)));
}
static void
toggle_spin (GtkWidget *checkbutton,
GtkWidget *spinner)
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(checkbutton)))
gtk_spinner_start (GTK_SPINNER (spinner));
else
gtk_spinner_stop (GTK_SPINNER (spinner));
}
static void
on_axis_value_change (GtkAdjustment *adjustment,
gpointer data)
{
GtkGears *gears = GTK_GEARS (data);
int axis = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (adjustment), "axis"));
gtk_gears_set_axis (gears, axis, gtk_adjustment_get_value (adjustment));
}
static GtkWidget *
create_axis_slider (GtkGears *gears,
int axis)
{
GtkWidget *box, *label, *slider;
GtkAdjustment *adj;
const char *text;
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
switch (axis)
{
case GTK_GEARS_X_AXIS:
text = "X";
break;
case GTK_GEARS_Y_AXIS:
text = "Y";
break;
case GTK_GEARS_Z_AXIS:
text = "Z";
break;
default:
g_assert_not_reached ();
}
label = gtk_label_new (text);
gtk_container_add (GTK_CONTAINER (box), label);
gtk_widget_show (label);
adj = gtk_adjustment_new (gtk_gears_get_axis (gears, axis), 0.0, 360.0, 1.0, 12.0, 0.0);
g_object_set_data (G_OBJECT (adj), "axis", GINT_TO_POINTER (axis));
g_signal_connect (adj, "value-changed",
G_CALLBACK (on_axis_value_change),
gears);
slider = gtk_scale_new (GTK_ORIENTATION_VERTICAL, adj);
gtk_scale_set_draw_value (GTK_SCALE (slider), FALSE);
gtk_container_add (GTK_CONTAINER (box), slider);
gtk_widget_set_vexpand (slider, TRUE);
gtk_widget_show (slider);
gtk_widget_show (box);
return box;
}
static void
moar_gears (GtkButton *button, gpointer data)
{
GtkContainer *container = GTK_CONTAINER (data);
GtkWidget *gears;
gears = gtk_gears_new ();
gtk_widget_set_size_request (gears, 100, 100);
gtk_container_add (container, gears);
gtk_widget_show (gears);
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *box, *hbox, *button, *spinner, *check,
*fps_label, *gears, *extra_hbox, *bbox, *overlay,
*revealer, *frame, *label, *scrolled;
int i;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GdkGears");
gtk_window_set_default_size (GTK_WINDOW (window), 640, 640);
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
overlay = gtk_overlay_new ();
gtk_container_add (GTK_CONTAINER (window), overlay);
gtk_widget_show (overlay);
revealer = gtk_revealer_new ();
gtk_widget_set_halign (revealer, GTK_ALIGN_END);
gtk_widget_set_valign (revealer, GTK_ALIGN_START);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay),
revealer);
gtk_widget_show (revealer);
frame = gtk_frame_new (NULL);
gtk_style_context_add_class (gtk_widget_get_style_context (frame),
"app-notification");
gtk_container_add (GTK_CONTAINER (revealer), frame);
gtk_widget_show (frame);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
gtk_box_set_spacing (GTK_BOX (hbox), 6);
gtk_container_add (GTK_CONTAINER (frame), hbox);
gtk_widget_show (hbox);
label = gtk_label_new ("This is a transparent overlay widget!!!!\nAmazing, eh?");
gtk_container_add (GTK_CONTAINER (hbox), label);
gtk_widget_show (label);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_box_set_spacing (GTK_BOX (box), 6);
gtk_container_add (GTK_CONTAINER (overlay), box);
gtk_widget_show (box);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
gtk_box_set_spacing (GTK_BOX (box), 6);
gtk_container_add (GTK_CONTAINER (box), hbox);
gtk_widget_show (hbox);
gears = gtk_gears_new ();
gtk_widget_set_hexpand (gears, TRUE);
gtk_widget_set_vexpand (gears, TRUE);
gtk_container_add (GTK_CONTAINER (hbox), gears);
gtk_widget_show (gears);
for (i = 0; i < GTK_GEARS_N_AXIS; i++)
gtk_container_add (GTK_CONTAINER (hbox), create_axis_slider (GTK_GEARS (gears), i));
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
gtk_box_set_spacing (GTK_BOX (hbox), 6);
gtk_container_add (GTK_CONTAINER (box), hbox);
gtk_widget_show (hbox);
fps_label = gtk_label_new ("");
gtk_container_add (GTK_CONTAINER (hbox), fps_label);
gtk_widget_show (fps_label);
gtk_gears_set_fps_label (GTK_GEARS (gears), GTK_LABEL (fps_label));
spinner = gtk_spinner_new ();
gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
gtk_widget_show (spinner);
gtk_spinner_start (GTK_SPINNER (spinner));
check = gtk_check_button_new_with_label ("Animate spinner");
gtk_box_pack_end (GTK_BOX (hbox), check, FALSE, FALSE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
gtk_widget_show (check);
g_signal_connect (check, "toggled",
G_CALLBACK (toggle_spin), spinner);
check = gtk_check_button_new_with_label ("Alpha");
gtk_box_pack_end (GTK_BOX (hbox), check, FALSE, FALSE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), FALSE);
gtk_widget_show (check);
g_signal_connect (check, "toggled",
G_CALLBACK (toggle_alpha), gears);
check = gtk_check_button_new_with_label ("Overlay");
gtk_box_pack_end (GTK_BOX (hbox), check, FALSE, FALSE, 0);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), FALSE);
gtk_widget_show (check);
g_signal_connect (check, "toggled",
G_CALLBACK (toggle_overlay), revealer);
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_NEVER);
gtk_container_add (GTK_CONTAINER (box), scrolled);
gtk_widget_show (scrolled);
extra_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
gtk_box_set_spacing (GTK_BOX (extra_hbox), 6);
gtk_container_add (GTK_CONTAINER (scrolled), extra_hbox);
gtk_widget_show (extra_hbox);
bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
gtk_box_set_spacing (GTK_BOX (bbox), 6);
gtk_container_add (GTK_CONTAINER (box), bbox);
gtk_widget_show (bbox);
button = gtk_button_new_with_label ("Moar gears!");
gtk_widget_set_hexpand (button, TRUE);
gtk_container_add (GTK_CONTAINER (bbox), button);
g_signal_connect (button, "clicked", G_CALLBACK (moar_gears), extra_hbox);
gtk_widget_show (button);
button = gtk_button_new_with_label ("Quit");
gtk_widget_set_hexpand (button, TRUE);
gtk_container_add (GTK_CONTAINER (bbox), button);
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return EXIT_SUCCESS;
}

439
tests/gtkgears.c Normal file
View File

@@ -0,0 +1,439 @@
/* The rendering code in here is taken from glxgears, which has the
* following copyright notice:
*
* Copyright (C) 1999-2001 Brian Paul All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <math.h>
#include <epoxy/gl.h>
#include "gtkgears.h"
typedef struct {
GLfloat view_rot[GTK_GEARS_N_AXIS];
GLint gear1, gear2, gear3;
GLfloat angle;
gint64 first_frame_time;
guint tick;
GtkLabel *fps_label;
} GtkGearsPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GtkGears, gtk_gears, GTK_TYPE_GL_AREA);
static gboolean gtk_gears_render (GtkGLArea *area,
GdkGLContext *context);
static void gtk_gears_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void gtk_gears_realize (GtkWidget *widget);
static gboolean gtk_gears_tick (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data);
GtkWidget *
gtk_gears_new ()
{
GtkWidget *gears;
gears = g_object_new (gtk_gears_get_type (),
"has-depth-buffer", TRUE,
NULL);
return gears;
}
static void
gtk_gears_init (GtkGears *gears)
{
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
priv->view_rot[GTK_GEARS_X_AXIS] = 20.0;
priv->view_rot[GTK_GEARS_Y_AXIS] = 30.0;
priv->view_rot[GTK_GEARS_Z_AXIS] = 20.0;
priv->tick = gtk_widget_add_tick_callback (GTK_WIDGET (gears), gtk_gears_tick, gears, NULL);
}
static void
gtk_gears_finalize (GObject *obj)
{
GtkGears *gears = GTK_GEARS (obj);
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
gtk_widget_remove_tick_callback (GTK_WIDGET (gears), priv->tick);
g_clear_object (&priv->fps_label);
G_OBJECT_CLASS (gtk_gears_parent_class)->finalize (obj);
}
static void
gtk_gears_class_init (GtkGearsClass *klass)
{
GTK_GL_AREA_CLASS (klass)->render = gtk_gears_render;
GTK_WIDGET_CLASS (klass)->realize = gtk_gears_realize;
GTK_WIDGET_CLASS (klass)->size_allocate = gtk_gears_size_allocate;
G_OBJECT_CLASS (klass)->finalize = gtk_gears_finalize;
}
/*
*
* Draw a gear wheel. You'll probably want to call this function when
* building a display list since we do a lot of trig here.
*
* Input: inner_radius - radius of hole at center
* outer_radius - radius at center of teeth
* width - width of gear
* teeth - number of teeth
* tooth_depth - depth of tooth
*/
static void
gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
GLint teeth, GLfloat tooth_depth)
{
GLint i;
GLfloat r0, r1, r2;
GLfloat angle, da;
GLfloat u, v, len;
r0 = inner_radius;
r1 = outer_radius - tooth_depth / 2.0;
r2 = outer_radius + tooth_depth / 2.0;
da = 2.0 * G_PI / teeth / 4.0;
glShadeModel(GL_FLAT);
glNormal3f(0.0, 0.0, 1.0);
/* draw front face */
glBegin(GL_QUAD_STRIP);
for (i = 0; i <= teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
if (i < teeth) {
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
width * 0.5);
}
}
glEnd();
/* draw front sides of teeth */
glBegin(GL_QUADS);
da = 2.0 * M_PI / teeth / 4.0;
for (i = 0; i < teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
width * 0.5);
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
width * 0.5);
}
glEnd();
glNormal3f(0.0, 0.0, -1.0);
/* draw back face */
glBegin(GL_QUAD_STRIP);
for (i = 0; i <= teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
if (i < teeth) {
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
-width * 0.5);
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
}
}
glEnd();
/* draw back sides of teeth */
glBegin(GL_QUADS);
da = 2.0 * M_PI / teeth / 4.0;
for (i = 0; i < teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
-width * 0.5);
glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
-width * 0.5);
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
}
glEnd();
/* draw outward faces of teeth */
glBegin(GL_QUAD_STRIP);
for (i = 0; i < teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
u = r2 * cos(angle + da) - r1 * cos(angle);
v = r2 * sin(angle + da) - r1 * sin(angle);
len = sqrt(u * u + v * v);
u /= len;
v /= len;
glNormal3f(v, -u, 0.0);
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
glNormal3f(cos(angle), sin(angle), 0.0);
glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
width * 0.5);
glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
-width * 0.5);
u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
glNormal3f(v, -u, 0.0);
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
width * 0.5);
glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
-width * 0.5);
glNormal3f(cos(angle), sin(angle), 0.0);
}
glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);
glEnd();
glShadeModel(GL_SMOOTH);
/* draw inside radius cylinder */
glBegin(GL_QUAD_STRIP);
for (i = 0; i <= teeth; i++) {
angle = i * 2.0 * M_PI / teeth;
glNormal3f(-cos(angle), -sin(angle), 0.0);
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
}
glEnd();
}
/* new window size or exposure */
static void
reshape(int width, int height)
{
GLfloat h = (GLfloat) height / (GLfloat) width;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -40.0);
}
static gboolean
gtk_gears_render (GtkGLArea *area,
GdkGLContext *context)
{
GtkGearsPrivate *priv = gtk_gears_get_instance_private (GTK_GEARS (area));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(priv->view_rot[GTK_GEARS_X_AXIS], 1.0, 0.0, 0.0);
glRotatef(priv->view_rot[GTK_GEARS_Y_AXIS], 0.0, 1.0, 0.0);
glRotatef(priv->view_rot[GTK_GEARS_Z_AXIS], 0.0, 0.0, 1.0);
glPushMatrix();
glTranslatef(-3.0, -2.0, 0.0);
glRotatef(priv->angle, 0.0, 0.0, 1.0);
glCallList(priv->gear1);
glPopMatrix();
glPushMatrix();
glTranslatef(3.1, -2.0, 0.0);
glRotatef(-2.0 * priv->angle - 9.0, 0.0, 0.0, 1.0);
glCallList(priv->gear2);
glPopMatrix();
glPushMatrix();
glTranslatef(-3.1, 4.2, 0.0);
glRotatef(-2.0 * priv->angle - 25.0, 0.0, 0.0, 1.0);
glCallList(priv->gear3);
glPopMatrix();
glPopMatrix();
return TRUE;
}
static void
gtk_gears_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkGLArea *glarea = GTK_GL_AREA (widget);
GTK_WIDGET_CLASS (gtk_gears_parent_class)->size_allocate (widget, allocation);
if (gtk_widget_get_realized (widget))
{
gtk_gl_area_make_current (glarea);
reshape (allocation->width, allocation->height);
}
}
static void
gtk_gears_realize (GtkWidget *widget)
{
GtkGLArea *glarea = GTK_GL_AREA (widget);
GtkGears *gears = GTK_GEARS(widget);
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
GtkAllocation allocation;
static GLfloat pos[4] = { 5.0, 5.0, 10.0, 0.0 };
static GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 };
static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 };
static GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 };
GTK_WIDGET_CLASS (gtk_gears_parent_class)->realize (widget);
gtk_gl_area_make_current (glarea);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
/* make the gears */
priv->gear1 = glGenLists(1);
glNewList(priv->gear1, GL_COMPILE);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
gear(1.0, 4.0, 1.0, 20, 0.7);
glEndList();
priv->gear2 = glGenLists(1);
glNewList(priv->gear2, GL_COMPILE);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
gear(0.5, 2.0, 2.0, 10, 0.7);
glEndList();
priv->gear3 = glGenLists(1);
glNewList(priv->gear3, GL_COMPILE);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
gear(1.3, 2.0, 0.5, 10, 0.7);
glEndList();
glEnable(GL_NORMALIZE);
gtk_widget_get_allocation (widget, &allocation);
reshape (allocation.width, allocation.height);
}
static gboolean
gtk_gears_tick (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
GtkGears *gears = GTK_GEARS (widget);
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
GdkFrameTimings *timings, *previous_timings;
gint64 previous_frame_time = 0;
gint64 frame_time;
gint64 history_start, history_len;
gint64 frame;
char *s;
frame = gdk_frame_clock_get_frame_counter (frame_clock);
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
if (priv->first_frame_time == 0)
{
/* No need for changes on first frame */
priv->first_frame_time = frame_time;
if (priv->fps_label)
gtk_label_set_label (priv->fps_label, "FPS: ---");
return G_SOURCE_CONTINUE;
}
/* glxgears advances 70 degrees per second, so do the same */
priv->angle = fmod ((frame_time - priv->first_frame_time) / (double)G_USEC_PER_SEC * 70.0, 360.0);
gtk_widget_queue_draw (widget);
history_start = gdk_frame_clock_get_history_start (frame_clock);
if (priv->fps_label && frame % 60 == 0)
{
history_len = frame - history_start;
if (history_len > 0)
{
previous_timings = gdk_frame_clock_get_timings (frame_clock, frame - history_len);
previous_frame_time = gdk_frame_timings_get_frame_time (previous_timings);
s = g_strdup_printf ("FPS: %-4.1f", (G_USEC_PER_SEC * history_len) / (double)(frame_time - previous_frame_time));
gtk_label_set_label (priv->fps_label, s);
g_free (s);
}
}
timings = gdk_frame_clock_get_current_timings (frame_clock);
previous_timings = gdk_frame_clock_get_timings (frame_clock,
gdk_frame_timings_get_frame_counter (timings) - 1);
if (previous_timings != NULL)
previous_frame_time = gdk_frame_timings_get_frame_time (previous_timings);
return G_SOURCE_CONTINUE;
}
void
gtk_gears_set_axis (GtkGears *gears, int axis, double value)
{
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
if (axis < 0 || axis >= GTK_GEARS_N_AXIS)
return;
priv->view_rot[axis] = value;
gtk_widget_queue_draw (GTK_WIDGET (gears));
}
double
gtk_gears_get_axis (GtkGears *gears, int axis)
{
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
if (axis < 0 || axis >= GTK_GEARS_N_AXIS)
return 0.0;
return priv->view_rot[axis];
}
void
gtk_gears_set_fps_label (GtkGears *gears, GtkLabel *label)
{
GtkGearsPrivate *priv = gtk_gears_get_instance_private (gears);
if (label)
g_object_ref (label);
g_clear_object (&priv->fps_label);
priv->fps_label = label;
}

44
tests/gtkgears.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef __GTK_GEARS_H__
#define __GTK_GEARS_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
enum {
GTK_GEARS_X_AXIS,
GTK_GEARS_Y_AXIS,
GTK_GEARS_Z_AXIS,
GTK_GEARS_N_AXIS
};
#define GTK_TYPE_GEARS (gtk_gears_get_type ())
#define GTK_GEARS(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
GTK_TYPE_GEARS, \
GtkGears))
#define GTK_IS_GEARS(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
GTK_TYPE_GEARS))
typedef struct {
GtkGLArea parent;
} GtkGears;
typedef struct {
GtkGLAreaClass parent_class;
} GtkGearsClass;
GtkWidget *gtk_gears_new ();
void gtk_gears_set_axis (GtkGears *gears,
int axis,
double value);
double gtk_gears_get_axis (GtkGears *gears,
int axis);
void gtk_gears_set_fps_label (GtkGears *gears,
GtkLabel *label);
G_END_DECLS
#endif /* __GTK_GEARS_H__ */

159
tests/testglarea.c Normal file
View File

@@ -0,0 +1,159 @@
#include <stdlib.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>
enum {
X_AXIS,
Y_AXIS,
Z_AXIS,
N_AXIS
};
static float rotation_angles[N_AXIS] = { 0.0 };
static GtkWidget *gl_area;
static void
draw_triangle (void)
{
glColor3f (1.0f, 0.85f, 0.35f);
glBegin (GL_TRIANGLES);
{
glVertex3f ( 0.0, 0.6, 0.0);
glVertex3f (-0.2, -0.3, 0.0);
glVertex3f ( 0.2, -0.3, 0.0);
}
glEnd ();
}
static gboolean
render (GtkGLArea *area,
GdkGLContext *context)
{
glClearColor (0.5, 0.5, 0.5, 1.0);
glClear (GL_COLOR_BUFFER_BIT);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glRotatef (rotation_angles[X_AXIS], 1, 0, 0);
glRotatef (rotation_angles[Y_AXIS], 0, 1, 0);
glRotatef (rotation_angles[Z_AXIS], 0, 0, 1);
draw_triangle ();
glFlush ();
return TRUE;
}
static void
on_axis_value_change (GtkAdjustment *adjustment,
gpointer data)
{
int axis = GPOINTER_TO_INT (data);
if (axis < 0 || axis >= N_AXIS)
return;
rotation_angles[axis] = gtk_adjustment_get_value (adjustment);
gtk_widget_queue_draw (gl_area);
}
static GtkWidget *
create_axis_slider (int axis)
{
GtkWidget *box, *label, *slider;
GtkAdjustment *adj;
const char *text;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE);
switch (axis)
{
case X_AXIS:
text = "X axis";
break;
case Y_AXIS:
text = "Y axis";
break;
case Z_AXIS:
text = "Z axis";
break;
default:
g_assert_not_reached ();
}
label = gtk_label_new (text);
gtk_container_add (GTK_CONTAINER (box), label);
gtk_widget_show (label);
adj = gtk_adjustment_new (0.0, 0.0, 360.0, 1.0, 12.0, 0.0);
g_signal_connect (adj, "value-changed",
G_CALLBACK (on_axis_value_change),
GINT_TO_POINTER (axis));
slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj);
gtk_container_add (GTK_CONTAINER (box), slider);
gtk_widget_set_hexpand (slider, TRUE);
gtk_widget_show (slider);
gtk_widget_show (box);
return box;
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *box, *button, *controls;
int i;
gtk_init (&argc, &argv);
/* create a new pixel format; we use this to configure the
* GL context, and to check for features
*/
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GtkGLArea - Golden Triangle");
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_box_set_spacing (GTK_BOX (box), 6);
gtk_container_add (GTK_CONTAINER (window), box);
gtk_widget_show (box);
gl_area = gtk_gl_area_new ();
gtk_widget_set_hexpand (gl_area, TRUE);
gtk_widget_set_vexpand (gl_area, TRUE);
gtk_container_add (GTK_CONTAINER (box), gl_area);
g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
gtk_widget_show (gl_area);
controls = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
gtk_container_add (GTK_CONTAINER (box), controls);
gtk_widget_set_hexpand (controls, TRUE);
gtk_widget_show (controls);
for (i = 0; i < N_AXIS; i++)
gtk_container_add (GTK_CONTAINER (controls), create_axis_slider (i));
button = gtk_button_new_with_label ("Quit");
gtk_widget_set_hexpand (button, TRUE);
gtk_container_add (GTK_CONTAINER (box), button);
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return EXIT_SUCCESS;
}

94
tests/testglblending.c Normal file
View File

@@ -0,0 +1,94 @@
#include <stdlib.h>
#include <gtk/gtk.h>
#include "gtkgears.h"
int
main (int argc, char *argv[])
{
GtkWidget *window, *fixed, *gears, *spinner;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Test GL/gtk inter-blending");
gtk_window_set_default_size (GTK_WINDOW (window), 250, 250);
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
fixed = gtk_fixed_new ();
gtk_container_add (GTK_CONTAINER (window), fixed);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 90, 80);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 100, 80);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 110, 80);
gears = gtk_gears_new ();
gtk_widget_set_size_request (gears, 70, 50);
gtk_fixed_put (GTK_FIXED (fixed), gears, 60, 100);
gears = gtk_gears_new ();
gtk_gl_area_set_has_alpha (GTK_GL_AREA (gears), TRUE);
gtk_widget_set_size_request (gears, 70, 50);
gtk_fixed_put (GTK_FIXED (fixed), gears, 120, 100);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 90, 110);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 100, 110);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 110, 110);
gears = gtk_gears_new ();
gtk_widget_set_size_request (gears, 70, 50);
gtk_fixed_put (GTK_FIXED (fixed), gears, 60, 130);
gears = gtk_gears_new ();
gtk_gl_area_set_has_alpha (GTK_GL_AREA (gears), TRUE);
gtk_widget_set_size_request (gears, 70, 50);
gtk_fixed_put (GTK_FIXED (fixed), gears, 120, 130);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 90, 150);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 100, 150);
spinner = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (spinner));
gtk_widget_set_size_request (spinner, 50, 50);
gtk_fixed_put (GTK_FIXED (fixed), spinner, 110, 150);
gtk_widget_show_all (window);
gtk_main ();
return EXIT_SUCCESS;
}