1/* gskgltexturelibrary.c
2 *
3 * Copyright 2020 Christian Hergert <chergert@redhat.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: LGPL-2.1-or-later
19 */
20
21#include "config.h"
22
23#include <gdk/gdkglcontextprivate.h>
24#include <gsk/gskdebugprivate.h>
25
26#include "gskglcommandqueueprivate.h"
27#include "gskgldriverprivate.h"
28#include "gskgltexturelibraryprivate.h"
29
30#define MAX_FRAME_AGE 60
31
32G_DEFINE_ABSTRACT_TYPE (GskGLTextureLibrary, gsk_gl_texture_library, G_TYPE_OBJECT)
33
34enum {
35 PROP_0,
36 PROP_DRIVER,
37 N_PROPS
38};
39
40static GParamSpec *properties [N_PROPS];
41
42static void
43gsk_gl_texture_library_constructed (GObject *object)
44{
45 G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->constructed (object);
46
47 g_assert (GSK_GL_TEXTURE_LIBRARY (object)->hash_table != NULL);
48}
49
50static void
51gsk_gl_texture_library_dispose (GObject *object)
52{
53 GskGLTextureLibrary *self = (GskGLTextureLibrary *)object;
54
55 g_clear_object (&self->driver);
56
57 G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->dispose (object);
58}
59
60static void
61gsk_gl_texture_library_get_property (GObject *object,
62 guint prop_id,
63 GValue *value,
64 GParamSpec *pspec)
65{
66 GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
67
68 switch (prop_id)
69 {
70 case PROP_DRIVER:
71 g_value_set_object (value, v_object: self->driver);
72 break;
73
74 default:
75 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
76 }
77}
78
79static void
80gsk_gl_texture_library_set_property (GObject *object,
81 guint prop_id,
82 const GValue *value,
83 GParamSpec *pspec)
84{
85 GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
86
87 switch (prop_id)
88 {
89 case PROP_DRIVER:
90 self->driver = g_value_dup_object (value);
91 break;
92
93 default:
94 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
95 }
96}
97
98static void
99gsk_gl_texture_library_class_init (GskGLTextureLibraryClass *klass)
100{
101 GObjectClass *object_class = G_OBJECT_CLASS (klass);
102
103 object_class->constructed = gsk_gl_texture_library_constructed;
104 object_class->dispose = gsk_gl_texture_library_dispose;
105 object_class->get_property = gsk_gl_texture_library_get_property;
106 object_class->set_property = gsk_gl_texture_library_set_property;
107
108 properties [PROP_DRIVER] =
109 g_param_spec_object (name: "driver",
110 nick: "Driver",
111 blurb: "Driver",
112 GSK_TYPE_GL_DRIVER,
113 flags: (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
114
115 g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPS, pspecs: properties);
116}
117
118static void
119gsk_gl_texture_library_init (GskGLTextureLibrary *self)
120{
121}
122
123void
124gsk_gl_texture_library_set_funcs (GskGLTextureLibrary *self,
125 GHashFunc hash_func,
126 GEqualFunc equal_func,
127 GDestroyNotify key_destroy,
128 GDestroyNotify value_destroy)
129{
130 g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
131 g_return_if_fail (self->hash_table == NULL);
132
133 self->hash_table = g_hash_table_new_full (hash_func, key_equal_func: equal_func,
134 key_destroy_func: key_destroy, value_destroy_func: value_destroy);
135}
136
137void
138gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self,
139 gint64 frame_id,
140 GPtrArray *removed_atlases)
141{
142 GHashTableIter iter;
143
144 g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
145
146 if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
147 GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases);
148
149 if (removed_atlases != NULL)
150 {
151 GskGLTextureAtlasEntry *entry;
152 guint dropped = 0;
153
154 g_hash_table_iter_init (iter: &iter, hash_table: self->hash_table);
155 while (g_hash_table_iter_next (iter: &iter, NULL, value: (gpointer *)&entry))
156 {
157 if (entry->is_atlased)
158 {
159 for (guint i = 0; i < removed_atlases->len; i++)
160 {
161 GskGLTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i);
162
163 if (atlas == entry->atlas)
164 {
165 g_hash_table_iter_remove (iter: &iter);
166 dropped++;
167 break;
168 }
169 }
170 }
171 }
172
173 GSK_NOTE (GLYPH_CACHE,
174 if (dropped > 0)
175 g_message ("%s: Dropped %d items",
176 G_OBJECT_TYPE_NAME (self), dropped));
177 }
178
179 if (frame_id % MAX_FRAME_AGE == 0)
180 {
181 GskGLTextureAtlasEntry *entry;
182 int atlased = 0;
183 int dropped = 0;
184
185 g_hash_table_iter_init (iter: &iter, hash_table: self->hash_table);
186 while (g_hash_table_iter_next (iter: &iter, NULL, value: (gpointer *)&entry))
187 {
188 if (!entry->is_atlased && !entry->accessed)
189 {
190 gsk_gl_driver_release_texture (self: self->driver, texture: entry->texture);
191 g_hash_table_iter_remove (iter: &iter);
192 dropped++;
193 continue;
194 }
195
196 gsk_gl_texture_atlas_entry_mark_unused (entry);
197 entry->accessed = FALSE;
198 if (entry->is_atlased)
199 atlased++;
200 }
201
202 GSK_NOTE (GLYPH_CACHE, g_message ("%s: Dropped %d individual items",
203 G_OBJECT_TYPE_NAME (self),
204 dropped);
205 g_message ("%s: %d items cached (%d atlased, %d individually)",
206 G_OBJECT_TYPE_NAME (self),
207 g_hash_table_size (self->hash_table),
208 atlased,
209 g_hash_table_size (self->hash_table) - atlased));
210 }
211}
212
213static GskGLTexture *
214gsk_gl_texture_library_pack_one (GskGLTextureLibrary *self,
215 guint width,
216 guint height)
217{
218 GskGLTexture *texture;
219
220 g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self));
221
222 if (width > self->driver->command_queue->max_texture_size ||
223 height > self->driver->command_queue->max_texture_size)
224 {
225 g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.",
226 width, height, self->driver->command_queue->max_texture_size);
227 width = MIN (width, self->driver->command_queue->max_texture_size);
228 height = MIN (height, self->driver->command_queue->max_texture_size);
229 }
230
231 texture = gsk_gl_driver_create_texture (self: self->driver, width, height, GL_RGBA8, GL_LINEAR, GL_LINEAR);
232 texture->permanent = TRUE;
233
234 return texture;
235}
236
237static inline gboolean
238gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self,
239 int width,
240 int height,
241 int *out_x,
242 int *out_y)
243{
244 stbrp_rect rect;
245
246 rect.w = width;
247 rect.h = height;
248
249 stbrp_pack_rects (context: &self->context, rects: &rect, num_rects: 1);
250
251 if (rect.was_packed)
252 {
253 *out_x = rect.x;
254 *out_y = rect.y;
255 }
256
257 return rect.was_packed;
258}
259
260static void
261gsk_gl_texture_atlas_initialize (GskGLDriver *driver,
262 GskGLTextureAtlas *atlas)
263{
264 /* Insert a single pixel at 0,0 for use in coloring */
265
266 gboolean packed G_GNUC_UNUSED;
267 int x, y;
268 guint gl_format;
269 guint gl_type;
270 guint8 pixel_data[4 * 3 * 3];
271
272 gdk_gl_context_push_debug_group_printf (context: gdk_gl_context_get_current (),
273 format: "Initializing Atlas");
274
275 packed = gsk_gl_texture_atlas_pack (self: atlas, width: 3, height: 3, out_x: &x, out_y: &y);
276 g_assert (packed);
277 g_assert (x == 0 && y == 0);
278
279 memset (s: pixel_data, c: 255, n: sizeof pixel_data);
280
281 if (gdk_gl_context_get_use_es (context: gdk_gl_context_get_current ()))
282 {
283 gl_format = GL_RGBA;
284 gl_type = GL_UNSIGNED_BYTE;
285 }
286 else
287 {
288 gl_format = GL_BGRA;
289 gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
290 }
291
292 glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
293
294 glTexSubImage2D (GL_TEXTURE_2D, 0,
295 0, 0,
296 3, 3,
297 gl_format, gl_type,
298 pixel_data);
299
300 gdk_gl_context_pop_debug_group (context: gdk_gl_context_get_current ());
301
302 driver->command_queue->n_uploads++;
303}
304
305static void
306gsk_gl_texture_atlases_pack (GskGLDriver *driver,
307 int width,
308 int height,
309 GskGLTextureAtlas **out_atlas,
310 int *out_x,
311 int *out_y)
312{
313 GskGLTextureAtlas *atlas = NULL;
314 int x, y;
315
316 for (guint i = 0; i < driver->atlases->len; i++)
317 {
318 atlas = g_ptr_array_index (driver->atlases, i);
319
320 if (gsk_gl_texture_atlas_pack (self: atlas, width, height, out_x: &x, out_y: &y))
321 break;
322
323 atlas = NULL;
324 }
325
326 if (atlas == NULL)
327 {
328 /* No atlas has enough space, so create a new one... */
329 atlas = gsk_gl_driver_create_atlas (self: driver);
330
331 gsk_gl_texture_atlas_initialize (driver, atlas);
332
333 /* Pack it onto that one, which surely has enough space... */
334 if (!gsk_gl_texture_atlas_pack (self: atlas, width, height, out_x: &x, out_y: &y))
335 g_assert_not_reached ();
336 }
337
338 *out_atlas = atlas;
339 *out_x = x;
340 *out_y = y;
341}
342
343gpointer
344gsk_gl_texture_library_pack (GskGLTextureLibrary *self,
345 gpointer key,
346 gsize valuelen,
347 guint width,
348 guint height,
349 int padding,
350 guint *out_packed_x,
351 guint *out_packed_y)
352{
353 GskGLTextureAtlasEntry *entry;
354 GskGLTextureAtlas *atlas = NULL;
355
356 g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self));
357 g_assert (key != NULL);
358 g_assert (valuelen > sizeof (GskGLTextureAtlasEntry));
359 g_assert (out_packed_x != NULL);
360 g_assert (out_packed_y != NULL);
361
362 entry = g_slice_alloc0 (block_size: valuelen);
363 entry->n_pixels = width * height;
364 entry->accessed = TRUE;
365 entry->used = TRUE;
366
367 /* If our size is invisible then we just want an entry in the
368 * cache for faster lookups, but do not actually spend any texture
369 * allocations on this entry.
370 */
371 if (width <= 0 && height <= 0)
372 {
373 entry->is_atlased = FALSE;
374 entry->texture = NULL;
375 entry->area.x = 0.0f;
376 entry->area.y = 0.0f;
377 entry->area.x2 = 0.0f;
378 entry->area.y2 = 0.0f;
379
380 *out_packed_x = 0;
381 *out_packed_y = 0;
382 }
383 else if (width <= self->max_entry_size && height <= self->max_entry_size)
384 {
385 int packed_x;
386 int packed_y;
387
388 gsk_gl_texture_atlases_pack (driver: self->driver,
389 width: padding + width + padding,
390 height: padding + height + padding,
391 out_atlas: &atlas,
392 out_x: &packed_x,
393 out_y: &packed_y);
394
395 entry->atlas = atlas;
396 entry->is_atlased = TRUE;
397 entry->area.x = (packed_x + padding) / (float)atlas->width;
398 entry->area.y = (packed_y + padding) / (float)atlas->height;
399 entry->area.x2 = (packed_x + padding + width) / (float)atlas->width;
400 entry->area.y2 = (packed_y + padding + height) / (float)atlas->height;
401
402 *out_packed_x = packed_x;
403 *out_packed_y = packed_y;
404 }
405 else
406 {
407 GskGLTexture *texture = gsk_gl_texture_library_pack_one (self,
408 width: padding + width + padding,
409 height: padding + height + padding);
410
411 entry->texture = texture;
412 entry->is_atlased = FALSE;
413 entry->accessed = TRUE;
414 entry->area.x = padding / (float) (padding + width + padding);
415 entry->area.y = padding / (float) (padding + height + padding);
416 entry->area.x2 = (padding + width) / (float) (padding + width + padding);
417 entry->area.y2 = (padding + height) / (float) (padding + height + padding);
418
419 *out_packed_x = 0;
420 *out_packed_y = 0;
421 }
422
423 g_hash_table_insert (hash_table: self->hash_table, key, value: entry);
424
425 return entry;
426}
427

source code of gtk/gsk/gl/gskgltexturelibrary.c