gtk/gsk/ngl/gskngltexturelibrary.c
Christian Hergert af80f3a976 ngl: implement atlas compaction
This required finishing up the begin_frame/end_frame semantics for
GskNglTextureLibraryw which was apparently overlooked.

The driver was changed to provide more information to the library when
beginning frames. We do not need to use end_frame so that was removed.

The frame age is the same as GL (60) but I do wonder if that is based
on seconds if we should be using something longer for situations where
we have higher frame rates.

Fixes #3771
2021-03-18 17:59:56 -07:00

362 lines
11 KiB
C

/* gskngltexturelibrary.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 <gsk/gskdebugprivate.h>
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gskngltexturelibraryprivate.h"
#define MAX_FRAME_AGE 60
G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_DRIVER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
gsk_ngl_texture_library_constructed (GObject *object)
{
G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->constructed (object);
g_assert (GSK_NGL_TEXTURE_LIBRARY (object)->hash_table != NULL);
}
static void
gsk_ngl_texture_library_dispose (GObject *object)
{
GskNglTextureLibrary *self = (GskNglTextureLibrary *)object;
g_clear_object (&self->driver);
G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->dispose (object);
}
static void
gsk_ngl_texture_library_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
g_value_set_object (value, self->driver);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_texture_library_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
self->driver = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_texture_library_class_init (GskNglTextureLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gsk_ngl_texture_library_constructed;
object_class->dispose = gsk_ngl_texture_library_dispose;
object_class->get_property = gsk_ngl_texture_library_get_property;
object_class->set_property = gsk_ngl_texture_library_set_property;
properties [PROP_DRIVER] =
g_param_spec_object ("driver",
"Driver",
"Driver",
GSK_TYPE_NGL_DRIVER,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gsk_ngl_texture_library_init (GskNglTextureLibrary *self)
{
}
void
gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
GHashFunc hash_func,
GEqualFunc equal_func,
GDestroyNotify key_destroy,
GDestroyNotify value_destroy)
{
g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
g_return_if_fail (self->hash_table == NULL);
self->hash_table = g_hash_table_new_full (hash_func, equal_func,
key_destroy, value_destroy);
}
void
gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self,
gint64 frame_id,
GPtrArray *removed_atlases)
{
GHashTableIter iter;
g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases);
if (removed_atlases != NULL)
{
GskNglTextureAtlasEntry *entry;
guint dropped = 0;
g_hash_table_iter_init (&iter, self->hash_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
{
if (entry->is_atlased)
{
for (guint i = 0; i < removed_atlases->len; i++)
{
GskNglTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i);
if (atlas == entry->atlas)
{
g_hash_table_iter_remove (&iter);
dropped++;
break;
}
}
}
}
GSK_NOTE (GLYPH_CACHE,
if (dropped > 0)
g_message ("%s: Dropped %d icons",
G_OBJECT_TYPE_NAME (self), dropped));
}
if (frame_id % MAX_FRAME_AGE == 0)
{
GskNglTextureAtlasEntry *entry;
g_hash_table_iter_init (&iter, self->hash_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
{
gsk_ngl_texture_atlas_entry_mark_unused (entry);
entry->accessed = FALSE;
}
GSK_NOTE (GLYPH_CACHE, g_message ("%s: %d atlas items cached",
G_OBJECT_TYPE_NAME (self),
g_hash_table_size (self->hash_table)));
}
}
static GskNglTexture *
gsk_ngl_texture_library_pack_one (GskNglTextureLibrary *self,
guint width,
guint height)
{
GskNglTexture *texture;
g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
if (width > self->driver->command_queue->max_texture_size ||
height > self->driver->command_queue->max_texture_size)
{
g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.",
width, height, self->driver->command_queue->max_texture_size);
width = MIN (width, self->driver->command_queue->max_texture_size);
height = MIN (height, self->driver->command_queue->max_texture_size);
}
texture = gsk_ngl_driver_create_texture (self->driver, width, height, GL_LINEAR, GL_LINEAR);
texture->permanent = TRUE;
return texture;
}
static inline gboolean
gsk_ngl_texture_atlas_pack (GskNglTextureAtlas *self,
int width,
int height,
int *out_x,
int *out_y)
{
stbrp_rect rect;
rect.w = width;
rect.h = height;
stbrp_pack_rects (&self->context, &rect, 1);
if (rect.was_packed)
{
*out_x = rect.x;
*out_y = rect.y;
}
return rect.was_packed;
}
static void
gsk_ngl_texture_atlases_pack (GskNglDriver *driver,
int width,
int height,
GskNglTextureAtlas **out_atlas,
int *out_x,
int *out_y)
{
GskNglTextureAtlas *atlas = NULL;
int x, y;
for (guint i = 0; i < driver->atlases->len; i++)
{
atlas = g_ptr_array_index (driver->atlases, i);
if (gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
break;
atlas = NULL;
}
if (atlas == NULL)
{
/* No atlas has enough space, so create a new one... */
atlas = gsk_ngl_driver_create_atlas (driver);
/* Pack it onto that one, which surely has enough space... */
if (!gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
g_assert_not_reached ();
}
*out_atlas = atlas;
*out_x = x;
*out_y = y;
}
gpointer
gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
gpointer key,
gsize valuelen,
guint width,
guint height,
int padding,
guint *out_packed_x,
guint *out_packed_y)
{
GskNglTextureAtlasEntry *entry;
GskNglTextureAtlas *atlas = NULL;
g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
g_assert (key != NULL);
g_assert (valuelen > sizeof (GskNglTextureAtlasEntry));
g_assert (out_packed_x != NULL);
g_assert (out_packed_y != NULL);
entry = g_slice_alloc0 (valuelen);
entry->n_pixels = width * height;
entry->accessed = TRUE;
entry->used = TRUE;
/* If our size is invisible then we just want an entry in the
* cache for faster lookups, but do not actually spend any texture
* allocations on this entry.
*/
if (width <= 0 && height <= 0)
{
entry->is_atlased = FALSE;
entry->texture = NULL;
entry->area.x = 0.0f;
entry->area.y = 0.0f;
entry->area.x2 = 0.0f;
entry->area.y2 = 0.0f;
*out_packed_x = 0;
*out_packed_y = 0;
}
else if (width <= self->max_entry_size && height <= self->max_entry_size)
{
int packed_x;
int packed_y;
gsk_ngl_texture_atlases_pack (self->driver,
padding + width + padding,
padding + height + padding,
&atlas,
&packed_x,
&packed_y);
entry->atlas = atlas;
entry->is_atlased = TRUE;
entry->area.x = (float)(packed_x + padding) / atlas->width;
entry->area.y = (float)(packed_y + padding) / atlas->height;
entry->area.x2 = entry->area.x + (float)width / atlas->width;
entry->area.y2 = entry->area.y + (float)height / atlas->height;
*out_packed_x = packed_x;
*out_packed_y = packed_y;
}
else
{
GskNglTexture *texture = gsk_ngl_texture_library_pack_one (self,
padding + width + padding,
padding + height + padding);
entry->texture = texture;
entry->is_atlased = FALSE;
entry->accessed = TRUE;
entry->area.x = 0.0f;
entry->area.y = 0.0f;
entry->area.x2 = 1.0f;
entry->area.y2 = 1.0f;
*out_packed_x = padding;
*out_packed_y = padding;
}
g_hash_table_insert (self->hash_table, key, entry);
return entry;
}