/* gskglglyphlibrary.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 */ #include "config.h" #include #include #include #include "gskglcommandqueueprivate.h" #include "gskgldriverprivate.h" #include "gskglglyphlibraryprivate.h" #define MAX_GLYPH_SIZE 128 G_DEFINE_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY) GskGLGlyphLibrary * gsk_gl_glyph_library_new (GskGLDriver *driver) { g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY, "driver", driver, NULL); } static guint gsk_gl_glyph_key_hash (gconstpointer data) { const GskGLGlyphKey *key = data; /* We do not store the hash within the key because GHashTable will already * store the hash value for us and so this is called only a single time per * cached item. This saves an extra 4 bytes per GskGLGlyphKey which means on * 64-bit, we fit nicely within 2 pointers (the smallest allocation size * for GSlice). */ return GPOINTER_TO_UINT (key->font) ^ key->glyph ^ (key->xshift << 24) ^ (key->yshift << 26) ^ key->scale; } static gboolean gsk_gl_glyph_key_equal (gconstpointer v1, gconstpointer v2) { return memcmp (v1, v2, sizeof (GskGLGlyphKey)) == 0; } static void gsk_gl_glyph_key_free (gpointer data) { GskGLGlyphKey *key = data; g_clear_object (&key->font); g_slice_free (GskGLGlyphKey, key); } static void gsk_gl_glyph_value_free (gpointer data) { g_slice_free (GskGLGlyphValue, data); } static void gsk_gl_glyph_library_clear_cache (GskGLTextureLibrary *library) { GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)library; g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); memset (self->front, 0, sizeof self->front); } static void gsk_gl_glyph_library_init_atlas (GskGLTextureLibrary *self, GskGLTextureAtlas *atlas) { gboolean packed G_GNUC_UNUSED; int x, y; guint gl_format; guint gl_type; guint8 pixel_data[4 * 3 * 3]; g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); g_assert (atlas != NULL); /* Insert a single pixel at 0,0 for use in coloring */ gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (), "Initializing Atlas"); packed = gsk_gl_texture_library_allocate (self, atlas, 3, 3, &x, &y); g_assert (packed); g_assert (x == 0 && y == 0); memset (pixel_data, 255, sizeof pixel_data); if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) { gl_format = GL_RGBA; gl_type = GL_UNSIGNED_BYTE; } else { gl_format = GL_BGRA; gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; } glBindTexture (GL_TEXTURE_2D, atlas->texture_id); glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 3, 3, gl_format, gl_type, pixel_data); gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ()); self->driver->command_queue->n_uploads++; } static void gsk_gl_glyph_library_finalize (GObject *object) { GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)object; g_clear_pointer (&self->surface_data, g_free); G_OBJECT_CLASS (gsk_gl_glyph_library_parent_class)->finalize (object); } static void gsk_gl_glyph_library_class_init (GskGLGlyphLibraryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass); object_class->finalize = gsk_gl_glyph_library_finalize; library_class->clear_cache = gsk_gl_glyph_library_clear_cache; library_class->init_atlas = gsk_gl_glyph_library_init_atlas; } static void gsk_gl_glyph_library_init (GskGLGlyphLibrary *self) { GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; tl->max_entry_size = MAX_GLYPH_SIZE; gsk_gl_texture_library_set_funcs (tl, gsk_gl_glyph_key_hash, gsk_gl_glyph_key_equal, gsk_gl_glyph_key_free, gsk_gl_glyph_value_free); } static cairo_surface_t * gsk_gl_glyph_library_create_surface (GskGLGlyphLibrary *self, int stride, int width, int height, int uwidth, int uheight) { cairo_surface_t *surface; gsize n_bytes; g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); g_assert (width > 0); g_assert (height > 0); n_bytes = stride * height; if G_LIKELY (n_bytes > self->surface_data_len) { self->surface_data = g_realloc (self->surface_data, n_bytes); self->surface_data_len = n_bytes; } memset (self->surface_data, 0, n_bytes); surface = cairo_image_surface_create_for_data (self->surface_data, CAIRO_FORMAT_ARGB32, width, height, stride); cairo_surface_set_device_scale (surface, width / (double)uwidth, height / (double)uheight); return surface; } static void render_glyph (cairo_surface_t *surface, const GskGLGlyphKey *key, const GskGLGlyphValue *value) { cairo_t *cr; PangoGlyphString glyph_string; PangoGlyphInfo glyph_info; g_assert (surface != NULL); cr = cairo_create (surface); cairo_set_source_rgba (cr, 1, 1, 1, 1); glyph_info.glyph = key->glyph; glyph_info.geometry.width = value->ink_rect.width * 1024; glyph_info.geometry.x_offset = (0.25 * key->xshift - value->ink_rect.x) * 1024; glyph_info.geometry.y_offset = (0.25 * key->yshift - value->ink_rect.y) * 1024; glyph_string.num_glyphs = 1; glyph_string.glyphs = &glyph_info; pango_cairo_show_glyph_string (cr, key->font, &glyph_string); cairo_destroy (cr); cairo_surface_flush (surface); } static void gsk_gl_glyph_library_upload_glyph (GskGLGlyphLibrary *self, const GskGLGlyphKey *key, const GskGLGlyphValue *value, int packed_x, int packed_y, int width, int height, int uwidth, int uheight) { GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; cairo_surface_t *surface; guchar *pixel_data; guchar *free_data = NULL; guint gl_format; guint gl_type; guint texture_id; gsize stride; g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); g_assert (key != NULL); g_assert (value != NULL); stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width); gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (), "Uploading glyph %d", key->glyph); surface = gsk_gl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight); render_glyph (surface, key, value); texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value); g_assert (texture_id > 0); if G_UNLIKELY (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) { pixel_data = free_data = g_malloc (width * height * 4); gdk_memory_convert (pixel_data, width * 4, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, cairo_image_surface_get_data (surface), stride, GDK_MEMORY_DEFAULT, width, height); stride = width * 4; gl_format = GL_RGBA; gl_type = GL_UNSIGNED_BYTE; } else { pixel_data = cairo_image_surface_get_data (surface); gl_format = GL_BGRA; gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; } glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4); glBindTexture (GL_TEXTURE_2D, texture_id); glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + 1, packed_y + 1, width, height, gl_format, gl_type, pixel_data); /* Padding top */ glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + 1, packed_y, width, 1, gl_format, gl_type, pixel_data); /* Padding left */ glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x, packed_y + 1, 1, height, gl_format, gl_type, pixel_data); /* Padding top left */ glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x, packed_y, 1, 1, gl_format, gl_type, pixel_data); /* Padding right */ glPixelStorei (GL_UNPACK_ROW_LENGTH, width); glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + width + 1, packed_y + 1, 1, height, gl_format, gl_type, pixel_data); /* Padding top right */ glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + width + 1, packed_y, 1, 1, gl_format, gl_type, pixel_data); /* Padding bottom */ glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1); glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + 1, packed_y + 1 + height, width, 1, gl_format, gl_type, pixel_data); /* Padding bottom left */ glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x, packed_y + 1 + height, 1, 1, gl_format, gl_type, pixel_data); /* Padding bottom right */ glPixelStorei (GL_UNPACK_ROW_LENGTH, width); glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); glTexSubImage2D (GL_TEXTURE_2D, 0, packed_x + 1 + width, packed_y + 1 + height, 1, 1, gl_format, gl_type, pixel_data); /* Reset this */ glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); cairo_surface_destroy (surface); g_free (free_data); gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ()); tl->driver->command_queue->n_uploads++; if (gdk_profiler_is_running ()) { char message[64]; g_snprintf (message, sizeof message, "Size %dx%d", width, height); gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Glyph", message); } } gboolean gsk_gl_glyph_library_add (GskGLGlyphLibrary *self, GskGLGlyphKey *key, const GskGLGlyphValue **out_value) { GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; PangoRectangle ink_rect; GskGLGlyphValue *value; int width; int height; guint packed_x; guint packed_y; g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); g_assert (key != NULL); g_assert (out_value != NULL); pango_font_get_glyph_extents (key->font, key->glyph, &ink_rect, NULL); pango_extents_to_pixels (&ink_rect, NULL); ink_rect.x -= 1; ink_rect.width += 2; ink_rect.y -= 1; ink_rect.height += 2; width = (int) ceil (ink_rect.width * key->scale / 1024.0); height = (int) ceil (ink_rect.height * key->scale / 1024.0); value = gsk_gl_texture_library_pack (tl, key, sizeof *value, width, height, 1, &packed_x, &packed_y); memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect); if (key->scale > 0 && width > 0 && height > 0) gsk_gl_glyph_library_upload_glyph (self, key, value, packed_x, packed_y, width, height, ink_rect.width, ink_rect.height); *out_value = value; return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0; }