gtk/gsk/gl/gskglglyphlibrary.c
Christian Hergert c64836e1c9 gsk/gl: make texture libraries more autonomous
This moves a lot of the texture atlas control out of the driver and into
the various texture libraries through their base GskGLTextureLibrary class.

Additionally, this gives more control to libraries on allocating which can
be necessary for some tooling such as future Glyphy integration.

As part of this, the 1x1 pixel initialization is moved to the Glyph library
which is the only place where it is actually needed.

The compact vfunc now is responsible for compaction and it allows for us
to iterate the atlas hashtable a single time instead of twice as we were
doing previously.

The init_atlas vfunc is used to do per-library initialization such as
adding a 1x1 pixel in the Glyph cache used for coloring lines.

The allocate vfunc purely allocates but does no upload. This can be useful
for situations where a library wants to reuse the allocator from the
base class but does not want to actually insert a key/value entry. The
glyph library uses this for it's 1x1 pixel.

In the future, we will also likely want to decouple the rectangle packing
implementation from the atlas structure, or at least move it into a union
so that we do not allocate unused memory for alternate allocators.
2022-03-18 14:59:46 -07:00

373 lines
11 KiB
C

/* gskglglyphlibrary.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 "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 x,
int 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);
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4);
glBindTexture (GL_TEXTURE_2D, texture_id);
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),
width * 4,
GDK_MEMORY_DEFAULT,
width, height);
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;
}
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height,
gl_format, gl_type, pixel_data);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 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 + 1,
packed_y + 1,
width,
height,
ink_rect.width,
ink_rect.height);
*out_value = value;
return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
}