Compare commits

...

7 Commits

Author SHA1 Message Date
Matthias Clasen
c476c45007 gsk: Maintain clip info for glyph nodes
We can determine once whether we are contained, instead of doing
it again for every glyph.

The performance impact of this is hard to measure.
2024-02-25 18:26:00 -05:00
Matthias Clasen
5d05270ca7 gsk: Don't add images needlessly
Most likely, the image for all glyphs will be the same atlas,
so check first before calling the add_image function, which
will loop over all images.

This matches what the pattern code does for glyph nodes.

The performance impact of this is hard to measure.
2024-02-25 18:25:42 -05:00
Matthias Clasen
34fb85afd1 gsk: Split the glyph cache
Do the glyph cache lookup in two steps: First find a cache for
the given font+scale, then look up the glyph+flags. Since
font+scale change much less frequently (in particular, they are
the same for all glyphs in a text node), it makes sense to keep
the result of the last lookup around and check if it still fits.

Here are changes in running widget-factory with no-vsync:

                                      before    after
gsk_gpu_device_lookup_glyph_image     1.43%     1.02%
g_hash_table_lookup                   0.76%     0.43%
gsk_gpu_cached_glyph_equal            0.28%     0.21%
2024-02-25 13:47:32 -05:00
Matthias Clasen
05bc4a86c7 gsk: Keep scaled fonts around
Create the scaled font on demand, only when we need to render a glyph,
and keep it around while processing the remaining glyphs in the text
node, rather than recreating it over and over.
2024-02-25 09:35:56 -05:00
Matthias Clasen
93302cc42d gsk: Stop passing scale to glyph upload op
It is always 1.0 now, so there is no point. Instead of the scale,
print the font when doing verbose logging.
2024-02-25 09:35:56 -05:00
Matthias Clasen
976f45e614 gsk: Use a scaled font for glyph rendering
This changes the approach we take to rendering glyphs in the
presence of a scale transform: Instead of scaling the extents
and rendering to an image surface with device scale, simply
create a scaled font and use it for extents and rendering.

This avoids clipping problems with scaling of extents in
the presence of hinting.
2024-02-25 09:35:56 -05:00
Matthias Clasen
befccc943a gsk: Add a way to get a scaled font
Add a function to change the size of a font while keeping everything
else the same. We use pango api for this if available.
2024-02-25 09:35:55 -05:00
7 changed files with 260 additions and 58 deletions

View File

@@ -11,6 +11,7 @@
#include "gdk/gdkprofilerprivate.h"
#include "gsk/gskdebugprivate.h"
#include "gsk/gskprivate.h"
#define MAX_SLICES_PER_ATLAS 64
@@ -32,6 +33,37 @@ typedef struct _GskGpuCachedGlyph GskGpuCachedGlyph;
typedef struct _GskGpuCachedTexture GskGpuCachedTexture;
typedef struct _GskGpuDevicePrivate GskGpuDevicePrivate;
typedef struct {
PangoFont *font;
float scale;
} FontCacheKey;
typedef struct {
PangoGlyph glyph;
GskGpuGlyphLookupFlags flags;
} GlyphCacheKey;
typedef struct {
FontCacheKey key;
GHashTable *cache;
} FontGlyphCache;
static void
font_glyph_cache_free (gpointer data)
{
FontGlyphCache *cache = data;
g_object_unref (cache->key.font);
g_hash_table_unref (cache->cache);
g_free (cache);
}
static FontGlyphCache no_font_cache = {
.key = { .font = NULL, .scale = -1 },
.cache = NULL
};
struct _GskGpuDevicePrivate
{
GdkDisplay *display;
@@ -44,6 +76,7 @@ struct _GskGpuDevicePrivate
GHashTable *texture_cache;
GHashTable *glyph_cache;
FontGlyphCache *last_font_cache;
GskGpuCachedAtlas *current_atlas;
@@ -68,14 +101,15 @@ struct _GskGpuCachedClass
struct _GskGpuCached
{
const GskGpuCachedClass *class;
GskGpuCachedAtlas *atlas;
GskGpuCached *next;
GskGpuCached *prev;
gint64 timestamp;
gboolean stale;
guint pixels; /* For glyphs and textures, pixels. For atlases, dead pixels */
unsigned int stale : 1;
unsigned int pixels : 31; /* For glyphs and textures, pixels. For atlases, dead pixels */
};
static inline void
@@ -360,10 +394,8 @@ struct _GskGpuCachedGlyph
{
GskGpuCached parent;
PangoFont *font;
PangoGlyph glyph;
GskGpuGlyphLookupFlags flags;
float scale;
FontGlyphCache *font_cache;
GlyphCacheKey glyph_key;
GskGpuImage *image;
graphene_rect_t bounds;
@@ -376,10 +408,17 @@ gsk_gpu_cached_glyph_free (GskGpuDevice *device,
{
GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (device);
GskGpuCachedGlyph *self = (GskGpuCachedGlyph *) cached;
FontGlyphCache *font_cache;
g_hash_table_remove (priv->glyph_cache, self);
font_cache = self->font_cache;
g_hash_table_remove (font_cache->cache, self);
if (g_hash_table_size (font_cache->cache) == 0)
{
if (priv->last_font_cache == font_cache)
priv->last_font_cache = &no_font_cache;
g_hash_table_remove (priv->glyph_cache, font_cache);
}
g_object_unref (self->font);
g_object_unref (self->image);
g_free (self);
@@ -402,15 +441,35 @@ gsk_gpu_cached_glyph_should_collect (GskGpuDevice *device,
return FALSE;
}
static guint
gsk_gpu_cached_font_hash (gconstpointer data)
{
const FontGlyphCache *cache = data;
const FontCacheKey *key = &cache->key;
return GPOINTER_TO_UINT (key->font) ^ ((guint) key->scale * PANGO_SCALE);
}
static gboolean
gsk_gpu_cached_font_equal (gconstpointer v1,
gconstpointer v2)
{
const FontGlyphCache *c1 = v1;
const FontCacheKey *key1 = &c1->key;
const FontGlyphCache *c2 = v2;
const FontCacheKey *key2 = &c2->key;
return key1->font == key2->font &&
key1->scale == key2->scale;
}
static guint
gsk_gpu_cached_glyph_hash (gconstpointer data)
{
const GskGpuCachedGlyph *glyph = data;
const GlyphCacheKey *key = &glyph->glyph_key;
return GPOINTER_TO_UINT (glyph->font) ^
glyph->glyph ^
(glyph->flags << 24) ^
((guint) glyph->scale * PANGO_SCALE);
return key->glyph ^ (key->flags << 24);
}
static gboolean
@@ -419,11 +478,11 @@ gsk_gpu_cached_glyph_equal (gconstpointer v1,
{
const GskGpuCachedGlyph *glyph1 = v1;
const GskGpuCachedGlyph *glyph2 = v2;
const GlyphCacheKey *key1 = &glyph1->glyph_key;
const GlyphCacheKey *key2 = &glyph2->glyph_key;
return glyph1->font == glyph2->font
&& glyph1->glyph == glyph2->glyph
&& glyph1->flags == glyph2->flags
&& glyph1->scale == glyph2->scale;
return key1->glyph == key2->glyph &&
key1->flags == key2->flags;
}
static const GskGpuCachedClass GSK_GPU_CACHED_GLYPH_CLASS =
@@ -622,10 +681,13 @@ gsk_gpu_device_init (GskGpuDevice *self)
{
GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self);
priv->glyph_cache = g_hash_table_new (gsk_gpu_cached_glyph_hash,
gsk_gpu_cached_glyph_equal);
priv->glyph_cache = g_hash_table_new_full (gsk_gpu_cached_font_hash,
gsk_gpu_cached_font_equal,
NULL,
font_glyph_cache_free);
priv->texture_cache = g_hash_table_new (g_direct_hash,
g_direct_equal);
priv->last_font_cache = &no_font_cache;
}
void
@@ -889,40 +951,71 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self,
GskGpuGlyphLookupFlags flags,
float scale,
graphene_rect_t *out_bounds,
graphene_point_t *out_origin)
graphene_point_t *out_origin,
PangoFont **out_scaled_font)
{
GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self);
GskGpuCachedGlyph lookup = {
.font = font,
.glyph = glyph,
.flags = flags,
.scale = scale
FontGlyphCache font_lookup = {
.key = { .font = font, .scale = scale }
};
GskGpuCachedGlyph *cache;
GskGpuCachedGlyph glyph_lookup = {
.glyph_key = { .glyph = glyph, .flags = flags }
};
FontGlyphCache *font_cache = NULL;
GskGpuCachedGlyph *cache = NULL;
PangoRectangle ink_rect;
graphene_rect_t rect;
graphene_point_t origin;
GskGpuImage *image;
gsize atlas_x, atlas_y, padding;
float subpixel_x, subpixel_y;
PangoFont *scaled_font;
cache = g_hash_table_lookup (priv->glyph_cache, &lookup);
if (cache)
if (priv->last_font_cache->key.font == font &&
priv->last_font_cache->key.scale == scale)
font_cache = priv->last_font_cache;
else
font_cache = g_hash_table_lookup (priv->glyph_cache, &font_lookup);
if (font_cache)
{
gsk_gpu_cached_use (self, (GskGpuCached *) cache, gsk_gpu_frame_get_timestamp (frame));
cache = g_hash_table_lookup (font_cache->cache, &glyph_lookup);
if (cache)
{
gsk_gpu_cached_use (self, (GskGpuCached *) cache, gsk_gpu_frame_get_timestamp (frame));
*out_bounds = cache->bounds;
*out_origin = cache->origin;
return cache->image;
*out_bounds = cache->bounds;
*out_origin = cache->origin;
priv->last_font_cache = font_cache;
return cache->image;
}
}
else
{
font_cache = g_new (FontGlyphCache, 1);
font_cache->key.font = g_object_ref (font);
font_cache->key.scale = scale;
font_cache->cache = g_hash_table_new (gsk_gpu_cached_glyph_hash,
gsk_gpu_cached_glyph_equal);
g_hash_table_insert (priv->glyph_cache, font_cache, font_cache);
}
priv->last_font_cache = font_cache;
if (*out_scaled_font)
scaled_font = *out_scaled_font;
else
scaled_font = gsk_get_scaled_font (font, scale);
subpixel_x = (flags & 3) / 4.f;
subpixel_y = ((flags >> 2) & 3) / 4.f;
pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
origin.x = floor (ink_rect.x * scale / PANGO_SCALE + subpixel_x);
origin.y = floor (ink_rect.y * scale / PANGO_SCALE + subpixel_y);
rect.size.width = ceil ((ink_rect.x + ink_rect.width) * scale / PANGO_SCALE + subpixel_x) - origin.x;
rect.size.height = ceil ((ink_rect.y + ink_rect.height) * scale / PANGO_SCALE + subpixel_y) - origin.y;
pango_font_get_glyph_extents (scaled_font, glyph, &ink_rect, NULL);
origin.x = floor (ink_rect.x * 1.0 / PANGO_SCALE + subpixel_x);
origin.y = floor (ink_rect.y * 1.0 / PANGO_SCALE + subpixel_y);
rect.size.width = ceil ((ink_rect.x + ink_rect.width) * 1.0 / PANGO_SCALE + subpixel_x) - origin.x;
rect.size.height = ceil ((ink_rect.y + ink_rect.height) * 1.0 / PANGO_SCALE + subpixel_y) - origin.y;
padding = 1;
image = gsk_gpu_device_add_atlas_image (self,
@@ -944,10 +1037,9 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self,
cache = gsk_gpu_cached_new (self, &GSK_GPU_CACHED_GLYPH_CLASS, NULL);
}
cache->font = g_object_ref (font);
cache->glyph = glyph;
cache->flags = flags;
cache->scale = scale;
cache->font_cache = font_cache;
cache->glyph_key.glyph = glyph;
cache->glyph_key.flags = flags;
cache->bounds = rect;
cache->image = image;
cache->origin = GRAPHENE_POINT_INIT (- origin.x + subpixel_x,
@@ -956,7 +1048,7 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self,
gsk_gpu_upload_glyph_op (frame,
cache->image,
font,
scaled_font,
glyph,
&(cairo_rectangle_int_t) {
.x = rect.origin.x - padding,
@@ -964,15 +1056,16 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self,
.width = rect.size.width + 2 * padding,
.height = rect.size.height + 2 * padding,
},
scale,
&GRAPHENE_POINT_INIT (cache->origin.x + padding,
cache->origin.y + padding));
g_hash_table_insert (priv->glyph_cache, cache, cache);
g_hash_table_insert (font_cache->cache, cache, cache);
gsk_gpu_cached_use (self, (GskGpuCached *) cache, gsk_gpu_frame_get_timestamp (frame));
*out_bounds = cache->bounds;
*out_origin = cache->origin;
*out_scaled_font = scaled_font;
return cache->image;
}

View File

@@ -96,7 +96,8 @@ GskGpuImage * gsk_gpu_device_lookup_glyph_image (GskGpuD
GskGpuGlyphLookupFlags flags,
float scale,
graphene_rect_t *out_bounds,
graphene_point_t *out_origin);
graphene_point_t *out_origin,
PangoFont **out_scaled_font);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskGpuDevice, g_object_unref)

View File

@@ -2992,11 +2992,15 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
GskGpuDevice *device;
const PangoGlyphInfo *glyphs;
PangoFont *font;
PangoFont *scaled_font = NULL;
graphene_point_t offset;
guint i, num_glyphs;
float scale, inv_scale;
GdkRGBA color;
gboolean glyph_align;
GskGpuImage *last_image;
guint32 descriptor = 0;
GskGpuClip old_clip;
if (self->opacity < 1.0 &&
gsk_text_node_has_color_glyphs (node))
@@ -3005,6 +3009,10 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
return;
}
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
if (gsk_gpu_clip_contains_rect (&self->clip, &self->offset, &node->bounds))
gsk_gpu_clip_init_contained (&self->clip, &node->bounds);
glyph_align = gsk_gpu_frame_should_optimize (self->frame, GSK_GPU_OPTIMIZE_GLYPH_ALIGN) &&
gsk_transform_get_category (self->modelview) >= GSK_TRANSFORM_CATEGORY_2D;
device = gsk_gpu_frame_get_device (self->frame);
@@ -3020,12 +3028,12 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
scale = MAX (graphene_vec2_get_x (&self->scale), graphene_vec2_get_y (&self->scale));
inv_scale = 1.f / scale;
last_image = NULL;
for (i = 0; i < num_glyphs; i++)
{
GskGpuImage *image;
graphene_rect_t glyph_bounds, glyph_tex_rect;
graphene_point_t glyph_offset, glyph_origin;
guint32 descriptor;
GskGpuGlyphLookupFlags flags;
glyph_origin = GRAPHENE_POINT_INIT (offset.x + (float) glyphs[i].geometry.x_offset / PANGO_SCALE,
@@ -3051,13 +3059,20 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
flags,
scale,
&glyph_bounds,
&glyph_offset);
&glyph_offset,
&scaled_font);
gsk_rect_scale (&GRAPHENE_RECT_INIT (-glyph_bounds.origin.x, -glyph_bounds.origin.y, gsk_gpu_image_get_width (image), gsk_gpu_image_get_height (image)), inv_scale, inv_scale, &glyph_tex_rect);
gsk_rect_scale (&GRAPHENE_RECT_INIT(0, 0, glyph_bounds.size.width, glyph_bounds.size.height), inv_scale, inv_scale, &glyph_bounds);
glyph_origin = GRAPHENE_POINT_INIT (glyph_origin.x - glyph_offset.x * inv_scale,
glyph_origin.y - glyph_offset.y * inv_scale);
descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_DEFAULT);
if (image != last_image)
{
descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_DEFAULT);
last_image = image;
}
if (glyphs[i].attr.is_color)
gsk_gpu_texture_op (self->frame,
gsk_gpu_clip_get_shader_clip (&self->clip, &glyph_offset, &glyph_bounds),
@@ -3078,6 +3093,10 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
offset.x += (float) glyphs[i].geometry.width / PANGO_SCALE;
}
g_clear_object (&scaled_font);
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
}
static gboolean
@@ -3087,6 +3106,7 @@ gsk_gpu_node_processor_create_glyph_pattern (GskGpuPatternWriter *self,
GskGpuDevice *device;
const PangoGlyphInfo *glyphs;
PangoFont *font;
PangoFont *scaled_font = NULL;
guint num_glyphs;
gsize i;
float scale, inv_scale;
@@ -3126,12 +3146,13 @@ gsk_gpu_node_processor_create_glyph_pattern (GskGpuPatternWriter *self,
0,
scale,
&glyph_bounds,
&glyph_offset);
&glyph_offset,
&scaled_font);
if (image != last_image)
{
if (!gsk_gpu_pattern_writer_add_image (self, image, GSK_GPU_SAMPLER_DEFAULT, &tex_id))
return FALSE;
break;
last_image = image;
}
@@ -3160,7 +3181,9 @@ gsk_gpu_node_processor_create_glyph_pattern (GskGpuPatternWriter *self,
offset.x += (float) glyphs[i].geometry.width / PANGO_SCALE;
}
return TRUE;
g_clear_object (&scaled_font);
return i == num_glyphs;
}
static gboolean

View File

@@ -485,7 +485,6 @@ struct _GskGpuUploadGlyphOp
cairo_rectangle_int_t area;
PangoFont *font;
PangoGlyph glyph;
float scale;
graphene_point_t origin;
GskGpuBuffer *buffer;
@@ -509,11 +508,19 @@ gsk_gpu_upload_glyph_op_print (GskGpuOp *op,
guint indent)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
PangoFontDescription *desc;
char *str;
desc = pango_font_describe_with_absolute_size (self->font);
str = pango_font_description_to_string (desc);
gsk_gpu_print_op (string, indent, "upload-glyph");
gsk_gpu_print_int_rect (string, &self->area);
g_string_append_printf (string, "glyph %u @ %g ", self->glyph, self->scale);
g_string_append_printf (string, "glyph %u font %s ", self->glyph, str);
gsk_gpu_print_newline (string);
g_free (str);
pango_font_description_free (desc);
}
static void
@@ -531,7 +538,6 @@ gsk_gpu_upload_glyph_op_draw (GskGpuOp *op,
self->area.height,
stride);
cairo_surface_set_device_offset (surface, self->origin.x, self->origin.y);
cairo_surface_set_device_scale (surface, self->scale, self->scale);
cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
@@ -610,7 +616,6 @@ gsk_gpu_upload_glyph_op (GskGpuFrame *frame,
PangoFont *font,
const PangoGlyph glyph,
const cairo_rectangle_int_t *area,
float scale,
const graphene_point_t *origin)
{
GskGpuUploadGlyphOp *self;
@@ -621,6 +626,5 @@ gsk_gpu_upload_glyph_op (GskGpuFrame *frame,
self->area = *area;
self->font = g_object_ref (font);
self->glyph = glyph;
self->scale = scale;
self->origin = *origin;
}

View File

@@ -25,7 +25,6 @@ void gsk_gpu_upload_glyph_op (GskGpuF
PangoFont *font,
PangoGlyph glyph,
const cairo_rectangle_int_t *area,
float scale,
const graphene_point_t *origin);
G_END_DECLS

View File

@@ -1,6 +1,10 @@
#include "gskresources.h"
#include "gskprivate.h"
#include <cairo.h>
#include <pango/pangocairo.h>
#include <math.h>
static gpointer
register_resources (gpointer data)
{
@@ -15,3 +19,78 @@ gsk_ensure_resources (void)
g_once (&register_resources_once, register_resources, NULL);
}
/*< private >
* gsk_get_scaled_font:
* @font: a `PangoFont`
* @scale: the scale
*
* Returns a font that is just like @font, at a size that
* is multiplied by @scale.
*
* Returns: (transfer full): a scaled version of @font
*/
PangoFont *
gsk_get_scaled_font (PangoFont *font,
float scale)
{
if (scale == 1.0)
return g_object_ref (font);
#if PANGO_VERSION_CHECK (1, 52, 0)
return pango_font_map_reload_font (pango_font_get_font_map (font), font, scale, NULL, NULL);
#else
GHashTable *fonts;
int key;
PangoFont *font2;
PangoFontDescription *desc;
int size;
PangoFontMap *fontmap;
PangoContext *context;
cairo_scaled_font_t *sf;
cairo_font_options_t *options;
key = (int) roundf (scale * PANGO_SCALE);
fonts = (GHashTable *) g_object_get_data (G_OBJECT (font), "gsk-scaled-fonts");
if (fonts)
{
font2 = g_hash_table_lookup (fonts, GINT_TO_POINTER (key));
if (font2)
return g_object_ref (font2);
}
else
{
fonts = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
g_object_set_data_full (G_OBJECT (font), "gsk-scaled-fonts",
fonts, (GDestroyNotify) g_hash_table_unref);
}
desc = pango_font_describe (font);
size = pango_font_description_get_size (desc);
if (pango_font_description_get_size_is_absolute (desc))
pango_font_description_set_absolute_size (desc, size * scale);
else
pango_font_description_set_size (desc, (int) roundf (size * scale));
fontmap = pango_font_get_font_map (font);
context = pango_font_map_create_context (fontmap);
sf = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font));
options = cairo_font_options_create ();
cairo_scaled_font_get_font_options (sf, options);
pango_cairo_context_set_font_options (context, options);
cairo_font_options_destroy (options);
font2 = pango_font_map_load_font (fontmap, context, desc);
pango_font_description_free (desc);
g_object_unref (context);
g_hash_table_insert (fonts, GINT_TO_POINTER (key), font2);
return g_object_ref (font2);
#endif
}

View File

@@ -5,7 +5,10 @@
G_BEGIN_DECLS
void gsk_ensure_resources (void);
void gsk_ensure_resources (void);
PangoFont *gsk_get_scaled_font (PangoFont *font,
float scale);
G_END_DECLS