[gtk+/wip/matthiasc/glyph-cache] Make the glyph cache grow as needed



commit 2e064864849f13a145904c96230e1b51e99cc3b8
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Sep 19 23:32:07 2017 -0400

    Make the glyph cache grow as needed
    
    Make it possible to have more than one texture in the
    glyph cache, and create new ones when we run out of space
    in the existing ones.

 gsk/gskglyphpcache.c             |  269 ++++++++++++++++++++++++++++++++++++++
 gsk/gskvulkancolortextpipeline.c |    4 +-
 gsk/gskvulkanglyphcache.c        |  156 +++++++++++++---------
 gsk/gskvulkanrenderpass.c        |   48 +++++---
 gsk/gskvulkantextpipeline.c      |    5 +-
 5 files changed, 399 insertions(+), 83 deletions(-)
---
diff --git a/gsk/gskglyphpcache.c b/gsk/gskglyphpcache.c
new file mode 100644
index 0000000..c92a2b6
--- /dev/null
+++ b/gsk/gskglyphpcache.c
@@ -0,0 +1,269 @@
+#include "config.h"
+
+#include "gskglyphcacheprivate.h"
+
+#include <graphene.h>
+
+typedef struct _GlyphCache GlyphCache;
+
+struct _GlyphCache {
+  GHashTable *hash_table;
+
+  cairo_surface_t *surface;
+  int width, height;
+  int x, y, y0;
+
+  GskVulkanImage *image;
+};
+
+
+static GlyphCache *create_glyph_cache     (void);
+static void        free_glyph_cache       (GlyphCache *cache);
+static void        dump_glyph_cache_stats (GlyphCache *cache);
+
+
+typedef struct _GlyphCacheKey GlyphCacheKey;
+typedef struct _GlyphCacheValue GlyphCacheValue;
+
+struct _GlyphCacheKey {
+  PangoFont *font;
+  PangoGlyph glyph;
+};
+
+struct _GlyphCacheValue {
+  float tx;
+  float ty;
+  float tw;
+  float th;
+
+  float draw_x;
+  float draw_y;
+  float draw_width;
+  float draw_height;
+};
+
+static GlyphCacheValue *glyph_cache_lookup (GlyphCache *cache,
+                                            gboolean    create,
+                                            PangoFont  *font,
+                                            PangoGlyph  glyph);
+
+void
+gsk_vulkan_renderer_get_glyph_coords (GskVulkanRenderer *self,
+                                      PangoFont         *font,
+                                      PangoGlyph         glyph,
+                                      float             *tx,
+                                      float             *ty,
+                                      float             *tw,
+                                      float             *th,
+                                      float             *dx,
+                                      float             *dy,
+                                      float             *dw,
+                                      float             *dh)
+{
+  GlyphCacheValue *gv;
+
+  gv = glyph_cache_lookup (self->glyph_cache, FALSE, font, glyph);
+
+  if (gv)
+    {
+      *tx = gv->tx;
+      *ty = gv->ty;
+      *tw = gv->tw;
+      *th = gv->th;
+      *dx = gv->draw_x;
+      *dy = gv->draw_y;
+      *dw = gv->draw_width;
+      *dh = gv->draw_height;
+    }
+}
+
+static gboolean
+glyph_cache_equal (gconstpointer v1, gconstpointer v2)
+{
+  const GlyphCacheKey *key1 = v1;
+  const GlyphCacheKey *key2 = v2;
+
+  return key1->font == key2->font &&
+         key1->glyph == key2->glyph;
+}
+
+static guint
+glyph_cache_hash (gconstpointer v)
+{
+  const GlyphCacheKey *key = v;
+
+  return GPOINTER_TO_UINT (key->font) ^ key->glyph;
+}
+
+static void
+glyph_cache_key_free (gpointer v)
+{
+  GlyphCacheKey *f = v;
+
+  g_object_unref (f->font);
+  g_free (f);
+}
+
+static void
+glyph_cache_value_free (gpointer v)
+{
+  g_free (v);
+}
+
+static void
+add_to_cache (GlyphCache      *cache,
+              PangoFont       *font,
+              PangoGlyph       glyph,
+              GlyphCacheValue *value)
+{
+  cairo_t *cr;
+  cairo_scaled_font_t *scaled_font;
+  cairo_glyph_t cg;
+
+  if (cache->x + value->draw_width + 1 >= cache->width)
+    {
+      /* start a new row */
+      cache->y0 = cache->y + 1;
+      cache->x = 1;
+    }
+
+  if (cache->y0 + value->draw_height + 1 >= cache->height)
+    {
+      g_critical ("Drats! Out of cache space. We should really handle this");
+      return;
+    }
+
+  cr = cairo_create (cache->surface);
+
+  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
+  if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
+    return;
+
+  cairo_set_scaled_font (cr, scaled_font);
+  cairo_set_source_rgba (cr, 1, 1, 1, 1);
+
+  cg.index = glyph;
+  cg.x = cache->x - value->draw_x;
+  cg.y = cache->y0 - value->draw_y;
+
+  cairo_show_glyphs (cr, &cg, 1);
+
+  cairo_destroy (cr);
+
+  cache->x = cache->x + value->draw_width + 1;
+  cache->y = MAX (cache->y, cache->y0 + value->draw_height + 1);
+
+  value->tx = (cg.x + value->draw_x) / cache->width;
+  value->ty = (cg.y + value->draw_y) / cache->height;
+  value->tw = (float)value->draw_width / cache->width;
+  value->th = (float)value->draw_height / cache->height;
+}
+
+static GlyphCacheValue *
+glyph_cache_lookup (GlyphCache *cache,
+                    gboolean    create,
+                    PangoFont  *font,
+                    PangoGlyph  glyph)
+{
+  GlyphCacheKey lookup_key;
+  GlyphCacheValue *value;
+
+  lookup_key.font = font;
+  lookup_key.glyph = glyph;
+
+  value = g_hash_table_lookup (cache->hash_table, &lookup_key);
+
+  if (create && value == NULL)
+    {
+      GlyphCacheKey *key;
+      PangoRectangle ink_rect;
+
+      value = g_new (GlyphCacheValue, 1);
+
+      pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
+      pango_extents_to_pixels (&ink_rect, NULL);
+
+      value->draw_x = ink_rect.x;
+      value->draw_y = ink_rect.y;
+      value->draw_width = ink_rect.width;
+      value->draw_height = ink_rect.height;
+
+      if (ink_rect.width > 0 && ink_rect.height > 0)
+        {
+          add_to_cache (cache, font, glyph, value);
+
+          g_clear_object (&cache->image);
+        }
+
+      key = g_new (GlyphCacheKey, 1);
+      key->font = g_object_ref (font);
+      key->glyph = glyph;
+
+      g_hash_table_insert (cache->hash_table, key, value);
+    }
+
+  return value;
+}
+
+void
+gsk_vulkan_renderer_cache_glyphs (GskVulkanRenderer *self,
+                                  PangoFont         *font,
+                                  PangoGlyphString  *glyphs)
+{
+  int i;
+
+  for (i = 0; i < glyphs->num_glyphs; i++)
+    {
+      PangoGlyphInfo *gi = &glyphs->glyphs[i];
+
+      if (gi->glyph != PANGO_GLYPH_EMPTY)
+        {
+          if (!(gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG))
+            (void) glyph_cache_lookup (self->glyph_cache, TRUE, font, gi->glyph);
+        }
+    }
+}
+
+static GlyphCache *
+create_glyph_cache (void)
+{
+  GlyphCache *cache;
+
+  cache = g_new0 (GlyphCache, 1);
+  cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
+                                             glyph_cache_key_free, glyph_cache_value_free);
+  cache->width = 1024;
+  cache->height = 1024;
+  cache->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, cache->width, cache->height);
+  cache->y0 = 1;
+  cache->y = 1;
+  cache->x = 1;
+
+  return cache;
+}
+
+static void
+free_glyph_cache (GlyphCache *cache)
+{
+  g_hash_table_unref (cache->hash_table);
+  cairo_surface_destroy (cache->surface);
+  g_free (cache);
+}
+
+static void
+dump_glyph_cache_stats (GlyphCache *cache)
+{
+  static gint64 time;
+  gint64 now;
+
+  if (!cache->hash_table)
+    return;
+
+  now = g_get_monotonic_time ();
+  if (now - time < 1000000)
+    return;
+
+  time = now;
+
+  cairo_surface_write_to_png (cache->surface, "gsk-glyph-cache.png");
+}
diff --git a/gsk/gskvulkancolortextpipeline.c b/gsk/gskvulkancolortextpipeline.c
index 6f97a8d..42b4e8c 100644
--- a/gsk/gskvulkancolortextpipeline.c
+++ b/gsk/gskvulkancolortextpipeline.c
@@ -104,14 +104,14 @@ gsk_vulkan_color_text_pipeline_collect_vertex_data (GskVulkanColorTextPipeline *
                                                     guint                       num_glyphs)
 {
   GskVulkanColorTextInstance *instances = (GskVulkanColorTextInstance *) data;
-  int i
+  int i;
   int count = 0;
   int x_position = 0;
 
   for (i = 0; i < start_glyph; i++)
     x_position += glyphs->glyphs[i].geometry.width;
 
-  for (; i < num_glyphs; i++)
+  for (; i < glyphs->num_glyphs && count < num_glyphs; i++)
     {
       PangoGlyphInfo *gi = &glyphs->glyphs[i];
 
diff --git a/gsk/gskvulkanglyphcache.c b/gsk/gskvulkanglyphcache.c
index 5656785..fc36b0a 100644
--- a/gsk/gskvulkanglyphcache.c
+++ b/gsk/gskvulkanglyphcache.c
@@ -8,16 +8,18 @@
 
 #include <graphene.h>
 
-struct _GskVulkanGlyphCache {
-  GObject parent_instance;
-
-  GHashTable *hash_table;
-
+typedef struct {
   cairo_surface_t *surface;
+  GskVulkanImage *image;
   int width, height;
   int x, y, y0;
+} Atlas;
 
-  GskVulkanImage *image;
+struct _GskVulkanGlyphCache {
+  GObject parent_instance;
+
+  GHashTable *hash_table;
+  GPtrArray *atlases;
 };
 
 struct _GskVulkanGlyphCacheClass {
@@ -32,17 +34,41 @@ static gboolean glyph_cache_equal      (gconstpointer v1,
 static void     glyph_cache_key_free   (gpointer      v);
 static void     glyph_cache_value_free (gpointer      v);
 
+static Atlas *
+create_atlas (void)
+{
+  Atlas *atlas;
+
+  atlas = g_new (Atlas, 1);
+  atlas->width = 512;
+  atlas->height = 512;
+  atlas->y0 = 1;
+  atlas->y = 1;
+  atlas->x = 1;
+  atlas->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, atlas->width, atlas->height);
+  atlas->image = NULL;
+
+  return atlas;
+}
+
+static void
+free_atlas (gpointer v)
+{
+  Atlas *atlas = v;
+
+  if (atlas->surface)
+    cairo_surface_destroy (atlas->surface);
+  g_clear_object (&atlas->image);
+  g_free (atlas);
+}
+
 static void
 gsk_vulkan_glyph_cache_init (GskVulkanGlyphCache *cache)
 {
   cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
                                              glyph_cache_key_free, glyph_cache_value_free);
-  cache->width = 1024;
-  cache->height = 1024;
-  cache->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, cache->width, cache->height);
-  cache->y0 = 1;
-  cache->y = 1;
-  cache->x = 1;
+  cache->atlases = g_ptr_array_new_with_free_func (free_atlas);
+  g_ptr_array_add (cache->atlases, create_atlas ());
 }
 
 static void
@@ -51,8 +77,7 @@ gsk_vulkan_glyph_cache_finalize (GObject *object)
   GskVulkanGlyphCache *cache = GSK_VULKAN_GLYPH_CACHE (object);
 
   g_hash_table_unref (cache->hash_table);
-  cairo_surface_destroy (cache->surface);
-  g_clear_object (&cache->image);
+  g_ptr_array_unref (cache->atlases);
 
   G_OBJECT_CLASS (gsk_vulkan_glyph_cache_parent_class)->finalize (object);
 }
@@ -112,21 +137,41 @@ add_to_cache (GskVulkanGlyphCache  *cache,
   cairo_t *cr;
   cairo_scaled_font_t *scaled_font;
   cairo_glyph_t cg;
+  Atlas *atlas;
+  int i;
 
-  if (cache->x + value->draw_width + 1 >= cache->width)
+  for (i = 0; i < cache->atlases->len; i++)
     {
-      /* start a new row */
-      cache->y0 = cache->y + 1;
-      cache->x = 1;
+      int x, y, y0;
+
+      atlas = g_ptr_array_index (cache->atlases, i);
+      x = atlas->x;
+      y = atlas->y;
+      y0 = atlas->y0;
+
+      if (atlas->x + value->draw_width + 1 >= atlas->width)
+        {
+          /* start a new row */
+          y0 = y + 1;
+          x = 1;
+        }
+
+      if (y0 + value->draw_height + 1 >= atlas->height)
+        continue;
+
+      atlas->y0 = y0;
+      atlas->x = x;
+      atlas->y = y;
+      break;
     }
 
-  if (cache->y0 + value->draw_height + 1 >= cache->height)
+  if (i == cache->atlases->len)
     {
-      g_critical ("Drats! Out of cache space. We should really handle this");
-      return;
+      atlas = create_atlas ();
+      g_ptr_array_add (cache->atlases, atlas);
     }
 
-  cr = cairo_create (cache->surface);
+  cr = cairo_create (atlas->surface);
 
   scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
   if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
@@ -136,43 +181,25 @@ add_to_cache (GskVulkanGlyphCache  *cache,
   cairo_set_source_rgba (cr, 1, 1, 1, 1);
 
   cg.index = glyph;
-  cg.x = cache->x - value->draw_x;
-  cg.y = cache->y0 - value->draw_y;
+  cg.x = atlas->x - value->draw_x;
+  cg.y = atlas->y0 - value->draw_y;
 
   cairo_show_glyphs (cr, &cg, 1);
 
   cairo_destroy (cr);
 
-  cache->x = cache->x + value->draw_width + 1;
-  cache->y = MAX (cache->y, cache->y0 + value->draw_height + 1);
-
-  value->tx = (cg.x + value->draw_x) / cache->width;
-  value->ty = (cg.y + value->draw_y) / cache->height;
-  value->tw = (float)value->draw_width / cache->width;
-  value->th = (float)value->draw_height / cache->height;
-
-  value->texture_index = 0;
-}
-
-#if 0
-static void
-dump_glyph_cache_stats (GskVulkanGlyphCache *cache)
-{
-  static gint64 time;
-  gint64 now;
-
-  if (!cache->hash_table)
-    return;
+  atlas->x = atlas->x + value->draw_width + 1;
+  atlas->y = MAX (atlas->y, atlas->y0 + value->draw_height + 1);
 
-  now = g_get_monotonic_time ();
-  if (now - time < 1000000)
-    return;
+  value->tx = (cg.x + value->draw_x) / atlas->width;
+  value->ty = (cg.y + value->draw_y) / atlas->height;
+  value->tw = (float)value->draw_width / atlas->width;
+  value->th = (float)value->draw_height / atlas->height;
 
-  time = now;
+  value->texture_index = i;
 
-  cairo_surface_write_to_png (cache->surface, "gsk-glyph-cache.png");
+  g_clear_object (&atlas->image); /* force re-upload */
 }
-#endif
 
 GskVulkanGlyphCache *
 gsk_vulkan_glyph_cache_new (void)
@@ -199,7 +226,7 @@ gsk_vulkan_glyph_cache_lookup (GskVulkanGlyphCache *cache,
       GlyphCacheKey *key;
       PangoRectangle ink_rect;
 
-      value = g_new (GskVulkanCachedGlyph, 1);
+      value = g_new0 (GskVulkanCachedGlyph, 1);
 
       pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
       pango_extents_to_pixels (&ink_rect, NULL);
@@ -210,10 +237,7 @@ gsk_vulkan_glyph_cache_lookup (GskVulkanGlyphCache *cache,
       value->draw_height = ink_rect.height;
 
       if (ink_rect.width > 0 && ink_rect.height > 0)
-        {
-          add_to_cache (cache, font, glyph, value);
-          g_clear_object (&cache->image);
-        }
+        add_to_cache (cache, font, glyph, value);
 
       key = g_new (GlyphCacheKey, 1);
       key->font = g_object_ref (font);
@@ -230,12 +254,18 @@ gsk_vulkan_glyph_cache_get_glyph_image (GskVulkanGlyphCache *cache,
                                         GskVulkanUploader   *uploader,
                                         guint                index)
 {
-  if (cache->image == NULL)
-    cache->image = gsk_vulkan_image_new_from_data (uploader,
-                                                   cairo_image_surface_get_data (cache->surface),
-                                                   cairo_image_surface_get_width (cache->surface),
-                                                   cairo_image_surface_get_height (cache->surface),
-                                                   cairo_image_surface_get_stride (cache->surface));
-
-  return cache->image;
+  Atlas *atlas;
+
+  g_return_val_if_fail (index < cache->atlases->len, NULL);
+
+  atlas = g_ptr_array_index (cache->atlases, index);
+
+  if (atlas->image == NULL)
+    atlas->image = gsk_vulkan_image_new_from_data (uploader,
+                                                   cairo_image_surface_get_data (atlas->surface),
+                                                   cairo_image_surface_get_width (atlas->surface),
+                                                   cairo_image_surface_get_height (atlas->surface),
+                                                   cairo_image_surface_get_stride (atlas->surface));
+
+  return atlas->image;
 }
diff --git a/gsk/gskvulkanrenderpass.c b/gsk/gskvulkanrenderpass.c
index d6dc158..f3b3dea 100644
--- a/gsk/gskvulkanrenderpass.c
+++ b/gsk/gskvulkanrenderpass.c
@@ -71,9 +71,9 @@ struct _GskVulkanOpText
   gsize                vertex_offset; /* offset into vertex buffer */
   gsize                vertex_count; /* number of vertices */
   gsize                descriptor_set_index; /* index into descriptor sets array for the right descriptor 
set to bind */
-  guint                texture_index;
-  guint                start_glyph;
-  guint                num_glyphs;
+  guint                texture_index; /* index of the texture in the glyph cache */
+  guint                start_glyph; /* the first glyph in nodes glyphstring that we render */
+  guint                num_glyphs; /* number of *non-empty* glyphs (== instances) we render */
 };
 
 struct _GskVulkanOpPushConstants
@@ -217,8 +217,8 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass           *self,
       {
         PangoFont *font = gsk_text_node_get_font (node);
         PangoGlyphString *glyphs = gsk_text_node_get_glyphs (node);
-        PangoGlyph glyph;
         int i;
+        guint count;
         guint texture_index;
         GskVulkanRenderer *renderer = GSK_VULKAN_RENDERER (gsk_vulkan_render_get_renderer (render));
 
@@ -248,23 +248,39 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass           *self,
           }
         op.text.pipeline = gsk_vulkan_render_get_pipeline (render, pipeline_type);
 
-        i = 0;
-        texture_index = gsk_vulkan_renderer_cache_glyph (renderer, font, glyphs->glyphs[0].glyph);
-        while (i < glyphs->num_glyphs)
-          {
-            op.text.start_glyph = i;
-            op.text.texture_index = texture_index;
+        op.text.start_glyph = 0;
+        op.text.texture_index = G_MAXUINT;
 
-            do {
-              i++;
-              glyph = glyphs->glyphs[i].glyph;
-              if (glyph != PANGO_GLYPH_EMPTY && !(glyph & PANGO_GLYPH_UNKNOWN_FLAG))
-                texture_index = gsk_vulkan_renderer_cache_glyph (renderer, font, glyph);
-            } while (i < glyphs->num_glyphs && op.text.texture_index == texture_index);
+        for (i = 0, count = 0; i < glyphs->num_glyphs; i++)
+          {
+            PangoGlyphInfo *gi = &glyphs->glyphs[i];
+
+            if (gi->glyph != PANGO_GLYPH_EMPTY && !(gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG))
+              {
+                texture_index = gsk_vulkan_renderer_cache_glyph (renderer, font, gi->glyph);
+                if (op.text.texture_index == G_MAXUINT)
+                  op.text.texture_index = texture_index;
+                if (texture_index != op.text.texture_index)
+                  {
+                     op.text.num_glyphs = count;
+
+                     g_array_append_val (self->render_ops, op);
+
+                     count = 1;
+                     op.text.start_glyph = i;
+                     op.text.texture_index = texture_index;
+                  }
+                else
+                  count++;
+              }
+          }
 
-            op.text.num_glyphs = i - op.text.start_glyph;
+        if (op.text.texture_index != G_MAXUINT && count != 0)
+          {
+            op.text.num_glyphs = count;
             g_array_append_val (self->render_ops, op);
           }
+
         return;
       }
 
diff --git a/gsk/gskvulkantextpipeline.c b/gsk/gskvulkantextpipeline.c
index 76c9988..e3295c4 100644
--- a/gsk/gskvulkantextpipeline.c
+++ b/gsk/gskvulkantextpipeline.c
@@ -112,14 +112,14 @@ gsk_vulkan_text_pipeline_collect_vertex_data (GskVulkanTextPipeline  *pipeline,
                                               guint                   num_glyphs)
 {
   GskVulkanTextInstance *instances = (GskVulkanTextInstance *) data;
-  int i
+  int i;
   int count = 0;
   int x_position = 0;
 
   for (i = 0; i < start_glyph; i++)
     x_position += glyphs->glyphs[i].geometry.width;
 
-  for (; i < num_glyphs; i++)
+  for (; i < glyphs->num_glyphs && count < num_glyphs; i++)
     {
       PangoGlyphInfo *gi = &glyphs->glyphs[i];
 
@@ -134,6 +134,7 @@ gsk_vulkan_text_pipeline_collect_vertex_data (GskVulkanTextPipeline  *pipeline,
               GskVulkanCachedGlyph *glyph;
 
               glyph = gsk_vulkan_renderer_get_cached_glyph (renderer, font, gi->glyph);
+
               instance->tex_rect[0] = glyph->tx;
               instance->tex_rect[1] = glyph->ty;
               instance->tex_rect[2] = glyph->tw;


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