summaryrefslogtreecommitdiff
path: root/gsk
diff options
context:
space:
mode:
Diffstat (limited to 'gsk')
-rw-r--r--gsk/Makefile.am146
-rw-r--r--gsk/gsk.h33
-rw-r--r--gsk/gskcairorenderer.c195
-rw-r--r--gsk/gskcairorendererprivate.h26
-rw-r--r--gsk/gskdebug.c58
-rw-r--r--gsk/gskdebugprivate.h43
-rw-r--r--gsk/gskenums.h46
-rw-r--r--gsk/gskenumtypes.c.template38
-rw-r--r--gsk/gskenumtypes.h.template24
-rw-r--r--gsk/gskglrenderer.c1092
-rw-r--r--gsk/gskglrendererprivate.h23
-rw-r--r--gsk/gskprivate.c16
-rw-r--r--gsk/gskprivate.h12
-rw-r--r--gsk/gskrenderer.c1377
-rw-r--r--gsk/gskrenderer.h113
-rw-r--r--gsk/gskrendererprivate.h62
-rw-r--r--gsk/gskrendernode.c1246
-rw-r--r--gsk/gskrendernode.h117
-rw-r--r--gsk/gskrendernodeiter.c254
-rw-r--r--gsk/gskrendernodeiter.h45
-rw-r--r--gsk/gskrendernodeprivate.h124
-rw-r--r--gsk/gsktypes.h29
-rw-r--r--gsk/resources/glsl/base-renderer-fragment.glsl13
-rw-r--r--gsk/resources/glsl/base-renderer-vertex.glsl16
24 files changed, 5112 insertions, 36 deletions
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 =
-
-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)
-
-lib_LTLIBRARIES += libgsk-3.la
+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
+
+all_sources = \
+ $(gsk_public_source_h) \
+ $(gsk_private_source_h) \
+ $(gsk_built_source_h) \
+ $(gsk_private_source_c) \
+ $(gsk_source_c)
+
+BUILT_SOURCES += $(gsk_built_source_h) $(gsk_built_source_c) gsk.resources.xml
+
+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 "<?xml version='1.0' encoding='UTF-8'?>" > $@; \
+ echo "<gresources>" >> $@; \
+ echo " <gresource prefix='/org/gtk/libgsk'>" >> $@; \
+ for f in $(top_srcdir)/gsk/resources/glsl/*; do \
+ n=`basename $$f`; \
+ echo " <file alias='glsl/$$n'>resources/glsl/$$n</file>" >> $@; \
+ done; \
+ echo " </gresource>" >> $@; \
+ echo "</gresources>" >> $@
+
+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
-gskincludedir = $(includedir)/gtk-3.0/gsk
-gskinclude_HEADERS = $(gsk_public_source_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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_H__
+#define __GSK_H__
+
+#define __GSK_H_INSIDE__
+
+#include <gsk/gskenums.h>
+#include <gsk/gskrenderer.h>
+#include <gsk/gskrendernode.h>
+#include <gsk/gskrendernodeiter.h>
+
+#include <gsk/gsktypes.h>
+#include <gsk/gskenumtypes.h>
+
+#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 <cairo.h>
+#include <gsk/gskrenderer.h>
+
+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 <glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_ENUMS_H__
+#define __GSK_ENUMS_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only <gsk/gsk.h> 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 <gsk.h>
+
+/*** 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 <gdk/gdk.h>
+
+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 <epoxy/gl.h>
+
+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 <gsk/gskrenderer.h>
+
+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 (&register_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 <glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * 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 <graphene-gobject.h>
+#include <cairo-gobject.h>
+#include <gdk/gdk.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/x11/gdkx.h>
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/wayland/gdkwayland.h>
+#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:
+ *
+ * |[<!-- language="plain"
+ * | 1.0, 0.0, 0.0, 0.0 |
+ * | 0.0, 1.0, 0.0, 0.0 |
+ * | 0.0, 0.0, 1.0, 0.0 |
+ * | 0.0, 0.0, 0.0, 1.0 |
+ * ]|
+ *
+ * Is used instead.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_MODELVIEW] =
+ g_param_spec_boxed ("modelview",
+ "Modelview",
+ "The modelview matrix used by the renderer",
+ GRAPHENE_TYPE_MATRIX,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:projection:
+ *
+ * The projection matrix used by the #GskRenderer.
+ *
+ * If set to %NULL, the identity matrix:
+ *
+ * |[<!-- language="plain"
+ * | 1.0, 0.0, 0.0, 0.0 |
+ * | 0.0, 1.0, 0.0, 0.0 |
+ * | 0.0, 0.0, 1.0, 0.0 |
+ * | 0.0, 0.0, 0.0, 1.0 |
+ * ]|
+ *
+ * Is used instead.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_PROJECTION] =
+ g_param_spec_boxed ("projection",
+ "Projection",
+ "The projection matrix used by the renderer",
+ GRAPHENE_TYPE_MATRIX,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:minification-filter:
+ *
+ * The filter to be used when scaling textures down.
+ *
+ * See also: gsk_renderer_set_scaling_filters()
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_MINIFICATION_FILTER] =
+ g_param_spec_enum ("minification-filter",
+ "Minification Filter",
+ "The minification filter used by the renderer for texture targets",
+ GSK_TYPE_SCALING_FILTER,
+ GSK_SCALING_FILTER_LINEAR,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:magnification-filter:
+ *
+ * The filter to be used when scaling textures up.
+ *
+ * See also: gsk_renderer_set_scaling_filters()
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_MAGNIFICATION_FILTER] =
+ g_param_spec_enum ("magnification-filter",
+ "Magnification Filter",
+ "The magnification filter used by the renderer for texture targets",
+ GSK_TYPE_SCALING_FILTER,
+ GSK_SCALING_FILTER_LINEAR,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:auto-clear:
+ *
+ * Automatically clear the rendering surface when rendering.
+ *
+ * Setting this property to %FALSE assumes that the owner of the
+ * rendering surface will have cleared it prior to calling
+ * gsk_renderer_render().
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_AUTO_CLEAR] =
+ g_param_spec_boolean ("auto-clear",
+ "Auto Clear",
+ "Automatically clears the rendering target on render",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:root-node:
+ *
+ * The root #GskRenderNode of the scene to be rendered.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_ROOT_NODE] =
+ g_param_spec_object ("root-node",
+ "Root Node",
+ "The root render node to render",
+ GSK_TYPE_RENDER_NODE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:surface:
+ *
+ * The target rendering surface.
+ *
+ * See also: #GskRenderer:window.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_SURFACE] =
+ g_param_spec_boxed ("surface",
+ "Surface",
+ "The Cairo surface used to render to",
+ CAIRO_GOBJECT_TYPE_SURFACE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:display:
+ *
+ * The #GdkDisplay used by the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_DISPLAY] =
+ g_param_spec_object ("display",
+ "Display",
+ "The GdkDisplay object used by the renderer",
+ GDK_TYPE_DISPLAY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GskRenderer:window:
+ *
+ * The #GdkWindow used to create a target surface, if #GskRenderer:surface
+ * is not explicitly set.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_WINDOW] =
+ g_param_spec_object ("window",
+ "Window",
+ "The GdkWindow associated to the renderer",
+ GDK_TYPE_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GskRenderer:use-alpha:
+ *
+ * Whether the #GskRenderer should use the alpha channel when rendering.
+ *
+ * Since: 3.22
+ */
+ gsk_renderer_properties[PROP_USE_ALPHA] =
+ g_param_spec_boolean ("use-alpha",
+ "Use Alpha",
+ "Whether the renderer should use the alpha channel when rendering",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, gsk_renderer_properties);
+}
+
+static void
+gsk_renderer_init (GskRenderer *self)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ graphene_matrix_init_identity (&priv->modelview);
+ graphene_matrix_init_identity (&priv->projection);
+
+ priv->auto_clear = TRUE;
+
+ priv->min_filter = GSK_SCALING_FILTER_LINEAR;
+ priv->mag_filter = GSK_SCALING_FILTER_LINEAR;
+}
+
+/**
+ * gsk_renderer_set_viewport:
+ * @renderer: a #GskRenderer
+ * @viewport: (nullable): the viewport rectangle used by the @renderer
+ *
+ * Sets the visible rectangle to be used as the viewport for
+ * the rendering.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_viewport (GskRenderer *renderer,
+ const graphene_rect_t *viewport)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (viewport == NULL)
+ {
+ graphene_rect_init (&priv->viewport, 0.f, 0.f, 0.f, 0.f);
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_VIEWPORT]);
+ return;
+ }
+
+ if (graphene_rect_equal (viewport, &priv->viewport))
+ return;
+
+ graphene_rect_init_from_rect (&priv->viewport, viewport);
+ priv->needs_viewport_resize = TRUE;
+ priv->needs_modelview_update = TRUE;
+ priv->needs_projection_update = TRUE;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_VIEWPORT]);
+}
+
+/**
+ * gsk_renderer_get_viewport:
+ * @renderer: a #GskRenderer
+ * @viewport: (out caller-allocates): return location for the viewport rectangle
+ *
+ * Retrieves the viewport of the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_get_viewport (GskRenderer *renderer,
+ graphene_rect_t *viewport)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (viewport != NULL);
+
+ graphene_rect_init_from_rect (viewport, &priv->viewport);
+}
+
+/**
+ * gsk_renderer_set_modelview:
+ * @renderer: a #GskRenderer
+ * @modelview: the modelview matrix used by the @renderer
+ *
+ * Sets the initial modelview matrix used by the #GskRenderer.
+ *
+ * A modelview matrix defines the initial transformation imposed
+ * on the scene graph.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_modelview (GskRenderer *renderer,
+ const graphene_matrix_t *modelview)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (modelview == NULL)
+ graphene_matrix_init_identity (&priv->modelview);
+ else
+ graphene_matrix_init_from_matrix (&priv->modelview, modelview);
+
+ priv->needs_modelview_update = TRUE;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_MODELVIEW]);
+}
+
+/**
+ * gsk_renderer_get_modelview:
+ * @renderer: a #GskRenderer
+ * @modelview: (out caller-allocates): return location for the modelview matrix
+ *
+ * Retrieves the modelview matrix used by the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_get_modelview (GskRenderer *renderer,
+ graphene_matrix_t *modelview)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (modelview != NULL);
+
+ graphene_matrix_init_from_matrix (modelview, &priv->modelview);
+}
+
+/**
+ * gsk_renderer_set_projection:
+ * @renderer: a #GskRenderer
+ * @projection: the projection matrix used by the @renderer
+ *
+ * Sets the projection matrix used by the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_projection (GskRenderer *renderer,
+ const graphene_matrix_t *projection)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (projection == NULL)
+ graphene_matrix_init_identity (&priv->projection);
+ else
+ graphene_matrix_init_from_matrix (&priv->projection, projection);
+
+ priv->needs_projection_update = TRUE;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_PROJECTION]);
+}
+
+/**
+ * gsk_renderer_get_projection:
+ * @renderer: a #GskRenderer
+ * @projection: (out caller-allocates): return location for the projection matrix
+ *
+ * Retrieves the projection matrix used by the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_get_projection (GskRenderer *renderer,
+ graphene_matrix_t *projection)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (projection != NULL);
+
+ graphene_matrix_init_from_matrix (projection, &priv->projection);
+}
+
+static void
+gsk_renderer_invalidate_tree (GskRenderNode *node,
+ gpointer data)
+{
+ GskRenderer *self = data;
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
+
+ GSK_NOTE (RENDERER, g_print ("Invalidating tree.\n"));
+
+ /* Since the scene graph has changed in some way, we need to re-validate it. */
+ priv->needs_tree_validation = TRUE;
+}
+
+/**
+ * gsk_renderer_set_root_node:
+ * @renderer: a #GskRenderer
+ * @root: (nullable): a #GskRenderNode
+ *
+ * Sets the root node of the scene graph to be rendered.
+ *
+ * The #GskRenderer will acquire a reference on @root.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_root_node (GskRenderer *renderer,
+ GskRenderNode *root)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+ GskRenderNode *old_root;
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (GSK_IS_RENDER_NODE (root));
+
+ old_root = priv->root_node != NULL ? g_object_ref (priv->root_node) : NULL;
+
+ if (g_set_object (&priv->root_node, root))
+ {
+ /* We need to unset the invalidate function on the old instance */
+ if (old_root != NULL)
+ {
+ gsk_render_node_set_invalidate_func (old_root, NULL, NULL, NULL);
+ g_object_unref (old_root);
+ }
+
+ if (priv->root_node != NULL)
+ gsk_render_node_set_invalidate_func (priv->root_node,
+ gsk_renderer_invalidate_tree,
+ renderer,
+ NULL);
+
+ /* If we don't have a root node, there's really no point in validating a
+ * tree that it's not going to be drawn
+ */
+ priv->needs_tree_validation = priv->root_node != NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_ROOT_NODE]);
+ }
+}
+
+/**
+ * gsk_renderer_set_scaling_filters:
+ * @renderer: a #GskRenderer
+ * @min_filter: the minification scaling filter
+ * @mag_filter: the magnification scaling filter
+ *
+ * Sets the scaling filters to be applied when scaling textures
+ * up and down.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_scaling_filters (GskRenderer *renderer,
+ GskScalingFilter min_filter,
+ GskScalingFilter mag_filter)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+ GObject *gobject;
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ gobject = G_OBJECT (renderer);
+
+ g_object_freeze_notify (gobject);
+
+ if (priv->min_filter != min_filter)
+ {
+ priv->min_filter = min_filter;
+ g_object_notify_by_pspec (gobject, gsk_renderer_properties[PROP_MINIFICATION_FILTER]);
+ }
+
+ if (priv->mag_filter != mag_filter)
+ {
+ priv->mag_filter = mag_filter;
+ g_object_notify_by_pspec (gobject, gsk_renderer_properties[PROP_MAGNIFICATION_FILTER]);
+ }
+
+ g_object_thaw_notify (gobject);
+}
+
+/**
+ * gsk_renderer_get_scaling_filters:
+ * @renderer: a #GskRenderer
+ * @min_filter: (out) (nullable): return location for the minification filter
+ * @mag_filter: (out) (nullable): return location for the magnification filter
+ *
+ * Retrieves the minification and magnification filters used by the #GskRenderer.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_get_scaling_filters (GskRenderer *renderer,
+ GskScalingFilter *min_filter,
+ GskScalingFilter *mag_filter)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (min_filter != NULL)
+ *min_filter = priv->min_filter;
+
+ if (mag_filter != NULL)
+ *mag_filter = priv->mag_filter;
+}
+
+/**
+ * gsk_renderer_set_surface:
+ * @renderer: a #GskRenderer
+ * @surface: (nullable): a Cairo surface
+ *
+ * Sets the #cairo_surface_t used as the target rendering surface.
+ *
+ * This function will acquire a reference to @surface.
+ *
+ * See also: gsk_renderer_set_window()
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_surface (GskRenderer *renderer,
+ cairo_surface_t *surface)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (priv->surface == surface)
+ return;
+
+ g_clear_pointer (&priv->surface, cairo_surface_destroy);
+
+ if (surface != NULL)
+ priv->surface = cairo_surface_reference (surface);
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_SURFACE]);
+}
+
+/**
+ * gsk_renderer_get_surface:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieve the target rendering surface used by @renderer.
+ *
+ * If you did not use gsk_renderer_set_surface(), a compatible surface
+ * will be created by using the #GdkWindow passed to gsk_renderer_set_window().
+ *
+ * Returns: (transfer none) (nullable): a Cairo surface
+ *
+ * Since: 3.22
+ */
+cairo_surface_t *
+gsk_renderer_get_surface (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+
+ if (priv->surface != NULL)
+ return priv->surface;
+
+ if (priv->window != NULL)
+ {
+ int scale = gdk_window_get_scale_factor (priv->window);
+ int width = gdk_window_get_width (priv->window);
+ int height = gdk_window_get_height (priv->window);
+ cairo_content_t content;
+
+ if (priv->use_alpha)
+ content = CAIRO_CONTENT_COLOR_ALPHA;
+ else
+ content = CAIRO_CONTENT_COLOR;
+
+ GSK_NOTE (RENDERER, g_print ("Creating surface from window [%p] (w:%d, h:%d, s:%d, a:%s)\n",
+ priv->window,
+ width, height, scale,
+ priv->use_alpha ? "y" : "n"));
+
+ priv->surface = gdk_window_create_similar_surface (priv->window,
+ content,
+ width, height);
+ }
+
+ return priv->surface;
+}
+
+void
+gsk_renderer_set_draw_context (GskRenderer *renderer,
+ cairo_t *cr)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (priv->draw_context == cr)
+ return;
+
+ g_clear_pointer (&priv->draw_context, cairo_destroy);
+ priv->draw_context = cr != NULL ? cairo_reference (cr) : NULL;
+}
+
+cairo_t *
+gsk_renderer_get_draw_context (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+
+ if (priv->draw_context != NULL)
+ return priv->draw_context;
+
+ return cairo_create (gsk_renderer_get_surface (renderer));
+}
+
+/**
+ * gsk_renderer_get_display:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieves the #GdkDisplay used when creating the #GskRenderer.
+ *
+ * Returns: (transfer none): a #GdkDisplay
+ *
+ * Since: 3.22
+ */
+GdkDisplay *
+gsk_renderer_get_display (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+
+ return priv->display;
+}
+
+/**
+ * gsk_renderer_get_root_node:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieves the root node of the scene graph.
+ *
+ * Returns: (transfer none) (nullable): a #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_renderer_get_root_node (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+
+ return priv->root_node;
+}
+
+/*< private >
+ * gsk_renderer_is_realized:
+ * @renderer: a #GskRenderer
+ *
+ * Checks whether the @renderer is realized or not.
+ *
+ * Returns: %TRUE if the #GskRenderer was realized, and %FALSE otherwise
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_renderer_is_realized (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
+
+ return priv->is_realized;
+}
+
+/**
+ * gsk_renderer_set_window:
+ * @renderer: a #GskRenderer
+ * @window: (nullable): a #GdkWindow
+ *
+ * Sets the #GdkWindow used to create the target rendering surface.
+ *
+ * See also: gsk_renderer_set_surface()
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_window (GskRenderer *renderer,
+ GdkWindow *window)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (window == NULL || GDK_IS_WINDOW (window));
+ g_return_if_fail (!priv->is_realized);
+
+ if (g_set_object (&priv->window, window))
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_WINDOW]);
+}
+
+/**
+ * gsk_renderer_get_window:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieves the #GdkWindow set with gsk_renderer_set_window().
+ *
+ * Returns: (transfer none) (nullable): a #GdkWindow
+ *
+ * Since: 3.22
+ */
+GdkWindow *
+gsk_renderer_get_window (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
+
+ return priv->window;
+}
+
+/**
+ * gsk_renderer_realize:
+ * @renderer: a #GskRenderer
+ *
+ * Creates the resources needed by the @renderer to render the scene
+ * graph.
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_renderer_realize (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
+
+ if (priv->is_realized)
+ return TRUE;
+
+ if (priv->window == NULL && priv->surface == NULL)
+ {
+ g_critical ("No rendering surface has been set.");
+ return FALSE;
+ }
+
+ priv->is_realized = GSK_RENDERER_GET_CLASS (renderer)->realize (renderer);
+
+ return priv->is_realized;
+}
+
+/**
+ * gsk_renderer_unrealize:
+ * @renderer: a #GskRenderer
+ *
+ * Releases all the resources created by gsk_renderer_realize().
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_unrealize (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ if (!priv->is_realized)
+ return;
+
+ GSK_RENDERER_GET_CLASS (renderer)->unrealize (renderer);
+
+ priv->is_realized = FALSE;
+}
+
+/*< private >
+ * gsk_renderer_maybe_resize_viewport:
+ * @renderer: a #GskRenderer
+ *
+ * Optionally resize the viewport of @renderer.
+ *
+ * This function should be called by gsk_renderer_render().
+ *
+ * This function may call @GskRendererClass.resize_viewport().
+ */
+void
+gsk_renderer_maybe_resize_viewport (GskRenderer *renderer)
+{
+ GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ if (priv->needs_viewport_resize)
+ {
+ renderer_class->resize_viewport (renderer, &priv->viewport);
+ priv->needs_viewport_resize = FALSE;
+
+ GSK_NOTE (RENDERER, g_print ("Viewport size: %g x %g\n",
+ priv->viewport.size.width,
+ priv->viewport.size.height));
+
+ /* If the target surface has been created from a window, we need
+ * to clear it, so that it gets recreated with the right size
+ */
+ if (priv->window != NULL && priv->surface != NULL)
+ g_clear_pointer (&priv->surface, cairo_surface_destroy);
+ }
+}
+
+/*< private >
+ * gsk_renderer_maybe_update:
+ * @renderer: a #GskRenderer
+ *
+ * Optionally recomputes the modelview-projection matrix used by
+ * the @renderer.
+ *
+ * This function should be called by gsk_renderer_render().
+ *
+ * This function may call @GskRendererClass.update().
+ */
+void
+gsk_renderer_maybe_update (GskRenderer *renderer)
+{
+ GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ if (priv->needs_modelview_update || priv->needs_projection_update)
+ {
+ renderer_class->update (renderer, &priv->modelview, &priv->projection);
+ priv->needs_modelview_update = FALSE;
+ priv->needs_projection_update = FALSE;
+ }
+}
+
+/*< private >
+ * gsk_renderer_maybe_validate_tree:
+ * @renderer: a #GskRenderer
+ *
+ * Optionally validates the #GskRenderNode scene graph, and uses it
+ * to generate more efficient intermediate representations depending
+ * on the type of @renderer.
+ *
+ * This function should be called by gsk_renderer_render().
+ *
+ * This function may call @GskRendererClas.validate_tree().
+ */
+void
+gsk_renderer_maybe_validate_tree (GskRenderer *renderer)
+{
+ GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ if (priv->root_node == NULL)
+ return;
+
+ /* Ensure that the render nodes are valid; this will change the
+ * needs_tree_validation flag on the renderer, if needed
+ */
+ gsk_render_node_validate (priv->root_node);
+
+ if (priv->needs_tree_validation)
+ {
+ /* Ensure that the Renderer can update itself */
+ renderer_class->validate_tree (renderer, priv->root_node);
+ priv->needs_tree_validation = FALSE;
+ }
+}
+
+/*< private >
+ * gsk_renderer_maybe_clear:
+ * @renderer: a #GskRenderer
+ *
+ * Optionally calls @GskRendererClass.clear(), depending on the value
+ * of #GskRenderer:auto-clear.
+ *
+ * This function should be called by gsk_renderer_render().
+ */
+void
+gsk_renderer_maybe_clear (GskRenderer *renderer)
+{
+ GskRendererClass *renderer_class = GSK_RENDERER_GET_CLASS (renderer);
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ if (priv->auto_clear)
+ renderer_class->clear (renderer);
+}
+
+/**
+ * gsk_renderer_render:
+ * @renderer: a#GskRenderer
+ *
+ * Renders the scene graph associated to @renderer, using the
+ * given target surface.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_render (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (priv->is_realized);
+ g_return_if_fail (priv->root_node != NULL);
+
+ /* We need to update the viewport and the modelview, to allow renderers
+ * to update their clip region and/or frustum; this allows them to cull
+ * render nodes in the tree validation phase
+ */
+ gsk_renderer_maybe_resize_viewport (renderer);
+
+ gsk_renderer_maybe_update (renderer);
+
+ gsk_renderer_maybe_validate_tree (renderer);
+
+ /* Clear the output surface */
+ gsk_renderer_maybe_clear (renderer);
+
+ GSK_RENDERER_GET_CLASS (renderer)->render (renderer);
+}
+
+/**
+ * gsk_renderer_set_auto_clear:
+ * @renderer: a #GskRenderer
+ * @clear: whether the target surface should be cleared prior
+ * to rendering to it
+ *
+ * Sets whether the target surface used by @renderer should be cleared
+ * before rendering.
+ *
+ * If you pass a custom surface to gsk_renderer_set_surface(), you may
+ * want to manage the clearing manually; this is possible by passing
+ * %FALSE to this function.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_auto_clear (GskRenderer *renderer,
+ gboolean clear)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+
+ clear = !!clear;
+
+ if (clear == priv->auto_clear)
+ return;
+
+ priv->auto_clear = clear;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_AUTO_CLEAR]);
+}
+
+/**
+ * gsk_renderer_get_auto_clear:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieves the value set using gsk_renderer_set_auto_clear().
+ *
+ * Returns: %TRUE if the target surface should be cleared prior to rendering
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_renderer_get_auto_clear (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
+
+ return priv->auto_clear;
+}
+
+/**
+ * gsk_renderer_set_use_alpha:
+ * @renderer: a #GskRenderer
+ * @use_alpha: whether to use the alpha channel of the target surface or not
+ *
+ * Sets whether the @renderer should use the alpha channel of the target surface
+ * or not.
+ *
+ * Since: 3.22
+ */
+void
+gsk_renderer_set_use_alpha (GskRenderer *renderer,
+ gboolean use_alpha)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (GSK_IS_RENDERER (renderer));
+ g_return_if_fail (!priv->is_realized);
+
+ use_alpha = !!use_alpha;
+
+ if (use_alpha == priv->use_alpha)
+ return;
+
+ priv->use_alpha = use_alpha;
+
+ g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_USE_ALPHA]);
+}
+
+/**
+ * gsk_renderer_get_use_alpha:
+ * @renderer: a #GskRenderer
+ *
+ * Retrieves the value set using gsk_renderer_set_use_alpha().
+ *
+ * Returns: %TRUE if the target surface should use an alpha channel
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_renderer_get_use_alpha (GskRenderer *renderer)
+{
+ GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
+
+ return priv->use_alpha;
+}
+
+/**
+ * gsk_renderer_get_for_display:
+ * @display: a #GdkDisplay
+ *
+ * Creates an appropriate #GskRenderer instance for the given @display.
+ *
+ * Returns: (transfer full): a #GskRenderer
+ *
+ * Since: 3.22
+ */
+GskRenderer *
+gsk_renderer_get_for_display (GdkDisplay *display)
+{
+ static const char *use_software;
+
+ GType renderer_type = G_TYPE_INVALID;
+
+ if (use_software == NULL)
+ {
+ use_software = g_getenv ("GSK_USE_SOFTWARE");
+ if (use_software == NULL)
+ use_software = "0";
+ }
+
+ if (use_software[0] != '0')
+ {
+ renderer_type = GSK_TYPE_CAIRO_RENDERER;
+ goto out;
+ }
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (display))
+ renderer_type = GSK_TYPE_GL_RENDERER;
+ else
+#endif
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (display))
+ renderer_type = GSK_TYPE_GL_RENDERER;
+ else
+#endif
+ renderer_type = GSK_TYPE_CAIRO_RENDERER;
+
+ GSK_NOTE (RENDERER, g_print ("Creating renderer of type '%s' for display '%s'\n",
+ g_type_name (renderer_type),
+ G_OBJECT_TYPE_NAME (display)));
+
+ g_assert (renderer_type != G_TYPE_INVALID);
+
+out:
+ return g_object_new (renderer_type, "display", display, NULL);
+}
diff --git a/gsk/gskrenderer.h b/gsk/gskrenderer.h
new file mode 100644
index 0000000000..a18ab9b19a
--- /dev/null
+++ b/gsk/gskrenderer.h
@@ -0,0 +1,113 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_RENDERER_H__
+#define __GSK_RENDERER_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only <gsk/gsk.h> can be included directly."
+#endif
+
+#include <gsk/gsktypes.h>
+#include <gsk/gskrendernode.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_RENDERER (gsk_renderer_get_type ())
+
+#define GSK_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_RENDERER, GskRenderer))
+#define GSK_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_RENDERER))
+
+typedef struct _GskRenderer GskRenderer;
+typedef struct _GskRendererClass GskRendererClass;
+
+GDK_AVAILABLE_IN_3_22
+GType gsk_renderer_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_22
+GskRenderer * gsk_renderer_get_for_display (GdkDisplay *display);
+
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_viewport (GskRenderer *renderer,
+ const graphene_rect_t *viewport);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_get_viewport (GskRenderer *renderer,
+ graphene_rect_t *viewport);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_projection (GskRenderer *renderer,
+ const graphene_matrix_t *projection);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_get_projection (GskRenderer *renderer,
+ graphene_matrix_t *projection);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_modelview (GskRenderer *renderer,
+ const graphene_matrix_t *modelview);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_get_modelview (GskRenderer *renderer,
+ graphene_matrix_t *modelview);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_scaling_filters (GskRenderer *renderer,
+ GskScalingFilter min_filter,
+ GskScalingFilter mag_filter);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_get_scaling_filters (GskRenderer *renderer,
+ GskScalingFilter *min_filter,
+ GskScalingFilter *mag_filter);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_auto_clear (GskRenderer *renderer,
+ gboolean clear);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_renderer_get_auto_clear (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_root_node (GskRenderer *renderer,
+ GskRenderNode *root);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_renderer_get_root_node (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_surface (GskRenderer *renderer,
+ cairo_surface_t *surface);
+GDK_AVAILABLE_IN_3_22
+cairo_surface_t * gsk_renderer_get_surface (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_window (GskRenderer *renderer,
+ GdkWindow *window);
+GDK_AVAILABLE_IN_3_22
+GdkWindow * gsk_renderer_get_window (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_draw_context (GskRenderer *renderer,
+ cairo_t *cr);
+GDK_AVAILABLE_IN_3_22
+cairo_t * gsk_renderer_get_draw_context (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+GdkDisplay * gsk_renderer_get_display (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_set_use_alpha (GskRenderer *renderer,
+ gboolean use_alpha);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_renderer_get_use_alpha (GskRenderer *renderer);
+
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_renderer_realize (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_unrealize (GskRenderer *renderer);
+GDK_AVAILABLE_IN_3_22
+void gsk_renderer_render (GskRenderer *renderer);
+
+G_END_DECLS
+
+#endif /* __GSK_RENDERER_H__ */
diff --git a/gsk/gskrendererprivate.h b/gsk/gskrendererprivate.h
new file mode 100644
index 0000000000..404502c5be
--- /dev/null
+++ b/gsk/gskrendererprivate.h
@@ -0,0 +1,62 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_RENDERER_PRIVATE_H__
+#define __GSK_RENDERER_PRIVATE_H__
+
+#include "gskrenderer.h"
+
+G_BEGIN_DECLS
+
+#define GSK_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_RENDERER, GskRendererClass))
+#define GSK_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_RENDERER))
+#define GSK_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDERER, GskRendererClass))
+
+struct _GskRenderer
+{
+ GObject parent_instance;
+};
+
+struct _GskRendererClass
+{
+ GObjectClass parent_class;
+
+ gboolean (* realize) (GskRenderer *renderer);
+ void (* unrealize) (GskRenderer *renderer);
+
+ void (* resize_viewport) (GskRenderer *renderer,
+ const graphene_rect_t *viewport);
+ void (* update) (GskRenderer *renderer,
+ const graphene_matrix_t *modelview,
+ const graphene_matrix_t *projection);
+ void (* validate_tree) (GskRenderer *renderer,
+ GskRenderNode *root);
+ void (* clear) (GskRenderer *renderer);
+ void (* render) (GskRenderer *renderer);
+};
+
+gboolean gsk_renderer_is_realized (GskRenderer *renderer);
+
+void gsk_renderer_maybe_resize_viewport (GskRenderer *renderer);
+void gsk_renderer_maybe_update (GskRenderer *renderer);
+void gsk_renderer_maybe_validate_tree (GskRenderer *renderer);
+void gsk_renderer_maybe_clear (GskRenderer *renderer);
+
+G_END_DECLS
+
+#endif /* __GSK_RENDERER_PRIVATE_H__ */
diff --git a/gsk/gskrendernode.c b/gsk/gskrendernode.c
new file mode 100644
index 0000000000..092b96a2e3
--- /dev/null
+++ b/gsk/gskrendernode.c
@@ -0,0 +1,1246 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:GskRenderNode
+ * @title: GskRenderNode
+ * @Short_desc: Simple scene graph element
+ *
+ * TODO
+ */
+
+#include "config.h"
+
+#include "gskrendernodeprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskrendernodeiter.h"
+
+#include <graphene-gobject.h>
+
+G_DEFINE_TYPE (GskRenderNode, gsk_render_node, G_TYPE_OBJECT)
+
+static void
+gsk_render_node_dispose (GObject *gobject)
+{
+ GskRenderNode *self = GSK_RENDER_NODE (gobject);
+ GskRenderNodeIter iter;
+
+ gsk_render_node_set_invalidate_func (self, NULL, NULL, NULL);
+
+ gsk_render_node_iter_init (&iter, self);
+ while (gsk_render_node_iter_next (&iter, NULL))
+ gsk_render_node_iter_remove (&iter);
+
+ G_OBJECT_CLASS (gsk_render_node_parent_class)->dispose (gobject);
+}
+
+static void
+gsk_render_node_real_resize (GskRenderNode *node)
+{
+}
+
+static void
+gsk_render_node_class_init (GskRenderNodeClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = gsk_render_node_dispose;
+
+ klass->resize = gsk_render_node_real_resize;
+}
+
+static void
+gsk_render_node_init (GskRenderNode *self)
+{
+ graphene_rect_init_from_rect (&self->bounds, graphene_rect_zero ());
+
+ graphene_matrix_init_identity (&self->transform);
+ graphene_matrix_init_identity (&self->child_transform);
+
+ self->opacity = 1.0;
+}
+
+/**
+ * gsk_render_node_new:
+ *
+ * Creates a new #GskRenderNode, to be used with #GskRenderer.
+ *
+ * Returns: (transfer full): the newly created #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_new (void)
+{
+ return g_object_new (GSK_TYPE_RENDER_NODE, NULL);
+}
+
+/**
+ * gsk_render_node_get_parent:
+ * @node: a #GskRenderNode
+ *
+ * Returns the parent of the @node.
+ *
+ * Returns: (transfer none): the parent of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_get_parent (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->parent;
+}
+
+/**
+ * gsk_render_node_get_first_child:
+ * @node: a #GskRenderNode
+ *
+ * Returns the first child of @node.
+ *
+ * Returns: (transfer none): the first child of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_get_first_child (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->first_child;
+}
+
+/**
+ * gsk_render_node_get_last_child:
+ *
+ * Returns the last child of @node.
+ *
+ * Returns: (transfer none): the last child of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_get_last_child (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->last_child;
+}
+
+/**
+ * gsk_render_node_get_next_sibling:
+ *
+ * Returns the next sibling of @node.
+ *
+ * Returns: (transfer none): the next sibling of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_get_next_sibling (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->next_sibling;
+}
+
+/**
+ * gsk_render_node_get_previous_sibling:
+ *
+ * Returns the previous sibling of @node.
+ *
+ * Returns: (transfer none): the previous sibling of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_get_previous_sibling (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->prev_sibling;
+}
+
+typedef void (* InsertChildFunc) (GskRenderNode *node,
+ GskRenderNode *child,
+ gpointer user_data);
+
+static void
+gsk_render_node_insert_child_internal (GskRenderNode *node,
+ GskRenderNode *child,
+ InsertChildFunc insert_func,
+ gpointer insert_func_data)
+{
+ if (node == child)
+ {
+ g_critical ("The render node of type '%s' cannot be added to itself.",
+ G_OBJECT_TYPE_NAME (node));
+ return;
+ }
+
+ if (child->parent != NULL)
+ {
+ g_critical ("The render node of type '%s' already has a parent of type '%s'; "
+ "render nodes cannot be added to multiple parents.",
+ G_OBJECT_TYPE_NAME (child),
+ G_OBJECT_TYPE_NAME (node));
+ return;
+ }
+
+ insert_func (node, child, insert_func_data);
+
+ g_object_ref (child);
+
+ child->parent = node;
+ child->age = 0;
+ child->needs_world_matrix_update = TRUE;
+
+ node->n_children += 1;
+ node->age += 1;
+ node->needs_world_matrix_update = TRUE;
+
+ /* Transfer invalidated children to the current top-level */
+ if (child->invalidated_descendants != NULL)
+ {
+ if (node->parent == NULL)
+ node->invalidated_descendants = child->invalidated_descendants;
+ else
+ {
+ GskRenderNode *tmp = gsk_render_node_get_toplevel (node);
+
+ tmp->invalidated_descendants = child->invalidated_descendants;
+ }
+
+ child->invalidated_descendants = NULL;
+ }
+
+ if (child->prev_sibling == NULL)
+ node->first_child = child;
+ if (child->next_sibling == NULL)
+ node->last_child = child;
+}
+
+static void
+insert_child_at_pos (GskRenderNode *node,
+ GskRenderNode *child,
+ gpointer user_data)
+{
+ int pos = GPOINTER_TO_INT (user_data);
+
+ if (pos == 0)
+ {
+ GskRenderNode *tmp = node->first_child;
+
+ if (tmp != NULL)
+ tmp->prev_sibling = child;
+
+ child->prev_sibling = NULL;
+ child->next_sibling = tmp;
+
+ return;
+ }
+
+ if (pos < 0 || pos >= node->n_children)
+ {
+ GskRenderNode *tmp = node->last_child;
+
+ if (tmp != NULL)
+ tmp->next_sibling = child;
+
+ child->prev_sibling = tmp;
+ child->next_sibling = NULL;
+
+ return;
+ }
+
+ {
+ GskRenderNode *iter;
+ int i;
+
+ for (iter = node->first_child, i = 0;
+ iter != NULL;
+ iter = iter->next_sibling, i++)
+ {
+ if (i == pos)
+ {
+ GskRenderNode *tmp = iter->prev_sibling;
+
+ child->prev_sibling = tmp;
+ child->next_sibling = iter;
+
+ iter->prev_sibling = child;
+
+ if (tmp != NULL)
+ tmp->next_sibling = child;
+
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * gsk_render_node_insert_child_at_pos:
+ * @node: a #GskRenderNode
+ * @child: a #GskRenderNode
+ * @index_: the index in the list of children where @child should be inserted at
+ *
+ * Inserts @child into the list of children of @node, using the given @index_.
+ *
+ * If @index_ is 0, the @child will be prepended to the list of children.
+ *
+ * If @index_ is less than zero, or equal to the number of children, the @child
+ * will be appended to the list of children.
+ *
+ * This function acquires a reference on @child.
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_insert_child_at_pos (GskRenderNode *node,
+ GskRenderNode *child,
+ int index_)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node);
+
+ gsk_render_node_insert_child_internal (node, child,
+ insert_child_at_pos,
+ GINT_TO_POINTER (index_));
+
+ return node;
+}
+
+static void
+insert_child_before (GskRenderNode *node,
+ GskRenderNode *child,
+ gpointer user_data)
+{
+ GskRenderNode *sibling = user_data;
+
+ if (sibling == NULL)
+ sibling = node->first_child;
+
+ child->next_sibling = sibling;
+
+ if (sibling != NULL)
+ {
+ GskRenderNode *tmp = sibling->prev_sibling;
+
+ child->prev_sibling = tmp;
+
+ if (tmp != NULL)
+ tmp->next_sibling = child;
+
+ sibling->prev_sibling = child;
+ }
+ else
+ child->prev_sibling = NULL;
+}
+
+/**
+ * gsk_render_node_insert_child_before:
+ * @node: a #GskRenderNode
+ * @child: a #GskRenderNode
+ * @sibling: (nullable): a #GskRenderNode, or %NULL
+ *
+ * Inserts @child in the list of children of @node, before @sibling.
+ *
+ * If @sibling is %NULL, the @child will be inserted at the beginning of the
+ * list of children.
+ *
+ * This function acquires a reference of @child.
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_insert_child_before (GskRenderNode *node,
+ GskRenderNode *child,
+ GskRenderNode *sibling)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node);
+ g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node);
+
+ gsk_render_node_insert_child_internal (node, child, insert_child_before, sibling);
+
+ return node;
+}
+
+static void
+insert_child_after (GskRenderNode *node,
+ GskRenderNode *child,
+ gpointer user_data)
+{
+ GskRenderNode *sibling = user_data;
+
+ if (sibling == NULL)
+ sibling = node->last_child;
+
+ child->prev_sibling = sibling;
+
+ if (sibling != NULL)
+ {
+ GskRenderNode *tmp = sibling->next_sibling;
+
+ child->next_sibling = tmp;
+
+ if (tmp != NULL)
+ tmp->prev_sibling = child;
+
+ sibling->next_sibling = child;
+ }
+ else
+ child->next_sibling = NULL;
+}
+
+/**
+ * gsk_render_node_insert_child_after:
+ * @node: a #GskRenderNode
+ * @child: a #GskRenderNode
+ * @sibling: (nullable): a #GskRenderNode, or %NULL
+ *
+ * Inserts @child in the list of children of @node, after @sibling.
+ *
+ * If @sibling is %NULL, the @child will be inserted at the end of the list
+ * of children.
+ *
+ * This function acquires a reference of @child.
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_insert_child_after (GskRenderNode *node,
+ GskRenderNode *child,
+ GskRenderNode *sibling)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node);
+ g_return_val_if_fail (sibling == NULL || GSK_IS_RENDER_NODE (sibling), node);
+
+ if (sibling != NULL)
+ g_return_val_if_fail (sibling->parent == node, node);
+
+ gsk_render_node_insert_child_internal (node, child, insert_child_after, sibling);
+
+ return node;
+}
+
+typedef struct {
+ GskRenderNode *prev_sibling;
+ GskRenderNode *next_sibling;
+} InsertBetween;
+
+static void
+insert_child_between (GskRenderNode *node,
+ GskRenderNode *child,
+ gpointer data_)
+{
+ InsertBetween *data = data_;
+
+ child->prev_sibling = data->prev_sibling;
+ child->next_sibling = data->next_sibling;
+
+ if (data->prev_sibling != NULL)
+ data->prev_sibling->next_sibling = child;
+
+ if (data->next_sibling != NULL)
+ data->next_sibling->prev_sibling = child;
+}
+
+/**
+ * gsk_render_node_replace_child:
+ * @node: a #GskRenderNode
+ * @new_child: the #GskRenderNode to add
+ * @old_child: the #GskRenderNode to replace
+ *
+ * Replaces @old_child with @new_child in the list of children of @node.
+ *
+ * This function acquires a reference to @new_child, and releases a reference
+ * of @old_child.
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_replace_child (GskRenderNode *node,
+ GskRenderNode *new_child,
+ GskRenderNode *old_child)
+{
+ InsertBetween clos;
+
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (new_child), node);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (old_child), node);
+
+ g_return_val_if_fail (new_child->parent == NULL, node);
+ g_return_val_if_fail (old_child->parent == node, node);
+
+ clos.prev_sibling = old_child->prev_sibling;
+ clos.next_sibling = old_child->next_sibling;
+ gsk_render_node_remove_child (node, old_child);
+
+ gsk_render_node_insert_child_internal (node, new_child, insert_child_between, &clos);
+
+ return node;
+}
+
+/**
+ * gsk_render_node_remove_child:
+ * @node: a #GskRenderNode
+ * @child: a #GskRenderNode child of @node
+ *
+ * Removes @child from the list of children of @node.
+ *
+ * This function releases the reference acquired when adding @child to the
+ * list of children.
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ */
+GskRenderNode *
+gsk_render_node_remove_child (GskRenderNode *node,
+ GskRenderNode *child)
+{
+ GskRenderNode *prev_sibling, *next_sibling;
+
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (child), node);
+
+ if (child->parent != node)
+ {
+ g_critical ("The render node of type '%s' is not a child of the render node of type '%s'",
+ G_OBJECT_TYPE_NAME (child),
+ G_OBJECT_TYPE_NAME (node));
+ return node;
+ }
+
+ prev_sibling = child->prev_sibling;
+ next_sibling = child->next_sibling;
+
+ child->parent = NULL;
+ child->prev_sibling = NULL;
+ child->next_sibling = NULL;
+ child->age = 0;
+
+ if (prev_sibling)
+ prev_sibling->next_sibling = next_sibling;
+ if (next_sibling)
+ next_sibling->prev_sibling = prev_sibling;
+
+ node->age += 1;
+ node->n_children -= 1;
+
+ if (node->first_child == child)
+ node->first_child = next_sibling;
+ if (node->last_child == child)
+ node->last_child = prev_sibling;
+
+ g_object_unref (child);
+
+ return node;
+}
+
+/**
+ * gsk_render_node_remove_all_children:
+ * @node: a #GskRenderNode
+ *
+ * Removes all children of @node.
+ *
+ * See also: gsk_render_node_remove_child()
+ *
+ * Returns: (transfer none): the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+GskRenderNode *
+gsk_render_node_remove_all_children (GskRenderNode *node)
+{
+ GskRenderNodeIter iter;
+
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ if (node->n_children == 0)
+ return node;
+
+ gsk_render_node_iter_init (&iter, node);
+ while (gsk_render_node_iter_next (&iter, NULL))
+ gsk_render_node_iter_remove (&iter);
+
+ g_assert (node->n_children == 0);
+ g_assert (node->first_child == NULL);
+ g_assert (node->last_child == NULL);
+
+ return node;
+}
+
+/**
+ * gsk_render_node_get_n_children:
+ * @node: a #GskRenderNode
+ *
+ * Retrieves the number of direct children of @node.
+ *
+ * Returns: the number of children of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+guint
+gsk_render_node_get_n_children (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0);
+
+ return node->n_children;
+}
+
+/**
+ * gsk_render_node_set_bounds:
+ * @node: a #GskRenderNode
+ * @bounds: (nullable): the boundaries of @node
+ *
+ * Sets the boundaries of @node, which describe the geometry of the
+ * render node, and are used to clip the surface associated to it
+ * when rendering.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_bounds (GskRenderNode *node,
+ const graphene_rect_t *bounds)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ if (bounds == NULL)
+ graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
+ else
+ graphene_rect_init_from_rect (&node->bounds, bounds);
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS);
+}
+
+/**
+ * gsk_render_node_get_bounds:
+ * @node: a #GskRenderNode
+ * @bounds: (out caller-allocates): return location for the boundaries
+ *
+ * Retrieves the boundaries set using gsk_render_node_set_bounds().
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_get_bounds (GskRenderNode *node,
+ graphene_rect_t *bounds)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+ g_return_if_fail (bounds != NULL);
+
+ *bounds = node->bounds;
+}
+
+/**
+ * gsk_render_node_set_transform:
+ * @node: a #GskRenderNode
+ * @transform: (nullable): a transformation matrix
+ *
+ * Sets the transformation matrix used when rendering the @node.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_transform (GskRenderNode *node,
+ const graphene_matrix_t *transform)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ if (transform == NULL)
+ graphene_matrix_init_identity (&node->transform);
+ else
+ graphene_matrix_init_from_matrix (&node->transform, transform);
+
+ node->transform_set = !graphene_matrix_is_identity (&node->transform);
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM);
+}
+
+/**
+ * gsk_render_node_set_child_transform:
+ * @node: a #GskRenderNode
+ * @transform: (nullable): a transformation matrix
+ *
+ * Sets the transformation matrix used when rendering the children
+ * of @node.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_child_transform (GskRenderNode *node,
+ const graphene_matrix_t *transform)
+{
+ GskRenderNodeIter iter;
+ GskRenderNode *child;
+
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ if (transform == NULL)
+ graphene_matrix_init_identity (&node->child_transform);
+ else
+ graphene_matrix_init_from_matrix (&node->child_transform, transform);
+
+ node->child_transform_set = !graphene_matrix_is_identity (&node->child_transform);
+
+ /* We need to invalidate the world matrix for our children */
+ gsk_render_node_iter_init (&iter, node);
+ while (gsk_render_node_iter_next (&iter, &child))
+ gsk_render_node_queue_invalidate (child, GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM);
+}
+
+/**
+ * gsk_render_node_set_opacity:
+ * @node: a #GskRenderNode
+ * @opacity: the opacity of the node, between 0 (fully transparent) and
+ * 1 (fully opaque)
+ *
+ * Sets the opacity of the @node.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_opacity (GskRenderNode *node,
+ double opacity)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ node->opacity = CLAMP (opacity, 0.0, 1.0);
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY);
+}
+
+/**
+ * gsk_render_node_get_opacity:
+ * @node: a #GskRenderNode
+ *
+ * Retrieves the opacity set using gsk_render_node_set_opacity().
+ *
+ * Returns: the opacity of the #GskRenderNode
+ *
+ * Since: 3.22
+ */
+double
+gsk_render_node_get_opacity (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0.0);
+
+ return node->opacity;
+}
+
+/**
+ * gsk_render_node_set_hidden:
+ * @node: a #GskRenderNode
+ * @hidden: whether the @node should be hidden or not
+ *
+ * Sets whether the @node should be hidden.
+ *
+ * Hidden nodes, and their descendants, are not rendered.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_hidden (GskRenderNode *node,
+ gboolean hidden)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ node->hidden = !!hidden;
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY);
+}
+
+/**
+ * gsk_render_node_is_hidden:
+ * @node: a #GskRenderNode
+ *
+ * Checks whether a @node is hidden.
+ *
+ * Returns: %TRUE if the #GskRenderNode is hidden
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_is_hidden (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE);
+
+ return node->hidden;
+}
+
+/**
+ * gsk_render_node_set_opaque:
+ * @node: a #GskRenderNode
+ * @opaque: whether the node is fully opaque or not
+ *
+ * Sets whether the node is known to be fully opaque.
+ *
+ * Fully opaque nodes will ignore the opacity set using gsk_render_node_set_opacity(),
+ * but if their parent is not opaque they may still be rendered with an opacity.
+ *
+ * Renderers may use this information to optimize the rendering pipeline.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_opaque (GskRenderNode *node,
+ gboolean opaque)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ node->opaque = !!opaque;
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY);
+}
+
+/**
+ * gsk_render_node_is_opaque:
+ * @node: a #GskRenderNode
+ *
+ * Retrieves the value set using gsk_render_node_set_opaque().
+ *
+ * Returns: %TRUE if the #GskRenderNode is fully opaque
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_is_opaque (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE);
+
+ return node->opaque;
+}
+
+/**
+ * gsk_render_node_contains:
+ * @node: a #GskRenderNode
+ * @descendant: a #GskRenderNode
+ *
+ * Checks whether @node contains @descendant.
+ *
+ * Returns: %TRUE if the #GskRenderNode contains the given
+ * descendant
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_contains (GskRenderNode *node,
+ GskRenderNode *descendant)
+{
+ GskRenderNode *tmp;
+
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE);
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (descendant), FALSE);
+
+ for (tmp = descendant; tmp != NULL; tmp = tmp->parent)
+ if (tmp == node)
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * gsk_render_node_set_surface:
+ * @node: a #GskRenderNode
+ * @surface: (nullable): a Cairo surface
+ *
+ * Sets the contents of the #GskRenderNode.
+ *
+ * The @node will acquire a reference on the given @surface.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_surface (GskRenderNode *node,
+ cairo_surface_t *surface)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ g_clear_pointer (&node->surface, cairo_surface_destroy);
+
+ if (surface != NULL)
+ node->surface = cairo_surface_reference (surface);
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE);
+}
+
+/*< private >
+ * gsk_render_node_get_toplevel:
+ * @node: a #GskRenderNode
+ *
+ * Retrieves the top level #GskRenderNode without a parent.
+ *
+ * Returns: (transfer none): the top level #GskRenderNode
+ */
+GskRenderNode *
+gsk_render_node_get_toplevel (GskRenderNode *node)
+{
+ GskRenderNode *parent;
+
+ parent = node->parent;
+ if (parent == NULL)
+ return node;
+
+ while (parent != NULL)
+ {
+ if (parent->parent == NULL)
+ return parent;
+
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+/*< private >
+ * gsk_render_node_update_world_matrix:
+ * @node: a #GskRenderNode
+ * @force: %TRUE if the update should be forced
+ *
+ * Updates the cached world matrix of @node and its children, if needed.
+ */
+void
+gsk_render_node_update_world_matrix (GskRenderNode *node,
+ gboolean force)
+{
+ GskRenderNodeIter iter;
+ GskRenderNode *child;
+
+ if (force || node->needs_world_matrix_update)
+ {
+ GSK_NOTE (RENDER_NODE, g_print ("Updating cached world matrix on node %p [parent=%p, t_set=%s, ct_set=%s]\n",
+ node,
+ node->parent != NULL ? node->parent : 0,
+ node->transform_set ? "y" : "n",
+ node->parent != NULL && node->parent->child_transform_set ? "y" : "n"));
+
+ if (node->parent == NULL)
+ {
+ if (node->transform_set)
+ graphene_matrix_init_from_matrix (&node->world_matrix, &node->transform);
+ else
+ graphene_matrix_init_identity (&node->world_matrix);
+ }
+ else
+ {
+ GskRenderNode *parent = node->parent;
+ graphene_matrix_t tmp;
+
+ if (parent->child_transform_set)
+ graphene_matrix_init_from_matrix (&tmp, &parent->child_transform);
+ else
+ graphene_matrix_init_identity (&tmp);
+
+ if (node->transform_set)
+ graphene_matrix_multiply (&tmp, &node->transform, &tmp);
+
+ graphene_matrix_multiply (&tmp, &parent->world_matrix, &node->world_matrix);
+ }
+
+ node->needs_world_matrix_update = FALSE;
+ }
+
+ gsk_render_node_iter_init (&iter, node);
+ while (gsk_render_node_iter_next (&iter, &child))
+ gsk_render_node_update_world_matrix (child, TRUE);
+}
+
+/*< private >
+ * gsk_render_node_get_surface:
+ * @node: a #GskRenderNode
+ *
+ * Retrieves the surface set using gsk_render_node_set_surface().
+ *
+ * Returns: (transfer none) (nullable): a Cairo surface
+ */
+cairo_surface_t *
+gsk_render_node_get_surface (GskRenderNode *node)
+{
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ return node->surface;
+}
+
+/*< private >
+ * gsk_render_node_get_world_matrix:
+ * @node: a #GskRenderNode
+ * @mv: (out caller-allocates): return location for the modelview matrix
+ * in world-relative coordinates
+ *
+ * Retrieves the modelview matrix in world-relative coordinates.
+ */
+void
+gsk_render_node_get_world_matrix (GskRenderNode *node,
+ graphene_matrix_t *mv)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+ g_return_if_fail (mv != NULL);
+
+ if (node->needs_world_matrix_update)
+ {
+ GskRenderNode *tmp = gsk_render_node_get_toplevel (node);
+
+ gsk_render_node_update_world_matrix (tmp, TRUE);
+
+ g_assert (!node->needs_world_matrix_update);
+ }
+
+ *mv = node->world_matrix;
+}
+
+void
+gsk_render_node_set_invalidate_func (GskRenderNode *node,
+ GskRenderNodeInvalidateFunc invalidate_func,
+ gpointer func_data,
+ GDestroyNotify destroy_func_data)
+{
+ if (node->parent != NULL)
+ {
+ g_critical ("Render node of type '%s' is not a root node. Only root "
+ "nodes can have an invalidation function.",
+ G_OBJECT_TYPE_NAME (node));
+ return;
+ }
+
+ if (node->invalidate_func != NULL)
+ {
+ if (node->destroy_func_data != NULL)
+ node->destroy_func_data (node->func_data);
+ }
+
+ node->invalidate_func = invalidate_func;
+ node->func_data = func_data;
+ node->destroy_func_data = destroy_func_data;
+}
+
+GskRenderNodeChanges
+gsk_render_node_get_current_state (GskRenderNode *node)
+{
+ GskRenderNodeChanges res = 0;
+
+ if (node->needs_resize)
+ res |= GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS;
+ if (node->needs_world_matrix_update)
+ res |= GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
+ if (node->needs_content_update)
+ res |= GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE;
+ if (node->needs_opacity_update)
+ res |= GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY;
+ if (node->needs_visibility_update)
+ res |= GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY;
+
+ return res;
+}
+
+GskRenderNodeChanges
+gsk_render_node_get_last_state (GskRenderNode *node)
+{
+ return node->last_state_change;
+}
+
+void
+gsk_render_node_queue_invalidate (GskRenderNode *node,
+ GskRenderNodeChanges changes)
+{
+ GskRenderNodeChanges cur_invalidated_bits = 0;
+ GskRenderNode *root;
+ int i;
+
+ cur_invalidated_bits = gsk_render_node_get_current_state (node);
+ if ((cur_invalidated_bits & changes) != 0)
+ return;
+
+ node->needs_resize = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS) != 0;
+ node->needs_world_matrix_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM) != 0;
+ node->needs_content_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE) != 0;
+ node->needs_opacity_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0;
+ node->needs_visibility_update = (changes & GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY) != 0;
+
+ if (node->parent == NULL)
+ {
+ GSK_NOTE (RENDER_NODE, g_print ("Invalid node [%p] is top-level\n", node));
+ return;
+ }
+
+ root = gsk_render_node_get_toplevel (node);
+
+ if (root->invalidated_descendants == NULL)
+ root->invalidated_descendants = g_ptr_array_new ();
+
+ for (i = 0; i < root->invalidated_descendants->len; i++)
+ {
+ if (node == g_ptr_array_index (root->invalidated_descendants, i))
+ {
+ GSK_NOTE (RENDER_NODE, g_print ("Node [%p] already invalidated; skipping...\n", node));
+ return;
+ }
+ }
+
+ GSK_NOTE (RENDER_NODE, g_print ("Adding node [%p] to list of invalid descendants of [%p]\n", node, root));
+ g_ptr_array_add (root->invalidated_descendants, node);
+}
+
+void
+gsk_render_node_validate (GskRenderNode *node)
+{
+ GPtrArray *invalidated_descendants;
+ gboolean call_invalidate_func;
+ int i;
+
+ node->last_state_change = gsk_render_node_get_current_state (node);
+
+ /* We call the invalidation function if our state changed, or if
+ * the descendants state has changed
+ */
+ call_invalidate_func = node->last_state_change != 0 ||
+ node->invalidated_descendants != NULL;
+
+ gsk_render_node_maybe_resize (node);
+ gsk_render_node_update_world_matrix (node, FALSE);
+ node->needs_content_update = FALSE;
+ node->needs_visibility_update = FALSE;
+ node->needs_opacity_update = FALSE;
+
+ /* Steal the array of invalidated descendants, so that changes caused by
+ * the validation will not cause recursions
+ */
+ invalidated_descendants = node->invalidated_descendants;
+ node->invalidated_descendants = NULL;
+
+ if (invalidated_descendants != NULL)
+ {
+ for (i = 0; i < invalidated_descendants->len; i++)
+ {
+ GskRenderNode *child = g_ptr_array_index (invalidated_descendants, i);
+
+ child->last_state_change = 0;
+
+ GSK_NOTE (RENDER_NODE, g_print ("Validating descendant node [%p] (resize:%s, transform:%s)\n",
+ child,
+ child->needs_resize ? "yes" : "no",
+ child->needs_world_matrix_update ? "yes" : "no"));
+
+ child->last_state_change = gsk_render_node_get_current_state (child);
+
+ gsk_render_node_maybe_resize (child);
+ gsk_render_node_update_world_matrix (child, FALSE);
+
+ child->needs_content_update = FALSE;
+ child->needs_visibility_update = FALSE;
+ child->needs_opacity_update = FALSE;
+ }
+ }
+
+ g_clear_pointer (&invalidated_descendants, g_ptr_array_unref);
+
+ if (call_invalidate_func && node->invalidate_func != NULL)
+ node->invalidate_func (node, node->func_data);
+}
+
+void
+gsk_render_node_maybe_resize (GskRenderNode *node)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ if (!node->needs_resize)
+ return;
+
+ GSK_RENDER_NODE_GET_CLASS (node)->resize (node);
+
+ node->needs_resize = FALSE;
+}
+
+/**
+ * gsk_render_node_set_name:
+ * @node: a #GskRenderNode
+ * @name: (nullable): a name for the node
+ *
+ * Sets the name of the node.
+ *
+ * A name is generally useful for debugging purposes.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_set_name (GskRenderNode *node,
+ const char *name)
+{
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ g_free (node->name);
+ node->name = g_strdup (name);
+}
+
+static cairo_user_data_key_t render_node_context_key;
+
+static void
+surface_invalidate (void *data)
+{
+ GskRenderNode *node = data;
+
+ gsk_render_node_queue_invalidate (node, GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE);
+}
+
+/**
+ * gsk_render_node_get_draw_context:
+ * @node: a #GskRenderNode
+ *
+ * Creates a Cairo context for drawing using the surface associated
+ * to the render node. If no surface has been attached to the render
+ * node, a new surface will be created as a side effect.
+ *
+ * Returns: (transfer full): a Cairo context used for drawing; use
+ * cairo_destroy() when done drawing
+ *
+ * Since: 3.22
+ */
+cairo_t *
+gsk_render_node_get_draw_context (GskRenderNode *node)
+{
+ cairo_t *res;
+
+ g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);
+
+ if (node->surface == NULL)
+ node->surface = cairo_image_surface_create (node->opaque ? CAIRO_FORMAT_RGB24
+ : CAIRO_FORMAT_ARGB32,
+ node->bounds.size.width,
+ node->bounds.size.height);
+
+ res = cairo_create (node->surface);
+
+ cairo_rectangle (res,
+ node->bounds.origin.x, node->bounds.origin.y,
+ node->bounds.size.width, node->bounds.size.height);
+ cairo_clip (res);
+
+ cairo_set_user_data (res, &render_node_context_key, node, surface_invalidate);
+
+ return res;
+}
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
new file mode 100644
index 0000000000..f729c274f0
--- /dev/null
+++ b/gsk/gskrendernode.h
@@ -0,0 +1,117 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_RENDER_NODE_H__
+#define __GSK_RENDER_NODE_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only <gsk/gsk.h> can be included directly."
+#endif
+
+#include <gsk/gsktypes.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_RENDER_NODE (gsk_render_node_get_type ())
+
+#define GSK_RENDER_NODE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_RENDER_NODE, GskRenderNode))
+#define GSK_IS_RENDER_NODE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_RENDER_NODE))
+
+typedef struct _GskRenderNode GskRenderNode;
+typedef struct _GskRenderNodeClass GskRenderNodeClass;
+
+GDK_AVAILABLE_IN_3_22
+GType gsk_render_node_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_new (void);
+
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_get_parent (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_get_first_child (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_get_last_child (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_get_next_sibling (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_get_previous_sibling (GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_insert_child_at_pos (GskRenderNode *node,
+ GskRenderNode *child,
+ int index_);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_insert_child_before (GskRenderNode *node,
+ GskRenderNode *child,
+ GskRenderNode *sibling);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_insert_child_after (GskRenderNode *node,
+ GskRenderNode *child,
+ GskRenderNode *sibling);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_remove_child (GskRenderNode *node,
+ GskRenderNode *child);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_replace_child (GskRenderNode *node,
+ GskRenderNode *new_child,
+ GskRenderNode *old_child);
+GDK_AVAILABLE_IN_3_22
+GskRenderNode * gsk_render_node_remove_all_children (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+guint gsk_render_node_get_n_children (GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_contains (GskRenderNode *node,
+ GskRenderNode *descendant);
+
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_bounds (GskRenderNode *node,
+ const graphene_rect_t *bounds);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_transform (GskRenderNode *node,
+ const graphene_matrix_t *transform);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_child_transform (GskRenderNode *node,
+ const graphene_matrix_t *transform);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_opacity (GskRenderNode *node,
+ double opacity);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_hidden (GskRenderNode *node,
+ gboolean hidden);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_is_hidden (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_opaque (GskRenderNode *node,
+ gboolean opaque);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_is_opaque (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_surface (GskRenderNode *node,
+ cairo_surface_t *surface);
+GDK_AVAILABLE_IN_3_22
+cairo_t * gsk_render_node_get_draw_context (GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_set_name (GskRenderNode *node,
+ const char *name);
+
+G_END_DECLS
+
+#endif /* __GSK_RENDER_NODE_H__ */
diff --git a/gsk/gskrendernodeiter.c b/gsk/gskrendernodeiter.c
new file mode 100644
index 0000000000..d354fb21ac
--- /dev/null
+++ b/gsk/gskrendernodeiter.c
@@ -0,0 +1,254 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:GskRenderNodeIter
+ * @title: GskRenderNodeIter
+ * @Short_desc: Iterator helper for render nodes
+ *
+ * TODO
+ */
+
+#include "config.h"
+
+#include "gskrendernodeiter.h"
+#include "gskrendernodeprivate.h"
+
+typedef struct {
+ GskRenderNode *root;
+ GskRenderNode *current;
+ gint64 age;
+ gpointer reserved1;
+ gpointer reserved2;
+} RealIter;
+
+#define REAL_ITER(iter) ((RealIter *) (iter))
+
+/**
+ * gsk_render_node_iter_new: (constructor)
+ *
+ * Allocates a new #GskRenderNodeIter.
+ *
+ * Returns: (transfer full): the newly allocated #GskRenderNodeIter
+ *
+ * Since: 3.22
+ */
+GskRenderNodeIter *
+gsk_render_node_iter_new (void)
+{
+ return g_slice_new (GskRenderNodeIter);
+}
+
+/*< private >
+ * gsk_render_node_iter_copy:
+ * @src: a #GskRenderNodeIter
+ *
+ * Copies a #GskRenderNodeIter.
+ *
+ * Returns: (transfer full): a #GskRenderNodeIter
+ */
+static GskRenderNodeIter *
+gsk_render_node_iter_copy (GskRenderNodeIter *src)
+{
+ return g_slice_dup (GskRenderNodeIter, src);
+}
+
+/**
+ * gsk_render_node_iter_free:
+ * @iter: a #GskRenderNodeIter
+ *
+ * Frees the resources allocated by gsk_render_node_iter_new().
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_iter_free (GskRenderNodeIter *iter)
+{
+ g_slice_free (GskRenderNodeIter, iter);
+}
+
+G_DEFINE_BOXED_TYPE (GskRenderNodeIter, gsk_render_node_iter,
+ gsk_render_node_iter_copy,
+ gsk_render_node_iter_free)
+
+/**
+ * gsk_render_node_iter_init:
+ * @iter: a #GskRenderNodeIter
+ * @node: a #GskRenderNode
+ *
+ * Initializes a #GskRenderNodeIter for iterating over the
+ * children of @node.
+ *
+ * It's safe to call this function multiple times on the same
+ * #GskRenderNodeIter instance.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_iter_init (GskRenderNodeIter *iter,
+ GskRenderNode *node)
+{
+ RealIter *riter = REAL_ITER (iter);
+
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+ riter->root = node;
+ riter->age = node->age;
+ riter->current = NULL;
+}
+
+/**
+ * gsk_render_node_iter_is_valid:
+ * @iter: a #GskRenderNodeIter
+ *
+ * Checks whether a #GskRenderNodeIter is associated to a #GskRenderNode,
+ * or whether the associated node was modified while iterating.
+ *
+ * Returns: %TRUE if the iterator is still valid.
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_iter_is_valid (GskRenderNodeIter *iter)
+{
+ RealIter *riter = REAL_ITER (iter);
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (riter->root == NULL)
+ return FALSE;
+
+ return riter->root->age == riter->age;
+}
+
+/**
+ * gsk_render_node_iter_next:
+ * @iter: a #GskRenderNodeIter
+ * @child: (out) (transfer none): return location for a #GskRenderNode
+ *
+ * Advances the @iter and retrieves the next child of the root #GskRenderNode
+ * used to initialize the #GskRenderNodeIter.
+ *
+ * If the iterator could advance, this function returns %TRUE and sets the
+ * @child argument with the child #GskRenderNode.
+ *
+ * If the iterator could not advance, this function returns %FALSE and the
+ * contents of the @child argument are undefined.
+ *
+ * Returns: %TRUE if the iterator could advance, and %FALSE otherwise
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_iter_next (GskRenderNodeIter *iter,
+ GskRenderNode **child)
+{
+ RealIter *riter = REAL_ITER (iter);
+
+ g_return_val_if_fail (riter != NULL, FALSE);
+ g_return_val_if_fail (riter->root != NULL, FALSE);
+ g_return_val_if_fail (riter->root->age == riter->age, FALSE);
+
+ if (riter->current == NULL)
+ riter->current = riter->root->first_child;
+ else
+ riter->current = riter->current->next_sibling;
+
+ if (child != NULL)
+ *child = riter->current;
+
+ return riter->current != NULL;
+}
+
+/**
+ * gsk_render_node_iter_prev:
+ * @iter: a #GskRenderNodeIter
+ * @child: (out) (transfer none): return location for a #GskRenderNode
+ *
+ * Advances the @iter and retrieves the previous child of the root
+ * #GskRenderNode used to initialize the #GskRenderNodeIter.
+ *
+ * If the iterator could advance, this function returns %TRUE and sets the
+ * @child argument with the child #GskRenderNode.
+ *
+ * If the iterator could not advance, this function returns %FALSE and the
+ * contents of the @child argument are undefined.
+ *
+ * Returns: %TRUE if the iterator could advance, and %FALSE otherwise
+ *
+ * Since: 3.22
+ */
+gboolean
+gsk_render_node_iter_prev (GskRenderNodeIter *iter,
+ GskRenderNode **child)
+{
+ RealIter *riter = REAL_ITER (iter);
+
+ g_return_val_if_fail (riter != NULL, FALSE);
+ g_return_val_if_fail (riter->root != NULL, FALSE);
+ g_return_val_if_fail (riter->root->age == riter->age, FALSE);
+
+ if (riter->current == NULL)
+ riter->current = riter->root->last_child;
+ else
+ riter->current = riter->current->prev_sibling;
+
+ if (child != NULL)
+ *child = riter->current;
+
+ return riter->current != NULL;
+}
+
+/**
+ * gsk_render_node_iter_remove:
+ * @iter: a #GskRenderNodeIter
+ *
+ * Removes the child #GskRenderNode currently being visited by
+ * the iterator.
+ *
+ * Calling this function on an invalid #GskRenderNodeIter results
+ * in undefined behavior.
+ *
+ * Since: 3.22
+ */
+void
+gsk_render_node_iter_remove (GskRenderNodeIter *iter)
+{
+ RealIter *riter = REAL_ITER (iter);
+ GskRenderNode *tmp;
+
+ g_return_if_fail (riter != NULL);
+ g_return_if_fail (riter->root != NULL);
+ g_return_if_fail (riter->root->age == riter->age);
+ g_return_if_fail (riter->current != NULL);
+
+ tmp = riter->current;
+
+ if (tmp != NULL)
+ {
+ riter->current = tmp->prev_sibling;
+
+ gsk_render_node_remove_child (riter->root, tmp);
+
+ riter->age += 1;
+
+ /* Safety net */
+ g_assert (riter->age == riter->root->age);
+ }
+}
diff --git a/gsk/gskrendernodeiter.h b/gsk/gskrendernodeiter.h
new file mode 100644
index 0000000000..4114e85256
--- /dev/null
+++ b/gsk/gskrendernodeiter.h
@@ -0,0 +1,45 @@
+#ifndef __GSK_RENDER_NODE_ITER_H__
+#define __GSK_RENDER_NODE_ITER_H__
+
+#include <gsk/gskrendernode.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_RENDER_NODE_ITER (gsk_render_node_iter_get_type())
+
+typedef struct _GskRenderNodeIter GskRenderNodeIter;
+
+struct _GskRenderNodeIter
+{
+ /*< private >*/
+ gpointer dummy1;
+ gpointer dummy2;
+ gint64 dummy3;
+ gpointer dummy4;
+ gpointer dummy5;
+};
+
+GDK_AVAILABLE_IN_3_22
+GType gsk_render_node_iter_get_type (void) G_GNUC_CONST;
+GDK_AVAILABLE_IN_3_22
+GskRenderNodeIter * gsk_render_node_iter_new (void);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_iter_free (GskRenderNodeIter *iter);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_iter_init (GskRenderNodeIter *iter,
+ GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_iter_is_valid (GskRenderNodeIter *iter);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_iter_prev (GskRenderNodeIter *iter,
+ GskRenderNode **child);
+GDK_AVAILABLE_IN_3_22
+gboolean gsk_render_node_iter_next (GskRenderNodeIter *iter,
+ GskRenderNode **child);
+GDK_AVAILABLE_IN_3_22
+void gsk_render_node_iter_remove (GskRenderNodeIter *iter);
+
+G_END_DECLS
+
+#endif /* GSK_RENDER_NODE_ITER_H */
diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h
new file mode 100644
index 0000000000..72373d8e68
--- /dev/null
+++ b/gsk/gskrendernodeprivate.h
@@ -0,0 +1,124 @@
+#ifndef __GSK_RENDER_NODE_PRIVATE_H__
+#define __GSK_RENDER_NODE_PRIVATE_H__
+
+#include "gskrendernode.h"
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+#define GSK_RENDER_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_RENDER_NODE, GskRenderNodeClass))
+#define GSK_IS_RENDER_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_RENDER_NODE))
+#define GSK_RENDER_NODE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDER_NODE, GskRenderNodeClass))
+
+typedef enum {
+ GSK_RENDER_NODE_CHANGES_UPDATE_BOUNDS = 1 << 0,
+ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM = 1 << 1,
+ GSK_RENDER_NODE_CHANGES_UPDATE_SURFACE = 1 << 2,
+ GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY = 1 << 3,
+ GSK_RENDER_NODE_CHANGES_UPDATE_VISIBILITY = 1 << 4,
+ GSK_RENDER_NODE_CHANEGS_UPDATE_HIERARCHY = 1 << 5
+} GskRenderNodeChanges;
+
+typedef void (* GskRenderNodeInvalidateFunc) (GskRenderNode *node,
+ gpointer data);
+
+struct _GskRenderNode
+{
+ GObject parent_instance;
+
+ /* The graph */
+ GskRenderNode *parent;
+ GskRenderNode *first_child;
+ GskRenderNode *last_child;
+ GskRenderNode *prev_sibling;
+ GskRenderNode *next_sibling;
+
+ int n_children;
+
+ /* Use for debugging */
+ char *name;
+
+ /* Tag updated when adding/removing children */
+ gint64 age;
+
+ /* The contents of the node */
+ cairo_surface_t *surface;
+
+ /* Paint opacity */
+ double opacity;
+
+ /* Clip rectangle */
+ graphene_rect_t bounds;
+
+ /* Transformations relative to the root of the scene */
+ graphene_matrix_t world_matrix;
+
+ /* Transformations applied to the node */
+ graphene_matrix_t transform;
+
+ /* Transformations applied to the children of the node */
+ graphene_matrix_t child_transform;
+
+ /* Invalidation function for root node */
+ GskRenderNodeInvalidateFunc invalidate_func;
+ gpointer func_data;
+ GDestroyNotify destroy_func_data;
+
+ /* Descendants that need to be validated; only for root node */
+ GPtrArray *invalidated_descendants;
+
+ GskRenderNodeChanges last_state_change;
+
+ /* Bit fields; leave at the end */
+ gboolean hidden : 1;
+ gboolean opaque : 1;
+ gboolean transform_set : 1;
+ gboolean child_transform_set : 1;
+ gboolean needs_resize : 1;
+ gboolean needs_world_matrix_update : 1;
+ gboolean needs_content_update : 1;
+ gboolean needs_opacity_update : 1;
+ gboolean needs_visibility_update : 1;
+};
+
+struct _GskRenderNodeClass
+{
+ GObjectClass parent_class;
+
+ void (* resize) (GskRenderNode *node);
+};
+
+void gsk_render_node_get_bounds (GskRenderNode *node,
+ graphene_rect_t *frame);
+void gsk_render_node_get_transform (GskRenderNode *node,
+ graphene_matrix_t *mv);
+double gsk_render_node_get_opacity (GskRenderNode *node);
+
+cairo_surface_t *gsk_render_node_get_surface (GskRenderNode *node);
+
+GskRenderNode *gsk_render_node_get_toplevel (GskRenderNode *node);
+
+void gsk_render_node_update_world_matrix (GskRenderNode *node,
+ gboolean force);
+
+void gsk_render_node_get_world_matrix (GskRenderNode *node,
+ graphene_matrix_t *mv);
+
+void gsk_render_node_queue_invalidate (GskRenderNode *node,
+ GskRenderNodeChanges changes);
+
+void gsk_render_node_set_invalidate_func (GskRenderNode *root,
+ GskRenderNodeInvalidateFunc validate_func,
+ gpointer data,
+ GDestroyNotify notify);
+
+void gsk_render_node_validate (GskRenderNode *node);
+
+void gsk_render_node_maybe_resize (GskRenderNode *node);
+
+GskRenderNodeChanges gsk_render_node_get_current_state (GskRenderNode *node);
+GskRenderNodeChanges gsk_render_node_get_last_state (GskRenderNode *node);
+
+G_END_DECLS
+
+#endif /* __GSK_RENDER_NODE_PRIVATE_H__ */
diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h
new file mode 100644
index 0000000000..8513328ba5
--- /dev/null
+++ b/gsk/gsktypes.h
@@ -0,0 +1,29 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GSK_TYPES_H__
+#define __GSK_TYPES_H__
+
+#if !defined (__GSK_H_INSIDE__) && !defined (GSK_COMPILATION)
+#error "Only <gsk/gsk.h> can be included directly."
+#endif
+
+#include <graphene.h>
+#include <gdk/gdk.h>
+#include <gsk/gskenums.h>
+
+#endif /* __GSK_TYPES_H__ */
diff --git a/gsk/resources/glsl/base-renderer-fragment.glsl b/gsk/resources/glsl/base-renderer-fragment.glsl
new file mode 100644
index 0000000000..07458db527
--- /dev/null
+++ b/gsk/resources/glsl/base-renderer-fragment.glsl
@@ -0,0 +1,13 @@
+#version 150
+
+smooth in vec2 vUv;
+
+out vec4 outputColor;
+
+uniform mat4 mvp;
+uniform sampler2D map;
+uniform float alpha;
+
+void main() {
+ outputColor = texture2D(map, vUv) * vec4(alpha);
+}
diff --git a/gsk/resources/glsl/base-renderer-vertex.glsl b/gsk/resources/glsl/base-renderer-vertex.glsl
new file mode 100644
index 0000000000..534f201bfa
--- /dev/null
+++ b/gsk/resources/glsl/base-renderer-vertex.glsl
@@ -0,0 +1,16 @@
+#version 150
+
+uniform mat4 mvp;
+uniform sampler2D map;
+uniform float alpha;
+
+in vec2 position;
+in vec2 uv;
+
+smooth out vec2 vUv;
+
+void main() {
+ gl_Position = mvp * vec4(position, 0.0, 1.0);
+
+ vUv = vec2(uv.x, 1 - uv.y);
+}