From 4cf2a482ea389a25f3b3aabc3f2258d2a21bfa51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Sat, 18 Nov 2017 14:30:57 +0100 Subject: [PATCH] gl: Add glyph cache Based on the one used by the vulkan renderer --- gsk/gskgldriver.c | 21 +- gsk/gskgldriverprivate.h | 3 + gsk/gskglglyphcache.c | 445 ++++++++++++++++++++++++++++ gsk/gskglglyphcacheprivate.h | 59 ++++ gsk/gskglimage.c | 68 +++++ gsk/gskglimageprivate.h | 40 +++ gsk/gskglrenderer.c | 161 +++++++++- gsk/meson.build | 3 + gsk/resources/glsl/coloring.fs.glsl | 12 + 9 files changed, 806 insertions(+), 6 deletions(-) create mode 100644 gsk/gskglglyphcache.c create mode 100644 gsk/gskglglyphcacheprivate.h create mode 100644 gsk/gskglimage.c create mode 100644 gsk/gskglimageprivate.h create mode 100644 gsk/resources/glsl/coloring.fs.glsl diff --git a/gsk/gskgldriver.c b/gsk/gskgldriver.c index 188ad231bf..f74e3962da 100644 --- a/gsk/gskgldriver.c +++ b/gsk/gskgldriver.c @@ -17,7 +17,8 @@ typedef struct { GLuint mag_filter; GArray *fbos; GdkTexture *user; - gboolean in_use : 1; + guint in_use : 1; + guint permanent : 1; } Texture; typedef struct { @@ -298,6 +299,7 @@ gsk_gl_driver_collect_textures (GskGLDriver *driver) GHashTableIter iter; gpointer value_p = NULL; int old_size; + /*return;*/ g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0); g_return_val_if_fail (!driver->in_frame, 0); @@ -309,7 +311,7 @@ gsk_gl_driver_collect_textures (GskGLDriver *driver) { Texture *t = value_p; - if (t->user) + if (t->user || t->permanent) continue; if (t->in_use) @@ -511,6 +513,21 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *driver, return t->texture_id; } +int +gsk_gl_driver_create_permanent_texture (GskGLDriver *self, + float width, + float height) +{ + Texture *t; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), -1); + + t = create_texture (self, width, height); + t->permanent = TRUE; + + return t->texture_id; +} + int gsk_gl_driver_create_texture (GskGLDriver *driver, float width, diff --git a/gsk/gskgldriverprivate.h b/gsk/gskgldriverprivate.h index 5a138b1789..c26ebffbf1 100644 --- a/gsk/gskgldriverprivate.h +++ b/gsk/gskgldriverprivate.h @@ -27,6 +27,9 @@ int gsk_gl_driver_get_texture_for_texture (GskGLDriver *driver GdkTexture *texture, int min_filter, int mag_filter); +int gsk_gl_driver_create_permanent_texture (GskGLDriver *driver, + float width, + float height); int gsk_gl_driver_create_texture (GskGLDriver *driver, float width, float height); diff --git a/gsk/gskglglyphcache.c b/gsk/gskglglyphcache.c new file mode 100644 index 0000000000..9e253e2587 --- /dev/null +++ b/gsk/gskglglyphcache.c @@ -0,0 +1,445 @@ +#include "config.h" + +#include "gskglglyphcacheprivate.h" +#include "gskgldriverprivate.h" +#include "gskdebugprivate.h" +#include "gskprivate.h" + +#include +#include +#include + +/* Parameters for our 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, then + * we drop the atlas an all the glyphs contained in it from the cache. + */ + +#define MAX_AGE 60 +#define CHECK_INTERVAL 10 +#define MAX_OLD 0.333 + +#define ATLAS_SIZE 512 + +typedef struct +{ + PangoFont *font; + PangoGlyph glyph; + guint scale; /* times 1024 */ +} GlyphCacheKey; + +typedef struct +{ + GlyphCacheKey *key; + GskGLCachedGlyph *value; + cairo_surface_t *surface; +} DirtyGlyph; + + +static guint glyph_cache_hash (gconstpointer v); +static gboolean glyph_cache_equal (gconstpointer v1, + gconstpointer v2); +static void glyph_cache_key_free (gpointer v); +static void glyph_cache_value_free (gpointer v); +static void dirty_glyph_free (gpointer v); + +static GskGLGlyphAtlas * +create_atlas (GskGLGlyphCache *cache) +{ + GskGLGlyphAtlas *atlas; + + atlas = g_new0 (GskGLGlyphAtlas, 1); + atlas->width = ATLAS_SIZE; + atlas->height = ATLAS_SIZE; + atlas->y0 = 1; + atlas->y = 1; + atlas->x = 1; + atlas->image = NULL; + atlas->num_glyphs = 0; + atlas->dirty_glyphs = NULL; + + return atlas; +} + +static void +free_atlas (gpointer v) +{ + GskGLGlyphAtlas *atlas = v; + + if (atlas->image) + { + g_assert (atlas->image->texture_id == 0); + g_free (atlas->image); + } + g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free); + g_free (atlas); +} + +void +gsk_gl_glyph_cache_init (GskGLGlyphCache *self, + GskGLDriver *gl_driver) +{ + 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_ptr_array_add (self->atlases, create_atlas (self)); + + self->gl_driver = gl_driver; +} + +void +gsk_gl_glyph_cache_free (GskGLGlyphCache *self) +{ + guint i; + + for (i = 0; i < self->atlases->len; i ++) + { + GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i); + + if (atlas->image) + { + gsk_gl_image_destroy (atlas->image, self->gl_driver); + atlas->image->texture_id = 0; + } + } + + g_ptr_array_unref (self->atlases); + g_hash_table_unref (self->hash_table); +} + +static gboolean +glyph_cache_equal (gconstpointer v1, gconstpointer v2) +{ + const GlyphCacheKey *key1 = v1; + const GlyphCacheKey *key2 = v2; + + return key1->font == key2->font && + key1->glyph == key2->glyph && + key1->scale == key2->scale; +} + +static guint +glyph_cache_hash (gconstpointer v) +{ + const GlyphCacheKey *key = v; + + return GPOINTER_TO_UINT (key->font) ^ key->glyph ^ key->scale; +} + +static void +glyph_cache_key_free (gpointer v) +{ + GlyphCacheKey *f = v; + + g_object_unref (f->font); + g_free (f); +} + +static void +glyph_cache_value_free (gpointer v) +{ + g_free (v); +} + +static void +dirty_glyph_free (gpointer v) +{ + DirtyGlyph *glyph = v; + + if (glyph->surface) + cairo_surface_destroy (glyph->surface); + g_free (glyph); +} + +static void +add_to_cache (GskGLGlyphCache *cache, + GlyphCacheKey *key, + GskGLCachedGlyph *value) +{ + GskGLGlyphAtlas *atlas; + int i; + DirtyGlyph *dirty; + int width = value->draw_width * key->scale / 1024; + int height = value->draw_height * key->scale / 1024; + + for (i = 0; i < cache->atlases->len; i++) + { + int x, y, y0; + + atlas = g_ptr_array_index (cache->atlases, i); + x = atlas->x; + y = atlas->y; + y0 = atlas->y0; + + if (atlas->x + width + 1 >= atlas->width) + { + /* start a new row */ + y0 = y + 1; + x = 1; + } + + if (y0 + height + 1 >= atlas->height) + continue; + + atlas->y0 = y0; + atlas->x = x; + atlas->y = y; + break; + } + + if (i == cache->atlases->len) + { + atlas = create_atlas (cache); + g_ptr_array_add (cache->atlases, atlas); + } + + value->tx = (float)atlas->x / atlas->width; + value->ty = (float)atlas->y0 / atlas->height; + value->tw = (float)width / atlas->width; + value->th = (float)height / atlas->height; + + value->atlas = atlas; + + dirty = g_new0 (DirtyGlyph, 1); + dirty->key = key; + dirty->value = value; + atlas->dirty_glyphs = g_list_prepend (atlas->dirty_glyphs, dirty); + + atlas->x = atlas->x + width + 1; + atlas->y = MAX (atlas->y, atlas->y0 + height + 1); + + atlas->num_glyphs++; + +#ifdef G_ENABLE_DEBUG + if (GSK_DEBUG_CHECK(GLYPH_CACHE)) + { + g_print ("Glyph cache:\n"); + for (i = 0; i < cache->atlases->len; i++) + { + atlas = g_ptr_array_index (cache->atlases, i); + g_print ("\tGskGLGlyphAtlas %d (%dx%d): %d glyphs (%d dirty), %.2g%% old pixels, filled to %d, %d / %d\n", + i, atlas->width, atlas->height, + atlas->num_glyphs, g_list_length (atlas->dirty_glyphs), + 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height), + atlas->x, atlas->y0, atlas->y); + } + } +#endif +} + +static void +render_glyph (const GskGLGlyphAtlas *atlas, + DirtyGlyph *glyph, + GskImageRegion *region) +{ + GlyphCacheKey *key = glyph->key; + GskGLCachedGlyph *value = glyph->value; + cairo_surface_t *surface; + cairo_t *cr; + cairo_scaled_font_t *scaled_font; + cairo_glyph_t cg; + + 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; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + value->draw_width * key->scale / 1024, + value->draw_height * key->scale / 1024); + cairo_surface_set_device_scale (surface, key->scale / 1024.0, key->scale / 1024.0); + + cr = cairo_create (surface); + + cairo_set_scaled_font (cr, scaled_font); + cairo_set_source_rgba (cr, 1, 1, 1, 1); + + cg.index = key->glyph; + cg.x = - value->draw_x; + cg.y = - value->draw_y; + + cairo_show_glyphs (cr, &cg, 1); + + cairo_destroy (cr); + + glyph->surface = surface; + + region->data = cairo_image_surface_get_data (surface); + region->width = cairo_image_surface_get_width (surface); + region->height = cairo_image_surface_get_height (surface); + region->stride = cairo_image_surface_get_stride (surface); + region->x = (gsize)(value->tx * atlas->width); + region->y = (gsize)(value->ty * atlas->height); +} + +static void +upload_dirty_glyphs (GskGLGlyphCache *self, + GskGLGlyphAtlas *atlas) +{ + GList *l; + guint num_regions; + GskImageRegion *regions; + int i; + + num_regions = g_list_length (atlas->dirty_glyphs); + regions = alloca (sizeof (GskImageRegion) * num_regions); + + for (l = atlas->dirty_glyphs, i = 0; l; l = l->next, i++) + render_glyph (atlas, (DirtyGlyph *)l->data, ®ions[i]); + + GSK_NOTE (GLYPH_CACHE, + g_print ("uploading %d glyphs to cache\n", num_regions)); + + + gsk_gl_image_upload_regions (atlas->image, self->gl_driver, num_regions, regions); + + g_list_free_full (atlas->dirty_glyphs, dirty_glyph_free); + atlas->dirty_glyphs = NULL; +} + +const GskGLCachedGlyph * +gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache, + gboolean create, + PangoFont *font, + PangoGlyph glyph, + float scale) +{ + GlyphCacheKey lookup_key; + GskGLCachedGlyph *value; + + lookup_key.font = font; + lookup_key.glyph = glyph; + lookup_key.scale = (guint)(scale * 1024); + + value = g_hash_table_lookup (cache->hash_table, &lookup_key); + + if (value) + { + if (cache->timestamp - value->timestamp >= MAX_AGE) + { + GskGLGlyphAtlas *atlas = value->atlas; + + if (atlas) + atlas->old_pixels -= value->draw_width * value->draw_height; + + value->timestamp = cache->timestamp; + } + } + + if (create && 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->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 */ + + key->font = g_object_ref (font); + key->glyph = glyph; + key->scale = (guint)(scale * 1024); + + if (ink_rect.width > 0 && ink_rect.height > 0) + add_to_cache (cache, key, value); + + g_hash_table_insert (cache->hash_table, key, value); + } + + return value; +} + +GskGLImage * +gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache *self, + const GskGLCachedGlyph *glyph) +{ + GskGLGlyphAtlas *atlas = glyph->atlas; + + g_assert (atlas != NULL); + + + if (atlas->image == NULL) + { + atlas->image = g_new0 (GskGLImage, 1); + gsk_gl_image_create (atlas->image, self->gl_driver, atlas->width, atlas->height); + } + + if (atlas->dirty_glyphs) + upload_dirty_glyphs (self, atlas); + + return atlas->image; +} + +void +gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self) +{ + int i; + GHashTableIter iter; + GlyphCacheKey *key; + GskGLCachedGlyph *value; + guint dropped = 0; + + self->timestamp++; + + + if (self->timestamp % CHECK_INTERVAL != 0) + return; + + /* look for glyphs that have grown old since last time */ + g_hash_table_iter_init (&iter, self->hash_table); + while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value)) + { + guint age; + + age = self->timestamp - value->timestamp; + if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL) + { + GskGLGlyphAtlas *atlas = value->atlas; + + if (atlas) + atlas->old_pixels += value->draw_width * value->draw_height; + } + } + + /* look for atlases to drop, and create a mapping of updated texture indices */ + for (i = self->atlases->len - 1; i >= 0; i--) + { + GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i); + + if (atlas->old_pixels > MAX_OLD * atlas->width * atlas->height) + { + GSK_NOTE(GLYPH_CACHE, + g_print ("Dropping atlas %d (%g.2%% old)\n", + i, 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height))); + + if (atlas->image) + { + gsk_gl_image_destroy (atlas->image, self->gl_driver); + atlas->image->texture_id = 0; + } + + /* Remove all glyphs that point to this atlas */ + g_hash_table_iter_init (&iter, self->hash_table); + while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value)) + { + if (value->atlas == atlas) + g_hash_table_iter_remove (&iter); + } + /* TODO: The above loop inside this other loop could be slow... */ + + g_ptr_array_remove_index (self->atlases, i); + } + } + + GSK_NOTE(GLYPH_CACHE, g_print ("Dropped %d glyphs\n", dropped)); +} diff --git a/gsk/gskglglyphcacheprivate.h b/gsk/gskglglyphcacheprivate.h new file mode 100644 index 0000000000..71664020de --- /dev/null +++ b/gsk/gskglglyphcacheprivate.h @@ -0,0 +1,59 @@ +#ifndef __GSK_GL_GLYPH_CACHE_PRIVATE_H__ +#define __GSK_GL_GLYPH_CACHE_PRIVATE_H__ + +#include "gskgldriverprivate.h" +#include "gskglimageprivate.h" +#include +#include + +typedef struct +{ + GskGLDriver *gl_driver; + + GHashTable *hash_table; + GPtrArray *atlases; + + guint64 timestamp; +} GskGLGlyphCache; + + +typedef struct +{ + GskGLImage *image; + int width, height; + int x, y, y0; + int num_glyphs; + GList *dirty_glyphs; + guint old_pixels; +} GskGLGlyphAtlas; + +typedef struct +{ + GskGLGlyphAtlas *atlas; + + float tx; + float ty; + float tw; + float th; + + int draw_x; + int draw_y; + int draw_width; + int draw_height; + + guint64 timestamp; +} GskGLCachedGlyph; + +void gsk_gl_glyph_cache_init (GskGLGlyphCache *self, + GskGLDriver *gl_driver); +void gsk_gl_glyph_cache_free (GskGLGlyphCache *self); +void gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self); +GskGLImage * gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache *self, + const GskGLCachedGlyph *glyph); +const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup (GskGLGlyphCache *self, + gboolean create, + PangoFont *font, + PangoGlyph glyph, + float scale); + +#endif diff --git a/gsk/gskglimage.c b/gsk/gskglimage.c new file mode 100644 index 0000000000..6d91b35568 --- /dev/null +++ b/gsk/gskglimage.c @@ -0,0 +1,68 @@ + +#include "gskglimageprivate.h" +#include + +void +gsk_gl_image_create (GskGLImage *self, + GskGLDriver *gl_driver, + int width, + int height) +{ + self->texture_id = gsk_gl_driver_create_permanent_texture (gl_driver, width, height); + self->width = width; + self->height = height; + + gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id); + gsk_gl_driver_init_texture_empty (gl_driver, self->texture_id); +} + +void +gsk_gl_image_destroy (GskGLImage *self, + GskGLDriver *gl_driver) +{ + gsk_gl_driver_destroy_texture (gl_driver, self->texture_id); +} + +void +gsk_gl_image_write_to_png (const GskGLImage *self, + GskGLDriver *gl_driver, + const char *filename) +{ + int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, self->width); + guchar *data = g_malloc (self->height * stride); + cairo_surface_t *s; + + gsk_gl_driver_bind_source_texture (gl_driver, self->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, self->width, self->height, stride); + cairo_surface_write_to_png (s, filename); + + cairo_surface_destroy (s); + g_free (data); +} + +void +gsk_gl_image_upload_regions (GskGLImage *self, + GskGLDriver *gl_driver, + guint n_regions, + const GskImageRegion *regions) +{ + guint i; + + for (i = 0; i < n_regions; i ++) + { + const GskImageRegion *region = ®ions[i]; + + gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id); + glBindTexture (GL_TEXTURE_2D, self->texture_id); + + glTexSubImage2D (GL_TEXTURE_2D, 0, region->x, region->y, region->width, region->height, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data); + } + +#ifdef G_ENABLE_DEBUG + /*gsk_gl_driver_bind_source_texture (gl_driver, self->texture_id);*/ + /*gsk_gl_image_dump (self, gl_driver, "/home/baedert/atlases/test_dump.png");*/ +#endif +} + diff --git a/gsk/gskglimageprivate.h b/gsk/gskglimageprivate.h new file mode 100644 index 0000000000..5a58fea5c9 --- /dev/null +++ b/gsk/gskglimageprivate.h @@ -0,0 +1,40 @@ +#ifndef __GSK_GL_IMAGE_H__ +#define __GSK_GL_IMAGE_H__ + +#include "gskgldriverprivate.h" +#include + +typedef struct +{ + guint texture_id; + int width; + int height; +} GskGLImage; + +typedef struct +{ + guchar *data; + gsize width; + gsize height; + gsize stride; + gsize x; + gsize y; +} GskImageRegion; + +void gsk_gl_image_create (GskGLImage *self, + GskGLDriver *gl_driver, + int width, + int height); +void gsk_gl_image_destroy (GskGLImage *self, + GskGLDriver *gl_driver); +void gsk_gl_image_write_to_png (const GskGLImage *self, + GskGLDriver *gl_driver, + const char *filename); +void gsk_gl_image_upload_regions (GskGLImage *self, + GskGLDriver *gl_driver, + guint n_regions, + const GskImageRegion *regions); + + +#endif + diff --git a/gsk/gskglrenderer.c b/gsk/gskglrenderer.c index 617bf32d43..665c48eee4 100644 --- a/gsk/gskglrenderer.c +++ b/gsk/gskglrenderer.c @@ -10,11 +10,13 @@ #include "gskrendererprivate.h" #include "gskrendernodeprivate.h" #include "gskshaderbuilderprivate.h" +#include "gskglglyphcacheprivate.h" #include "gdk/gdktextureprivate.h" #include "gskprivate.h" #include +#include #define SHADER_VERSION_GLES 100 #define SHADER_VERSION_GL2_LEGACY 110 @@ -41,6 +43,22 @@ dump_framebuffer (const char *filename, int w, int h) g_free (data); } +static gboolean +font_has_color_glyphs (const PangoFont *font) +{ + cairo_scaled_font_t *scaled_font; + gboolean has_color = FALSE; + + 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; +} static void gsk_gl_renderer_setup_render_mode (GskGLRenderer *self); @@ -95,6 +113,7 @@ typedef struct { enum { MODE_BLIT = 1, MODE_COLOR, + MODE_COLORING, MODE_TEXTURE, MODE_COLOR_MATRIX, MODE_LINEAR_GRADIENT, @@ -219,12 +238,15 @@ struct _GskGLRenderer Program blend_program; Program blit_program; Program color_program; + Program coloring_program; Program color_matrix_program; Program linear_gradient_program; Program clip_program; GArray *render_items; + GskGLGlyphCache glyph_cache; + #ifdef G_ENABLE_DEBUG ProfileCounters profile_counters; ProfileTimers profile_timers; @@ -429,6 +451,20 @@ gsk_gl_renderer_create_programs (GskGLRenderer *self, init_common_locations (self, builder, &self->color_program); INIT_PROGRAM_UNIFORM_LOCATION (color_program, color_location, "uColor"); + self->coloring_program.id = gsk_shader_builder_create_program (builder, + "blit.vs.glsl", + "coloring.fs.glsl", + &shader_error); + if (shader_error != NULL) + { + g_propagate_prefixed_error (error, + shader_error, + "Unable to create 'coloring' program: "); + goto out; + } + init_common_locations (self, builder, &self->coloring_program); + INIT_PROGRAM_UNIFORM_LOCATION (coloring_program, color_location, "uColor"); + self->color_matrix_program.id = gsk_shader_builder_create_program (builder, "color_matrix.vs.glsl", "color_matrix.fs.glsl", @@ -502,6 +538,8 @@ gsk_gl_renderer_realize (GskRenderer *renderer, if (!gsk_gl_renderer_create_programs (self, error)) return FALSE; + gsk_gl_glyph_cache_init (&self->glyph_cache, self->gl_driver); + return TRUE; } @@ -524,11 +562,14 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer) glDeleteProgram (self->blend_program.id); glDeleteProgram (self->blit_program.id); glDeleteProgram (self->color_program.id); + glDeleteProgram (self->coloring_program.id); glDeleteProgram (self->color_matrix_program.id); glDeleteProgram (self->linear_gradient_program.id); gsk_gl_renderer_destroy_buffers (self); + gsk_gl_glyph_cache_free (&self->glyph_cache); + g_clear_object (&self->gl_profiler); g_clear_object (&self->gl_driver); @@ -666,6 +707,20 @@ render_item (GskGLRenderer *self, } break; + case MODE_COLORING: + { + glUniform4f (item->program->color_location, + item->color_data.color.red, + item->color_data.color.green, + item->color_data.color.blue, + item->color_data.color.alpha); + g_assert(item->texture_id != 0); + /* Use texture unit 0 for the source */ + glUniform1i (item->program->source_location, 0); + gsk_gl_driver_bind_source_texture (self->gl_driver, item->texture_id); + } + break; + case MODE_TEXTURE: { g_assert(item->texture_id != 0); @@ -949,7 +1004,6 @@ gsk_gl_renderer_add_render_item (GskGLRenderer *self, if (gsk_render_node_get_node_type (child) != GSK_TEXTURE_NODE) { - graphene_matrix_t p; graphene_matrix_t identity; @@ -1068,6 +1122,104 @@ gsk_gl_renderer_add_render_item (GskGLRenderer *self, } return; + case GSK_TEXT_NODE: + { + const PangoFont *font = gsk_text_node_peek_font (node); + const PangoGlyphInfo *glyphs = gsk_text_node_peek_glyphs (node); + guint num_glyphs = gsk_text_node_get_num_glyphs (node); + int i; + int x_position = 0; + int x = gsk_text_node_get_x (node); + int y = gsk_text_node_get_y (node); + + /* We use one quad per character, unlike the other nodes which + * use at most one quad altogether */ + for (i = 0; i < num_glyphs; i++) + { + const PangoGlyphInfo *gi = &glyphs[i]; + const GskGLCachedGlyph *glyph; + int glyph_x, glyph_y, glyph_w, glyph_h; + float tx, ty, tx2, ty2; + double cx; + double cy; + + if (gi->glyph == PANGO_GLYPH_EMPTY || + (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG) > 0) + continue; + + glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache, + TRUE, + (PangoFont *)font, + gi->glyph, + self->scale_factor); + + /* e.g. whitespace */ + if (glyph->draw_width <= 0 || glyph->draw_height <= 0) + { + x_position += gi->geometry.width; + continue; + } + cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE; + cy = (double)(gi->geometry.y_offset) / PANGO_SCALE; + + /* If the font has color glyphs, we don't need to recolor anything */ + if (font_has_color_glyphs (font)) + { + item.mode = MODE_BLIT; + item.program = &self->blit_program; + } + else + { + item.mode = MODE_COLORING; + item.program = &self->coloring_program; + item.color_data.color = *gsk_text_node_peek_color (node); + } + + item.texture_id = gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache, glyph)->texture_id; + + { + 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; + + item.min.x = glyph_x; + item.min.y = glyph_y; + item.size.width = glyph_w; + item.size.height = glyph_h; + item.max.x = item.min.x + item.size.width; + item.max.y = item.min.y + item.size.height; + + GskQuadVertex vertex_data[N_VERTICES] = { + { { glyph_x, glyph_y }, { tx, ty }, }, + { { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, }, + { { glyph_x + glyph_w, glyph_y }, { tx2, ty }, }, + + { { glyph_x + glyph_w, glyph_y + glyph_h }, { tx2, ty2 }, }, + { { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, }, + { { glyph_x + glyph_w, glyph_y }, { tx2, ty }, }, + }; + + item.vao_id = gsk_gl_driver_create_vao_for_quad (self->gl_driver, + item.program->position_location, + item.program->uv_location, + N_VERTICES, + vertex_data); + } + + + g_array_append_val (render_items, item); + + x_position += gi->geometry.width; + } + } + return; + case GSK_NOT_A_RENDER_NODE: case GSK_CONTAINER_NODE: g_assert_not_reached (); @@ -1077,7 +1229,6 @@ gsk_gl_renderer_add_render_item (GskGLRenderer *self, case GSK_BORDER_NODE: case GSK_INSET_SHADOW_NODE: case GSK_OUTSET_SHADOW_NODE: - case GSK_TEXT_NODE: case GSK_BLUR_NODE: case GSK_SHADOW_NODE: case GSK_CROSS_FADE_NODE: @@ -1242,8 +1393,8 @@ gsk_gl_renderer_setup_render_mode (GskGLRenderer *self) window_height = gdk_window_get_height (window) * self->scale_factor; - cairo_region_get_extents (clip, &extents); - /*cairo_region_get_rectangle (clip, 0, &extents);*/ + /*cairo_region_get_extents (clip, &extents);*/ + cairo_region_get_rectangle (clip, 0, &extents); glEnable (GL_SCISSOR_TEST); glScissor (extents.x * self->scale_factor, @@ -1302,6 +1453,7 @@ gsk_gl_renderer_do_render (GskRenderer *renderer, graphene_matrix_scale (&projection, 1, -1, 1); gsk_gl_driver_begin_frame (self->gl_driver); + gsk_gl_glyph_cache_begin_frame (&self->glyph_cache); gsk_gl_renderer_validate_tree (self, root, &projection); #ifdef G_ENABLE_DEBUG @@ -1437,6 +1589,7 @@ gsk_gl_renderer_init (GskGLRenderer *self) { gsk_ensure_resources (); + self->render_items = g_array_new (FALSE, FALSE, sizeof (RenderItem)); g_array_set_clear_func (self->render_items, (GDestroyNotify)destroy_render_item); self->scale_factor = 1; diff --git a/gsk/meson.build b/gsk/meson.build index 6c827c5e4f..894c314a11 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -5,6 +5,7 @@ gsk_private_source_shaders = [ 'resources/glsl/blit.vs.glsl', 'resources/glsl/color.fs.glsl', 'resources/glsl/color.vs.glsl', + 'resources/glsl/coloring.fs.glsl', 'resources/glsl/color_matrix.fs.glsl', 'resources/glsl/color_matrix.vs.glsl', 'resources/glsl/linear_gradient.fs.glsl', @@ -30,6 +31,8 @@ gsk_private_sources = files([ 'gskgldriver.c', 'gskglprofiler.c', 'gskglrenderer.c', + 'gskglglyphcache.c', + 'gskglimage.c', 'gskprivate.c', 'gskprofiler.c', 'gskshaderbuilder.c', diff --git a/gsk/resources/glsl/coloring.fs.glsl b/gsk/resources/glsl/coloring.fs.glsl new file mode 100644 index 0000000000..8bb1d63cbf --- /dev/null +++ b/gsk/resources/glsl/coloring.fs.glsl @@ -0,0 +1,12 @@ + +uniform vec4 uColor; + +void main() { + vec4 diffuse = Texture(uSource, vUv); + vec4 color = uColor; + + // pre-multiply + color.rgb *= color.a; + + setOutputColor((diffuse * color) * uAlpha); +}