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, ®ion); + + *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 +#include + +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; - get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter); + 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); + + 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', ])