forked from AuroraMiddleware/gtk
259bbdcb09
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.
223 lines
6.1 KiB
C
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, ®ion);
|
|
|
|
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
|
|
}
|
|
}
|