[gtk/wip/matthiasc/shared-glyph-cache: 2/3] Share glyph and icon caches



commit 0dd5948286c29c081a5f05fa2962663ddf99fbfc
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Jun 4 21:27:09 2019 +0000

    Share glyph and icon caches
    
    Use the same texture atlases to back both
    the glyph and icon caches, and unify their
    sizes and management. Store big glyphs
    in separate textures, so all atlases have
    the same size. Tweak some of the eviction
    parameters.
    
    We share the caches across all GL contexts
    on a display, unless the GSK_NO_SHARED_CACHES
    env var is set.

 gsk/gl/gskglglyphcache.c          | 416 ++++++++++++++++----------------------
 gsk/gl/gskglglyphcacheprivate.h   |  39 ++--
 gsk/gl/gskgliconcache.c           | 186 +++++++----------
 gsk/gl/gskgliconcacheprivate.h    |  14 +-
 gsk/gl/gskglrenderer.c            | 133 +++++++++---
 gsk/gl/gskgltextureatlas.c        | 213 ++++++++++++++++++-
 gsk/gl/gskgltextureatlasprivate.h |  24 ++-
 7 files changed, 608 insertions(+), 417 deletions(-)
---
diff --git a/gsk/gl/gskglglyphcache.c b/gsk/gl/gskglglyphcache.c
index b1338d9e58..6d8872a6cd 100644
--- a/gsk/gl/gskglglyphcache.c
+++ b/gsk/gl/gskglglyphcache.c
@@ -12,20 +12,18 @@
 #include <cairo.h>
 #include <epoxy/gl.h>
 
-/* Parameters for our cache eviction strategy.
+/* Cache eviction strategy
  *
- * 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_RATIO, then
- * we drop the atlas an all the glyphs contained in it from the cache.
+ * 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_FRAME_AGE frames are considered old.
+ *
+ * We keep count of the pixels of each atlas that are taken up by old
+ * data. When the fraction of old pixels gets too high, we drop the
+ * atlas and all the items it contained.
  */
 
-#define MAX_AGE 60
-#define CHECK_INTERVAL 10
-#define MAX_OLD_RATIO 0.333
-
-#define ATLAS_SIZE 512
+#define MAX_FRAME_AGE (5 * 60)
 
 static guint    glyph_cache_hash       (gconstpointer v);
 static gboolean glyph_cache_equal      (gconstpointer v1,
@@ -33,59 +31,47 @@ static gboolean glyph_cache_equal      (gconstpointer v1,
 static void     glyph_cache_key_free   (gpointer      v);
 static void     glyph_cache_value_free (gpointer      v);
 
-static GskGLTextureAtlas *
-create_atlas (GskGLGlyphCache *self,
-              int              width,
-              int              height)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_new (GdkDisplay *display,
+                        GskGLTextureAtlases *atlases)
 {
-  GskGLTextureAtlas *atlas;
+  GskGLGlyphCache *glyph_cache;
 
-  atlas = g_new (GskGLTextureAtlas, 1);
-  gsk_gl_texture_atlas_init (atlas, MAX (width, ATLAS_SIZE), MAX (height, ATLAS_SIZE));
+  glyph_cache = g_new0 (GskGLGlyphCache, 1);
 
-  GSK_NOTE(GLYPH_CACHE, g_message ("Create atlas %d x %d", atlas->width, atlas->height));
-
-  return atlas;
-}
+  glyph_cache->display = display;
+  glyph_cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
+                                                   glyph_cache_key_free, glyph_cache_value_free);
 
-static void
-free_atlas (gpointer v)
-{
-  GskGLTextureAtlas *atlas = v;
+  glyph_cache->atlases = gsk_gl_texture_atlases_ref (atlases);
 
-  g_assert (atlas->image.texture_id == 0);
-  gsk_gl_texture_atlas_free (atlas);
+  glyph_cache->ref_count = 1;
 
-  g_free (atlas);
+  return glyph_cache;
 }
 
-void
-gsk_gl_glyph_cache_init (GskGLGlyphCache *self)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_ref (GskGLGlyphCache *self)
 {
-  self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
-                                            glyph_cache_key_free, glyph_cache_value_free);
-  self->atlases = g_ptr_array_new_with_free_func (free_atlas);
+  self->ref_count++;
+
+  return self;
 }
 
 void
-gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
-                         GskGLDriver     *driver)
+gsk_gl_glyph_cache_unref (GskGLGlyphCache *self)
 {
-  guint i;
+  g_assert (self->ref_count > 0);
 
-  for (i = 0; i < self->atlases->len; i ++)
+  if (self->ref_count == 1)
     {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image.texture_id != 0)
-        {
-          gsk_gl_image_destroy (&atlas->image, driver);
-          atlas->image.texture_id = 0;
-        }
+      gsk_gl_texture_atlases_unref (self->atlases);
+      g_hash_table_unref (self->hash_table);
+      g_free (self);
+      return;
     }
 
-  g_ptr_array_unref (self->atlases);
-  g_hash_table_unref (self->hash_table);
+  self->ref_count--;
 }
 
 static gboolean
@@ -122,83 +108,11 @@ glyph_cache_value_free (gpointer v)
   g_free (v);
 }
 
-static void
-add_to_cache (GskGLGlyphCache  *cache,
-              GlyphCacheKey    *key,
-              GskGLCachedGlyph *value)
-{
-  const int width = value->draw_width * key->scale / 1024;
-  const int height = value->draw_height * key->scale / 1024;
-  GskGLTextureAtlas *atlas = NULL;
-  guint i, p;
-  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 ++)
-    {
-      GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
-      gboolean was_packed;
-
-      was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y);
-
-      if (was_packed)
-        {
-          atlas = test_atlas;
-          break;
-        }
-    }
-
-  if (atlas == NULL)
-    {
-      gboolean was_packed;
-
-      atlas = create_atlas (cache, width + 2, height + 2);
-
-      g_ptr_array_add (cache->atlases, atlas);
-
-      was_packed = gsk_gl_texture_atlas_pack (atlas,
-                                              width + 2, height + 2,
-                                              &packed_x, &packed_y);
-
-      g_assert (was_packed);
-    }
-
-  value->tx = (float)(packed_x + 1) / atlas->width;
-  value->ty = (float)(packed_y + 1) / atlas->height;
-  value->tw = (float)width    / atlas->width;
-  value->th = (float)height   / atlas->height;
-  value->used = TRUE;
-
-  value->atlas = atlas;
-
-  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_DEBUG_CHECK (GLYPH_CACHE))
-    {
-      for (i = 0; i < cache->atlases->len; i++)
-        {
-          atlas = g_ptr_array_index (cache->atlases, i);
-          g_message ("atlas %d (%dx%d): %.2g%% old pixels",
-                   i, atlas->width, atlas->height,
-                   gsk_gl_texture_atlas_get_unused_ratio (atlas));
-        }
-    }
-#endif
-}
-
 static gboolean
-render_glyph (const GskGLTextureAtlas *atlas,
-              const DirtyGlyph        *glyph,
-              GskImageRegion          *region)
+render_glyph (GlyphCacheKey    *key,
+              GskGLCachedGlyph *value,
+              GskImageRegion   *region)
 {
-  GlyphCacheKey *key = glyph->key;
-  GskGLCachedGlyph *value = glyph->value;
   cairo_surface_t *surface;
   cairo_t *cr;
   cairo_scaled_font_t *scaled_font;
@@ -210,16 +124,14 @@ render_glyph (const GskGLTextureAtlas *atlas,
 
   scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
   if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
-    return FALSE;
+    {
+      g_warning ("Failed to get a font");
+      return FALSE;
+    }
 
   surface_width = value->draw_width * key->scale / 1024;
   surface_height = value->draw_height * key->scale / 1024;
 
-  /* TODO: Give glyphs that large their own texture in the proper size. Don't
-   *       put them in the atlas at all. */
-  if (surface_width > atlas->width || surface_height > atlas->height)
-    return FALSE;
-
   stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, surface_width);
   data = g_malloc0 (stride * surface_height);
   surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
@@ -252,45 +164,120 @@ render_glyph (const GskGLTextureAtlas *atlas,
   region->height = cairo_image_surface_get_height (surface);
   region->stride = cairo_image_surface_get_stride (surface);
   region->data = data;
-  region->x = (gsize)(value->tx * atlas->width);
-  region->y = (gsize)(value->ty * atlas->height);
+  if (value->atlas)
+    {
+      region->x = (gsize)(value->tx * value->atlas->width);
+      region->y = (gsize)(value->ty * value->atlas->height);
+    }
+  else
+    {
+      region->x = 0;
+      region->y = 0;
+    }
 
   cairo_surface_destroy (surface);
+
   return TRUE;
 }
 
 static void
-upload_dirty_glyph (GskGLGlyphCache   *self,
-                    GskGLTextureAtlas *atlas,
-                    GskGLDriver       *driver)
+upload_glyph (GlyphCacheKey    *key,
+              GskGLCachedGlyph *value)
 {
-  GskImageRegion region;
-
-  g_assert (atlas->user_data != NULL);
+  GskImageRegion r;
 
-  gdk_gl_context_push_debug_group_printf (gsk_gl_driver_get_gl_context (driver),
-                                          "Uploading glyph %d", ((DirtyGlyph 
*)atlas->user_data)->key->glyph);
+  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                          "Uploading glyph %d",
+                                          key->glyph);
 
-  if (render_glyph (atlas, (DirtyGlyph *)atlas->user_data, &region))
+  if (render_glyph (key, value, &r))
     {
+      glBindTexture (GL_TEXTURE_2D, value->texture_id);
+      glTextureSubImage2D (value->texture_id, 0,
+                           r.x, r.y, r.width, r.height,
+                           GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+                           r.data);
+      g_free (r.data);
+    }
 
-      gsk_gl_image_upload_region (&atlas->image, driver, &region);
+  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+}
 
-      g_free (region.data);
-    }
+static void
+add_to_cache (GskGLGlyphCache  *self,
+              GlyphCacheKey    *key,
+              GskGLCachedGlyph *value)
+{
+  const int width = value->draw_width * key->scale / 1024;
+  const int height = value->draw_height * key->scale / 1024;
+  GskGLTextureAtlas *atlas = NULL;
+  int packed_x = 0;
+  int packed_y = 0;
+
+  gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
+
+  value->tx = (float)(packed_x + 1) / atlas->width;
+  value->ty = (float)(packed_y + 1) / atlas->height;
+  value->tw = (float)width / atlas->width;
+  value->th = (float)height / atlas->height;
+  value->used = TRUE;
+
+  value->atlas = atlas;
+  value->texture_id = atlas->texture_id;
+
+  upload_glyph (key, value);
+}
+
+void
+gsk_gl_glyph_cache_get_texture (GskGLDriver      *driver,
+                                PangoFont        *font,
+                                PangoGlyph        glyph,
+                                float             scale,
+                                GskGLCachedGlyph *value)
+{
+  PangoRectangle ink_rect;
+  GlyphCacheKey key;
+  int width, height;
+  guint texture_id;
+
+  pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
+  pango_extents_to_pixels (&ink_rect, NULL);
+
+  key.font = font;
+  key.glyph = glyph;
+  key.scale = (guint)(scale * 1024);
+
+  value->atlas = NULL;
+  value->timestamp = 0;
+
+  value->draw_x = ink_rect.x;
+  value->draw_y = ink_rect.y;
+  value->draw_width = ink_rect.width;
+  value->draw_height = ink_rect.height;
+
+  value->tx = 0.0f;
+  value->ty = 0.0f;
+  value->tw = 1.0f;
+  value->th = 1.0f;
+
+  width = value->draw_width * key.scale / 1024;
+  height = value->draw_height * key.scale / 1024;
+
+  texture_id = gsk_gl_driver_create_texture (driver, width, height);
+  gsk_gl_driver_bind_source_texture (driver, texture_id);
+  gsk_gl_driver_init_texture_empty (driver, texture_id, GL_NEAREST, GL_NEAREST);
 
-  gdk_gl_context_pop_debug_group (gsk_gl_driver_get_gl_context (driver));
-  /* TODO: This could be unnecessary. We can just reuse the allocated
-   *       DirtyGlyph next time. */
-  g_clear_pointer (&atlas->user_data, g_free);
+  value->texture_id = texture_id;
+
+  upload_glyph (&key, value);
 }
 
-const GskGLCachedGlyph *
-gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
-                           gboolean         create,
-                           PangoFont       *font,
-                           PangoGlyph       glyph,
-                           float            scale)
+gboolean
+gsk_gl_glyph_cache_lookup (GskGLGlyphCache  *cache,
+                           PangoFont        *font,
+                           PangoGlyph        glyph,
+                           float             scale,
+                           GskGLCachedGlyph *cached_glyph_out)
 {
   GskGLCachedGlyph *value;
 
@@ -305,7 +292,7 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
     {
       const guint age = cache->timestamp - value->timestamp;
 
-      if (MAX_AGE <= age)
+      if (age > MAX_FRAME_AGE)
         {
           GskGLTextureAtlas *atlas = value->atlas;
 
@@ -321,116 +308,68 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
       value->timestamp = cache->timestamp;
     }
 
-  if (create && value == NULL)
+  if (value == NULL)
     {
-      GlyphCacheKey *key;
       PangoRectangle ink_rect;
 
-      key = g_new0 (GlyphCacheKey, 1);
-      value = g_new0 (GskGLCachedGlyph, 1);
-
       pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
       pango_extents_to_pixels (&ink_rect, NULL);
 
+      value = g_new0 (GskGLCachedGlyph, 1);
+
       value->draw_x = ink_rect.x;
       value->draw_y = ink_rect.y;
       value->draw_width = ink_rect.width;
       value->draw_height = ink_rect.height;
       value->timestamp = cache->timestamp;
       value->atlas = NULL; /* For now */
-      value->scale = (guint)(scale * 1024);
-
-      key->font = g_object_ref (font);
-      key->glyph = glyph;
-      key->scale = (guint)(scale * 1024);
 
-      if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
-        add_to_cache (cache, key, value);
-
-      g_hash_table_insert (cache->hash_table, key, value);
-    }
+      if (ink_rect.width < 128 && ink_rect.height < 128)
+        {
+          GlyphCacheKey *key;
 
-  return value;
-}
+          key = g_new0 (GlyphCacheKey, 1);
 
-guint
-gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache        *self,
-                                         GskGLDriver            *driver,
-                                         const GskGLCachedGlyph *glyph)
-{
-  GskGLTextureAtlas *atlas = glyph->atlas;
+          key->font = g_object_ref (font);
+          key->glyph = glyph;
+          key->scale = (guint)(scale * 1024);
 
-  g_assert (atlas != NULL);
+          if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
+            add_to_cache (cache, key, value);
 
-  if (atlas->image.texture_id == 0)
+          *cached_glyph_out = *value;
+          g_hash_table_insert (cache->hash_table, key, value);
+        }
+      else
+        {
+          *cached_glyph_out = *value;
+          glyph_cache_value_free (value);
+        }
+    }
+  else
     {
-      gsk_gl_image_create (&atlas->image, driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
-      gdk_gl_context_label_object_printf (gsk_gl_driver_get_gl_context (driver),
-                                          GL_TEXTURE, atlas->image.texture_id,
-                                          "Glyph atlas %d", atlas->image.texture_id);
+      *cached_glyph_out = *value;
     }
 
-  if (atlas->user_data != NULL)
-    upload_dirty_glyph (self, atlas, driver);
-
-  return atlas->image.texture_id;
+  return cached_glyph_out->atlas != NULL;
 }
 
 void
-gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
-                                GskGLDriver     *driver)
+gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
 {
-  int i;
   GHashTableIter iter;
   GlyphCacheKey *key;
   GskGLCachedGlyph *value;
-  GHashTable *removed = g_hash_table_new (g_direct_hash, g_direct_equal);
   guint dropped = 0;
 
   self->timestamp++;
 
-  if ((self->timestamp - 1) % CHECK_INTERVAL != 0)
-    return;
-
-  /* look for atlases to drop, and create a mapping of updated texture indices */
-  for (i = self->atlases->len - 1; i >= 0; i--)
-    {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
-        {
-          GSK_NOTE(GLYPH_CACHE,
-                   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 glyph cache... Ratio: %f",
-                     gsk_gl_texture_atlas_get_unused_ratio (atlas));
-          gsk_gl_image_write_to_png (&atlas->image, driver,
-                                     g_strdup_printf ("dropped_%d.png", kk++));
-#endif
-
-          if (atlas->image.texture_id != 0)
-            {
-              gsk_gl_image_destroy (&atlas->image, driver);
-              atlas->image.texture_id = 0;
-            }
-
-          g_hash_table_add (removed, atlas);
-
-          g_ptr_array_remove_index (self->atlases, i);
-       }
-    }
-
-  /* Remove all glyphs whose atlas was removed, and
-   * mark old glyphs as unused
-   */
   g_hash_table_iter_init (&iter, self->hash_table);
   while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
     {
-      if (g_hash_table_contains (removed, value->atlas))
+      guint pos;
+
+      if (!g_ptr_array_find (self->atlases->atlases, value->atlas, &pos))
         {
           g_hash_table_iter_remove (&iter);
           dropped++;
@@ -439,7 +378,7 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
         {
           const guint age = self->timestamp - value->timestamp;
 
-          if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
+          if (age > MAX_FRAME_AGE)
             {
               GskGLTextureAtlas *atlas = value->atlas;
 
@@ -451,23 +390,6 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
             }
         }
     }
-  g_hash_table_unref (removed);
 
   GSK_NOTE(GLYPH_CACHE, if (dropped > 0) g_message ("Dropped %d glyphs", dropped));
-
-#if 0
-  for (i = 0; i < self->atlases->len; i++)
-    {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image)
-        {
-          char *filename;
-
-          filename = g_strdup_printf ("glyphatlas%d-%ld.png", i, self->timestamp);
-          gsk_gl_image_write_to_png (atlas->image, driver, filename);
-          g_free (filename);
-        }
-    }
-#endif
 }
diff --git a/gsk/gl/gskglglyphcacheprivate.h b/gsk/gl/gskglglyphcacheprivate.h
index c3ab245cf4..72fce67539 100644
--- a/gsk/gl/gskglglyphcacheprivate.h
+++ b/gsk/gl/gskglglyphcacheprivate.h
@@ -9,8 +9,11 @@
 
 typedef struct
 {
+  int ref_count;
+
+  GdkDisplay *display;
   GHashTable *hash_table;
-  GPtrArray *atlases;
+  GskGLTextureAtlases *atlases;
 
   guint64 timestamp;
 } GskGLGlyphCache;
@@ -22,18 +25,12 @@ typedef struct
   guint scale; /* times 1024 */
 } GlyphCacheKey;
 
-typedef struct _DirtyGlyph DirtyGlyph;
 typedef struct _GskGLCachedGlyph GskGLCachedGlyph;
 
-struct _DirtyGlyph
-{
-  GlyphCacheKey *key;
-  GskGLCachedGlyph *value;
-};
-
 struct _GskGLCachedGlyph
 {
   GskGLTextureAtlas *atlas;
+  guint texture_id;
 
   float tx;
   float ty;
@@ -45,25 +42,25 @@ struct _GskGLCachedGlyph
   int draw_width;
   int draw_height;
 
-  float scale;
-
   guint64 timestamp;
   guint used: 1;
 };
 
 
-void                     gsk_gl_glyph_cache_init            (GskGLGlyphCache        *self);
-void                     gsk_gl_glyph_cache_free            (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver);
-void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver);
-guint                    gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver,
-                                                             const GskGLCachedGlyph *glyph);
-const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
-                                                             gboolean                create,
+GskGLGlyphCache *        gsk_gl_glyph_cache_new             (GdkDisplay *display,
+                                                             GskGLTextureAtlases *atlases);
+GskGLGlyphCache *        gsk_gl_glyph_cache_ref             (GskGLGlyphCache *self);
+void                     gsk_gl_glyph_cache_unref           (GskGLGlyphCache        *self);
+void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self);
+gboolean                 gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
                                                              PangoFont              *font,
                                                              PangoGlyph              glyph,
-                                                             float                   scale);
+                                                             float                   scale,
+                                                             GskGLCachedGlyph *cached_glyph_out);
+void                    gsk_gl_glyph_cache_get_texture      (GskGLDriver *driver,
+                                                             PangoFont   *font,
+                                                             PangoGlyph   glyph,
+                                                             float        scale,
+                                                             GskGLCachedGlyph *glyph_out);
 
 #endif
diff --git a/gsk/gl/gskgliconcache.c b/gsk/gl/gskgliconcache.c
index cc823f496b..c0ad9af374 100644
--- a/gsk/gl/gskgliconcache.c
+++ b/gsk/gl/gskgliconcache.c
@@ -1,12 +1,11 @@
 #include "gskgliconcacheprivate.h"
 #include "gskgltextureatlasprivate.h"
 #include "gdk/gdktextureprivate.h"
+#include "gdk/gdkglcontextprivate.h"
 
 #include <epoxy/gl.h>
 
-#define ATLAS_SIZE    (1024)
 #define MAX_FRAME_AGE (5 * 60)
-#define MAX_UNUSED_RATIO 0.8
 
 typedef struct
 {
@@ -22,57 +21,49 @@ icon_data_free (gpointer p)
   g_free (p);
 }
 
-static void
-free_atlas (gpointer v)
+GskGLIconCache *
+gsk_gl_icon_cache_new (GdkDisplay *display,
+                       GskGLTextureAtlases *atlases)
 {
-  GskGLTextureAtlas *atlas = v;
+  GskGLIconCache *self;
+
+  self = g_new0 (GskGLIconCache, 1);
 
-  g_assert (atlas->image.texture_id == 0);
-  gsk_gl_texture_atlas_free (atlas);
+  self->display = display;
+  self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+  self->atlases = gsk_gl_texture_atlases_ref (atlases);
+  self->ref_count = 1;
 
-  g_free (atlas);
+  return self;
 }
 
-void
-gsk_gl_icon_cache_init (GskGLIconCache *self,
-                        GskRenderer    *renderer,
-                        GskGLDriver    *gl_driver)
+GskGLIconCache *
+gsk_gl_icon_cache_ref (GskGLIconCache *self)
 {
-  self->renderer = renderer;
-  self->gl_driver = gl_driver;
+  self->ref_count++;
 
-  self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)free_atlas);
-  self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+  return self;
 }
 
 void
-gsk_gl_icon_cache_free (GskGLIconCache *self)
+gsk_gl_icon_cache_unref (GskGLIconCache *self)
 {
-  guint i, p;
+  g_assert (self->ref_count > 0);
 
-  for (i = 0, p = self->atlases->len; i < p; i ++)
+  if (self->ref_count == 1)
     {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image.texture_id != 0)
-        {
-          gsk_gl_image_destroy (&atlas->image, self->gl_driver);
-          atlas->image.texture_id = 0;
-        }
-
-      gsk_gl_texture_atlas_free (atlas);
-
-      g_free (atlas);
+      gsk_gl_texture_atlases_unref (self->atlases);
+      g_hash_table_unref (self->icons);
+      g_free (self);
+      return;
     }
-  g_ptr_array_free (self->atlases, TRUE);
 
-  g_hash_table_unref (self->icons);
+  self->ref_count--;
 }
 
 void
 gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
 {
-  gint i, p;
   GHashTableIter iter;
   GdkTexture *texture;
   IconData *icon_data;
@@ -81,45 +72,30 @@ gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
   g_hash_table_iter_init (&iter, self->icons);
   while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
     {
-      icon_data->frame_age ++;
+      guint pos;
 
-      if (icon_data->frame_age > MAX_FRAME_AGE)
+      if (!g_ptr_array_find (self->atlases->atlases, icon_data->atlas, &pos))
         {
-
-          if (icon_data->used)
-            {
-              const int w = icon_data->texture_rect.size.width  * ATLAS_SIZE;
-              const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
-
-              gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
-              icon_data->used = FALSE;
-            }
-          /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
-           * This way we can revive it when we use it again. */
+          g_hash_table_iter_remove (&iter);
         }
-    }
-
-  for (i = 0, p = self->atlases->len; i < p; i ++)
-    {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_UNUSED_RATIO)
+      else
         {
-          g_hash_table_iter_init (&iter, self->icons);
-          while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
-            {
-              if (icon_data->atlas == atlas)
-                g_hash_table_iter_remove (&iter);
-            }
+          icon_data->frame_age ++;
 
-          if (atlas->image.texture_id != 0)
+          if (icon_data->frame_age > MAX_FRAME_AGE)
             {
-              gsk_gl_image_destroy (&atlas->image, self->gl_driver);
-              atlas->image.texture_id = 0;
-            }
 
-          g_ptr_array_remove_index_fast (self->atlases, i);
-          i --; /* Check the current index again */
+              if (icon_data->used)
+                {
+                  const int w = icon_data->texture_rect.size.width  * icon_data->atlas->width;
+                  const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
+
+                  gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
+                  icon_data->used = FALSE;
+                }
+              /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
+               * This way we can revive it when we use it again. */
+            }
         }
     }
 }
@@ -154,6 +130,16 @@ pad_surface (cairo_surface_t *surface)
   return padded;
 }
 
+static void
+upload_region_or_else (GskGLIconCache *self,
+                       guint           texture_id,
+                       GskImageRegion *region)
+{
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+  glTextureSubImage2D (texture_id, 0, region->x, region->y, region->width, region->height,
+                   GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data);
+}
+
 void
 gsk_gl_icon_cache_lookup_or_add (GskGLIconCache  *self,
                                  GdkTexture      *texture,
@@ -167,84 +153,60 @@ gsk_gl_icon_cache_lookup_or_add (GskGLIconCache  *self,
       icon_data->frame_age = 0;
       if (!icon_data->used)
         {
-          const int w = icon_data->texture_rect.size.width  * ATLAS_SIZE;
-          const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
+          const int w = icon_data->texture_rect.size.width  * icon_data->atlas->width;
+          const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
 
           gsk_gl_texture_atlas_mark_used (icon_data->atlas, w + 2, h + 2);
           icon_data->used = TRUE;
         }
 
-      *out_texture_id = icon_data->atlas->image.texture_id;
+      *out_texture_id = icon_data->atlas->texture_id;
       *out_texture_rect = icon_data->texture_rect;
       return;
     }
 
   /* texture not on any atlas yet. Find a suitable one. */
   {
-    const int twidth = gdk_texture_get_width (texture);
-    const int theight = gdk_texture_get_height (texture);
-    int packed_x, packed_y;
+    const int width = gdk_texture_get_width (texture);
+    const int height = gdk_texture_get_height (texture);
     GskGLTextureAtlas *atlas = NULL;
-    guint i, p;
+    int packed_x = 0;
+    int packed_y = 0;
     GskImageRegion region;
     cairo_surface_t *surface;
     cairo_surface_t *padded_surface;
 
-    g_assert (twidth  < ATLAS_SIZE);
-    g_assert (theight < ATLAS_SIZE);
-
-    for (i = 0, p = self->atlases->len; i < p; i ++)
-      {
-        atlas = g_ptr_array_index (self->atlases, i);
-
-        if (gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y))
-          {
-            packed_x += 1;
-            packed_y += 1;
-            break;
-          }
-
-        atlas = NULL;
-      }
-
-    if (!atlas)
-      {
-        /* No atlas has enough space, so create a new one... */
-        atlas = g_malloc (sizeof (GskGLTextureAtlas));
-        gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
-        gsk_gl_image_create (&atlas->image, self->gl_driver, atlas->width, atlas->height, GL_LINEAR, 
GL_LINEAR);
-        /* Pack it onto that one, which surely has enought space... */
-        gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y);
-        packed_x += 1;
-        packed_y += 1;
-
-        g_ptr_array_add (self->atlases, atlas);
-      }
+    gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
 
     icon_data = g_new0 (IconData, 1);
     icon_data->atlas = atlas;
     icon_data->frame_age = 0;
     icon_data->used = TRUE;
     graphene_rect_init (&icon_data->texture_rect,
-                        (float)packed_x / ATLAS_SIZE,
-                        (float)packed_y / ATLAS_SIZE,
-                        (float)twidth   / ATLAS_SIZE,
-                        (float)theight  / ATLAS_SIZE);
+                        (float)(packed_x + 1) / atlas->width,
+                        (float)(packed_y + 1) / atlas->height,
+                        (float)width / atlas->width,
+                        (float)height / atlas->height);
 
     g_hash_table_insert (self->icons, texture, icon_data);
 
     /* actually upload the texture */
     surface = gdk_texture_download_surface (texture);
     padded_surface = pad_surface (surface);
-    region.x = packed_x - 1;
-    region.y = packed_y - 1;
-    region.width = twidth + 2;
-    region.height = theight + 2;
+    region.x = packed_x;
+    region.y = packed_y;
+    region.width = width + 2;
+    region.height = height + 2;
     region.data = cairo_image_surface_get_data (padded_surface);
 
-    gsk_gl_image_upload_region (&atlas->image, self->gl_driver, &region);
+    gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                            "Uploading texture");
+
+    upload_region_or_else (self, atlas->texture_id, &region);
+
+    gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
 
-    *out_texture_id = atlas->image.texture_id;
+    *out_texture_id = atlas->texture_id;
     *out_texture_rect = icon_data->texture_rect;
 
     cairo_surface_destroy (surface);
diff --git a/gsk/gl/gskgliconcacheprivate.h b/gsk/gl/gskgliconcacheprivate.h
index 883a274ded..ade371b930 100644
--- a/gsk/gl/gskgliconcacheprivate.h
+++ b/gsk/gl/gskgliconcacheprivate.h
@@ -10,18 +10,20 @@
 
 typedef struct
 {
+  int ref_count;
+
+  GdkDisplay *display;
   GskGLDriver *gl_driver;
-  GskRenderer *renderer;
 
-  GPtrArray *atlases;
+  GskGLTextureAtlases *atlases;
   GHashTable *icons; /* GdkTexture -> IconData */
 
 } GskGLIconCache;
 
-void             gsk_gl_icon_cache_init           (GskGLIconCache        *self,
-                                                   GskRenderer            *renderer,
-                                                   GskGLDriver            *gl_driver);
-void             gsk_gl_icon_cache_free           (GskGLIconCache        *self);
+GskGLIconCache * gsk_gl_icon_cache_new            (GdkDisplay *display,
+                                                   GskGLTextureAtlases *atlases);
+GskGLIconCache * gsk_gl_icon_cache_ref            (GskGLIconCache        *self);
+void             gsk_gl_icon_cache_unref          (GskGLIconCache        *self);
 void             gsk_gl_icon_cache_begin_frame    (GskGLIconCache        *self);
 void             gsk_gl_icon_cache_lookup_or_add  (GskGLIconCache        *self,
                                                    GdkTexture            *texture,
diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c
index c2f5e0abde..a288fea50b 100644
--- a/gsk/gl/gskglrenderer.c
+++ b/gsk/gl/gskglrenderer.c
@@ -335,8 +335,9 @@ struct _GskGLRenderer
   RenderOpBuilder op_builder;
   GArray *render_ops;
 
-  GskGLGlyphCache glyph_cache;
-  GskGLIconCache icon_cache;
+  GskGLTextureAtlases *atlases;
+  GskGLGlyphCache *glyph_cache;
+  GskGLIconCache *icon_cache;
   GskGLShadowCache shadow_cache;
 
 #ifdef G_ENABLE_DEBUG
@@ -571,7 +572,7 @@ render_text_node (GskGLRenderer   *self,
   for (i = 0; i < num_glyphs; i++)
     {
       const PangoGlyphInfo *gi = &glyphs[i];
-      const GskGLCachedGlyph *glyph;
+      GskGLCachedGlyph glyph;
       float glyph_x, glyph_y, glyph_w, glyph_h;
       float tx, ty, tx2, ty2;
       double cx;
@@ -580,32 +581,41 @@ render_text_node (GskGLRenderer   *self,
       if (gi->glyph == PANGO_GLYPH_EMPTY)
         continue;
 
-      glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
-                                         TRUE,
-                                         (PangoFont *)font,
-                                         gi->glyph,
-                                         text_scale);
+      gsk_gl_glyph_cache_lookup (self->glyph_cache,
+                                 (PangoFont *)font,
+                                 gi->glyph,
+                                 text_scale,
+                                 &glyph);
 
       /* e.g. whitespace */
-      if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0)
+      if (glyph.draw_width <= 0 || glyph.draw_height <= 0)
         goto next;
 
+      /* big glyphs are not cached */
+      if (!glyph.texture_id)
+        {
+          gsk_gl_glyph_cache_get_texture (self->gl_driver,
+                                          (PangoFont *)font,
+                                          gi->glyph,
+                                          text_scale,
+                                          &glyph);
+          g_assert (glyph.texture_id != 0);
+        }
+
       cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
       cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
 
-      ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_texture_id (&self->glyph_cache,
-                                                                         self->gl_driver,
-                                                                         glyph));
+      ops_set_texture (builder, glyph.texture_id);
 
-      tx  = glyph->tx;
-      ty  = glyph->ty;
-      tx2 = tx + glyph->tw;
-      ty2 = ty + glyph->th;
+      tx  = glyph.tx;
+      ty  = glyph.ty;
+      tx2 = tx + glyph.tw;
+      ty2 = ty + glyph.th;
 
-      glyph_x = x + cx + glyph->draw_x;
-      glyph_y = y + cy + glyph->draw_y;
-      glyph_w = glyph->draw_width;
-      glyph_h = glyph->draw_height;
+      glyph_x = x + cx + glyph.draw_x;
+      glyph_y = y + cy + glyph.draw_y;
+      glyph_w = glyph.draw_width;
+      glyph_h = glyph.draw_height;
 
       ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
         { { glyph_x,           glyph_y           }, { tx,  ty  }, },
@@ -829,12 +839,12 @@ render_texture_node (GskGLRenderer       *self,
       int texture_id;
       float tx = 0, ty = 0, tx2 = 1, ty2 = 1;
 
-      if (texture->width <= 64 &&
-          texture->height <= 64)
+      if (texture->width <= 128 &&
+          texture->height <= 128)
         {
           graphene_rect_t trect;
 
-          gsk_gl_icon_cache_lookup_or_add (&self->icon_cache,
+          gsk_gl_icon_cache_lookup_or_add (self->icon_cache,
                                            texture,
                                            &texture_id,
                                            &trect);
@@ -2466,6 +2476,68 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
   return TRUE;
 }
 
+static GskGLTextureAtlases *
+get_texture_atlases_for_display (GdkDisplay *display)
+{
+  GskGLTextureAtlases *atlases;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_texture_atlases_new ();
+
+  atlases = (GskGLTextureAtlases*)g_object_get_data (G_OBJECT (display), "gsk-gl-texture-atlases");
+  if (atlases == NULL)
+    {
+      atlases = gsk_gl_texture_atlases_new ();
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-texture-atlases",
+                              gsk_gl_texture_atlases_ref (atlases),
+                              (GDestroyNotify) gsk_gl_texture_atlases_unref);
+    }
+
+  return atlases;
+}
+
+static GskGLGlyphCache *
+get_glyph_cache_for_display (GdkDisplay *display,
+                             GskGLTextureAtlases *atlases)
+{
+  GskGLGlyphCache *glyph_cache;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_glyph_cache_new (display, atlases);
+
+  glyph_cache = (GskGLGlyphCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-glyph-cache");
+  if (glyph_cache == NULL)
+    {
+      glyph_cache = gsk_gl_glyph_cache_new (display, atlases);
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-glyph-cache",
+                              gsk_gl_glyph_cache_ref (glyph_cache),
+                              (GDestroyNotify) gsk_gl_glyph_cache_unref);
+    }
+
+  return glyph_cache;
+}
+
+static GskGLIconCache *
+get_icon_cache_for_display (GdkDisplay *display,
+                            GskGLTextureAtlases *atlases)
+{
+  GskGLIconCache *icon_cache;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_icon_cache_new (display, atlases);
+
+  icon_cache = (GskGLIconCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-icon-cache");
+  if (icon_cache == NULL)
+    {
+      icon_cache = gsk_gl_icon_cache_new (display, atlases);
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-icon-cache",
+                              gsk_gl_icon_cache_ref (icon_cache),
+                              (GDestroyNotify) gsk_gl_icon_cache_unref);
+    }
+
+  return icon_cache;
+}
+
 static gboolean
 gsk_gl_renderer_realize (GskRenderer  *renderer,
                          GdkSurface    *surface,
@@ -2496,8 +2568,9 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
   if (!gsk_gl_renderer_create_programs (self, error))
     return FALSE;
 
-  gsk_gl_glyph_cache_init (&self->glyph_cache);
-  gsk_gl_icon_cache_init (&self->icon_cache, renderer, self->gl_driver);
+  self->atlases = get_texture_atlases_for_display (gdk_surface_get_display (surface));
+  self->glyph_cache = get_glyph_cache_for_display (gdk_surface_get_display (surface), self->atlases);
+  self->icon_cache = get_icon_cache_for_display (gdk_surface_get_display (surface), self->atlases);
   gsk_gl_shadow_cache_init (&self->shadow_cache);
 
   return TRUE;
@@ -2522,8 +2595,9 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
   for (i = 0; i < GL_N_PROGRAMS; i ++)
     glDeleteProgram (self->programs[i].id);
 
-  gsk_gl_glyph_cache_free (&self->glyph_cache, self->gl_driver);
-  gsk_gl_icon_cache_free (&self->icon_cache);
+  g_clear_pointer (&self->glyph_cache, gsk_gl_glyph_cache_unref);
+  g_clear_pointer (&self->icon_cache, gsk_gl_icon_cache_unref);
+  g_clear_pointer (&self->atlases, gsk_gl_texture_atlases_unref);
   gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
 
   g_clear_object (&self->gl_profiler);
@@ -3089,8 +3163,9 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
                               ORTHO_FAR_PLANE);
   graphene_matrix_scale (&projection, 1, -1, 1);
 
-  gsk_gl_glyph_cache_begin_frame (&self->glyph_cache, self->gl_driver);
-  gsk_gl_icon_cache_begin_frame (&self->icon_cache);
+  gsk_gl_texture_atlases_begin_frame (self->atlases);
+  gsk_gl_glyph_cache_begin_frame (self->glyph_cache);
+  gsk_gl_icon_cache_begin_frame (self->icon_cache);
   gsk_gl_shadow_cache_begin_frame (&self->shadow_cache, self->gl_driver);
 
   ops_set_projection (&self->op_builder, &projection);
diff --git a/gsk/gl/gskgltextureatlas.c b/gsk/gl/gskgltextureatlas.c
index 3419a2d5a2..06f6fcbedc 100644
--- a/gsk/gl/gskgltextureatlas.c
+++ b/gsk/gl/gskgltextureatlas.c
@@ -1,6 +1,169 @@
 
+#include "config.h"
 #include "gskgltextureatlasprivate.h"
+#include "gskdebugprivate.h"
+#include "gdkglcontextprivate.h"
+#include <epoxy/gl.h>
 
+#define ATLAS_SIZE (512)
+#define MAX_OLD_RATIO 0.5
+
+static void
+free_atlas (gpointer v)
+{
+  GskGLTextureAtlas *atlas = v;
+
+  gsk_gl_texture_atlas_free (atlas);
+
+  g_free (atlas);
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_new (void)
+{
+  GskGLTextureAtlases *atlases;
+
+  atlases = g_new (GskGLTextureAtlases, 1);
+  atlases->atlases = g_ptr_array_new_with_free_func (free_atlas);
+
+  atlases->ref_count = 1;
+
+  return atlases;
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases)
+{
+  atlases->ref_count++;
+
+  return atlases;
+}
+
+void
+gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases)
+{
+  g_assert (atlases->ref_count > 0);
+
+  if (atlases->ref_count == 1)
+    {
+      g_ptr_array_unref (atlases->atlases);
+      g_free (atlases);
+      return;
+    }
+
+  atlases->ref_count--;
+}
+
+#if 1
+static void
+write_atlas_to_png (GskGLTextureAtlas *atlas,
+                    const char        *filename)
+{
+  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
+  guchar *data = g_malloc (atlas->height * stride);
+  cairo_surface_t *s;
+
+  glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
+  glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
+  cairo_surface_write_to_png (s, filename);
+
+  cairo_surface_destroy (s);
+  g_free (data);
+}
+#endif
+
+void
+gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases)
+{
+  int i;
+
+  for (i = atlases->atlases->len - 1; i >= 0; i--)
+    {
+      GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
+        {
+          GSK_NOTE(GLYPH_CACHE,
+                   g_message ("Dropping atlas %d (%g.2%% old)", i,
+                              gsk_gl_texture_atlas_get_unused_ratio (atlas)));
+
+          if (atlas->texture_id != 0)
+            {
+              glDeleteTextures (1, &atlas->texture_id);
+              atlas->texture_id = 0;
+            }
+
+          g_ptr_array_remove_index (atlases->atlases, i);
+       }
+    }
+
+#if 1
+  {
+    static guint timestamp;
+
+    timestamp++;
+    if (timestamp % 10 == 0)
+      for (i = 0; i < atlases->atlases->len; i++)
+        {
+          GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+          if (atlas->texture_id)
+            {
+              char *filename;
+
+              filename = g_strdup_printf ("textureatlas%d-%u.png", i, timestamp);
+              write_atlas_to_png (atlas, filename);
+              g_free (filename);
+            }
+         }
+   }
+#endif
+}
+
+gboolean
+gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
+                             int                  width,
+                             int                  height,
+                             GskGLTextureAtlas  **atlas_out,
+                             int                 *out_x,
+                             int                 *out_y)
+{
+  GskGLTextureAtlas *atlas;
+  int x, y;
+  int i;
+
+  g_assert (width  < ATLAS_SIZE);
+  g_assert (height < ATLAS_SIZE);
+
+  for (i = 0; i < atlases->atlases->len; i++)
+    {
+      atlas = g_ptr_array_index (atlases->atlases, i);
+
+      if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
+        break;
+
+      atlas = NULL;
+    }
+
+  if (atlas == NULL)
+    {
+      /* No atlas has enough space, so create a new one... */
+      atlas = g_malloc (sizeof (GskGLTextureAtlas));
+      gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
+      gsk_gl_texture_atlas_realize (atlas);
+      g_ptr_array_add (atlases->atlases, atlas);
+
+      /* Pack it onto that one, which surely has enough space... */
+      gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y);
+    }
+
+  *atlas_out = atlas;
+  *out_x = x;
+  *out_y = y;
+
+  return TRUE;
+}
 
 void
 gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
@@ -9,7 +172,7 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
 {
   memset (self, 0, sizeof (*self));
 
-  self->image.texture_id = 0;
+  self->texture_id = 0;
   self->width = width;
   self->height = height;
 
@@ -20,11 +183,19 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
                      width, height,
                      self->nodes,
                      width);
+
+  gsk_gl_texture_atlas_realize (self);
 }
 
 void
 gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
 {
+  if (self->texture_id != 0)
+    {
+      glDeleteTextures (1, &self->texture_id);
+      self->texture_id = 0;
+    }
+
   g_clear_pointer (&self->nodes, g_free);
 }
 
@@ -82,3 +253,43 @@ gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
   return 0.0;
 }
 
+/* Not using gdk_gl_driver_create_texture here, since we want
+ * this texture to survive the driver and stay around until
+ * the display gets closed.
+ */
+static guint
+create_shared_texture (int width,
+                       int height)
+{
+  guint texture_id;
+
+  glGenTextures (1, &texture_id);
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+  else
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+  glBindTexture (GL_TEXTURE_2D, 0);
+
+  return texture_id;
+}
+
+void
+gsk_gl_texture_atlas_realize (GskGLTextureAtlas *atlas)
+{
+  if (atlas->texture_id)
+    return;
+
+  atlas->texture_id = create_shared_texture (atlas->width, atlas->height);
+  gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
+                                      GL_TEXTURE, atlas->texture_id,
+                                      "Glyph atlas %d", atlas->texture_id);
+}
diff --git a/gsk/gl/gskgltextureatlasprivate.h b/gsk/gl/gskgltextureatlasprivate.h
index d3aea54142..2864fd8d05 100644
--- a/gsk/gl/gskgltextureatlasprivate.h
+++ b/gsk/gl/gskgltextureatlasprivate.h
@@ -14,7 +14,7 @@ struct _GskGLTextureAtlas
   int width;
   int height;
 
-  GskGLImage image;
+  guint texture_id;
 
   int unused_pixels; /* Pixels of rects that have been used at some point,
                         But are now unused. */
@@ -23,12 +23,34 @@ struct _GskGLTextureAtlas
 };
 typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
 
+struct _GskGLTextureAtlases
+{
+  int ref_count;
+
+  GPtrArray *atlases;
+};
+typedef struct _GskGLTextureAtlases GskGLTextureAtlases;
+
+GskGLTextureAtlases *gsk_gl_texture_atlases_new         (void);
+GskGLTextureAtlases *gsk_gl_texture_atlases_ref         (GskGLTextureAtlases *atlases);
+void                 gsk_gl_texture_atlases_unref       (GskGLTextureAtlases *atlases);
+
+void                 gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases);
+gboolean             gsk_gl_texture_atlases_pack        (GskGLTextureAtlases *atlases,
+                                                         int                  width,
+                                                         int                  height,
+                                                         GskGLTextureAtlas  **atlas_out,
+                                                         int                 *out_x,
+                                                         int                 *out_y);
+
 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_realize           (GskGLTextureAtlas       *self);
+
 void        gsk_gl_texture_atlas_mark_unused       (GskGLTextureAtlas       *self,
                                                     int                      width,
                                                     int                      height);


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