8123330061
The glyph cache is now stored in a CoglAtlas structure instead of the custom atlasing code. This has the advantage that it can share code with the main texture atlas and that it supports reorganizing the atlas when it becomes full. Unlike the texture atlas, the glyph cache can use multiple atlases which would be neccessary if the maximum texture size is reached and we need to create a second texture. Whenever a display list is created it now has to register a callback with the glyph cache so that the display list can be recreated whenever any of the atlases are reorganized. This is needed because the display list directly stores texture coordinates within the atlas texture and they would become invalid when the texture is moved. The ensure_glyphs_for_layout now works in two steps. First it reserves space in the atlas for all of the glyphs. The atlas is created with the DISABLE_MIGRATION flag so that it won't actually copy any textures if any rearranging is needed. Whenever the position is updated for a glyph then it is marked as dirty. After space for all of the glyphs has been reserved it will iterate over all dirty glyphs and redraw them using Cairo. The rendered glyph is then stored in the texture with a sub texture update. The glyphs need to all be set at the right location before starting to create the display list because the display list stores the texture coordinates of the glyph. If any of the glyphs were moved around then the parts of the display list that was created already would become invalid. To make this work, ensure_glyphs_for_layout is now always called before rendering a layout or a layout line.
308 lines
9.0 KiB
C
308 lines
9.0 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* Copyright (C) 2008 OpenedHand
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#include "cogl-pango-glyph-cache.h"
|
|
#include "cogl-pango-private.h"
|
|
#include "cogl/cogl-atlas.h"
|
|
#include "cogl/cogl-callback-list.h"
|
|
|
|
typedef struct _CoglPangoGlyphCacheKey CoglPangoGlyphCacheKey;
|
|
|
|
struct _CoglPangoGlyphCache
|
|
{
|
|
/* Hash table to quickly check whether a particular glyph in a
|
|
particular font is already cached */
|
|
GHashTable *hash_table;
|
|
|
|
/* List of CoglAtlases */
|
|
GSList *atlases;
|
|
|
|
/* List of callbacks to invoke when an atlas is reorganized */
|
|
CoglCallbackList reorganize_callbacks;
|
|
|
|
/* True if some of the glyphs are dirty. This is used as an
|
|
optimization in _cogl_pango_glyph_cache_set_dirty_glyphs to avoid
|
|
iterating the hash table if we know none of them are dirty */
|
|
gboolean has_dirty_glyphs;
|
|
};
|
|
|
|
struct _CoglPangoGlyphCacheKey
|
|
{
|
|
PangoFont *font;
|
|
PangoGlyph glyph;
|
|
};
|
|
|
|
static void
|
|
cogl_pango_glyph_cache_value_free (CoglPangoGlyphCacheValue *value)
|
|
{
|
|
cogl_handle_unref (value->texture);
|
|
g_slice_free (CoglPangoGlyphCacheValue, value);
|
|
}
|
|
|
|
static void
|
|
cogl_pango_glyph_cache_key_free (CoglPangoGlyphCacheKey *key)
|
|
{
|
|
g_object_unref (key->font);
|
|
g_slice_free (CoglPangoGlyphCacheKey, key);
|
|
}
|
|
|
|
static guint
|
|
cogl_pango_glyph_cache_hash_func (gconstpointer key)
|
|
{
|
|
const CoglPangoGlyphCacheKey *cache_key
|
|
= (const CoglPangoGlyphCacheKey *) key;
|
|
|
|
/* Generate a number affected by both the font and the glyph
|
|
number. We can safely directly compare the pointers because the
|
|
key holds a reference to the font so it is not possible that a
|
|
different font will have the same memory address */
|
|
return GPOINTER_TO_UINT (cache_key->font) ^ cache_key->glyph;
|
|
}
|
|
|
|
static gboolean
|
|
cogl_pango_glyph_cache_equal_func (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
const CoglPangoGlyphCacheKey *key_a
|
|
= (const CoglPangoGlyphCacheKey *) a;
|
|
const CoglPangoGlyphCacheKey *key_b
|
|
= (const CoglPangoGlyphCacheKey *) b;
|
|
|
|
/* We can safely directly compare the pointers for the fonts because
|
|
the key holds a reference to the font so it is not possible that
|
|
a different font will have the same memory address */
|
|
return key_a->font == key_b->font
|
|
&& key_a->glyph == key_b->glyph;
|
|
}
|
|
|
|
CoglPangoGlyphCache *
|
|
cogl_pango_glyph_cache_new (void)
|
|
{
|
|
CoglPangoGlyphCache *cache;
|
|
|
|
cache = g_malloc (sizeof (CoglPangoGlyphCache));
|
|
|
|
cache->hash_table = g_hash_table_new_full
|
|
(cogl_pango_glyph_cache_hash_func,
|
|
cogl_pango_glyph_cache_equal_func,
|
|
(GDestroyNotify) cogl_pango_glyph_cache_key_free,
|
|
(GDestroyNotify) cogl_pango_glyph_cache_value_free);
|
|
|
|
cache->atlases = NULL;
|
|
_cogl_callback_list_init (&cache->reorganize_callbacks);
|
|
|
|
cache->has_dirty_glyphs = FALSE;
|
|
|
|
return cache;
|
|
}
|
|
|
|
void
|
|
cogl_pango_glyph_cache_clear (CoglPangoGlyphCache *cache)
|
|
{
|
|
g_slist_foreach (cache->atlases, (GFunc) _cogl_atlas_free, NULL);
|
|
g_slist_free (cache->atlases);
|
|
cache->atlases = NULL;
|
|
cache->has_dirty_glyphs = FALSE;
|
|
|
|
g_hash_table_remove_all (cache->hash_table);
|
|
}
|
|
|
|
void
|
|
cogl_pango_glyph_cache_free (CoglPangoGlyphCache *cache)
|
|
{
|
|
cogl_pango_glyph_cache_clear (cache);
|
|
|
|
g_hash_table_unref (cache->hash_table);
|
|
|
|
_cogl_callback_list_destroy (&cache->reorganize_callbacks);
|
|
|
|
g_free (cache);
|
|
}
|
|
|
|
static void
|
|
cogl_pango_glyph_cache_update_position_cb (void *user_data,
|
|
CoglHandle new_texture,
|
|
const CoglRectangleMapEntry *rect)
|
|
{
|
|
CoglPangoGlyphCacheValue *value = user_data;
|
|
float tex_width, tex_height;
|
|
|
|
if (value->texture)
|
|
cogl_handle_unref (value->texture);
|
|
value->texture = cogl_handle_ref (new_texture);
|
|
|
|
tex_width = cogl_texture_get_width (new_texture);
|
|
tex_height = cogl_texture_get_height (new_texture);
|
|
|
|
value->tx1 = rect->x / tex_width;
|
|
value->ty1 = rect->y / tex_height;
|
|
value->tx2 = (rect->x + value->draw_width) / tex_width;
|
|
value->ty2 = (rect->y + value->draw_height) / tex_height;
|
|
|
|
value->tx_pixel = rect->x;
|
|
value->ty_pixel = rect->y;
|
|
|
|
/* The glyph has changed position so it will need to be redrawn */
|
|
value->dirty = TRUE;
|
|
}
|
|
|
|
static void
|
|
cogl_pango_glyph_cache_reorganize_cb (void *user_data)
|
|
{
|
|
CoglPangoGlyphCache *cache = user_data;
|
|
|
|
_cogl_callback_list_invoke (&cache->reorganize_callbacks);
|
|
}
|
|
|
|
CoglPangoGlyphCacheValue *
|
|
cogl_pango_glyph_cache_lookup (CoglPangoGlyphCache *cache,
|
|
gboolean create,
|
|
PangoFont *font,
|
|
PangoGlyph glyph)
|
|
{
|
|
CoglPangoGlyphCacheKey lookup_key;
|
|
CoglPangoGlyphCacheValue *value;
|
|
|
|
lookup_key.font = font;
|
|
lookup_key.glyph = glyph;
|
|
|
|
value = g_hash_table_lookup (cache->hash_table, &lookup_key);
|
|
|
|
if (create && value == NULL)
|
|
{
|
|
CoglPangoGlyphCacheKey *key;
|
|
PangoRectangle ink_rect;
|
|
CoglAtlas *atlas = NULL;
|
|
GSList *l;
|
|
|
|
pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
|
|
pango_extents_to_pixels (&ink_rect, NULL);
|
|
|
|
value = g_slice_new (CoglPangoGlyphCacheValue);
|
|
value->texture = COGL_INVALID_HANDLE;
|
|
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->dirty = TRUE;
|
|
|
|
/* Look for an atlas that can reserve the space */
|
|
for (l = cache->atlases; l; l = l->next)
|
|
if (_cogl_atlas_reserve_space (l->data,
|
|
ink_rect.width + 1, ink_rect.height + 1,
|
|
value))
|
|
{
|
|
atlas = l->data;
|
|
break;
|
|
}
|
|
|
|
/* If we couldn't find one then start a new atlas */
|
|
if (atlas == NULL)
|
|
{
|
|
atlas = _cogl_atlas_new (COGL_PIXEL_FORMAT_A_8,
|
|
TRUE,
|
|
cogl_pango_glyph_cache_update_position_cb);
|
|
/* If we still can't reserve space then something has gone
|
|
seriously wrong so we'll just give up */
|
|
if (!_cogl_atlas_reserve_space (atlas,
|
|
ink_rect.width + 1,
|
|
ink_rect.height + 1, value))
|
|
{
|
|
_cogl_atlas_free (atlas);
|
|
cogl_pango_glyph_cache_value_free (value);
|
|
return NULL;
|
|
}
|
|
|
|
_cogl_atlas_add_reorganize_callback
|
|
(atlas, cogl_pango_glyph_cache_reorganize_cb, cache);
|
|
|
|
cache->atlases = g_slist_prepend (cache->atlases, atlas);
|
|
}
|
|
|
|
key = g_slice_new (CoglPangoGlyphCacheKey);
|
|
key->font = g_object_ref (font);
|
|
key->glyph = glyph;
|
|
|
|
g_hash_table_insert (cache->hash_table, key, value);
|
|
|
|
cache->has_dirty_glyphs = TRUE;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static void
|
|
_cogl_pango_glyph_cache_set_dirty_glyphs_cb (gpointer key_ptr,
|
|
gpointer value_ptr,
|
|
gpointer user_data)
|
|
{
|
|
CoglPangoGlyphCacheKey *key = key_ptr;
|
|
CoglPangoGlyphCacheValue *value = value_ptr;
|
|
CoglPangoGlyphCacheDirtyFunc func = user_data;
|
|
|
|
if (value->dirty)
|
|
{
|
|
func (key->font, key->glyph, value);
|
|
|
|
value->dirty = FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
_cogl_pango_glyph_cache_set_dirty_glyphs (CoglPangoGlyphCache *cache,
|
|
CoglPangoGlyphCacheDirtyFunc func)
|
|
{
|
|
/* If we know that there are no dirty glyphs then we can shortcut
|
|
out early */
|
|
if (!cache->has_dirty_glyphs)
|
|
return;
|
|
|
|
g_hash_table_foreach (cache->hash_table,
|
|
_cogl_pango_glyph_cache_set_dirty_glyphs_cb,
|
|
func);
|
|
|
|
cache->has_dirty_glyphs = FALSE;
|
|
}
|
|
|
|
void
|
|
_cogl_pango_glyph_cache_add_reorganize_callback (CoglPangoGlyphCache *cache,
|
|
CoglCallbackListFunc func,
|
|
void *user_data)
|
|
{
|
|
_cogl_callback_list_add (&cache->reorganize_callbacks, func, user_data);
|
|
}
|
|
|
|
void
|
|
_cogl_pango_glyph_cache_remove_reorganize_callback (CoglPangoGlyphCache *cache,
|
|
CoglCallbackListFunc func,
|
|
void *user_data)
|
|
{
|
|
_cogl_callback_list_remove (&cache->reorganize_callbacks, func, user_data);
|
|
}
|