[gtk/wip/chergert/glyphy: 19/20] gsk/gl: upload glyphy arc lists to texture atlas




commit 5383b8f657848dfd4b727db9b2a9f1a6842f7d5d
Author: Christian Hergert <chergert redhat com>
Date:   Mon Mar 14 16:22:52 2022 -0700

    gsk/gl: upload glyphy arc lists to texture atlas
    
    Glyphy provides a way to serialize a glyph from a hb_font_t using
    the Harfbuzz 4.0 API into a list of arcs. This commit provides a new
    texture library subclass to contain those arc lists and integration
    to perform basic reclaimation.
    
    Lookups are cached using a 256-entry fronting cache similar to what
    was done in the GskGLGlyphLibrary for Cairo rendered fonts.

 gsk/gl/gskgldriver.c               |   2 +
 gsk/gl/gskgldriverprivate.h        |   1 +
 gsk/gl/gskglglyphylibrary.c        | 290 +++++++++++++++++++++++++++++++++++++
 gsk/gl/gskglglyphylibraryprivate.h | 101 +++++++++++++
 gsk/gl/gskgltypesprivate.h         |   1 +
 gsk/meson.build                    |   1 +
 6 files changed, 396 insertions(+)
---
diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c
index 327dea93ff..712cc59ed0 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"
@@ -457,6 +458,7 @@ gsk_gl_driver_new (GskGLCommandQueue  *command_queue,
     }
 
   self->glyphs = gsk_gl_glyph_library_new (self);
+  self->glyphy = gsk_gl_glyphy_library_new (self);
   self->icons = gsk_gl_icon_library_new (self);
   self->shadows = gsk_gl_shadow_library_new (self);
 
diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h
index 4500962ed8..5878591396 100644
--- a/gsk/gl/gskgldriverprivate.h
+++ b/gsk/gl/gskgldriverprivate.h
@@ -101,6 +101,7 @@ struct _GskGLDriver
   GskGLCommandQueue *command_queue;
 
   GskGLGlyphLibrary *glyphs;
+  GskGLGlyphyLibrary *glyphy;
   GskGLIconLibrary *icons;
   GskGLShadowLibrary *shadows;
 
diff --git a/gsk/gl/gskglglyphylibrary.c b/gsk/gl/gskglglyphylibrary.c
new file mode 100644
index 0000000000..5e5406596f
--- /dev/null
+++ b/gsk/gl/gskglglyphylibrary.c
@@ -0,0 +1,290 @@
+/* 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
+ */
+
+#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_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->clear_cache = gsk_gl_glyphy_library_clear_cache;
+}
+
+static void
+gsk_gl_glyphy_library_init (GskGLGlyphyLibrary *self)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+
+  tl->max_entry_size = 0;
+  tl->max_frame_age = 512;
+  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));
+}
+
+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 (!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 = 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);
+
+  /* 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 / ITEM_W;
+  value->atlas_y = packed_y / ITEM_H_QUANTUM;
+
+  *out_value = value;
+
+  return TRUE;
+}
diff --git a/gsk/gl/gskglglyphylibraryprivate.h b/gsk/gl/gskglglyphylibraryprivate.h
new file mode 100644
index 0000000000..800c1fb0d9
--- /dev/null
+++ b/gsk/gl/gskglglyphylibraryprivate.h
@@ -0,0 +1,101 @@
+/* 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;
+  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/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 1c3cae4a63..99020f48fd 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -45,6 +45,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',


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