[gtk+/wip/ebassi/gsk-renderer: 34/79] Initial implementation of GSK rendering pipeline



commit f5aba2d09c25fed953788c6e32601edd0dc4ad01
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Thu Mar 17 13:48:19 2016 +0000

    Initial implementation of GSK rendering pipeline
    
    GSK is conceptually split into two scene graphs:
    
     * a simple rendering tree of operations
     * a complex set of logical layers
    
    The latter is built on the former, and adds convenience and high level
    API for application developers.
    
    The lower layer, though, is what gets transformed into the rendering
    pipeline, as it's simple and thus can be transformed into appropriate
    rendering commands with minimal state changes.
    
    The lower layer is also suitable for reuse from more complex higher
    layers, like the CSS machinery in GTK, without necessarily port those
    layers to the GSK high level API.
    
    This lower layer is based on GskRenderNode instances, which represent
    the tree of rendering operations; and a GskRenderer instance, which
    takes the render nodes and submits them (after potentially reordering
    and transforming them to a more appropriate representation) to the
    underlying graphic system.

 gsk/Makefile.am                                |  112 ++-
 gsk/gsk.h                                      |   33 +
 gsk/gskcairorenderer.c                         |  195 ++++
 gsk/gskcairorendererprivate.h                  |   26 +
 gsk/gskdebug.c                                 |   58 +
 gsk/gskdebugprivate.h                          |   43 +
 gsk/gskenums.h                                 |   46 +
 gsk/gskenumtypes.c.template                    |   38 +
 gsk/gskenumtypes.h.template                    |   24 +
 gsk/gskglrenderer.c                            | 1092 +++++++++++++++++++
 gsk/gskglrendererprivate.h                     |   23 +
 gsk/gskprivate.c                               |   16 +
 gsk/gskprivate.h                               |   12 +
 gsk/gskrenderer.c                              | 1377 ++++++++++++++++++++++++
 gsk/gskrenderer.h                              |  113 ++
 gsk/gskrendererprivate.h                       |   62 ++
 gsk/gskrendernode.c                            | 1246 +++++++++++++++++++++
 gsk/gskrendernode.h                            |  117 ++
 gsk/gskrendernodeiter.c                        |  254 +++++
 gsk/gskrendernodeiter.h                        |   45 +
 gsk/gskrendernodeprivate.h                     |  124 +++
 gsk/gsktypes.h                                 |   29 +
 gsk/resources/glsl/base-renderer-fragment.glsl |   13 +
 gsk/resources/glsl/base-renderer-vertex.glsl   |   16 +
 tests/Makefile.am                              |    7 +
 tests/testgskrenderer.c                        |  229 ++++
 26 files changed, 5331 insertions(+), 19 deletions(-)
---
diff --git a/gsk/Makefile.am b/gsk/Makefile.am
index 2b20fcd..4a8ec18 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)                      \
        $(GSK_DEP_CFLAGS)
 
@@ -34,15 +23,88 @@ LDADD = \
 BUILT_SOURCES =
 
 CLEANFILES =
+DISTCLEANFILES =
 
 lib_LTLIBRARIES =
 
-gsk_public_source_h =
-gsk_private_source_h =
-gsk_private_source_c =
-gsk_source_c =
+gsk_public_source_h = \
+       gskenums.h \
+       gskrenderer.h \
+       gskrendernode.h \
+       gskrendernodeiter.h \
+       gsktypes.h
+gsk_private_source_h = \
+       gskcairorendererprivate.h \
+       gskdebugprivate.h \
+       gskglrendererprivate.h \
+       gskrendererprivate.h \
+       gskrendernodeprivate.h \
+       gskprivate.h
+gsk_private_source_c = \
+       gskprivate.c
+gsk_built_source_h = \
+       gskenumtypes.h \
+       gskresources.h
+gsk_built_source_c = \
+       gskenumtypes.c \
+       gskresources.c
+gsk_source_c = \
+       gskcairorenderer.c \
+       gskdebug.c \
+       gskglrenderer.c \
+       gskrenderer.c \
+       gskrendernode.c \
+       gskrendernodeiter.c
+
+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_3_la_SOURCES = $(all_sources)
+nodist_libgsk_3_la_SOURCES = $(gsk_built_source_h) $(gsk_built_source_c)
 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)
@@ -50,11 +112,23 @@ libgsk_3_la_LDFLAGS = $(LDADD)
 lib_LTLIBRARIES += libgsk-3.la
 
 gskincludedir = $(includedir)/gtk-3.0/gsk
-gskinclude_HEADERS = $(gsk_public_source_h) gsk.h
+gskinclude_HEADERS = $(gsk_public_source_h) gskenumtypes.h gsk.h
+
+-include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ENV = \
+       CC="$(CC)"
+INTROSPECTION_SCANNER_ARGS = \
+       --add-include-path=../gdk \
+       --warn-all
+INTROSPECTION_COMPILER_ARGS = \
+       --includedir=$(srcdir) \
+       --includedir=. \
+       --includedir=../gdk
 
 if HAVE_INTROSPECTION
 
-introspection_files = $(gsk_source_c) $(gsk_public_source_h)
+introspection_files = $(filter-out $(wildcard *private.h),$(all_sources))
 
 Gsk-3.0.gir: libgsk-3.la Makefile
 Gsk_3_0_gir_SCANNERFLAGS = \
diff --git a/gsk/gsk.h b/gsk/gsk.h
new file mode 100644
index 0000000..01c4569
--- /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 0000000..62b7a47
--- /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 0000000..7a9bd23
--- /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 0000000..ebc5366
--- /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 0000000..439be07
--- /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 0000000..b831d49
--- /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 0000000..430ea8f
--- /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 0000000..15a8ac6
--- /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 0000000..96f04af
--- /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 0000000..a30b201
--- /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 0000000..9e82502
--- /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 0000000..84539c1
--- /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 0000000..cf90e38
--- /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 0000000..a18ab9b
--- /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 0000000..404502c
--- /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 0000000..092b96a
--- /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 0000000..f729c27
--- /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 0000000..d354fb2
--- /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 0000000..4114e85
--- /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 0000000..72373d8
--- /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 0000000..8513328
--- /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 0000000..07458db
--- /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 0000000..534f201
--- /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);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 649981f..9d451e2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -7,6 +7,8 @@ AM_CPPFLAGS =                           \
        -I$(top_srcdir)                 \
        -I$(top_builddir)/gdk           \
        -I$(top_srcdir)/gdk             \
+       -I$(top_builddir)/gsk           \
+       -I$(top_srcdir)/gsk             \
        $(GTK_DEBUG_FLAGS)              \
        $(GTK_DEP_CFLAGS)               \
        $(GDK_DEP_CFLAGS)
@@ -16,6 +18,7 @@ DEPS = \
 
 LDADD = \
        $(top_builddir)/gtk/libgtk-3.la \
+       $(top_builddir)/gsk/libgsk-3.la \
        $(top_builddir)/gdk/libgdk-3.la \
        $(GTK_DEP_LIBS)                 \
        $(GDK_DEP_LIBS)                 \
@@ -173,6 +176,7 @@ noinst_PROGRAMS =  $(TEST_PROGS)    \
        listmodel                       \
        testpopup                       \
        testpopupat                     \
+       testgskrenderer                 \
        $(NULL)
 
 if USE_X11
@@ -301,6 +305,7 @@ testtitlebar_DEPENDENCIES = $(TEST_DEPS)
 testwindowsize_DEPENDENCIES = $(TEST_DEPS)
 listmodel_DEPENDENCIES = $(TEST_DEPS)
 foreigndrawing_DEPENDENCIES = $(TEST_DEPS)
+testgskrenderer_DEPENDENCIES = $(TEST_DEPS)
 
 animated_resizing_SOURCES =    \
        animated-resizing.c     \
@@ -541,6 +546,8 @@ listmodel_SOURCES = listmodel.c
 
 foreigndrawing_SOURCES = foreigndrawing.c
 
+testgskrenderer_SOURCES = testgskrenderer.c
+
 EXTRA_DIST +=                  \
        gradient1.png           \
        testgtk.1               \
diff --git a/tests/testgskrenderer.c b/tests/testgskrenderer.c
new file mode 100644
index 0000000..a7a9eeb
--- /dev/null
+++ b/tests/testgskrenderer.c
@@ -0,0 +1,229 @@
+#include "config.h"
+
+#include <graphene.h>
+#include <cairo.h>
+#include <gsk/gsk.h>
+#include <gtk/gtk.h>
+
+#define BOX_SIZE        50.f
+#define PADDING         10.f
+#define ROOT_SIZE       BOX_SIZE * 2 + PADDING * 2
+
+static void
+create_color_surface (cairo_t *cr, GdkRGBA *color, int w, int h)
+{
+  cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha);
+  cairo_rectangle (cr, 0, 0, w, h);
+  cairo_fill (cr);
+}
+
+static GskRenderer *
+get_renderer (GtkWidget *widget)
+{
+  GskRenderer *res;
+
+  res = g_object_get_data (G_OBJECT (widget), "-gsk-renderer");
+  if (res == NULL)
+    {
+      res = gsk_renderer_get_for_display (gtk_widget_get_display (widget));
+
+      g_object_set_data_full (G_OBJECT (widget), "-gsk-renderer",
+                              res,
+                              (GDestroyNotify) g_object_unref);
+    }
+
+  return res;
+}
+
+static void
+create_scene (GskRenderer *renderer)
+{
+  GskRenderNode *root, *node;
+  graphene_matrix_t ctm;
+  cairo_t *cr;
+
+  root = gsk_render_node_new ();
+  gsk_render_node_set_name (root, "Root node");
+  gsk_render_node_set_bounds (root, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = ROOT_SIZE,
+                                .size.height = ROOT_SIZE
+                              });
+  cr = gsk_render_node_get_draw_context (root);
+  create_color_surface (cr, &(GdkRGBA) { .red = 1, .green = 0, .blue = 0, .alpha = 1 }, ROOT_SIZE, 
ROOT_SIZE);
+  cairo_destroy (cr);
+  gsk_renderer_set_root_node (renderer, root);
+  g_object_set_data (G_OBJECT (renderer), "-gsk-renderer-root-node", root);
+
+  g_object_unref (root);
+
+  node = gsk_render_node_new ();
+  gsk_render_node_set_name (node, "Green node");
+  gsk_render_node_set_bounds (node, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = BOX_SIZE,
+                                .size.height = BOX_SIZE
+                              });
+  cr = gsk_render_node_get_draw_context (node);
+  create_color_surface (cr, &(GdkRGBA) { .red = 0, .green = 1, .blue = 0, .alpha = 1 }, BOX_SIZE, BOX_SIZE);
+  cairo_destroy (cr);
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = -0.5, .y = -0.5, .z = 0.f });
+  gsk_render_node_set_transform (node, &ctm);
+  gsk_render_node_insert_child_at_pos (root, node, 0);
+  g_object_unref (node);
+
+  node = gsk_render_node_new ();
+  gsk_render_node_set_name (node, "Blue node");
+  gsk_render_node_set_bounds (node, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = BOX_SIZE,
+                                .size.height = BOX_SIZE
+                              });
+  cr = gsk_render_node_get_draw_context (node);
+  create_color_surface (cr, &(GdkRGBA) { .red = 0, .green = 0, .blue = 1, .alpha = 1 }, BOX_SIZE, BOX_SIZE);
+  cairo_destroy (cr);
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = 0.5, .y = 0.5, .z = 0.f });
+  gsk_render_node_set_transform (node, &ctm);
+  gsk_render_node_insert_child_at_pos (root, node, 1);
+  g_object_unref (node);
+}
+
+static void
+realize (GtkWidget *widget)
+{
+  GskRenderer *renderer = get_renderer (widget);
+
+  gsk_renderer_set_window (renderer, gtk_widget_get_window (widget));
+  gsk_renderer_set_use_alpha (renderer, TRUE);
+  gsk_renderer_realize (renderer);
+
+  create_scene (renderer);
+}
+
+static void
+unrealize (GtkWidget *widget)
+{
+  g_object_set_data (G_OBJECT (widget), "-gsk-renderer", NULL);
+}
+
+static void
+size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+  GskRenderer *renderer = get_renderer (widget);
+  GskRenderNode *root;
+  graphene_matrix_t ctm;
+
+  gsk_renderer_set_viewport (renderer, &(graphene_rect_t) {
+                               .origin.x = 0,
+                               .origin.y = 0,
+                               .size.width = allocation->width,
+                               .size.height = allocation->height
+                             });
+
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) {
+                                    allocation->x,
+                                    allocation->y,
+                                    0.f
+                                  });
+  gsk_renderer_set_modelview (renderer, &ctm);
+
+  root = g_object_get_data (G_OBJECT (renderer), "-gsk-renderer-root-node");
+  if (root == NULL)
+    {
+      create_scene (renderer);
+      root = g_object_get_data (G_OBJECT (renderer), "-gsk-renderer-root-node");
+    }
+
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) {
+                                    .x = 0,
+                                    .y = 0,
+                                    .z = 0
+                                  });
+  gsk_render_node_set_transform (root, &ctm);
+}
+
+static gboolean
+draw (GtkWidget *widget, cairo_t *cr)
+{
+  GskRenderer *renderer = get_renderer (widget);
+
+  gsk_renderer_set_draw_context (renderer, cr);
+  gsk_renderer_render (renderer);
+
+  return TRUE;
+}
+
+static gboolean
+fade_out (GtkWidget *widget,
+          GdkFrameClock *frame_clock,
+          gpointer data)
+{
+  static gint64 first_frame_time;
+  static gboolean flip = FALSE;
+  gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+
+  if (first_frame_time == 0)
+    {
+      first_frame_time = now;
+
+      return G_SOURCE_CONTINUE;
+    }
+
+  double start = first_frame_time;
+  double end = first_frame_time + (double) 1000000;
+  double progress = (now - first_frame_time) / (end - start);
+
+  if (flip)
+    progress = 1 - progress;
+
+  if (progress < 0 || progress >= 1)
+    {
+      first_frame_time = now;
+      flip = !flip;
+      return G_SOURCE_CONTINUE;
+    }
+
+  GskRenderer *renderer = get_renderer (widget);
+  GskRenderNode *root = gsk_renderer_get_root_node (renderer);
+
+  gsk_render_node_set_opacity (root, 1.0 - progress);
+
+  gtk_widget_queue_draw (widget);
+
+  return G_SOURCE_CONTINUE;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window, *area;
+
+  gtk_init (NULL, NULL);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
+  gtk_window_set_title (GTK_WINDOW (window), "GSK Renderer");
+  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+  area = gtk_drawing_area_new ();
+  gtk_widget_set_hexpand (area, TRUE);
+  gtk_widget_set_vexpand (area, TRUE);
+  gtk_widget_set_has_window (GTK_WIDGET (area), FALSE);
+  gtk_widget_set_app_paintable (GTK_WIDGET (area), TRUE);
+  gtk_container_add (GTK_CONTAINER (window), area);
+
+  g_signal_connect (area, "realize", G_CALLBACK (realize), NULL);
+  g_signal_connect (area, "unrealize", G_CALLBACK (unrealize), NULL);
+  g_signal_connect (area, "size-allocate", G_CALLBACK (size_allocate), NULL);
+  g_signal_connect (area, "draw", G_CALLBACK (draw), NULL);
+
+  gtk_widget_add_tick_callback (area, fade_out, NULL, NULL);
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]