From f518d780ed32f2fd5dc85aa0ff8ca72f627b32f4 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 25 Sep 2023 21:12:20 +0200 Subject: [PATCH] gpu: Add atlas support ... and use it for glyphs. --- gsk/gpu/gskgldevice.c | 14 +++ gsk/gpu/gskgpudevice.c | 200 +++++++++++++++++++++++++++++++--- gsk/gpu/gskgpudeviceprivate.h | 3 + gsk/gpu/gskgpunodeprocessor.c | 8 +- gsk/gpu/gskgpuuploadop.c | 7 +- gsk/gpu/gskvulkandevice.c | 13 +++ 6 files changed, 223 insertions(+), 22 deletions(-) diff --git a/gsk/gpu/gskgldevice.c b/gsk/gpu/gskgldevice.c index 1e8fc06b62..281f25d481 100644 --- a/gsk/gpu/gskgldevice.c +++ b/gsk/gpu/gskgldevice.c @@ -86,6 +86,19 @@ gsk_gl_device_create_upload_image (GskGpuDevice *device, height); } +static GskGpuImage * +gsk_gl_device_create_atlas_image (GskGpuDevice *device, + gsize width, + gsize height) +{ + GskGLDevice *self = GSK_GL_DEVICE (device); + + return gsk_gl_image_new (self, + GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, + width, + height); +} + static void gsk_gl_device_finalize (GObject *object) { @@ -109,6 +122,7 @@ gsk_gl_device_class_init (GskGLDeviceClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); gpu_device_class->create_offscreen_image = gsk_gl_device_create_offscreen_image; + gpu_device_class->create_atlas_image = gsk_gl_device_create_atlas_image; gpu_device_class->create_upload_image = gsk_gl_device_create_upload_image; object_class->finalize = gsk_gl_device_finalize; diff --git a/gsk/gpu/gskgpudevice.c b/gsk/gpu/gskgpudevice.c index 60507fd720..2462aa16a7 100644 --- a/gsk/gpu/gskgpudevice.c +++ b/gsk/gpu/gskgpudevice.c @@ -8,14 +8,22 @@ #include "gdk/gdkdisplayprivate.h" #include "gdk/gdktextureprivate.h" +#define MAX_SLICES_PER_ATLAS 64 + +#define ATLAS_SIZE 1024 + +#define MAX_ATLAS_ITEM_SIZE 256 + typedef enum { GSK_GPU_CACHE_TEXTURE, - GSK_GPU_CACHE_GLYPH + GSK_GPU_CACHE_GLYPH, + GSK_GPU_CACHE_ATLAS } GskGpuCacheType; typedef struct _GskGpuCachedTexture GskGpuCachedTexture; typedef struct _GskGpuCachedGlyph GskGpuCachedGlyph; +typedef struct _GskGpuCachedAtlas GskGpuCachedAtlas; typedef struct _GskGpuCacheEntry GskGpuCacheEntry; struct _GskGpuCacheEntry @@ -24,6 +32,18 @@ struct _GskGpuCacheEntry guint64 last_use_timestamp; }; +struct _GskGpuCachedAtlas +{ + GskGpuCacheEntry entry; + GskGpuImage *image; + + gsize n_slices; + struct { + gsize width; + gsize height; + } slices[MAX_SLICES_PER_ATLAS]; +}; + struct _GskGpuCachedTexture { GskGpuCacheEntry entry; @@ -60,6 +80,8 @@ struct _GskGpuDevicePrivate GskGpuCacheEntries cache; guint cache_gc_source; GHashTable *glyph_cache; + + GskGpuCachedAtlas *current_atlas; }; G_DEFINE_TYPE_WITH_PRIVATE (GskGpuDevice, gsk_gpu_device, G_TYPE_OBJECT) @@ -112,6 +134,7 @@ gsk_gpu_device_gc (GskGpuDevice *self, } break; + case GSK_GPU_CACHE_ATLAS: case GSK_GPU_CACHE_GLYPH: break; @@ -141,6 +164,14 @@ gsk_gpu_device_clear_cache (GskGpuDevice *self) switch (entry->type) { + case GSK_GPU_CACHE_ATLAS: + { + GskGpuCachedAtlas *atlas = (GskGpuCachedAtlas *) entry; + + g_object_unref (atlas->image); + } + break; + case GSK_GPU_CACHE_TEXTURE: { GskGpuCachedTexture *texture = (GskGpuCachedTexture *) entry; @@ -250,6 +281,123 @@ gsk_gpu_device_create_upload_image (GskGpuDevice *self, return GSK_GPU_DEVICE_GET_CLASS (self)->create_upload_image (self, format, width, height); } +/* This rounds up to the next number that has <= 2 bits set: + * 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, ... + * That is roughly sqrt(2), so it should limit waste + */ +static gsize +round_up_atlas_size (gsize num) +{ + gsize storage = g_bit_storage (num); + + num = num + (((1 << storage) - 1) >> 2); + num &= (((gsize) 7) << storage) >> 2; + + return num; +} + +static gboolean +gsk_gpu_cached_atlas_allocate (GskGpuCachedAtlas *atlas, + gsize width, + gsize height, + gsize *out_x, + gsize *out_y) +{ + gsize i; + gsize waste, slice_waste; + gsize best_slice; + gsize y, best_y; + gboolean can_add_slice; + + best_y = 0; + best_slice = G_MAXSIZE; + can_add_slice = atlas->n_slices < MAX_SLICES_PER_ATLAS; + if (can_add_slice) + waste = height; /* Require less than 100% waste */ + else + waste = G_MAXSIZE; /* Accept any slice, we can't make better ones */ + + for (i = 0, y = 0; i < atlas->n_slices; y += atlas->slices[i].height, i++) + { + if (atlas->slices[i].height < height || ATLAS_SIZE - atlas->slices[i].width < width) + continue; + + slice_waste = atlas->slices[i].height - height; + if (slice_waste < waste) + { + waste = slice_waste; + best_slice = i; + best_y = y; + if (waste == 0) + break; + } + } + + if (best_slice >= i && i == atlas->n_slices) + { + if (!can_add_slice) + return FALSE; + + atlas->n_slices++; + if (atlas->n_slices == MAX_SLICES_PER_ATLAS) + atlas->slices[i].height = ATLAS_SIZE - y; + else + atlas->slices[i].height = round_up_atlas_size (MAX (height, 4)); + atlas->slices[i].width = 0; + best_y = y; + best_slice = i; + } + + *out_x = atlas->slices[best_slice].width; + *out_y = best_y; + + atlas->slices[best_slice].width += width; + g_assert (atlas->slices[best_slice].width <= ATLAS_SIZE); + + return TRUE; +} + +static GskGpuImage * +gsk_gpu_device_add_atlas_image (GskGpuDevice *self, + gint64 timestamp, + gsize width, + gsize height, + gsize *out_x, + gsize *out_y) +{ + GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); + + if (width > MAX_ATLAS_ITEM_SIZE || height > MAX_ATLAS_ITEM_SIZE) + return NULL; + + if (priv->current_atlas && + gsk_gpu_cached_atlas_allocate (priv->current_atlas, width, height, out_x, out_y)) + { + priv->current_atlas->entry.last_use_timestamp = timestamp; + return priv->current_atlas->image; + } + + priv->current_atlas = g_new (GskGpuCachedAtlas, 1); + *priv->current_atlas = (GskGpuCachedAtlas) { + .entry = { + .type = GSK_GPU_CACHE_ATLAS, + .last_use_timestamp = timestamp, + }, + .image = GSK_GPU_DEVICE_GET_CLASS (self)->create_atlas_image (self, ATLAS_SIZE, ATLAS_SIZE), + .n_slices = 0, + }; + + gsk_gpu_cache_entries_append (&priv->cache, (GskGpuCacheEntry *) priv->current_atlas); + + if (gsk_gpu_cached_atlas_allocate (priv->current_atlas, width, height, out_x, out_y)) + { + priv->current_atlas->entry.last_use_timestamp = timestamp; + return priv->current_atlas->image; + } + + return NULL; +} + GskGpuImage * gsk_gpu_device_lookup_texture_image (GskGpuDevice *self, GdkTexture *texture, @@ -321,6 +469,9 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, GskGpuCachedGlyph *cache; PangoRectangle ink_rect; graphene_rect_t rect; + graphene_point_t origin; + GskGpuImage *image; + gsize atlas_x, atlas_y, padding; cache = g_hash_table_lookup (priv->glyph_cache, &lookup); if (cache) @@ -333,10 +484,30 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, cache = g_new (GskGpuCachedGlyph, 1); pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL); - rect.origin.x = floor (ink_rect.x * scale / PANGO_SCALE) - 1; - rect.origin.y = floor (ink_rect.y * scale / PANGO_SCALE) - 1; - rect.size.width = ceil ((ink_rect.x + ink_rect.width) * scale / PANGO_SCALE) - rect.origin.x + 2; - rect.size.height = ceil ((ink_rect.y + ink_rect.height) * scale / PANGO_SCALE) - rect.origin.y + 2; + origin.x = floor (ink_rect.x * scale / PANGO_SCALE); + origin.y = floor (ink_rect.y * scale / PANGO_SCALE); + rect.size.width = ceil ((ink_rect.x + ink_rect.width) * scale / PANGO_SCALE) - origin.x; + rect.size.height = ceil ((ink_rect.y + ink_rect.height) * scale / PANGO_SCALE) - origin.y; + padding = 1; + + image = gsk_gpu_device_add_atlas_image (self, + gsk_gpu_frame_get_timestamp (frame), + rect.size.width + 2 * padding, rect.size.height + 2 * padding, + &atlas_x, &atlas_y); + if (image) + { + g_object_ref (image); + rect.origin.x = atlas_x + padding; + rect.origin.y = atlas_y + padding; + } + else + { + image = gsk_gpu_device_create_upload_image (self, GDK_MEMORY_DEFAULT, rect.size.width, rect.size.height), + rect.origin.x = 0; + rect.origin.y = 0; + padding = 0; + } + *cache = (GskGpuCachedGlyph) { .entry = { @@ -347,10 +518,10 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, .glyph = glyph, .flags = flags, .scale = scale, - .image = gsk_gpu_device_create_upload_image (self, GDK_MEMORY_DEFAULT, rect.size.width, rect.size.height), - .bounds = GRAPHENE_RECT_INIT (0, 0, rect.size.width, rect.size.height), - .origin = GRAPHENE_POINT_INIT (-rect.origin.x + (flags & 3) / 4.f, - -rect.origin.y + ((flags >> 2) & 3) / 4.f) + .bounds = rect, + .image = image, + .origin = GRAPHENE_POINT_INIT (- origin.x + (flags & 3) / 4.f, + - origin.y + ((flags >> 2) & 3) / 4.f) }; gsk_gpu_upload_glyph_op (frame, @@ -358,13 +529,14 @@ gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, font, glyph, &(cairo_rectangle_int_t) { - .x = 0, - .y = 0, - .width = rect.size.width, - .height = rect.size.height + .x = rect.origin.x - padding, + .y = rect.origin.y - padding, + .width = rect.size.width + 2 * padding, + .height = rect.size.height + 2 * padding, }, scale, - &cache->origin); + &GRAPHENE_POINT_INIT (cache->origin.x + 1, + cache->origin.y + 1)); g_hash_table_insert (priv->glyph_cache, cache, cache); gsk_gpu_cache_entries_append (&priv->cache, (GskGpuCacheEntry *) cache); diff --git a/gsk/gpu/gskgpudeviceprivate.h b/gsk/gpu/gskgpudeviceprivate.h index 48aebe9c52..661158d372 100644 --- a/gsk/gpu/gskgpudeviceprivate.h +++ b/gsk/gpu/gskgpudeviceprivate.h @@ -28,6 +28,9 @@ struct _GskGpuDeviceClass GdkMemoryDepth depth, gsize width, gsize height); + GskGpuImage * (* create_atlas_image) (GskGpuDevice *self, + gsize width, + gsize height); GskGpuImage * (* create_upload_image) (GskGpuDevice *self, GdkMemoryFormat format, gsize width, diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index e729939bd4..4afbb47bca 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -1502,14 +1502,10 @@ gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self, &glyph_bounds, &glyph_offset); - graphene_rect_scale (&glyph_bounds, inv_scale, inv_scale, &glyph_bounds); + graphene_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); + graphene_rect_scale (&GRAPHENE_RECT_INIT(0, 0, glyph_bounds.size.width, glyph_bounds.size.height), inv_scale, inv_scale, &glyph_bounds); glyph_offset = GRAPHENE_POINT_INIT (offset.x - glyph_offset.x * inv_scale + (float) glyphs[i].geometry.x_offset / PANGO_SCALE, offset.y - glyph_offset.y * inv_scale + (float) glyphs[i].geometry.y_offset / PANGO_SCALE); - glyph_tex_rect = GRAPHENE_RECT_INIT ( - 0, 0, - gsk_gpu_image_get_width (image) * inv_scale, - gsk_gpu_image_get_height (image) * inv_scale - ); if (gsk_text_node_has_color_glyphs (node)) gsk_gpu_texture_op (self->frame, gsk_gpu_clip_get_shader_clip (&self->clip, &glyph_offset, &glyph_bounds), diff --git a/gsk/gpu/gskgpuuploadop.c b/gsk/gpu/gskgpuuploadop.c index edcd20a9be..fb98752511 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -504,12 +504,15 @@ gsk_gpu_upload_glyph_op_draw (GskGpuOp *op, cairo_surface_set_device_scale (surface, self->scale, self->scale); cr = cairo_create (surface); - - /* Make sure the entire surface is initialized to black */ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + /* Make sure the entire surface is initialized to black */ + cairo_set_source_rgba (cr, 0, 0, 0, 0); + cairo_rectangle (cr, 0.0, 0.0, self->area.width, self->area.height); + cairo_fill (cr); + /* Draw glyph */ cairo_set_source_rgba (cr, 1, 1, 1, 1); diff --git a/gsk/gpu/gskvulkandevice.c b/gsk/gpu/gskvulkandevice.c index ae84f94444..09ae3b0bb1 100644 --- a/gsk/gpu/gskvulkandevice.c +++ b/gsk/gpu/gskvulkandevice.c @@ -109,6 +109,18 @@ gsk_vulkan_device_create_offscreen_image (GskGpuDevice *device, height); } +static GskGpuImage * +gsk_vulkan_device_create_atlas_image (GskGpuDevice *device, + gsize width, + gsize height) +{ + GskVulkanDevice *self = GSK_VULKAN_DEVICE (device); + + return gsk_vulkan_image_new_for_atlas (self, + width, + height); +} + static GskGpuImage * gsk_vulkan_device_create_upload_image (GskGpuDevice *device, GdkMemoryFormat format, @@ -187,6 +199,7 @@ gsk_vulkan_device_class_init (GskVulkanDeviceClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); gpu_device_class->create_offscreen_image = gsk_vulkan_device_create_offscreen_image; + gpu_device_class->create_atlas_image = gsk_vulkan_device_create_atlas_image; gpu_device_class->create_upload_image = gsk_vulkan_device_create_upload_image; object_class->finalize = gsk_vulkan_device_finalize;