From d03fbf9f64da5448c6ab625049489474d0b78c71 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Wed, 4 Aug 2010 18:05:21 +0100 Subject: [PATCH] cogl-pango: Use a CoglAtlas to maintain the glyph cache 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. --- clutter/cogl/pango/cogl-pango-glyph-cache.c | 365 +++++++++----------- clutter/cogl/pango/cogl-pango-glyph-cache.h | 46 ++- clutter/cogl/pango/cogl-pango-render.c | 222 ++++++++---- 3 files changed, 338 insertions(+), 295 deletions(-) diff --git a/clutter/cogl/pango/cogl-pango-glyph-cache.c b/clutter/cogl/pango/cogl-pango-glyph-cache.c index df26f672f..0bd81a1a9 100644 --- a/clutter/cogl/pango/cogl-pango-glyph-cache.c +++ b/clutter/cogl/pango/cogl-pango-glyph-cache.c @@ -29,28 +29,27 @@ #include "cogl-pango-glyph-cache.h" #include "cogl-pango-private.h" - -/* Minimum width/height for each texture */ -#define MIN_TEXTURE_SIZE 256 -/* All glyph with heights within this margin from each other can be - put in the same band */ -#define BAND_HEIGHT_ROUND 4 +#include "cogl/cogl-atlas.h" +#include "cogl/cogl-callback-list.h" typedef struct _CoglPangoGlyphCacheKey CoglPangoGlyphCacheKey; -typedef struct _CoglPangoGlyphCacheTexture CoglPangoGlyphCacheTexture; -typedef struct _CoglPangoGlyphCacheBand CoglPangoGlyphCacheBand; struct _CoglPangoGlyphCache { /* Hash table to quickly check whether a particular glyph in a particular font is already cached */ - GHashTable *hash_table; + GHashTable *hash_table; - /* List of textures */ - CoglPangoGlyphCacheTexture *textures; + /* List of CoglAtlases */ + GSList *atlases; - /* List of horizontal bands of glyphs */ - CoglPangoGlyphCacheBand *bands; + /* 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 @@ -59,48 +58,6 @@ struct _CoglPangoGlyphCacheKey PangoGlyph glyph; }; -/* Represents one texture that will be used to store glyphs. The - texture is divided into horizontal bands which all contain glyphs - of approximatly the same height */ -struct _CoglPangoGlyphCacheTexture -{ - /* The width and height of the texture which should always be a - power of two. This can vary so that glyphs larger than - MIN_TEXTURE_SIZE can use a bigger texture */ - int texture_size; - - /* The remaining vertical space not taken up by any bands */ - int space_remaining; - - /* The actual texture */ - CoglHandle texture; - - CoglPangoGlyphCacheTexture *next; -}; - -/* Represents one horizontal band of a texture. Each band contains - glyphs of a similar height */ -struct _CoglPangoGlyphCacheBand -{ - /* The y position of the top of the band */ - int top; - - /* The height of the band */ - int height; - - /* The remaining horizontal space not taken up by any glyphs */ - int space_remaining; - - /* The size of the texture. Needed to calculate texture - coordinates */ - int texture_size; - - /* The texture containing this band */ - CoglHandle texture; - - CoglPangoGlyphCacheBand *next; -}; - static void cogl_pango_glyph_cache_value_free (CoglPangoGlyphCacheValue *value) { @@ -144,34 +101,6 @@ cogl_pango_glyph_cache_equal_func (gconstpointer a, && key_a->glyph == key_b->glyph; } -static void -cogl_pango_glyph_cache_free_textures (CoglPangoGlyphCacheTexture *node) -{ - CoglPangoGlyphCacheTexture *next; - - while (node) - { - next = node->next; - cogl_handle_unref (node->texture); - g_slice_free (CoglPangoGlyphCacheTexture, node); - node = next; - } -} - -static void -cogl_pango_glyph_cache_free_bands (CoglPangoGlyphCacheBand *node) -{ - CoglPangoGlyphCacheBand *next; - - while (node) - { - next = node->next; - cogl_handle_unref (node->texture); - g_slice_free (CoglPangoGlyphCacheBand, node); - node = next; - } -} - CoglPangoGlyphCache * cogl_pango_glyph_cache_new (void) { @@ -185,8 +114,10 @@ cogl_pango_glyph_cache_new (void) (GDestroyNotify) cogl_pango_glyph_cache_key_free, (GDestroyNotify) cogl_pango_glyph_cache_value_free); - cache->textures = NULL; - cache->bands = NULL; + cache->atlases = NULL; + _cogl_callback_list_init (&cache->reorganize_callbacks); + + cache->has_dirty_glyphs = FALSE; return cache; } @@ -194,10 +125,10 @@ cogl_pango_glyph_cache_new (void) void cogl_pango_glyph_cache_clear (CoglPangoGlyphCache *cache) { - cogl_pango_glyph_cache_free_textures (cache->textures); - cache->textures = NULL; - cogl_pango_glyph_cache_free_bands (cache->bands); - cache->bands = NULL; + 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); } @@ -209,144 +140,168 @@ cogl_pango_glyph_cache_free (CoglPangoGlyphCache *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, - PangoFont *font, - PangoGlyph glyph) + gboolean create, + PangoFont *font, + PangoGlyph glyph) { - CoglPangoGlyphCacheKey key; - - key.font = font; - key.glyph = glyph; - - return (CoglPangoGlyphCacheValue *) - g_hash_table_lookup (cache->hash_table, &key); -} - -CoglPangoGlyphCacheValue * -cogl_pango_glyph_cache_set (CoglPangoGlyphCache *cache, - PangoFont *font, - PangoGlyph glyph, - gconstpointer pixels, - int width, - int height, - int stride, - int draw_x, - int draw_y) -{ - int band_height; - CoglPangoGlyphCacheBand *band; - CoglPangoGlyphCacheKey *key; + CoglPangoGlyphCacheKey lookup_key; CoglPangoGlyphCacheValue *value; - /* Reserve an extra pixel gap around the glyph so that it can pull - in blank pixels when linear filtering is enabled */ - width++; - height++; + lookup_key.font = font; + lookup_key.glyph = glyph; - /* Round the height up to the nearest multiple of - BAND_HEIGHT_ROUND */ - band_height = (height + BAND_HEIGHT_ROUND - 1) & ~(BAND_HEIGHT_ROUND - 1); + value = g_hash_table_lookup (cache->hash_table, &lookup_key); - /* Look for a band with the same height and enough width available */ - for (band = cache->bands; - band && (band->height != band_height || band->space_remaining < width); - band = band->next); - if (band == NULL) + if (create && value == NULL) { - CoglPangoGlyphCacheTexture *texture; + CoglPangoGlyphCacheKey *key; + PangoRectangle ink_rect; + CoglAtlas *atlas = NULL; + GSList *l; - /* Look for a texture with enough vertical space left for a band - with this height */ - for (texture = cache->textures; - texture && (texture->space_remaining < band_height - || texture->texture_size < width); - texture = texture->next); - if (texture == NULL) - { - guchar *clear_data; + pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL); + pango_extents_to_pixels (&ink_rect, NULL); - /* Allocate a new texture that is the nearest power of two - greater than the band height or the minimum size, - whichever is lower */ - texture = g_slice_new (CoglPangoGlyphCacheTexture); + 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; - texture->texture_size = MIN_TEXTURE_SIZE; - while (texture->texture_size < band_height || - texture->texture_size < width) + /* 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)) { - texture->texture_size *= 2; + _cogl_atlas_free (atlas); + cogl_pango_glyph_cache_value_free (value); + return NULL; } - /* Allocate an empty buffer to clear the texture */ - clear_data = - g_malloc0 (texture->texture_size * texture->texture_size); + _cogl_atlas_add_reorganize_callback + (atlas, cogl_pango_glyph_cache_reorganize_cb, cache); - texture->texture = - cogl_texture_new_from_data (texture->texture_size, - texture->texture_size, - COGL_TEXTURE_NONE, - COGL_PIXEL_FORMAT_A_8, - COGL_PIXEL_FORMAT_A_8, - texture->texture_size, - clear_data); + cache->atlases = g_slist_prepend (cache->atlases, atlas); + } - g_free (clear_data); + key = g_slice_new (CoglPangoGlyphCacheKey); + key->font = g_object_ref (font); + key->glyph = glyph; - texture->space_remaining = texture->texture_size; - texture->next = cache->textures; - cache->textures = texture; - } + g_hash_table_insert (cache->hash_table, key, value); - band = g_slice_new (CoglPangoGlyphCacheBand); - band->top = texture->texture_size - texture->space_remaining; - band->height = band_height; - band->space_remaining = texture->texture_size; - band->texture = cogl_handle_ref (texture->texture); - band->texture_size = texture->texture_size; - band->next = cache->bands; - cache->bands = band; - texture->space_remaining -= band_height; + cache->has_dirty_glyphs = TRUE; } - band->space_remaining -= width; - - width--; - height--; - - cogl_texture_set_region (band->texture, - 0, 0, - band->space_remaining, - band->top, - width, height, - width, height, - COGL_PIXEL_FORMAT_A_8, - stride, - pixels); - - key = g_slice_new (CoglPangoGlyphCacheKey); - key->font = g_object_ref (font); - key->glyph = glyph; - - value = g_slice_new (CoglPangoGlyphCacheValue); - value->texture = cogl_handle_ref (band->texture); - value->tx1 = (float)(band->space_remaining) - / band->texture_size; - value->tx2 = (float)(band->space_remaining + width) - / band->texture_size; - value->ty1 = (float)(band->top) - / band->texture_size; - value->ty2 = (float)(band->top + height) - / band->texture_size; - value->draw_x = draw_x; - value->draw_y = draw_y; - value->draw_width = width; - value->draw_height = height; - - g_hash_table_insert (cache->hash_table, key, value); - 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); +} diff --git a/clutter/cogl/pango/cogl-pango-glyph-cache.h b/clutter/cogl/pango/cogl-pango-glyph-cache.h index ab5265afa..134239cf5 100644 --- a/clutter/cogl/pango/cogl-pango-glyph-cache.h +++ b/clutter/cogl/pango/cogl-pango-glyph-cache.h @@ -26,6 +26,7 @@ #include #include +#include #include G_BEGIN_DECLS @@ -37,17 +38,28 @@ struct _CoglPangoGlyphCacheValue { CoglHandle texture; - float tx1; - float ty1; - float tx2; - float ty2; + float tx1; + float ty1; + float tx2; + float ty2; + + int tx_pixel; + int ty_pixel; int draw_x; int draw_y; int draw_width; int draw_height; + + /* This will be set to TRUE when the glyph atlas is reorganized + which means the glyph will need to be redrawn */ + gboolean dirty; }; +typedef void (* CoglPangoGlyphCacheDirtyFunc) (PangoFont *font, + PangoGlyph glyph, + CoglPangoGlyphCacheValue *value); + CoglPangoGlyphCache * cogl_pango_glyph_cache_new (void); @@ -56,23 +68,27 @@ cogl_pango_glyph_cache_free (CoglPangoGlyphCache *cache); CoglPangoGlyphCacheValue * cogl_pango_glyph_cache_lookup (CoglPangoGlyphCache *cache, + gboolean create, PangoFont *font, PangoGlyph glyph); -CoglPangoGlyphCacheValue * -cogl_pango_glyph_cache_set (CoglPangoGlyphCache *cache, - PangoFont *font, - PangoGlyph glyph, - gconstpointer pixels, - int width, - int height, - int stride, - int draw_x, - int draw_y); - void cogl_pango_glyph_cache_clear (CoglPangoGlyphCache *cache); +void +_cogl_pango_glyph_cache_add_reorganize_callback (CoglPangoGlyphCache *cache, + CoglCallbackListFunc func, + void *user_data); + +void +_cogl_pango_glyph_cache_remove_reorganize_callback (CoglPangoGlyphCache *cache, + CoglCallbackListFunc func, + void *user_data); + +void +_cogl_pango_glyph_cache_set_dirty_glyphs (CoglPangoGlyphCache *cache, + CoglPangoGlyphCacheDirtyFunc func); + G_END_DECLS #endif /* __COGL_PANGO_GLYPH_CACHE_H__ */ diff --git a/clutter/cogl/pango/cogl-pango-render.c b/clutter/cogl/pango/cogl-pango-render.c index fc48c40ad..5a554a268 100644 --- a/clutter/cogl/pango/cogl-pango-render.c +++ b/clutter/cogl/pango/cogl-pango-render.c @@ -66,6 +66,7 @@ typedef struct _CoglPangoRendererQdata CoglPangoRendererQdata; cache the VBO and to detect changes to the layout */ struct _CoglPangoRendererQdata { + CoglPangoRenderer *renderer; /* The cache of the geometry for the layout */ CoglPangoDisplayList *display_list; /* A reference to the first line of the layout. This is just used to @@ -73,6 +74,9 @@ struct _CoglPangoRendererQdata PangoLayoutLine *first_line; }; +static void +_cogl_pango_ensure_glyph_cache_for_layout_line (PangoLayoutLine *line); + static void cogl_pango_renderer_draw_glyph (CoglPangoRenderer *priv, CoglPangoGlyphCacheValue *cache_value, @@ -202,10 +206,25 @@ cogl_pango_render_get_qdata_key (void) } static void -cogl_pango_render_qdata_destroy (CoglPangoRendererQdata *qdata) +cogl_pango_render_qdata_forget_display_list (CoglPangoRendererQdata *qdata) { if (qdata->display_list) - _cogl_pango_display_list_free (qdata->display_list); + { + _cogl_pango_glyph_cache_remove_reorganize_callback + (qdata->renderer->glyph_cache, + (CoglCallbackListFunc) cogl_pango_render_qdata_forget_display_list, + qdata); + + _cogl_pango_display_list_free (qdata->display_list); + + qdata->display_list = NULL; + } +} + +static void +cogl_pango_render_qdata_destroy (CoglPangoRendererQdata *qdata) +{ + cogl_pango_render_qdata_forget_display_list (qdata); if (qdata->first_line) pango_layout_line_unref (qdata->first_line); g_slice_free (CoglPangoRendererQdata, qdata); @@ -245,6 +264,7 @@ cogl_pango_render_layout_subpixel (PangoLayout *layout, if (qdata == NULL) { qdata = g_slice_new0 (CoglPangoRendererQdata); + qdata->renderer = priv; g_object_set_qdata_full (G_OBJECT (layout), cogl_pango_render_get_qdata_key (), qdata, @@ -257,15 +277,21 @@ cogl_pango_render_layout_subpixel (PangoLayout *layout, http://mail.gnome.org/archives/gtk-i18n-list/2009-May/msg00019.html */ if (qdata->display_list && qdata->first_line && qdata->first_line->layout != layout) - { - _cogl_pango_display_list_free (qdata->display_list); - qdata->display_list = NULL; - } + cogl_pango_render_qdata_forget_display_list (qdata); if (qdata->display_list == NULL) { + cogl_pango_ensure_glyph_cache_for_layout (layout); + qdata->display_list = _cogl_pango_display_list_new (); + /* Register for notification of when the glyph cache changes so + we can rebuild the display list */ + _cogl_pango_glyph_cache_add_reorganize_callback + (priv->glyph_cache, + (CoglCallbackListFunc) cogl_pango_render_qdata_forget_display_list, + qdata); + priv->display_list = qdata->display_list; pango_renderer_draw_layout (PANGO_RENDERER (priv), layout, 0, 0); priv->display_list = NULL; @@ -346,6 +372,8 @@ cogl_pango_render_layout_line (PangoLayoutLine *line, priv->display_list = _cogl_pango_display_list_new (); + _cogl_pango_ensure_glyph_cache_for_layout_line (line); + pango_renderer_draw_layout_line (PANGO_RENDERER (priv), line, x, y); _cogl_pango_display_list_render (priv->display_list, @@ -390,105 +418,144 @@ _cogl_pango_renderer_get_use_mipmapping (CoglPangoRenderer *renderer) static CoglPangoGlyphCacheValue * cogl_pango_renderer_get_cached_glyph (PangoRenderer *renderer, + gboolean create, PangoFont *font, PangoGlyph glyph) { CoglPangoRenderer *priv = COGL_PANGO_RENDERER (renderer); - CoglPangoGlyphCacheValue *value; - value = cogl_pango_glyph_cache_lookup (priv->glyph_cache, font, glyph); - if (value == NULL) + return cogl_pango_glyph_cache_lookup (priv->glyph_cache, create, font, glyph); +} + +static void +cogl_pango_renderer_set_dirty_glyph (PangoFont *font, + PangoGlyph glyph, + CoglPangoGlyphCacheValue *value) +{ + cairo_surface_t *surface; + cairo_t *cr; + cairo_scaled_font_t *scaled_font; + cairo_glyph_t cairo_glyph; + + COGL_NOTE (PANGO, "redrawing glyph %i", glyph); + + surface = cairo_image_surface_create (CAIRO_FORMAT_A8, + value->draw_width, + value->draw_height); + cr = cairo_create (surface); + + scaled_font = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font)); + cairo_set_scaled_font (cr, scaled_font); + + cairo_glyph.x = -value->draw_x; + cairo_glyph.y = -value->draw_y; + /* The PangoCairo glyph numbers directly map to Cairo glyph + numbers */ + cairo_glyph.index = glyph; + cairo_show_glyphs (cr, &cairo_glyph, 1); + + cairo_destroy (cr); + cairo_surface_flush (surface); + + /* Copy the glyph to the texture */ + cogl_texture_set_region (value->texture, + 0, /* src_x */ + 0, /* src_y */ + value->tx_pixel, /* dst_x */ + value->ty_pixel, /* dst_y */ + value->draw_width, /* dst_width */ + value->draw_height, /* dst_height */ + value->draw_width, /* width */ + value->draw_height, /* height */ + COGL_PIXEL_FORMAT_A_8, + cairo_image_surface_get_stride (surface), + cairo_image_surface_get_data (surface)); + + cairo_surface_destroy (surface); +} + +static void +_cogl_pango_ensure_glyph_cache_for_layout_line_internal (PangoLayoutLine *line) +{ + PangoContext *context; + PangoRenderer *renderer; + GSList *l; + + context = pango_layout_get_context (line->layout); + renderer = + PANGO_RENDERER (cogl_pango_get_renderer_from_context (context)); + + for (l = line->runs; l; l = l->next) { - cairo_surface_t *surface; - cairo_t *cr; - cairo_scaled_font_t *scaled_font; - PangoRectangle ink_rect; - cairo_glyph_t cairo_glyph; + PangoLayoutRun *run = l->data; + PangoGlyphString *glyphs = run->glyphs; + int i; - pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL); - pango_extents_to_pixels (&ink_rect, NULL); + for (i = 0; i < glyphs->num_glyphs; i++) + { + PangoGlyphInfo *gi = &glyphs->glyphs[i]; - surface = cairo_image_surface_create (CAIRO_FORMAT_A8, - ink_rect.width, - ink_rect.height); - cr = cairo_create (surface); - - scaled_font = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font)); - cairo_set_scaled_font (cr, scaled_font); - - cairo_glyph.x = -ink_rect.x; - cairo_glyph.y = -ink_rect.y; - /* The PangoCairo glyph numbers directly map to Cairo glyph - numbers */ - cairo_glyph.index = glyph; - cairo_show_glyphs (cr, &cairo_glyph, 1); - - cairo_destroy (cr); - cairo_surface_flush (surface); - - /* Copy the glyph to the cache */ - value = - cogl_pango_glyph_cache_set (priv->glyph_cache, font, glyph, - cairo_image_surface_get_data (surface), - cairo_image_surface_get_width (surface), - cairo_image_surface_get_height (surface), - cairo_image_surface_get_stride (surface), - ink_rect.x, ink_rect.y); - - cairo_surface_destroy (surface); - - COGL_NOTE (PANGO, "cache fail %i", glyph); - } - else - { - COGL_NOTE (PANGO, "cache success %i", glyph); + /* If the glyph isn't cached then this will reserve + space for it now. We won't actually draw the glyph + yet because reserving space could cause all of the + other glyphs to be moved so we might as well redraw + them all later once we know that the position is + settled */ + cogl_pango_renderer_get_cached_glyph (renderer, TRUE, + run->item->analysis.font, + gi->glyph); + } } +} - return value; +static void +_cogl_pango_ensure_glyph_cache_for_layout_line (PangoLayoutLine *line) +{ + PangoContext *context; + CoglPangoRenderer *priv; + + context = pango_layout_get_context (line->layout); + priv = cogl_pango_get_renderer_from_context (context); + + _cogl_pango_ensure_glyph_cache_for_layout_line_internal (line); + + /* Now that we know all of the positions are settled we'll fill in + any dirty glyphs */ + _cogl_pango_glyph_cache_set_dirty_glyphs + (priv->glyph_cache, cogl_pango_renderer_set_dirty_glyph); } void cogl_pango_ensure_glyph_cache_for_layout (PangoLayout *layout) { - PangoContext *context; - PangoRenderer *renderer; + PangoContext *context; + CoglPangoRenderer *priv; PangoLayoutIter *iter; + context = pango_layout_get_context (layout); + priv = cogl_pango_get_renderer_from_context (context); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); if ((iter = pango_layout_get_iter (layout)) == NULL) return; - context = pango_layout_get_context (layout); - renderer = - PANGO_RENDERER (cogl_pango_get_renderer_from_context (context)); - do { PangoLayoutLine *line; - GSList *l; line = pango_layout_iter_get_line_readonly (iter); - for (l = line->runs; l; l = l->next) - { - PangoLayoutRun *run = l->data; - PangoGlyphString *glyphs = run->glyphs; - int i; - - for (i = 0; i < glyphs->num_glyphs; i++) - { - PangoGlyphInfo *gi = &glyphs->glyphs[i]; - - cogl_pango_renderer_get_cached_glyph (renderer, - run->item->analysis.font, - gi->glyph); - } - } + _cogl_pango_ensure_glyph_cache_for_layout_line_internal (line); } while (pango_layout_iter_next_line (iter)); pango_layout_iter_free (iter); + + /* Now that we know all of the positions are settled we'll fill in + any dirty glyphs */ + _cogl_pango_glyph_cache_set_dirty_glyphs + (priv->glyph_cache, cogl_pango_renderer_set_dirty_glyph); } static void @@ -668,13 +735,18 @@ cogl_pango_renderer_draw_glyphs (PangoRenderer *renderer, } else { - /* Get the texture containing the glyph. This will create - the cache entry if there isn't already one */ + /* Get the texture containing the glyph */ cache_value = cogl_pango_renderer_get_cached_glyph (renderer, + FALSE, font, gi->glyph); + /* cogl_pango_ensure_glyph_cache_for_layout should always be + called before rendering a layout so we should never have + a dirty glyph here */ + g_assert (cache_value == NULL || !cache_value->dirty); + if (cache_value == NULL) { cogl_pango_renderer_draw_box (renderer,