[gtk/wip/chergert/fix-ngl-cache-eviction: 1/2] ngl: implement atlas compaction




commit af80f3a9765df5a2b09e99dc43f6a8b9f2b52819
Author: Christian Hergert <chergert redhat com>
Date:   Thu Mar 18 17:53:37 2021 -0700

    ngl: implement atlas compaction
    
    This required finishing up the begin_frame/end_frame semantics for
    GskNglTextureLibraryw which was apparently overlooked.
    
    The driver was changed to provide more information to the library when
    beginning frames. We do not need to use end_frame so that was removed.
    
    The frame age is the same as GL (60) but I do wonder if that is based
    on seconds if we should be using something longer for situations where
    we have higher frame rates.
    
    Fixes #3771

 gsk/ngl/gskngldriver.c                | 53 ++++++++++++++++++++++++++---
 gsk/ngl/gskngltexturelibrary.c        | 64 ++++++++++++++++++++++++++++++-----
 gsk/ngl/gskngltexturelibraryprivate.h | 39 +++++++++++++--------
 3 files changed, 128 insertions(+), 28 deletions(-)
---
diff --git a/gsk/ngl/gskngldriver.c b/gsk/ngl/gskngldriver.c
index 93763dbb12..a4254c8da4 100644
--- a/gsk/ngl/gskngldriver.c
+++ b/gsk/ngl/gskngldriver.c
@@ -39,6 +39,7 @@
 #include "gskngltexturepoolprivate.h"
 
 #define ATLAS_SIZE 512
+#define MAX_OLD_RATIO 0.5
 
 typedef struct _GskNglTextureState
 {
@@ -494,6 +495,37 @@ failure:
   return g_steal_pointer (&driver);
 }
 
+static GPtrArray *
+gsk_ngl_driver_compact_atlases (GskNglDriver *self)
+{
+  GPtrArray *removed = NULL;
+
+  g_assert (GSK_IS_NGL_DRIVER (self));
+
+  for (guint i = self->atlases->len; i > 0; i--)
+    {
+      GskNglTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1);
+
+      if (gsk_ngl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
+        {
+          GSK_NOTE (GLYPH_CACHE,
+                    g_message ("Dropping atlas %d (%g.2%% old)", i,
+                               100.0 * gsk_ngl_texture_atlas_get_unused_ratio (atlas)));
+          if (removed == NULL)
+            removed = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_ngl_texture_atlas_free);
+          g_ptr_array_add (removed, g_ptr_array_steal_index (self->atlases, i - 1));
+        }
+    }
+
+  GSK_NOTE (GLYPH_CACHE, {
+    static guint timestamp;
+    if (timestamp++ % 60 == 0)
+      g_message ("%d atlases", self->atlases->len);
+  });
+
+  return removed;
+}
+
 /**
  * gsk_ngl_driver_begin_frame:
  * @self: a #GskNglDriver
@@ -510,6 +542,7 @@ gsk_ngl_driver_begin_frame (GskNglDriver       *self,
                             GskNglCommandQueue *command_queue)
 {
   gint64 last_frame_id;
+  GPtrArray *removed;
 
   g_return_if_fail (GSK_IS_NGL_DRIVER (self));
   g_return_if_fail (GSK_IS_NGL_COMMAND_QUEUE (command_queue));
@@ -524,8 +557,18 @@ gsk_ngl_driver_begin_frame (GskNglDriver       *self,
 
   gsk_ngl_command_queue_begin_frame (self->command_queue);
 
-  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons));
-  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs));
+  /* Compact atlases with too many freed pixels */
+  removed = gsk_ngl_driver_compact_atlases (self);
+
+  /* Mark unused pixel regions of the atlases */
+  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons),
+                                       self->current_frame_id,
+                                       removed);
+  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs),
+                                       self->current_frame_id,
+                                       removed);
+
+  /* Cleanup old shadows */
   gsk_ngl_shadow_library_begin_frame (self->shadows);
 
   /* Remove all textures that are from a previous frame or are no
@@ -534,6 +577,9 @@ gsk_ngl_driver_begin_frame (GskNglDriver       *self,
    * we block on any resources while delivering our frames.
    */
   gsk_ngl_driver_collect_unused_textures (self, last_frame_id - 1);
+
+  /* Now free atlas textures */
+  g_clear_pointer (&removed, g_ptr_array_unref);
 }
 
 /**
@@ -553,9 +599,6 @@ gsk_ngl_driver_end_frame (GskNglDriver *self)
   gsk_ngl_command_queue_make_current (self->command_queue);
   gsk_ngl_command_queue_end_frame (self->command_queue);
 
-  gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons));
-  gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs));
-
   self->in_frame = FALSE;
 }
 
diff --git a/gsk/ngl/gskngltexturelibrary.c b/gsk/ngl/gskngltexturelibrary.c
index dc9303f373..85449b3fa2 100644
--- a/gsk/ngl/gskngltexturelibrary.c
+++ b/gsk/ngl/gskngltexturelibrary.c
@@ -20,10 +20,14 @@
 
 #include "config.h"
 
+#include <gsk/gskdebugprivate.h>
+
 #include "gsknglcommandqueueprivate.h"
 #include "gskngldriverprivate.h"
 #include "gskngltexturelibraryprivate.h"
 
+#define MAX_FRAME_AGE 60
+
 G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT)
 
 enum {
@@ -130,21 +134,62 @@ gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
 }
 
 void
-gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self)
+gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self,
+                                     gint64                frame_id,
+                                     GPtrArray            *removed_atlases)
 {
+  GHashTableIter iter;
+
   g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
 
   if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
-    GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self);
-}
+    GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases);
 
-void
-gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self)
-{
-  g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
+  if (removed_atlases != NULL)
+    {
+      GskNglTextureAtlasEntry *entry;
+      guint dropped = 0;
+
+      g_hash_table_iter_init (&iter, self->hash_table);
+      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
+        {
+          if (entry->is_atlased)
+            {
+              for (guint i = 0; i < removed_atlases->len; i++)
+                {
+                  GskNglTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i);
+
+                  if (atlas == entry->atlas)
+                    {
+                      g_hash_table_iter_remove (&iter);
+                      dropped++;
+                      break;
+                    }
+                }
+            }
+        }
+
+      GSK_NOTE (GLYPH_CACHE,
+                if (dropped > 0)
+                  g_message ("%s: Dropped %d icons",
+                             G_OBJECT_TYPE_NAME (self), dropped));
+    }
 
-  if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame)
-    GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame (self);
+  if (frame_id % MAX_FRAME_AGE == 0)
+    {
+      GskNglTextureAtlasEntry *entry;
+
+      g_hash_table_iter_init (&iter, self->hash_table);
+      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
+        {
+          gsk_ngl_texture_atlas_entry_mark_unused (entry);
+          entry->accessed = FALSE;
+        }
+
+      GSK_NOTE (GLYPH_CACHE, g_message ("%s: %d atlas items cached",
+                                        G_OBJECT_TYPE_NAME (self),
+                                        g_hash_table_size (self->hash_table)));
+    }
 }
 
 static GskNglTexture *
@@ -252,6 +297,7 @@ gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
   entry = g_slice_alloc0 (valuelen);
   entry->n_pixels = width * height;
   entry->accessed = TRUE;
+  entry->used = TRUE;
 
   /* If our size is invisible then we just want an entry in the
    * cache for faster lookups, but do not actually spend any texture
diff --git a/gsk/ngl/gskngltexturelibraryprivate.h b/gsk/ngl/gskngltexturelibraryprivate.h
index 7e042036eb..05c2216ed7 100644
--- a/gsk/ngl/gskngltexturelibraryprivate.h
+++ b/gsk/ngl/gskngltexturelibraryprivate.h
@@ -99,8 +99,9 @@ typedef struct _GskNglTextureLibraryClass
 {
   GObjectClass parent_class;
 
-  void (*begin_frame) (GskNglTextureLibrary *library);
-  void (*end_frame)   (GskNglTextureLibrary *library);
+  void (*begin_frame) (GskNglTextureLibrary *library,
+                       gint64                frame_id,
+                       GPtrArray            *removed_atlases);
 } GskNglTextureLibraryClass;
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskNglTextureLibrary, g_object_unref)
@@ -111,8 +112,9 @@ void     gsk_ngl_texture_library_set_funcs   (GskNglTextureLibrary *self,
                                               GEqualFunc            equal_func,
                                               GDestroyNotify        key_destroy,
                                               GDestroyNotify        value_destroy);
-void     gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self);
-void     gsk_ngl_texture_library_end_frame   (GskNglTextureLibrary *self);
+void     gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self,
+                                              gint64                frame_id,
+                                              GPtrArray            *removed_atlases);
 gpointer gsk_ngl_texture_library_pack        (GskNglTextureLibrary *self,
                                               gpointer              key,
                                               gsize                 valuelen,
@@ -126,14 +128,29 @@ static inline void
 gsk_ngl_texture_atlas_mark_unused (GskNglTextureAtlas *self,
                                    int                 n_pixels)
 {
+  g_assert (n_pixels >= 0);
+
   self->unused_pixels += n_pixels;
 }
 
 static inline void
-gsk_ngl_texture_atlas_mark_used (GskNglTextureAtlas *self,
-                                 int                 n_pixels)
+gsk_ngl_texture_atlas_entry_mark_used (GskNglTextureAtlasEntry *entry)
+{
+  if (entry->used == TRUE || entry->is_atlased == FALSE)
+    return;
+
+  entry->atlas->unused_pixels -= entry->n_pixels;
+  entry->used = TRUE;
+}
+
+static inline void
+gsk_ngl_texture_atlas_entry_mark_unused (GskNglTextureAtlasEntry *entry)
 {
-  self->unused_pixels -= n_pixels;
+  if (entry->used == FALSE || entry->is_atlased == FALSE)
+    return;
+
+  entry->atlas->unused_pixels += entry->n_pixels;
+  entry->used = FALSE;
 }
 
 static inline gboolean
@@ -151,13 +168,7 @@ gsk_ngl_texture_library_lookup (GskNglTextureLibrary     *self,
 
   if (entry != NULL)
     {
-      if (!entry->used && entry->is_atlased)
-        {
-          g_assert (entry->atlas != NULL);
-          gsk_ngl_texture_atlas_mark_used (entry->atlas, entry->n_pixels);
-          entry->used = TRUE;
-        }
-
+      gsk_ngl_texture_atlas_entry_mark_used (entry);
       entry->accessed = TRUE;
       *out_entry = entry;
       return TRUE;


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