gpu: Add atlas support
... and use it for glyphs.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user