diff --git a/gsk/Makefile.am b/gsk/Makefile.am
index 9f6f866c07..c72ba04155 100644
--- a/gsk/Makefile.am
+++ b/gsk/Makefile.am
@@ -1,23 +1,12 @@
include $(top_srcdir)/Makefile.decl
--include $(INTROSPECTION_MAKEFILE)
-
-# Preamble
-INTROSPECTION_GIRS =
-INTROSPECTION_SCANNER_ARGS = \
- --add-include-path=../gdk \
- --warn-all
-INTROSPECTION_COMPILER_ARGS = \
- --includedir=$(srcdir) \
- --includedir=. \
- --includedir=../gdk
AM_CPPFLAGS = \
-DG_LOG_DOMAIN=\"Gsk\" \
-DGSK_COMPILATION \
- -I$(top_builddir) \
- -I$(top_builddir)/gsk \
-I$(top_srcdir) \
-I$(top_srcdir)/gdk \
+ -I$(top_builddir) \
+ -I$(top_builddir)/gsk \
$(GTK_DEBUG_FLAGS) \
$(GTK_WARN_FLAGS) \
$(GSK_DEP_CFLAGS)
@@ -35,39 +24,124 @@ LDADD = \
BUILT_SOURCES =
CLEANFILES =
+DISTCLEANFILES =
lib_LTLIBRARIES =
-gsk_public_source_h =
-gsk_private_source_h =
-gsk_private_source_c =
-gsk_source_c =
+gsk_public_source_h = \
+ gskenums.h \
+ gskrenderer.h \
+ gskrendernode.h \
+ gskrendernodeiter.h \
+ gsktypes.h
+gsk_private_source_h = \
+ gskcairorendererprivate.h \
+ gskdebugprivate.h \
+ gskglrendererprivate.h \
+ gskrendererprivate.h \
+ gskrendernodeprivate.h \
+ gskprivate.h
+gsk_private_source_c = \
+ gskprivate.c
+gsk_built_source_h = \
+ gskenumtypes.h \
+ gskresources.h
+gsk_built_source_c = \
+ gskenumtypes.c \
+ gskresources.c
+gsk_source_c = \
+ gskcairorenderer.c \
+ gskdebug.c \
+ gskglrenderer.c \
+ gskrenderer.c \
+ gskrendernode.c \
+ gskrendernodeiter.c
-libgsk_3_la_SOURCES = $(all_sources)
-libgsk_3_la_CFLAGS = $(AM_CFLAGS) $(GDK_HIDDEN_VISIBILITY_CFLAGS)
-libgsk_3_la_LIBADD = $(GSK_DEP_LIBS) $(top_builddir)/gdk/libgdk-3.la
-libgsk_3_la_LDFLAGS = $(LDADD)
+all_sources = \
+ $(gsk_public_source_h) \
+ $(gsk_private_source_h) \
+ $(gsk_built_source_h) \
+ $(gsk_private_source_c) \
+ $(gsk_source_c)
-lib_LTLIBRARIES += libgsk-3.la
+BUILT_SOURCES += $(gsk_built_source_h) $(gsk_built_source_c) gsk.resources.xml
-gskincludedir = $(includedir)/gtk-3.0/gsk
-gskinclude_HEADERS = $(gsk_public_source_h) gsk.h
+gskenumtypes.h: $(gsk_public_source_h) gskenumtypes.h.template
+ $(AM_V_GEN) $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \
+ gskenumtypes.h.tmp && \
+ mv gskenumtypes.h.tmp gskenumtypes.h
+
+gskenumtypes.c: $(gsk_public_source_h) gskenumtypes.c.template
+ $(AM_V_GEN) $(GLIB_MKENUMS) --template $(filter %.template,$^) $(filter-out %.template,$^) > \
+ gskenumtypes.c.tmp && \
+ mv gskenumtypes.c.tmp gskenumtypes.c
+
+EXTRA_DIST += gskenumtypes.h.template gskenumtypes.c.template
+DISTCLEANFILES += gskenumtypes.h gskenumtypes.c
+
+resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(builddir)/gsk.resources.xml)
+
+gsk.resources.xml: Makefile.am
+ $(AM_V_GEN) echo "" > $@; \
+ echo "" >> $@; \
+ echo " " >> $@; \
+ for f in $(top_srcdir)/gsk/resources/glsl/*; do \
+ n=`basename $$f`; \
+ echo " resources/glsl/$$n" >> $@; \
+ done; \
+ echo " " >> $@; \
+ echo "" >> $@
+
+gskresources.h: gsk.resources.xml
+ $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $< \
+ --target=$@ --sourcedir=$(srcdir) --c-name _gsk --generate-header --manual-register
+
+gskresources.c: gsk.resources.xml $(resource_files)
+ $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $< \
+ --target=$@ --sourcedir=$(srcdir) --c-name _gsk --generate-source --manual-register
+
+EXTRA_DIST += $(resource_files)
+CLEANFILES += gsk.resources.xml
+DISTCLEANFILES += gskresources.h gskresources.c
+
+libgsk_4_la_SOURCES = $(all_sources)
+nodist_libgsk_4_la_SOURCES = $(gsk_built_source_h) $(gsk_built_source_c)
+libgsk_4_la_CFLAGS = $(AM_CFLAGS) $(GDK_HIDDEN_VISIBILITY_CFLAGS)
+libgsk_4_la_LIBADD = $(GSK_DEP_LIBS) $(top_builddir)/gdk/libgdk-4.la
+libgsk_4_la_LDFLAGS = $(LDADD)
+
+lib_LTLIBRARIES += libgsk-4.la
+
+gskincludedir = $(includedir)/gtk-4.0/gsk
+gskinclude_HEADERS = $(gsk_public_source_h) gskenumtypes.h gsk.h
+
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ENV = \
+ CC="$(CC)"
+INTROSPECTION_SCANNER_ARGS = \
+ --add-include-path=../gdk \
+ --warn-all
+INTROSPECTION_COMPILER_ARGS = \
+ --includedir=$(srcdir) \
+ --includedir=. \
+ --includedir=../gdk
if HAVE_INTROSPECTION
-introspection_files = $(gsk_source_c) $(gsk_public_source_h)
+introspection_files = $(filter-out $(wildcard *private.h),$(all_sources))
-Gsk-3.0.gir: libgsk-3.la Makefile
-Gsk_3_0_gir_SCANNERFLAGS = \
+Gsk-4.0.gir: libgsk-4.la Makefile
+Gsk_4_0_gir_SCANNERFLAGS = \
--add-include-path=$(top_builddir)/gdk \
- --include-uninstalled=$(top_builddir)/gdk/Gdk-3.0.gir \
+ --include-uninstalled=$(top_builddir)/gdk/Gdk-4.0.gir \
--c-include="gsk/gsk.h"
-Gsk_3_0_gir_LIBS = libgsk-3.la
-Gsk_3_0_gir_FILES = $(introspection_files)
-Gsk_3_0_gir_CFLAGS = $(AM_CPPFLAGS)
-Gsk_3_0_gir_EXPORT_PACKAGES = gsk-3.0
-Gsk_3_0_gir_INCLUDES = GObject-2.0 cairo-1.0 Graphene-1.0
-INTROSPECTION_GIRS += Gsk-3.0.gir
+Gsk_4_0_gir_LIBS = libgsk-4.la $(top_builddir)/gdk/libgdk-4.la
+Gsk_4_0_gir_FILES = $(introspection_files)
+Gsk_4_0_gir_CFLAGS = $(AM_CPPFLAGS)
+Gsk_4_0_gir_EXPORT_PACKAGES = gsk-4.0
+Gsk_4_0_gir_INCLUDES = GObject-2.0 cairo-1.0 Graphene-1.0
+INTROSPECTION_GIRS += Gsk-4.0.gir
girdir = $(datadir)/gir-1.0
gir_DATA = $(INTROSPECTION_GIRS)
diff --git a/gsk/gsk.h b/gsk/gsk.h
new file mode 100644
index 0000000000..01c45693f5
--- /dev/null
+++ b/gsk/gsk.h
@@ -0,0 +1,33 @@
+/* GSK - The GTK Scene Kit
+ * Copyright 2016 Endless
+ *
+ * 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 .
+ */
+
+#ifndef __GSK_H__
+#define __GSK_H__
+
+#define __GSK_H_INSIDE__
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#undef __GSK_H_INSIDE__
+
+#endif /* __GSK_H__ */
diff --git a/gsk/gskcairorenderer.c b/gsk/gskcairorenderer.c
new file mode 100644
index 0000000000..62b7a47cfe
--- /dev/null
+++ b/gsk/gskcairorenderer.c
@@ -0,0 +1,195 @@
+#include "config.h"
+
+#include "gskcairorendererprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskrendererprivate.h"
+#include "gskrendernodeiter.h"
+#include "gskrendernodeprivate.h"
+
+struct _GskCairoRenderer
+{
+ GskRenderer parent_instance;
+
+ graphene_rect_t viewport;
+};
+
+struct _GskCairoRendererClass
+{
+ GskRendererClass parent_class;
+};
+
+G_DEFINE_TYPE (GskCairoRenderer, gsk_cairo_renderer, GSK_TYPE_RENDERER)
+
+static gboolean
+gsk_cairo_renderer_realize (GskRenderer *renderer)
+{
+ return TRUE;
+}
+
+static void
+gsk_cairo_renderer_unrealize (GskRenderer *renderer)
+{
+
+}
+
+static void
+gsk_cairo_renderer_render_node (GskCairoRenderer *self,
+ GskRenderNode *node,
+ cairo_t *cr)
+{
+ GskRenderNodeIter iter;
+ GskRenderNode *child;
+ gboolean pop_group = FALSE;
+ graphene_matrix_t mvp;
+ cairo_matrix_t ctm;
+ graphene_rect_t frame;
+
+ if (gsk_render_node_is_hidden (node))
+ return;
+
+ cairo_save (cr);
+
+ gsk_render_node_get_world_matrix (node, &mvp);
+ if (graphene_matrix_to_2d (&mvp, &ctm.xx, &ctm.yx, &ctm.xy, &ctm.yy, &ctm.x0, &ctm.y0))
+ {
+ GSK_NOTE (CAIRO, g_print ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }\n",
+ ctm.xx, ctm.yx,
+ ctm.xy, ctm.yy,
+ ctm.x0, ctm.y0));
+ cairo_transform (cr, &ctm);
+ }
+ else
+ g_critical ("Invalid non-affine transformation for node %p", node);
+
+ gsk_render_node_get_bounds (node, &frame);
+ GSK_NOTE (CAIRO, g_print ("CLIP = { .x = %g, .y = %g, .width = %g, .height = %g }\n",
+ frame.origin.x, frame.origin.y,
+ frame.size.width, frame.size.height));
+
+ if (!GSK_RENDER_MODE_CHECK (GEOMETRY))
+ {
+ cairo_rectangle (cr, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
+ cairo_clip (cr);
+ }
+
+ if (!gsk_render_node_is_opaque (node) && gsk_render_node_get_opacity (node) != 1.0)
+ {
+ GSK_NOTE (CAIRO, g_print ("Pushing opacity group (opacity:%g)\n",
+ gsk_render_node_get_opacity (node)));
+ cairo_push_group (cr);
+ pop_group = TRUE;
+ }
+
+ GSK_NOTE (CAIRO, g_print ("Rendering surface %p for node %p at %g, %g\n",
+ gsk_render_node_get_surface (node),
+ node,
+ frame.origin.x, frame.origin.y));
+ cairo_set_source_surface (cr, gsk_render_node_get_surface (node), frame.origin.x, frame.origin.y);
+ cairo_paint (cr);
+
+ if (GSK_RENDER_MODE_CHECK (GEOMETRY))
+ {
+ cairo_save (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_rectangle (cr, frame.origin.x - 1, frame.origin.y - 1, frame.size.width + 2, frame.size.height + 2);
+ cairo_set_line_width (cr, 2);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+ cairo_matrix_invert (&ctm);
+ cairo_transform (cr, &ctm);
+
+ if (gsk_render_node_get_n_children (node) != 0)
+ {
+ GSK_NOTE (CAIRO, g_print ("Drawing %d children of node [%p]\n",
+ gsk_render_node_get_n_children (node),
+ node));
+ gsk_render_node_iter_init (&iter, node);
+ while (gsk_render_node_iter_next (&iter, &child))
+ gsk_cairo_renderer_render_node (self, child, cr);
+ }
+
+ if (pop_group)
+ {
+ cairo_pop_group_to_source (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_paint_with_alpha (cr, gsk_render_node_get_opacity (node));
+ }
+
+ cairo_restore (cr);
+}
+
+static void
+gsk_cairo_renderer_resize_viewport (GskRenderer *renderer,
+ const graphene_rect_t *viewport)
+{
+ GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer);
+
+ self->viewport = *viewport;
+}
+
+static void
+gsk_cairo_renderer_render (GskRenderer *renderer)
+{
+ GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer);
+ cairo_surface_t *target = gsk_renderer_get_surface (renderer);
+ GskRenderNode *root = gsk_renderer_get_root_node (renderer);
+ cairo_t *cr = cairo_create (target);
+
+ if (GSK_RENDER_MODE_CHECK (GEOMETRY))
+ {
+ cairo_save (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_rectangle (cr,
+ self->viewport.origin.x,
+ self->viewport.origin.y,
+ self->viewport.size.width,
+ self->viewport.size.height);
+ cairo_set_source_rgba (cr, 0, 0, 0.85, 0.5);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+
+ gsk_cairo_renderer_render_node (self, root, cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+gsk_cairo_renderer_clear (GskRenderer *renderer)
+{
+ cairo_surface_t *surface = gsk_renderer_get_surface (renderer);
+ cairo_t *cr = cairo_create (surface);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ if (gsk_renderer_get_use_alpha (renderer))
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
+ else
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+}
+
+static void
+gsk_cairo_renderer_class_init (GskCairoRendererClass *klass)
+{
+ GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+ renderer_class->realize = gsk_cairo_renderer_realize;
+ renderer_class->unrealize = gsk_cairo_renderer_unrealize;
+ renderer_class->resize_viewport = gsk_cairo_renderer_resize_viewport;
+ renderer_class->clear = gsk_cairo_renderer_clear;
+ renderer_class->render = gsk_cairo_renderer_render;
+}
+
+static void
+gsk_cairo_renderer_init (GskCairoRenderer *self)
+{
+
+}
diff --git a/gsk/gskcairorendererprivate.h b/gsk/gskcairorendererprivate.h
new file mode 100644
index 0000000000..7a9bd23456
--- /dev/null
+++ b/gsk/gskcairorendererprivate.h
@@ -0,0 +1,26 @@
+#ifndef __GSK_CAIRO_RENDERER_PRIVATE_H__
+#define __GSK_CAIRO_RENDERER_PRIVATE_H__
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_CAIRO_RENDERER (gsk_cairo_renderer_get_type ())
+
+#define GSK_CAIRO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_CAIRO_RENDERER, GskCairoRenderer))
+#define GSK_IS_CAIRO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_CAIRO_RENDERER))
+#define GSK_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_CAIRO_RENDERER, GskCairoRendererClass))
+#define GSK_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_CAIRO_RENDERER))
+#define GSK_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_CAIRO_RENDERER, GskCairoRendererClass))
+
+typedef struct _GskCairoRenderer GskCairoRenderer;
+typedef struct _GskCairoRendererClass GskCairoRendererClass;
+
+GType gsk_cairo_renderer_get_type (void) G_GNUC_CONST;
+
+GskRenderer *gsk_cairo_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* __GSK_CAIRO_RENDERER_PRIVATE_H__ */
diff --git a/gsk/gskdebug.c b/gsk/gskdebug.c
new file mode 100644
index 0000000000..ebc5366876
--- /dev/null
+++ b/gsk/gskdebug.c
@@ -0,0 +1,58 @@
+#include "gskdebugprivate.h"
+
+#ifdef G_ENABLE_DEBUG
+static const GDebugKey gsk_debug_keys[] = {
+ { "rendernode", GSK_DEBUG_RENDER_NODE },
+ { "renderer", GSK_DEBUG_RENDERER },
+ { "cairo", GSK_DEBUG_CAIRO },
+ { "opengl", GSK_DEBUG_OPENGL },
+};
+#endif
+
+static const GDebugKey gsk_rendering_keys[] = {
+ { "geometry", GSK_RENDERING_MODE_GEOMETRY },
+};
+
+gboolean
+gsk_check_debug_flags (GskDebugFlags flags)
+{
+#ifdef G_ENABLE_DEBUG
+ static volatile gsize gsk_debug_flags__set;
+ static guint gsk_debug_flags;
+
+ if (g_once_init_enter (&gsk_debug_flags__set))
+ {
+ const char *env = g_getenv ("GSK_DEBUG");
+
+ gsk_debug_flags = g_parse_debug_string (env,
+ (GDebugKey *) gsk_debug_keys,
+ G_N_ELEMENTS (gsk_debug_keys));
+
+ g_once_init_leave (&gsk_debug_flags__set, TRUE);
+ }
+
+ return (gsk_debug_flags & flags) != 0;
+#else
+ return FALSE;
+#endif
+}
+
+gboolean
+gsk_check_rendering_flags (GskRenderingMode flags)
+{
+ static volatile gsize gsk_rendering_flags__set;
+ static guint gsk_rendering_flags;
+
+ if (g_once_init_enter (&gsk_rendering_flags__set))
+ {
+ const char *env = g_getenv ("GSK_RENDERING_MODE");
+
+ gsk_rendering_flags = g_parse_debug_string (env,
+ (GDebugKey *) gsk_rendering_keys,
+ G_N_ELEMENTS (gsk_rendering_keys));
+
+ g_once_init_leave (&gsk_rendering_flags__set, TRUE);
+ }
+
+ return (gsk_rendering_flags & flags) != 0;
+}
diff --git a/gsk/gskdebugprivate.h b/gsk/gskdebugprivate.h
new file mode 100644
index 0000000000..439be0757b
--- /dev/null
+++ b/gsk/gskdebugprivate.h
@@ -0,0 +1,43 @@
+#ifndef __GSK_DEBUG_PRIVATE_H__
+#define __GSK_DEBUG_PRIVATE_H__
+
+#include
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GSK_DEBUG_RENDER_NODE = 1 << 0,
+ GSK_DEBUG_RENDERER = 1 << 1,
+ GSK_DEBUG_CAIRO = 1 << 2,
+ GSK_DEBUG_OPENGL = 1 << 3
+} GskDebugFlags;
+
+typedef enum {
+ GSK_RENDERING_MODE_GEOMETRY = 1 << 0
+} GskRenderingMode;
+
+gboolean gsk_check_debug_flags (GskDebugFlags flags);
+
+gboolean gsk_check_rendering_flags (GskRenderingMode flags);
+
+#ifdef G_ENABLE_DEBUG
+
+#define GSK_DEBUG_CHECK(type) G_UNLIKELY (gsk_check_debug_flags (GSK_DEBUG_ ## type))
+#define GSK_RENDER_MODE_CHECK(type) G_UNLIKELY (gsk_check_rendering_flags (GSK_RENDERING_MODE_ ## type))
+
+#define GSK_NOTE(type,action) G_STMT_START { \
+ if (GSK_DEBUG_CHECK (type)) { \
+ action; \
+ } } G_STMT_END
+
+#else
+
+#define GSK_RENDER_MODE_CHECK(type) 0
+#define GSK_DEBUG_CHECK(type) 0
+#define GSK_NOTE(type,action)
+
+#endif
+
+G_END_DECLS
+
+#endif /* __GSK_DEBUG_PRIVATE_H__ */
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
new file mode 100644
index 0000000000..b831d4903e
--- /dev/null
+++ b/gsk/gskenums.h
@@ -0,0 +1,46 @@
+/* GSK - The GTK Scene Kit
+ * Copyright 2016 Endless
+ *
+ * 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 .
+ */
+
+#ifndef __GSK_ENUMS_H__
+#define __GSK_ENUMS_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+/**
+ * GskScalingFilter:
+ * @GSK_SCALING_FILTER_LINEAR: linear interpolation filter
+ * @GSK_SCALING_FILTER_NEAREST: nearest neighbor interpolation filter
+ * @GSK_SCALING_FILTER_TRILINEAR: linear interpolation along each axis,
+ * plus mipmap generation, with linear interpolation along the mipmap
+ * levels
+ *
+ * The filters used when scaling texture data.
+ *
+ * The actual implementation of each filter is deferred to the
+ * rendering pipeline.
+ *
+ * Since: 3.22
+ */
+typedef enum {
+ GSK_SCALING_FILTER_LINEAR,
+ GSK_SCALING_FILTER_NEAREST,
+ GSK_SCALING_FILTER_TRILINEAR
+} GskScalingFilter;
+
+#endif /* __GSK_TYPES_H__ */
diff --git a/gsk/gskenumtypes.c.template b/gsk/gskenumtypes.c.template
new file mode 100644
index 0000000000..430ea8fd26
--- /dev/null
+++ b/gsk/gskenumtypes.c.template
@@ -0,0 +1,38 @@
+/*** BEGIN file-header ***/
+#include "config.h"
+#include "gskenumtypes.h"
+#include
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static volatile gsize g_define_type_id__volatile = 0;
+
+ if (g_once_init_enter (&g_define_type_id__volatile))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ GType g_define_type_id =
+ g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+ }
+
+ return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
diff --git a/gsk/gskenumtypes.h.template b/gsk/gskenumtypes.h.template
new file mode 100644
index 0000000000..15a8ac6525
--- /dev/null
+++ b/gsk/gskenumtypes.h.template
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __GSK_ENUM_TYPES_H__
+#define __GSK_ENUM_TYPES_H__
+
+#include
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GDK_AVAILABLE_IN_ALL GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __GSK_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c
new file mode 100644
index 0000000000..96f04afe7d
--- /dev/null
+++ b/gsk/gskglrenderer.c
@@ -0,0 +1,1092 @@
+#include "config.h"
+
+#include "gskglrendererprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskenums.h"
+#include "gskrendererprivate.h"
+#include "gskrendernodeprivate.h"
+#include "gskrendernodeiter.h"
+
+#include "gskprivate.h"
+
+#include
+
+typedef struct {
+ /* Back pointer to the node, only meant for comparison */
+ GskRenderNode *node;
+
+ graphene_point3d_t min;
+ graphene_point3d_t max;
+
+ graphene_size_t size;
+
+ graphene_matrix_t mvp;
+
+ gboolean opaque : 1;
+ float opacity;
+ float z;
+
+ const char *name;
+
+ guint vao_id;
+ guint texture_id;
+ guint program_id;
+ guint mvp_location;
+ guint map_location;
+ guint uv_location;
+ guint position_location;
+ guint alpha_location;
+ guint buffer_id;
+} RenderItem;
+
+struct _GskGLRenderer
+{
+ GskRenderer parent_instance;
+
+ GdkGLContext *context;
+
+ graphene_matrix_t mvp;
+ graphene_frustum_t frustum;
+
+ guint frame_buffer;
+ guint render_buffer;
+ guint depth_stencil_buffer;
+ guint texture_id;
+
+ guint program_id;
+ guint mvp_location;
+ guint map_location;
+ guint uv_location;
+ guint position_location;
+ guint alpha_location;
+
+ guint vao_id;
+
+ GArray *opaque_render_items;
+ GArray *transparent_render_items;
+
+ gboolean has_buffers : 1;
+ gboolean has_alpha : 1;
+ gboolean has_stencil_buffer : 1;
+ gboolean has_depth_buffer : 1;
+};
+
+struct _GskGLRendererClass
+{
+ GskRendererClass parent_class;
+};
+
+static void render_item_clear (gpointer data_);
+
+G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
+
+static void
+gsk_gl_renderer_dispose (GObject *gobject)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (gobject);
+
+ g_clear_object (&self->context);
+
+ G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
+}
+
+static void
+gsk_gl_renderer_create_buffers (GskGLRenderer *self)
+{
+ if (self->has_buffers)
+ return;
+
+ GSK_NOTE (OPENGL, g_print ("Creating buffers\n"));
+
+ glGenFramebuffersEXT (1, &self->frame_buffer);
+
+ if (gsk_renderer_get_use_alpha (GSK_RENDERER (self)))
+ {
+ if (self->texture_id == 0)
+ glGenTextures (1, &self->texture_id);
+
+ if (self->render_buffer != 0)
+ {
+ glDeleteRenderbuffersEXT (1, &self->render_buffer);
+ self->render_buffer = 0;
+ }
+ }
+ else
+ {
+ if (self->render_buffer == 0)
+ glGenRenderbuffersEXT (1, &self->render_buffer);
+
+ if (self->texture_id != 0)
+ {
+ glDeleteTextures (1, &self->texture_id);
+ self->texture_id = 0;
+ }
+ }
+
+ if (self->has_depth_buffer || self->has_stencil_buffer)
+ {
+ if (self->depth_stencil_buffer == 0)
+ glGenRenderbuffersEXT (1, &self->depth_stencil_buffer);
+ }
+ else
+ {
+ if (self->depth_stencil_buffer != 0)
+ {
+ glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer);
+ self->depth_stencil_buffer = 0;
+ }
+ }
+
+ /* We only have one VAO at the moment */
+ glGenVertexArrays (1, &self->vao_id);
+ glBindVertexArray (self->vao_id);
+
+ self->has_buffers = TRUE;
+}
+
+static void
+gsk_gl_renderer_allocate_buffers (GskGLRenderer *self,
+ int width,
+ int height)
+{
+ if (self->context == NULL)
+ return;
+
+ if (self->texture_id != 0)
+ {
+ glBindTexture (GL_TEXTURE_2D, self->texture_id);
+ 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, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ if (self->render_buffer != 0)
+ {
+ glBindRenderbuffer (GL_RENDERBUFFER, self->render_buffer);
+ glRenderbufferStorage (GL_RENDERBUFFER, GL_RGB8, width, height);
+ }
+
+ if (self->has_depth_buffer || self->has_stencil_buffer)
+ {
+ glBindRenderbuffer (GL_RENDERBUFFER, self->depth_stencil_buffer);
+
+ if (self->has_stencil_buffer)
+ glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+ else
+ glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
+ }
+}
+
+static void
+gsk_gl_renderer_attach_buffers (GskGLRenderer *self)
+{
+ gsk_gl_renderer_create_buffers (self);
+
+ GSK_NOTE (OPENGL, g_print ("Attaching buffers\n"));
+
+ glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->frame_buffer);
+
+ if (self->texture_id != 0)
+ {
+ glFramebufferTexture2D (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_2D, self->texture_id, 0);
+ }
+ else if (self->render_buffer != 0)
+ {
+ glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_RENDERBUFFER_EXT, self->render_buffer);
+ }
+
+ if (self->depth_stencil_buffer != 0)
+ {
+ if (self->has_depth_buffer)
+ glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, self->depth_stencil_buffer);
+
+ if (self->has_stencil_buffer)
+ glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
+ GL_RENDERBUFFER_EXT, self->depth_stencil_buffer);
+ }
+}
+
+static void
+gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
+{
+ if (self->context == NULL)
+ return;
+
+ if (!self->has_buffers)
+ return;
+
+ GSK_NOTE (OPENGL, g_print ("Destroying buffers\n"));
+
+ gdk_gl_context_make_current (self->context);
+
+ if (self->vao_id != 0)
+ {
+ glDeleteVertexArrays (1, &self->vao_id);
+ self->vao_id = 0;
+ }
+
+ if (self->depth_stencil_buffer != 0)
+ {
+ glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer);
+ self->depth_stencil_buffer = 0;
+ }
+
+ if (self->render_buffer != 0)
+ {
+ glDeleteRenderbuffersEXT (1, &self->render_buffer);
+ self->render_buffer = 0;
+ }
+
+ if (self->texture_id != 0)
+ {
+ glDeleteTextures (1, &self->texture_id);
+ self->texture_id = 0;
+ }
+
+ if (self->frame_buffer != 0)
+ {
+ glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
+ glDeleteFramebuffersEXT (1, &self->frame_buffer);
+ self->frame_buffer = 0;
+ }
+
+ self->has_buffers = FALSE;
+}
+
+static guint
+create_shader (int type,
+ const char *code)
+{
+ guint shader;
+ int status;
+
+ shader = glCreateShader (type);
+ glShaderSource (shader, 1, &code, NULL);
+ glCompileShader (shader);
+
+ glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE)
+ {
+ int log_len;
+ char *buffer;
+
+ glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
+
+ buffer = g_malloc0 (log_len + 1);
+ glGetShaderInfoLog (shader, log_len, NULL, buffer);
+
+ g_critical ("Compile failure in %s shader:\n%s",
+ type == GL_VERTEX_SHADER ? "vertex" : "fragment",
+ buffer);
+ g_free (buffer);
+
+ glDeleteShader (shader);
+
+ return 0;
+ }
+
+ return shader;
+}
+
+static void
+gsk_gl_renderer_create_program (GskGLRenderer *self)
+{
+ guint vertex_shader = 0, fragment_shader = 0;
+ GBytes *source;
+ int status;
+
+ GSK_NOTE (OPENGL, g_print ("Compiling vertex shader\n"));
+ source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-vertex.glsl", 0, NULL);
+ vertex_shader = create_shader (GL_VERTEX_SHADER, g_bytes_get_data (source, NULL));
+ g_bytes_unref (source);
+ if (vertex_shader == 0)
+ goto out;
+
+ GSK_NOTE (OPENGL, g_print ("Compiling fragment shader\n"));
+ source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-fragment.glsl", 0, NULL);
+ fragment_shader = create_shader (GL_FRAGMENT_SHADER, g_bytes_get_data (source, NULL));
+ g_bytes_unref (source);
+ if (fragment_shader == 0)
+ goto out;
+
+ self->program_id = glCreateProgram ();
+ glAttachShader (self->program_id, vertex_shader);
+ glAttachShader (self->program_id, fragment_shader);
+ glLinkProgram (self->program_id);
+
+ glGetProgramiv (self->program_id, GL_LINK_STATUS, &status);
+ if (status == GL_FALSE)
+ {
+ char *buffer = NULL;
+ int log_len = 0;
+
+ glGetProgramiv (self->program_id, GL_INFO_LOG_LENGTH, &log_len);
+
+ buffer = g_malloc0 (log_len + 1);
+ glGetProgramInfoLog (self->program_id, log_len, NULL, buffer);
+
+ g_critical ("Linking failure in shader:\n%s", buffer);
+ g_free (buffer);
+
+ glDeleteProgram (self->program_id);
+ self->program_id = 0;
+
+ goto out;
+ }
+
+ /* Find the location of each uniform and attribute we use in our
+ * shaders
+ */
+ self->mvp_location = glGetUniformLocation (self->program_id, "mvp");
+ self->map_location = glGetUniformLocation (self->program_id, "map");
+ self->alpha_location = glGetUniformLocation (self->program_id, "alpha");
+ self->position_location = glGetAttribLocation (self->program_id, "position");
+ self->uv_location = glGetAttribLocation (self->program_id, "uv");
+
+ GSK_NOTE (OPENGL, g_print ("Program [%d] { mvp:%u, map:%u, alpha:%u, position:%u, uv:%u }\n",
+ self->program_id,
+ self->mvp_location,
+ self->map_location,
+ self->alpha_location,
+ self->position_location,
+ self->uv_location));
+
+ /* We can detach and destroy the shaders from the linked program */
+ glDetachShader (self->program_id, vertex_shader);
+ glDetachShader (self->program_id, fragment_shader);
+
+out:
+ if (vertex_shader != 0)
+ glDeleteShader (vertex_shader);
+ if (fragment_shader != 0)
+ glDeleteShader (fragment_shader);
+}
+
+static void
+gsk_gl_renderer_destroy_program (GskGLRenderer *self)
+{
+ if (self->program_id != 0)
+ {
+ glDeleteProgram (self->program_id);
+ self->program_id = 0;
+ }
+}
+
+static gboolean
+gsk_gl_renderer_realize (GskRenderer *renderer)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+ GError *error = NULL;
+
+ /* If we didn't get a GdkGLContext before realization, try creating
+ * one now, for our exclusive use.
+ */
+ if (self->context == NULL)
+ {
+ GdkWindow *window = gsk_renderer_get_window (renderer);
+
+ if (window == NULL)
+ return FALSE;
+
+ self->context = gdk_window_create_gl_context (window, &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to create GL context for renderer: %s",
+ error->message);
+ g_error_free (error);
+
+ return FALSE;
+ }
+ }
+
+ gdk_gl_context_realize (self->context, &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to realize GL renderer: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ gdk_gl_context_make_current (self->context);
+
+ GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
+
+ gsk_gl_renderer_create_buffers (self);
+ gsk_gl_renderer_create_program (self);
+
+ self->opaque_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16);
+ g_array_set_clear_func (self->opaque_render_items, render_item_clear);
+
+ self->transparent_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16);
+ g_array_set_clear_func (self->opaque_render_items, render_item_clear);
+
+ return TRUE;
+}
+
+static void
+gsk_gl_renderer_unrealize (GskRenderer *renderer)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+ if (self->context == NULL)
+ return;
+
+ gdk_gl_context_make_current (self->context);
+
+ g_clear_pointer (&self->opaque_render_items, g_array_unref);
+ g_clear_pointer (&self->transparent_render_items, g_array_unref);
+
+ gsk_gl_renderer_destroy_buffers (self);
+ gsk_gl_renderer_destroy_program (self);
+
+ if (self->context == gdk_gl_context_get_current ())
+ gdk_gl_context_clear_current ();
+}
+
+static void
+gsk_gl_renderer_resize_viewport (GskRenderer *renderer,
+ const graphene_rect_t *viewport)
+{
+}
+
+static void
+gsk_gl_renderer_update (GskRenderer *renderer,
+ const graphene_matrix_t *modelview,
+ const graphene_matrix_t *projection)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+ GSK_NOTE (OPENGL, g_print ("Updating the modelview/projection\n"));
+
+ graphene_matrix_multiply (modelview, projection, &self->mvp);
+
+ graphene_frustum_init_from_matrix (&self->frustum, &self->mvp);
+}
+
+static void
+render_item_clear (gpointer data_)
+{
+ RenderItem *item = data_;
+
+ GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] buffer %u\n",
+ item,
+ item->buffer_id));
+ glDeleteBuffers (1, &item->buffer_id);
+ item->buffer_id = 0;
+
+ GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] texture %u\n",
+ item,
+ item->texture_id));
+ glDeleteTextures (1, &item->texture_id);
+ item->texture_id = 0;
+
+ graphene_matrix_init_identity (&item->mvp);
+
+ item->opacity = 1;
+}
+
+#define N_VERTICES 6
+
+static void
+render_item (RenderItem *item)
+{
+ struct vertex_info {
+ float position[2];
+ float uv[2];
+ };
+ float mvp[16];
+
+ glBindVertexArray (item->vao_id);
+
+ /* Generate the vertex buffer for the texture quad */
+ if (item->buffer_id == 0)
+ {
+ struct vertex_info vertex_data[] = {
+ { { item->min.x, item->min.y }, { 0, 0 }, },
+ { { item->min.x, item->max.y }, { 0, 1 }, },
+ { { item->max.x, item->min.y }, { 1, 0 }, },
+
+ { { item->max.x, item->max.y }, { 1, 1 }, },
+ { { item->min.x, item->max.y }, { 0, 1 }, },
+ { { item->max.x, item->min.y }, { 1, 0 }, },
+ };
+
+ GSK_NOTE (OPENGL, g_print ("Creating quad for render item [%p]\n", item));
+
+ glGenBuffers (1, &item->buffer_id);
+ glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id);
+
+ /* The data won't change */
+ glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
+
+ /* Set up the buffers with the computed position and texels */
+ glEnableVertexAttribArray (item->position_location);
+ glVertexAttribPointer (item->position_location, 2, GL_FLOAT, GL_FALSE,
+ sizeof (struct vertex_info),
+ (void *) G_STRUCT_OFFSET (struct vertex_info, position));
+ glEnableVertexAttribArray (item->uv_location);
+ glVertexAttribPointer (item->uv_location, 2, GL_FLOAT, GL_FALSE,
+ sizeof (struct vertex_info),
+ (void *) G_STRUCT_OFFSET (struct vertex_info, uv));
+ }
+ else
+ {
+ /* We already set up the vertex buffer, so we just need to reuse it */
+ glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id);
+ glEnableVertexAttribArray (item->position_location);
+ glEnableVertexAttribArray (item->uv_location);
+ }
+
+ glUseProgram (item->program_id);
+
+ /* Use texture unit 0 for the sampler */
+ glActiveTexture (GL_TEXTURE0);
+ glBindTexture (GL_TEXTURE_2D, item->texture_id);
+ glUniform1i (item->map_location, 0);
+
+ /* Pass the opacity component */
+ glUniform1f (item->alpha_location, item->opaque ? 1 : item->opacity);
+
+ /* Pass the mvp to the vertex shader */
+ GSK_NOTE (OPENGL, graphene_matrix_print (&item->mvp));
+ graphene_matrix_to_float (&item->mvp, mvp);
+ glUniformMatrix4fv (item->mvp_location, 1, GL_FALSE, mvp);
+
+ /* Draw the quad */
+ GSK_NOTE (OPENGL, g_print ("Drawing item <%s>[%p] with opacity: %g\n",
+ item->name,
+ item,
+ item->opaque ? 1 : item->opacity));
+
+ glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
+
+ /* Reset the state */
+ glBindTexture (GL_TEXTURE_2D, 0);
+ glDisableVertexAttribArray (item->position_location);
+ glDisableVertexAttribArray (item->uv_location);
+ glUseProgram (0);
+}
+
+static void
+surface_to_texture (cairo_surface_t *surface,
+ graphene_rect_t *clip,
+ int min_filter,
+ int mag_filter,
+ guint *texture_out)
+{
+ cairo_surface_t *tmp;
+ guint texture_id;
+
+ cairo_surface_flush (surface);
+
+ tmp = cairo_surface_map_to_image (surface, &(cairo_rectangle_int_t) {
+ 0, 0,
+ clip->size.width,
+ clip->size.height
+ });
+
+ glGenTextures (1, &texture_id);
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+
+ GSK_NOTE (OPENGL, g_print ("Uploading px@[%p] { w:%g, h:%g } to texid:%d\n",
+ cairo_image_surface_get_data (tmp),
+ clip->size.width,
+ clip->size.height,
+ texture_id));
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, cairo_image_surface_get_stride (tmp) / 4);
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
+ clip->size.width,
+ clip->size.height,
+ 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ cairo_image_surface_get_data (tmp));
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+
+ if (min_filter != GL_NEAREST)
+ glGenerateMipmap (GL_TEXTURE_2D);
+
+ GSK_NOTE (OPENGL, g_print ("New texture id %d from surface %p\n", texture_id, surface));
+
+ cairo_surface_unmap_image (surface, tmp);
+
+ *texture_out = texture_id;
+}
+
+static void
+get_gl_scaling_filters (GskRenderer *renderer,
+ int *min_filter_r,
+ int *mag_filter_r)
+{
+ GskScalingFilter min_filter, mag_filter;
+
+ gsk_renderer_get_scaling_filters (renderer, &min_filter, &mag_filter);
+
+ switch (min_filter)
+ {
+ case GSK_SCALING_FILTER_NEAREST:
+ *min_filter_r = GL_NEAREST;
+ break;
+
+ case GSK_SCALING_FILTER_LINEAR:
+ *min_filter_r = GL_LINEAR;
+ break;
+
+ case GSK_SCALING_FILTER_TRILINEAR:
+ *min_filter_r = GL_LINEAR_MIPMAP_LINEAR;
+ break;
+ }
+
+ switch (mag_filter)
+ {
+ case GSK_SCALING_FILTER_NEAREST:
+ *mag_filter_r = GL_NEAREST;
+ break;
+
+ /* There's no point in using anything above GL_LINEAR for
+ * magnification filters
+ */
+ case GSK_SCALING_FILTER_LINEAR:
+ case GSK_SCALING_FILTER_TRILINEAR:
+ *mag_filter_r = GL_LINEAR;
+ break;
+ }
+}
+
+static gboolean
+check_in_frustum (const graphene_frustum_t *frustum,
+ RenderItem *item)
+{
+ graphene_box_t aabb;
+
+ graphene_box_init (&aabb, &item->min, &item->max);
+ graphene_matrix_transform_box (&item->mvp, &aabb, &aabb);
+
+ return graphene_frustum_intersects_box (frustum, &aabb);
+}
+
+static float
+project_item (const graphene_matrix_t *projection,
+ const graphene_matrix_t *modelview)
+{
+ graphene_vec4_t vec;
+
+ graphene_matrix_get_row (modelview, 3, &vec);
+ graphene_matrix_transform_vec4 (projection, &vec, &vec);
+
+ return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec);
+}
+
+static void
+gsk_gl_renderer_add_render_item (GskGLRenderer *self,
+ GskRenderNode *node)
+{
+ graphene_rect_t viewport;
+ int gl_min_filter, gl_mag_filter;
+ cairo_surface_t *surface;
+ GskRenderNodeIter iter;
+ graphene_matrix_t mv, projection;
+ graphene_rect_t bounds;
+ GskRenderNode *child;
+ RenderItem item;
+
+ if (gsk_render_node_is_hidden (node))
+ {
+ GSK_NOTE (OPENGL, g_print ("Skipping hidden node <%s>[%p]\n",
+ node->name != NULL ? node->name : "unnamed",
+ node));
+ return;
+ }
+
+ gsk_renderer_get_viewport (GSK_RENDERER (self), &viewport);
+
+ gsk_render_node_get_bounds (node, &bounds);
+
+ item.node = node;
+ item.name = node->name != NULL ? node->name : "unnamed";
+
+ /* The texture size */
+ item.size = bounds.size;
+
+ /* Each render item is an axis-aligned bounding box that we
+ * transform using the given transformation matrix
+ */
+ item.min.x = (bounds.origin.x * 2) / bounds.size.width - 1;
+ item.min.y = (bounds.origin.y * 2) / bounds.size.height - 1;
+ item.min.z = 0.f;
+
+ item.max.x = (bounds.origin.x + bounds.size.width) * 2 / bounds.size.width - 1;
+ item.max.y = (bounds.origin.y + bounds.size.height) * 2 / bounds.size.height - 1;
+ item.max.z = 0.f;
+
+ /* The location of the item, in normalized world coordinates */
+ gsk_render_node_get_world_matrix (node, &mv);
+ item.mvp = mv;
+
+ item.opaque = gsk_render_node_is_opaque (node);
+ item.opacity = gsk_render_node_get_opacity (node);
+
+ /* GL objects */
+ item.vao_id = self->vao_id;
+ item.buffer_id = 0;
+ item.program_id = self->program_id;
+ item.map_location = self->map_location;
+ item.mvp_location = self->mvp_location;
+ item.uv_location = self->uv_location;
+ item.position_location = self->position_location;
+ item.alpha_location = self->alpha_location;
+
+ gsk_renderer_get_projection (GSK_RENDERER (self), &projection);
+ item.z = project_item (&projection, &mv);
+
+ /* Discard the item if it's outside of the frustum as determined by the
+ * viewport and the projection matrix
+ */
+#if 0
+ if (!check_in_frustum (&self->frustum, &item))
+ {
+ GSK_NOTE (OPENGL, g_print ("Node <%s>[%p] culled by frustum\n",
+ node->name != NULL ? node->name : "unnamed",
+ node));
+ return;
+ }
+#endif
+
+ /* TODO: This should really be an asset atlas, to avoid uploading a ton
+ * of textures. Ideally we could use a single Cairo surface to get around
+ * the GL texture limits and reorder the texture data on the CPU side and
+ * do a single upload; alternatively, we could use a separate FBO and
+ * render each texture into it
+ */
+ get_gl_scaling_filters (GSK_RENDERER (self), &gl_min_filter, &gl_mag_filter);
+ surface = gsk_render_node_get_surface (node);
+
+ /* If the node does not have any surface we skip drawing it, but we still
+ * recurse.
+ *
+ * XXX: This needs to be re-done if the opacity is != 0, in which case we
+ * need to composite the opacity level of the children
+ */
+ if (surface == NULL)
+ goto recurse_children;
+
+ surface_to_texture (surface, &bounds, gl_min_filter, gl_mag_filter, &item.texture_id);
+
+ GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n",
+ node->name != NULL ? node->name : "unnamed",
+ node));
+ if (gsk_render_node_is_opaque (node) && gsk_render_node_get_opacity (node) == 1.f)
+ g_array_append_val (self->opaque_render_items, item);
+ else
+ g_array_append_val (self->transparent_render_items, item);
+
+recurse_children:
+ gsk_render_node_iter_init (&iter, node);
+ while (gsk_render_node_iter_next (&iter, &child))
+ gsk_gl_renderer_add_render_item (self, child);
+}
+
+static int
+opaque_item_cmp (gconstpointer _a,
+ gconstpointer _b)
+{
+ const RenderItem *a = _a;
+ const RenderItem *b = _b;
+
+ if (a->z != b->z)
+ {
+ if (a->z > b->z)
+ return 1;
+
+ return -1;
+ }
+
+ if (a != b)
+ {
+ if ((gsize) a > (gsize) b)
+ return 1;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+transparent_item_cmp (gconstpointer _a,
+ gconstpointer _b)
+{
+ const RenderItem *a = _a;
+ const RenderItem *b = _b;
+
+ if (a->z != b->z)
+ {
+ if (a->z < b->z)
+ return 1;
+
+ return -1;
+ }
+
+ if (a != b)
+ {
+ if ((gsize) a < (gsize) b)
+ return 1;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+gsk_gl_renderer_validate_tree (GskRenderer *renderer,
+ GskRenderNode *root)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+ gboolean clear_items = FALSE;
+ int i;
+
+ if (self->context == NULL)
+ return;
+
+ gdk_gl_context_make_current (self->context);
+
+ if (self->opaque_render_items->len > 0 || self->transparent_render_items->len > 0)
+ {
+ /* If we only changed the opacity and transformations then there is no
+ * reason to clear the render items
+ */
+ for (i = 0; i < self->opaque_render_items->len; i++)
+ {
+ RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i);
+ GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node);
+
+ if (changes == 0)
+ continue;
+
+ if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0)
+ {
+ item->opaque = gsk_render_node_is_opaque (item->node);
+ item->opacity = gsk_render_node_get_opacity (item->node);
+ changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY;
+ }
+
+ if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM)
+ {
+ gsk_render_node_get_world_matrix (item->node, &item->mvp);
+ changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
+ }
+
+ if (changes != 0)
+ {
+ clear_items = TRUE;
+ break;
+ }
+ }
+
+ for (i = 0; i < self->transparent_render_items->len; i++)
+ {
+ RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i);
+ GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node);
+
+ if (changes == 0)
+ continue;
+
+ if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0)
+ {
+ item->opaque = gsk_render_node_is_opaque (item->node);
+ item->opacity = gsk_render_node_get_opacity (item->node);
+ changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY;
+ }
+
+ if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM)
+ {
+ gsk_render_node_get_world_matrix (item->node, &item->mvp);
+ changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
+ }
+
+ if (changes != 0)
+ {
+ clear_items = TRUE;
+ break;
+ }
+ }
+ }
+ else
+ clear_items = TRUE;
+
+ if (!clear_items)
+ {
+ GSK_NOTE (OPENGL, g_print ("Tree is still valid\n"));
+ goto out;
+ }
+
+ for (i = 0; i < self->opaque_render_items->len; i++)
+ render_item_clear (&g_array_index (self->opaque_render_items, RenderItem, i));
+ for (i = 0; i < self->transparent_render_items->len; i++)
+ render_item_clear (&g_array_index (self->transparent_render_items, RenderItem, i));
+
+ g_array_set_size (self->opaque_render_items, 0);
+ g_array_set_size (self->transparent_render_items, 0);
+
+ GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n"));
+ gsk_gl_renderer_add_render_item (self, gsk_renderer_get_root_node (renderer));
+
+ GSK_NOTE (OPENGL, g_print ("Sorting render nodes\n"));
+ g_array_sort (self->opaque_render_items, opaque_item_cmp);
+ g_array_sort (self->transparent_render_items, transparent_item_cmp);
+
+out:
+ GSK_NOTE (OPENGL, g_print ("Total render items: %d (opaque:%d, transparent:%d)\n",
+ self->opaque_render_items->len + self->transparent_render_items->len,
+ self->opaque_render_items->len,
+ self->transparent_render_items->len));
+}
+
+static void
+gsk_gl_renderer_clear (GskRenderer *renderer)
+{
+}
+
+static void
+gsk_gl_renderer_render (GskRenderer *renderer)
+{
+ GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+ graphene_rect_t viewport;
+ int scale, status, clear_bits;
+ guint i;
+
+ if (self->context == NULL)
+ return;
+
+ gdk_gl_context_make_current (self->context);
+
+ gsk_renderer_get_viewport (renderer, &viewport);
+
+ gsk_gl_renderer_create_buffers (self);
+ gsk_gl_renderer_allocate_buffers (self, viewport.size.width, viewport.size.height);
+ gsk_gl_renderer_attach_buffers (self);
+
+ if (self->has_depth_buffer)
+ glEnable (GL_DEPTH_TEST);
+ else
+ glDisable (GL_DEPTH_TEST);
+
+ /* Ensure that the viewport is up to date */
+ status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
+ if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
+ {
+ GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %g, %g)\n",
+ viewport.size.width,
+ viewport.size.height));
+ glViewport (0, 0, viewport.size.width, viewport.size.height);
+ }
+
+ clear_bits = GL_COLOR_BUFFER_BIT;
+ if (self->has_depth_buffer)
+ clear_bits |= GL_DEPTH_BUFFER_BIT;
+ if (self->has_stencil_buffer)
+ clear_bits |= GL_STENCIL_BUFFER_BIT;
+
+ GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
+ glClearColor (0, 0, 0, 0);
+ glClear (clear_bits);
+
+ /* Opaque pass: front-to-back */
+ GSK_NOTE (OPENGL, g_print ("Rendering %u opaque items\n", self->opaque_render_items->len));
+ for (i = 0; i < self->opaque_render_items->len; i++)
+ {
+ RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i);
+
+ render_item (item);
+ }
+
+ glEnable (GL_BLEND);
+
+ /* Transparent pass: back-to-front */
+ GSK_NOTE (OPENGL, g_print ("Rendering %u transparent items\n", self->transparent_render_items->len));
+ for (i = 0; i < self->transparent_render_items->len; i++)
+ {
+ RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i);
+
+ render_item (item);
+ }
+
+ glDisable (GL_BLEND);
+
+ /* Draw the output of the GL rendering to the window */
+ GSK_NOTE (OPENGL, g_print ("Drawing GL content on Cairo surface using a %s\n",
+ self->texture_id != 0 ? "texture" : "renderbuffer"));
+ scale = 1;
+ gdk_cairo_draw_from_gl (gsk_renderer_get_draw_context (renderer),
+ gsk_renderer_get_window (renderer),
+ self->texture_id != 0 ? self->texture_id : self->render_buffer,
+ self->texture_id != 0 ? GL_TEXTURE : GL_RENDERBUFFER,
+ scale,
+ 0, 0, viewport.size.width, viewport.size.height);
+
+ gdk_gl_context_make_current (self->context);
+}
+
+static void
+gsk_gl_renderer_class_init (GskGLRendererClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+ gobject_class->dispose = gsk_gl_renderer_dispose;
+
+ renderer_class->realize = gsk_gl_renderer_realize;
+ renderer_class->unrealize = gsk_gl_renderer_unrealize;
+ renderer_class->resize_viewport = gsk_gl_renderer_resize_viewport;
+ renderer_class->update = gsk_gl_renderer_update;
+ renderer_class->clear = gsk_gl_renderer_clear;
+ renderer_class->validate_tree = gsk_gl_renderer_validate_tree;
+ renderer_class->render = gsk_gl_renderer_render;
+}
+
+static void
+gsk_gl_renderer_init (GskGLRenderer *self)
+{
+ gsk_ensure_resources ();
+
+ graphene_matrix_init_identity (&self->mvp);
+
+ self->has_depth_buffer = TRUE;
+ self->has_stencil_buffer = TRUE;
+}
+
+void
+gsk_gl_renderer_set_context (GskGLRenderer *renderer,
+ GdkGLContext *context)
+{
+ g_return_if_fail (GSK_IS_GL_RENDERER (renderer));
+ g_return_if_fail (context == NULL || GDK_IS_GL_CONTEXT (context));
+
+ if (gsk_renderer_is_realized (GSK_RENDERER (renderer)))
+ return;
+
+ if (gdk_gl_context_get_display (context) != gsk_renderer_get_display (GSK_RENDERER (renderer)))
+ return;
+
+ g_set_object (&renderer->context, context);
+}
+
+GdkGLContext *
+gsk_gl_renderer_get_context (GskGLRenderer *renderer)
+{
+ g_return_val_if_fail (GSK_IS_GL_RENDERER (renderer), NULL);
+
+ return renderer->context;
+}
diff --git a/gsk/gskglrendererprivate.h b/gsk/gskglrendererprivate.h
new file mode 100644
index 0000000000..a30b20140a
--- /dev/null
+++ b/gsk/gskglrendererprivate.h
@@ -0,0 +1,23 @@
+#ifndef __GSK_GL_RENDERER_PRIVATE_H__
+#define __GSK_GL_RENDERER_PRIVATE_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_RENDERER (gsk_gl_renderer_get_type ())
+
+#define GSK_GL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_RENDERER, GskGLRenderer))
+#define GSK_IS_GL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+#define GSK_IS_GL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+
+typedef struct _GskGLRenderer GskGLRenderer;
+typedef struct _GskGLRendererClass GskGLRendererClass;
+
+GType gsk_gl_renderer_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GSK_GL_RENDERER_PRIVATE_H__ */
diff --git a/gsk/gskprivate.c b/gsk/gskprivate.c
new file mode 100644
index 0000000000..9e82502da5
--- /dev/null
+++ b/gsk/gskprivate.c
@@ -0,0 +1,16 @@
+#include "gskresources.h"
+
+static gpointer
+register_resources (gpointer data)
+{
+ _gsk_register_resource ();
+ return NULL;
+}
+
+void
+gsk_ensure_resources (void)
+{
+ static GOnce register_resources_once = G_ONCE_INIT;
+
+ g_once (®ister_resources_once, register_resources, NULL);
+}
diff --git a/gsk/gskprivate.h b/gsk/gskprivate.h
new file mode 100644
index 0000000000..84539c1557
--- /dev/null
+++ b/gsk/gskprivate.h
@@ -0,0 +1,12 @@
+#ifndef __GSK_PRIVATE_H__
+#define __GSK_PRIVATE_H__
+
+#include
+
+G_BEGIN_DECLS
+
+void gsk_ensure_resources (void);
+
+G_END_DECLS
+
+#endif /* __GSK_PRIVATE_H__ */
diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c
new file mode 100644
index 0000000000..cf90e382d9
--- /dev/null
+++ b/gsk/gskrenderer.c
@@ -0,0 +1,1377 @@
+/* GSK - The GTK Scene Kit
+ *
+ * Copyright 2016 Endless
+ *
+ * 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 .
+ */
+
+/**
+ * SECTION:GskRenderer
+ * @title: GskRenderer
+ * @Short_desc: Renders a scene with a simplified graph
+ *
+ * TODO
+ */
+
+#include "config.h"
+
+#include "gskrendererprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskcairorendererprivate.h"
+#include "gskglrendererprivate.h"
+#include "gskrendernodeprivate.h"
+
+#include "gskenumtypes.h"
+
+#include
+#include
+#include
+
+#ifdef GDK_WINDOWING_X11
+#include
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+#include
+#endif
+
+typedef struct
+{
+ GObject parent_instance;
+
+ GdkDisplay *display;
+ GdkWindow *window;
+
+ graphene_rect_t viewport;
+ graphene_matrix_t modelview;
+ graphene_matrix_t projection;
+
+ GskScalingFilter min_filter;
+ GskScalingFilter mag_filter;
+
+ GskRenderNode *root_node;
+
+ cairo_surface_t *surface;
+ cairo_t *draw_context;
+
+ gboolean is_realized : 1;
+ gboolean needs_viewport_resize : 1;
+ gboolean needs_modelview_update : 1;
+ gboolean needs_projection_update : 1;
+ gboolean needs_tree_validation : 1;
+ gboolean auto_clear : 1;
+ gboolean use_alpha : 1;
+} GskRendererPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GskRenderer, gsk_renderer, G_TYPE_OBJECT)
+
+enum {
+ PROP_VIEWPORT = 1,
+ PROP_MODELVIEW,
+ PROP_PROJECTION,
+ PROP_MINIFICATION_FILTER,
+ PROP_MAGNIFICATION_FILTER,
+ PROP_AUTO_CLEAR,
+ PROP_ROOT_NODE,
+ PROP_DISPLAY,
+ PROP_WINDOW,
+ PROP_SURFACE,
+ PROP_USE_ALPHA,
+
+ N_PROPS
+};
+
+static GParamSpec *gsk_renderer_properties[N_PROPS];
+
+#define GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
+ g_critical ("Renderer of type '%s' does not implement GskRenderer::" # method, G_OBJECT_TYPE_NAME (obj))
+
+static gboolean
+gsk_renderer_real_realize (GskRenderer *self)
+{
+ GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, realize);
+ return FALSE;
+}
+
+static void
+gsk_renderer_real_unrealize (GskRenderer *self)
+{
+ GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, unrealize);
+}
+
+static void
+gsk_renderer_real_render (GskRenderer *self)
+{
+ GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, render);
+}
+
+static void
+gsk_renderer_real_resize_viewport (GskRenderer *self,
+ const graphene_rect_t *viewport)
+{
+}
+
+static void
+gsk_renderer_real_update (GskRenderer *self,
+ const graphene_matrix_t *mv,
+ const graphene_matrix_t *proj)
+{
+}
+
+static void
+gsk_renderer_real_validate_tree (GskRenderer *self,
+ GskRenderNode *root)
+{
+}
+
+static void
+gsk_renderer_dispose (GObject *gobject)
+{
+ GskRenderer *self = GSK_RENDERER (gobject);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ gsk_renderer_unrealize (self);
+
+ g_clear_pointer (&priv->surface, cairo_surface_destroy);
+ g_clear_pointer (&priv->draw_context, cairo_destroy);
+
+ g_clear_object (&priv->window);
+ g_clear_object (&priv->root_node);
+ g_clear_object (&priv->display);
+
+ G_OBJECT_CLASS (gsk_renderer_parent_class)->dispose (gobject);
+}
+
+static void
+gsk_renderer_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GskRenderer *self = GSK_RENDERER (gobject);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_VIEWPORT:
+ gsk_renderer_set_viewport (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_MODELVIEW:
+ gsk_renderer_set_modelview (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_PROJECTION:
+ gsk_renderer_set_projection (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_MINIFICATION_FILTER:
+ gsk_renderer_set_scaling_filters (self, g_value_get_enum (value), priv->mag_filter);
+ break;
+
+ case PROP_MAGNIFICATION_FILTER:
+ gsk_renderer_set_scaling_filters (self, priv->min_filter, g_value_get_enum (value));
+ break;
+
+ case PROP_AUTO_CLEAR:
+ gsk_renderer_set_auto_clear (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_ROOT_NODE:
+ gsk_renderer_set_root_node (self, g_value_get_object (value));
+ break;
+
+ case PROP_SURFACE:
+ gsk_renderer_set_surface (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_WINDOW:
+ gsk_renderer_set_window (self, g_value_get_object (value));
+ break;
+
+ case PROP_DISPLAY:
+ priv->display = g_value_dup_object (value);
+ break;
+
+ case PROP_USE_ALPHA:
+ gsk_renderer_set_use_alpha (self, g_value_get_boolean (value));
+ break;
+ }
+}
+
+static void
+gsk_renderer_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GskRenderer *self = GSK_RENDERER (gobject);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_VIEWPORT:
+ g_value_set_boxed (value, &priv->viewport);
+ break;
+
+ case PROP_MODELVIEW:
+ g_value_set_boxed (value, &priv->modelview);
+ break;
+
+ case PROP_PROJECTION:
+ g_value_set_boxed (value, &priv->projection);
+ break;
+
+ case PROP_MINIFICATION_FILTER:
+ g_value_set_enum (value, priv->min_filter);
+ break;
+
+ case PROP_MAGNIFICATION_FILTER:
+ g_value_set_enum (value, priv->mag_filter);
+ break;
+
+ case PROP_AUTO_CLEAR:
+ g_value_set_boolean (value, priv->auto_clear);
+ break;
+
+ case PROP_ROOT_NODE:
+ g_value_set_object (value, priv->root_node);
+ break;
+
+ case PROP_SURFACE:
+ g_value_set_boxed (value, priv->surface);
+ break;
+
+ case PROP_DISPLAY:
+ g_value_set_object (value, priv->display);
+ break;
+
+ case PROP_WINDOW:
+ g_value_set_object (value, priv->window);
+ break;
+
+ case PROP_USE_ALPHA:
+ g_value_set_boolean (value, priv->use_alpha);
+ break;
+ }
+}
+
+static void
+gsk_renderer_constructed (GObject *gobject)
+{
+ GskRenderer *self = GSK_RENDERER (gobject);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ if (priv->display == NULL)
+ {
+ GdkDisplayManager *manager = gdk_display_manager_get ();
+
+ priv->display = gdk_display_manager_get_default_display (manager);
+ g_assert (priv->display != NULL);
+ }
+
+ G_OBJECT_CLASS (gsk_renderer_parent_class)->constructed (gobject);
+}
+
+static void
+gsk_renderer_class_init (GskRendererClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ klass->realize = gsk_renderer_real_realize;
+ klass->unrealize = gsk_renderer_real_unrealize;
+ klass->resize_viewport = gsk_renderer_real_resize_viewport;
+ klass->update = gsk_renderer_real_update;
+ klass->validate_tree = gsk_renderer_real_validate_tree;
+ klass->render = gsk_renderer_real_render;
+
+ gobject_class->constructed = gsk_renderer_constructed;
+ gobject_class->set_property = gsk_renderer_set_property;
+ gobject_class->get_property = gsk_renderer_get_property;
+ gobject_class->dispose = gsk_renderer_dispose;
+
+ /**
+ * GskRenderer:viewport:
+ *
+ * The visible area used by the #GskRenderer to render its contents.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_VIEWPORT] =
+ g_param_spec_boxed ("viewport",
+ "Viewport",
+ "The visible area used by the renderer",
+ GRAPHENE_TYPE_RECT,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:modelview:
+ *
+ * The initial modelview matrix used by the #GskRenderer.
+ *
+ * If set to %NULL, the identity matrix:
+ *
+ * |[