gtk2/gsk/gl/gskgliconcache.c
Matthias Clasen 259bbdcb09 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.
2019-06-04 23:00:02 +00:00

223 lines
6.1 KiB
C

#include "gskgliconcacheprivate.h"
#include "gskgltextureatlasprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdkglcontextprivate.h"
#include <epoxy/gl.h>
#define MAX_FRAME_AGE (5 * 60)
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)
{
g_free (p);
}
GskGLIconCache *
gsk_gl_icon_cache_new (GdkDisplay *display,
GskGLTextureAtlases *atlases)
{
GskGLIconCache *self;
self = g_new0 (GskGLIconCache, 1);
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;
return self;
}
GskGLIconCache *
gsk_gl_icon_cache_ref (GskGLIconCache *self)
{
self->ref_count++;
return self;
}
void
gsk_gl_icon_cache_unref (GskGLIconCache *self)
{
g_assert (self->ref_count > 0);
if (self->ref_count == 1)
{
gsk_gl_texture_atlases_unref (self->atlases);
g_hash_table_unref (self->icons);
g_free (self);
return;
}
self->ref_count--;
}
void
gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
{
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))
{
guint pos;
if (!g_ptr_array_find (self->atlases->atlases, icon_data->atlas, &pos))
{
g_hash_table_iter_remove (&iter);
}
else
{
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 * 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. */
}
}
}
}
/* FIXME: this could probably be done more efficiently */
static cairo_surface_t *
pad_surface (cairo_surface_t *surface)
{
cairo_surface_t *padded;
cairo_t *cr;
cairo_pattern_t *pattern;
cairo_matrix_t matrix;
padded = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
cairo_image_surface_get_width (surface) + 2,
cairo_image_surface_get_height (surface) + 2);
cr = cairo_create (padded);
pattern = cairo_pattern_create_for_surface (surface);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_matrix_init_translate (&matrix, -1, -1);
cairo_pattern_set_matrix (pattern, &matrix);
cairo_set_source (cr, pattern);
cairo_paint (cr);
cairo_destroy (cr);
cairo_pattern_destroy (pattern);
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,
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 * 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->texture_id;
*out_texture_rect = icon_data->texture_rect;
return;
}
/* texture not on any atlas yet. Find a suitable one. */
{
const int width = gdk_texture_get_width (texture);
const int height = gdk_texture_get_height (texture);
GskGLTextureAtlas *atlas = NULL;
int packed_x = 0;
int packed_y = 0;
GskImageRegion region;
cairo_surface_t *surface;
cairo_surface_t *padded_surface;
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 + 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;
region.y = packed_y;
region.width = width + 2;
region.height = height + 2;
region.data = cairo_image_surface_get_data (padded_surface);
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->texture_id;
*out_texture_rect = icon_data->texture_rect;
cairo_surface_destroy (surface);
cairo_surface_destroy (padded_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
}
}