mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 14:10:30 +00:00
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:
parent
1f70bc4bf3
commit
259bbdcb09
@ -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, ®ion))
|
||||
if (render_glyph (key, value, &r))
|
||||
{
|
||||
|
||||
gsk_gl_image_upload_region (&atlas->image, driver, ®ion);
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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, ®ion);
|
||||
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, ®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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user