[gtk/glyphy2: 16/29] gsk/gl: add texture library for Glyphy




commit 9c097a3d057afe91d61e025516537f403ab23e44
Author: Christian Hergert <chergert redhat com>
Date:   Thu Mar 17 16:53:17 2022 -0700

    gsk/gl: add 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         | 383 ++++++++++++++++++++++++++++++++++++
 gsk/gl/gskglglyphylibraryprivate.h  | 103 ++++++++++
 gsk/gl/gskgltexturelibraryprivate.h |   6 +-
 gsk/gl/gskgltypesprivate.h          |   1 +
 gsk/meson.build                     |   1 +
 7 files changed, 499 insertions(+), 1 deletion(-)
---
diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c
index 0390f97702..d54d13a85e 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"
@@ -274,6 +275,7 @@ gsk_gl_driver_dispose (GObject *object)
   g_assert (!self->key_to_texture_id|| g_hash_table_size (self->key_to_texture_id) == 0);
 
   g_clear_object (&self->glyphs_library);
+  g_clear_object (&self->glyphy_library);
   g_clear_object (&self->icons_library);
   g_clear_object (&self->shadows_library);
 
@@ -462,6 +464,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);
 
@@ -558,6 +561,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 accae5b3bb..56b95473d4 100644
--- a/gsk/gl/gskgldriverprivate.h
+++ b/gsk/gl/gskgldriverprivate.h
@@ -101,6 +101,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..c29408f639
--- /dev/null
+++ b/gsk/gl/gskglglyphylibrary.c
@@ -0,0 +1,383 @@
+/* gskglglyphylibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemoryformatprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglglyphylibraryprivate.h"
+
+#include <glyphy-harfbuzz.h>
+
+#define TOLERANCE (1/2048.)
+#define MIN_FONT_SIZE 10
+#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,
+              double             *advance,
+              gboolean           *is_empty)
+{
+  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);
+
+  *advance = hb_font_get_glyph_h_advance (font, glyph_index) / (double)upem;
+  *is_empty = glyphy_extents_is_empty (extents);
+
+  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;
+  gboolean is_empty;
+  double advance;
+  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, &advance, &is_empty))
+    return FALSE;
+
+  /* Allocate space for list within atlas */
+  width = self->item_w;
+  height = (output_len + width - 1) / width;
+  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 (!is_empty)
+    {
+      /* 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->advance = advance;
+  value->extents = extents;
+  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..3d2a817768
--- /dev/null
+++ b/gsk/gl/gskglglyphylibraryprivate.h
@@ -0,0 +1,103 @@
+/* gskglglyphylibraryprivate.h
+ *
+ * Copyright 2020-2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
+#define __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
+
+#include <glyphy.h>
+#include <pango/pango.h>
+
+#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;
+  glyphy_extents_t extents;
+  double advance;
+  guint nominal_w;
+  guint nominal_h;
+  guint atlas_x;
+  guint atlas_y;
+  guint is_empty : 1;
+} 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 a0292c497b..b448b296a7 100644
--- a/gsk/gl/gskgltexturelibraryprivate.h
+++ b/gsk/gl/gskgltexturelibraryprivate.h
@@ -37,9 +37,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;
 
@@ -49,7 +54,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 1250756fb7..57364fcf4e 100644
--- a/gsk/gl/gskgltypesprivate.h
+++ b/gsk/gl/gskgltypesprivate.h
@@ -37,6 +37,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 2e77665dc4..4a9f16211a 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -59,6 +59,7 @@ gsk_private_sources = files([
   'gl/gskglprofiler.c',
   'gl/gskgldriver.c',
   'gl/gskglglyphlibrary.c',
+  'gl/gskglglyphylibrary.c',
   'gl/gskgliconlibrary.c',
   'gl/gskglprogram.c',
   'gl/gskglrenderjob.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]