diff options
author | Timm Bäder <mail@baedert.org> | 2019-05-22 07:33:45 +0200 |
---|---|---|
committer | Timm Bäder <mail@baedert.org> | 2019-05-22 07:36:28 +0200 |
commit | 27ab1d1826d458d7591fe8910c1e3e213ddea3ad (patch) | |
tree | 639a45dc37c6ed65d638125477b98b4858796349 | |
parent | 5132850f1c447045e74de792c19a3a332969517c (diff) | |
download | gtk+-27ab1d1826d458d7591fe8910c1e3e213ddea3ad.tar.gz |
gl renderer: Move texture atlas into its own file
We want to reuse the code later.
-rw-r--r-- | gsk/gl/gskglglyphcache.c | 186 | ||||
-rw-r--r-- | gsk/gl/gskglglyphcacheprivate.h | 17 | ||||
-rw-r--r-- | gsk/gl/gskglimage.c | 1 | ||||
-rw-r--r-- | gsk/gl/gskgltextureatlas.c | 84 | ||||
-rw-r--r-- | gsk/gl/gskgltextureatlasprivate.h | 49 | ||||
-rw-r--r-- | gsk/meson.build | 1 |
6 files changed, 232 insertions, 106 deletions
diff --git a/gsk/gl/gskglglyphcache.c b/gsk/gl/gskglglyphcache.c index 96d7f27e5f..2e5a737d31 100644 --- a/gsk/gl/gskglglyphcache.c +++ b/gsk/gl/gskglglyphcache.c @@ -4,6 +4,7 @@ #include "gskgldriverprivate.h" #include "gskdebugprivate.h" #include "gskprivate.h" +#include "gskgltextureatlasprivate.h" #include "gdk/gdkglcontextprivate.h" @@ -16,16 +17,15 @@ * 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 + * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD_RATIO, 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 MAX_OLD_RATIO 0.333 #define ATLAS_SIZE 512 -#define NODES_PER_ATLAS 512 static guint glyph_cache_hash (gconstpointer v); static gboolean glyph_cache_equal (gconstpointer v1, @@ -33,22 +33,13 @@ static gboolean glyph_cache_equal (gconstpointer v1, static void glyph_cache_key_free (gpointer v); static void glyph_cache_value_free (gpointer v); -static GskGLGlyphAtlas * +static GskGLTextureAtlas * create_atlas (GskGLGlyphCache *cache) { - GskGLGlyphAtlas *atlas; + GskGLTextureAtlas *atlas; - atlas = g_new0 (GskGLGlyphAtlas, 1); - atlas->width = ATLAS_SIZE; - atlas->height = ATLAS_SIZE; - atlas->image = NULL; - - atlas->nodes = g_malloc0 (sizeof (struct stbrp_node) * NODES_PER_ATLAS); - stbrp_init_target (&atlas->context, - ATLAS_SIZE, - ATLAS_SIZE, - atlas->nodes, - NODES_PER_ATLAS); + atlas = g_new (GskGLTextureAtlas, 1); + gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE); return atlas; } @@ -56,15 +47,11 @@ create_atlas (GskGLGlyphCache *cache) static void free_atlas (gpointer v) { - GskGLGlyphAtlas *atlas = v; + GskGLTextureAtlas *atlas = v; - if (atlas->image) - { - g_assert (atlas->image->texture_id == 0); - g_free (atlas->image); - } + g_assert (atlas->image.texture_id == 0); + gsk_gl_texture_atlas_free (atlas); - g_free (atlas->nodes); g_free (atlas); } @@ -89,12 +76,12 @@ gsk_gl_glyph_cache_free (GskGLGlyphCache *self) for (i = 0; i < self->atlases->len; i ++) { - GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i); + GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i); - if (atlas->image) + if (atlas->image.texture_id != 0) { - gsk_gl_image_destroy (atlas->image, self->gl_driver); - atlas->image->texture_id = 0; + gsk_gl_image_destroy (&atlas->image, self->gl_driver); + atlas->image.texture_id = 0; } } @@ -143,22 +130,20 @@ add_to_cache (GskGLGlyphCache *cache, { const int width = value->draw_width * key->scale / 1024; const int height = value->draw_height * key->scale / 1024; - GskGLGlyphAtlas *atlas = NULL; - stbrp_rect glyph_rect; + GskGLTextureAtlas *atlas = NULL; guint i, p; - - glyph_rect.w = width; - glyph_rect.h = height; + int packed_x, packed_y; /* Try all the atlases and pick the first one that can hold * our new glyph */ for (i = 0, p = cache->atlases->len; i < p; i ++) { - GskGLGlyphAtlas *test_atlas = g_ptr_array_index (cache->atlases, i); + GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i); + gboolean was_packed; - stbrp_pack_rects (&test_atlas->context, &glyph_rect, 1); + was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y); - if (glyph_rect.was_packed) + if (was_packed) { atlas = test_atlas; break; @@ -167,22 +152,31 @@ add_to_cache (GskGLGlyphCache *cache, if (atlas == NULL) { + gboolean was_packed; + atlas = create_atlas (cache); g_ptr_array_add (cache->atlases, atlas); - stbrp_pack_rects (&atlas->context, &glyph_rect, 1); - g_assert (glyph_rect.was_packed); + was_packed = gsk_gl_texture_atlas_pack (atlas, + width, height, + &packed_x, &packed_y); + + g_assert (was_packed); } - value->tx = (float)glyph_rect.x / atlas->width; - value->ty = (float)glyph_rect.y / atlas->height; - value->tw = (float)glyph_rect.w / atlas->width; - value->th = (float)glyph_rect.h / atlas->height; + value->tx = (float)packed_x / atlas->width; + value->ty = (float)packed_y / atlas->height; + value->tw = (float)width / atlas->width; + value->th = (float)height / atlas->height; + value->used = TRUE; value->atlas = atlas; - atlas->pending_glyph.key = key; - atlas->pending_glyph.value = value; + if (atlas->user_data == NULL) + atlas->user_data = g_new0 (DirtyGlyph, 1); + + ((DirtyGlyph *)atlas->user_data)->key = key; + ((DirtyGlyph *)atlas->user_data)->value = value; #ifdef G_ENABLE_DEBUG if (GSK_RENDERER_DEBUG_CHECK (cache->renderer, GLYPH_CACHE)) @@ -191,18 +185,18 @@ add_to_cache (GskGLGlyphCache *cache, for (i = 0; i < cache->atlases->len; i++) { atlas = g_ptr_array_index (cache->atlases, i); - g_print ("\tGskGLGlyphAtlas %d (%dx%d): %.2g%% old pixels\n", + g_print ("\tGskGLTextureAtlas %d (%dx%d): %.2g%% old pixels\n", i, atlas->width, atlas->height, - 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height)); + gsk_gl_texture_atlas_get_unused_ratio (atlas)); } } #endif } static gboolean -render_glyph (const GskGLGlyphAtlas *atlas, - const DirtyGlyph *glyph, - GskImageRegion *region) +render_glyph (const GskGLTextureAtlas *atlas, + const DirtyGlyph *glyph, + GskImageRegion *region) { GlyphCacheKey *key = glyph->key; GskGLCachedGlyph *value = glyph->value; @@ -262,27 +256,28 @@ render_glyph (const GskGLGlyphAtlas *atlas, } static void -upload_dirty_glyph (GskGLGlyphCache *self, - GskGLGlyphAtlas *atlas) +upload_dirty_glyph (GskGLGlyphCache *self, + GskGLTextureAtlas *atlas) { GskImageRegion region; - g_assert (atlas->pending_glyph.key != NULL); + g_assert (atlas->user_data != NULL); gdk_gl_context_push_debug_group_printf (gsk_gl_driver_get_gl_context (self->gl_driver), - "Uploading glyph %d", atlas->pending_glyph.key->glyph); + "Uploading glyph %d", ((DirtyGlyph *)atlas->user_data)->key->glyph); - if (render_glyph (atlas, &atlas->pending_glyph, ®ion)) + if (render_glyph (atlas, (DirtyGlyph *)atlas->user_data, ®ion)) { - gsk_gl_image_upload_regions (atlas->image, self->gl_driver, 1, ®ion); + gsk_gl_image_upload_regions (&atlas->image, self->gl_driver, 1, ®ion); g_free (region.data); } gdk_gl_context_pop_debug_group (gsk_gl_driver_get_gl_context (self->gl_driver)); - atlas->pending_glyph.key = NULL; - atlas->pending_glyph.value = NULL; + /* TODO: This could be unnecessary. We can just reuse the allocated + * DirtyGlyph next time. */ + g_clear_pointer (&atlas->user_data, g_free); } const GskGLCachedGlyph * @@ -307,10 +302,13 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache, if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL) { - GskGLGlyphAtlas *atlas = value->atlas; + GskGLTextureAtlas *atlas = value->atlas; - if (atlas) - atlas->old_pixels -= value->draw_width * value->draw_height; + if (atlas && !value->used) + { + gsk_gl_texture_atlas_mark_used (atlas, value->draw_width, value->draw_height); + value->used = TRUE; + } value->timestamp = cache->timestamp; } @@ -352,23 +350,22 @@ GskGLImage * gsk_gl_glyph_cache_get_glyph_image (GskGLGlyphCache *self, const GskGLCachedGlyph *glyph) { - GskGLGlyphAtlas *atlas = glyph->atlas; + GskGLTextureAtlas *atlas = glyph->atlas; g_assert (atlas != NULL); - if (atlas->image == NULL) + if (atlas->image.texture_id == 0) { - atlas->image = g_new0 (GskGLImage, 1); - gsk_gl_image_create (atlas->image, self->gl_driver, atlas->width, atlas->height); + gsk_gl_image_create (&atlas->image, self->gl_driver, atlas->width, atlas->height); gdk_gl_context_label_object_printf (gsk_gl_driver_get_gl_context (self->gl_driver), - GL_TEXTURE, atlas->image->texture_id, - "Glyph atlas %d", atlas->image->texture_id); + GL_TEXTURE, atlas->image.texture_id, + "Glyph atlas %d", atlas->image.texture_id); } - if (atlas->pending_glyph.key != NULL) + if (atlas->user_data != NULL) upload_dirty_glyph (self, atlas); - return atlas->image; + return &atlas->image; } void @@ -382,46 +379,33 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self) self->timestamp++; - if ((self->timestamp - 1) % 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)) - { - const guint 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); + GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i); - if (atlas->old_pixels > MAX_OLD * atlas->width * atlas->height) + if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO) { GSK_RENDERER_NOTE(self->renderer, GLYPH_CACHE, - g_message ("Dropping atlas %d (%g.2%% old)", - i, 100.0 * (double)atlas->old_pixels / (double)(atlas->width * atlas->height))); + g_message ("Dropping atlas %d (%g.2%% old)", i, + gsk_gl_texture_atlas_get_unused_ratio (atlas))); +#if 0 static int kk; - g_message ("Dropping cache..."); - gsk_gl_image_write_to_png (atlas->image, self->gl_driver, + g_message ("Dropping cache... Ratio: %f", + gsk_gl_texture_atlas_get_unused_ratio (atlas)); + gsk_gl_image_write_to_png (&atlas->image, self->gl_driver, g_strdup_printf ("dropped_%d.png", kk++)); +#endif - if (atlas->image) + if (atlas->image.texture_id != 0) { - gsk_gl_image_destroy (atlas->image, self->gl_driver); - atlas->image->texture_id = 0; + gsk_gl_image_destroy (&atlas->image, self->gl_driver); + atlas->image.texture_id = 0; } /* Remove all glyphs that point to this atlas */ @@ -437,5 +421,23 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self) } } + /* 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)) + { + const guint age = self->timestamp - value->timestamp; + + if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL) + { + GskGLTextureAtlas *atlas = value->atlas; + + if (atlas && value->used) + { + gsk_gl_texture_atlas_mark_unused (atlas, value->draw_width, value->draw_height); + value->used = FALSE; + } + } + } + GSK_RENDERER_NOTE(self->renderer, GLYPH_CACHE, g_message ("Dropped %d glyphs", dropped)); } diff --git a/gsk/gl/gskglglyphcacheprivate.h b/gsk/gl/gskglglyphcacheprivate.h index 0132e33138..29427f0cd8 100644 --- a/gsk/gl/gskglglyphcacheprivate.h +++ b/gsk/gl/gskglglyphcacheprivate.h @@ -4,7 +4,7 @@ #include "gskgldriverprivate.h" #include "gskglimageprivate.h" #include "gskrendererprivate.h" -#include "stb_rect_pack.h" +#include "gskgltextureatlasprivate.h" #include <pango/pango.h> #include <gdk/gdk.h> @@ -35,21 +35,9 @@ struct _DirtyGlyph GskGLCachedGlyph *value; }; -typedef struct -{ - GskGLImage *image; - int width, height; - guint old_pixels; - - DirtyGlyph pending_glyph; - - struct stbrp_context context; - struct stbrp_node *nodes; -} GskGLGlyphAtlas; - struct _GskGLCachedGlyph { - GskGLGlyphAtlas *atlas; + GskGLTextureAtlas *atlas; float tx; float ty; @@ -64,6 +52,7 @@ struct _GskGLCachedGlyph float scale; guint64 timestamp; + guint used: 1; }; diff --git a/gsk/gl/gskglimage.c b/gsk/gl/gskglimage.c index 6d91b35568..49dd630bde 100644 --- a/gsk/gl/gskglimage.c +++ b/gsk/gl/gskglimage.c @@ -21,6 +21,7 @@ gsk_gl_image_destroy (GskGLImage *self, GskGLDriver *gl_driver) { gsk_gl_driver_destroy_texture (gl_driver, self->texture_id); + self->texture_id = 0; } void diff --git a/gsk/gl/gskgltextureatlas.c b/gsk/gl/gskgltextureatlas.c new file mode 100644 index 0000000000..3419a2d5a2 --- /dev/null +++ b/gsk/gl/gskgltextureatlas.c @@ -0,0 +1,84 @@ + +#include "gskgltextureatlasprivate.h" + + +void +gsk_gl_texture_atlas_init (GskGLTextureAtlas *self, + int width, + int height) +{ + memset (self, 0, sizeof (*self)); + + self->image.texture_id = 0; + self->width = width; + self->height = height; + + /* TODO: We might want to change the strategy about the amount of + * nodes here? stb_rect_pack.h says with is optimal. */ + self->nodes = g_malloc0 (sizeof (struct stbrp_node) * width); + stbrp_init_target (&self->context, + width, height, + self->nodes, + width); +} + +void +gsk_gl_texture_atlas_free (GskGLTextureAtlas *self) +{ + g_clear_pointer (&self->nodes, g_free); +} + +void +gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self, + int width, + int height) +{ + self->unused_pixels += (width * height); +} + + +void +gsk_gl_texture_atlas_mark_used (GskGLTextureAtlas *self, + int width, + int height) +{ + self->unused_pixels -= (width * height); + + g_assert (self->unused_pixels >= 0); +} + +gboolean +gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self, + int width, + int height, + int *out_x, + int *out_y) +{ + stbrp_rect rect; + + g_assert (out_x); + g_assert (out_y); + + rect.w = width; + rect.h = height; + + stbrp_pack_rects (&self->context, &rect, 1); + + if (rect.was_packed) + { + *out_x = rect.x; + *out_y = rect.y; + } + + return rect.was_packed; +} + +double +gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self) +{ + if (self->unused_pixels > 0) + return (double)(self->unused_pixels) / (double)(self->width * self->height); + + return 0.0; +} + diff --git a/gsk/gl/gskgltextureatlasprivate.h b/gsk/gl/gskgltextureatlasprivate.h new file mode 100644 index 0000000000..d3aea54142 --- /dev/null +++ b/gsk/gl/gskgltextureatlasprivate.h @@ -0,0 +1,49 @@ + +#ifndef __GSK_GL_TEXTURE_ATLAS_H__ +#define __GSK_GL_TEXTURE_ATLAS_H__ + +#include "stb_rect_pack.h" +#include "gskglimageprivate.h" +#include "gskgldriverprivate.h" + +struct _GskGLTextureAtlas +{ + struct stbrp_context context; + struct stbrp_node *nodes; + + int width; + int height; + + GskGLImage image; + + int unused_pixels; /* Pixels of rects that have been used at some point, + But are now unused. */ + + void *user_data; +}; +typedef struct _GskGLTextureAtlas GskGLTextureAtlas; + +void gsk_gl_texture_atlas_init (GskGLTextureAtlas *self, + int width, + int height); + +void gsk_gl_texture_atlas_free (GskGLTextureAtlas *self); + +void gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self, + int width, + int height); + +void gsk_gl_texture_atlas_mark_used (GskGLTextureAtlas *self, + int width, + int height); + + +gboolean gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self, + int width, + int height, + int *out_x, + int *out_y); + +double gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self); + +#endif diff --git a/gsk/meson.build b/gsk/meson.build index 6263bdc925..af99a928e4 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -45,6 +45,7 @@ gsk_private_sources = files([ 'gl/gskglrenderops.c', 'gl/gskglshadowcache.c', 'gl/gskglnodesample.c', + 'gl/gskgltextureatlas.c', 'gl/stb_rect_pack.c', ]) |