[gtk+/wip/baedert/gl: 13/94] Rework the GL renderer



commit 02e89a2a818faefcaaf2d28e42e7ba11e7d481bf
Author: Timm Bäder <mail baedert org>
Date:   Fri Nov 3 13:09:02 2017 +0100

    Rework the GL renderer

 gsk/gskgldriver.c                          |   42 +-
 gsk/gskglrenderer.c                        |  900 ++++++++++++++++------------
 gsk/gskshaderbuilder.c                     |    2 -
 gsk/meson.build                            |    4 +
 gsk/resources/glsl/blend.fs.glsl           |    3 +-
 gsk/resources/glsl/blend.vs.glsl           |    1 -
 gsk/resources/glsl/blit.fs.glsl            |    2 +-
 gsk/resources/glsl/color.fs.glsl           |    6 +-
 gsk/resources/glsl/color_matrix.fs.glsl    |   20 +
 gsk/resources/glsl/color_matrix.vs.glsl    |    6 +
 gsk/resources/glsl/gl3_common.fs.glsl      |    3 +-
 gsk/resources/glsl/gl3_common.vs.glsl      |    1 +
 gsk/resources/glsl/linear_gradient.fs.glsl |   43 ++
 gsk/resources/glsl/linear_gradient.vs.glsl |    5 +
 14 files changed, 633 insertions(+), 405 deletions(-)
---
diff --git a/gsk/gskgldriver.c b/gsk/gskgldriver.c
index dbc3977..68c9148 100644
--- a/gsk/gskgldriver.c
+++ b/gsk/gskgldriver.c
@@ -520,7 +520,7 @@ int
 gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
                                    int            position_id,
                                    int            uv_id,
-                                   int            n_quads,
+                                   int            n_vertices,
                                    GskQuadVertex *quads)
 
 {
@@ -530,7 +530,7 @@ gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
   g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
   g_return_val_if_fail (driver->in_frame, -1);
 
-  v = find_vao (driver->vaos, position_id, uv_id, n_quads, quads);
+  v = find_vao (driver->vaos, position_id, uv_id, n_vertices, quads);
   if (v != NULL && !v->in_use)
     {
       GSK_NOTE (OPENGL, g_print ("Reusing VAO(%d)\n", v->vao_id));
@@ -543,17 +543,20 @@ gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
 
   glGenBuffers (1, &buffer_id);
   glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
-  glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_quads, quads, GL_STATIC_DRAW);
+  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));
 
-  glEnableVertexAttribArray (uv_id);
-  glVertexAttribPointer (uv_id, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskQuadVertex),
-                         (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
+  if (uv_id != -1)
+    {
+      glEnableVertexAttribArray (uv_id);
+      glVertexAttribPointer (uv_id, 2, GL_FLOAT, GL_FALSE,
+                             sizeof (GskQuadVertex),
+                             (void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
+    }
 
   glBindBuffer (GL_ARRAY_BUFFER, 0);
   glBindVertexArray (0);
@@ -563,8 +566,8 @@ gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
   v->buffer_id =  buffer_id;
   v->position_id = position_id;
   v->uv_id = uv_id;
-  v->n_quads = n_quads;
-  v->quads = g_memdup (quads, sizeof (GskQuadVertex) * n_quads);
+  v->n_quads = n_vertices;
+  v->quads = g_memdup (quads, sizeof (GskQuadVertex) * n_vertices);
   v->in_use = TRUE;
   g_hash_table_insert (driver->vaos, GINT_TO_POINTER (vao_id), v);
 
@@ -572,8 +575,8 @@ gsk_gl_driver_create_vao_for_quad (GskGLDriver   *driver,
   if (GSK_DEBUG_CHECK (OPENGL))
     {
       int i;
-      g_print ("New VAO(%d) for quad[%d] : {\n", v->vao_id, n_quads);
-      for (i = 0; i < n_quads; i++)
+      g_print ("New VAO(%d) for quad[%d] : {\n", v->vao_id, n_vertices);
+      for (i = 0; i < n_vertices; i++)
         {
           g_print ("  { x:%.2f, y:%.2f } { u:%.2f, v:%.2f }\n",
                   quads[i].position[0], quads[i].position[1],
@@ -641,6 +644,8 @@ gsk_gl_driver_create_render_target (GskGLDriver *driver,
 
   g_array_append_val (t->fbos, f);
 
+  g_assert (glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
   glBindFramebuffer (GL_FRAMEBUFFER, driver->default_fbo.fbo_id);
 
   return fbo_id;
@@ -719,7 +724,9 @@ gsk_gl_driver_bind_vao (GskGLDriver *driver,
       glBindVertexArray (v->vao_id);
       glBindBuffer (GL_ARRAY_BUFFER, v->buffer_id);
       glEnableVertexAttribArray (v->position_id);
-      glEnableVertexAttribArray (v->uv_id);
+
+      if (v->uv_id != -1)
+        glEnableVertexAttribArray (v->uv_id);
 
       driver->bound_vao = v;
     }
@@ -757,9 +764,14 @@ gsk_gl_driver_bind_render_target (GskGLDriver *driver,
     }
 
 out:
-  status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
 
-  return status == GL_FRAMEBUFFER_COMPLETE;
+  if (texture_id != 0)
+    {
+      status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
+      g_assert_cmpint (status, ==, GL_FRAMEBUFFER_COMPLETE);
+    }
+
+  return TRUE;
 }
 
 void
@@ -856,6 +868,4 @@ gsk_gl_driver_init_texture_with_surface (GskGLDriver     *driver,
 
   if (t->min_filter != GL_NEAREST)
     glGenerateMipmap (GL_TEXTURE_2D);
-
-  glBindTexture (GL_TEXTURE_2D, 0);
 }
diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c
index e1c3154..88ee17b 100644
--- a/gsk/gskglrenderer.c
+++ b/gsk/gskglrenderer.c
@@ -21,6 +21,29 @@
 #define SHADER_VERSION_GL3_LEGACY       130
 #define SHADER_VERSION_GL3              150
 
+#define ORTHO_NEAR_PLANE        -10000
+#define ORTHO_FAR_PLANE          10000
+
+#define HIGHLIGHT_FALLBACK 0
+
+static void G_GNUC_UNUSED
+dump_framebuffer (const char *filename, int w, int h)
+{
+  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w);
+  guchar *data = g_malloc (h * stride);
+  cairo_surface_t *s;
+
+  glReadPixels (0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, data);
+  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, w, h, stride);
+  cairo_surface_write_to_png (s, filename);
+
+  cairo_surface_destroy (s);
+  g_free (data);
+}
+
+
+static void
+gsk_gl_renderer_setup_render_mode (GskGLRenderer *self);
 typedef struct {
   int id;
   /* Common locations (gl_common)*/
@@ -31,28 +54,40 @@ typedef struct {
   int position_location;
   int alpha_location;
   int blendMode_location;
+  int viewport_location;
+  int projection_location;
 
   /* Shader-specific locations */
   union {
     struct {
       int color_location;
     };
+    struct {
+      int color_matrix_location;
+      int color_offset_location;
+    };
+    struct {
+      int n_color_stops_location;
+      int color_stops_location;
+      int color_offsets_location;
+      int start_point_location;
+      int end_point_location;
+    };
   };
 } Program;
 
-typedef struct {
-  int render_target_id;
-  int vao_id;
-  int buffer_id;
-  int texture_id;
-  int program_id;
-
-  Program *program;
-} RenderData;
+#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_COLOR = 1,
+  MODE_BLIT = 1,
+  MODE_COLOR,
   MODE_TEXTURE,
+  MODE_COLOR_MATRIX,
+  MODE_LINEAR_GRADIENT,
   N_MODES
 };
 
@@ -65,6 +100,7 @@ typedef struct {
   graphene_size_t size;
 
   graphene_matrix_t mvp;
+  graphene_matrix_t projection;
 
   float opacity;
   float z;
@@ -73,18 +109,40 @@ typedef struct {
     struct {
       GdkRGBA color;
     } color_data;
+    struct {
+      graphene_matrix_t color_matrix;
+      graphene_vec4_t color_offset;
+    } color_matrix_data;
+    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;
   };
 
   const char *name;
 
   GskBlendMode blend_mode;
 
-  RenderData render_data;
-  RenderData *parent_data;
+  /* 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 {
@@ -93,6 +151,8 @@ enum {
   MASK,
   ALPHA,
   BLEND_MODE,
+  VIEWPORT,
+  PROJECTION,
   N_UNIFORMS
 };
 
@@ -123,8 +183,10 @@ struct _GskGLRenderer
 {
   GskRenderer parent_instance;
 
+  int scale_factor;
+
   graphene_matrix_t mvp;
-  graphene_frustum_t frustum;
+  graphene_rect_t viewport;
 
   guint frame_buffer;
   guint depth_stencil_buffer;
@@ -136,11 +198,12 @@ struct _GskGLRenderer
   GdkGLContext *gl_context;
   GskGLDriver *gl_driver;
   GskGLProfiler *gl_profiler;
-  GskShaderBuilder *shader_builder;
 
   Program blend_program;
   Program blit_program;
   Program color_program;
+  Program color_matrix_program;
+  Program linear_gradient_program;
 
   GArray *render_items;
 
@@ -192,6 +255,7 @@ gsk_gl_renderer_create_buffers (GskGLRenderer *self,
     }
 
   gsk_gl_driver_create_render_target (self->gl_driver, self->texture_id, TRUE, TRUE);
+  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
 
   self->has_buffers = TRUE;
 }
@@ -219,24 +283,29 @@ gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
 }
 
 static void
-init_common_locations (GskGLRenderer *self,
-                       Program       *prog)
+init_common_locations (GskGLRenderer    *self,
+                       GskShaderBuilder *builder,
+                       Program          *prog)
 {
   prog->source_location =
-    gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[SOURCE]);
+    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[SOURCE]);
   prog->mask_location =
-    gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MASK]);
+    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[MASK]);
   prog->mvp_location =
-    gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MVP]);
+    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[MVP]);
   prog->alpha_location =
-    gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[ALPHA]);
+    gsk_shader_builder_get_uniform_location (builder, prog->id, self->uniforms[ALPHA]);
   prog->blendMode_location =
-    gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[BLEND_MODE]);
+    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->position_location =
-    gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[POSITION]);
+    gsk_shader_builder_get_attribute_location (builder, prog->id, self->attributes[POSITION]);
   prog->uv_location =
-    gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[UV]);
+    gsk_shader_builder_get_attribute_location (builder, prog->id, self->attributes[UV]);
 }
 
 static gboolean
@@ -256,7 +325,9 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
   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->attributes[POSITION] = gsk_shader_builder_add_attribute (builder, "aPosition");
   self->attributes[UV] = gsk_shader_builder_add_attribute (builder, "aUv");
 
@@ -293,10 +364,6 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
   if (GSK_RENDER_MODE_CHECK (SHADERS))
     gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
 #endif
-  /* Keep a pointer to query for the uniform and attribute locations
-   * when rendering the scene
-   */
-  self->shader_builder = builder;
 
   self->blend_program.id = 
     gsk_shader_builder_create_program (builder, "blend.vs.glsl", "blend.fs.glsl", &shader_error);
@@ -305,10 +372,9 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
       g_propagate_prefixed_error (error,
                                   shader_error,
                                   "Unable to create 'blend' program: ");
-      g_object_unref (builder);
       goto out;
     }
-  init_common_locations (self, &self->blend_program);
+  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);
@@ -317,10 +383,9 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
       g_propagate_prefixed_error (error,
                                   shader_error,
                                   "Unable to create 'blit' program: ");
-      g_object_unref (builder);
       goto out;
     }
-  init_common_locations (self, &self->blit_program);
+  init_common_locations (self, builder, &self->blit_program);
 
   self->color_program.id =
     gsk_shader_builder_create_program (builder, "color.vs.glsl", "color.fs.glsl", &shader_error);
@@ -329,26 +394,50 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
       g_propagate_prefixed_error (error,
                                   shader_error,
                                   "Unable to create 'color' program: ");
-      g_object_unref (builder);
       goto out;
     }
-  init_common_locations (self, &self->color_program);
-  self->color_program.color_location = gsk_shader_builder_get_uniform_location (self->shader_builder,
-                                                                                self->color_program.id,
-                                                                                
g_quark_from_string("uColor"));
-  self->color_program.color_location = glGetUniformLocation(self->color_program.id, "uColor");
-  g_assert(self->color_program.color_location >= 0);
+  init_common_locations (self, builder, &self->color_program);
+  INIT_PROGRAM_UNIFORM_LOCATION (color_program, color_location, "uColor");
+
+  self->color_matrix_program.id = gsk_shader_builder_create_program (builder,
+                                                                     "color_matrix.vs.glsl",
+                                                                     "color_matrix.fs.glsl",
+                                                                     &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'color_matrix' program: ");
+      goto out;
+    }
+  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,
+                                                                        "linear_gradient.vs.glsl",
+                                                                        "linear_gradient.fs.glsl",
+                                                                        &shader_error);
+  if (shader_error != NULL)
+    {
+      g_propagate_prefixed_error (error,
+                                  shader_error,
+                                  "Unable to create 'linear_gradient' program: ");
+      goto out;
+    }
+  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");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, n_color_stops_location, "uNumColorStops");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, start_point_location, "uStartPoint");
+  INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient_program, end_point_location, "uEndPoint");
 
   res = TRUE;
 
 out:
-  return res;
-}
 
-static void
-gsk_gl_renderer_destroy_programs (GskGLRenderer *self)
-{
-  g_clear_object (&self->shader_builder);
+  g_object_unref (builder);
+  return res;
 }
 
 static gboolean
@@ -358,6 +447,8 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
 {
   GskGLRenderer *self = GSK_GL_RENDERER (renderer);
 
+  self->scale_factor = gdk_window_get_scale_factor (window);
+
   /* If we didn't get a GdkGLContext before realization, try creating
    * one now, for our exclusive use.
    */
@@ -374,8 +465,8 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
   gdk_gl_context_make_current (self->gl_context);
 
   g_assert (self->gl_driver == NULL);
-  self->gl_driver = gsk_gl_driver_new (self->gl_context);
   self->gl_profiler = gsk_gl_profiler_new (self->gl_context);
+  self->gl_driver = gsk_gl_driver_new (self->gl_context);
 
   GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
   if (!gsk_gl_renderer_create_programs (self, error))
@@ -399,8 +490,14 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
    */
   g_array_set_size (self->render_items, 0);
 
+
+  glDeleteProgram (self->blend_program.id);
+  glDeleteProgram (self->blit_program.id);
+  glDeleteProgram (self->color_program.id);
+  glDeleteProgram (self->color_matrix_program.id);
+  glDeleteProgram (self->linear_gradient_program.id);
+
   gsk_gl_renderer_destroy_buffers (self);
-  gsk_gl_renderer_destroy_programs (self);
 
   g_clear_object (&self->gl_profiler);
   g_clear_object (&self->gl_driver);
@@ -424,12 +521,12 @@ gsk_gl_renderer_begin_draw_frame (GskRenderer          *renderer,
   window = gsk_renderer_get_window (renderer);
   whole_window = (GdkRectangle) {
                      0, 0,
-                     gdk_window_get_width (window),
-                     gdk_window_get_height (window) 
+                     gdk_window_get_width (window) * self->scale_factor,
+                     gdk_window_get_height (window) * self->scale_factor
                  };
   damage = gdk_gl_context_get_damage (self->gl_context);
   cairo_region_union (damage, update_area);
-  
+
   if (cairo_region_contains_rectangle (damage, &whole_window) == CAIRO_REGION_OVERLAP_IN)
     {
       self->render_mode = RENDER_FULL;
@@ -458,66 +555,80 @@ gsk_gl_renderer_begin_draw_frame (GskRenderer          *renderer,
 
 static void
 gsk_gl_renderer_resize_viewport (GskGLRenderer         *self,
-                                 const graphene_rect_t *viewport,
-                                 int                    scale_factor)
+                                 const graphene_rect_t *viewport)
 {
-  int width = viewport->size.width * scale_factor;
-  int height = viewport->size.height * scale_factor;
+  int width = viewport->size.width;
+  int height = viewport->size.height;
 
   GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %d, %d) [scale:%d]\n",
                              width,
                              height,
-                             scale_factor));
+                             self->scale_factor));
 
+  graphene_rect_init (&self->viewport, 0, 0, width, height);
   glViewport (0, 0, width, height);
 }
 
-static void
-gsk_gl_renderer_update_frustum (GskGLRenderer           *self,
-                                const graphene_matrix_t *modelview,
-                                const graphene_matrix_t *projection)
-{
-  GSK_NOTE (TRANSFORMS, g_print ("Updating the modelview/projection\n"));
-
-  graphene_matrix_multiply (modelview, projection, &self->mvp);
-
-  graphene_frustum_init_from_matrix (&self->frustum, &self->mvp);
-
-  GSK_NOTE (TRANSFORMS,
-            g_print ("Renderer MVP:\n");
-            graphene_matrix_print (&self->mvp);
-            g_print ("\n"));
-}
-
 #define N_VERTICES      6
 
 static void
-render_item (GskGLRenderer *self,
-             RenderItem    *item)
+render_item (GskGLRenderer    *self,
+             const RenderItem *item)
 {
-  float mvp[16];
-  float opacity;
+  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 (item->children != NULL)
+  if (draw_children)
     {
-      if (gsk_gl_driver_bind_render_target (self->gl_driver, item->render_data.render_target_id))
+      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 ++)
         {
-          glViewport (0, 0, item->size.width, item->size.height);
+          const RenderItem *child = &g_array_index (item->children, RenderItem, i);
 
-          glClearColor (0.0, 0.0, 0.0, 0.0);
-          glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+          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);
     }
 
-  gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id);
 
-  glUseProgram (item->render_data.program->id);
+  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->render_data.program->color_location,
+          glUniform4f (item->program->color_location,
                        item->color_data.color.red,
                        item->color_data.color.green,
                        item->color_data.color.blue,
@@ -527,108 +638,67 @@ render_item (GskGLRenderer *self,
 
       case MODE_TEXTURE:
         {
-          g_assert(item->render_data.texture_id != 0);
+          g_assert(item->texture_id != 0);
           /* Use texture unit 0 for the source */
-          glUniform1i (item->render_data.program->source_location, 0);
-          gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.texture_id);
-
-          if (item->parent_data != NULL)
-            {
-              glUniform1i (item->render_data.program->blendMode_location, item->blend_mode);
-
-              /* Use texture unit 1 for the mask */
-              if (item->parent_data->texture_id != 0)
-                {
-                  glUniform1i (item->render_data.program->mask_location, 1);
-                  gsk_gl_driver_bind_mask_texture (self->gl_driver, item->parent_data->texture_id);
-                }
-            }
+          glUniform1i (item->program->source_location, 0);
+          gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id);
         }
       break;
 
-      default:
-        g_assert_not_reached ();
-    }
-
-  /* Pass the opacity component */
-  if (item->children != NULL)
-    opacity = 1.0;
-  else
-    opacity = item->opacity;
-
-  glUniform1f (item->render_data.program->alpha_location, opacity);
-
-  /* Pass the mvp to the vertex shader */
-  GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp));
-  graphene_matrix_to_float (&item->mvp, mvp);
-  glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp);
+      case MODE_BLIT:
+        g_assert (item->program == &self->blit_program);
+        glUniform1i (item->program->source_location, 0);
+        gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_target);
+      break;
 
-  /* Draw the quad */
-  GSK_NOTE2 (OPENGL, TRANSFORMS,
-             g_print ("Drawing item <%s>[%p] (w:%g, h:%g) with opacity: %g blend mode: %d\n",
-                      item->name,
-                      item,
-                      item->size.width, item->size.height,
-                      item->opacity,
-                      item->blend_mode));
+      case MODE_COLOR_MATRIX:
+        {
+          float vec[4];
 
-  glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
+          glUniform1i (item->program->source_location, 0);
+          gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_target);
 
-#ifdef G_ENABLE_DEBUG
-  gsk_profiler_counter_inc (gsk_renderer_get_profiler (GSK_RENDERER (self)),
-                            self->profile_counters.draw_calls);
-#endif
+          graphene_matrix_to_float (&item->color_matrix_data.color_matrix, mat);
+          glUniformMatrix4fv (item->program->color_matrix_location, 1, GL_FALSE, mat);
 
-  /* Render all children items, so we can take the result
-   * render target texture during the compositing
-   */
-  if (item->children != NULL)
-    {
-      int i;
+          graphene_vec4_to_float (&item->color_matrix_data.color_offset, vec);
+          glUniform4fv (item->program->color_offset_location, 1, vec);
+        }
+      break;
 
-      for (i = 0; i < item->children->len; i++)
+      case MODE_LINEAR_GRADIENT:
         {
-          RenderItem *child = &g_array_index (item->children, RenderItem, i);
-
-          render_item (self, child);
+          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;
 
-      /* Bind the parent render target */
-      if (item->parent_data != NULL)
-        gsk_gl_driver_bind_render_target (self->gl_driver, item->parent_data->render_target_id);
-
-      /* Bind the same VAO, as the render target is created with the same size
-       * and vertices as the texture target
-       */
-      gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id);
-
-      /* Since we're rendering the target texture, we only need the blit program */
-      glUseProgram (self->blit_program.id);
-
-      /* Use texture unit 0 for the render target */
-      glUniform1i (item->render_data.program->source_location, 0);
-      gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.render_target_id);
-
-      /* Pass the opacity component; if we got here, we know that the original render
-       * target is neither fully opaque nor at full opacity
-       */
-      glUniform1f (item->render_data.program->alpha_location, item->opacity);
-
-      /* Pass the mvp to the vertex shader */
-      GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp));
-      graphene_matrix_to_float (&item->mvp, mvp);
-      glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp);
-
-      /* Draw the quad */
-      GSK_NOTE2 (OPENGL, TRANSFORMS,
-                 g_print ("Drawing offscreen item <%s>[%p] (w:%g, h:%g) with opacity: %g\n",
-                          item->name,
-                          item,
-                          item->size.width, item->size.height,
-                          item->opacity));
-
-      glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
+      default:
+        g_assert_not_reached ();
     }
+
+  /* Common uniforms */
+  graphene_matrix_to_float (&item->mvp, mat);
+  glUniformMatrix4fv (item->program->mvp_location, 1, GL_FALSE, mat);
+  graphene_matrix_to_float (&item->projection, mat);
+  glUniformMatrix4fv (item->program->projection_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);
+
+  gsk_gl_driver_bind_vao (self->gl_driver, item->vao_id);
+  glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
 }
 
 static void
@@ -652,10 +722,31 @@ project_item (const graphene_matrix_t *projection,
   return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec);
 }
 
-static gboolean
-render_node_needs_render_target (GskRenderNode *node)
+static void
+init_framebuffer_for_node (GskGLRenderer           *self,
+                           RenderItem              *item,
+                           GskRenderNode           *node,
+                           const graphene_matrix_t *projection,
+                           graphene_matrix_t       *out_projection)
 {
-  return FALSE;
+  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);
 }
 
 static void
@@ -664,75 +755,121 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
                                  const graphene_matrix_t *modelview,
                                  GArray                  *render_items,
                                  GskRenderNode           *node,
-                                 RenderItem              *parent)
+                                 int                      render_target)
 {
   RenderItem item;
-  RenderItem *ritem = NULL;
-  int program_id;
-  int scale_factor;
+
+  /* We handle container nodes here directly */
+  if (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE)
+    {
+      guint i, p;
+
+      for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i++)
+        {
+          GskRenderNode *child = gsk_container_node_get_child (node, i);
+          gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, render_target);
+        }
+      return;
+    }
 
   memset (&item, 0, sizeof (RenderItem));
 
   item.name = node->name != NULL ? node->name : "unnamed";
 
-  /* The texture size */
-  item.size.width = node->bounds.size.width * scale_factor;
-  item.size.height = node->bounds.size.height * scale_factor;
+  /*g_message ("%s: %s", __FUNCTION__, item.name);*/
 
+  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.min.z = 0.f;
+
+  /*g_message ("%s: %f, %f", item.name, item.min.x, item.min.y);*/
 
   item.max.x = item.min.x + node->bounds.size.width;
   item.max.y = item.min.y + node->bounds.size.height;
-  item.max.z = 0.f;
 
   /* The location of the item, in normalized world coordinates */
-  graphene_matrix_multiply (modelview, &self->mvp, &item.mvp);
+  graphene_matrix_multiply (modelview, projection, &item.mvp);
   item.z = project_item (projection, modelview);
 
   item.opacity = 1.0;
+  item.projection = *projection;
 
   item.blend_mode = GSK_BLEND_MODE_DEFAULT;
 
-  /* Back-pointer to the parent node */
-  if (parent != NULL)
-    item.parent_data = &(parent->render_data);
-  else
-    item.parent_data = NULL;
+  item.parent_render_target = render_target;
 
-  /* Select the program to use */
-  if (parent != NULL)
-    {
-      item.render_data.program = &self->blend_program;
-      program_id = self->blend_program.id;
-    }
-  else
-    {
-      item.render_data.program = &self->blit_program;
-      program_id = self->blit_program.id;
-    }
+  item.mode = MODE_BLIT;
+  item.program = &self->blit_program;
+  item.render_target = 0;
+  item.children = NULL;
 
-  if (render_node_needs_render_target (node))
+  switch (gsk_render_node_get_node_type (node))
     {
-      item.render_data.render_target_id =
-        gsk_gl_driver_create_texture (self->gl_driver, item.size.width, item.size.height);
-      gsk_gl_driver_init_texture_empty (self->gl_driver, item.render_data.render_target_id);
-      gsk_gl_driver_create_render_target (self->gl_driver, item.render_data.render_target_id, TRUE, TRUE);
+    case GSK_OPACITY_NODE:
+      {
+        GskRenderNode *child = gsk_opacity_node_get_child (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);
+        item.mode = MODE_BLIT;
+        item.opacity = gsk_opacity_node_get_opacity (node);
+      }
+      break;
 
-      item.children = g_array_new (FALSE, FALSE, sizeof (RenderItem));
-    }
-  else
-    {
-      item.render_data.render_target_id = self->texture_id;
-      item.children = NULL;
-    }
+    case GSK_CLIP_NODE:
+      {
+        GskRenderNode *child = gsk_clip_node_get_child (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);
+        item.mode = MODE_BLIT;
+      }
+      break;
+
+    case GSK_ROUNDED_CLIP_NODE:
+      {
+        GskRenderNode *child = gsk_rounded_clip_node_get_child (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);
+        item.mode = MODE_BLIT;
+      }
+      break;
+
+    case GSK_COLOR_MATRIX_NODE:
+      {
+        GskRenderNode *child = gsk_color_matrix_node_get_child (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);
+
+        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);
+      }
+      break;
 
-  switch (gsk_render_node_get_node_type (node))
-    {
     case GSK_TEXTURE_NODE:
       {
         GdkTexture *texture = gsk_texture_node_get_texture (node);
@@ -740,10 +877,10 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
 
         get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
 
-        item.render_data.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                                             texture,
-                                                                             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;
       }
       break;
@@ -758,13 +895,12 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
 
         get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
 
-        /* Upload the Cairo surface to a GL texture */
-        item.render_data.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.render_data.texture_id);
+        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.render_data.texture_id,
+                                                 item.texture_id,
                                                  (cairo_surface_t *)surface,
                                                  gl_min_filter,
                                                  gl_mag_filter);
@@ -774,71 +910,39 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
 
     case GSK_COLOR_NODE:
       {
-        const GdkRGBA *c = gsk_color_node_peek_color(node);
-        program_id = self->color_program.id;
-        item.render_data.program = &self->color_program;
         item.mode = MODE_COLOR;
-        item.color_data.color= *c;
+        item.program = &self->color_program;
+        item.color_data.color= *gsk_color_node_peek_color (node);
       }
       break;
 
-    case GSK_COLOR_MATRIX_NODE:
-      {
-        GskRenderNode *child = gsk_color_matrix_node_get_child (node);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-      }
-      return;
-
-    case GSK_SHADOW_NODE:
-      {
-        GskRenderNode *child = gsk_shadow_node_get_child (node);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-      }
-      return;
-
-    case GSK_REPEAT_NODE:
-      {
-        GskRenderNode *child = gsk_repeat_node_get_child (node);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-      }
-      return;
-
-    case GSK_BLEND_NODE:
-      {
-        GskRenderNode *child = gsk_blend_node_get_bottom_child (node);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-
-        child = gsk_blend_node_get_top_child (node);
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-      }
-      return;
-
-    case GSK_CROSS_FADE_NODE:
+    case GSK_LINEAR_GRADIENT_NODE:
       {
-        GskRenderNode *child = gsk_cross_fade_node_get_start_child (node);
-
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-
-        child = gsk_cross_fade_node_get_end_child (node);
-        gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
-      }
-      return;
+        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;
 
-    case GSK_CONTAINER_NODE:
-      {
-        guint i, p;
+        item.mode = MODE_LINEAR_GRADIENT;
+        item.program = &self->linear_gradient_program;
 
-        for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i++)
+        for (i = 0; i < n_color_stops; i ++)
           {
-            GskRenderNode *child = gsk_container_node_get_child (node, i);
-            gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
+            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;
           }
+
+        item.linear_gradient_data.n_color_stops = n_color_stops;
+        item.linear_gradient_data.start_point = *start;
+        item.linear_gradient_data.end_point = *end;
       }
-      return;
+      break;
 
     case GSK_TRANSFORM_NODE:
       {
@@ -850,47 +954,57 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
                                          projection, &transformed_mv,
                                          render_items,
                                          gsk_transform_node_get_child (node),
-                                         ritem);
+                                         render_target);
       }
       return;
 
     case GSK_NOT_A_RENDER_NODE:
+    case GSK_CONTAINER_NODE:
       g_assert_not_reached ();
       return;
 
-    case GSK_LINEAR_GRADIENT_NODE:
     case GSK_REPEATING_LINEAR_GRADIENT_NODE:
     case GSK_BORDER_NODE:
     case GSK_INSET_SHADOW_NODE:
     case GSK_OUTSET_SHADOW_NODE:
-    case GSK_OPACITY_NODE:
-    case GSK_CLIP_NODE:
-    case GSK_ROUNDED_CLIP_NODE:
     case GSK_TEXT_NODE:
     case GSK_BLUR_NODE:
+    case GSK_SHADOW_NODE:
+    case GSK_CROSS_FADE_NODE:
+    case GSK_BLEND_NODE:
+    case GSK_REPEAT_NODE:
     default:
       {
         cairo_surface_t *surface;
         cairo_t *cr;
 
         surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                              item.size.width,
-                                              item.size.height);
-        cairo_surface_set_device_scale (surface, scale_factor, scale_factor);
+                                              item.size.width * self->scale_factor,
+                                              item.size.height * self->scale_factor);
+        cairo_surface_set_device_scale (surface, self->scale_factor, self->scale_factor);
         cr = cairo_create (surface);
-        cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
 
+        cairo_save (cr);
+        cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.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_set_source_rgba (cr, 1, 0, 0, 1);
+        cairo_stroke (cr);
+#endif
 
         cairo_destroy (cr);
 
         /* Upload the Cairo surface to a GL texture */
-        item.render_data.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.render_data.texture_id);
+        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);
         gsk_gl_driver_init_texture_with_surface (self->gl_driver,
-                                                 item.render_data.texture_id,
+                                                 item.texture_id,
                                                  surface,
                                                  GL_NEAREST, GL_NEAREST);
 
@@ -900,66 +1014,62 @@ gsk_gl_renderer_add_render_item (GskGLRenderer           *self,
       break;
     }
 
-  item.render_data.program_id = program_id;
-
   /* Create the vertex buffers holding the geometry of the quad */
-  {
-    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.render_data.vao_id =
-      gsk_gl_driver_create_vao_for_quad (self->gl_driver,
-                                         item.render_data.program->position_location,
-                                         item.render_data.program->uv_location,
-                                         N_VERTICES,
-                                         vertex_data);
-  }
+  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);
+    }
 
   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);
-  ritem = &g_array_index (render_items, RenderItem, render_items->len - 1);
-
-  if (item.children != NULL)
-    render_items = item.children;
 }
 
-static gboolean
+static void
 gsk_gl_renderer_validate_tree (GskGLRenderer           *self,
                                GskRenderNode           *root,
                                const graphene_matrix_t *projection)
 {
-  graphene_matrix_t identity;
-
-  if (self->gl_context == NULL)
-    {
-      GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer"));
-      return FALSE;
-    }
-
-  graphene_matrix_init_identity (&identity);
+  graphene_matrix_t modelview;
+  graphene_matrix_init_scale (&modelview, self->scale_factor, self->scale_factor, 1.0f);
 
   gdk_gl_context_make_current (self->gl_context);
 
-  gsk_gl_driver_begin_frame (self->gl_driver);
-
-  GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n"));
-  gsk_gl_renderer_add_render_item (self, projection, &identity, self->render_items, root, NULL);
-
-  GSK_NOTE (OPENGL, g_print ("Total render items: %d\n",
-                             self->render_items->len));
-
-  gsk_gl_driver_end_frame (self->gl_driver);
-
-  return TRUE;
+  gsk_gl_renderer_add_render_item (self, projection, &modelview, self->render_items, root,
+                                   self->texture_id);
 }
 
 static void
@@ -1003,15 +1113,31 @@ gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
       {
         GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
         GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
-        GdkRectangle extents;
-        int scale_factor = gdk_window_get_scale_factor (window);
+        cairo_region_t *clip = gdk_drawing_context_get_clip (context);
+        cairo_rectangle_int_t extents;
+        int window_height;
+
+        /* Fall back to RENDER_FULL */
+        if (clip == NULL)
+          {
+            glDisable (GL_SCISSOR_TEST);
+            return;
+          }
 
-        cairo_region_get_extents (gdk_drawing_context_get_clip (context), &extents);
+        g_assert (cairo_region_num_rectangles (clip) == 1);
+
+        window_height = gdk_window_get_height (window) * self->scale_factor;
+
+        cairo_region_get_extents (clip, &extents);
+        /*cairo_region_get_rectangle (clip, 0, &extents);*/
 
-        glScissor (extents.x * scale_factor,
-                   (gdk_window_get_height (window) - extents.height - extents.y) * scale_factor,
-                   extents.width * scale_factor, extents.height * scale_factor);
         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);
+
+        cairo_region_destroy (clip);
         break;
       }
 
@@ -1021,8 +1147,6 @@ gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
   }
 }
 
-#define ORTHO_NEAR_PLANE        -10000
-#define ORTHO_FAR_PLANE          10000
 
 static void
 gsk_gl_renderer_do_render (GskRenderer           *renderer,
@@ -1042,22 +1166,29 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
   profiler = gsk_renderer_get_profiler (renderer);
 #endif
 
+  if (self->gl_context == NULL)
+    {
+      GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer"));
+      return;
+    }
+
+  self->viewport = *viewport;
+
   /* Set up the modelview and projection matrices to fit our viewport */
   graphene_matrix_init_scale (&modelview, scale_factor, scale_factor, 1.0);
   graphene_matrix_init_ortho (&projection,
                               viewport->origin.x,
-                              viewport->origin.x + viewport->size.width * scale_factor,
-                              viewport->origin.y + viewport->size.height * scale_factor,
+                              viewport->origin.x + viewport->size.width,
                               viewport->origin.y,
+                              viewport->origin.y + viewport->size.height,
                               ORTHO_NEAR_PLANE,
                               ORTHO_FAR_PLANE);
 
-  gsk_gl_renderer_update_frustum (self, &modelview, &projection);
-
-  if (!gsk_gl_renderer_validate_tree (self, root, &projection))
-    return;
+  if (self->texture_id == 0)
+    graphene_matrix_scale (&projection, 1, -1, 1);
 
   gsk_gl_driver_begin_frame (self->gl_driver);
+  gsk_gl_renderer_validate_tree (self, root, &projection);
 
 #ifdef G_ENABLE_DEBUG
   gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
@@ -1065,8 +1196,8 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
 #endif
 
   /* Ensure that the viewport is up to date */
-  if (gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id))
-    gsk_gl_renderer_resize_viewport (self, viewport, scale_factor);
+  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);
 
@@ -1076,12 +1207,13 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
   glDepthFunc (GL_LEQUAL);
 
   glEnable (GL_BLEND);
+  /* Pre-multiplied alpha! */
   glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+  glBlendEquation (GL_FUNC_ADD);
 
-  GSK_NOTE (OPENGL, g_print ("Rendering %u items\n", self->render_items->len));
   for (i = 0; i < self->render_items->len; i++)
     {
-      RenderItem *item = &g_array_index (self->render_items, RenderItem, i);
+      const RenderItem *item = &g_array_index (self->render_items, RenderItem, i);
 
       render_item (self, item);
     }
@@ -1109,37 +1241,39 @@ gsk_gl_renderer_render_texture (GskRenderer           *renderer,
 {
   GskGLRenderer *self = GSK_GL_RENDERER (renderer);
   GdkTexture *texture;
-  cairo_surface_t *surface;
-  cairo_t *cr;
+  int stride;
+  guchar *data;
+  int width, height;
 
   g_return_val_if_fail (self->gl_context != NULL, NULL);
 
   self->render_mode = RENDER_FULL;
+  width = ceilf (viewport->size.width);
+  height = ceilf (viewport->size.height);
 
   gdk_gl_context_make_current (self->gl_context);
 
+  /* Prepare our framebuffer */
   gsk_gl_driver_begin_frame (self->gl_driver);
-  gsk_gl_renderer_create_buffers (self, ceilf (viewport->size.width), ceilf (viewport->size.height), 1);
+  gsk_gl_renderer_create_buffers (self, width, height, 1);
+  gsk_gl_renderer_clear (self);
   gsk_gl_driver_end_frame (self->gl_driver);
 
+  /* Render the actual scene */
   gsk_gl_renderer_do_render (renderer, root, viewport, 1);
 
-  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                        ceilf (viewport->size.width),
-                                        ceilf (viewport->size.height));
-  cr = cairo_create (surface);
-  gdk_cairo_draw_from_gl (cr,
-                          gsk_renderer_get_window (renderer),
-                          self->texture_id,
-                          GL_TEXTURE,
-                          1.0,
-                          0, 0,
-                          viewport->size.width,
-                          viewport->size.height);
-  cairo_destroy (cr);
-
-  texture = gdk_texture_new_for_surface (surface);
-  cairo_surface_destroy (surface);
+  /* Prepare memory for the glReadPixels call */
+  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+  data = g_malloc (height * stride);
+
+  /* Bind our framebuffer again and read from it */
+  gsk_gl_driver_begin_frame (self->gl_driver);
+  gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id);
+  glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+  gsk_gl_driver_end_frame (self->gl_driver);
+
+  /* Create texture from the downloaded data */
+  texture = gdk_texture_new_for_data (data, width, height, stride);
 
   return texture;
 }
@@ -1149,18 +1283,20 @@ gsk_gl_renderer_render (GskRenderer   *renderer,
                         GskRenderNode *root)
 {
   GskGLRenderer *self = GSK_GL_RENDERER (renderer);
+  GdkWindow *window = gsk_renderer_get_window (renderer);
   graphene_rect_t viewport;
-  int scale_factor;
 
   if (self->gl_context == NULL)
     return;
 
   gdk_gl_context_make_current (self->gl_context);
 
-  gsk_renderer_get_viewport (renderer, &viewport);
-  scale_factor = gdk_window_get_scale_factor (gsk_renderer_get_window (renderer));
+  viewport.origin.x = 0;
+  viewport.origin.y = 0;
+  viewport.size.width = gdk_window_get_width (window) * self->scale_factor;
+  viewport.size.height = gdk_window_get_height (window) * self->scale_factor;
 
-  gsk_gl_renderer_do_render (renderer, root, &viewport, scale_factor);
+  gsk_gl_renderer_do_render (renderer, root, &viewport, self->scale_factor);
 
   gdk_gl_context_make_current (self->gl_context);
   gsk_gl_renderer_clear_tree (self);
@@ -1190,6 +1326,8 @@ gsk_gl_renderer_init (GskGLRenderer *self)
   graphene_matrix_init_identity (&self->mvp);
 
   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;
 
 #ifdef G_ENABLE_DEBUG
   {
diff --git a/gsk/gskshaderbuilder.c b/gsk/gskshaderbuilder.c
index 7085c41..d34360c 100644
--- a/gsk/gskshaderbuilder.c
+++ b/gsk/gskshaderbuilder.c
@@ -41,8 +41,6 @@ shader_program_free (gpointer data)
   g_clear_pointer (&p->uniform_locations, g_hash_table_unref);
   g_clear_pointer (&p->attribute_locations, g_hash_table_unref);
 
-  glDeleteProgram (p->program_id);
-
   g_slice_free (ShaderProgram, data);
 }
 
diff --git a/gsk/meson.build b/gsk/meson.build
index f51eff5..95b093b 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -5,6 +5,10 @@ gsk_private_source_shaders = [
   'resources/glsl/blit.vs.glsl',
   'resources/glsl/color.fs.glsl',
   'resources/glsl/color.vs.glsl',
+  'resources/glsl/color_matrix.fs.glsl',
+  'resources/glsl/color_matrix.vs.glsl',
+  'resources/glsl/linear_gradient.fs.glsl',
+  'resources/glsl/linear_gradient.vs.glsl',
   'resources/glsl/es2_common.fs.glsl',
   'resources/glsl/es2_common.vs.glsl',
   'resources/glsl/gl3_common.fs.glsl',
diff --git a/gsk/resources/glsl/blend.fs.glsl b/gsk/resources/glsl/blend.fs.glsl
index 39b3e4c..1473d05 100644
--- a/gsk/resources/glsl/blend.fs.glsl
+++ b/gsk/resources/glsl/blend.fs.glsl
@@ -57,6 +57,5 @@ void main() {
     // Use red for debugging missing blend modes
     res = vec3(1.0, 0.0, 0.0);
   }
-
-  setOutputColor(vec4(res, Cs.a * uAlpha));
+  setOutputColor(vec4(res, Cs.a) * uAlpha);
 }
diff --git a/gsk/resources/glsl/blend.vs.glsl b/gsk/resources/glsl/blend.vs.glsl
index 1208513..a453e32 100644
--- a/gsk/resources/glsl/blend.vs.glsl
+++ b/gsk/resources/glsl/blend.vs.glsl
@@ -1,6 +1,5 @@
 void main() {
   gl_Position = uMVP * vec4(aPosition, 0.0, 1.0);
 
-  // Flip the sampling
   vUv = vec2(aUv.x, aUv.y);
 }
diff --git a/gsk/resources/glsl/blit.fs.glsl b/gsk/resources/glsl/blit.fs.glsl
index ceb88ef..9f2eb4c 100644
--- a/gsk/resources/glsl/blit.fs.glsl
+++ b/gsk/resources/glsl/blit.fs.glsl
@@ -1,5 +1,5 @@
 void main() {
   vec4 diffuse = Texture(uSource, vUv);
 
-  setOutputColor(vec4(diffuse.xyz, diffuse.a * uAlpha));
+  setOutputColor(diffuse * uAlpha);
 }
diff --git a/gsk/resources/glsl/color.fs.glsl b/gsk/resources/glsl/color.fs.glsl
index 62d4ba0..6b7e34d 100644
--- a/gsk/resources/glsl/color.fs.glsl
+++ b/gsk/resources/glsl/color.fs.glsl
@@ -1,5 +1,9 @@
 uniform vec4 uColor;
 
 void main() {
-  setOutputColor(uColor);
+  vec4 color = uColor;
+
+  // Pre-multiply alpha
+  color.rgb *= color.a;
+  setOutputColor(color * uAlpha);
 }
diff --git a/gsk/resources/glsl/color_matrix.fs.glsl b/gsk/resources/glsl/color_matrix.fs.glsl
new file mode 100644
index 0000000..9025bfb
--- /dev/null
+++ b/gsk/resources/glsl/color_matrix.fs.glsl
@@ -0,0 +1,20 @@
+uniform mat4 uColorMatrix;
+uniform vec4 uColorOffset;
+
+void main() {
+  vec4 diffuse = Texture(uSource, vUv);
+  vec4 color;
+
+  color = diffuse;
+
+  // Un-premultilpy
+  if (color.a != 0.0)
+    color.rgb /= color.a;
+
+  color = uColorMatrix * diffuse + uColorOffset;
+  color = clamp(color, 0.0f, 1.0f);
+
+  color.rgb *= color.a;
+
+  setOutputColor(color * uAlpha);
+}
diff --git a/gsk/resources/glsl/color_matrix.vs.glsl b/gsk/resources/glsl/color_matrix.vs.glsl
new file mode 100644
index 0000000..1208513
--- /dev/null
+++ b/gsk/resources/glsl/color_matrix.vs.glsl
@@ -0,0 +1,6 @@
+void main() {
+  gl_Position = uMVP * vec4(aPosition, 0.0, 1.0);
+
+  // Flip the sampling
+  vUv = vec2(aUv.x, aUv.y);
+}
diff --git a/gsk/resources/glsl/gl3_common.fs.glsl b/gsk/resources/glsl/gl3_common.fs.glsl
index 50f72f5..8ee5a14 100644
--- a/gsk/resources/glsl/gl3_common.fs.glsl
+++ b/gsk/resources/glsl/gl3_common.fs.glsl
@@ -3,8 +3,9 @@ precision highp float;
 uniform sampler2D uSource;
 uniform sampler2D uMask;
 uniform mat4 uMVP;
-uniform float uAlpha;
+uniform float uAlpha = 1.0;
 uniform int uBlendMode;
+uniform vec4 uViewport;
 
 in vec2 vUv;
 
diff --git a/gsk/resources/glsl/gl3_common.vs.glsl b/gsk/resources/glsl/gl3_common.vs.glsl
index b90c2fe..f82f99c 100644
--- a/gsk/resources/glsl/gl3_common.vs.glsl
+++ b/gsk/resources/glsl/gl3_common.vs.glsl
@@ -1,4 +1,5 @@
 uniform mat4 uMVP;
+uniform mat4 uProjection;
 
 in vec2 aPosition;
 in vec2 aUv;
diff --git a/gsk/resources/glsl/linear_gradient.fs.glsl b/gsk/resources/glsl/linear_gradient.fs.glsl
new file mode 100644
index 0000000..cbbc71c
--- /dev/null
+++ b/gsk/resources/glsl/linear_gradient.fs.glsl
@@ -0,0 +1,43 @@
+uniform vec4 uColorStops[8];
+uniform float uColorOffsets[8];
+uniform int uNumColorStops;
+uniform vec2 uStartPoint;
+uniform vec2 uEndPoint;
+
+vec4 fragCoord() {
+  vec4 f = gl_FragCoord;
+  f.x += uViewport.x;
+  f.y = (uViewport.y + uViewport.w) - f.y;
+  return f;
+}
+
+void main() {
+  float maxDist = length(uEndPoint - uStartPoint);
+
+  // Position relative to startPoint
+  vec2 pos = fragCoord().xy - uStartPoint;
+
+  // Gradient direction
+  vec2 gradient = uEndPoint - uStartPoint;
+  float gradientLength = length(gradient);
+
+  // Current pixel, projected onto the line between the start point and the end point
+  // The projection will be relative to the start point!
+  vec2 proj = (dot(gradient, pos) / (gradientLength * gradientLength)) * gradient;
+
+  // Offset of the current pixel
+  float offset = length(proj) / maxDist;
+
+  vec4 color = uColorStops[0];
+  for (int i = 1; i < uNumColorStops; i ++) {
+    if (offset >= uColorOffsets[i - 1])  {
+      float o = (offset - uColorOffsets[i - 1]) / (uColorOffsets[i] - uColorOffsets[i - 1]);
+      color = mix(uColorStops[i - 1], uColorStops[i], o);
+    }
+  }
+
+  /* Pre-multiply */
+  color.rgb *= color.a;
+
+  setOutputColor(color * uAlpha);
+}
diff --git a/gsk/resources/glsl/linear_gradient.vs.glsl b/gsk/resources/glsl/linear_gradient.vs.glsl
new file mode 100644
index 0000000..a453e32
--- /dev/null
+++ b/gsk/resources/glsl/linear_gradient.vs.glsl
@@ -0,0 +1,5 @@
+void main() {
+  gl_Position = uMVP * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}


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