diff --git a/gsk/gpu/gskgpudevice.c b/gsk/gpu/gskgpudevice.c index aa3c9c5b3d..60507fd720 100644 --- a/gsk/gpu/gskgpudevice.c +++ b/gsk/gpu/gskgpudevice.c @@ -2,15 +2,20 @@ #include "gskgpudeviceprivate.h" +#include "gskgpuframeprivate.h" +#include "gskgpuuploadopprivate.h" + #include "gdk/gdkdisplayprivate.h" #include "gdk/gdktextureprivate.h" typedef enum { - GSK_GPU_CACHE_TEXTURE + GSK_GPU_CACHE_TEXTURE, + GSK_GPU_CACHE_GLYPH } GskGpuCacheType; typedef struct _GskGpuCachedTexture GskGpuCachedTexture; +typedef struct _GskGpuCachedGlyph GskGpuCachedGlyph; typedef struct _GskGpuCacheEntry GskGpuCacheEntry; struct _GskGpuCacheEntry @@ -26,6 +31,19 @@ struct _GskGpuCachedTexture GskGpuImage *image; }; +struct _GskGpuCachedGlyph +{ + GskGpuCacheEntry entry; + PangoFont *font; + PangoGlyph glyph; + GskGpuGlyphLookupFlags flags; + float scale; + + GskGpuImage *image; + graphene_rect_t bounds; + graphene_point_t origin; +}; + #define GDK_ARRAY_NAME gsk_gpu_cache_entries #define GDK_ARRAY_TYPE_NAME GskGpuCacheEntries #define GDK_ARRAY_ELEMENT_TYPE GskGpuCacheEntry * @@ -41,10 +59,35 @@ struct _GskGpuDevicePrivate GskGpuCacheEntries cache; guint cache_gc_source; + GHashTable *glyph_cache; }; G_DEFINE_TYPE_WITH_PRIVATE (GskGpuDevice, gsk_gpu_device, G_TYPE_OBJECT) +static guint +gsk_gpu_cached_glyph_hash (gconstpointer data) +{ + const GskGpuCachedGlyph *glyph = data; + + return GPOINTER_TO_UINT (glyph->font) ^ + glyph->glyph ^ + (glyph->flags << 24) ^ + ((guint) glyph->scale * PANGO_SCALE); +} + +static gboolean +gsk_gpu_cached_glyph_equal (gconstpointer v1, + gconstpointer v2) +{ + const GskGpuCachedGlyph *glyph1 = v1; + const GskGpuCachedGlyph *glyph2 = v2; + + return glyph1->font == glyph2->font + && glyph1->glyph == glyph2->glyph + && glyph1->flags == glyph2->flags + && glyph1->scale == glyph2->scale; +} + void gsk_gpu_device_gc (GskGpuDevice *self, gint64 timestamp) @@ -69,6 +112,9 @@ gsk_gpu_device_gc (GskGpuDevice *self, } break; + case GSK_GPU_CACHE_GLYPH: + break; + default: g_assert_not_reached (); break; @@ -105,6 +151,16 @@ gsk_gpu_device_clear_cache (GskGpuDevice *self) } break; + case GSK_GPU_CACHE_GLYPH: + { + GskGpuCachedGlyph *glyph = (GskGpuCachedGlyph *) entry; + + g_object_unref (glyph->font); + g_object_unref (glyph->image); + } + + break; + default: g_assert_not_reached (); break; @@ -122,6 +178,7 @@ gsk_gpu_device_dispose (GObject *object) GskGpuDevice *self = GSK_GPU_DEVICE (object); GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); + g_hash_table_unref (priv->glyph_cache); gsk_gpu_device_clear_cache (self); gsk_gpu_cache_entries_clear (&priv->cache); @@ -154,6 +211,8 @@ gsk_gpu_device_init (GskGpuDevice *self) GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); gsk_gpu_cache_entries_init (&priv->cache); + priv->glyph_cache = g_hash_table_new (gsk_gpu_cached_glyph_hash, + gsk_gpu_cached_glyph_equal); } void @@ -241,3 +300,77 @@ gsk_gpu_device_cache_texture_image (GskGpuDevice *self, gdk_texture_set_render_data (texture, self, cache, gsk_gpu_device_cache_texture_destroy_cb); gsk_gpu_cache_entries_append (&priv->cache, (GskGpuCacheEntry *) cache); } + +GskGpuImage * +gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, + GskGpuFrame *frame, + PangoFont *font, + PangoGlyph glyph, + GskGpuGlyphLookupFlags flags, + float scale, + graphene_rect_t *out_bounds, + graphene_point_t *out_origin) +{ + GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); + GskGpuCachedGlyph lookup = { + .font = font, + .glyph = glyph, + .flags = flags, + .scale = scale + }; + GskGpuCachedGlyph *cache; + PangoRectangle ink_rect; + graphene_rect_t rect; + + cache = g_hash_table_lookup (priv->glyph_cache, &lookup); + if (cache) + { + cache->entry.last_use_timestamp = gsk_gpu_frame_get_timestamp (frame); + *out_bounds = cache->bounds; + *out_origin = cache->origin; + return cache->image; + } + + 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; + + *cache = (GskGpuCachedGlyph) { + .entry = { + .type = GSK_GPU_CACHE_GLYPH, + .last_use_timestamp = gsk_gpu_frame_get_timestamp (frame), + }, + .font = g_object_ref (font), + .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) + }; + + gsk_gpu_upload_glyph_op (frame, + cache->image, + font, + glyph, + &(cairo_rectangle_int_t) { + .x = 0, + .y = 0, + .width = rect.size.width, + .height = rect.size.height + }, + scale, + &cache->origin); + + g_hash_table_insert (priv->glyph_cache, cache, cache); + gsk_gpu_cache_entries_append (&priv->cache, (GskGpuCacheEntry *) cache); + + *out_bounds = cache->bounds; + *out_origin = cache->origin; + return cache->image; +} + diff --git a/gsk/gpu/gskgpudeviceprivate.h b/gsk/gpu/gskgpudeviceprivate.h index f8ba4d9d8f..48aebe9c52 100644 --- a/gsk/gpu/gskgpudeviceprivate.h +++ b/gsk/gpu/gskgpudeviceprivate.h @@ -2,6 +2,8 @@ #include "gskgputypesprivate.h" +#include + G_BEGIN_DECLS #define GSK_TYPE_GPU_DEVICE (gsk_gpu_device_get_type ()) @@ -58,6 +60,26 @@ void gsk_gpu_device_cache_texture_image (GskGpuD gint64 timestamp, GskGpuImage *image); +typedef enum +{ + GSK_GPU_GLYPH_X_OFFSET_1 = 0x1, + GSK_GPU_GLYPH_X_OFFSET_2 = 0x2, + GSK_GPU_GLYPH_X_OFFSET_3 = 0x3, + GSK_GPU_GLYPH_Y_OFFSET_1 = 0x4, + GSK_GPU_GLYPH_Y_OFFSET_2 = 0x8, + GSK_GPU_GLYPH_Y_OFFSET_3 = 0xC +} GskGpuGlyphLookupFlags; + +GskGpuImage * gsk_gpu_device_lookup_glyph_image (GskGpuDevice *self, + GskGpuFrame *frame, + PangoFont *font, + PangoGlyph glyph, + GskGpuGlyphLookupFlags flags, + float scale, + graphene_rect_t *out_bounds, + graphene_point_t *out_origin); + + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskGpuDevice, g_object_unref) G_END_DECLS diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 1a54fe36b0..076894108e 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -766,6 +766,97 @@ gsk_gpu_node_processor_create_texture_pattern (GskGpuNodeProcessor *self, return TRUE; } +static gboolean +gsk_gpu_node_processor_create_glyph_pattern (GskGpuNodeProcessor *self, + GskGpuBufferWriter *writer, + GskRenderNode *node, + GskGpuShaderImage *images, + gsize n_images, + gsize *out_n_images) +{ + GskGpuDevice *device; + const PangoGlyphInfo *glyphs; + PangoFont *font; + guint num_glyphs; + gsize i; + float scale, inv_scale; + guint n_used; + guint32 tex_id; + GskGpuImage *last_image; + graphene_point_t offset; + + if (gsk_text_node_has_color_glyphs (node)) + return FALSE; + + device = gsk_gpu_frame_get_device (self->frame); + num_glyphs = gsk_text_node_get_num_glyphs (node); + glyphs = gsk_text_node_get_glyphs (node, NULL); + font = gsk_text_node_get_font (node); + offset = *gsk_text_node_get_offset (node); + offset.x += self->offset.x; + offset.y += self->offset.y; + + scale = MAX (graphene_vec2_get_x (&self->scale), graphene_vec2_get_y (&self->scale)); + inv_scale = 1.f / scale; + + gsk_gpu_buffer_writer_append_uint (writer, GSK_GPU_PATTERN_GLYPHS); + gsk_gpu_buffer_writer_append_rgba (writer, gsk_text_node_get_color (node)); + gsk_gpu_buffer_writer_append_uint (writer, num_glyphs); + + last_image = NULL; + n_used = 0; + for (i = 0; i < num_glyphs; i++) + { + GskGpuImage *image; + graphene_rect_t glyph_bounds; + graphene_point_t glyph_offset; + + image = gsk_gpu_device_lookup_glyph_image (device, + self->frame, + font, + glyphs[i].glyph, + 0, + scale, + &glyph_bounds, + &glyph_offset); + + if (image != last_image) + { + if (n_used >= n_images) + return FALSE; + + tex_id = gsk_gpu_frame_get_image_descriptor (self->frame, image, GSK_GPU_SAMPLER_DEFAULT); + images[n_used].image = image; + images[n_used].sampler = GSK_GPU_SAMPLER_DEFAULT; + images[n_used].descriptor = tex_id; + last_image = image; + n_used++; + } + + graphene_rect_scale (&glyph_bounds, 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); + + gsk_gpu_buffer_writer_append_uint (writer, tex_id); + gsk_gpu_buffer_writer_append_rect (writer, + &glyph_bounds, + &glyph_offset); + gsk_gpu_buffer_writer_append_rect (writer, + &GRAPHENE_RECT_INIT ( + 0, 0, + gsk_gpu_image_get_width (image) * inv_scale, + gsk_gpu_image_get_height (image) * inv_scale + ), + &glyph_offset); + + offset.x += (float) glyphs[i].geometry.width / PANGO_SCALE; + } + + *out_n_images = n_used; + + return TRUE; +} + static gboolean gsk_gpu_node_processor_create_opacity_pattern (GskGpuNodeProcessor *self, GskGpuBufferWriter *writer, @@ -944,7 +1035,7 @@ static const struct [GSK_TEXT_NODE] = { 0, NULL, - NULL, + gsk_gpu_node_processor_create_glyph_pattern, }, [GSK_BLUR_NODE] = { 0, diff --git a/gsk/gpu/gskgputypesprivate.h b/gsk/gpu/gskgputypesprivate.h index 4a99a46075..63f1b4a7c5 100644 --- a/gsk/gpu/gskgputypesprivate.h +++ b/gsk/gpu/gskgputypesprivate.h @@ -33,5 +33,6 @@ typedef enum { GSK_GPU_PATTERN_OPACITY, GSK_GPU_PATTERN_TEXTURE, GSK_GPU_PATTERN_COLOR_MATRIX, + GSK_GPU_PATTERN_GLYPHS, } GskGpuPatternType; diff --git a/gsk/gpu/gskgpuuploadop.c b/gsk/gpu/gskgpuuploadop.c index c85676795b..edcd20a9be 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -445,3 +445,151 @@ gsk_gpu_upload_cairo_op (GskGpuFrame *frame, return self->image; } +typedef struct _GskGpuUploadGlyphOp GskGpuUploadGlyphOp; + +struct _GskGpuUploadGlyphOp +{ + GskGpuOp op; + + GskGpuImage *image; + cairo_rectangle_int_t area; + PangoFont *font; + PangoGlyph glyph; + float scale; + graphene_point_t origin; + + GskGpuBuffer *buffer; +}; + +static void +gsk_gpu_upload_glyph_op_finish (GskGpuOp *op) +{ + GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op; + + g_object_unref (self->image); + g_object_unref (self->font); + + g_clear_object (&self->buffer); +} + +static void +gsk_gpu_upload_glyph_op_print (GskGpuOp *op, + GskGpuFrame *frame, + GString *string, + guint indent) +{ + GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op; + + gsk_gpu_print_op (string, indent, "upload-glyph"); + gsk_gpu_print_int_rect (string, &self->area); + g_string_append_printf (string, "glyph %u @ %g ", self->glyph, self->scale); + gsk_gpu_print_newline (string); +} + +static void +gsk_gpu_upload_glyph_op_draw (GskGpuOp *op, + guchar *data, + gsize stride) +{ + GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op; + cairo_surface_t *surface; + cairo_t *cr; + + surface = cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_ARGB32, + self->area.width, + self->area.height, + stride); + cairo_surface_set_device_offset (surface, self->origin.x, self->origin.y); + 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); + + /* Draw glyph */ + cairo_set_source_rgba (cr, 1, 1, 1, 1); + + pango_cairo_show_glyph_string (cr, + self->font, + &(PangoGlyphString) { + .num_glyphs = 1, + .glyphs = (PangoGlyphInfo[1]) { { + .glyph = self->glyph + } } + }); + + cairo_destroy (cr); + + cairo_surface_finish (surface); + cairo_surface_destroy (surface); +} + +#ifdef GDK_RENDERING_VULKAN +static GskGpuOp * +gsk_gpu_upload_glyph_op_vk_command (GskGpuOp *op, + GskGpuFrame *frame, + VkRenderPass render_pass, + VkFormat format, + VkCommandBuffer command_buffer) +{ + GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op; + + return gsk_gpu_upload_op_vk_command_with_area (op, + frame, + command_buffer, + GSK_VULKAN_IMAGE (self->image), + &self->area, + gsk_gpu_upload_glyph_op_draw, + &self->buffer); +} +#endif + +static GskGpuOp * +gsk_gpu_upload_glyph_op_gl_command (GskGpuOp *op, + GskGpuFrame *frame, + gsize flip_y) +{ + GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op; + + return gsk_gpu_upload_op_gl_command_with_area (op, + frame, + self->image, + &self->area, + gsk_gpu_upload_glyph_op_draw); +} + +static const GskGpuOpClass GSK_GPU_UPLOAD_GLYPH_OP_CLASS = { + GSK_GPU_OP_SIZE (GskGpuUploadGlyphOp), + GSK_GPU_STAGE_UPLOAD, + gsk_gpu_upload_glyph_op_finish, + gsk_gpu_upload_glyph_op_print, +#ifdef GDK_RENDERING_VULKAN + gsk_gpu_upload_glyph_op_vk_command, +#endif + gsk_gpu_upload_glyph_op_gl_command, +}; + +void +gsk_gpu_upload_glyph_op (GskGpuFrame *frame, + GskGpuImage *image, + PangoFont *font, + const PangoGlyph glyph, + const cairo_rectangle_int_t *area, + float scale, + const graphene_point_t *origin) +{ + GskGpuUploadGlyphOp *self; + + self = (GskGpuUploadGlyphOp *) gsk_gpu_op_alloc (frame, &GSK_GPU_UPLOAD_GLYPH_OP_CLASS); + + self->image = g_object_ref (image); + self->area = *area; + self->font = g_object_ref (font); + self->glyph = glyph; + self->scale = scale; + self->origin = *origin; +} diff --git a/gsk/gpu/gskgpuuploadopprivate.h b/gsk/gpu/gskgpuuploadopprivate.h index f460ac7205..9775b5d404 100644 --- a/gsk/gpu/gskgpuuploadopprivate.h +++ b/gsk/gpu/gskgpuuploadopprivate.h @@ -16,10 +16,11 @@ GskGpuImage * gsk_gpu_upload_cairo_op (GskGpuF void gsk_gpu_upload_glyph_op (GskGpuFrame *frame, GskGpuImage *image, - cairo_rectangle_int_t *area, PangoFont *font, - PangoGlyphInfo *glyph_info, - float scale); + PangoGlyph glyph, + const cairo_rectangle_int_t *area, + float scale, + const graphene_point_t *origin); G_END_DECLS diff --git a/gsk/gpu/shaders/enums.glsl b/gsk/gpu/shaders/enums.glsl index 7b7e50cfbb..8623d1b15b 100644 --- a/gsk/gpu/shaders/enums.glsl +++ b/gsk/gpu/shaders/enums.glsl @@ -10,5 +10,6 @@ #define GSK_GPU_PATTERN_OPACITY 2u #define GSK_GPU_PATTERN_TEXTURE 3u #define GSK_GPU_PATTERN_COLOR_MATRIX 4u +#define GSK_GPU_PATTERN_GLYPHS 5u #endif diff --git a/gsk/gpu/shaders/pattern.glsl b/gsk/gpu/shaders/pattern.glsl index 6d02d99bfd..5d293c08fa 100644 --- a/gsk/gpu/shaders/pattern.glsl +++ b/gsk/gpu/shaders/pattern.glsl @@ -2,6 +2,7 @@ #define _PATTERN_ #include "common.glsl" +#include "rect.glsl" #ifdef GSK_FRAGMENT_SHADER @@ -27,6 +28,12 @@ read_vec4 (inout uint reader) return vec4 (read_float (reader), read_float (reader), read_float (reader), read_float (reader)); } +Rect +read_rect (inout uint reader) +{ + return rect_from_gsk (read_vec4 (reader)); +} + mat4 read_mat4 (inout uint reader) { @@ -68,6 +75,29 @@ color_matrix_pattern (inout uint reader, color = color_premultiply (color); } +vec4 +glyphs_pattern (inout uint reader, + vec2 pos) +{ + float opacity = 0.0; + vec4 color = color_premultiply (read_vec4 (reader)); + uint num_glyphs = read_uint (reader); + uint i; + + for (i = 0u; i < num_glyphs; i++) + { + uint tex_id = read_uint (reader); + Rect glyph_bounds = read_rect (reader); + vec4 tex_rect = read_vec4 (reader); + + float coverage = rect_coverage (glyph_bounds, pos); + if (coverage > 0.0) + opacity += coverage * texture (gsk_get_texture (tex_id), (pos - push.scale * tex_rect.xy) / (push.scale * tex_rect.zw)).a; + } + + return color * opacity; +} + vec4 texture_pattern (inout uint reader, vec2 pos) @@ -106,6 +136,9 @@ pattern (uint reader, case GSK_GPU_PATTERN_TEXTURE: color = texture_pattern (reader, pos); break; + case GSK_GPU_PATTERN_GLYPHS: + color = glyphs_pattern (reader, pos); + break; case GSK_GPU_PATTERN_COLOR_MATRIX: color_matrix_pattern (reader, color, pos); break;