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.
This commit is contained in:
Matthias Clasen 2019-06-04 21:27:09 +00:00
parent 1f70bc4bf3
commit 259bbdcb09
7 changed files with 615 additions and 424 deletions

View File

@ -12,20 +12,18 @@
#include <cairo.h>
#include <epoxy/gl.h>
/* Parameters for our cache eviction strategy.
/* Cache eviction strategy
*
* Each cached glyph has an age that gets reset every time a cached glyph gets used.
* Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
* count of the pixels of each atlas that are taken up by old glyphs. We check the
* fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD_RATIO, then
* we drop the atlas an all the glyphs contained in it from the cache.
* Each cached glyph has an age that gets reset every time a cached
* glyph gets used. Glyphs that have not been used for the
* MAX_FRAME_AGE frames are considered old.
*
* We keep count of the pixels of each atlas that are taken up by old
* data. When the fraction of old pixels gets too high, we drop the
* atlas and all the items it contained.
*/
#define MAX_AGE 60
#define CHECK_INTERVAL 10
#define MAX_OLD_RATIO 0.333
#define ATLAS_SIZE 512
#define MAX_FRAME_AGE (5 * 60)
static guint glyph_cache_hash (gconstpointer v);
static gboolean glyph_cache_equal (gconstpointer v1,
@ -33,59 +31,47 @@ static gboolean glyph_cache_equal (gconstpointer v1,
static void glyph_cache_key_free (gpointer v);
static void glyph_cache_value_free (gpointer v);
static GskGLTextureAtlas *
create_atlas (GskGLGlyphCache *self,
int width,
int height)
GskGLGlyphCache *
gsk_gl_glyph_cache_new (GdkDisplay *display,
GskGLTextureAtlases *atlases)
{
GskGLTextureAtlas *atlas;
GskGLGlyphCache *glyph_cache;
atlas = g_new (GskGLTextureAtlas, 1);
gsk_gl_texture_atlas_init (atlas, MAX (width, ATLAS_SIZE), MAX (height, ATLAS_SIZE));
glyph_cache = g_new0 (GskGLGlyphCache, 1);
GSK_NOTE(GLYPH_CACHE, g_message ("Create atlas %d x %d", atlas->width, atlas->height));
glyph_cache->display = display;
glyph_cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
glyph_cache_key_free, glyph_cache_value_free);
return atlas;
glyph_cache->atlases = gsk_gl_texture_atlases_ref (atlases);
glyph_cache->ref_count = 1;
return glyph_cache;
}
static void
free_atlas (gpointer v)
GskGLGlyphCache *
gsk_gl_glyph_cache_ref (GskGLGlyphCache *self)
{
GskGLTextureAtlas *atlas = v;
self->ref_count++;
g_assert (atlas->image.texture_id == 0);
gsk_gl_texture_atlas_free (atlas);
g_free (atlas);
return self;
}
void
gsk_gl_glyph_cache_init (GskGLGlyphCache *self)
gsk_gl_glyph_cache_unref (GskGLGlyphCache *self)
{
self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
glyph_cache_key_free, glyph_cache_value_free);
self->atlases = g_ptr_array_new_with_free_func (free_atlas);
}
g_assert (self->ref_count > 0);
void
gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
GskGLDriver *driver)
{
guint i;
for (i = 0; i < self->atlases->len; i ++)
if (self->ref_count == 1)
{
GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
if (atlas->image.texture_id != 0)
{
gsk_gl_image_destroy (&atlas->image, driver);
atlas->image.texture_id = 0;
}
gsk_gl_texture_atlases_unref (self->atlases);
g_hash_table_unref (self->hash_table);
g_free (self);
return;
}
g_ptr_array_unref (self->atlases);
g_hash_table_unref (self->hash_table);
self->ref_count--;
}
static gboolean
@ -122,83 +108,11 @@ glyph_cache_value_free (gpointer v)
g_free (v);
}
static void
add_to_cache (GskGLGlyphCache *cache,
GlyphCacheKey *key,
GskGLCachedGlyph *value)
{
const int width = value->draw_width * key->scale / 1024;
const int height = value->draw_height * key->scale / 1024;
GskGLTextureAtlas *atlas = NULL;
guint i, p;
int packed_x, packed_y;
/* Try all the atlases and pick the first one that can hold
* our new glyph */
for (i = 0, p = cache->atlases->len; i < p; i ++)
{
GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
gboolean was_packed;
was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y);
if (was_packed)
{
atlas = test_atlas;
break;
}
}
if (atlas == NULL)
{
gboolean was_packed;
atlas = create_atlas (cache, width + 2, height + 2);
g_ptr_array_add (cache->atlases, atlas);
was_packed = gsk_gl_texture_atlas_pack (atlas,
width + 2, height + 2,
&packed_x, &packed_y);
g_assert (was_packed);
}
value->tx = (float)(packed_x + 1) / atlas->width;
value->ty = (float)(packed_y + 1) / atlas->height;
value->tw = (float)width / atlas->width;
value->th = (float)height / atlas->height;
value->used = TRUE;
value->atlas = atlas;
if (atlas->user_data == NULL)
atlas->user_data = g_new0 (DirtyGlyph, 1);
((DirtyGlyph *)atlas->user_data)->key = key;
((DirtyGlyph *)atlas->user_data)->value = value;
#ifdef G_ENABLE_DEBUG
if (GSK_DEBUG_CHECK (GLYPH_CACHE))
{
for (i = 0; i < cache->atlases->len; i++)
{
atlas = g_ptr_array_index (cache->atlases, i);
g_message ("atlas %d (%dx%d): %.2g%% old pixels",
i, atlas->width, atlas->height,
gsk_gl_texture_atlas_get_unused_ratio (atlas));
}
}
#endif
}
static gboolean
render_glyph (const GskGLTextureAtlas *atlas,
const DirtyGlyph *glyph,
GskImageRegion *region)
render_glyph (GlyphCacheKey *key,
GskGLCachedGlyph *value,
GskImageRegion *region)
{
GlyphCacheKey *key = glyph->key;
GskGLCachedGlyph *value = glyph->value;
cairo_surface_t *surface;
cairo_t *cr;
cairo_scaled_font_t *scaled_font;
@ -210,16 +124,14 @@ render_glyph (const GskGLTextureAtlas *atlas,
scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
return FALSE;
{
g_warning ("Failed to get a font");
return FALSE;
}
surface_width = value->draw_width * key->scale / 1024;
surface_height = value->draw_height * key->scale / 1024;
/* TODO: Give glyphs that large their own texture in the proper size. Don't
* put them in the atlas at all. */
if (surface_width > atlas->width || surface_height > atlas->height)
return FALSE;
stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, surface_width);
data = g_malloc0 (stride * surface_height);
surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
@ -252,45 +164,120 @@ render_glyph (const GskGLTextureAtlas *atlas,
region->height = cairo_image_surface_get_height (surface);
region->stride = cairo_image_surface_get_stride (surface);
region->data = data;
region->x = (gsize)(value->tx * atlas->width);
region->y = (gsize)(value->ty * atlas->height);
if (value->atlas)
{
region->x = (gsize)(value->tx * value->atlas->width);
region->y = (gsize)(value->ty * value->atlas->height);
}
else
{
region->x = 0;
region->y = 0;
}
cairo_surface_destroy (surface);
return TRUE;
}
static void
upload_dirty_glyph (GskGLGlyphCache *self,
GskGLTextureAtlas *atlas,
GskGLDriver *driver)
upload_glyph (GlyphCacheKey *key,
GskGLCachedGlyph *value)
{
GskImageRegion region;
GskImageRegion r;
g_assert (atlas->user_data != NULL);
gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
"Uploading glyph %d",
key->glyph);
gdk_gl_context_push_debug_group_printf (gsk_gl_driver_get_gl_context (driver),
"Uploading glyph %d", ((DirtyGlyph *)atlas->user_data)->key->glyph);
if (render_glyph (atlas, (DirtyGlyph *)atlas->user_data, &region))
if (render_glyph (key, value, &r))
{
gsk_gl_image_upload_region (&atlas->image, driver, &region);
g_free (region.data);
glBindTexture (GL_TEXTURE_2D, value->texture_id);
glTextureSubImage2D (value->texture_id, 0,
r.x, r.y, r.width, r.height,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
r.data);
g_free (r.data);
}
gdk_gl_context_pop_debug_group (gsk_gl_driver_get_gl_context (driver));
/* TODO: This could be unnecessary. We can just reuse the allocated
* DirtyGlyph next time. */
g_clear_pointer (&atlas->user_data, g_free);
gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
}
const GskGLCachedGlyph *
gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
gboolean create,
PangoFont *font,
PangoGlyph glyph,
float scale)
static void
add_to_cache (GskGLGlyphCache *self,
GlyphCacheKey *key,
GskGLCachedGlyph *value)
{
const int width = value->draw_width * key->scale / 1024;
const int height = value->draw_height * key->scale / 1024;
GskGLTextureAtlas *atlas = NULL;
int packed_x = 0;
int packed_y = 0;
gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
value->tx = (float)(packed_x + 1) / atlas->width;
value->ty = (float)(packed_y + 1) / atlas->height;
value->tw = (float)width / atlas->width;
value->th = (float)height / atlas->height;
value->used = TRUE;
value->atlas = atlas;
value->texture_id = atlas->texture_id;
upload_glyph (key, value);
}
void
gsk_gl_glyph_cache_get_texture (GskGLDriver *driver,
PangoFont *font,
PangoGlyph glyph,
float scale,
GskGLCachedGlyph *value)
{
PangoRectangle ink_rect;
GlyphCacheKey key;
int width, height;
guint texture_id;
pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
pango_extents_to_pixels (&ink_rect, NULL);
key.font = font;
key.glyph = glyph;
key.scale = (guint)(scale * 1024);
value->atlas = NULL;
value->timestamp = 0;
value->draw_x = ink_rect.x;
value->draw_y = ink_rect.y;
value->draw_width = ink_rect.width;
value->draw_height = ink_rect.height;
value->tx = 0.0f;
value->ty = 0.0f;
value->tw = 1.0f;
value->th = 1.0f;
width = value->draw_width * key.scale / 1024;
height = value->draw_height * key.scale / 1024;
texture_id = gsk_gl_driver_create_texture (driver, width, height);
gsk_gl_driver_bind_source_texture (driver, texture_id);
gsk_gl_driver_init_texture_empty (driver, texture_id, GL_NEAREST, GL_NEAREST);
value->texture_id = texture_id;
upload_glyph (&key, value);
}
gboolean
gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
PangoFont *font,
PangoGlyph glyph,
float scale,
GskGLCachedGlyph *cached_glyph_out)
{
GskGLCachedGlyph *value;
@ -305,7 +292,7 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
{
const guint age = cache->timestamp - value->timestamp;
if (MAX_AGE <= age)
if (age > MAX_FRAME_AGE)
{
GskGLTextureAtlas *atlas = value->atlas;
@ -321,116 +308,68 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
value->timestamp = cache->timestamp;
}
if (create && value == NULL)
if (value == NULL)
{
GlyphCacheKey *key;
PangoRectangle ink_rect;
key = g_new0 (GlyphCacheKey, 1);
value = g_new0 (GskGLCachedGlyph, 1);
pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
pango_extents_to_pixels (&ink_rect, NULL);
value = g_new0 (GskGLCachedGlyph, 1);
value->draw_x = ink_rect.x;
value->draw_y = ink_rect.y;
value->draw_width = ink_rect.width;
value->draw_height = ink_rect.height;
value->timestamp = cache->timestamp;
value->atlas = NULL; /* For now */
value->scale = (guint)(scale * 1024);
key->font = g_object_ref (font);
key->glyph = glyph;
key->scale = (guint)(scale * 1024);
if (ink_rect.width < 128 && ink_rect.height < 128)
{
GlyphCacheKey *key;
if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
add_to_cache (cache, key, value);
key = g_new0 (GlyphCacheKey, 1);
g_hash_table_insert (cache->hash_table, key, value);
key->font = g_object_ref (font);
key->glyph = glyph;
key->scale = (guint)(scale * 1024);
if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
add_to_cache (cache, key, value);
*cached_glyph_out = *value;
g_hash_table_insert (cache->hash_table, key, value);
}
else
{
*cached_glyph_out = *value;
glyph_cache_value_free (value);
}
}
return value;
}
guint
gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache *self,
GskGLDriver *driver,
const GskGLCachedGlyph *glyph)
{
GskGLTextureAtlas *atlas = glyph->atlas;
g_assert (atlas != NULL);
if (atlas->image.texture_id == 0)
else
{
gsk_gl_image_create (&atlas->image, driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
gdk_gl_context_label_object_printf (gsk_gl_driver_get_gl_context (driver),
GL_TEXTURE, atlas->image.texture_id,
"Glyph atlas %d", atlas->image.texture_id);
*cached_glyph_out = *value;
}
if (atlas->user_data != NULL)
upload_dirty_glyph (self, atlas, driver);
return atlas->image.texture_id;
return cached_glyph_out->atlas != NULL;
}
void
gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
GskGLDriver *driver)
gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
{
int i;
GHashTableIter iter;
GlyphCacheKey *key;
GskGLCachedGlyph *value;
GHashTable *removed = g_hash_table_new (g_direct_hash, g_direct_equal);
guint dropped = 0;
self->timestamp++;
if ((self->timestamp - 1) % CHECK_INTERVAL != 0)
return;
/* look for atlases to drop, and create a mapping of updated texture indices */
for (i = self->atlases->len - 1; i >= 0; i--)
{
GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
{
GSK_NOTE(GLYPH_CACHE,
g_message ("Dropping atlas %d (%g.2%% old)", i,
gsk_gl_texture_atlas_get_unused_ratio (atlas)));
#if 0
static int kk;
g_message ("Dropping glyph cache... Ratio: %f",
gsk_gl_texture_atlas_get_unused_ratio (atlas));
gsk_gl_image_write_to_png (&atlas->image, driver,
g_strdup_printf ("dropped_%d.png", kk++));
#endif
if (atlas->image.texture_id != 0)
{
gsk_gl_image_destroy (&atlas->image, driver);
atlas->image.texture_id = 0;
}
g_hash_table_add (removed, atlas);
g_ptr_array_remove_index (self->atlases, i);
}
}
/* Remove all glyphs whose atlas was removed, and
* mark old glyphs as unused
*/
g_hash_table_iter_init (&iter, self->hash_table);
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
{
if (g_hash_table_contains (removed, value->atlas))
guint pos;
if (!g_ptr_array_find (self->atlases->atlases, value->atlas, &pos))
{
g_hash_table_iter_remove (&iter);
dropped++;
@ -439,7 +378,7 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
{
const guint age = self->timestamp - value->timestamp;
if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
if (age > MAX_FRAME_AGE)
{
GskGLTextureAtlas *atlas = value->atlas;
@ -451,23 +390,6 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
}
}
}
g_hash_table_unref (removed);
GSK_NOTE(GLYPH_CACHE, if (dropped > 0) g_message ("Dropped %d glyphs", dropped));
#if 0
for (i = 0; i < self->atlases->len; i++)
{
GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
if (atlas->image)
{
char *filename;
filename = g_strdup_printf ("glyphatlas%d-%ld.png", i, self->timestamp);
gsk_gl_image_write_to_png (atlas->image, driver, filename);
g_free (filename);
}
}
#endif
}

View File

@ -9,8 +9,11 @@
typedef struct
{
int ref_count;
GdkDisplay *display;
GHashTable *hash_table;
GPtrArray *atlases;
GskGLTextureAtlases *atlases;
guint64 timestamp;
} GskGLGlyphCache;
@ -22,18 +25,12 @@ typedef struct
guint scale; /* times 1024 */
} GlyphCacheKey;
typedef struct _DirtyGlyph DirtyGlyph;
typedef struct _GskGLCachedGlyph GskGLCachedGlyph;
struct _DirtyGlyph
{
GlyphCacheKey *key;
GskGLCachedGlyph *value;
};
struct _GskGLCachedGlyph
{
GskGLTextureAtlas *atlas;
guint texture_id;
float tx;
float ty;
@ -45,25 +42,25 @@ struct _GskGLCachedGlyph
int draw_width;
int draw_height;
float scale;
guint64 timestamp;
guint used: 1;
};
void gsk_gl_glyph_cache_init (GskGLGlyphCache *self);
void gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
GskGLDriver *driver);
void gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
GskGLDriver *driver);
guint gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache *self,
GskGLDriver *driver,
const GskGLCachedGlyph *glyph);
const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup (GskGLGlyphCache *self,
gboolean create,
GskGLGlyphCache * gsk_gl_glyph_cache_new (GdkDisplay *display,
GskGLTextureAtlases *atlases);
GskGLGlyphCache * gsk_gl_glyph_cache_ref (GskGLGlyphCache *self);
void gsk_gl_glyph_cache_unref (GskGLGlyphCache *self);
void gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self);
gboolean gsk_gl_glyph_cache_lookup (GskGLGlyphCache *self,
PangoFont *font,
PangoGlyph glyph,
float scale);
float scale,
GskGLCachedGlyph *cached_glyph_out);
void gsk_gl_glyph_cache_get_texture (GskGLDriver *driver,
PangoFont *font,
PangoGlyph glyph,
float scale,
GskGLCachedGlyph *glyph_out);
#endif

View File

@ -1,12 +1,11 @@
#include "gskgliconcacheprivate.h"
#include "gskgltextureatlasprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdkglcontextprivate.h"
#include <epoxy/gl.h>
#define ATLAS_SIZE (1024)
#define MAX_FRAME_AGE (5 * 60)
#define MAX_UNUSED_RATIO 0.8
typedef struct
{
@ -22,57 +21,49 @@ icon_data_free (gpointer p)
g_free (p);
}
static void
free_atlas (gpointer v)
GskGLIconCache *
gsk_gl_icon_cache_new (GdkDisplay *display,
GskGLTextureAtlases *atlases)
{
GskGLTextureAtlas *atlas = v;
GskGLIconCache *self;
g_assert (atlas->image.texture_id == 0);
gsk_gl_texture_atlas_free (atlas);
self = g_new0 (GskGLIconCache, 1);
g_free (atlas);
}
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_with_free_func ((GDestroyNotify)free_atlas);
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_free (GskGLIconCache *self)
gsk_gl_icon_cache_unref (GskGLIconCache *self)
{
guint i, p;
g_assert (self->ref_count > 0);
for (i = 0, p = self->atlases->len; i < p; i ++)
if (self->ref_count == 1)
{
GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
if (atlas->image.texture_id != 0)
{
gsk_gl_image_destroy (&atlas->image, self->gl_driver);
atlas->image.texture_id = 0;
}
gsk_gl_texture_atlas_free (atlas);
g_free (atlas);
gsk_gl_texture_atlases_unref (self->atlases);
g_hash_table_unref (self->icons);
g_free (self);
return;
}
g_ptr_array_free (self->atlases, TRUE);
g_hash_table_unref (self->icons);
self->ref_count--;
}
void
gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
{
gint i, p;
GHashTableIter iter;
GdkTexture *texture;
IconData *icon_data;
@ -81,45 +72,30 @@ gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
g_hash_table_iter_init (&iter, self->icons);
while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
{
icon_data->frame_age ++;
guint pos;
if (icon_data->frame_age > MAX_FRAME_AGE)
if (!g_ptr_array_find (self->atlases->atlases, icon_data->atlas, &pos))
{
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 + 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. */
g_hash_table_iter_remove (&iter);
}
}
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)
else
{
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);
}
icon_data->frame_age ++;
if (atlas->image.texture_id != 0)
if (icon_data->frame_age > MAX_FRAME_AGE)
{
gsk_gl_image_destroy (&atlas->image, self->gl_driver);
atlas->image.texture_id = 0;
}
g_ptr_array_remove_index_fast (self->atlases, i);
i --; /* Check the current index again */
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. */
}
}
}
}
@ -154,6 +130,16 @@ pad_surface (cairo_surface_t *surface)
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,
@ -167,84 +153,60 @@ gsk_gl_icon_cache_lookup_or_add (GskGLIconCache *self,
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;
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->image.texture_id;
*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 twidth = gdk_texture_get_width (texture);
const int theight = gdk_texture_get_height (texture);
int packed_x, packed_y;
const int width = gdk_texture_get_width (texture);
const int height = gdk_texture_get_height (texture);
GskGLTextureAtlas *atlas = NULL;
guint i, p;
int packed_x = 0;
int packed_y = 0;
GskImageRegion region;
cairo_surface_t *surface;
cairo_surface_t *padded_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 + 2, theight + 2, &packed_x, &packed_y))
{
packed_x += 1;
packed_y += 1;
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, GL_LINEAR, GL_LINEAR);
/* Pack it onto that one, which surely has enought space... */
gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y);
packed_x += 1;
packed_y += 1;
g_ptr_array_add (self->atlases, atlas);
}
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 / ATLAS_SIZE,
(float)packed_y / ATLAS_SIZE,
(float)twidth / ATLAS_SIZE,
(float)theight / ATLAS_SIZE);
(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 - 1;
region.y = packed_y - 1;
region.width = twidth + 2;
region.height = theight + 2;
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);
gsk_gl_image_upload_region (&atlas->image, self->gl_driver, &region);
gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
"Uploading texture");
*out_texture_id = atlas->image.texture_id;
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);

View File

@ -10,18 +10,20 @@
typedef struct
{
GskGLDriver *gl_driver;
GskRenderer *renderer;
int ref_count;
GPtrArray *atlases;
GdkDisplay *display;
GskGLDriver *gl_driver;
GskGLTextureAtlases *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);
GskGLIconCache * gsk_gl_icon_cache_new (GdkDisplay *display,
GskGLTextureAtlases *atlases);
GskGLIconCache * gsk_gl_icon_cache_ref (GskGLIconCache *self);
void gsk_gl_icon_cache_unref (GskGLIconCache *self);
void gsk_gl_icon_cache_begin_frame (GskGLIconCache *self);
void gsk_gl_icon_cache_lookup_or_add (GskGLIconCache *self,
GdkTexture *texture,

View File

@ -335,8 +335,9 @@ struct _GskGLRenderer
RenderOpBuilder op_builder;
GArray *render_ops;
GskGLGlyphCache glyph_cache;
GskGLIconCache icon_cache;
GskGLTextureAtlases *atlases;
GskGLGlyphCache *glyph_cache;
GskGLIconCache *icon_cache;
GskGLShadowCache shadow_cache;
#ifdef G_ENABLE_DEBUG
@ -571,7 +572,7 @@ render_text_node (GskGLRenderer *self,
for (i = 0; i < num_glyphs; i++)
{
const PangoGlyphInfo *gi = &glyphs[i];
const GskGLCachedGlyph *glyph;
GskGLCachedGlyph glyph;
float glyph_x, glyph_y, glyph_w, glyph_h;
float tx, ty, tx2, ty2;
double cx;
@ -580,32 +581,41 @@ render_text_node (GskGLRenderer *self,
if (gi->glyph == PANGO_GLYPH_EMPTY)
continue;
glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
TRUE,
(PangoFont *)font,
gi->glyph,
text_scale);
gsk_gl_glyph_cache_lookup (self->glyph_cache,
(PangoFont *)font,
gi->glyph,
text_scale,
&glyph);
/* e.g. whitespace */
if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0)
if (glyph.draw_width <= 0 || glyph.draw_height <= 0)
goto next;
/* big glyphs are not cached */
if (!glyph.texture_id)
{
gsk_gl_glyph_cache_get_texture (self->gl_driver,
(PangoFont *)font,
gi->glyph,
text_scale,
&glyph);
g_assert (glyph.texture_id != 0);
}
cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_texture_id (&self->glyph_cache,
self->gl_driver,
glyph));
ops_set_texture (builder, glyph.texture_id);
tx = glyph->tx;
ty = glyph->ty;
tx2 = tx + glyph->tw;
ty2 = ty + glyph->th;
tx = glyph.tx;
ty = glyph.ty;
tx2 = tx + glyph.tw;
ty2 = ty + glyph.th;
glyph_x = x + cx + glyph->draw_x;
glyph_y = y + cy + glyph->draw_y;
glyph_w = glyph->draw_width;
glyph_h = glyph->draw_height;
glyph_x = x + cx + glyph.draw_x;
glyph_y = y + cy + glyph.draw_y;
glyph_w = glyph.draw_width;
glyph_h = glyph.draw_height;
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
{ { glyph_x, glyph_y }, { tx, ty }, },
@ -829,12 +839,12 @@ render_texture_node (GskGLRenderer *self,
int texture_id;
float tx = 0, ty = 0, tx2 = 1, ty2 = 1;
if (texture->width <= 64 &&
texture->height <= 64)
if (texture->width <= 128 &&
texture->height <= 128)
{
graphene_rect_t trect;
gsk_gl_icon_cache_lookup_or_add (&self->icon_cache,
gsk_gl_icon_cache_lookup_or_add (self->icon_cache,
texture,
&texture_id,
&trect);
@ -2466,6 +2476,68 @@ gsk_gl_renderer_create_programs (GskGLRenderer *self,
return TRUE;
}
static GskGLTextureAtlases *
get_texture_atlases_for_display (GdkDisplay *display)
{
GskGLTextureAtlases *atlases;
if (g_getenv ("GSK_NO_SHARED_CACHES"))
return gsk_gl_texture_atlases_new ();
atlases = (GskGLTextureAtlases*)g_object_get_data (G_OBJECT (display), "gsk-gl-texture-atlases");
if (atlases == NULL)
{
atlases = gsk_gl_texture_atlases_new ();
g_object_set_data_full (G_OBJECT (display), "gsk-gl-texture-atlases",
gsk_gl_texture_atlases_ref (atlases),
(GDestroyNotify) gsk_gl_texture_atlases_unref);
}
return atlases;
}
static GskGLGlyphCache *
get_glyph_cache_for_display (GdkDisplay *display,
GskGLTextureAtlases *atlases)
{
GskGLGlyphCache *glyph_cache;
if (g_getenv ("GSK_NO_SHARED_CACHES"))
return gsk_gl_glyph_cache_new (display, atlases);
glyph_cache = (GskGLGlyphCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-glyph-cache");
if (glyph_cache == NULL)
{
glyph_cache = gsk_gl_glyph_cache_new (display, atlases);
g_object_set_data_full (G_OBJECT (display), "gsk-gl-glyph-cache",
gsk_gl_glyph_cache_ref (glyph_cache),
(GDestroyNotify) gsk_gl_glyph_cache_unref);
}
return glyph_cache;
}
static GskGLIconCache *
get_icon_cache_for_display (GdkDisplay *display,
GskGLTextureAtlases *atlases)
{
GskGLIconCache *icon_cache;
if (g_getenv ("GSK_NO_SHARED_CACHES"))
return gsk_gl_icon_cache_new (display, atlases);
icon_cache = (GskGLIconCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-icon-cache");
if (icon_cache == NULL)
{
icon_cache = gsk_gl_icon_cache_new (display, atlases);
g_object_set_data_full (G_OBJECT (display), "gsk-gl-icon-cache",
gsk_gl_icon_cache_ref (icon_cache),
(GDestroyNotify) gsk_gl_icon_cache_unref);
}
return icon_cache;
}
static gboolean
gsk_gl_renderer_realize (GskRenderer *renderer,
GdkSurface *surface,
@ -2496,8 +2568,9 @@ gsk_gl_renderer_realize (GskRenderer *renderer,
if (!gsk_gl_renderer_create_programs (self, error))
return FALSE;
gsk_gl_glyph_cache_init (&self->glyph_cache);
gsk_gl_icon_cache_init (&self->icon_cache, renderer, self->gl_driver);
self->atlases = get_texture_atlases_for_display (gdk_surface_get_display (surface));
self->glyph_cache = get_glyph_cache_for_display (gdk_surface_get_display (surface), self->atlases);
self->icon_cache = get_icon_cache_for_display (gdk_surface_get_display (surface), self->atlases);
gsk_gl_shadow_cache_init (&self->shadow_cache);
return TRUE;
@ -2522,8 +2595,9 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
for (i = 0; i < GL_N_PROGRAMS; i ++)
glDeleteProgram (self->programs[i].id);
gsk_gl_glyph_cache_free (&self->glyph_cache, self->gl_driver);
gsk_gl_icon_cache_free (&self->icon_cache);
g_clear_pointer (&self->glyph_cache, gsk_gl_glyph_cache_unref);
g_clear_pointer (&self->icon_cache, gsk_gl_icon_cache_unref);
g_clear_pointer (&self->atlases, gsk_gl_texture_atlases_unref);
gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
g_clear_object (&self->gl_profiler);
@ -3089,8 +3163,9 @@ gsk_gl_renderer_do_render (GskRenderer *renderer,
ORTHO_FAR_PLANE);
graphene_matrix_scale (&projection, 1, -1, 1);
gsk_gl_glyph_cache_begin_frame (&self->glyph_cache, self->gl_driver);
gsk_gl_icon_cache_begin_frame (&self->icon_cache);
gsk_gl_texture_atlases_begin_frame (self->atlases);
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);

View File

@ -1,6 +1,169 @@
#include "config.h"
#include "gskgltextureatlasprivate.h"
#include "gskdebugprivate.h"
#include "gdkglcontextprivate.h"
#include <epoxy/gl.h>
#define ATLAS_SIZE (512)
#define MAX_OLD_RATIO 0.5
static void
free_atlas (gpointer v)
{
GskGLTextureAtlas *atlas = v;
gsk_gl_texture_atlas_free (atlas);
g_free (atlas);
}
GskGLTextureAtlases *
gsk_gl_texture_atlases_new (void)
{
GskGLTextureAtlases *atlases;
atlases = g_new (GskGLTextureAtlases, 1);
atlases->atlases = g_ptr_array_new_with_free_func (free_atlas);
atlases->ref_count = 1;
return atlases;
}
GskGLTextureAtlases *
gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases)
{
atlases->ref_count++;
return atlases;
}
void
gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases)
{
g_assert (atlases->ref_count > 0);
if (atlases->ref_count == 1)
{
g_ptr_array_unref (atlases->atlases);
g_free (atlases);
return;
}
atlases->ref_count--;
}
#if 1
static void
write_atlas_to_png (GskGLTextureAtlas *atlas,
const char *filename)
{
int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
guchar *data = g_malloc (atlas->height * stride);
cairo_surface_t *s;
glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
cairo_surface_write_to_png (s, filename);
cairo_surface_destroy (s);
g_free (data);
}
#endif
void
gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases)
{
int i;
for (i = atlases->atlases->len - 1; i >= 0; i--)
{
GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
{
GSK_NOTE(GLYPH_CACHE,
g_message ("Dropping atlas %d (%g.2%% old)", i,
gsk_gl_texture_atlas_get_unused_ratio (atlas)));
if (atlas->texture_id != 0)
{
glDeleteTextures (1, &atlas->texture_id);
atlas->texture_id = 0;
}
g_ptr_array_remove_index (atlases->atlases, i);
}
}
#if 1
{
static guint timestamp;
timestamp++;
if (timestamp % 10 == 0)
for (i = 0; i < atlases->atlases->len; i++)
{
GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
if (atlas->texture_id)
{
char *filename;
filename = g_strdup_printf ("textureatlas%d-%u.png", i, timestamp);
write_atlas_to_png (atlas, filename);
g_free (filename);
}
}
}
#endif
}
gboolean
gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
int width,
int height,
GskGLTextureAtlas **atlas_out,
int *out_x,
int *out_y)
{
GskGLTextureAtlas *atlas;
int x, y;
int i;
g_assert (width < ATLAS_SIZE);
g_assert (height < ATLAS_SIZE);
for (i = 0; i < atlases->atlases->len; i++)
{
atlas = g_ptr_array_index (atlases->atlases, i);
if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
break;
atlas = NULL;
}
if (atlas == NULL)
{
/* 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_texture_atlas_realize (atlas);
g_ptr_array_add (atlases->atlases, atlas);
/* Pack it onto that one, which surely has enough space... */
gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y);
}
*atlas_out = atlas;
*out_x = x;
*out_y = y;
return TRUE;
}
void
gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
@ -9,7 +172,7 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
{
memset (self, 0, sizeof (*self));
self->image.texture_id = 0;
self->texture_id = 0;
self->width = width;
self->height = height;
@ -20,11 +183,19 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
width, height,
self->nodes,
width);
gsk_gl_texture_atlas_realize (self);
}
void
gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
{
if (self->texture_id != 0)
{
glDeleteTextures (1, &self->texture_id);
self->texture_id = 0;
}
g_clear_pointer (&self->nodes, g_free);
}
@ -82,3 +253,43 @@ gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
return 0.0;
}
/* Not using gdk_gl_driver_create_texture here, since we want
* this texture to survive the driver and stay around until
* the display gets closed.
*/
static guint
create_shared_texture (int width,
int height)
{
guint texture_id;
glGenTextures (1, &texture_id);
glBindTexture (GL_TEXTURE_2D, texture_id);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture (GL_TEXTURE_2D, 0);
return texture_id;
}
void
gsk_gl_texture_atlas_realize (GskGLTextureAtlas *atlas)
{
if (atlas->texture_id)
return;
atlas->texture_id = create_shared_texture (atlas->width, atlas->height);
gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
GL_TEXTURE, atlas->texture_id,
"Glyph atlas %d", atlas->texture_id);
}

View File

@ -14,7 +14,7 @@ struct _GskGLTextureAtlas
int width;
int height;
GskGLImage image;
guint texture_id;
int unused_pixels; /* Pixels of rects that have been used at some point,
But are now unused. */
@ -23,12 +23,34 @@ struct _GskGLTextureAtlas
};
typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
struct _GskGLTextureAtlases
{
int ref_count;
GPtrArray *atlases;
};
typedef struct _GskGLTextureAtlases GskGLTextureAtlases;
GskGLTextureAtlases *gsk_gl_texture_atlases_new (void);
GskGLTextureAtlases *gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases);
void gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases);
void gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases);
gboolean gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
int width,
int height,
GskGLTextureAtlas **atlas_out,
int *out_x,
int *out_y);
void gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
int width,
int height);
void gsk_gl_texture_atlas_free (GskGLTextureAtlas *self);
void gsk_gl_texture_atlas_realize (GskGLTextureAtlas *self);
void gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self,
int width,
int height);