[gtk/mask-nodes: 123/123] gl: Render fill/stroke nodes using the mask shader




commit 4ae73eb987dee162002591bf0a459ed66b06beab
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Dec 14 20:35:39 2020 -0500

    gl: Render fill/stroke nodes using the mask shader
    
    Convert the path to an alpha mask and upload that
    as a texture to use with the mask shader. We use
    the path cache to reuse masks for the same path.

 gsk/gl/gskglpathcache.c        |  15 +++
 gsk/gl/gskglpathcacheprivate.h |   4 +
 gsk/gl/gskglrenderer.c         | 222 ++++++++++++++++++++++++++++++++++++++++-
 gsk/gl/gskglrenderopsprivate.h |   1 +
 4 files changed, 239 insertions(+), 3 deletions(-)
---
diff --git a/gsk/gl/gskglpathcache.c b/gsk/gl/gskglpathcache.c
index 1dcb0c33d8..13f159e7c1 100644
--- a/gsk/gl/gskglpathcache.c
+++ b/gsk/gl/gskglpathcache.c
@@ -10,6 +10,8 @@ typedef struct
   GskPath *path;
   GskFillRule fill_rule;
   const GskStroke *stroke;
+  float scale_x;
+  float scale_y;
   guint hash;
 
   GskStroke stroke_copy;
@@ -27,6 +29,8 @@ compute_hash (CacheItem *item)
   hash += item->fill_rule;
   if (item->stroke)
     hash += gsk_stroke_hash (item->stroke);
+  hash += (int)(item->scale_x * 100);
+  hash += (int)(item->scale_y * 200);
 
   return hash;
 }
@@ -48,6 +52,8 @@ cache_key_equal (gconstpointer v1,
 
   return item1->path == item2->path &&
          item1->fill_rule == item2->fill_rule &&
+         item1->scale_x == item2->scale_x &&
+         item1->scale_y == item2->scale_y &&
          (item1->stroke == item2->stroke ||
           (item1->stroke && item2->stroke &&
            gsk_stroke_equal (item1->stroke, item2->stroke)));
@@ -102,6 +108,8 @@ gsk_gl_path_cache_get_texture_id (GskGLPathCache  *self,
                                   GskPath         *path,
                                   GskFillRule      fill_rule,
                                   const GskStroke *stroke,
+                                  float            scale_x,
+                                  float            scale_y,
                                   graphene_rect_t *out_bounds)
 {
   CacheItem key;
@@ -110,6 +118,8 @@ gsk_gl_path_cache_get_texture_id (GskGLPathCache  *self,
   key.path = path;
   key.fill_rule = fill_rule;
   key.stroke = stroke;
+  key.scale_x = scale_x;
+  key.scale_y = scale_y;
   key.hash = compute_hash (&key);
 
   item = g_hash_table_lookup (self->textures, &key);
@@ -130,6 +140,8 @@ gsk_gl_path_cache_commit (GskGLPathCache        *self,
                           GskPath               *path,
                           GskFillRule            fill_rule,
                           const GskStroke       *stroke,
+                          float                  scale_x,
+                          float                  scale_y,
                           int                    texture_id,
                           const graphene_rect_t *bounds)
 {
@@ -147,6 +159,9 @@ gsk_gl_path_cache_commit (GskGLPathCache        *self,
       gsk_stroke_init_copy (&item->stroke_copy, stroke);
       item->stroke = &item->stroke_copy;
     }
+  item->scale_x = scale_x;
+  item->scale_y = scale_y;
+
   item->hash = compute_hash (item);
 
   item->unused_frames = 0;
diff --git a/gsk/gl/gskglpathcacheprivate.h b/gsk/gl/gskglpathcacheprivate.h
index 181d4be26f..cc2081c8ea 100644
--- a/gsk/gl/gskglpathcacheprivate.h
+++ b/gsk/gl/gskglpathcacheprivate.h
@@ -20,11 +20,15 @@ int  gsk_gl_path_cache_get_texture_id (GskGLPathCache        *self,
                                        GskPath               *path,
                                        GskFillRule            fill_rule,
                                        const GskStroke       *stroke,
+                                       float                  scale_x,
+                                       float                  scale_y,
                                        graphene_rect_t       *out_bounds);
 void gsk_gl_path_cache_commit         (GskGLPathCache        *self,
                                        GskPath               *path,
                                        GskFillRule            fill_rule,
                                        const GskStroke       *stroke,
+                                       float                  scale_x,
+                                       float                  scale_y,
                                        int                    texture_id,
                                        const graphene_rect_t *bounds);
 
diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c
index 09b7b4f0a1..4b70320b71 100644
--- a/gsk/gl/gskglrenderer.c
+++ b/gsk/gl/gskglrenderer.c
@@ -2764,6 +2764,217 @@ render_mask_node (GskGLRenderer   *self,
   load_offscreen_vertex_data (ops_draw (builder, NULL), node, builder);
 }
 
+static GdkTexture *
+make_path_mask (GskPath         *path,
+                GskFillRule      fill_rule,
+                float            scale_x,
+                float            scale_y,
+                graphene_rect_t *bounds)
+{
+  cairo_surface_t *surface;
+  cairo_t *cr;
+  int width;
+  int height;
+  int stride;
+  guchar *buffer;
+  GBytes *bytes;
+  GdkTexture *mask;
+
+  gsk_path_get_bounds (path, bounds);
+
+  width = ceilf (bounds->size.width * scale_x);
+  height = ceilf (bounds->size.height * scale_y);
+  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+
+  buffer = g_malloc0 (stride * height);
+  surface = cairo_image_surface_create_for_data (buffer,
+                                                 CAIRO_FORMAT_ARGB32,
+                                                 width, height,
+                                                 stride);
+  cairo_surface_set_device_scale (surface, scale_x, scale_y);
+
+  cr = cairo_create (surface);
+  cairo_translate (cr, 0, bounds->size.height);
+  cairo_scale (cr, 1, -1);
+  cairo_translate (cr, - bounds->origin.x, - bounds->origin.y);
+
+ switch (fill_rule)
+  {
+    case GSK_FILL_RULE_WINDING:
+      cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
+      break;
+    case GSK_FILL_RULE_EVEN_ODD:
+      cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+
+  gsk_path_to_cairo (path, cr);
+  cairo_clip (cr);
+
+  cairo_set_source_rgb (cr, 0, 0, 0);
+  cairo_paint (cr);
+
+  cairo_destroy (cr);
+
+  bytes = g_bytes_new_take (buffer, stride * height);
+  mask = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
+  g_bytes_unref (bytes);
+
+  cairo_surface_destroy (surface);
+
+  return mask;
+}
+
+static inline void
+render_fill_node (GskGLRenderer   *self,
+                  GskRenderNode   *node,
+                  RenderOpBuilder *builder)
+{
+  GskRenderNode *child = gsk_fill_node_get_child (node);
+  TextureRegion child_region;
+  gboolean is_offscreen1;
+  GskPath *path;
+  GskFillRule fill_rule;
+  graphene_rect_t mask_bounds;
+  int mask_texture_id;
+  OpMask *op;
+  float scale_x = builder->scale_x;
+  float scale_y = builder->scale_y;
+
+  if (!add_offscreen_ops (self, builder,
+                          &node->bounds,
+                          child,
+                          &child_region, &is_offscreen1,
+                          FORCE_OFFSCREEN | RESET_CLIP))
+    {
+      gsk_gl_renderer_add_render_ops (self, child, builder);
+      return;
+    }
+
+  path = gsk_fill_node_get_path (node);
+  fill_rule = gsk_fill_node_get_fill_rule (node);
+  mask_texture_id = gsk_gl_path_cache_get_texture_id (&self->path_cache,
+                                                      path,
+                                                      fill_rule,
+                                                      NULL,
+                                                      scale_x, scale_y,
+                                                      &mask_bounds);
+  if (mask_texture_id == 0)
+    {
+      GdkTexture *mask;
+
+      mask = make_path_mask (path, fill_rule, scale_x, scale_y, &mask_bounds);
+
+      mask_texture_id =
+          gsk_gl_driver_get_texture_for_texture (self->gl_driver,
+                                                 mask,
+                                                 GL_LINEAR,
+                                                 GL_LINEAR);
+
+      gsk_gl_driver_mark_texture_permanent (self->gl_driver, mask_texture_id);
+      gsk_gl_path_cache_commit (&self->path_cache,
+                                path,
+                                fill_rule,
+                                NULL,
+                                scale_x, scale_y,
+                                mask_texture_id,
+                                &mask_bounds);
+
+      g_object_unref (mask);
+    }
+
+  ops_set_program (builder, &self->programs->mask_program);
+
+  op = ops_begin (builder, OP_CHANGE_MASK);
+  op->mask = mask_texture_id;
+  op->texture_rect[0] = (mask_bounds.origin.x - node->bounds.origin.x) / node->bounds.size.width;
+  op->texture_rect[1] = (mask_bounds.origin.y - node->bounds.origin.y) / node->bounds.size.height;
+  op->texture_rect[2] = (mask_bounds.origin.x + mask_bounds.size.width - node->bounds.origin.x) / 
node->bounds.size.width;
+  op->texture_rect[3] =  (mask_bounds.origin.y + mask_bounds.size.height - node->bounds.origin.y) / 
node->bounds.size.height;
+
+  ops_set_texture (builder, child_region.texture_id);
+
+  load_offscreen_vertex_data (ops_draw (builder, NULL), node, builder);
+}
+
+static inline void
+render_stroke_node (GskGLRenderer   *self,
+                    GskRenderNode   *node,
+                    RenderOpBuilder *builder)
+{
+  GskRenderNode *child = gsk_stroke_node_get_child (node);
+  TextureRegion child_region;
+  gboolean is_offscreen1;
+  GskPath *path;
+  GskStroke *stroke;
+  int mask_texture_id;
+  graphene_rect_t mask_bounds;
+  OpMask *op;
+  float scale_x = builder->scale_x;
+  float scale_y = builder->scale_y;
+
+  if (!add_offscreen_ops (self, builder,
+                          &node->bounds,
+                          child,
+                          &child_region, &is_offscreen1,
+                          FORCE_OFFSCREEN | RESET_CLIP))
+    {
+      gsk_gl_renderer_add_render_ops (self, child, builder);
+      return;
+    }
+
+  path = gsk_stroke_node_get_path (node);
+  stroke = (GskStroke *)gsk_stroke_node_get_stroke (node);
+
+  mask_texture_id = gsk_gl_path_cache_get_texture_id (&self->path_cache,
+                                                      path,
+                                                      GSK_FILL_RULE_EVEN_ODD,
+                                                      stroke,
+                                                      scale_x, scale_y,
+                                                      &mask_bounds);
+  if (mask_texture_id == 0)
+    {
+      GskPath *stroke_path;
+      GdkTexture *mask;
+
+      stroke_path = gsk_path_stroke (path, stroke);
+      mask = make_path_mask (stroke_path, GSK_FILL_RULE_EVEN_ODD, scale_x, scale_y, &mask_bounds);
+
+      mask_texture_id =
+          gsk_gl_driver_get_texture_for_texture (self->gl_driver,
+                                                 mask,
+                                                 GL_LINEAR,
+                                                 GL_LINEAR);
+
+      gsk_gl_driver_mark_texture_permanent (self->gl_driver, mask_texture_id);
+      gsk_gl_path_cache_commit (&self->path_cache,
+                                path,
+                                GSK_FILL_RULE_EVEN_ODD,
+                                stroke,
+                                scale_x, scale_y,
+                                mask_texture_id,
+                                &mask_bounds);
+      g_object_unref (mask);
+      gsk_path_unref (stroke_path);
+    }
+
+  ops_set_program (builder, &self->programs->mask_program);
+
+  op = ops_begin (builder, OP_CHANGE_MASK);
+  op->mask = mask_texture_id;
+  op->texture_rect[0] = (mask_bounds.origin.x - node->bounds.origin.x) / node->bounds.size.width;
+  op->texture_rect[1] = (mask_bounds.origin.y - node->bounds.origin.y) / node->bounds.size.height;
+  op->texture_rect[2] = (mask_bounds.origin.x + mask_bounds.size.width - node->bounds.origin.x) / 
node->bounds.size.width;
+  op->texture_rect[3] =  (mask_bounds.origin.y + mask_bounds.size.height - node->bounds.origin.y) / 
node->bounds.size.height;
+
+  ops_set_texture (builder, child_region.texture_id);
+
+  load_offscreen_vertex_data (ops_draw (builder, NULL), node, builder);
+}
+
 static inline void
 render_blend_node (GskGLRenderer   *self,
                    GskRenderNode   *node,
@@ -3493,7 +3704,6 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
   INIT_PROGRAM_UNIFORM_LOCATION (repeat, child_bounds);
   INIT_PROGRAM_UNIFORM_LOCATION (repeat, texture_rect);
 
-
   /* We initialize the alpha uniform here, since the default value is important.
    * We can't do it in the shader like a reasonable person would because that doesn't
    * work in gles. */
@@ -3875,10 +4085,16 @@ gsk_gl_renderer_add_render_ops (GskGLRenderer   *self,
       render_mask_node (self, node, builder);
     break;
 
-    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
-    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
     case GSK_FILL_NODE:
+      render_fill_node (self, node, builder);
+    break;
+
     case GSK_STROKE_NODE:
+      render_stroke_node (self, node, builder);
+    break;
+
+    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
     case GSK_CAIRO_NODE:
     default:
       {
diff --git a/gsk/gl/gskglrenderopsprivate.h b/gsk/gl/gskglrenderopsprivate.h
index 3d02866b83..148b6b8df8 100644
--- a/gsk/gl/gskglrenderopsprivate.h
+++ b/gsk/gl/gskglrenderopsprivate.h
@@ -181,6 +181,7 @@ struct _Program
     } glshader;
     struct {
       int mask_location;
+      int child_rect_location;
       int texture_rect_location;
     } mask;
   };


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