1 | /* gskglglyphlibrary.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 <gdk/gdkmemoryformatprivate.h> |
25 | #include <gdk/gdkprofilerprivate.h> |
26 | |
27 | #include "gskglcommandqueueprivate.h" |
28 | #include "gskgldriverprivate.h" |
29 | #include "gskglglyphlibraryprivate.h" |
30 | |
31 | #define MAX_GLYPH_SIZE 128 |
32 | |
33 | G_DEFINE_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY) |
34 | |
35 | GskGLGlyphLibrary * |
36 | gsk_gl_glyph_library_new (GskGLDriver *driver) |
37 | { |
38 | g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); |
39 | |
40 | return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY, |
41 | first_property_name: "driver" , driver, |
42 | NULL); |
43 | } |
44 | |
45 | static guint |
46 | gsk_gl_glyph_key_hash (gconstpointer data) |
47 | { |
48 | const GskGLGlyphKey *key = data; |
49 | |
50 | /* We do not store the hash within the key because GHashTable will already |
51 | * store the hash value for us and so this is called only a single time per |
52 | * cached item. This saves an extra 4 bytes per GskGLGlyphKey which means on |
53 | * 64-bit, we fit nicely within 2 pointers (the smallest allocation size |
54 | * for GSlice). |
55 | */ |
56 | |
57 | return GPOINTER_TO_UINT (key->font) ^ |
58 | key->glyph ^ |
59 | (key->xshift << 24) ^ |
60 | (key->yshift << 26) ^ |
61 | key->scale; |
62 | } |
63 | |
64 | static gboolean |
65 | gsk_gl_glyph_key_equal (gconstpointer v1, |
66 | gconstpointer v2) |
67 | { |
68 | return memcmp (s1: v1, s2: v2, n: sizeof (GskGLGlyphKey)) == 0; |
69 | } |
70 | |
71 | static void |
72 | gsk_gl_glyph_key_free (gpointer data) |
73 | { |
74 | GskGLGlyphKey *key = data; |
75 | |
76 | g_clear_object (&key->font); |
77 | g_slice_free (GskGLGlyphKey, key); |
78 | } |
79 | |
80 | static void |
81 | gsk_gl_glyph_value_free (gpointer data) |
82 | { |
83 | g_slice_free (GskGLGlyphValue, data); |
84 | } |
85 | |
86 | static void |
87 | gsk_gl_glyph_library_begin_frame (GskGLTextureLibrary *library, |
88 | gint64 frame_id, |
89 | GPtrArray *removed_atlases) |
90 | { |
91 | GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)library; |
92 | |
93 | memset (s: self->front, c: 0, n: sizeof self->front); |
94 | } |
95 | |
96 | static void |
97 | gsk_gl_glyph_library_finalize (GObject *object) |
98 | { |
99 | GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)object; |
100 | |
101 | g_clear_pointer (&self->surface_data, g_free); |
102 | |
103 | G_OBJECT_CLASS (gsk_gl_glyph_library_parent_class)->finalize (object); |
104 | } |
105 | |
106 | static void |
107 | gsk_gl_glyph_library_class_init (GskGLGlyphLibraryClass *klass) |
108 | { |
109 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
110 | GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass); |
111 | |
112 | object_class->finalize = gsk_gl_glyph_library_finalize; |
113 | |
114 | library_class->begin_frame = gsk_gl_glyph_library_begin_frame; |
115 | } |
116 | |
117 | static void |
118 | gsk_gl_glyph_library_init (GskGLGlyphLibrary *self) |
119 | { |
120 | GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; |
121 | |
122 | tl->max_entry_size = MAX_GLYPH_SIZE; |
123 | gsk_gl_texture_library_set_funcs (self: tl, |
124 | hash_func: gsk_gl_glyph_key_hash, |
125 | equal_func: gsk_gl_glyph_key_equal, |
126 | key_destroy: gsk_gl_glyph_key_free, |
127 | value_destroy: gsk_gl_glyph_value_free); |
128 | } |
129 | |
130 | static cairo_surface_t * |
131 | gsk_gl_glyph_library_create_surface (GskGLGlyphLibrary *self, |
132 | int stride, |
133 | int width, |
134 | int height, |
135 | int uwidth, |
136 | int uheight) |
137 | { |
138 | cairo_surface_t *surface; |
139 | gsize n_bytes; |
140 | |
141 | g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); |
142 | g_assert (width > 0); |
143 | g_assert (height > 0); |
144 | |
145 | n_bytes = stride * height; |
146 | |
147 | if G_LIKELY (n_bytes > self->surface_data_len) |
148 | { |
149 | self->surface_data = g_realloc (mem: self->surface_data, n_bytes); |
150 | self->surface_data_len = n_bytes; |
151 | } |
152 | |
153 | memset (s: self->surface_data, c: 0, n: n_bytes); |
154 | surface = cairo_image_surface_create_for_data (data: self->surface_data, |
155 | format: CAIRO_FORMAT_ARGB32, |
156 | width, height, stride); |
157 | cairo_surface_set_device_scale (surface, x_scale: width / (double)uwidth, y_scale: height / (double)uheight); |
158 | |
159 | return surface; |
160 | } |
161 | |
162 | static void |
163 | render_glyph (cairo_surface_t *surface, |
164 | const GskGLGlyphKey *key, |
165 | const GskGLGlyphValue *value) |
166 | { |
167 | cairo_t *cr; |
168 | PangoGlyphString glyph_string; |
169 | PangoGlyphInfo glyph_info; |
170 | |
171 | g_assert (surface != NULL); |
172 | |
173 | cr = cairo_create (target: surface); |
174 | cairo_set_source_rgba (cr, red: 1, green: 1, blue: 1, alpha: 1); |
175 | |
176 | glyph_info.glyph = key->glyph; |
177 | glyph_info.geometry.width = value->ink_rect.width * 1024; |
178 | glyph_info.geometry.x_offset = (0.25 * key->xshift - value->ink_rect.x) * 1024; |
179 | glyph_info.geometry.y_offset = (0.25 * key->yshift - value->ink_rect.y) * 1024; |
180 | |
181 | glyph_string.num_glyphs = 1; |
182 | glyph_string.glyphs = &glyph_info; |
183 | |
184 | pango_cairo_show_glyph_string (cr, font: key->font, glyphs: &glyph_string); |
185 | cairo_destroy (cr); |
186 | |
187 | cairo_surface_flush (surface); |
188 | } |
189 | |
190 | static void |
191 | gsk_gl_glyph_library_upload_glyph (GskGLGlyphLibrary *self, |
192 | const GskGLGlyphKey *key, |
193 | const GskGLGlyphValue *value, |
194 | int x, |
195 | int y, |
196 | int width, |
197 | int height, |
198 | int uwidth, |
199 | int uheight) |
200 | { |
201 | GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; |
202 | G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; |
203 | cairo_surface_t *surface; |
204 | guchar *pixel_data; |
205 | guchar *free_data = NULL; |
206 | guint gl_format; |
207 | guint gl_type; |
208 | guint texture_id; |
209 | gsize stride; |
210 | |
211 | g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); |
212 | g_assert (key != NULL); |
213 | g_assert (value != NULL); |
214 | |
215 | stride = cairo_format_stride_for_width (format: CAIRO_FORMAT_ARGB32, width); |
216 | |
217 | gdk_gl_context_push_debug_group_printf (context: gdk_gl_context_get_current (), |
218 | format: "Uploading glyph %d" , |
219 | key->glyph); |
220 | |
221 | surface = gsk_gl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight); |
222 | render_glyph (surface, key, value); |
223 | |
224 | texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (d: value); |
225 | |
226 | g_assert (texture_id > 0); |
227 | |
228 | glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4); |
229 | glBindTexture (GL_TEXTURE_2D, texture_id); |
230 | |
231 | if G_UNLIKELY (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) |
232 | { |
233 | pixel_data = free_data = g_malloc (n_bytes: width * height * 4); |
234 | gdk_memory_convert (dest_data: pixel_data, |
235 | dest_stride: width * 4, |
236 | dest_format: GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, |
237 | src_data: cairo_image_surface_get_data (surface), |
238 | src_stride: width * 4, |
239 | GDK_MEMORY_DEFAULT, |
240 | width, height); |
241 | gl_format = GL_RGBA; |
242 | gl_type = GL_UNSIGNED_BYTE; |
243 | } |
244 | else |
245 | { |
246 | pixel_data = cairo_image_surface_get_data (surface); |
247 | gl_format = GL_BGRA; |
248 | gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; |
249 | } |
250 | |
251 | glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, |
252 | gl_format, gl_type, pixel_data); |
253 | glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); |
254 | |
255 | cairo_surface_destroy (surface); |
256 | g_free (mem: free_data); |
257 | |
258 | gdk_gl_context_pop_debug_group (context: gdk_gl_context_get_current ()); |
259 | |
260 | tl->driver->command_queue->n_uploads++; |
261 | |
262 | if (gdk_profiler_is_running ()) |
263 | { |
264 | char message[64]; |
265 | g_snprintf (string: message, n: sizeof message, format: "Size %dx%d" , width, height); |
266 | gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Glyph" , message); |
267 | } |
268 | } |
269 | |
270 | gboolean |
271 | gsk_gl_glyph_library_add (GskGLGlyphLibrary *self, |
272 | GskGLGlyphKey *key, |
273 | const GskGLGlyphValue **out_value) |
274 | { |
275 | GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; |
276 | PangoRectangle ink_rect; |
277 | GskGLGlyphValue *value; |
278 | int width; |
279 | int height; |
280 | guint packed_x; |
281 | guint packed_y; |
282 | |
283 | g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); |
284 | g_assert (key != NULL); |
285 | g_assert (out_value != NULL); |
286 | |
287 | pango_font_get_glyph_extents (font: key->font, glyph: key->glyph, ink_rect: &ink_rect, NULL); |
288 | pango_extents_to_pixels (inclusive: &ink_rect, NULL); |
289 | |
290 | ink_rect.x -= 1; |
291 | ink_rect.width += 2; |
292 | ink_rect.y -= 1; |
293 | ink_rect.height += 2; |
294 | |
295 | width = (int) ceil (x: ink_rect.width * key->scale / 1024.0); |
296 | height = (int) ceil (x: ink_rect.height * key->scale / 1024.0); |
297 | |
298 | value = gsk_gl_texture_library_pack (self: tl, |
299 | key, |
300 | valuelen: sizeof *value, |
301 | width, |
302 | height, |
303 | padding: 1, |
304 | out_packed_x: &packed_x, out_packed_y: &packed_y); |
305 | |
306 | memcpy (dest: &value->ink_rect, src: &ink_rect, n: sizeof ink_rect); |
307 | |
308 | if (key->scale > 0 && width > 0 && height > 0) |
309 | gsk_gl_glyph_library_upload_glyph (self, |
310 | key, |
311 | value, |
312 | x: packed_x + 1, |
313 | y: packed_y + 1, |
314 | width, |
315 | height, |
316 | uwidth: ink_rect.width, |
317 | uheight: ink_rect.height); |
318 | |
319 | *out_value = value; |
320 | |
321 | return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (d: value) != 0; |
322 | } |
323 | |