gpu: Add glyphs support

This is very rudimentary, but it's a step in the direction of getting
text going.

There's lots of things missing still.
This commit is contained in:
Benjamin Otte
2023-09-06 02:11:39 +02:00
parent 14081c2c59
commit 5d5e63eb1d
8 changed files with 435 additions and 5 deletions

View File

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

View File

@@ -2,6 +2,8 @@
#include "gskgputypesprivate.h"
#include <graphene.h>
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

View File

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

View File

@@ -33,5 +33,6 @@ typedef enum {
GSK_GPU_PATTERN_OPACITY,
GSK_GPU_PATTERN_TEXTURE,
GSK_GPU_PATTERN_COLOR_MATRIX,
GSK_GPU_PATTERN_GLYPHS,
} GskGpuPatternType;

View File

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

View File

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

View File

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

View File

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