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