gtk2/gsk/ngl/gsknglglyphlibrary.c
Matthias Clasen c6cacd2b2d ngl: Use exact device scales for glyphs
We are rendering the glyphs on a larger surface,
and we should avoid introducing unnecessary
rounding errors here. Also, I've found that
we always need to enlarge the surface by one
pixels in each direction to avoid cutting off
the tops of large glyphs.
2021-09-17 19:07:55 -04:00

327 lines
10 KiB
C

/* gsknglglyphlibrary.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/gdkmemorytextureprivate.h>
#include <gdk/gdkprofilerprivate.h>
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gsknglglyphlibraryprivate.h"
#define MAX_GLYPH_SIZE 128
G_DEFINE_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
GskNglGlyphLibrary *
gsk_ngl_glyph_library_new (GskNglDriver *driver)
{
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
"driver", driver,
NULL);
}
static guint
gsk_ngl_glyph_key_hash (gconstpointer data)
{
const GskNglGlyphKey *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 GskNglGlyphKey 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_ngl_glyph_key_equal (gconstpointer v1,
gconstpointer v2)
{
return memcmp (v1, v2, sizeof (GskNglGlyphKey)) == 0;
}
static void
gsk_ngl_glyph_key_free (gpointer data)
{
GskNglGlyphKey *key = data;
g_clear_object (&key->font);
g_slice_free (GskNglGlyphKey, key);
}
static void
gsk_ngl_glyph_value_free (gpointer data)
{
g_slice_free (GskNglGlyphValue, data);
}
static void
gsk_ngl_glyph_library_begin_frame (GskNglTextureLibrary *library,
gint64 frame_id,
GPtrArray *removed_atlases)
{
GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)library;
memset (self->front, 0, sizeof self->front);
}
static void
gsk_ngl_glyph_library_finalize (GObject *object)
{
GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)object;
g_clear_pointer (&self->surface_data, g_free);
G_OBJECT_CLASS (gsk_ngl_glyph_library_parent_class)->finalize (object);
}
static void
gsk_ngl_glyph_library_class_init (GskNglGlyphLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GskNglTextureLibraryClass *library_class = GSK_NGL_TEXTURE_LIBRARY_CLASS (klass);
object_class->finalize = gsk_ngl_glyph_library_finalize;
library_class->begin_frame = gsk_ngl_glyph_library_begin_frame;
}
static void
gsk_ngl_glyph_library_init (GskNglGlyphLibrary *self)
{
GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
tl->max_entry_size = MAX_GLYPH_SIZE;
gsk_ngl_texture_library_set_funcs (tl,
gsk_ngl_glyph_key_hash,
gsk_ngl_glyph_key_equal,
gsk_ngl_glyph_key_free,
gsk_ngl_glyph_value_free);
}
static cairo_surface_t *
gsk_ngl_glyph_library_create_surface (GskNglGlyphLibrary *self,
int stride,
int width,
int height,
int uwidth,
int uheight)
{
cairo_surface_t *surface;
gsize n_bytes;
g_assert (GSK_IS_NGL_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 cairo_scaled_font_t *scaled_font,
const GskNglGlyphKey *key,
const GskNglGlyphValue *value)
{
cairo_t *cr;
cairo_glyph_t glyph;
g_assert (surface != NULL);
g_assert (scaled_font != NULL);
cr = cairo_create (surface);
cairo_set_scaled_font (cr, scaled_font);
cairo_set_source_rgba (cr, 1, 1, 1, 1);
glyph.index = key->glyph;
glyph.x = 0.25 * key->xshift - value->ink_rect.x;
glyph.y = 0.25 * key->yshift - value->ink_rect.y;
cairo_show_glyphs (cr, &glyph, 1);
cairo_destroy (cr);
cairo_surface_flush (surface);
}
static void
gsk_ngl_glyph_library_upload_glyph (GskNglGlyphLibrary *self,
const GskNglGlyphKey *key,
const GskNglGlyphValue *value,
int x,
int y,
int width,
int height,
int uwidth,
int uheight)
{
GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
cairo_scaled_font_t *scaled_font;
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_NGL_GLYPH_LIBRARY (self));
g_assert (key != NULL);
g_assert (value != NULL);
scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
if G_UNLIKELY (scaled_font == NULL ||
cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS)
return;
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_ngl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight);
render_glyph (surface, scaled_font, key, value);
texture_id = GSK_NGL_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_CONVERT_GLES_RGBA,
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_ngl_glyph_library_add (GskNglGlyphLibrary *self,
GskNglGlyphKey *key,
const GskNglGlyphValue **out_value)
{
GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
PangoRectangle ink_rect;
GskNglGlyphValue *value;
int width;
int height;
guint packed_x;
guint packed_y;
g_assert (GSK_IS_NGL_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_ngl_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_ngl_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_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
}