From 4715afc822c21ad1921b1366166afc0cbb74b42d Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 17 Mar 2022 16:53:17 -0700 Subject: [PATCH] gsk/gl: Add a texture library for Glyphy This adds a new texture library that can upload SDF data from libglyphy into regions of a texture atlas so that it can be accessed by Glyphy shaders in the appropriate place and format. Some of the placement positioning may seem odd in that it needs to follow a certain format to be decoded from the Glyphy shaders. --- gsk/gl/gskgldriver.c | 5 + gsk/gl/gskgldriverprivate.h | 1 + gsk/gl/gskglglyphylibrary.c | 390 ++++++++++++++++++++++++++++ gsk/gl/gskglglyphylibraryprivate.h | 106 ++++++++ gsk/gl/gskgltexturelibraryprivate.h | 6 +- gsk/gl/gskgltypesprivate.h | 1 + gsk/meson.build | 1 + 7 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 gsk/gl/gskglglyphylibrary.c create mode 100644 gsk/gl/gskglglyphylibraryprivate.h diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c index 225cae6920..8c1306b933 100644 --- a/gsk/gl/gskgldriver.c +++ b/gsk/gl/gskgldriver.c @@ -32,6 +32,7 @@ #include "gskglcommandqueueprivate.h" #include "gskglcompilerprivate.h" #include "gskglglyphlibraryprivate.h" +#include "gskglglyphylibraryprivate.h" #include "gskgliconlibraryprivate.h" #include "gskglprogramprivate.h" #include "gskglshadowlibraryprivate.h" @@ -273,6 +274,7 @@ gsk_gl_driver_dispose (GObject *object) } g_clear_object (&self->glyphs_library); + g_clear_object (&self->glyphy_library); g_clear_object (&self->icons_library); g_clear_object (&self->shadows_library); @@ -463,6 +465,7 @@ gsk_gl_driver_new (GskGLCommandQueue *command_queue, } self->glyphs_library = gsk_gl_glyph_library_new (self); + self->glyphy_library = gsk_gl_glyphy_library_new (self); self->icons_library = gsk_gl_icon_library_new (self); self->shadows_library = gsk_gl_shadow_library_new (self); @@ -573,6 +576,8 @@ gsk_gl_driver_begin_frame (GskGLDriver *self, self->current_frame_id); gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs_library), self->current_frame_id); + gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphy_library), + self->current_frame_id); /* Cleanup old shadows */ gsk_gl_shadow_library_begin_frame (self->shadows_library); diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h index 021792bd8c..b360551a87 100644 --- a/gsk/gl/gskgldriverprivate.h +++ b/gsk/gl/gskgldriverprivate.h @@ -97,6 +97,7 @@ struct _GskGLDriver GskGLCommandQueue *command_queue; GskGLGlyphLibrary *glyphs_library; + GskGLGlyphyLibrary *glyphy_library; GskGLIconLibrary *icons_library; GskGLShadowLibrary *shadows_library; diff --git a/gsk/gl/gskglglyphylibrary.c b/gsk/gl/gskglglyphylibrary.c new file mode 100644 index 0000000000..b86561a9a3 --- /dev/null +++ b/gsk/gl/gskglglyphylibrary.c @@ -0,0 +1,390 @@ +/* gskglglyphylibrary.c + * + * Copyright 2020 Christian Hergert + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/* Some of the glyphy cache is based upon the original glyphy code. + * It's license is provided below. + */ +/* + * Copyright 2012 Google, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Google Author(s): Behdad Esfahbod + */ + +#include "config.h" + +#include +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskglglyphylibraryprivate.h" +#include "gskdebugprivate.h" + +#include + +#define TOLERANCE (1/2048.) +#define MIN_FONT_SIZE 10 + +/* We split the atlas into cells of size 64x8, so the minimum number of + * bytes we store per glyph is 2048, and an atlas of size 2048x1024 can + * hold at most 4096 glyphs. We need 5 and 7 bits to store the position + * of a glyph in the atlas. + * + * We allocate each glyph a column of as many vertically adjacent cells + * as it needs. + */ +#define ITEM_W 64 +#define ITEM_H_QUANTUM 8 + +G_DEFINE_TYPE (GskGLGlyphyLibrary, gsk_gl_glyphy_library, GSK_TYPE_GL_TEXTURE_LIBRARY) + +GskGLGlyphyLibrary * +gsk_gl_glyphy_library_new (GskGLDriver *driver) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + + return g_object_new (GSK_TYPE_GL_GLYPHY_LIBRARY, + "driver", driver, + NULL); +} + +static guint +gsk_gl_glyphy_key_hash (gconstpointer data) +{ + const GskGLGlyphyKey *key = data; + + /* malloc()'d pointers already guarantee 3 bits from the LSB on 64-bit and + * 2 bits from the LSB on 32-bit. Shift by enough to give us 256 entries + * in our front cache for the glyph since languages will naturally cluster + * for us. + */ + +#if GLIB_SIZEOF_VOID_P == 4 + return (guint)(GPOINTER_TO_SIZE (key->font) << 6) ^ key->glyph; +#else + return (guint)(GPOINTER_TO_SIZE (key->font) << 5) ^ key->glyph; +#endif +} + +static gboolean +gsk_gl_glyphy_key_equal (gconstpointer v1, + gconstpointer v2) +{ + return memcmp (v1, v2, sizeof (GskGLGlyphyKey)) == 0; +} + +static void +gsk_gl_glyphy_key_free (gpointer data) +{ + GskGLGlyphyKey *key = data; + + g_clear_object (&key->font); + g_slice_free (GskGLGlyphyKey, key); +} + +static void +gsk_gl_glyphy_value_free (gpointer data) +{ + g_slice_free (GskGLGlyphyValue, data); +} + +static void +gsk_gl_glyphy_library_clear_cache (GskGLTextureLibrary *library) +{ + GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)library; + + g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self)); + + memset (self->front, 0, sizeof self->front); +} + +static void +gsk_gl_glyphy_library_init_atlas (GskGLTextureLibrary *library, + GskGLTextureAtlas *atlas) +{ + g_assert (GSK_IS_GL_GLYPHY_LIBRARY (library)); + g_assert (atlas != NULL); + + atlas->cursor_x = 0; + atlas->cursor_y = 0; +} + +static gboolean +gsk_gl_glyphy_library_allocate (GskGLTextureLibrary *library, + GskGLTextureAtlas *atlas, + int width, + int height, + int *out_x, + int *out_y) +{ + GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)library; + int cursor_save_x; + int cursor_save_y; + + g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self)); + g_assert (atlas != NULL); + + cursor_save_x = atlas->cursor_x; + cursor_save_y = atlas->cursor_y; + + if ((height & (self->item_h_q-1)) != 0) + height = (height + self->item_h_q - 1) & ~(self->item_h_q - 1); + + /* Require allocations in columns of 64 and rows of 8 */ + g_assert (width == self->item_w); + g_assert ((height % self->item_h_q) == 0); + + if (atlas->cursor_y + height > atlas->height) + { + /* Go to next column */ + atlas->cursor_x += self->item_w; + atlas->cursor_y = 0; + } + + if (atlas->cursor_x + width <= atlas->width && + atlas->cursor_y + height <= atlas->height) + { + *out_x = atlas->cursor_x; + *out_y = atlas->cursor_y; + atlas->cursor_y += height; + return TRUE; + } + + atlas->cursor_x = cursor_save_x; + atlas->cursor_y = cursor_save_y; + + return FALSE; +} + +static void +gsk_gl_glyphy_library_finalize (GObject *object) +{ + GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)object; + + g_clear_pointer (&self->acc, glyphy_arc_accumulator_destroy); + g_clear_pointer (&self->acc_endpoints, g_array_unref); + + G_OBJECT_CLASS (gsk_gl_glyphy_library_parent_class)->finalize (object); +} + +static void +gsk_gl_glyphy_library_class_init (GskGLGlyphyLibraryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass); + + object_class->finalize = gsk_gl_glyphy_library_finalize; + + library_class->allocate = gsk_gl_glyphy_library_allocate; + library_class->clear_cache = gsk_gl_glyphy_library_clear_cache; + library_class->init_atlas = gsk_gl_glyphy_library_init_atlas; +} + +static void +gsk_gl_glyphy_library_init (GskGLGlyphyLibrary *self) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + + tl->max_entry_size = 0; + tl->max_frame_age = 512; + tl->atlas_width = 2048; + tl->atlas_height = 1024; + gsk_gl_texture_library_set_funcs (tl, + gsk_gl_glyphy_key_hash, + gsk_gl_glyphy_key_equal, + gsk_gl_glyphy_key_free, + gsk_gl_glyphy_value_free); + + self->acc = glyphy_arc_accumulator_create (); + self->acc_endpoints = g_array_new (FALSE, FALSE, sizeof (glyphy_arc_endpoint_t)); + self->item_w = ITEM_W; + self->item_h_q = ITEM_H_QUANTUM; +} + +static glyphy_bool_t +accumulate_endpoint (glyphy_arc_endpoint_t *endpoint, + GArray *endpoints) +{ + g_array_append_vals (endpoints, endpoint, 1); + return TRUE; +} + +static inline gboolean +encode_glyph (GskGLGlyphyLibrary *self, + hb_font_t *font, + unsigned int glyph_index, + double tolerance_per_em, + glyphy_rgba_t *buffer, + guint buffer_len, + guint *output_len, + guint *nominal_width, + guint *nominal_height, + glyphy_extents_t *extents) +{ + hb_face_t *face = hb_font_get_face (font); + guint upem = hb_face_get_upem (face); + double tolerance = upem * tolerance_per_em; + double faraway = (double)upem / (MIN_FONT_SIZE * M_SQRT2); + double avg_fetch_achieved; + + self->acc_endpoints->len = 0; + + glyphy_arc_accumulator_reset (self->acc); + glyphy_arc_accumulator_set_tolerance (self->acc, tolerance); + glyphy_arc_accumulator_set_callback (self->acc, + (glyphy_arc_endpoint_accumulator_callback_t)accumulate_endpoint, + self->acc_endpoints); + + glyphy_harfbuzz(font_get_glyph_shape) (font, glyph_index, self->acc); + if (!glyphy_arc_accumulator_successful (self->acc)) + return FALSE; + + g_assert (glyphy_arc_accumulator_get_error (self->acc) <= tolerance); + + if (self->acc_endpoints->len > 0) + glyphy_outline_winding_from_even_odd ((gpointer)self->acc_endpoints->data, + self->acc_endpoints->len, + FALSE); + + if (!glyphy_arc_list_encode_blob ((gpointer)self->acc_endpoints->data, + self->acc_endpoints->len, + buffer, + buffer_len, + faraway, + 4, /* UNUSED */ + &avg_fetch_achieved, + output_len, + nominal_width, + nominal_height, + extents)) + return FALSE; + + glyphy_extents_scale (extents, 1./upem, 1./upem); + + return TRUE; +} + +gboolean +gsk_gl_glyphy_library_add (GskGLGlyphyLibrary *self, + GskGLGlyphyKey *key, + const GskGLGlyphyValue **out_value) +{ + static glyphy_rgba_t buffer[4096 * 16]; + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + GskGLGlyphyValue *value; + glyphy_extents_t extents; + hb_font_t *font; + guint packed_x; + guint packed_y; + guint nominal_w, nominal_h; + guint output_len = 0; + guint texture_id; + guint width, height; + + g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self)); + g_assert (key != NULL); + g_assert (key->font != NULL); + g_assert (out_value != NULL); + + /* Convert the glyph to a list of arcs */ + font = pango_font_get_hb_font (key->font); + if (!encode_glyph (self, font, key->glyph, TOLERANCE, + buffer, sizeof buffer, &output_len, + &nominal_w, &nominal_h, + &extents)) + return FALSE; + + /* Allocate space for list within atlas */ + width = self->item_w; + height = (output_len + width - 1) / width; + GSK_DEBUG (GLYPH_CACHE, "font %u glyph %u: %u bytes (%u x %u)", key->font, key->glyph, output_len * 4, width, height); + + value = gsk_gl_texture_library_pack (tl, key, sizeof *value, + width, height, 0, + &packed_x, &packed_y); + + g_assert (packed_x % ITEM_W == 0); + g_assert (packed_y % ITEM_H_QUANTUM == 0); + + /* Make sure we found space to pack */ + texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value); + if (texture_id == 0) + return FALSE; + + if (!glyphy_extents_is_empty (&extents)) + { + /* Connect the texture for data upload */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, texture_id); + + g_assert (width > 0); + g_assert (height > 0); + + /* Upload the arc list */ + if (width * height == output_len) + { + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y, + width, height, + GL_RGBA, GL_UNSIGNED_BYTE, + buffer); + } + else + { + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y, + width, height - 1, + GL_RGBA, GL_UNSIGNED_BYTE, + buffer); + /* Upload the last row separately */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y + height - 1, + output_len - (width * (height - 1)), 1, + GL_RGBA, GL_UNSIGNED_BYTE, + buffer + (width * (height - 1))); + } + } + + value->extents.min_x = extents.min_x; + value->extents.min_y = extents.min_y; + value->extents.max_x = extents.max_x; + value->extents.max_y = extents.max_y; + value->nominal_w = nominal_w; + value->nominal_h = nominal_h; + value->atlas_x = packed_x / self->item_w; + value->atlas_y = packed_y / self->item_h_q; + + *out_value = value; + + return TRUE; +} diff --git a/gsk/gl/gskglglyphylibraryprivate.h b/gsk/gl/gskglglyphylibraryprivate.h new file mode 100644 index 0000000000..7bd83a5640 --- /dev/null +++ b/gsk/gl/gskglglyphylibraryprivate.h @@ -0,0 +1,106 @@ +/* gskglglyphylibraryprivate.h + * + * Copyright 2020-2022 Christian Hergert + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__ +#define __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__ + +#include +#include + +#include "gskgltexturelibraryprivate.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_GLYPHY_LIBRARY (gsk_gl_glyphy_library_get_type()) + +typedef struct _GskGLGlyphyKey +{ + PangoFont *font; + PangoGlyph glyph; +} GskGLGlyphyKey; + +typedef struct _GskGLGlyphyValue +{ + GskGLTextureAtlasEntry entry; + struct { + float min_x; + float min_y; + float max_x; + float max_y; + } extents; + guint nominal_w; + guint nominal_h; + guint atlas_x; + guint atlas_y; +} GskGLGlyphyValue; + +G_DECLARE_FINAL_TYPE (GskGLGlyphyLibrary, gsk_gl_glyphy_library, GSK, GL_GLYPHY_LIBRARY, GskGLTextureLibrary) + +struct _GskGLGlyphyLibrary +{ + GskGLTextureLibrary parent_instance; + glyphy_arc_accumulator_t *acc; + GArray *acc_endpoints; + guint item_w; + guint item_h_q; + struct { + GskGLGlyphyKey key; + const GskGLGlyphyValue *value; + } front[256]; +}; + +GskGLGlyphyLibrary *gsk_gl_glyphy_library_new (GskGLDriver *driver); +gboolean gsk_gl_glyphy_library_add (GskGLGlyphyLibrary *self, + GskGLGlyphyKey *key, + const GskGLGlyphyValue **out_value); + +static inline guint +gsk_gl_glyphy_library_lookup_or_add (GskGLGlyphyLibrary *self, + const GskGLGlyphyKey *key, + const GskGLGlyphyValue **out_value) +{ + GskGLTextureAtlasEntry *entry; + guint front_index = key->glyph & 0xFF; + + if (memcmp (key, &self->front[front_index], sizeof *key) == 0) + { + *out_value = self->front[front_index].value; + } + else if (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry)) + { + *out_value = (GskGLGlyphyValue *)entry; + self->front[front_index].key = *key; + self->front[front_index].value = *out_value; + } + else + { + GskGLGlyphyKey *k = g_slice_copy (sizeof *key, key); + g_object_ref (k->font); + gsk_gl_glyphy_library_add (self, k, out_value); + self->front[front_index].key = *key; + self->front[front_index].value = *out_value; + } + + return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value); +} + +G_END_DECLS + +#endif /* __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__ */ diff --git a/gsk/gl/gskgltexturelibraryprivate.h b/gsk/gl/gskgltexturelibraryprivate.h index e2c3589fab..373a4476fb 100644 --- a/gsk/gl/gskgltexturelibraryprivate.h +++ b/gsk/gl/gskgltexturelibraryprivate.h @@ -36,9 +36,14 @@ G_BEGIN_DECLS typedef struct _GskGLTextureAtlas { + /* Used by Glyph/Icons */ struct stbrp_context context; struct stbrp_node *nodes; + /* Used by Glyphy */ + int cursor_x; + int cursor_y; + int width; int height; @@ -48,7 +53,6 @@ typedef struct _GskGLTextureAtlas * But are now unused. */ int unused_pixels; - } GskGLTextureAtlas; typedef struct _GskGLTextureAtlasEntry diff --git a/gsk/gl/gskgltypesprivate.h b/gsk/gl/gskgltypesprivate.h index c16c22c5a0..f61a60b63e 100644 --- a/gsk/gl/gskgltypesprivate.h +++ b/gsk/gl/gskgltypesprivate.h @@ -36,6 +36,7 @@ typedef struct _GskGLCompiler GskGLCompiler; typedef struct _GskGLDrawVertex GskGLDrawVertex; typedef struct _GskGLRenderTarget GskGLRenderTarget; typedef struct _GskGLGlyphLibrary GskGLGlyphLibrary; +typedef struct _GskGLGlyphyLibrary GskGLGlyphyLibrary; typedef struct _GskGLIconLibrary GskGLIconLibrary; typedef struct _GskGLProgram GskGLProgram; typedef struct _GskGLRenderJob GskGLRenderJob; diff --git a/gsk/meson.build b/gsk/meson.build index b954e22656..9560ea154c 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -55,6 +55,7 @@ gsk_private_sources = files([ 'gl/gskglcompiler.c', 'gl/gskgldriver.c', 'gl/gskglglyphlibrary.c', + 'gl/gskglglyphylibrary.c', 'gl/gskgliconlibrary.c', 'gl/gskglprogram.c', 'gl/gskglrenderjob.c',