[gtk+/wip/ebassi/gsk-1] Snapshot/WIP



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

    Snapshot/WIP

 gsk/Makefile.am               |   81 +++-
 gsk/gsk.h                     |   33 ++
 gsk/gskcairorenderer.c        |  147 +++++++
 gsk/gskcairorendererprivate.h |   26 ++
 gsk/gskdebug.c                |   34 ++
 gsk/gskdebugprivate.h         |   34 ++
 gsk/gskenums.h                |   31 ++
 gsk/gskenumtypes.c.template   |   38 ++
 gsk/gskenumtypes.h.template   |   24 ++
 gsk/gskglrenderer.c           |  337 ++++++++++++++++
 gsk/gskglrendererprivate.h    |   23 ++
 gsk/gskrenderer.c             |  873 +++++++++++++++++++++++++++++++++++++++++
 gsk/gskrenderer.h             |   86 ++++
 gsk/gskrendererprivate.h      |   78 ++++
 gsk/gskrendernode.c           |  767 ++++++++++++++++++++++++++++++++++++
 gsk/gskrendernode.h           |  111 ++++++
 gsk/gskrendernodeiter.c       |  148 +++++++
 gsk/gskrendernodeiter.h       |   45 +++
 gsk/gskrendernodeprivate.h    |   63 +++
 gsk/gsktypes.h                |   29 ++
 tests/Makefile.am             |    7 +
 tests/testgskrenderer.c       |  178 +++++++++
 22 files changed, 3175 insertions(+), 18 deletions(-)
---
diff --git a/gsk/Makefile.am b/gsk/Makefile.am
index 2b20fcd..f89155d 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,59 @@ LDADD = \
 BUILT_SOURCES =
 
 CLEANFILES =
+DISTCLEANFILES =
 
 lib_LTLIBRARIES =
 
-gsk_public_source_h =
-gsk_private_source_h =
+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
 gsk_private_source_c =
-gsk_source_c =
+gsk_built_source_h = \
+       gskenumtypes.h
+gsk_built_source_c = \
+       gskenumtypes.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)
+
+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
 
 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 +83,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..1e4911d
--- /dev/null
+++ b/gsk/gskcairorenderer.c
@@ -0,0 +1,147 @@
+#include "config.h"
+
+#include "gskcairorendererprivate.h"
+
+#include "gskdebugprivate.h"
+#include "gskrendererprivate.h"
+#include "gskrendernodeiter.h"
+#include "gskrendernodeprivate.h"
+
+struct _GskCairoRenderer
+{
+  GskRenderer parent_instance;
+};
+
+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);
+    }
+
+  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));
+  //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))
+    {
+      cairo_push_group (cr);
+      pop_group = TRUE;
+    }
+
+  cairo_set_source_surface (cr, gsk_render_node_get_surface (node), frame.origin.x, frame.origin.y); 
+  cairo_paint (cr);
+
+  cairo_matrix_invert (&ctm);
+  cairo_transform (cr, &ctm);
+
+  if (gsk_render_node_get_n_children (node) != 0)
+    {
+      cairo_save (cr);
+
+      gsk_render_node_iter_init (&iter, node);
+      while (gsk_render_node_iter_next (&iter, &child))
+        gsk_cairo_renderer_render_node (self, child, cr);
+
+      cairo_restore (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_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);
+
+  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->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..2ecbebb
--- /dev/null
+++ b/gsk/gskdebug.c
@@ -0,0 +1,34 @@
+#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
+
+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 *debug_string = g_getenv ("GSK_DEBUG");
+
+      gsk_debug_flags = g_parse_debug_string (debug_string,
+                                              (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
+}
diff --git a/gsk/gskdebugprivate.h b/gsk/gskdebugprivate.h
new file mode 100644
index 0000000..ec1f618
--- /dev/null
+++ b/gsk/gskdebugprivate.h
@@ -0,0 +1,34 @@
+#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;
+
+gboolean gsk_check_debug_flags (GskDebugFlags flags);
+
+#ifdef G_ENABLE_DEBUG
+
+#define GSK_DEBUG_CHECK(type)   G_UNLIKELY (gsk_check_debug_flags (GSK_DEBUG_ ## type))
+#define GSK_NOTE(type,action)   G_STMT_START {  \
+  if (GSK_DEBUG_CHECK (type)) {                 \
+    action;                                     \
+  }                             } G_STMT_END
+
+#else
+
+#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..6aef13b
--- /dev/null
+++ b/gsk/gskenums.h
@@ -0,0 +1,31 @@
+/* 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
+
+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..0870a22
--- /dev/null
+++ b/gsk/gskglrenderer.c
@@ -0,0 +1,337 @@
+#include "config.h"
+
+#include "gskglrendererprivate.h"
+
+#include "gskrendererprivate.h"
+#include "gskrendernodeprivate.h"
+#include "gskrendernodeiter.h"
+
+#include <epoxy/gl.h>
+
+typedef struct {
+  GskRenderNode *node;
+  guint texture_id;
+  graphene_rect_t frame;
+  graphene_matrix_t modelview;
+  float opacity;
+} 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 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;
+};
+
+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_clear_pointer (&self->opaque_render_items, g_array_unref);
+  g_clear_pointer (&self->transparent_render_items, g_array_unref);
+
+  G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
+}
+
+static void
+gsk_gl_renderer_create_buffers (GskGLRenderer *self)
+{
+  glGenFramebuffersEXT (1, &self->frame_buffer);
+
+  if (self->has_alpha)
+    {
+      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;
+        }
+    }
+
+  self->has_buffers = TRUE;
+}
+
+static void
+gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
+{
+  if (!self->has_buffers)
+    return;
+
+  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 gboolean
+gsk_gl_renderer_realize (GskRenderer *renderer)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GError *error = NULL;
+
+  if (self->context == NULL)
+    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_gl_renderer_create_buffers (self);
+
+  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);
+  gsk_gl_renderer_destroy_buffers (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)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+  if (self->context == NULL)
+    return;
+
+  gdk_gl_context_make_current (self->context);
+
+  glViewport (0, 0, viewport->size.width, viewport->size.height);
+}
+
+static void
+gsk_gl_renderer_update (GskRenderer             *renderer,
+                        const graphene_matrix_t *modelview,
+                        const graphene_matrix_t *projection)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+  graphene_matrix_multiply (modelview, projection, &self->mvp);
+  graphene_frustum_init_from_matrix (&self->frustum, &self->mvp);
+}
+
+static void
+render_item_clear (gpointer data_)
+{
+}
+
+static void
+gsk_gl_renderer_add_render_item (GskGLRenderer *self,
+                                 GskRenderNode *node)
+{
+  cairo_surface_t *surface;
+  GskRenderNodeIter iter;
+  GskRenderNode *child;
+  RenderItem item;
+
+  if (gsk_render_node_is_hidden (node))
+    return;
+
+  item.node = g_object_ref (node);
+  gsk_render_node_get_bounds (node, &item.frame);
+  gsk_render_node_get_world_matrix (node, &item.modelview);
+  item.opacity = gsk_render_node_get_opacity (node);
+
+#if 0
+  /* TODO: This should really be an asset atlas */
+  surface = gsk_render_node_get_surface (node);
+  if (surface != NULL)
+    gdk_cairo_surface_to_texture (surface, &item.texture_id);
+#endif
+
+  if (gsk_render_node_is_opaque (node))
+    g_array_append_val (self->opaque_render_items, item);
+  else
+    g_array_prepend_val (self->transparent_render_items, item);
+
+  gsk_render_node_iter_init (&iter, node);
+  while (gsk_render_node_iter_next (&iter, &child))
+    gsk_gl_renderer_add_render_item (self, child);
+}
+
+static void
+gsk_gl_renderer_validate_tree (GskRenderer   *renderer,
+                               GskRenderNode *root)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+  g_array_set_size (self->opaque_render_items, 0);
+  g_array_set_size (self->transparent_render_items, 0);
+
+  gsk_gl_renderer_add_render_item (self, gsk_renderer_get_root_node (renderer));
+}
+
+static void
+gsk_gl_renderer_clear (GskRenderer *renderer)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+
+  gdk_gl_context_make_current (self->context);
+
+  glClearColor (0, 0, 0, 0);
+  glClear (GL_COLOR_BUFFER_BIT);
+}
+
+static void
+gsk_gl_renderer_render (GskRenderer *renderer)
+{
+  GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  guint i;
+
+  gdk_gl_context_make_current (self->context);
+
+  /* Opaque pass: front-to-back */
+  for (i = 0; i < self->opaque_render_items->len; i++)
+    {
+      RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i);
+    }
+
+  /* Transparent pass: back-to-front */
+  for (i = 0; i < self->transparent_render_items->len; i++)
+    {
+      RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i);
+    }
+}
+
+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)
+{
+  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);
+}
+
+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/gskrenderer.c b/gsk/gskrenderer.c
new file mode 100644
index 0000000..e415c4e
--- /dev/null
+++ b/gsk/gskrenderer.c
@@ -0,0 +1,873 @@
+#include "config.h"
+
+#include "gskrendererprivate.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>
+
+/**
+ * SECTION:GskRenderer
+ * @title: GskRenderer
+ * @Short_desc: Renders a scene with a simplified graph
+ *
+ * XXX
+ */
+
+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;
+
+  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_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.20
+   */
+  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.20
+   */
+  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.20
+   */
+  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);
+
+  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);
+
+  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);
+
+  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);
+
+  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);
+
+  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);
+
+  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);
+
+  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);
+
+  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;
+}
+
+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;
+
+  g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_VIEWPORT]);
+}
+
+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);
+}
+
+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]);
+}
+
+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);
+}
+
+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]);
+}
+
+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);
+}
+
+void
+gsk_renderer_set_root_node (GskRenderer *renderer,
+                            GskRenderNode *root)
+{
+  GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+  g_return_if_fail (GSK_IS_RENDERER (renderer));
+  g_return_if_fail (GSK_IS_RENDER_NODE (root));
+
+  if (g_set_object (&priv->root_node, root))
+    {
+      priv->needs_tree_validation = TRUE;
+      g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_ROOT_NODE]);
+    }
+}
+
+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);
+}
+
+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;
+}
+
+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));
+  g_return_if_fail (surface != NULL);
+  g_return_if_fail (!priv->is_realized);
+
+  if (priv->surface == surface)
+    return;
+
+  g_clear_pointer (&priv->surface, cairo_surface_destroy);
+  priv->surface = cairo_surface_reference (surface);
+
+  g_object_notify_by_pspec (G_OBJECT (renderer), gsk_renderer_properties[PROP_SURFACE]);
+}
+
+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_format_t format;
+
+      if (priv->use_alpha)
+        format = CAIRO_FORMAT_ARGB32;
+      else
+        format = CAIRO_FORMAT_RGB24;
+
+      priv->surface = gdk_window_create_similar_image_surface (priv->window,
+                                                               format,
+                                                               width, height,
+                                                               scale);
+    }
+
+  return priv->surface;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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 (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]);
+}
+
+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;
+}
+
+gboolean
+gsk_renderer_realize (GskRenderer *renderer)
+{
+  GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
+
+  g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
+  g_return_val_if_fail (priv->window != NULL, FALSE);
+
+  if (priv->is_realized)
+    return TRUE;
+
+  priv->is_realized = GSK_RENDERER_GET_CLASS (renderer)->realize (renderer);
+
+  return priv->is_realized;
+}
+
+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;
+}
+
+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;
+
+      /* If the target surface has been created by a window, then we
+       * need to clear it
+       */
+      if (priv->window != NULL && priv->surface != NULL)
+        g_clear_pointer (&priv->surface, cairo_surface_destroy);
+    }
+}
+
+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;
+    }
+}
+
+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->needs_tree_validation)
+    {
+      renderer_class->validate_tree (renderer, priv->root_node);
+      priv->needs_tree_validation = FALSE;
+    }
+}
+
+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);
+}
+
+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);
+
+  gsk_renderer_maybe_resize_viewport (renderer);
+
+  gsk_renderer_maybe_update (renderer);
+
+  gsk_renderer_maybe_validate_tree (renderer);
+
+  gsk_renderer_maybe_clear (renderer);
+
+  GSK_RENDERER_GET_CLASS (renderer)->render (renderer);
+}
+
+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]);
+}
+
+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;
+}
+
+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]);
+}
+
+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.20
+ */
+GskRenderer *
+gsk_renderer_get_for_display (GdkDisplay *display)
+{
+  return g_object_new (GSK_TYPE_CAIRO_RENDERER, "display", display, NULL);
+}
diff --git a/gsk/gskrenderer.h b/gsk/gskrenderer.h
new file mode 100644
index 0000000..c96a4be
--- /dev/null
+++ b/gsk/gskrenderer.h
@@ -0,0 +1,86 @@
+/* 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_20
+GType gsk_renderer_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_20
+GskRenderer *           gsk_renderer_get_for_display            (GdkDisplay              *display);
+
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_viewport               (GskRenderer             *renderer,
+                                                                 const graphene_rect_t   *viewport);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_projection             (GskRenderer             *renderer,
+                                                                 const graphene_matrix_t *projection);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_modelview              (GskRenderer             *renderer,
+                                                                 const graphene_matrix_t *modelview);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_scaling_filters        (GskRenderer             *renderer,
+                                                                 GskScalingFilter         min_filter,
+                                                                 GskScalingFilter         mag_filter);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_auto_clear             (GskRenderer             *renderer,
+                                                                 gboolean                 clear);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_root_node              (GskRenderer             *renderer,
+                                                                 GskRenderNode           *root);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_surface                (GskRenderer             *renderer,
+                                                                 cairo_surface_t         *surface);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_window                 (GskRenderer             *renderer,
+                                                                 GdkWindow               *window);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_set_use_alpha              (GskRenderer             *renderer,
+                                                                 gboolean                 use_alpha);
+
+GDK_AVAILABLE_IN_3_20
+cairo_surface_t *       gsk_renderer_get_surface                (GskRenderer             *renderer);
+
+GDK_AVAILABLE_IN_3_20
+gboolean                gsk_renderer_realize                    (GskRenderer             *renderer);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_renderer_unrealize                  (GskRenderer             *renderer);
+GDK_AVAILABLE_IN_3_20
+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..47ed218
--- /dev/null
+++ b/gsk/gskrendererprivate.h
@@ -0,0 +1,78 @@
+/* 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);
+};
+
+void gsk_renderer_get_viewport (GskRenderer *renderer,
+                                graphene_rect_t *viewport);
+void gsk_renderer_get_modelview (GskRenderer *renderer,
+                                 graphene_matrix_t *modelview);
+void gsk_renderer_get_projection (GskRenderer *renderer,
+                                  graphene_matrix_t *projection);
+void gsk_renderer_get_scaling_filters (GskRenderer *renderer,
+                                       GskScalingFilter *min_filter,
+                                       GskScalingFilter *mag_filter);
+gboolean gsk_renderer_get_auto_clear (GskRenderer *renderer);
+gboolean gsk_renderer_get_use_alpha (GskRenderer *renderer);
+
+GskRenderNode *gsk_renderer_get_root_node (GskRenderer *renderer);
+GdkDisplay *gsk_renderer_get_display (GskRenderer *renderer);
+GdkWindow *gsk_renderer_get_window (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..1a1a46a
--- /dev/null
+++ b/gsk/gskrendernode.c
@@ -0,0 +1,767 @@
+#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_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_class_init (GskRenderNodeClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = gsk_render_node_dispose;
+}
+
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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 (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;
+
+  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.20
+ */
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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.20
+ */
+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;
+}
+
+guint
+gsk_render_node_get_n_children (GskRenderNode *node)
+{
+  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0);
+
+  return node->n_children;
+}
+
+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);
+}
+
+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;
+}
+
+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);
+  node->needs_world_matrix_update = TRUE;
+}
+
+void
+gsk_render_node_set_child_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->child_transform);
+  else
+    graphene_matrix_init_from_matrix (&node->child_transform, transform);
+
+  node->child_transform_set = !graphene_matrix_is_identity (&node->child_transform);
+  node->needs_world_matrix_update = TRUE;
+}
+
+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);
+}
+
+double
+gsk_render_node_get_opacity (GskRenderNode *node)
+{
+  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), 0.0);
+
+  return node->opacity;
+}
+
+void
+gsk_render_node_set_hidden (GskRenderNode *node,
+                            gboolean       hidden)
+{
+  g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+  node->hidden = !!hidden;
+}
+
+gboolean
+gsk_render_node_is_hidden (GskRenderNode *node)
+{
+  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE);
+
+  return node->hidden;
+}
+
+void
+gsk_render_node_set_opaque (GskRenderNode *node,
+                            gboolean       opaque)
+{
+  g_return_if_fail (GSK_IS_RENDER_NODE (node));
+
+  node->opaque = !!opaque;
+}
+
+gboolean
+gsk_render_node_is_opaque (GskRenderNode *node)
+{
+  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), TRUE);
+
+  return node->opaque;
+}
+
+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;
+}
+
+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;
+}
+
+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)
+        graphene_matrix_init_from_matrix (&node->world_matrix, &node->transform);
+      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);
+}
+
+void
+gsk_render_node_set_surface (GskRenderNode   *node,
+                             cairo_surface_t *surface)
+{
+  g_return_if_fail (GSK_IS_RENDER_NODE (node));
+  g_return_if_fail (surface != NULL);
+
+  g_clear_pointer (&node->surface, cairo_surface_destroy);
+  node->surface = cairo_surface_reference (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;
+}
+
+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;
+}
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
new file mode 100644
index 0000000..43280e7
--- /dev/null
+++ b/gsk/gskrendernode.h
@@ -0,0 +1,111 @@
+/* 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_20
+GType gsk_render_node_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_new                     (void);
+
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_get_parent              (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_get_first_child         (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_get_last_child          (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_get_next_sibling        (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_get_previous_sibling    (GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_insert_child_at_pos     (GskRenderNode *node,
+                                                                 GskRenderNode *child,
+                                                                 int            index_);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_insert_child_before     (GskRenderNode *node,
+                                                                 GskRenderNode *child,
+                                                                 GskRenderNode *sibling);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_insert_child_after      (GskRenderNode *node,
+                                                                 GskRenderNode *child,
+                                                                 GskRenderNode *sibling);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_remove_child            (GskRenderNode *node,
+                                                                 GskRenderNode *child);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_replace_child           (GskRenderNode *node,
+                                                                 GskRenderNode *new_child,
+                                                                 GskRenderNode *old_child);
+GDK_AVAILABLE_IN_3_20
+GskRenderNode *         gsk_render_node_remove_all_children     (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+guint                   gsk_render_node_get_n_children          (GskRenderNode *node);
+
+GDK_AVAILABLE_IN_3_20
+gboolean                gsk_render_node_contains                (GskRenderNode *node,
+                                                                GskRenderNode *descendant);
+
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_bounds              (GskRenderNode         *node,
+                                                                 const graphene_rect_t *bounds);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_transform           (GskRenderNode           *node,
+                                                                 const graphene_matrix_t *transform);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_child_transform     (GskRenderNode           *node,
+                                                                 const graphene_matrix_t *transform);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_opacity             (GskRenderNode *node,
+                                                                 double         opacity);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_hidden              (GskRenderNode *node,
+                                                                 gboolean       hidden);
+GDK_AVAILABLE_IN_3_20
+gboolean                gsk_render_node_is_hidden               (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_opaque              (GskRenderNode *node,
+                                                                 gboolean       opaque);
+GDK_AVAILABLE_IN_3_20
+gboolean                gsk_render_node_is_opaque               (GskRenderNode *node);
+GDK_AVAILABLE_IN_3_20
+void                    gsk_render_node_set_surface             (GskRenderNode   *node,
+                                                                 cairo_surface_t *surface);
+
+G_END_DECLS
+
+#endif /* __GSK_RENDER_NODE_H__ */
diff --git a/gsk/gskrendernodeiter.c b/gsk/gskrendernodeiter.c
new file mode 100644
index 0000000..cdcf29c
--- /dev/null
+++ b/gsk/gskrendernodeiter.c
@@ -0,0 +1,148 @@
+#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))
+
+GskRenderNodeIter *
+gsk_render_node_iter_new (void)
+{
+  return g_slice_new (GskRenderNodeIter);
+}
+
+static GskRenderNodeIter *
+gsk_render_node_iter_copy (GskRenderNodeIter *src)
+{
+  return g_slice_dup (GskRenderNodeIter, src);
+}
+
+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)
+
+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;
+}
+
+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.20
+ */
+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;
+}
+
+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;
+}
+
+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..ed9f9a8
--- /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_20
+GType gsk_render_node_iter_get_type (void) G_GNUC_CONST;
+GDK_AVAILABLE_IN_3_20
+GskRenderNodeIter *gsk_render_node_iter_new (void);
+GDK_AVAILABLE_IN_3_20
+void gsk_render_node_iter_free (GskRenderNodeIter *iter);
+GDK_AVAILABLE_IN_3_20
+void gsk_render_node_iter_init (GskRenderNodeIter *iter,
+                                GskRenderNode     *node);
+
+GDK_AVAILABLE_IN_3_20
+gboolean gsk_render_node_iter_is_valid (GskRenderNodeIter *iter);
+GDK_AVAILABLE_IN_3_20
+gboolean gsk_render_node_iter_prev (GskRenderNodeIter  *iter,
+                                    GskRenderNode     **child);
+GDK_AVAILABLE_IN_3_20
+gboolean gsk_render_node_iter_next (GskRenderNodeIter  *iter,
+                                    GskRenderNode     **child);
+GDK_AVAILABLE_IN_3_20
+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..2a5a9a1
--- /dev/null
+++ b/gsk/gskrendernodeprivate.h
@@ -0,0 +1,63 @@
+#ifndef __GSK_RENDER_NODE_PRIVATE_H__
+#define __GSK_RENDER_NODE_PRIVATE_H__
+
+#include "gskrendernode.h"
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+struct _GskRenderNode
+{
+  GObject parent_instance;
+
+  GskRenderNode *parent;
+  GskRenderNode *first_child;
+  GskRenderNode *last_child;
+  GskRenderNode *prev_sibling;
+  GskRenderNode *next_sibling;
+
+  int n_children;
+  gint64 age;
+
+  cairo_surface_t *surface;
+
+  double opacity;
+
+  graphene_rect_t bounds;
+
+  graphene_matrix_t world_matrix;
+
+  graphene_matrix_t transform;
+  graphene_matrix_t child_transform;
+
+  gboolean hidden : 1;
+  gboolean opaque : 1;
+  gboolean transform_set : 1;
+  gboolean child_transform_set : 1;
+  gboolean needs_world_matrix_update : 1;
+};
+
+struct _GskRenderNodeClass
+{
+  GObjectClass parent_class;
+};
+
+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);
+
+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/tests/Makefile.am b/tests/Makefile.am
index 681807d..8ffd722 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)                 \
@@ -172,6 +175,7 @@ noinst_PROGRAMS =  $(TEST_PROGS)    \
        gdkgears                        \
        listmodel                       \
        testpopup                       \
+       testgskrenderer                 \
        $(NULL)
 
 if USE_X11
@@ -316,6 +320,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     \
@@ -556,6 +561,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..b1e74da
--- /dev/null
+++ b/tests/testgskrenderer.c
@@ -0,0 +1,178 @@
+#include "config.h"
+
+#include <graphene.h>
+#include <cairo.h>
+#include <gsk/gsk.h>
+#include <gtk/gtk.h>
+
+static cairo_surface_t *
+create_color_surface (GdkRGBA *color, int w, int h)
+{
+  cairo_surface_t *res;
+  cairo_t *cr;
+
+  res = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
+  cr = cairo_create (res);
+
+  cairo_set_source_rgb (cr, color->red, color->green, color->blue);
+  cairo_rectangle (cr, 0, 0, w, h);
+  cairo_fill (cr);
+
+  cairo_destroy (cr);
+
+  return res;
+}
+
+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;
+  cairo_surface_t *surface;
+  graphene_matrix_t ctm;
+
+  root = gsk_render_node_new ();
+  gsk_render_node_set_bounds (root, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = 120.f,
+                                .size.height = 120.f
+                              });
+  surface = create_color_surface (&(GdkRGBA) { .red = 1, .green = 0, .blue = 0, .alpha = 1 }, 120, 120);
+  gsk_render_node_set_surface (root, surface);
+  cairo_surface_destroy (surface);
+  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_bounds (node, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = 50.f,
+                                .size.height = 50.f
+                              });
+  surface = create_color_surface (&(GdkRGBA) { .red = 0, .green = 1, .blue = 0, .alpha = 1 }, 50, 50);
+  gsk_render_node_set_surface (node, surface);
+  cairo_surface_destroy (surface);
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = 10.f, .y = 10.f, .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_bounds (node, &(graphene_rect_t) {
+                                .origin.x = 0.f,
+                                .origin.y = 0.f,
+                                .size.width = 50.f,
+                                .size.height = 50.f
+                              });
+  surface = create_color_surface (&(GdkRGBA) { .red = 0, .green = 0, .blue = 1, .alpha = 1 }, 50, 50);
+  gsk_render_node_set_surface (node, surface);
+  cairo_surface_destroy (surface);
+  graphene_matrix_init_translate (&ctm, &(graphene_point3d_t) { .x = 60.f, .y = 60.f, .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.f,
+                               .origin.y = 0.f,
+                               .size.width = allocation->width,
+                               .size.height = allocation->height
+                             });
+
+  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 = (allocation->width - 120.f) / 2.f,
+                                    .y = (allocation->height - 120.f) / 2.f,
+                                    .z = 0.f
+                                  });
+  gsk_render_node_set_transform (root, &ctm);
+}
+
+static gboolean
+draw (GtkWidget *widget, cairo_t *cr)
+{
+  GskRenderer *renderer = get_renderer (widget);
+
+  gsk_renderer_render (renderer);
+
+  cairo_set_source_surface (cr, gsk_renderer_get_surface (renderer), 0, 0);
+  cairo_paint (cr);
+
+  return TRUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+  gtk_init (NULL, NULL);
+
+  GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
+  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+  GtkWidget *area = gtk_drawing_area_new ();
+  gtk_widget_set_hexpand (area, TRUE);
+  gtk_widget_set_vexpand (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_show_all (window);
+
+  gtk_main ();
+}


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