[gtk/wip/matthiasc/gl-icon-cache: 458/466] gl renderer: Add & use icon cache



commit 12444261fdcd7e6e30a732d9dccdac6f2ecf1a9b
Author: Timm Bäder <mail baedert org>
Date:   Wed May 22 09:33:25 2019 +0200

    gl renderer: Add & use icon cache
    
    Upload small icons all to the same texture atlas.

 gsk/gl/gskgliconcache.c        | 194 +++++++++++++++++++++++++++++++++++++++++
 gsk/gl/gskgliconcacheprivate.h |  31 +++++++
 gsk/gl/gskglrenderer.c         |  49 ++++++++---
 gsk/meson.build                |   1 +
 4 files changed, 262 insertions(+), 13 deletions(-)
---
diff --git a/gsk/gl/gskgliconcache.c b/gsk/gl/gskgliconcache.c
new file mode 100644
index 0000000000..06c8a3ae63
--- /dev/null
+++ b/gsk/gl/gskgliconcache.c
@@ -0,0 +1,194 @@
+#include "gskgliconcacheprivate.h"
+#include "gskgltextureatlasprivate.h"
+#include "gdk/gdktextureprivate.h"
+
+#define ATLAS_SIZE    (1024)
+#define MAX_FRAME_AGE (5 * 60)
+#define MAX_UNUSED_RATIO 0.8
+
+typedef struct
+{
+  graphene_rect_t texture_rect;
+  GskGLTextureAtlas *atlas;
+  int frame_age; /* Number of frames this icon is unused */
+  guint used: 1;
+} IconData;
+
+static void
+icon_data_free (gpointer p)
+{
+}
+
+void
+gsk_gl_icon_cache_init (GskGLIconCache *self,
+                        GskRenderer    *renderer,
+                        GskGLDriver    *gl_driver)
+{
+  self->renderer = renderer;
+  self->gl_driver = gl_driver;
+
+  self->atlases = g_ptr_array_new ();
+  self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+}
+
+void
+gsk_gl_icon_cache_free (GskGLIconCache *self)
+{
+  guint i, p;
+
+  for (i = 0, p = self->atlases->len; i < p; i ++)
+    {
+      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
+
+      gsk_gl_texture_atlas_free (atlas);
+      g_free (atlas);
+    }
+  g_ptr_array_free (self->atlases, TRUE);
+
+  g_hash_table_unref (self->icons);
+}
+
+void
+gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
+{
+  gint i, p;
+  GHashTableIter iter;
+  GdkTexture *texture;
+  IconData *icon_data;
+
+  /* Increase frame age of all icons */
+  g_hash_table_iter_init (&iter, self->icons);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
+    {
+      icon_data->frame_age ++;
+
+      if (icon_data->frame_age > MAX_FRAME_AGE)
+        {
+
+          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, h);
+              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. */
+        }
+    }
+
+  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)
+        {
+          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);
+            }
+
+          g_ptr_array_remove_index_fast (self->atlases, i);
+          i --; /* Check the current index again */
+        }
+    }
+}
+
+void
+gsk_gl_icon_cache_lookup_or_add (GskGLIconCache  *self,
+                                 GdkTexture      *texture,
+                                 int             *out_texture_id,
+                                 graphene_rect_t *out_texture_rect)
+{
+  IconData *icon_data = g_hash_table_lookup (self->icons, texture);
+
+  if (icon_data)
+    {
+      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;
+
+          gsk_gl_texture_atlas_mark_used (icon_data->atlas, w, h);
+          icon_data->used = TRUE;
+        }
+
+      *out_texture_id = icon_data->atlas->image.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;
+    GskGLTextureAtlas *atlas = NULL;
+    guint i, p;
+    GskImageRegion region;
+    cairo_surface_t *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, theight, &packed_x, &packed_y))
+          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);
+        /* Pack it onto that one, which surely has enought space... */
+        gsk_gl_texture_atlas_pack (atlas, twidth, theight, &packed_x, &packed_y);
+
+        g_ptr_array_add (self->atlases, atlas);
+      }
+
+    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);
+
+    g_hash_table_insert (self->icons, texture, icon_data);
+
+    /* actually upload the texture */
+    surface = gdk_texture_download_surface (texture);
+    region.x = packed_x;
+    region.y = packed_y;
+    region.width = twidth;
+    region.height = theight;
+    region.data = cairo_image_surface_get_data (surface);
+
+    gsk_gl_image_upload_region (&atlas->image, self->gl_driver, &region);
+
+    *out_texture_id = atlas->image.texture_id;
+    *out_texture_rect = icon_data->texture_rect;
+
+    cairo_surface_destroy (surface);
+
+#if 0
+    /* Some obvious debugging */
+    static int k;
+    gsk_gl_image_write_to_png (&atlas->image, self->gl_driver,
+                               g_strdup_printf ("icon%d.png", k ++));
+#endif
+  }
+}
diff --git a/gsk/gl/gskgliconcacheprivate.h b/gsk/gl/gskgliconcacheprivate.h
new file mode 100644
index 0000000000..883a274ded
--- /dev/null
+++ b/gsk/gl/gskgliconcacheprivate.h
@@ -0,0 +1,31 @@
+#ifndef __GSK_GL_ICON_CACHE_PRIVATE_H__
+#define __GSK_GL_ICON_CACHE_PRIVATE_H__
+
+#include "gskgldriverprivate.h"
+#include "gskglimageprivate.h"
+#include "gskrendererprivate.h"
+#include "gskgltextureatlasprivate.h"
+#include <pango/pango.h>
+#include <gdk/gdk.h>
+
+typedef struct
+{
+  GskGLDriver *gl_driver;
+  GskRenderer *renderer;
+
+  GPtrArray *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);
+void             gsk_gl_icon_cache_begin_frame    (GskGLIconCache        *self);
+void             gsk_gl_icon_cache_lookup_or_add  (GskGLIconCache        *self,
+                                                   GdkTexture            *texture,
+                                                   int                   *out_texture_id,
+                                                   graphene_rect_t       *out_texture_rect);
+
+#endif
diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c
index 0cced099f7..6ea34e396d 100644
--- a/gsk/gl/gskglrenderer.c
+++ b/gsk/gl/gskglrenderer.c
@@ -11,6 +11,7 @@
 #include "gsktransformprivate.h"
 #include "gskshaderbuilderprivate.h"
 #include "gskglglyphcacheprivate.h"
+#include "gskgliconcacheprivate.h"
 #include "gskglrenderopsprivate.h"
 #include "gskcairoblurprivate.h"
 #include "gskglshadowcacheprivate.h"
@@ -335,6 +336,7 @@ struct _GskGLRenderer
   GArray *render_ops;
 
   GskGLGlyphCache glyph_cache;
+  GskGLIconCache icon_cache;
   GskGLShadowCache shadow_cache;
 
 #ifdef G_ENABLE_DEBUG
@@ -823,27 +825,45 @@ render_texture_node (GskGLRenderer       *self,
     }
   else
     {
-      int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
       int texture_id;
+      float tx = 0, ty = 0, tx2 = 1, ty2 = 1;
+
+      if (texture->width <= 64 &&
+          texture->height <= 64)
+        {
+          graphene_rect_t trect;
+
+          gsk_gl_icon_cache_lookup_or_add (&self->icon_cache,
+                                           texture,
+                                           &texture_id,
+                                           &trect);
+          tx = trect.origin.x;
+          ty = trect.origin.y;
+          tx2 = tx + trect.size.width;
+          ty2 = ty + trect.size.height;
+        }
+      else
+        {
+          int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
+          get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
 
-      get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
+          texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
+                                                              texture,
+                                                              gl_min_filter,
+                                                              gl_mag_filter);
+        }
 
-      texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
-                                                          texture,
-                                                          gl_min_filter,
-                                                          gl_mag_filter);
       ops_set_program (builder, &self->blit_program);
       ops_set_texture (builder, texture_id);
 
-
       ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
-        { { min_x, min_y }, { 0, 0 }, },
-        { { min_x, max_y }, { 0, 1 }, },
-        { { max_x, min_y }, { 1, 0 }, },
+        { { min_x, min_y }, { tx,  ty  }, },
+        { { min_x, max_y }, { tx,  ty2 }, },
+        { { max_x, min_y }, { tx2, ty  }, },
 
-        { { max_x, max_y }, { 1, 1 }, },
-        { { min_x, max_y }, { 0, 1 }, },
-        { { max_x, min_y }, { 1, 0 }, },
+        { { max_x, max_y }, { tx2, ty2 }, },
+        { { min_x, max_y }, { tx,  ty2 }, },
+        { { max_x, min_y }, { tx2, ty  }, },
       });
     }
 }
@@ -2476,6 +2496,7 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
     return FALSE;
 
   gsk_gl_glyph_cache_init (&self->glyph_cache, renderer, self->gl_driver);
+  gsk_gl_icon_cache_init (&self->icon_cache, renderer, self->gl_driver);
   gsk_gl_shadow_cache_init (&self->shadow_cache);
 
   return TRUE;
@@ -2501,6 +2522,7 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
     glDeleteProgram (self->programs[i].id);
 
   gsk_gl_glyph_cache_free (&self->glyph_cache);
+  gsk_gl_icon_cache_free (&self->icon_cache);
   gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
 
   g_clear_object (&self->gl_profiler);
@@ -3067,6 +3089,7 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
   graphene_matrix_scale (&projection, 1, -1, 1);
 
   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/meson.build b/gsk/meson.build
index 1f415eb425..dd8a4c967b 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -46,6 +46,7 @@ gsk_private_sources = files([
   'gl/gskglshadowcache.c',
   'gl/gskglnodesample.c',
   'gl/gskgltextureatlas.c',
+  'gl/gskgliconcache.c',
   'gl/stb_rect_pack.c',
 ])
 


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