gpu: Add atlas support

... and use it for glyphs.
This commit is contained in:
Benjamin Otte
2023-09-25 21:12:20 +02:00
parent 9045431bde
commit f518d780ed
6 changed files with 223 additions and 22 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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),

View File

@@ -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);

View File

@@ -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;