[gtk+/wip/baedert/gl: 42/90] gl renderer: Rework once more



commit 5cac946bec0dfa81169a1f8a9b15154d4c4d4075
Author: Timm Bäder <mail baedert org>
Date:   Wed Nov 22 11:32:23 2017 +0100

    gl renderer: Rework once more
    
    Last time, I swear.

 demos/widget-factory/widget-factory.ui |    2 +-
 gsk/gskgldriver.c                      |   45 +-
 gsk/gskgldriverprivate.h               |    5 +
 gsk/gskglrenderer.c                    | 1525 +++++++++++++++++---------------
 gtk/theme/Adwaita/gtk-contained.css    |    2 +-
 5 files changed, 856 insertions(+), 723 deletions(-)
---
diff --git a/demos/widget-factory/widget-factory.ui b/demos/widget-factory/widget-factory.ui
index 064c534..14d3e75 100644
--- a/demos/widget-factory/widget-factory.ui
+++ b/demos/widget-factory/widget-factory.ui
@@ -424,7 +424,7 @@ Suspendisse feugiat quam quis dolor accumsan cursus.</property>
         <property name="margin">10</property>
         <child>
           <object class="GtkStack" id="toplevel_stack">
-            <property name="transition-duration">1000</property>
+            <property name="transition-duration">30000</property>
             <child>
               <object class="GtkBox" id="page1">
                 <property name="orientation">vertical</property>
diff --git a/gsk/gskgldriver.c b/gsk/gskgldriver.c
index f74e396..9b49ebe 100644
--- a/gsk/gskgldriver.c
+++ b/gsk/gskgldriver.c
@@ -28,7 +28,8 @@ typedef struct {
   GLuint uv_id;
   GskQuadVertex *quads;
   int n_quads;
-  gboolean in_use : 1;
+  guint in_use : 1;
+  guint permanent : 1;
 } Vao;
 
 typedef struct {
@@ -267,10 +268,6 @@ gsk_gl_driver_end_frame (GskGLDriver *self)
   g_return_if_fail (GSK_IS_GL_DRIVER (self));
   g_return_if_fail (self->in_frame);
 
-  glBindTexture (GL_TEXTURE_2D, 0);
-  glUseProgram (0);
-  glBindVertexArray (0);
-
   self->bound_source_texture = NULL;
   self->bound_mask_texture = NULL;
   self->bound_vao = NULL;
@@ -570,6 +567,29 @@ find_vao (GHashTable    *vaos,
   return NULL;
 }
 
+void
+gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
+                                             int                  n_vertices,
+                                             const GskQuadVertex *quads,
+                                             int                 *out_vao_id,
+                                             int                 *out_vao_buffer_id)
+{
+  GLuint vao_id, buffer_id;
+
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  glGenBuffers (1, &buffer_id);
+  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
+  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
+
+  glBindBuffer (GL_ARRAY_BUFFER, 0);
+  glBindVertexArray (0);
+
+  *out_vao_id = buffer_id;
+  *out_vao_buffer_id = vao_id;
+}
+
 int
 gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
                                    int            position_id,
@@ -599,10 +619,13 @@ gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
   glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
   glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_vertices, quads, GL_STATIC_DRAW);
 
-  glEnableVertexAttribArray (position_id);
-  glVertexAttribPointer (position_id, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskQuadVertex),
-                         (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
+  if (position_id != -1)
+    {
+      glEnableVertexAttribArray (position_id);
+      glVertexAttribPointer (position_id, 2, GL_FLOAT, GL_FALSE,
+                             sizeof (GskQuadVertex),
+                             (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
+    }
 
   if (uv_id != -1)
     {
@@ -776,7 +799,9 @@ gsk_gl_driver_bind_vao (GskGLDriver *driver,
     {
       glBindVertexArray (v->vao_id);
       glBindBuffer (GL_ARRAY_BUFFER, v->buffer_id);
-      glEnableVertexAttribArray (v->position_id);
+
+      if (v->position_id != -1)
+        glEnableVertexAttribArray (v->position_id);
 
       if (v->uv_id != -1)
         glEnableVertexAttribArray (v->uv_id);
diff --git a/gsk/gskgldriverprivate.h b/gsk/gskgldriverprivate.h
index c26ebff..f5f9986 100644
--- a/gsk/gskgldriverprivate.h
+++ b/gsk/gskgldriverprivate.h
@@ -38,6 +38,11 @@ int             gsk_gl_driver_create_vao_for_quad       (GskGLDriver     *driver
                                                          int              uv_id,
                                                          int              n_vertices,
                                                          GskQuadVertex   *vertices);
+void           gsk_gl_driver_create_permanent_vao_for_quad (GskGLDriver        *driver,
+                                                            int                  n_vertices,
+                                                            const GskQuadVertex *quads,
+                                                            int                 *out_vao_id,
+                                                            int                 *out_vao_buffer_id);
 int             gsk_gl_driver_create_render_target      (GskGLDriver     *driver,
                                                          int              texture_id,
                                                          gboolean         add_depth_buffer,
diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c
index c4484fa..3398861 100644
--- a/gsk/gskglrenderer.c
+++ b/gsk/gskglrenderer.c
@@ -26,7 +26,25 @@
 #define ORTHO_NEAR_PLANE        -10000
 #define ORTHO_FAR_PLANE          10000
 
+#define N_VERTICES      6
+#define N_PROGRAMS      6
+
 #define HIGHLIGHT_FALLBACK 0
+#define DEBUG_OPS          0
+
+#if DEBUG_OPS
+#define OP_PRINT(format, ...) g_print(format, ## __VA_ARGS__)
+#else
+#define OP_PRINT(format, ...)
+#endif
+
+#define INIT_PROGRAM_UNIFORM_LOCATION(program_name, location_name, uniform_name) \
+              G_STMT_START{\
+                self->program_name.location_name = glGetUniformLocation(self->program_name.id, 
uniform_name);\
+                g_assert (self->program_name.location_name != 0); \
+              }G_STMT_END
+
+
 
 static void G_GNUC_UNUSED
 dump_framebuffer (const char *filename, int w, int h)
@@ -43,7 +61,7 @@ dump_framebuffer (const char *filename, int w, int h)
   g_free (data);
 }
 
-static gboolean
+static gboolean G_GNUC_UNUSED
 font_has_color_glyphs (const PangoFont *font)
 {
   cairo_scaled_font_t *scaled_font;
@@ -63,7 +81,11 @@ font_has_color_glyphs (const PangoFont *font)
 static void
 gsk_gl_renderer_setup_render_mode (GskGLRenderer *self);
 
-typedef struct {
+typedef struct
+{
+  int index;        /* Into the renderer's program array */
+  const char *name; /* For debugging */
+
   int id;
   /* Common locations (gl_common)*/
   int source_location;
@@ -71,7 +93,7 @@ typedef struct {
   int uv_location;
   int position_location;
   int alpha_location;
-  int blendMode_location;
+  int blend_mode_location;
   int viewport_location;
   int projection_location;
   int modelview_location;
@@ -79,7 +101,7 @@ typedef struct {
   int clip_corner_widths_location;
   int clip_corner_heights_location;
 
-  /* Shader-specific locations */
+  /* Program-specific locations */
   union {
     struct {
       int color_location;
@@ -103,12 +125,6 @@ typedef struct {
   };
 } Program;
 
-#define INIT_PROGRAM_UNIFORM_LOCATION(program_name, location_name, uniform_name) \
-              G_STMT_START{\
-                self->program_name.location_name = glGetUniformLocation(self->program_name.id, 
uniform_name);\
-                g_assert (self->program_name.location_name != 0); \
-              }G_STMT_END
-
 enum {
   MODE_BLIT = 1,
   MODE_COLOR,
@@ -119,96 +135,65 @@ enum {
   N_MODES
 };
 
-typedef struct {
-  int mode;
-
-  graphene_point3d_t min;
-  graphene_point3d_t max;
-
-  graphene_size_t size;
-
-  graphene_matrix_t projection;
-  graphene_matrix_t modelview;
-
-  /* (Rounded) Clip */
-  GskRoundedRect rounded_clip;
+enum {
+  OP_NONE,
+  OP_CHANGE_OPACITY         =  1,
+  OP_CHANGE_COLOR           =  2,
+  OP_CHANGE_PROJECTION      =  3,
+  OP_CHANGE_MODELVIEW       =  4,
+  OP_CHANGE_PROGRAM         =  5,
+  OP_CHANGE_RENDER_TARGET   =  6,
+  OP_CHANGE_CLIP            =  7,
+  OP_CHANGE_VIEWPORT        =  8,
+  OP_CHANGE_SOURCE_TEXTURE  =  9,
+  OP_CHANGE_VAO             =  10,
+  OP_CHANGE_LINEAR_GRADIENT =  11,
+  OP_DRAW                   =  12,
+};
 
-  float opacity;
-  float z;
+typedef struct
+{
+  guint op;
 
   union {
-    struct {
-      GdkRGBA color;
-    } color_data;
-    struct {
-      graphene_matrix_t color_matrix;
-      graphene_vec4_t color_offset;
-    } color_matrix_data;
+    float opacity;
+    graphene_matrix_t modelview; // TODO: Make both matrix members just "matrix".
+    graphene_matrix_t projection;
+    const Program *program;
+    GdkRGBA color;
+    gsize vao_offset;
+    GskQuadVertex vertex_data[N_VERTICES]; // New Quad
+    int texture_id;
+    int render_target_id;
+    GskRoundedRect clip;
+    graphene_rect_t viewport;
     struct {
       int n_color_stops;
       float color_offsets[8];
       float color_stops[4 * 8];
       graphene_point_t start_point;
       graphene_point_t end_point;
-    } linear_gradient_data;
+    } linear_gradient;
   };
-
-  const char *name;
-
-  GskBlendMode blend_mode;
-
-  /* The render target this item will draw itself on */
-  int parent_render_target;
-  /* In case this item creates a new render target, this is its id */
-  int render_target;
-  int vao_id;
-  int texture_id;
-  const Program *program;
-
-  GArray *children;
-} RenderItem;
-
-static void
-destroy_render_item (RenderItem *item)
-{
-  if (item->children)
-    g_array_unref (item->children);
-}
-
-
-enum {
-  SOURCE,
-  MASK,
-  ALPHA,
-  BLEND_MODE,
-  VIEWPORT,
-  PROJECTION,
-  MODELVIEW,
-  CLIP,
-  CLIP_CORNER_WIDTHS,
-  CLIP_CORNER_HEIGHTS,
-  N_UNIFORMS
-};
-
-enum {
-  POSITION,
-  UV,
-  N_ATTRIBUTES
-};
+} RenderOp;
 
 #ifdef G_ENABLE_DEBUG
-typedef struct {
+typedef struct
+{
   GQuark frames;
   GQuark draw_calls;
 } ProfileCounters;
 
-typedef struct {
+typedef struct
+{
   GQuark cpu_time;
   GQuark gpu_time;
 } ProfileTimers;
 #endif
 
-typedef enum {
+
+typedef enum
+{
   RENDER_FULL,
   RENDER_SCISSOR
 } RenderMode;
@@ -225,24 +210,28 @@ struct _GskGLRenderer
   guint depth_stencil_buffer;
   guint texture_id;
 
-  GQuark uniforms[N_UNIFORMS];
-  GQuark attributes[N_ATTRIBUTES];
 
   GdkGLContext *gl_context;
   GskGLDriver *gl_driver;
   GskGLProfiler *gl_profiler;
 
-  Program blend_program;
-  Program blit_program;
-  Program color_program;
-  Program coloring_program;
-  Program color_matrix_program;
-  Program linear_gradient_program;
-  Program clip_program;
+  union {
+    Program programs[N_PROGRAMS];
+    struct {
+      Program blend_program;
+      Program blit_program;
+      Program color_program;
+      Program coloring_program;
+      Program color_matrix_program;
+      Program linear_gradient_program;
+    };
+  };
 
-  GArray *render_items;
+  GArray *render_ops;
 
   GskGLGlyphCache glyph_cache;
+  int full_vao_id;
+  int full_vao_buffer_id;
 
 #ifdef G_ENABLE_DEBUG
   ProfileCounters profile_counters;
@@ -261,12 +250,270 @@ struct _GskGLRendererClass
 
 G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
 
+typedef struct
+{
+  /* Per-Program State */
+  struct {
+    GskRoundedRect clip;
+    graphene_matrix_t modelview;
+    graphene_matrix_t projection;
+    int source_texture;
+    graphene_rect_t viewport;
+  } program_state[N_PROGRAMS];
+
+  /* Current global state */
+  const Program *current_program;
+  int current_render_target;
+  int current_vao;
+  int current_texture;
+  GskRoundedRect current_clip;
+  graphene_matrix_t current_modelview;
+  graphene_matrix_t current_projection;
+  graphene_rect_t current_viewport;
+  float current_opacity;
+
+  gsize buffer_size;
+
+  GskGLRenderer *renderer;
+} RenderOpBuilder;
+
+static void
+add_program_op (RenderOpBuilder *builder,
+                const Program   *new_program)
+{
+  static const GskRoundedRect empty_clip;
+  static const graphene_matrix_t empty_matrix;
+  static const graphene_rect_t empty_rect;
+  RenderOp op;
+
+  if (builder->current_program == new_program)
+    return;
+
+  op.op = OP_CHANGE_PROGRAM;
+  op.program = new_program;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_program = new_program;
+
+  /* If the projection is not yet set for this program, we use the current one. */
+  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].projection, sizeof 
(graphene_matrix_t)) == 0 ||
+      memcmp (&builder->current_projection, &builder->program_state[new_program->index].projection, sizeof 
(graphene_matrix_t)) != 0)
+    {
+      op.op = OP_CHANGE_PROJECTION;
+      op.projection = builder->current_projection;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].projection = builder->current_projection;
+    }
+
+  if (memcmp (&empty_matrix, &builder->program_state[new_program->index].modelview, sizeof 
(graphene_matrix_t)) == 0 ||
+      memcmp (&builder->current_modelview, &builder->program_state[new_program->index].modelview, sizeof 
(graphene_matrix_t)) != 0)
+    {
+      op.op = OP_CHANGE_MODELVIEW;
+      op.modelview = builder->current_modelview;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].modelview = builder->current_modelview;
+    }
+
+  if (memcmp (&empty_rect, &builder->program_state[new_program->index].viewport, sizeof (graphene_rect_t)) 
== 0 ||
+      memcmp (&builder->current_viewport, &builder->program_state[new_program->index].viewport, sizeof 
(graphene_rect_t)) != 0)
+    {
+      op.op = OP_CHANGE_VIEWPORT;
+      op.viewport = builder->current_viewport;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].viewport = builder->current_viewport;
+    }
+
+  if (memcmp (&empty_clip, &builder->program_state[new_program->index].clip, sizeof (GskRoundedRect)) == 0 ||
+      memcmp (&builder->current_clip, &builder->program_state[new_program->index].clip, sizeof 
(GskRoundedRect)) != 0)
+    {
+      op.op = OP_CHANGE_CLIP;
+      op.clip = builder->current_clip;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].clip = builder->current_clip;
+    }
+
+  if (graphene_rect_equal (&empty_rect, &builder->program_state[new_program->index].viewport) ||
+      !graphene_rect_equal (&builder->current_viewport, 
&builder->program_state[new_program->index].viewport))
+    {
+      op.op = OP_CHANGE_VIEWPORT;
+      op.viewport = builder->current_viewport;
+      g_array_append_val (builder->renderer->render_ops, op);
+      builder->program_state[new_program->index].viewport = builder->current_viewport;
+    }
+}
+
+static GskRoundedRect
+add_clip_op (RenderOpBuilder      *builder,
+             const GskRoundedRect *new_clip)
+{
+  RenderOp op;
+  GskRoundedRect prev_clip;
+
+  op.op = OP_CHANGE_CLIP;
+  op.clip = *new_clip;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].clip = *new_clip;
+
+  prev_clip = builder->current_clip;
+  builder->current_clip = *new_clip;
+
+  return prev_clip;
+}
+
+static graphene_matrix_t
+add_modelview_op (RenderOpBuilder         *builder,
+                  const graphene_matrix_t *matrix)
+{
+  RenderOp op;
+  graphene_matrix_t prev_mv;
+  RenderOp *last_op;
+
+  last_op = &g_array_index (builder->renderer->render_ops, RenderOp, builder->renderer->render_ops->len - 1);
+  if (last_op->op == OP_CHANGE_MODELVIEW)
+    {
+      last_op->modelview = *matrix;
+    }
+  else
+    {
+      op.op = OP_CHANGE_MODELVIEW;
+      op.modelview = *matrix;
+      g_array_append_val (builder->renderer->render_ops, op);
+    }
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].modelview = *matrix;
+
+  prev_mv = builder->current_modelview;
+  builder->current_modelview = *matrix;
+
+  return prev_mv;
+}
+
+static graphene_matrix_t
+add_projection_op (RenderOpBuilder         *builder,
+                   const graphene_matrix_t *matrix)
+{
+  RenderOp op;
+  graphene_matrix_t prev_proj;
+
+  op.op = OP_CHANGE_PROJECTION;
+  op.projection = *matrix;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].projection = *matrix;
+
+  prev_proj = builder->current_projection;
+  builder->current_projection = *matrix;
+
+  return prev_proj;
+}
+
+static graphene_rect_t
+add_viewport_op (RenderOpBuilder       *builder,
+                 const graphene_rect_t *viewport)
+{
+  RenderOp op;
+  graphene_rect_t prev_viewport;
+
+  op.op = OP_CHANGE_VIEWPORT;
+  op.viewport = *viewport;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  if (builder->current_program != NULL)
+    builder->program_state[builder->current_program->index].viewport = *viewport;
+
+  prev_viewport = builder->current_viewport;
+  builder->current_viewport = *viewport;
+
+  return prev_viewport;
+}
+
+static void
+add_texture_op (RenderOpBuilder *builder,
+                int              texture_id)
+{
+  RenderOp op;
+
+  if (builder->current_texture == texture_id)
+    return;
+
+  op.op = OP_CHANGE_SOURCE_TEXTURE;
+  op.texture_id = texture_id;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_texture = texture_id;
+}
+
+static float
+add_opacity_op (RenderOpBuilder *builder,
+                float            opacity)
+{
+  RenderOp op;
+  float prev_opacity;
+
+  if (builder->current_opacity == opacity)
+    return opacity;
+
+  op.op = OP_CHANGE_OPACITY;
+  op.opacity = opacity;
+  g_array_append_val (builder->renderer->render_ops, op);
+
+  prev_opacity = builder->current_opacity;
+  builder->current_opacity = opacity;
+
+  return prev_opacity;
+}
+
+static int
+add_render_target_op (RenderOpBuilder *builder,
+                      int              render_target_id)
+{
+  RenderOp op;
+  int prev_render_target;
+
+  if (builder->current_render_target == render_target_id)
+    return render_target_id;
+
+  prev_render_target = builder->current_render_target;
+  op.op = OP_CHANGE_RENDER_TARGET;
+  op.render_target_id = render_target_id;
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->current_render_target = render_target_id;
+
+  return prev_render_target;
+}
+
+static void
+add_draw_op (RenderOpBuilder     *builder,
+             const GskQuadVertex  vertex_data[N_VERTICES])
+{
+  RenderOp op;
+  gsize offset = builder->buffer_size / sizeof (GskQuadVertex);
+
+  op.op = OP_CHANGE_VAO;
+  memcpy (&op.vertex_data, vertex_data, sizeof(GskQuadVertex) * N_VERTICES);
+  g_array_append_val (builder->renderer->render_ops, op);
+  builder->buffer_size += sizeof (GskQuadVertex) * N_VERTICES;
+
+  op.op = OP_DRAW;
+  op.vao_offset = offset;
+  g_array_append_val (builder->renderer->render_ops, op);
+}
+
+static void
+add_op (RenderOpBuilder *builder,
+        const RenderOp  *op)
+{
+  g_array_append_val (builder->renderer->render_ops, *op);
+}
+
 static void
 gsk_gl_renderer_dispose (GObject *gobject)
 {
   GskGLRenderer *self = GSK_GL_RENDERER (gobject);
 
-  g_clear_pointer (&self->render_items, g_array_unref);
+  g_clear_pointer (&self->render_ops, g_array_unref);
 
   G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
 }
@@ -324,31 +571,19 @@ init_common_locations (GskGLRenderer    *self,
                        GskShaderBuilder *builder,
                        Program          *prog)
 {
-  prog->source_location =
-    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[SOURCE]);
-  prog->mask_location =
-    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[MASK]);
-  prog->alpha_location =
-    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[ALPHA]);
-  prog->blendMode_location =
-    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[BLEND_MODE]);
-  prog->viewport_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                     self->uniforms[VIEWPORT]);
-  prog->projection_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                       self->uniforms[PROJECTION]);
-  prog->modelview_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                      self->uniforms[MODELVIEW]);
-  prog->clip_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                 self->uniforms[CLIP]);
-  prog->clip_corner_widths_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                               
self->uniforms[CLIP_CORNER_WIDTHS]);
-  prog->clip_corner_heights_location = gsk_shader_builder_get_uniform_location (builder, prog->id,
-                                                                               
self->uniforms[CLIP_CORNER_HEIGHTS]);
-
-  prog->position_location =
-    gsk_shader_builder_get_attribute_location (builder, prog->id, self->attributes[POSITION]);
-  prog->uv_location =
-    gsk_shader_builder_get_attribute_location (builder, prog->id, self->attributes[UV]);
+  prog->source_location = glGetUniformLocation (prog->id, "uSource");
+  prog->mask_location = glGetUniformLocation (prog->id, "uMask");
+  prog->alpha_location = glGetUniformLocation (prog->id, "uAlpha");
+  prog->blend_mode_location = glGetUniformLocation (prog->id, "uBlendMode");
+  prog->viewport_location = glGetUniformLocation (prog->id, "uViewport");
+  prog->projection_location = glGetUniformLocation (prog->id, "uProjection");
+  prog->modelview_location = glGetUniformLocation (prog->id, "uModelview");
+  prog->clip_location = glGetUniformLocation (prog->id, "uClip");
+  prog->clip_corner_widths_location = glGetUniformLocation (prog->id, "uClipCornerWidths");
+  prog->clip_corner_heights_location = glGetUniformLocation (prog->id, "uClipCornerHeights");
+
+  prog->position_location = glGetAttribLocation (prog->id, "aPosition");
+  prog->uv_location = glGetAttribLocation (prog->id, "aUv");
 }
 
 static gboolean
@@ -363,20 +598,6 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
 
   gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl");
 
-  self->uniforms[SOURCE] = gsk_shader_builder_add_uniform (builder, "uSource");
-  self->uniforms[MASK] = gsk_shader_builder_add_uniform (builder, "uMask");
-  self->uniforms[ALPHA] = gsk_shader_builder_add_uniform (builder, "uAlpha");
-  self->uniforms[BLEND_MODE] = gsk_shader_builder_add_uniform (builder, "uBlendMode");
-  self->uniforms[VIEWPORT] = gsk_shader_builder_add_uniform (builder, "uViewport");
-  self->uniforms[PROJECTION] = gsk_shader_builder_add_uniform (builder, "uProjection");
-  self->uniforms[MODELVIEW] = gsk_shader_builder_add_uniform (builder, "uModelview");
-  self->uniforms[CLIP] = gsk_shader_builder_add_uniform (builder, "uClip");
-  self->uniforms[CLIP_CORNER_WIDTHS] = gsk_shader_builder_add_uniform (builder, "uClipCornerWidths");
-  self->uniforms[CLIP_CORNER_HEIGHTS] = gsk_shader_builder_add_uniform (builder, "uClipCornerHeights");
-
-  self->attributes[POSITION] = gsk_shader_builder_add_attribute (builder, "aPosition");
-  self->attributes[UV] = gsk_shader_builder_add_attribute (builder, "aUv");
-
   if (gdk_gl_context_get_use_es (self->gl_context))
     {
       gsk_shader_builder_set_version (builder, SHADER_VERSION_GLES);
@@ -411,8 +632,9 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
     gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
 #endif
 
-  self->blend_program.id = 
-    gsk_shader_builder_create_program (builder, "blend.vs.glsl", "blend.fs.glsl", &shader_error);
+  self->blend_program.id =  gsk_shader_builder_create_program (builder,
+                                                               "blend.vs.glsl", "blend.fs.glsl",
+                                                               &shader_error);
   if (shader_error != NULL)
     {
       g_propagate_prefixed_error (error,
@@ -420,10 +642,12 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'blend' program: ");
       goto out;
     }
+  self->blend_program.index = 0;
   init_common_locations (self, builder, &self->blend_program);
 
-  self->blit_program.id =
-    gsk_shader_builder_create_program (builder, "blit.vs.glsl", "blit.fs.glsl", &shader_error);
+  self->blit_program.id = gsk_shader_builder_create_program (builder,
+                                                             "blit.vs.glsl", "blit.fs.glsl",
+                                                             &shader_error);
   if (shader_error != NULL)
     {
       g_propagate_prefixed_error (error,
@@ -431,10 +655,12 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'blit' program: ");
       goto out;
     }
+  self->blit_program.index = 1;
   init_common_locations (self, builder, &self->blit_program);
 
-  self->color_program.id =
-    gsk_shader_builder_create_program (builder, "blit.vs.glsl", "color.fs.glsl", &shader_error);
+  self->color_program.id = gsk_shader_builder_create_program (builder,
+                                                              "blit.vs.glsl", "color.fs.glsl",
+                                                              &shader_error);
   if (shader_error != NULL)
     {
       g_propagate_prefixed_error (error,
@@ -442,12 +668,12 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'color' program: ");
       goto out;
     }
+  self->color_program.index = 2;
   init_common_locations (self, builder, &self->color_program);
   INIT_PROGRAM_UNIFORM_LOCATION (color_program, color_location, "uColor");
 
   self->coloring_program.id = gsk_shader_builder_create_program (builder,
-                                                                 "blit.vs.glsl",
-                                                                 "coloring.fs.glsl",
+                                                                 "blit.vs.glsl", "coloring.fs.glsl",
                                                                  &shader_error);
   if (shader_error != NULL)
     {
@@ -456,12 +682,12 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'coloring' program: ");
       goto out;
     }
+  self->coloring_program.index = 3;
   init_common_locations (self, builder, &self->coloring_program);
   INIT_PROGRAM_UNIFORM_LOCATION (coloring_program, color_location, "uColor");
 
   self->color_matrix_program.id = gsk_shader_builder_create_program (builder,
-                                                                     "blit.vs.glsl",
-                                                                     "color_matrix.fs.glsl",
+                                                                     "blit.vs.glsl", "color_matrix.fs.glsl",
                                                                      &shader_error);
   if (shader_error != NULL)
     {
@@ -470,13 +696,13 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'color_matrix' program: ");
       goto out;
     }
+  self->color_matrix_program.index = 4;
   init_common_locations (self, builder, &self->color_matrix_program);
   INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_matrix_location, "uColorMatrix");
   INIT_PROGRAM_UNIFORM_LOCATION (color_matrix_program, color_offset_location, "uColorOffset");
 
   self->linear_gradient_program.id = gsk_shader_builder_create_program (builder,
-                                                                        "blit.vs.glsl",
-                                                                        "linear_gradient.fs.glsl",
+                                                                        "blit.vs.glsl", 
"linear_gradient.fs.glsl",
                                                                         &shader_error);
   if (shader_error != NULL)
     {
@@ -485,6 +711,7 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
                                   "Unable to create 'linear_gradient' program: ");
       goto out;
     }
+  self->linear_gradient_program.index = 5;
   init_common_locations (self, builder, &self->linear_gradient_program);
   INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_stops_location, "uColorStops");
   INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, color_offsets_location, "uColorOffsets");
@@ -506,6 +733,16 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
                          GError      **error)
 {
   GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GskQuadVertex vertex_data[N_VERTICES] = {
+    { { 0, 0 }, { 0, 0 }, },
+    { { 0, 1 }, { 0, 1 }, },
+    { { 1, 0 }, { 1, 0 }, },
+
+    { { 1, 1 }, { 1, 1 }, },
+    { { 0, 1 }, { 0, 1 }, },
+    { { 1, 0 }, { 1, 0 }, },
+  };
+
 
   self->scale_factor = gdk_window_get_scale_factor (window);
 
@@ -534,6 +771,9 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
 
   gsk_gl_glyph_cache_init (&self->glyph_cache, self->gl_driver);
 
+  gsk_gl_driver_create_permanent_vao_for_quad (self->gl_driver, N_VERTICES, vertex_data,
+                                               &self->full_vao_id, &self->full_vao_buffer_id);
+
   return TRUE;
 }
 
@@ -550,7 +790,7 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
   /* We don't need to iterate to destroy the associated GL resources,
    * as they will be dropped when we finalize the GskGLDriver
    */
-  g_array_set_size (self->render_items, 0);
+  g_array_set_size (self->render_ops, 0);
 
 
   glDeleteProgram (self->blend_program.id);
@@ -634,173 +874,6 @@ gsk_gl_renderer_resize_viewport (GskGLRenderer         *self,
   glViewport (0, 0, width, height);
 }
 
-#define N_VERTICES      6
-
-static void
-render_item (GskGLRenderer    *self,
-             const RenderItem *item)
-{
-  float mat[16];
-  const gboolean draw_children = item->children != NULL &&
-                                 item->children->len > 0;
-  const gboolean drawing_offscreen = item->parent_render_target != 0;
-
-  /*g_message ("Rendering %s with %u children and parent render target %d", item->name, item->children ? 
item->children->len : 0, item->parent_render_target);*/
-
-  if (draw_children)
-    {
-      guint i;
-      guint p;
-      graphene_rect_t prev_viewport;
-
-      prev_viewport = self->viewport;
-
-      gsk_gl_driver_bind_render_target (self->gl_driver, item->render_target);
-      glDisable (GL_SCISSOR_TEST);
-      glClearColor (0.0, 0.0, 0.0, 0.0);
-      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-      graphene_rect_init (&self->viewport,
-                          item->min.x, item->min.y, item->size.width * self->scale_factor, item->size.height 
* self->scale_factor);
-      glViewport (0, 0, item->size.width * self->scale_factor, item->size.height * self->scale_factor);
-
-      p = item->children->len;
-      for (i = 0; i < p; i ++)
-        {
-          const RenderItem *child = &g_array_index (item->children, RenderItem, i);
-
-          g_assert (child->parent_render_target == item->render_target);
-          render_item (self, child);
-        }
-
-      /* At this point, all the child items should've been drawn */
-      gsk_gl_driver_bind_render_target (self->gl_driver, 0);
-      /* TODO: Manage pre-/post-framebuffer-state in the driver? */
-      /* Resets the scissor test, etc. */
-      if (!drawing_offscreen)
-        gsk_gl_renderer_setup_render_mode (self);
-
-      graphene_rect_init_from_rect (&self->viewport, &prev_viewport);
-      glViewport (0, 0, self->viewport.size.width, self->viewport.size.height);
-    }
-
-
-  if (drawing_offscreen)
-    g_assert (gsk_gl_driver_bind_render_target (self->gl_driver, item->parent_render_target));
-
-  glUseProgram (item->program->id);
-
-  switch(item->mode)
-    {
-      case MODE_COLOR:
-        {
-          glUniform4f (item->program->color_location,
-                       item->color_data.color.red,
-                       item->color_data.color.green,
-                       item->color_data.color.blue,
-                       item->color_data.color.alpha);
-        }
-      break;
-
-      case MODE_COLORING:
-        {
-          glUniform4f (item->program->color_location,
-                       item->color_data.color.red,
-                       item->color_data.color.green,
-                       item->color_data.color.blue,
-                       item->color_data.color.alpha);
-          g_assert(item->texture_id != 0);
-          /* Use texture unit 0 for the source */
-          glUniform1i (item->program->source_location, 0);
-          gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id);
-        }
-      break;
-
-      case MODE_TEXTURE:
-        {
-          g_assert(item->texture_id != 0);
-          /* Use texture unit 0 for the source */
-          glUniform1i (item->program->source_location, 0);
-          gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id);
-        }
-      break;
-
-      case MODE_BLIT:
-        g_assert (item->program == &self->blit_program);
-        glUniform1i (item->program->source_location, 0);
-        if (item->render_target != 0)
-          gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_target);
-        else
-          gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id);
-      break;
-
-      case MODE_COLOR_MATRIX:
-        {
-          float vec[4];
-
-          glUniform1i (item->program->source_location, 0);
-          if (item->render_target != 0)
-            gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_target);
-          else
-            gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id);
-
-          graphene_matrix_to_float (&item->color_matrix_data.color_matrix, mat);
-          glUniformMatrix4fv (item->program->color_matrix_location, 1, GL_FALSE, mat);
-
-          graphene_vec4_to_float (&item->color_matrix_data.color_offset, vec);
-          glUniform4fv (item->program->color_offset_location, 1, vec);
-        }
-      break;
-
-      case MODE_LINEAR_GRADIENT:
-        {
-          glUniform1i (item->program->n_color_stops_location,
-                       item->linear_gradient_data.n_color_stops);
-          glUniform4fv (item->program->color_stops_location,
-                        item->linear_gradient_data.n_color_stops,
-                        item->linear_gradient_data.color_stops);
-          glUniform1fv (item->program->color_offsets_location,
-                        item->linear_gradient_data.n_color_stops,
-                        item->linear_gradient_data.color_offsets);
-          glUniform2f (item->program->start_point_location,
-                       item->linear_gradient_data.start_point.x, item->linear_gradient_data.start_point.y);
-          glUniform2f (item->program->end_point_location,
-                       item->linear_gradient_data.end_point.x, item->linear_gradient_data.end_point.y);
-        }
-      break;
-
-      default:
-        g_assert_not_reached ();
-    }
-
-  /* Common uniforms */
-  graphene_matrix_to_float (&item->projection, mat);
-  glUniformMatrix4fv (item->program->projection_location, 1, GL_FALSE, mat);
-
-  graphene_matrix_to_float (&item->modelview, mat);
-  glUniformMatrix4fv (item->program->modelview_location, 1, GL_FALSE, mat);
-
-  glUniform1f (item->program->alpha_location, item->opacity);
-  glUniform4f (item->program->viewport_location,
-               self->viewport.origin.x, self->viewport.origin.y,
-               self->viewport.size.width, self->viewport.size.height);
-  glUniform4f (item->program->clip_location,
-               item->rounded_clip.bounds.origin.x, item->rounded_clip.bounds.origin.y,
-               item->rounded_clip.bounds.size.width, item->rounded_clip.bounds.size.height);
-
-  glUniform4f (item->program->clip_corner_widths_location,
-               MAX (item->rounded_clip.corner[0].width, 1),
-               MAX (item->rounded_clip.corner[1].width, 1),
-               MAX (item->rounded_clip.corner[2].width, 1),
-               MAX (item->rounded_clip.corner[3].width, 1));
-  glUniform4f (item->program->clip_corner_heights_location,
-               MAX (item->rounded_clip.corner[0].height, 1),
-               MAX (item->rounded_clip.corner[1].height, 1),
-               MAX (item->rounded_clip.corner[2].height, 1),
-               MAX (item->rounded_clip.corner[3].height, 1));
-
-  gsk_gl_driver_bind_vao (self->gl_driver, item->vao_id);
-  glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
-}
 
 static void
 get_gl_scaling_filters (GskRenderNode *node,
@@ -811,310 +884,306 @@ get_gl_scaling_filters (GskRenderNode *node,
   *mag_filter_r = GL_NEAREST;
 }
 
-static float
-project_item (const graphene_matrix_t *projection,
-              const graphene_matrix_t *modelview)
+static void
+gsk_gl_renderer_clear_tree (GskGLRenderer *self)
 {
-  graphene_vec4_t vec;
+  int removed_textures, removed_vaos;
 
-  graphene_matrix_get_row (modelview, 3, &vec);
-  graphene_matrix_transform_vec4 (projection, &vec, &vec);
+  if (self->gl_context == NULL)
+    return;
+
+  gdk_gl_context_make_current (self->gl_context);
+
+  g_array_remove_range (self->render_ops, 0, self->render_ops->len);
+
+  removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
+  removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver);
 
-  return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec);
+  GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n",
+                             removed_textures,
+                             removed_vaos));
 }
 
 static void
-init_framebuffer_for_node (GskGLRenderer           *self,
-                           RenderItem              *item,
-                           GskRenderNode           *node,
-                           const graphene_matrix_t *projection,
-                           graphene_matrix_t       *out_projection)
+gsk_gl_renderer_clear (GskGLRenderer *self)
 {
-  item->render_target = gsk_gl_driver_create_texture (self->gl_driver,
-                                                      item->size.width  * self->scale_factor,
-                                                      item->size.height * self->scale_factor);
-  gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_target);
-  gsk_gl_driver_init_texture_empty (self->gl_driver, item->render_target);
-  gsk_gl_driver_create_render_target (self->gl_driver, item->render_target, TRUE, TRUE);
-
-  item->children = g_array_new (FALSE, FALSE, sizeof (RenderItem));
-  g_array_set_clear_func (item->children, (GDestroyNotify)destroy_render_item);
-
-  g_assert (projection != NULL);
-  graphene_matrix_init_ortho (out_projection,
-                              item->min.x,
-                              item->min.x + item->size.width  * self->scale_factor,
-                              item->min.y,
-                              item->min.y + item->size.height * self->scale_factor,
-                              ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
-  graphene_matrix_scale (out_projection, 1, -1, 1);
+  GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
+  glClearColor (0, 0, 0, 0);
+  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 }
 
 static void
-gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
-                                 const graphene_matrix_t *projection,
-                                 const graphene_matrix_t *modelview,
-                                 GArray                  *render_items,
-                                 GskRenderNode           *node,
-                                 int                      render_target,
-                                 const GskRoundedRect    *parent_clip)
+gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
 {
-  RenderItem item;
+  switch (self->render_mode)
+  {
+    case RENDER_FULL:
+      glDisable (GL_SCISSOR_TEST);
+      break;
 
-  /* A few of the render nodes don't actually result in a RenderItem, they just
-   * change the current state and pass it down. */
-  /* We handle container nodes here directly */
-  switch ((guint)gsk_render_node_get_node_type (node))
-    {
-    case GSK_CONTAINER_NODE:
+    case RENDER_SCISSOR:
       {
-        guint i, p;
+        GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
+        GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
+        cairo_region_t *clip = gdk_drawing_context_get_clip (context);
+        cairo_rectangle_int_t extents;
+        int window_height;
 
-        for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i++)
+        /* Fall back to RENDER_FULL */
+        if (clip == NULL)
           {
-            GskRenderNode *child = gsk_container_node_get_child (node, i);
-            gsk_gl_renderer_add_render_item (self, projection, modelview, render_items,
-                                             child, render_target, parent_clip);
+            glDisable (GL_SCISSOR_TEST);
+            return;
           }
-      }
-    return;
 
-    case GSK_TRANSFORM_NODE:
-      {
-        graphene_matrix_t transform, transformed_mv;
-
-        graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
-        graphene_matrix_multiply (&transform, modelview, &transformed_mv);
-        gsk_gl_renderer_add_render_item (self, projection, &transformed_mv, render_items,
-                                         gsk_transform_node_get_child (node),
-                                         render_target,
-                                         parent_clip);
-      }
-    return;
-
-    case GSK_CLIP_NODE:
-      {
-        GskRenderNode *child = gsk_clip_node_get_child (node);
-        graphene_rect_t transformed_clip;
-        graphene_rect_t intersection;
-        GskRoundedRect child_clip;
-
-        transformed_clip = *gsk_clip_node_peek_clip (node);
-        graphene_matrix_transform_bounds (modelview, &transformed_clip, &transformed_clip);
-
-        /* Since we do the intersection here, we also need to transform by the modelview matrix.
-         * We can't do it in the shader. Same with rounded clips */
-        graphene_rect_intersection (&transformed_clip,
-                                    &parent_clip->bounds,
-                                    &intersection);
-
-        gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child,
-                                         render_target, &child_clip);
-      }
-    return;
+        g_assert (cairo_region_num_rectangles (clip) == 1);
 
-    case GSK_ROUNDED_CLIP_NODE:
-      {
-        GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
-        const GskRoundedRect *rounded_clip = gsk_rounded_clip_node_peek_clip (node);
-        graphene_rect_t transformed_clip;
-        graphene_rect_t intersection;
-        GskRoundedRect child_clip;
+        window_height = gdk_window_get_height (window) * self->scale_factor;
 
-        transformed_clip = rounded_clip->bounds;
-        graphene_matrix_transform_bounds (modelview, &transformed_clip, &transformed_clip);
+        /*cairo_region_get_extents (clip, &extents);*/
+        cairo_region_get_rectangle (clip, 0, &extents);
 
-        graphene_rect_intersection (&transformed_clip, &parent_clip->bounds,
-                                    &intersection);
-        gsk_rounded_rect_init (&child_clip, &intersection,
-                               &rounded_clip->corner[0],
-                               &rounded_clip->corner[1],
-                               &rounded_clip->corner[2],
-                               &rounded_clip->corner[3]);
+        glEnable (GL_SCISSOR_TEST);
+        glScissor (extents.x * self->scale_factor,
+                   window_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor),
+                   extents.width * self->scale_factor,
+                   extents.height * self->scale_factor);
 
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child,
-                                         render_target, &child_clip);
+        cairo_region_destroy (clip);
+        break;
       }
-    return;
-
-    default: {}
-    }
 
-  memset (&item, 0, sizeof (RenderItem));
-
-  item.name = node->name != NULL ? node->name : "unnamed";
-
-
-  item.size.width = node->bounds.size.width;
-  item.size.height = node->bounds.size.height;
-  /* Each render item is an axis-aligned bounding box that we
-   * transform using the given transformation matrix
-   */
-  item.min.x = node->bounds.origin.x;
-  item.min.y = node->bounds.origin.y;
-
-  item.max.x = item.min.x + node->bounds.size.width;
-  item.max.y = item.min.y + node->bounds.size.height;
-
-  item.z = project_item (projection, modelview);
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+}
 
-  item.opacity = 1.0;
-  item.projection = *projection;
-  item.modelview = *modelview;
-  item.rounded_clip = *parent_clip;
 
-  item.blend_mode = GSK_BLEND_MODE_DEFAULT;
+static void
+gsk_gl_renderer_add_render_ops (GskGLRenderer   *self,
+                                GskRenderNode   *node,
+                                RenderOpBuilder *builder)
+{
+  float min_x = node->bounds.origin.x;
+  float min_y = node->bounds.origin.y;
+  float max_x = min_x + node->bounds.size.width;
+  float max_y = min_y + node->bounds.size.height;
+
+  /* Default vertex data */
+  GskQuadVertex vertex_data[N_VERTICES] = {
+    { { min_x, min_y }, { 0, 0 }, },
+    { { min_x, max_y }, { 0, 1 }, },
+    { { max_x, min_y }, { 1, 0 }, },
+
+    { { max_x, max_y }, { 1, 1 }, },
+    { { min_x, max_y }, { 0, 1 }, },
+    { { max_x, min_y }, { 1, 0 }, },
+  };
 
-  item.parent_render_target = render_target;
+  /*if (gsk_render_node_get_node_type (node) != GSK_CONTAINER_NODE)*/
+    /*g_message ("Adding ops for node %s with type %u", node->name,*/
+               /*gsk_render_node_get_node_type (node));*/
 
-  item.mode = MODE_BLIT;
-  item.program = &self->blit_program;
-  item.render_target = 0;
-  item.children = NULL;
 
   switch (gsk_render_node_get_node_type (node))
     {
-    case GSK_OPACITY_NODE:
-      {
-        GskRenderNode *child = gsk_opacity_node_get_child (node);
+    case GSK_NOT_A_RENDER_NODE:
+      g_assert_not_reached ();
 
-        if (gsk_render_node_get_node_type (child) != GSK_TEXTURE_NODE)
-          {
-            graphene_matrix_t p;
-            graphene_matrix_t identity;
+    case GSK_CONTAINER_NODE:
+      {
+        guint i, p;
 
-            graphene_matrix_init_identity (&identity);
-            init_framebuffer_for_node (self, &item, node, projection, &p);
-            gsk_gl_renderer_add_render_item (self, &p, &identity, item.children, child,
-                                             item.render_target, parent_clip);
-          }
-        else
+        for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i ++)
           {
-            GdkTexture *texture = gsk_texture_node_get_texture (child);
-            int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
-
-            get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
+            GskRenderNode *child = gsk_container_node_get_child (node, i);
 
-            item.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                                     texture,
-                                                                     gl_min_filter,
-                                                                     gl_mag_filter);
+            gsk_gl_renderer_add_render_ops (self, child, builder);
           }
-
-        item.mode = MODE_BLIT;
-        item.opacity = gsk_opacity_node_get_opacity (node);
       }
-      break;
+    break;
 
-    case GSK_COLOR_MATRIX_NODE:
+    case GSK_COLOR_NODE:
       {
-        GskRenderNode *child = gsk_color_matrix_node_get_child (node);
-
-        if (gsk_render_node_get_node_type (child) != GSK_TEXTURE_NODE)
-          {
-            graphene_matrix_t p;
-            graphene_matrix_t identity;
-
-            graphene_matrix_init_identity (&identity);
-            init_framebuffer_for_node (self, &item, node, projection, &p);
-            gsk_gl_renderer_add_render_item (self, &p, &identity, item.children, child,
-                                             item.render_target, parent_clip);
-          }
-        else
-          {
-            GdkTexture *texture = gsk_texture_node_get_texture (child);
-            int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
-
-            get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
+        RenderOp op;
 
-            item.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                                     texture,
-                                                                     gl_min_filter,
-                                                                     gl_mag_filter);
-          }
-
-        item.mode = MODE_COLOR_MATRIX;
-        item.program = &self->color_matrix_program;
-        item.color_matrix_data.color_matrix = *gsk_color_matrix_node_peek_color_matrix (node);
-        item.color_matrix_data.color_offset = *gsk_color_matrix_node_peek_color_offset (node);
+        add_program_op (builder, &self->color_program);
+        op.op = OP_CHANGE_COLOR;
+        op.color = *gsk_color_node_peek_color (node);
+        add_op (builder, &op);
+        add_draw_op (builder, vertex_data);
       }
-      break;
+    break;
 
     case GSK_TEXTURE_NODE:
       {
         GdkTexture *texture = gsk_texture_node_get_texture (node);
         int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
+        int texture_id;
 
         get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
 
-        item.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                                 texture,
-                                                                 gl_min_filter,
-                                                                 gl_mag_filter);
-        item.mode = MODE_TEXTURE;
+        texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
+                                                            texture,
+                                                            gl_min_filter,
+                                                            gl_mag_filter);
+        add_program_op (builder, &self->blit_program);
+        add_texture_op (builder, texture_id);
+        add_draw_op (builder, vertex_data);
       }
-      break;
+    break;
 
-    case GSK_CAIRO_NODE:
+    case GSK_TRANSFORM_NODE:
       {
-        const cairo_surface_t *surface = gsk_cairo_node_peek_surface (node);
-        int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
+        GskRenderNode *child = gsk_transform_node_get_child (node);
+        graphene_matrix_t prev_mv;
+        graphene_matrix_t transform, transformed_mv;
 
-        if (surface == NULL)
-          return;
+        graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
+        graphene_matrix_multiply (&transform, &builder->current_modelview, &transformed_mv);
+        prev_mv = add_modelview_op (builder, &transformed_mv);
 
-        get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
+        gsk_gl_renderer_add_render_ops (self, child, builder);
 
-        item.texture_id = gsk_gl_driver_create_texture (self->gl_driver,
-                                                        item.size.width,
-                                                        item.size.height);
-        gsk_gl_driver_bind_source_texture (self->gl_driver, item.texture_id);
-        gsk_gl_driver_init_texture_with_surface (self->gl_driver,
-                                                 item.texture_id,
-                                                 (cairo_surface_t *)surface,
-                                                 gl_min_filter,
-                                                 gl_mag_filter);
-        item.mode = MODE_TEXTURE;
+        add_modelview_op (builder, &prev_mv);
       }
-      break;
+    break;
 
-    case GSK_COLOR_NODE:
+    case GSK_OPACITY_NODE:
       {
-        item.mode = MODE_COLOR;
-        item.program = &self->color_program;
-        item.color_data.color= *gsk_color_node_peek_color (node);
+        int render_target;
+        int texture;
+        int prev_render_target;
+        float prev_opacity;
+        graphene_matrix_t identity;
+        graphene_matrix_t prev_projection;
+        graphene_matrix_t prev_modelview;
+        graphene_rect_t prev_viewport;
+        graphene_matrix_t item_proj;
+        GskQuadVertex vertex_data[N_VERTICES] = {
+          { { min_x, min_y }, { 0, 1 }, },
+          { { min_x, max_y }, { 0, 0 }, },
+          { { max_x, min_y }, { 1, 1 }, },
+
+          { { max_x, max_y }, { 1, 0 }, },
+          { { min_x, max_y }, { 0, 0 }, },
+          { { max_x, min_y }, { 1, 1 }, },
+        };
+
+        texture = gsk_gl_driver_create_texture (self->gl_driver,
+                                                node->bounds.size.width,
+                                                node->bounds.size.height);
+        gsk_gl_driver_bind_source_texture (self->gl_driver, texture);
+        gsk_gl_driver_init_texture_empty (self->gl_driver, texture);
+        render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture, TRUE, TRUE);
+
+        graphene_matrix_init_ortho (&item_proj,
+                                    min_x, max_x,
+                                    min_y, max_y,
+                                    ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
+        graphene_matrix_scale (&item_proj, 1, -1, 1);
+        graphene_matrix_init_identity (&identity);
+
+        prev_render_target = add_render_target_op (builder, render_target);
+        prev_projection = add_projection_op (builder, &item_proj);
+        prev_modelview = add_modelview_op (builder, &identity);
+        prev_viewport = add_viewport_op (builder, &node->bounds);
+
+        gsk_gl_renderer_add_render_ops (self, gsk_opacity_node_get_child (node), builder);
+
+        add_viewport_op (builder, &prev_viewport);
+        add_modelview_op (builder, &prev_modelview);
+        add_projection_op (builder, &prev_projection);
+        add_render_target_op (builder, prev_render_target);
+
+        add_program_op (builder, &self->blit_program);
+        prev_opacity = add_opacity_op (builder, gsk_opacity_node_get_opacity (node));
+        add_texture_op (builder, texture);
+        add_draw_op (builder, vertex_data);
+        add_opacity_op (builder, prev_opacity);
       }
-      break;
+    break;
 
     case GSK_LINEAR_GRADIENT_NODE:
       {
+        RenderOp op;
         int n_color_stops = MIN (8, gsk_linear_gradient_node_get_n_color_stops (node));
         const GskColorStop *stops = gsk_linear_gradient_node_peek_color_stops (node);
         const graphene_point_t *start = gsk_linear_gradient_node_peek_start (node);
         const graphene_point_t *end = gsk_linear_gradient_node_peek_end (node);
         int i;
 
-        item.mode = MODE_LINEAR_GRADIENT;
-        item.program = &self->linear_gradient_program;
-
         for (i = 0; i < n_color_stops; i ++)
           {
             const GskColorStop *stop = stops + i;
 
-            item.linear_gradient_data.color_stops[(i * 4) + 0] = stop->color.red;
-            item.linear_gradient_data.color_stops[(i * 4) + 1] = stop->color.green;
-            item.linear_gradient_data.color_stops[(i * 4) + 2] = stop->color.blue;
-            item.linear_gradient_data.color_stops[(i * 4) + 3] = stop->color.alpha;
-            item.linear_gradient_data.color_offsets[i] = stop->offset;
+            op.linear_gradient.color_stops[(i * 4) + 0] = stop->color.red;
+            op.linear_gradient.color_stops[(i * 4) + 1] = stop->color.green;
+            op.linear_gradient.color_stops[(i * 4) + 2] = stop->color.blue;
+            op.linear_gradient.color_stops[(i * 4) + 3] = stop->color.alpha;
+            op.linear_gradient.color_offsets[i] = stop->offset;
           }
 
-        item.linear_gradient_data.n_color_stops = n_color_stops;
-        item.linear_gradient_data.start_point = *start;
-        item.linear_gradient_data.end_point = *end;
+        add_program_op (builder, &self->linear_gradient_program);
+        op.op = OP_CHANGE_LINEAR_GRADIENT;
+        op.linear_gradient.n_color_stops = n_color_stops;
+        op.linear_gradient.start_point = *start;
+        op.linear_gradient.end_point = *end;
+        add_op (builder, &op);
+
+        add_draw_op (builder, vertex_data);
       }
-      break;
+    break;
+
+    case GSK_CLIP_NODE:
+      {
+        GskRoundedRect prev_clip;
+        GskRenderNode *child = gsk_clip_node_get_child (node);
+        graphene_rect_t transformed_clip;
+        graphene_rect_t intersection;
+        GskRoundedRect child_clip;
+
+        transformed_clip = *gsk_clip_node_peek_clip (node);
+        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
+
+        graphene_rect_intersection (&transformed_clip,
+                                    &builder->current_clip.bounds,
+                                    &intersection);
+
+        gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f);
+
+        prev_clip = add_clip_op (builder, &child_clip);
+        gsk_gl_renderer_add_render_ops (self, child, builder);
+        add_clip_op (builder, &prev_clip);
+      }
+    break;
+
+    case GSK_ROUNDED_CLIP_NODE:
+      {
+        GskRoundedRect prev_clip;
+        GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
+        const GskRoundedRect *rounded_clip = gsk_rounded_clip_node_peek_clip (node);
+        graphene_rect_t transformed_clip;
+        graphene_rect_t intersection;
+        GskRoundedRect child_clip;
+
+        transformed_clip = rounded_clip->bounds;
+        graphene_matrix_transform_bounds (&builder->current_modelview, &transformed_clip, &transformed_clip);
+
+        graphene_rect_intersection (&transformed_clip, &builder->current_clip.bounds,
+                                    &intersection);
+        gsk_rounded_rect_init (&child_clip, &intersection,
+                               &rounded_clip->corner[0],
+                               &rounded_clip->corner[1],
+                               &rounded_clip->corner[2],
+                               &rounded_clip->corner[3]);
+
+        prev_clip = add_clip_op (builder, &child_clip);
+        gsk_gl_renderer_add_render_ops (self, child, builder);
+        add_clip_op (builder, &prev_clip);
+      }
+    break;
 
     case GSK_TEXT_NODE:
       {
@@ -1159,17 +1228,21 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
             /* If the font has color glyphs, we don't need to recolor anything */
             if (font_has_color_glyphs (font))
               {
-                item.mode = MODE_BLIT;
-                item.program = &self->blit_program;
+                add_program_op (builder, &self->blit_program);
               }
             else
               {
-                item.mode = MODE_COLORING;
-                item.program = &self->coloring_program;
-                item.color_data.color = *gsk_text_node_peek_color (node);
+                RenderOp op;
+
+                add_program_op (builder, &self->coloring_program);
+
+                op.op = OP_CHANGE_COLOR;
+                op.color = *gsk_text_node_peek_color (node);
+                add_op (builder, &op);
               }
 
-            item.texture_id = gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache, glyph)->texture_id;
+            add_texture_op (builder, gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache,
+                                                                         glyph)->texture_id);
 
             {
               tx  = glyph->tx;
@@ -1182,13 +1255,6 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
               glyph_w = glyph->draw_width;
               glyph_h = glyph->draw_height;
 
-              item.min.x = glyph_x;
-              item.min.y = glyph_y;
-              item.size.width = glyph_w;
-              item.size.height = glyph_h;
-              item.max.x = item.min.x + item.size.width;
-              item.max.y = item.min.y + item.size.height;
-
               GskQuadVertex vertex_data[N_VERTICES] = {
                 { { glyph_x,           glyph_y           }, { tx,  ty  }, },
                 { { glyph_x,           glyph_y + glyph_h }, { tx,  ty2 }, },
@@ -1199,28 +1265,15 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
                 { { glyph_x + glyph_w, glyph_y           }, { tx2, ty  }, },
               };
 
-              item.vao_id = gsk_gl_driver_create_vao_for_quad (self->gl_driver,
-                                                               item.program->position_location,
-                                                               item.program->uv_location,
-                                                               N_VERTICES,
-                                                               vertex_data);
+              add_draw_op (builder, vertex_data);
             }
 
-
-            g_array_append_val (render_items, item);
-
             x_position += gi->geometry.width;
           }
+
       }
-      return;
+    break;
 
-    case GSK_NOT_A_RENDER_NODE:
-    case GSK_CONTAINER_NODE:
-    case GSK_TRANSFORM_NODE:
-    case GSK_CLIP_NODE:
-    case GSK_ROUNDED_CLIP_NODE:
-      g_assert_not_reached ();
-      return;
 
     case GSK_REPEATING_LINEAR_GRADIENT_NODE:
     case GSK_BORDER_NODE:
@@ -1231,183 +1284,232 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
     case GSK_CROSS_FADE_NODE:
     case GSK_BLEND_NODE:
     case GSK_REPEAT_NODE:
+    case GSK_CAIRO_NODE:
+    case GSK_COLOR_MATRIX_NODE:
     default:
       {
         cairo_surface_t *surface;
         cairo_t *cr;
+        int texture_id;
 
         surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                              ceilf (item.size.width) * self->scale_factor,
-                                              ceilf (item.size.height) * self->scale_factor);
+                                              ceilf (node->bounds.size.width) * self->scale_factor,
+                                              ceilf (node->bounds.size.height) * self->scale_factor);
         cairo_surface_set_device_scale (surface, self->scale_factor, self->scale_factor);
         cr = cairo_create (surface);
 
         cairo_save (cr);
-        cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
+        cairo_translate (cr, -min_x, -min_y);
         gsk_render_node_draw (node, cr);
         cairo_restore (cr);
 
 #if HIGHLIGHT_FALLBACK
         cairo_move_to (cr, 0, 0);
-        cairo_rectangle (cr, 0, 0, item.size.width, item.size.height);
+        cairo_rectangle (cr, 0, 0, max_x - min_x, max_y - min_y);
         cairo_set_source_rgba (cr, 1, 0, 0, 1);
         cairo_stroke (cr);
 #endif
-
         cairo_destroy (cr);
 
         /* Upload the Cairo surface to a GL texture */
-        item.texture_id = gsk_gl_driver_create_texture (self->gl_driver,
-                                                        item.size.width * self->scale_factor,
-                                                        item.size.height * self->scale_factor);
-        gsk_gl_driver_bind_source_texture (self->gl_driver, item.texture_id);
+        texture_id = gsk_gl_driver_create_texture (self->gl_driver,
+                                                   node->bounds.size.width * self->scale_factor,
+                                                   node->bounds.size.height * self->scale_factor);
+
+        gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
         gsk_gl_driver_init_texture_with_surface (self->gl_driver,
-                                                 item.texture_id,
+                                                 texture_id,
                                                  surface,
                                                  GL_NEAREST, GL_NEAREST);
 
         cairo_surface_destroy (surface);
-        item.mode = MODE_TEXTURE;
-      }
-      break;
-    }
 
-  /* Create the vertex buffers holding the geometry of the quad */
-  if (item.render_target == 0)
-    {
-      GskQuadVertex vertex_data[N_VERTICES] = {
-        { { item.min.x, item.min.y }, { 0, 0 }, },
-        { { item.min.x, item.max.y }, { 0, 1 }, },
-        { { item.max.x, item.min.y }, { 1, 0 }, },
-
-        { { item.max.x, item.max.y }, { 1, 1 }, },
-        { { item.min.x, item.max.y }, { 0, 1 }, },
-        { { item.max.x, item.min.y }, { 1, 0 }, },
-      };
-
-      item.vao_id = gsk_gl_driver_create_vao_for_quad (self->gl_driver,
-                                                       item.program->position_location,
-                                                       item.program->uv_location,
-                                                       N_VERTICES,
-                                                       vertex_data);
-    }
-  else
-    {
-      GskQuadVertex vertex_data[N_VERTICES] = {
-        { { item.min.x, item.min.y }, { 0, 1 }, },
-        { { item.min.x, item.max.y }, { 0, 0 }, },
-        { { item.max.x, item.min.y }, { 1, 1 }, },
-
-        { { item.max.x, item.max.y }, { 1, 0 }, },
-        { { item.min.x, item.max.y }, { 0, 0 }, },
-        { { item.max.x, item.min.y }, { 1, 1 }, },
-      };
-
-      item.vao_id = gsk_gl_driver_create_vao_for_quad (self->gl_driver,
-                                                       item.program->position_location,
-                                                       item.program->uv_location,
-                                                       N_VERTICES,
-                                                       vertex_data);
+        add_program_op (builder, &self->blit_program);
+        add_texture_op (builder, texture_id);
+        add_draw_op (builder, vertex_data);
+      }
     }
-
-  GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n",
-                             node->name != NULL ? node->name : "unnamed",
-                             node));
-  g_array_append_val (render_items, item);
 }
 
 static void
-gsk_gl_renderer_validate_tree (GskGLRenderer           *self,
-                               GskRenderNode           *root,
-                               const graphene_matrix_t *projection,
-                               const graphene_matrix_t *modelview)
+gsk_gl_renderer_render_ops (GskGLRenderer *self,
+                            gsize          vertex_data_size)
 {
-  GskRoundedRect viewport_clip;
-
-  gdk_gl_context_make_current (self->gl_context);
-
-  gsk_rounded_rect_init_from_rect (&viewport_clip, &self->viewport, 0.0f);
+  guint i;
+  guint n_ops = self->render_ops->len;
+  float mat[16];
+  const Program *program = NULL;
+  gsize buffer_index = 0;
+  float *vertex_data = g_malloc (vertex_data_size);
 
-  gsk_gl_renderer_add_render_item (self, projection, modelview, self->render_items, root,
-                                   self->texture_id, &viewport_clip);
-}
+  /*g_message ("%s: Buffer size: %ld", __FUNCTION__, vertex_data_size);*/
 
-static void
-gsk_gl_renderer_clear_tree (GskGLRenderer *self)
-{
-  int removed_textures, removed_vaos;
 
-  if (self->gl_context == NULL)
-    return;
+  GLuint buffer_id, vao_id;
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
 
-  gdk_gl_context_make_current (self->gl_context);
+  glGenBuffers (1, &buffer_id);
+  glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
 
-  g_array_remove_range (self->render_items, 0, self->render_items->len);
 
-  removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
-  removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver);
-
-  GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n",
-                             removed_textures,
-                             removed_vaos));
-}
+  // Fill buffer data
+  for (i = 0; i < n_ops; i ++)
+    {
+      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
 
-static void
-gsk_gl_renderer_clear (GskGLRenderer *self)
-{
-  GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
-  glClearColor (0, 0, 0, 0);
-  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-}
+      if (op->op == OP_CHANGE_VAO)
+        {
+          memcpy (vertex_data + buffer_index, &op->vertex_data, sizeof (GskQuadVertex) * N_VERTICES);
+          buffer_index += sizeof (GskQuadVertex) * N_VERTICES / sizeof (float);
+        }
+    }
 
-static void
-gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
-{
-  switch (self->render_mode)
-  {
-    case RENDER_FULL:
-      glDisable (GL_SCISSOR_TEST);
-      break;
+  // Set buffer data
+  glBufferData (GL_ARRAY_BUFFER, vertex_data_size, vertex_data, GL_STATIC_DRAW);
 
-    case RENDER_SCISSOR:
-      {
-        GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
-        GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
-        cairo_region_t *clip = gdk_drawing_context_get_clip (context);
-        cairo_rectangle_int_t extents;
-        int window_height;
+  // Describe buffer contents
 
-        /* Fall back to RENDER_FULL */
-        if (clip == NULL)
-          {
-            glDisable (GL_SCISSOR_TEST);
-            return;
-          }
+  /* 0 = position location */
+  glEnableVertexAttribArray (0);
+  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskQuadVertex),
+                         (void *) G_STRUCT_OFFSET (GskQuadVertex, position));
+  /* 1 = texture coord location */
+  glEnableVertexAttribArray (1);
+  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskQuadVertex),
+                         (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
 
-        g_assert (cairo_region_num_rectangles (clip) == 1);
+  for (i = 0; i < n_ops; i ++)
+    {
+      const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
 
-        window_height = gdk_window_get_height (window) * self->scale_factor;
+      if (op->op == OP_NONE ||
+          op->op == OP_CHANGE_VAO)
+        continue;
 
-        /*cairo_region_get_extents (clip, &extents);*/
-        cairo_region_get_rectangle (clip, 0, &extents);
+      OP_PRINT ("Op %u: %u", i, op->op);
 
-        glEnable (GL_SCISSOR_TEST);
-        glScissor (extents.x * self->scale_factor,
-                   window_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor),
-                   extents.width * self->scale_factor,
-                   extents.height * self->scale_factor);
+      switch (op->op)
+        {
+        case OP_CHANGE_PROJECTION:
+          graphene_matrix_to_float (&op->projection, mat);
+          glUniformMatrix4fv (program->projection_location, 1, GL_FALSE, mat);
+          OP_PRINT (" -> Projection");
+          /*graphene_matrix_print (&op->projection);*/
+          break;
+
+        case OP_CHANGE_MODELVIEW:
+          graphene_matrix_to_float (&op->modelview, mat);
+          glUniformMatrix4fv (program->modelview_location, 1, GL_FALSE, mat);
+          OP_PRINT (" -> Modelview");
+          /*graphene_matrix_print (&op->modelview);*/
+          break;
+
+        case OP_CHANGE_PROGRAM:
+          program = op->program;
+          glUseProgram (op->program->id);
+          OP_PRINT (" -> Program: %d", op->program->id);
+          break;
+
+        case OP_CHANGE_RENDER_TARGET:
+          OP_PRINT (" -> Render Target: %d", op->render_target_id);
+
+          glBindFramebuffer (GL_FRAMEBUFFER, op->render_target_id);
+          if (op->render_target_id != 0)
+            {
+              glDisable (GL_SCISSOR_TEST);
+              glClearColor (0.0, 0.0, 0.0, 0.0);
+              glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+            }
+          else
+            {
+              /* Reset glScissor, etc. */
+              gsk_gl_renderer_setup_render_mode (self);
+            }
+          break;
+
+        case OP_CHANGE_VIEWPORT:
+          OP_PRINT (" -> New Viewport: %f, %f, %f, %f", op->viewport.origin.x, op->viewport.origin.y, 
op->viewport.size.width, op->viewport.size.height);
+          glUniform4f (program->viewport_location,
+                       op->viewport.origin.x, op->viewport.origin.y,
+                       op->viewport.size.width, op->viewport.size.height);
+          glViewport (0, 0, op->viewport.size.width, op->viewport.size.height);
+          break;
+
+        case OP_CHANGE_OPACITY:
+          glUniform1f (program->alpha_location, op->opacity);
+          break;
+
+        case OP_CHANGE_COLOR:
+          OP_PRINT (" -> Color: (%f, %f, %f, %f)", op->color.red, op->color.green, op->color.blue, 
op->color.alpha);
+          g_assert (program == &self->color_program || program == &self->coloring_program);
+          glUniform4f (program->color_location,
+                       op->color.red, op->color.green, op->color.blue, op->color.alpha);
+          break;
+
+        case OP_CHANGE_CLIP:
+          OP_PRINT (" -> Clip");
+          glUniform4f (program->clip_location,
+                       op->clip.bounds.origin.x, op->clip.bounds.origin.y,
+                       op->clip.bounds.size.width, op->clip.bounds.size.height);
+
+          glUniform4f (program->clip_corner_widths_location,
+                       MAX (op->clip.corner[0].width, 1),
+                       MAX (op->clip.corner[1].width, 1),
+                       MAX (op->clip.corner[2].width, 1),
+                       MAX (op->clip.corner[3].width, 1));
+          glUniform4f (program->clip_corner_heights_location,
+                       MAX (op->clip.corner[0].height, 1),
+                       MAX (op->clip.corner[1].height, 1),
+                       MAX (op->clip.corner[2].height, 1),
+                       MAX (op->clip.corner[3].height, 1));
+          break;
+
+        case OP_CHANGE_SOURCE_TEXTURE:
+          g_assert(op->texture_id != 0);
+          OP_PRINT (" -> New texture: %d", op->texture_id);
+          /* Use texture unit 0 for the source */
+          glUniform1i (program->source_location, 0);
+          glActiveTexture (GL_TEXTURE0);
+          glBindTexture (GL_TEXTURE_2D, op->texture_id);
+
+          break;
+
+        case OP_CHANGE_LINEAR_GRADIENT:
+            OP_PRINT (" -> Linear gradient");
+            glUniform1i (program->n_color_stops_location,
+                         op->linear_gradient.n_color_stops);
+            glUniform4fv (program->color_stops_location,
+                          op->linear_gradient.n_color_stops,
+                          op->linear_gradient.color_stops);
+            glUniform1fv (program->color_offsets_location,
+                          op->linear_gradient.n_color_stops,
+                          op->linear_gradient.color_offsets);
+            glUniform2f (program->start_point_location,
+                         op->linear_gradient.start_point.x, op->linear_gradient.start_point.y);
+            glUniform2f (program->end_point_location,
+                         op->linear_gradient.end_point.x, op->linear_gradient.end_point.y);
+          break;
+
+        case OP_DRAW:
+          OP_PRINT (" -> draw %ld\n", op->vao_offset);
+          glDrawArrays (GL_TRIANGLES, op->vao_offset, N_VERTICES);
+          break;
+
+        default:
+          g_warn_if_reached ();
+        }
 
-        cairo_region_destroy (clip);
-        break;
-      }
+      OP_PRINT ("\n");
+    }
 
-    default:
-      g_assert_not_reached ();
-      break;
-  }
+  /* Done drawing, destroy the buffer again.
+   * TODO: Can we reuse the memory, though? */
+  g_free (vertex_data);
 }
 
-
 static void
 gsk_gl_renderer_do_render (GskRenderer           *renderer,
                            GskRenderNode         *root,
@@ -1415,8 +1517,8 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
                            int                    scale_factor)
 {
   GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  RenderOpBuilder render_op_builder;
   graphene_matrix_t modelview, projection;
-  guint i;
 #ifdef G_ENABLE_DEBUG
   GskProfiler *profiler;
   gint64 gpu_time, cpu_time;
@@ -1449,37 +1551,39 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
 
   gsk_gl_driver_begin_frame (self->gl_driver);
   gsk_gl_glyph_cache_begin_frame (&self->glyph_cache);
-  gsk_gl_renderer_validate_tree (self, root, &projection, &modelview);
 
+  memset (&render_op_builder, 0, sizeof (render_op_builder));
+  render_op_builder.renderer = self;
+  render_op_builder.current_projection = projection;
+  render_op_builder.current_modelview = modelview;
+  render_op_builder.current_viewport = *viewport;
+  render_op_builder.current_render_target = self->texture_id;
+  render_op_builder.current_opacity = 1.0f;
+  gsk_rounded_rect_init_from_rect (&render_op_builder.current_clip, &self->viewport, 0.0f);
+  gsk_gl_renderer_add_render_ops (self, root, &render_op_builder);
+
+  /*g_message ("Ops: %u", self->render_ops->len);*/
+
+  /* Now actually draw things... */
 #ifdef G_ENABLE_DEBUG
   gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
   gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
 #endif
 
-  /* Ensure that the viewport is up to date */
-  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
   gsk_gl_renderer_resize_viewport (self, viewport);
-
   gsk_gl_renderer_setup_render_mode (self);
-
   gsk_gl_renderer_clear (self);
 
   glEnable (GL_DEPTH_TEST);
   glDepthFunc (GL_LEQUAL);
 
-  glEnable (GL_BLEND);
   /* Pre-multiplied alpha! */
+  glEnable (GL_BLEND);
   glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
   glBlendEquation (GL_FUNC_ADD);
 
-  for (i = 0; i < self->render_items->len; i++)
-    {
-      const RenderItem *item = &g_array_index (self->render_items, RenderItem, i);
-
-      render_item (self, item);
-    }
+  gsk_gl_renderer_render_ops (self, render_op_builder.buffer_size);
 
-  /* Draw the output of the GL rendering to the window */
   gsk_gl_driver_end_frame (self->gl_driver);
 
 #ifdef G_ENABLE_DEBUG
@@ -1585,9 +1689,8 @@ gsk_gl_renderer_init (GskGLRenderer *self)
   gsk_ensure_resources ();
 
 
-  self->render_items = g_array_new (FALSE, FALSE, sizeof (RenderItem));
-  g_array_set_clear_func (self->render_items, (GDestroyNotify)destroy_render_item);
   self->scale_factor = 1;
+  self->render_ops = g_array_new (TRUE, FALSE, sizeof (RenderOp));
 
 #ifdef G_ENABLE_DEBUG
   {
diff --git a/gtk/theme/Adwaita/gtk-contained.css b/gtk/theme/Adwaita/gtk-contained.css
index b5fa650..f1470cd 100644
--- a/gtk/theme/Adwaita/gtk-contained.css
+++ b/gtk/theme/Adwaita/gtk-contained.css
@@ -48,7 +48,7 @@ flowbox flowboxchild:selected { outline-offset: -2px; }
 
 .content-view .tile:disabled { background-color: transparent; }
 
-label { caret-color: currentColor; }
+label { caret-color: currentColor; border-radius: 99px;}
 
 label.separator { color: #2e3436; }
 


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