[gtk/wip/baedert/stb-rect-pack: 33/34] gl renderer: Move texture atlas into its own file



commit a07341b291b79645ff12c713d51a8b03dc13800a
Author: Timm Bäder <mail baedert org>
Date:   Thu Feb 14 10:45:29 2019 +0100

    gl renderer: Move texture atlas into its own file
    
    We want to reuse this somewhere else later.

 gsk/gl/gskglglyphcache.c          | 180 +++++++++++++++++++-------------------
 gsk/gl/gskglglyphcacheprivate.h   |  17 +---
 gsk/gl/gskglimage.c               |   1 +
 gsk/gl/gskgltextureatlas.c        |  84 ++++++++++++++++++
 gsk/gl/gskgltextureatlasprivate.h |  49 +++++++++++
 gsk/meson.build                   |   1 +
 6 files changed, 229 insertions(+), 103 deletions(-)
---
diff --git a/gsk/gl/gskglglyphcache.c b/gsk/gl/gskglglyphcache.c
index a372673be3..45a470c38e 100644
--- a/gsk/gl/gskglglyphcache.c
+++ b/gsk/gl/gskglglyphcache.c
@@ -4,6 +4,7 @@
 #include "gskgldriverprivate.h"
 #include "gskdebugprivate.h"
 #include "gskprivate.h"
+#include "gskgltextureatlasprivate.h"
 
 #include <graphene.h>
 #include <cairo.h>
@@ -14,16 +15,15 @@
  * Each cached glyph has an age that gets reset every time a cached glyph gets used.
  * Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
  * count of the pixels of each atlas that are taken up by old glyphs. We check the
- * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD, then
+ * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD_RATIO, then
  * we drop the atlas an all the glyphs contained in it from the cache.
  */
 
 #define MAX_AGE 60
 #define CHECK_INTERVAL 10
-#define MAX_OLD 0.333
+#define MAX_OLD_RATIO 0.333
 
 #define ATLAS_SIZE 512
-#define NODES_PER_ATLAS 512
 
 static guint    glyph_cache_hash       (gconstpointer v);
 static gboolean glyph_cache_equal      (gconstpointer v1,
@@ -31,22 +31,13 @@ static gboolean glyph_cache_equal      (gconstpointer v1,
 static void     glyph_cache_key_free   (gpointer      v);
 static void     glyph_cache_value_free (gpointer      v);
 
-static GskGLGlyphAtlas *
+static GskGLTextureAtlas *
 create_atlas (GskGLGlyphCache *cache)
 {
-  GskGLGlyphAtlas *atlas;
+  GskGLTextureAtlas *atlas;
 
-  atlas = g_new0 (GskGLGlyphAtlas, 1);
-  atlas->width = ATLAS_SIZE;
-  atlas->height = ATLAS_SIZE;
-  atlas->image = NULL;
-
-  atlas->nodes = g_malloc0 (sizeof (struct stbrp_node) * NODES_PER_ATLAS);
-  stbrp_init_target (&atlas->context,
-                     ATLAS_SIZE,
-                     ATLAS_SIZE,
-                     atlas->nodes,
-                     NODES_PER_ATLAS);
+  atlas = g_new (GskGLTextureAtlas, 1);
+  gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
 
   return atlas;
 }
@@ -54,15 +45,11 @@ create_atlas (GskGLGlyphCache *cache)
 static void
 free_atlas (gpointer v)
 {
-  GskGLGlyphAtlas *atlas = v;
+  GskGLTextureAtlas *atlas = v;
 
-  if (atlas->image)
-    {
-      g_assert (atlas->image->texture_id == 0);
-      g_free (atlas->image);
-    }
+  g_assert (atlas->image.texture_id == 0);
+  gsk_gl_texture_atlas_free (atlas);
 
-  g_free (atlas->nodes);
   g_free (atlas);
 }
 
@@ -87,12 +74,12 @@ gsk_gl_glyph_cache_free (GskGLGlyphCache *self)
 
   for (i = 0; i < self->atlases->len; i ++)
     {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
+      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
 
-      if (atlas->image)
+      if (atlas->image.texture_id != 0)
         {
-          gsk_gl_image_destroy (atlas->image, self->gl_driver);
-          atlas->image->texture_id = 0;
+          gsk_gl_image_destroy (&atlas->image, self->gl_driver);
+          atlas->image.texture_id = 0;
         }
     }
 
@@ -141,22 +128,20 @@ add_to_cache (GskGLGlyphCache  *cache,
 {
   const int width = value->draw_width * key->scale / 1024;
   const int height = value->draw_height * key->scale / 1024;
-  GskGLGlyphAtlas *atlas = NULL;
-  stbrp_rect glyph_rect;
+  GskGLTextureAtlas *atlas = NULL;
   guint i, p;
-
-  glyph_rect.w = width;
-  glyph_rect.h = height;
+  int packed_x, packed_y;
 
   /* Try all the atlases and pick the first one that can hold
    * our new glyph */
   for (i = 0, p = cache->atlases->len; i < p; i ++)
     {
-      GskGLGlyphAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
+      GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
+      gboolean was_packed;
 
-      stbrp_pack_rects (&test_atlas->context, &glyph_rect, 1);
+      was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y);
 
-      if (glyph_rect.was_packed)
+      if (was_packed)
         {
           atlas = test_atlas;
           break;
@@ -165,22 +150,31 @@ add_to_cache (GskGLGlyphCache  *cache,
 
   if (atlas == NULL)
     {
+      gboolean was_packed;
+
       atlas = create_atlas (cache);
       g_ptr_array_add (cache->atlases, atlas);
 
-      stbrp_pack_rects (&atlas->context, &glyph_rect, 1);
-      g_assert (glyph_rect.was_packed);
+      was_packed = gsk_gl_texture_atlas_pack (atlas,
+                                              width, height,
+                                              &packed_x, &packed_y);
+
+      g_assert (was_packed);
     }
 
-  value->tx = (float)glyph_rect.x / atlas->width;
-  value->ty = (float)glyph_rect.y / atlas->height;
-  value->tw = (float)glyph_rect.w / atlas->width;
-  value->th = (float)glyph_rect.h / atlas->height;
+  value->tx = (float)packed_x / atlas->width;
+  value->ty = (float)packed_y / atlas->height;
+  value->tw = (float)width    / atlas->width;
+  value->th = (float)height   / atlas->height;
+  value->used = TRUE;
 
   value->atlas = atlas;
 
-  atlas->pending_glyph.key = key;
-  atlas->pending_glyph.value = value;
+  if (atlas->user_data == NULL)
+    atlas->user_data = g_new0 (DirtyGlyph, 1);
+
+  ((DirtyGlyph *)atlas->user_data)->key = key;
+  ((DirtyGlyph *)atlas->user_data)->value = value;
 
 #ifdef G_ENABLE_DEBUG
   if (GSK_RENDERER_DEBUG_CHECK (cache->renderer, GLYPH_CACHE))
@@ -189,18 +183,18 @@ add_to_cache (GskGLGlyphCache  *cache,
       for (i = 0; i < cache->atlases->len; i++)
         {
           atlas = g_ptr_array_index (cache->atlases, i);
-          g_print ("\tGskGLGlyphAtlas %d (%dx%d): %.2g%% old pixels\n",
+          g_print ("\tGskGLTextureAtlas %d (%dx%d): %.2g%% old pixels\n",
                    i, atlas->width, atlas->height,
-                   100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height));
+                   gsk_gl_texture_atlas_get_unused_ratio (atlas));
         }
     }
 #endif
 }
 
 static void
-render_glyph (const GskGLGlyphAtlas *atlas,
-              const DirtyGlyph      *glyph,
-              GskImageRegion        *region)
+render_glyph (const GskGLTextureAtlas *atlas,
+              const DirtyGlyph        *glyph,
+              GskImageRegion          *region)
 {
   GlyphCacheKey *key = glyph->key;
   GskGLCachedGlyph *value = glyph->value;
@@ -252,21 +246,22 @@ render_glyph (const GskGLGlyphAtlas *atlas,
 }
 
 static void
-upload_dirty_glyph (GskGLGlyphCache *self,
-                    GskGLGlyphAtlas *atlas)
+upload_dirty_glyph (GskGLGlyphCache   *self,
+                    GskGLTextureAtlas *atlas)
 {
   GskImageRegion region;
 
-  g_assert (atlas->pending_glyph.key != NULL);
+  g_assert (atlas->user_data != NULL);
 
-  render_glyph (atlas, &atlas->pending_glyph, &region);
+  render_glyph (atlas, (DirtyGlyph *)atlas->user_data, &region);
 
-  gsk_gl_image_upload_regions (atlas->image, self->gl_driver, 1, &region);
+  gsk_gl_image_upload_regions (&atlas->image, self->gl_driver, 1, &region);
 
   g_free (region.data);
 
-  atlas->pending_glyph.key = NULL;
-  atlas->pending_glyph.value = NULL;
+  /* TODO: This could be unnecessary. We can just reuse the allocated
+   *       DirtyGlyph next time. */
+  g_clear_pointer (&atlas->user_data, g_free);
 }
 
 const GskGLCachedGlyph *
@@ -291,10 +286,13 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
 
       if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
         {
-          GskGLGlyphAtlas *atlas = value->atlas;
+          GskGLTextureAtlas *atlas = value->atlas;
 
-          if (atlas)
-            atlas->old_pixels -= value->draw_width * value->draw_height;
+          if (atlas && !value->used)
+            {
+              gsk_gl_texture_atlas_mark_used (atlas, value->draw_width, value->draw_height);
+              value->used = TRUE;
+            }
 
           value->timestamp = cache->timestamp;
         }
@@ -336,20 +334,19 @@ GskGLImage *
 gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache        *self,
                                     const GskGLCachedGlyph *glyph)
 {
-  GskGLGlyphAtlas *atlas = glyph->atlas;
+  GskGLTextureAtlas *atlas = glyph->atlas;
 
   g_assert (atlas != NULL);
 
-  if (atlas->image == NULL)
+  if (atlas->image.texture_id == 0)
     {
-      atlas->image = g_new0 (GskGLImage, 1);
-      gsk_gl_image_create (atlas->image, self->gl_driver, atlas->width, atlas->height);
+      gsk_gl_image_create (&atlas->image, self->gl_driver, atlas->width, atlas->height);
     }
 
-  if (atlas->pending_glyph.key != NULL)
+  if (atlas->user_data != NULL)
     upload_dirty_glyph (self, atlas);
 
-  return atlas->image;
+  return &atlas->image;
 }
 
 void
@@ -363,46 +360,33 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
 
   self->timestamp++;
 
-
   if ((self->timestamp - 1) % CHECK_INTERVAL != 0)
     return;
 
-  /* look for glyphs that have grown old since last time */
-  g_hash_table_iter_init (&iter, self->hash_table);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
-    {
-      const guint age = self->timestamp - value->timestamp;
-
-      if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
-        {
-          GskGLGlyphAtlas *atlas = value->atlas;
-
-          if (atlas)
-            atlas->old_pixels += value->draw_width * value->draw_height;
-        }
-    }
-
   /* look for atlases to drop, and create a mapping of updated texture indices */
   for (i = self->atlases->len - 1; i >= 0; i--)
     {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
+      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
 
-      if (atlas->old_pixels > MAX_OLD * atlas->width * atlas->height)
+      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
         {
           GSK_RENDERER_NOTE(self->renderer, GLYPH_CACHE,
-                   g_message ("Dropping atlas %d (%g.2%% old)",
-                            i, 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height)));
+                   g_message ("Dropping atlas %d (%g.2%% old)", i,
+                              gsk_gl_texture_atlas_get_unused_ratio (atlas)));
 
+#if 0
           static int kk;
 
-          g_message ("Dropping cache...");
-          gsk_gl_image_write_to_png (atlas->image, self->gl_driver,
+          g_message ("Dropping cache... Ratio: %f",
+                     gsk_gl_texture_atlas_get_unused_ratio (atlas));
+          gsk_gl_image_write_to_png (&atlas->image, self->gl_driver,
                                      g_strdup_printf ("dropped_%d.png", kk++));
+#endif
 
-          if (atlas->image)
+          if (atlas->image.texture_id != 0)
             {
-              gsk_gl_image_destroy (atlas->image, self->gl_driver);
-              atlas->image->texture_id = 0;
+              gsk_gl_image_destroy (&atlas->image, self->gl_driver);
+              atlas->image.texture_id = 0;
             }
 
           /* Remove all glyphs that point to this atlas */
@@ -418,5 +402,23 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
         }
     }
 
+  /* look for glyphs that have grown old since last time */
+  g_hash_table_iter_init (&iter, self->hash_table);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+    {
+      const guint age = self->timestamp - value->timestamp;
+
+      if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
+        {
+          GskGLTextureAtlas *atlas = value->atlas;
+
+          if (atlas && value->used)
+            {
+              gsk_gl_texture_atlas_mark_unused (atlas, value->draw_width, value->draw_height);
+              value->used = FALSE;
+            }
+        }
+    }
+
   GSK_RENDERER_NOTE(self->renderer, GLYPH_CACHE, g_message ("Dropped %d glyphs", dropped));
 }
diff --git a/gsk/gl/gskglglyphcacheprivate.h b/gsk/gl/gskglglyphcacheprivate.h
index 0132e33138..29427f0cd8 100644
--- a/gsk/gl/gskglglyphcacheprivate.h
+++ b/gsk/gl/gskglglyphcacheprivate.h
@@ -4,7 +4,7 @@
 #include "gskgldriverprivate.h"
 #include "gskglimageprivate.h"
 #include "gskrendererprivate.h"
-#include "stb_rect_pack.h"
+#include "gskgltextureatlasprivate.h"
 #include <pango/pango.h>
 #include <gdk/gdk.h>
 
@@ -35,21 +35,9 @@ struct _DirtyGlyph
   GskGLCachedGlyph *value;
 };
 
-typedef struct
-{
-  GskGLImage *image;
-  int width, height;
-  guint old_pixels;
-
-  DirtyGlyph pending_glyph;
-
-  struct stbrp_context context;
-  struct stbrp_node *nodes;
-} GskGLGlyphAtlas;
-
 struct _GskGLCachedGlyph
 {
-  GskGLGlyphAtlas *atlas;
+  GskGLTextureAtlas *atlas;
 
   float tx;
   float ty;
@@ -64,6 +52,7 @@ struct _GskGLCachedGlyph
   float scale;
 
   guint64 timestamp;
+  guint used: 1;
 };
 
 
diff --git a/gsk/gl/gskglimage.c b/gsk/gl/gskglimage.c
index 6d91b35568..49dd630bde 100644
--- a/gsk/gl/gskglimage.c
+++ b/gsk/gl/gskglimage.c
@@ -21,6 +21,7 @@ gsk_gl_image_destroy (GskGLImage  *self,
                       GskGLDriver *gl_driver)
 {
   gsk_gl_driver_destroy_texture (gl_driver, self->texture_id);
+  self->texture_id = 0;
 }
 
 void
diff --git a/gsk/gl/gskgltextureatlas.c b/gsk/gl/gskgltextureatlas.c
new file mode 100644
index 0000000000..3419a2d5a2
--- /dev/null
+++ b/gsk/gl/gskgltextureatlas.c
@@ -0,0 +1,84 @@
+
+#include "gskgltextureatlasprivate.h"
+
+
+void
+gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
+                           int                width,
+                           int                height)
+{
+  memset (self, 0, sizeof (*self));
+
+  self->image.texture_id = 0;
+  self->width = width;
+  self->height = height;
+
+  /* TODO: We might want to change the strategy about the amount of
+   *       nodes here? stb_rect_pack.h says with is optimal. */
+  self->nodes = g_malloc0 (sizeof (struct stbrp_node) * width);
+  stbrp_init_target (&self->context,
+                     width, height,
+                     self->nodes,
+                     width);
+}
+
+void
+gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
+{
+  g_clear_pointer (&self->nodes, g_free);
+}
+
+void
+gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self,
+                                  int                width,
+                                  int                height)
+{
+  self->unused_pixels += (width * height);
+}
+
+
+void
+gsk_gl_texture_atlas_mark_used (GskGLTextureAtlas *self,
+                                int                width,
+                                int                height)
+{
+  self->unused_pixels -= (width * height);
+
+  g_assert (self->unused_pixels >= 0);
+}
+
+gboolean
+gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self,
+                           int                width,
+                           int                height,
+                           int               *out_x,
+                           int               *out_y)
+{
+  stbrp_rect rect;
+
+  g_assert (out_x);
+  g_assert (out_y);
+
+  rect.w = width;
+  rect.h = height;
+
+  stbrp_pack_rects (&self->context, &rect, 1);
+
+  if (rect.was_packed)
+    {
+      *out_x = rect.x;
+      *out_y = rect.y;
+    }
+
+  return rect.was_packed;
+}
+
+double
+gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
+{
+  if (self->unused_pixels > 0)
+    return (double)(self->unused_pixels) / (double)(self->width * self->height);
+
+  return 0.0;
+}
+
diff --git a/gsk/gl/gskgltextureatlasprivate.h b/gsk/gl/gskgltextureatlasprivate.h
new file mode 100644
index 0000000000..d3aea54142
--- /dev/null
+++ b/gsk/gl/gskgltextureatlasprivate.h
@@ -0,0 +1,49 @@
+
+#ifndef __GSK_GL_TEXTURE_ATLAS_H__
+#define __GSK_GL_TEXTURE_ATLAS_H__
+
+#include "stb_rect_pack.h"
+#include "gskglimageprivate.h"
+#include "gskgldriverprivate.h"
+
+struct _GskGLTextureAtlas
+{
+  struct stbrp_context context;
+  struct stbrp_node *nodes;
+
+  int width;
+  int height;
+
+  GskGLImage image;
+
+  int unused_pixels; /* Pixels of rects that have been used at some point,
+                        But are now unused. */
+
+  void *user_data;
+};
+typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
+
+void        gsk_gl_texture_atlas_init              (GskGLTextureAtlas       *self,
+                                                    int                      width,
+                                                    int                      height);
+
+void        gsk_gl_texture_atlas_free              (GskGLTextureAtlas       *self);
+
+void        gsk_gl_texture_atlas_mark_unused       (GskGLTextureAtlas       *self,
+                                                    int                      width,
+                                                    int                      height);
+
+void        gsk_gl_texture_atlas_mark_used         (GskGLTextureAtlas       *self,
+                                                    int                      width,
+                                                    int                      height);
+
+
+gboolean    gsk_gl_texture_atlas_pack              (GskGLTextureAtlas       *self,
+                                                    int                      width,
+                                                    int                      height,
+                                                    int                     *out_x,
+                                                    int                     *out_y);
+
+double      gsk_gl_texture_atlas_get_unused_ratio  (const GskGLTextureAtlas *self);
+
+#endif
diff --git a/gsk/meson.build b/gsk/meson.build
index e7a11ed741..0467c79550 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -42,6 +42,7 @@ gsk_private_sources = files([
   'gl/gskglrenderops.c',
   'gl/gskglshadowcache.c',
   'gl/gskglnodesample.c',
+  'gl/gskgltextureatlas.c',
   'gl/stb_rect_pack.c',
 ])
 


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