From bb9dccd7735f3bcbccc53e5f75ac21f26bc11649 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 31 Jul 2021 11:41:08 -0400 Subject: [PATCH 1/5] gsk: Mark color glyphs Steal a bit from PangoGlyphVisAttr to mark glyphs that have color. --- gsk/gskrendernodeimpl.c | 66 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 9a9705d0fd..9958b7e33c 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -4377,6 +4377,15 @@ gsk_text_node_draw (GskRenderNode *node, cairo_restore (cr); } +/* We steal one of the bits in PangoGlyphVisAttr */ + +G_STATIC_ASSERT (sizeof (PangoGlyphVisAttr) == 4); + +#define COLOR_GLYPH_BIT 2 +#define GLYPH_IS_COLOR(g) (((*(guint32*)&(g)->attr) & COLOR_GLYPH_BIT) != 0) +#define GLYPH_SET_COLOR(g) (*(guint32*)(&(g)->attr) |= COLOR_GLYPH_BIT) +#define GLYPH_CLEAR_COLOR(g) (*(guint32*)(&(g)->attr) &= ~COLOR_GLYPH_BIT) + static void gsk_text_node_diff (GskRenderNode *node1, GskRenderNode *node2, @@ -4401,7 +4410,8 @@ gsk_text_node_diff (GskRenderNode *node1, info1->geometry.width == info2->geometry.width && info1->geometry.x_offset == info2->geometry.x_offset && info1->geometry.y_offset == info2->geometry.y_offset && - info1->attr.is_cluster_start == info2->attr.is_cluster_start) + info1->attr.is_cluster_start == info2->attr.is_cluster_start && + GLYPH_IS_COLOR (info1) == GLYPH_IS_COLOR (info2)) continue; gsk_render_node_diff_impossible (node1, node2, region); @@ -4431,6 +4441,52 @@ font_has_color_glyphs (const PangoFont *font) return has_color; } +static gboolean +glyph_has_color (FT_Face face, + guint glyph) +{ + FT_Error error; + + error = FT_Load_Glyph (face, glyph, FT_LOAD_COLOR); + if (error != 0) + return FALSE; + + error = FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL); + if (error != 0) + return FALSE; + + if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) + return TRUE; + + return FALSE; +} + +static void +mark_color_glyphs (const PangoFont *font, + PangoGlyphInfo *glyphs, + int num_glyphs) +{ + cairo_scaled_font_t *scaled_font; + FT_Face ft_face; + + scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font); + if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_FT) + return; + + ft_face = cairo_ft_scaled_font_lock_face (scaled_font); + if (!FT_HAS_COLOR (ft_face)) + goto out; + + for (int i = 0; i < num_glyphs; i++) + { + if (glyph_has_color (ft_face, glyphs[i].glyph)) + GLYPH_SET_COLOR (&glyphs[i]); + } + +out: + cairo_ft_scaled_font_unlock_face (scaled_font); +} + /** * gsk_text_node_new: * @font: the `PangoFont` containing the glyphs @@ -4476,9 +4532,15 @@ gsk_text_node_new (PangoFont *font, for (int i = 0; i < glyphs->num_glyphs; i++) { if (glyphs->glyphs[i].glyph != PANGO_GLYPH_EMPTY) - self->glyphs[self->num_glyphs++] = glyphs->glyphs[i]; + { + self->glyphs[self->num_glyphs] = glyphs->glyphs[i]; + GLYPH_CLEAR_COLOR (&self->glyphs[self->num_glyphs]); + self->num_glyphs++; + } } + mark_color_glyphs (font, self->glyphs, self->num_glyphs); + graphene_rect_init (&node->bounds, offset->x + ink_rect.x - 1, offset->y + ink_rect.y - 1, From 7e8f58685912e143abb6368f6c513a9e75b5e5c9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 31 Jul 2021 11:41:43 -0400 Subject: [PATCH 2/5] ngl: Use per-glyph color information Decide per-glyph whether we need color nor not. Fixes: #4141 --- gsk/ngl/gsknglrenderjob.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/gsk/ngl/gsknglrenderjob.c b/gsk/ngl/gsknglrenderjob.c index 87c407cd9b..532157d1be 100644 --- a/gsk/ngl/gsknglrenderjob.c +++ b/gsk/ngl/gsknglrenderjob.c @@ -2835,6 +2835,9 @@ compute_phase_and_pos (float value, float *pos) } } +#define COLOR_GLYPH_BIT 2 +#define GLYPH_IS_COLOR(g) (((*(guint32*)&(g)->attr) & COLOR_GLYPH_BIT) != 0) + static inline void gsk_ngl_render_job_visit_text_node (GskNglRenderJob *job, const GskRenderNode *node, @@ -2855,7 +2858,9 @@ gsk_ngl_render_job_visit_text_node (GskNglRenderJob *job, guint last_texture = 0; GskNglDrawVertex *vertices; guint used = 0; - guint16 c[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE }; + guint16 nc[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE }; + guint16 cc[4]; + const guint16 *c; const PangoGlyphInfo *gi; guint i; int yshift; @@ -2864,16 +2869,11 @@ gsk_ngl_render_job_visit_text_node (GskNglRenderJob *job, if (num_glyphs == 0) return; - /* If the font has color glyphs, we don't need to recolor anything. - * We tell the shader by setting the color to vec4(-1). - */ - if (force_color || !gsk_text_node_has_color_glyphs (node)) - { - if (RGBA_IS_CLEAR (color)) - return; + if ((force_color || !gsk_text_node_has_color_glyphs (node)) && + RGBA_IS_CLEAR (color)) + return; - rgba_to_half (color, c); - } + rgba_to_half (color, cc); lookup.font = (PangoFont *)font; lookup.scale = (guint) (text_scale * 1024); @@ -2897,6 +2897,14 @@ gsk_ngl_render_job_visit_text_node (GskNglRenderJob *job, lookup.glyph = gi->glyph; + /* If the glyph has color, we don't need to recolor anything. + * We tell the shader by setting the color to vec4(-1). + */ + if (!force_color && GLYPH_IS_COLOR (gi)) + c = nc; + else + c = cc; + cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE; lookup.xshift = compute_phase_and_pos (x + cx, &cx); From a7a2dbc3ea90a3086e3b11db81fac6dd860ae7a5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 31 Jul 2021 12:23:50 -0400 Subject: [PATCH 3/5] gsk: Cache per-glyph color information We don't really want to load the bitmaps every time we create a render node, so do it once and cache the information on the PangoFont. --- gsk/gskrendernodeimpl.c | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 9958b7e33c..c44ce88784 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -4461,6 +4461,46 @@ glyph_has_color (FT_Face face, return FALSE; } +static GHashTable * +ensure_color_glyph_cache (const PangoFont *font) +{ + GHashTable *cache; + + cache = (GHashTable *) g_object_get_data (G_OBJECT (font), "gsk-color-glyph-cache"); + if (!cache) + { + cache = g_hash_table_new (NULL, NULL); + g_object_set_data_full (G_OBJECT (font), "gsk-color-glyph-cache", + cache, (GDestroyNotify) g_hash_table_unref); + } + + return cache; +} + +static inline gboolean +lookup_color_glyph_cache (GHashTable *cache, + PangoGlyph glyph, + gboolean *has_color) +{ + gpointer value; + + if (g_hash_table_lookup_extended (cache, GUINT_TO_POINTER (glyph), NULL, &value)) + { + *has_color = GPOINTER_TO_UINT (value); + return TRUE; + } + + return FALSE; +} + +static inline void +insert_color_glyph_cache (GHashTable *cache, + PangoGlyph glyph, + gboolean has_color) +{ + g_hash_table_insert (cache, GUINT_TO_POINTER (glyph), GUINT_TO_POINTER (has_color)); +} + static void mark_color_glyphs (const PangoFont *font, PangoGlyphInfo *glyphs, @@ -4468,6 +4508,7 @@ mark_color_glyphs (const PangoFont *font, { cairo_scaled_font_t *scaled_font; FT_Face ft_face; + GHashTable *cache; scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font); if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_FT) @@ -4477,9 +4518,19 @@ mark_color_glyphs (const PangoFont *font, if (!FT_HAS_COLOR (ft_face)) goto out; + cache = ensure_color_glyph_cache (font); + for (int i = 0; i < num_glyphs; i++) { - if (glyph_has_color (ft_face, glyphs[i].glyph)) + gboolean has_color; + + if (!lookup_color_glyph_cache (cache, glyphs[i].glyph, &has_color)) + { + has_color = glyph_has_color (ft_face, glyphs[i].glyph); + insert_color_glyph_cache (cache, glyphs[i].glyph, has_color); + } + + if (has_color) GLYPH_SET_COLOR (&glyphs[i]); } From 0bf46b48456b97489d0e7cf3228746a8e54c9aeb Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 31 Jul 2021 15:22:49 -0400 Subject: [PATCH 4/5] Bump the harfbuzz dep We need 2.1.0, which has the hb_ot_color apis that we are going to use in the next commit. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5a19806e55..f2aef2f646 100644 --- a/meson.build +++ b/meson.build @@ -396,7 +396,7 @@ pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req, default_options: ['png=enabled', 'jpeg=enabled', 'builtin_loaders=png,jpeg', 'man=false']) epoxy_dep = dependency('epoxy', version: epoxy_req, fallback: ['libepoxy', 'libepoxy_dep']) -harfbuzz_dep = dependency('harfbuzz', version: '>= 0.9', required: false, +harfbuzz_dep = dependency('harfbuzz', version: '>= 2.1.0', required: false, fallback: ['harfbuzz', 'libharfbuzz_dep'], default_options: ['coretext=enabled']) xkbdep = dependency('xkbcommon', version: xkbcommon_req, required: wayland_enabled) From 915c229396e0127f84d6c21ed8a409326e5cd6b6 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 31 Jul 2021 15:22:14 -0400 Subject: [PATCH 5/5] gsk: Use harfbuzz for color fonts harfbuzz has all the information we need, so we can avoid poking directly at freetype apis. Also drop the caching of color glyph information until it turns out to be a problem. --- gsk/gskrendernodeimpl.c | 150 ++++++++++++---------------------------- 1 file changed, 45 insertions(+), 105 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index c44ce88784..5e07a8f92a 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -30,7 +30,7 @@ #include "gdk/gdktextureprivate.h" #include "gdk/gdk-private.h" -#include +#include static inline void gsk_cairo_rectangle (cairo_t *cr, @@ -4425,119 +4425,45 @@ gsk_text_node_diff (GskRenderNode *node1, } static gboolean -font_has_color_glyphs (const PangoFont *font) +font_has_color_glyphs (PangoFont *font) { - cairo_scaled_font_t *scaled_font; - gboolean has_color = FALSE; + hb_face_t *face = hb_font_get_face (pango_font_get_hb_font (font)); - scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font); - if (cairo_scaled_font_get_type (scaled_font) == CAIRO_FONT_TYPE_FT) - { - FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font); - has_color = (FT_HAS_COLOR (ft_face) != 0); - cairo_ft_scaled_font_unlock_face (scaled_font); - } - - return has_color; + return hb_ot_color_has_layers (face) || + hb_ot_color_has_png (face) || + hb_ot_color_has_svg (face); } static gboolean -glyph_has_color (FT_Face face, - guint glyph) +glyph_has_color (PangoFont *font, + guint glyph) { - FT_Error error; + hb_font_t *hb_font = pango_font_get_hb_font (font); + hb_face_t *face = hb_font_get_face (hb_font); + hb_blob_t *blob; - error = FT_Load_Glyph (face, glyph, FT_LOAD_COLOR); - if (error != 0) - return FALSE; - - error = FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL); - if (error != 0) - return FALSE; - - if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) + if (hb_ot_color_glyph_get_layers (face, glyph, 0, NULL, NULL) > 0) return TRUE; - return FALSE; -} - -static GHashTable * -ensure_color_glyph_cache (const PangoFont *font) -{ - GHashTable *cache; - - cache = (GHashTable *) g_object_get_data (G_OBJECT (font), "gsk-color-glyph-cache"); - if (!cache) + blob = hb_ot_color_glyph_reference_png (hb_font, glyph); + if (blob) { - cache = g_hash_table_new (NULL, NULL); - g_object_set_data_full (G_OBJECT (font), "gsk-color-glyph-cache", - cache, (GDestroyNotify) g_hash_table_unref); + guint length = hb_blob_get_length (blob); + hb_blob_destroy (blob); + return length > 0; } - return cache; -} - -static inline gboolean -lookup_color_glyph_cache (GHashTable *cache, - PangoGlyph glyph, - gboolean *has_color) -{ - gpointer value; - - if (g_hash_table_lookup_extended (cache, GUINT_TO_POINTER (glyph), NULL, &value)) + blob = hb_ot_color_glyph_reference_svg (face, glyph); + if (blob) { - *has_color = GPOINTER_TO_UINT (value); - return TRUE; + guint length = hb_blob_get_length (blob); + hb_blob_destroy (blob); + return length > 0; } return FALSE; } -static inline void -insert_color_glyph_cache (GHashTable *cache, - PangoGlyph glyph, - gboolean has_color) -{ - g_hash_table_insert (cache, GUINT_TO_POINTER (glyph), GUINT_TO_POINTER (has_color)); -} - -static void -mark_color_glyphs (const PangoFont *font, - PangoGlyphInfo *glyphs, - int num_glyphs) -{ - cairo_scaled_font_t *scaled_font; - FT_Face ft_face; - GHashTable *cache; - - scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font); - if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_FT) - return; - - ft_face = cairo_ft_scaled_font_lock_face (scaled_font); - if (!FT_HAS_COLOR (ft_face)) - goto out; - - cache = ensure_color_glyph_cache (font); - - for (int i = 0; i < num_glyphs; i++) - { - gboolean has_color; - - if (!lookup_color_glyph_cache (cache, glyphs[i].glyph, &has_color)) - { - has_color = glyph_has_color (ft_face, glyphs[i].glyph); - insert_color_glyph_cache (cache, glyphs[i].glyph, has_color); - } - - if (has_color) - GLYPH_SET_COLOR (&glyphs[i]); - } - -out: - cairo_ft_scaled_font_unlock_face (scaled_font); -} - /** * gsk_text_node_new: * @font: the `PangoFont` containing the glyphs @@ -4561,6 +4487,9 @@ gsk_text_node_new (PangoFont *font, GskTextNode *self; GskRenderNode *node; PangoRectangle ink_rect; + PangoGlyphInfo *glyph_infos; + gboolean has_color_glyphs; + int n; pango_glyph_string_extents (glyphs, font, &ink_rect, NULL); pango_extents_to_pixels (&ink_rect, NULL); @@ -4573,24 +4502,35 @@ gsk_text_node_new (PangoFont *font, node = (GskRenderNode *) self; self->font = g_object_ref (font); - self->has_color_glyphs = font_has_color_glyphs (font); self->color = *color; self->offset = *offset; - self->glyphs = g_malloc_n (glyphs->num_glyphs, sizeof (PangoGlyphInfo)); + self->has_color_glyphs = FALSE; - /* skip empty glyphs */ - self->num_glyphs = 0; + glyph_infos = g_malloc_n (glyphs->num_glyphs, sizeof (PangoGlyphInfo)); + has_color_glyphs = font_has_color_glyphs (font); + + n = 0; for (int i = 0; i < glyphs->num_glyphs; i++) { - if (glyphs->glyphs[i].glyph != PANGO_GLYPH_EMPTY) + /* skip empty glyphs */ + if (glyphs->glyphs[i].glyph == PANGO_GLYPH_EMPTY) + continue; + + glyph_infos[n] = glyphs->glyphs[i]; + GLYPH_CLEAR_COLOR (&glyph_infos[n]); + + if (has_color_glyphs && + glyph_has_color (font, glyph_infos[n].glyph)) { - self->glyphs[self->num_glyphs] = glyphs->glyphs[i]; - GLYPH_CLEAR_COLOR (&self->glyphs[self->num_glyphs]); - self->num_glyphs++; + self->has_color_glyphs = TRUE; + GLYPH_SET_COLOR (&glyph_infos[n]); } + + n++; } - mark_color_glyphs (font, self->glyphs, self->num_glyphs); + self->glyphs = glyph_infos; + self->num_glyphs = n; graphene_rect_init (&node->bounds, offset->x + ink_rect.x - 1,