1 | /* gskgldriver.c |
2 | * |
3 | * Copyright 2017 Timm Bäder <mail@baedert.org> |
4 | * Copyright 2018 Matthias Clasen <mclasen@redhat.com> |
5 | * Copyright 2018 Alexander Larsson <alexl@redhat.com> |
6 | * Copyright 2020 Christian Hergert <chergert@redhat.com> |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Lesser General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2.1 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Lesser General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Lesser General Public |
19 | * License along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | * |
21 | * SPDX-License-Identifier: LGPL-2.1-or-later |
22 | */ |
23 | |
24 | #include "config.h" |
25 | |
26 | #include "gskgldriverprivate.h" |
27 | |
28 | #include <gsk/gskdebugprivate.h> |
29 | #include <gsk/gskglshaderprivate.h> |
30 | #include <gsk/gskrendererprivate.h> |
31 | |
32 | #include "gskglcommandqueueprivate.h" |
33 | #include "gskglcompilerprivate.h" |
34 | #include "gskglglyphlibraryprivate.h" |
35 | #include "gskgliconlibraryprivate.h" |
36 | #include "gskglprogramprivate.h" |
37 | #include "gskglshadowlibraryprivate.h" |
38 | #include "gskgltextureprivate.h" |
39 | #include "fp16private.h" |
40 | |
41 | #include <gdk/gdkglcontextprivate.h> |
42 | #include <gdk/gdkdisplayprivate.h> |
43 | #include <gdk/gdkmemorytextureprivate.h> |
44 | #include <gdk/gdkprofilerprivate.h> |
45 | #include <gdk/gdktextureprivate.h> |
46 | |
47 | #define ATLAS_SIZE 512 |
48 | #define MAX_OLD_RATIO 0.5 |
49 | |
50 | G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT) |
51 | |
52 | static guint |
53 | texture_key_hash (gconstpointer v) |
54 | { |
55 | const GskTextureKey *k = (const GskTextureKey *)v; |
56 | |
57 | /* Optimize for 0..3 where 0 is the scaled out case. Usually |
58 | * we'll be squarely on 1 or 2 for standard vs HiDPI. When rendering |
59 | * to a texture scaled out like in node-editor, we might be < 1. |
60 | */ |
61 | guint scale_x = floorf (x: k->scale_x); |
62 | guint scale_y = floorf (x: k->scale_y); |
63 | |
64 | return GPOINTER_TO_SIZE (k->pointer) ^ |
65 | ((scale_x << 8) | |
66 | (scale_y << 6) | |
67 | (k->filter << 1) | |
68 | k->pointer_is_child); |
69 | } |
70 | |
71 | static gboolean |
72 | texture_key_equal (gconstpointer v1, |
73 | gconstpointer v2) |
74 | { |
75 | const GskTextureKey *k1 = (const GskTextureKey *)v1; |
76 | const GskTextureKey *k2 = (const GskTextureKey *)v2; |
77 | |
78 | return k1->pointer == k2->pointer && |
79 | k1->scale_x == k2->scale_x && |
80 | k1->scale_y == k2->scale_y && |
81 | k1->filter == k2->filter && |
82 | k1->pointer_is_child == k2->pointer_is_child && |
83 | (!k1->pointer_is_child || memcmp (s1: &k1->parent_rect, s2: &k2->parent_rect, n: sizeof k1->parent_rect) == 0); |
84 | } |
85 | |
86 | static void |
87 | remove_texture_key_for_id (GskGLDriver *self, |
88 | guint texture_id) |
89 | { |
90 | GskTextureKey *key; |
91 | |
92 | g_assert (GSK_IS_GL_DRIVER (self)); |
93 | g_assert (texture_id > 0); |
94 | |
95 | /* g_hash_table_remove() will cause @key to be freed */ |
96 | if (g_hash_table_steal_extended (hash_table: self->texture_id_to_key, |
97 | GUINT_TO_POINTER (texture_id), |
98 | NULL, |
99 | stolen_value: (gpointer *)&key)) |
100 | g_hash_table_remove (hash_table: self->key_to_texture_id, key); |
101 | } |
102 | |
103 | static void |
104 | gsk_gl_texture_destroyed (gpointer data) |
105 | { |
106 | ((GskGLTexture *)data)->user = NULL; |
107 | } |
108 | |
109 | static void |
110 | gsk_gl_driver_autorelease_texture (GskGLDriver *self, |
111 | guint texture_id) |
112 | { |
113 | g_assert (GSK_IS_GL_DRIVER (self)); |
114 | |
115 | g_array_append_val (self->texture_pool, texture_id); |
116 | } |
117 | |
118 | static guint |
119 | gsk_gl_driver_collect_unused_textures (GskGLDriver *self, |
120 | gint64 watermark) |
121 | { |
122 | GHashTableIter iter; |
123 | gpointer k, v; |
124 | guint old_size; |
125 | guint collected; |
126 | |
127 | g_assert (GSK_IS_GL_DRIVER (self)); |
128 | |
129 | old_size = g_hash_table_size (hash_table: self->textures); |
130 | |
131 | g_hash_table_iter_init (iter: &iter, hash_table: self->textures); |
132 | while (g_hash_table_iter_next (iter: &iter, key: &k, value: &v)) |
133 | { |
134 | GskGLTexture *t = v; |
135 | |
136 | if (t->user || t->permanent) |
137 | continue; |
138 | |
139 | if (t->last_used_in_frame <= watermark) |
140 | { |
141 | g_hash_table_iter_steal (iter: &iter); |
142 | |
143 | g_assert (t->link.prev == NULL); |
144 | g_assert (t->link.next == NULL); |
145 | g_assert (t->link.data == t); |
146 | |
147 | remove_texture_key_for_id (self, texture_id: t->texture_id); |
148 | gsk_gl_driver_autorelease_texture (self, texture_id: t->texture_id); |
149 | t->texture_id = 0; |
150 | gsk_gl_texture_free (texture: t); |
151 | } |
152 | } |
153 | |
154 | collected = old_size - g_hash_table_size (hash_table: self->textures); |
155 | |
156 | return collected; |
157 | } |
158 | |
159 | static void |
160 | gsk_gl_texture_atlas_free (GskGLTextureAtlas *atlas) |
161 | { |
162 | if (atlas->texture_id != 0) |
163 | { |
164 | glDeleteTextures (1, &atlas->texture_id); |
165 | atlas->texture_id = 0; |
166 | } |
167 | |
168 | g_clear_pointer (&atlas->nodes, g_free); |
169 | g_slice_free (GskGLTextureAtlas, atlas); |
170 | } |
171 | |
172 | GskGLTextureAtlas * |
173 | gsk_gl_driver_create_atlas (GskGLDriver *self) |
174 | { |
175 | GskGLTextureAtlas *atlas; |
176 | |
177 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
178 | |
179 | atlas = g_slice_new0 (GskGLTextureAtlas); |
180 | atlas->width = ATLAS_SIZE; |
181 | atlas->height = ATLAS_SIZE; |
182 | /* TODO: We might want to change the strategy about the amount of |
183 | * nodes here? stb_rect_pack.h says width is optimal. */ |
184 | atlas->nodes = g_malloc0_n (n_blocks: atlas->width, n_block_bytes: sizeof (struct stbrp_node)); |
185 | stbrp_init_target (context: &atlas->context, width: atlas->width, height: atlas->height, nodes: atlas->nodes, num_nodes: atlas->width); |
186 | atlas->texture_id = gsk_gl_command_queue_create_texture (self: self->command_queue, |
187 | width: atlas->width, |
188 | height: atlas->height, |
189 | GL_RGBA8, |
190 | GL_LINEAR, |
191 | GL_LINEAR); |
192 | |
193 | gdk_gl_context_label_object_printf (context: gdk_gl_context_get_current (), |
194 | GL_TEXTURE, name: atlas->texture_id, |
195 | format: "Texture atlas %d" , |
196 | atlas->texture_id); |
197 | |
198 | g_ptr_array_add (array: self->atlases, data: atlas); |
199 | |
200 | return atlas; |
201 | } |
202 | |
203 | static void |
204 | remove_program (gpointer data) |
205 | { |
206 | GskGLProgram *program = data; |
207 | |
208 | g_assert (!program || GSK_IS_GL_PROGRAM (program)); |
209 | |
210 | if (program != NULL) |
211 | { |
212 | gsk_gl_program_delete (self: program); |
213 | g_object_unref (object: program); |
214 | } |
215 | } |
216 | |
217 | static void |
218 | gsk_gl_driver_shader_weak_cb (gpointer data, |
219 | GObject *where_object_was) |
220 | { |
221 | GskGLDriver *self = data; |
222 | |
223 | g_assert (GSK_IS_GL_DRIVER (self)); |
224 | |
225 | if (self->shader_cache != NULL) |
226 | g_hash_table_remove (hash_table: self->shader_cache, key: where_object_was); |
227 | } |
228 | |
229 | static void |
230 | gsk_gl_driver_dispose (GObject *object) |
231 | { |
232 | GskGLDriver *self = (GskGLDriver *)object; |
233 | |
234 | g_assert (GSK_IS_GL_DRIVER (self)); |
235 | g_assert (self->in_frame == FALSE); |
236 | |
237 | #define GSK_GL_NO_UNIFORMS |
238 | #define GSK_GL_ADD_UNIFORM(pos, KEY, name) |
239 | #define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \ |
240 | GSK_GL_DELETE_PROGRAM(name); \ |
241 | GSK_GL_DELETE_PROGRAM(name ## _no_clip); \ |
242 | GSK_GL_DELETE_PROGRAM(name ## _rect_clip); |
243 | #define GSK_GL_DELETE_PROGRAM(name) \ |
244 | G_STMT_START { \ |
245 | if (self->name) \ |
246 | gsk_gl_program_delete (self->name); \ |
247 | g_clear_object (&self->name); \ |
248 | } G_STMT_END; |
249 | # include "gskglprograms.defs" |
250 | #undef GSK_GL_NO_UNIFORMS |
251 | #undef GSK_GL_ADD_UNIFORM |
252 | #undef GSK_GL_DEFINE_PROGRAM |
253 | |
254 | if (self->shader_cache != NULL) |
255 | { |
256 | GHashTableIter iter; |
257 | gpointer k, v; |
258 | |
259 | g_hash_table_iter_init (iter: &iter, hash_table: self->shader_cache); |
260 | while (g_hash_table_iter_next (iter: &iter, key: &k, value: &v)) |
261 | { |
262 | GskGLShader *shader = k; |
263 | g_object_weak_unref (G_OBJECT (shader), |
264 | notify: gsk_gl_driver_shader_weak_cb, |
265 | data: self); |
266 | g_hash_table_iter_remove (iter: &iter); |
267 | } |
268 | |
269 | g_clear_pointer (&self->shader_cache, g_hash_table_unref); |
270 | } |
271 | |
272 | if (self->command_queue != NULL) |
273 | { |
274 | gsk_gl_command_queue_make_current (self: self->command_queue); |
275 | gsk_gl_driver_collect_unused_textures (self, watermark: 0); |
276 | g_clear_object (&self->command_queue); |
277 | } |
278 | |
279 | if (self->autorelease_framebuffers->len > 0) |
280 | { |
281 | glDeleteFramebuffers (self->autorelease_framebuffers->len, |
282 | (GLuint *)(gpointer)self->autorelease_framebuffers->data); |
283 | self->autorelease_framebuffers->len = 0; |
284 | } |
285 | |
286 | g_clear_pointer (&self->texture_pool, g_array_unref); |
287 | |
288 | g_assert (!self->textures || g_hash_table_size (self->textures) == 0); |
289 | g_assert (!self->texture_id_to_key || g_hash_table_size (self->texture_id_to_key) == 0); |
290 | g_assert (!self->key_to_texture_id|| g_hash_table_size (self->key_to_texture_id) == 0); |
291 | |
292 | g_clear_object (&self->glyphs); |
293 | g_clear_object (&self->icons); |
294 | g_clear_object (&self->shadows); |
295 | |
296 | g_clear_pointer (&self->atlases, g_ptr_array_unref); |
297 | g_clear_pointer (&self->autorelease_framebuffers, g_array_unref); |
298 | g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref); |
299 | g_clear_pointer (&self->textures, g_hash_table_unref); |
300 | g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref); |
301 | g_clear_pointer (&self->texture_id_to_key, g_hash_table_unref); |
302 | g_clear_pointer (&self->render_targets, g_ptr_array_unref); |
303 | g_clear_pointer (&self->shader_cache, g_hash_table_unref); |
304 | |
305 | g_clear_object (&self->command_queue); |
306 | g_clear_object (&self->shared_command_queue); |
307 | |
308 | G_OBJECT_CLASS (gsk_gl_driver_parent_class)->dispose (object); |
309 | } |
310 | |
311 | static void |
312 | gsk_gl_driver_class_init (GskGLDriverClass *klass) |
313 | { |
314 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
315 | |
316 | object_class->dispose = gsk_gl_driver_dispose; |
317 | } |
318 | |
319 | static void |
320 | gsk_gl_driver_init (GskGLDriver *self) |
321 | { |
322 | self->autorelease_framebuffers = g_array_new (FALSE, FALSE, element_size: sizeof (guint)); |
323 | self->textures = g_hash_table_new_full (NULL, NULL, NULL, |
324 | value_destroy_func: (GDestroyNotify)gsk_gl_texture_free); |
325 | self->texture_id_to_key = g_hash_table_new (NULL, NULL); |
326 | self->key_to_texture_id = g_hash_table_new_full (hash_func: texture_key_hash, |
327 | key_equal_func: texture_key_equal, |
328 | key_destroy_func: g_free, |
329 | NULL); |
330 | self->shader_cache = g_hash_table_new_full (NULL, NULL, NULL, value_destroy_func: remove_program); |
331 | self->texture_pool = g_array_new (FALSE, FALSE, element_size: sizeof (guint)); |
332 | self->render_targets = g_ptr_array_new (); |
333 | self->atlases = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)gsk_gl_texture_atlas_free); |
334 | } |
335 | |
336 | static gboolean |
337 | gsk_gl_driver_load_programs (GskGLDriver *self, |
338 | GError **error) |
339 | { |
340 | GskGLCompiler *compiler; |
341 | gboolean ret = FALSE; |
342 | G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; |
343 | |
344 | g_assert (GSK_IS_GL_DRIVER (self)); |
345 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self->command_queue)); |
346 | |
347 | compiler = gsk_gl_compiler_new (driver: self, debug: self->debug); |
348 | |
349 | /* Setup preambles that are shared by all shaders */ |
350 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
351 | kind: GSK_GL_COMPILER_ALL, |
352 | resource_path: "/org/gtk/libgsk/gl/preamble.glsl" ); |
353 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
354 | kind: GSK_GL_COMPILER_VERTEX, |
355 | resource_path: "/org/gtk/libgsk/gl/preamble.vs.glsl" ); |
356 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
357 | kind: GSK_GL_COMPILER_FRAGMENT, |
358 | resource_path: "/org/gtk/libgsk/gl/preamble.fs.glsl" ); |
359 | |
360 | /* Setup attributes that are provided via VBO */ |
361 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aPosition" , location: 0); |
362 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aUv" , location: 1); |
363 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aColor" , location: 2); |
364 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aColor2" , location: 3); |
365 | |
366 | /* Use XMacros to register all of our programs and their uniforms */ |
367 | #define GSK_GL_NO_UNIFORMS |
368 | #define GSK_GL_ADD_UNIFORM(pos, KEY, name) \ |
369 | gsk_gl_program_add_uniform (program, #name, UNIFORM_##KEY); |
370 | #define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \ |
371 | gsk_gl_compiler_set_source_from_resource (compiler, GSK_GL_COMPILER_ALL, resource); \ |
372 | GSK_GL_COMPILE_PROGRAM(name ## _no_clip, uniforms, "#define NO_CLIP 1\n"); \ |
373 | GSK_GL_COMPILE_PROGRAM(name ## _rect_clip, uniforms, "#define RECT_CLIP 1\n"); \ |
374 | GSK_GL_COMPILE_PROGRAM(name, uniforms, ""); |
375 | #define GSK_GL_COMPILE_PROGRAM(name, uniforms, clip) \ |
376 | G_STMT_START { \ |
377 | GskGLProgram *program; \ |
378 | gboolean have_alpha; \ |
379 | gboolean have_source; \ |
380 | \ |
381 | if (!(program = gsk_gl_compiler_compile (compiler, #name, clip, error))) \ |
382 | goto failure; \ |
383 | \ |
384 | have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA); \ |
385 | have_source = gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE); \ |
386 | gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT); \ |
387 | gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT); \ |
388 | gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION); \ |
389 | gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW); \ |
390 | \ |
391 | uniforms \ |
392 | \ |
393 | gsk_gl_program_uniforms_added (program, have_source); \ |
394 | if (have_alpha) \ |
395 | gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f); \ |
396 | \ |
397 | *(GskGLProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskGLDriver, name)) = \ |
398 | g_steal_pointer (&program); \ |
399 | } G_STMT_END; |
400 | # include "gskglprograms.defs" |
401 | #undef GSK_GL_DEFINE_PROGRAM_CLIP |
402 | #undef GSK_GL_DEFINE_PROGRAM |
403 | #undef GSK_GL_ADD_UNIFORM |
404 | |
405 | ret = TRUE; |
406 | |
407 | failure: |
408 | g_clear_object (&compiler); |
409 | |
410 | gdk_profiler_end_mark (start_time, "load programs" , NULL); |
411 | |
412 | return ret; |
413 | } |
414 | |
415 | /** |
416 | * gsk_gl_driver_autorelease_framebuffer: |
417 | * @self: a `GskGLDriver` |
418 | * @framebuffer_id: the id of the OpenGL framebuffer |
419 | * |
420 | * Marks @framebuffer_id to be deleted when the current frame has cmopleted. |
421 | */ |
422 | static void |
423 | gsk_gl_driver_autorelease_framebuffer (GskGLDriver *self, |
424 | guint framebuffer_id) |
425 | { |
426 | g_assert (GSK_IS_GL_DRIVER (self)); |
427 | |
428 | g_array_append_val (self->autorelease_framebuffers, framebuffer_id); |
429 | } |
430 | |
431 | static GskGLDriver * |
432 | gsk_gl_driver_new (GskGLCommandQueue *command_queue, |
433 | gboolean debug_shaders, |
434 | GError **error) |
435 | { |
436 | GskGLDriver *self; |
437 | GdkGLContext *context; |
438 | gint64 before G_GNUC_UNUSED; |
439 | |
440 | g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL); |
441 | |
442 | before = GDK_PROFILER_CURRENT_TIME; |
443 | |
444 | context = gsk_gl_command_queue_get_context (self: command_queue); |
445 | |
446 | gdk_gl_context_make_current (context); |
447 | |
448 | self = g_object_new (GSK_TYPE_GL_DRIVER, NULL); |
449 | self->command_queue = g_object_ref (command_queue); |
450 | self->shared_command_queue = g_object_ref (command_queue); |
451 | self->debug = !!debug_shaders; |
452 | |
453 | if (!gsk_gl_driver_load_programs (self, error)) |
454 | { |
455 | g_object_unref (object: self); |
456 | return NULL; |
457 | } |
458 | |
459 | self->glyphs = gsk_gl_glyph_library_new (driver: self); |
460 | self->icons = gsk_gl_icon_library_new (driver: self); |
461 | self->shadows = gsk_gl_shadow_library_new (driver: self); |
462 | |
463 | gdk_profiler_end_mark (before, "create GskGLDriver" , NULL); |
464 | |
465 | return g_steal_pointer (&self); |
466 | } |
467 | |
468 | /** |
469 | * gsk_gl_driver_for_display: |
470 | * @display: A #GdkDisplay that is known to support GL |
471 | * @debug_shaders: if debug information for shaders should be displayed |
472 | * @error: location for error information |
473 | * |
474 | * Retrieves a driver for a shared display. Generally this is shared across all GL |
475 | * contexts for a display so that fewer programs are necessary for driving output. |
476 | * |
477 | * Returns: (transfer full): a `GskGLDriver` if successful; otherwise %NULL and |
478 | * @error is set. |
479 | */ |
480 | GskGLDriver * |
481 | gsk_gl_driver_for_display (GdkDisplay *display, |
482 | gboolean debug_shaders, |
483 | GError **error) |
484 | { |
485 | GdkGLContext *context; |
486 | GskGLCommandQueue *command_queue = NULL; |
487 | GskGLDriver *driver; |
488 | |
489 | g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); |
490 | |
491 | if ((driver = g_object_get_data (G_OBJECT (display), key: "GSK_GL_DRIVER" ))) |
492 | return g_object_ref (driver); |
493 | |
494 | context = gdk_display_get_gl_context (display); |
495 | g_assert (context); |
496 | |
497 | gdk_gl_context_make_current (context); |
498 | |
499 | /* Initially we create a command queue using the shared context. However, |
500 | * as frames are processed this will be replaced with the command queue |
501 | * for a given renderer. But since the programs are compiled into the |
502 | * shared context, all other contexts sharing with it will have access |
503 | * to those programs. |
504 | */ |
505 | command_queue = gsk_gl_command_queue_new (context, NULL); |
506 | |
507 | if (!(driver = gsk_gl_driver_new (command_queue, debug_shaders, error))) |
508 | goto failure; |
509 | |
510 | g_object_set_data_full (G_OBJECT (display), |
511 | key: "GSK_GL_DRIVER" , |
512 | g_object_ref (driver), |
513 | destroy: g_object_unref); |
514 | |
515 | failure: |
516 | g_clear_object (&command_queue); |
517 | |
518 | return g_steal_pointer (&driver); |
519 | } |
520 | |
521 | static GPtrArray * |
522 | gsk_gl_driver_compact_atlases (GskGLDriver *self) |
523 | { |
524 | GPtrArray *removed = NULL; |
525 | |
526 | g_assert (GSK_IS_GL_DRIVER (self)); |
527 | |
528 | for (guint i = self->atlases->len; i > 0; i--) |
529 | { |
530 | GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1); |
531 | |
532 | if (gsk_gl_texture_atlas_get_unused_ratio (self: atlas) > MAX_OLD_RATIO) |
533 | { |
534 | GSK_NOTE (GLYPH_CACHE, |
535 | g_message ("Dropping atlas %d (%g.2%% old)" , i, |
536 | 100.0 * gsk_gl_texture_atlas_get_unused_ratio (atlas))); |
537 | if (removed == NULL) |
538 | removed = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)gsk_gl_texture_atlas_free); |
539 | g_ptr_array_add (array: removed, data: g_ptr_array_steal_index (array: self->atlases, index_: i - 1)); |
540 | } |
541 | } |
542 | |
543 | GSK_NOTE (GLYPH_CACHE, { |
544 | static guint timestamp; |
545 | if (timestamp++ % 60 == 0) |
546 | g_message ("%d atlases" , self->atlases->len); |
547 | }); |
548 | |
549 | return removed; |
550 | } |
551 | |
552 | /** |
553 | * gsk_gl_driver_begin_frame: |
554 | * @self: a `GskGLDriver` |
555 | * @command_queue: A `GskGLCommandQueue` from the renderer |
556 | * |
557 | * Begin a new frame. |
558 | * |
559 | * Texture atlases, pools, and other resources will be prepared to draw the |
560 | * next frame. The command queue should be one that was created for the |
561 | * target context to be drawn into (the context of the renderer's surface). |
562 | */ |
563 | void |
564 | gsk_gl_driver_begin_frame (GskGLDriver *self, |
565 | GskGLCommandQueue *command_queue) |
566 | { |
567 | gint64 last_frame_id; |
568 | GPtrArray *removed; |
569 | |
570 | g_return_if_fail (GSK_IS_GL_DRIVER (self)); |
571 | g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue)); |
572 | g_return_if_fail (self->in_frame == FALSE); |
573 | |
574 | last_frame_id = self->current_frame_id; |
575 | |
576 | self->in_frame = TRUE; |
577 | self->current_frame_id++; |
578 | |
579 | g_set_object (&self->command_queue, command_queue); |
580 | |
581 | gsk_gl_command_queue_begin_frame (self: self->command_queue); |
582 | |
583 | /* Compact atlases with too many freed pixels */ |
584 | removed = gsk_gl_driver_compact_atlases (self); |
585 | |
586 | /* Mark unused pixel regions of the atlases */ |
587 | gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->icons), |
588 | frame_id: self->current_frame_id, |
589 | removed_atlases: removed); |
590 | gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs), |
591 | frame_id: self->current_frame_id, |
592 | removed_atlases: removed); |
593 | |
594 | /* Cleanup old shadows */ |
595 | gsk_gl_shadow_library_begin_frame (self: self->shadows); |
596 | |
597 | /* Remove all textures that are from a previous frame or are no |
598 | * longer used by linked GdkTexture. We do this at the beginning |
599 | * of the following frame instead of the end so that we reduce chances |
600 | * we block on any resources while delivering our frames. |
601 | */ |
602 | gsk_gl_driver_collect_unused_textures (self, watermark: last_frame_id - 1); |
603 | |
604 | /* Now free atlas textures */ |
605 | g_clear_pointer (&removed, g_ptr_array_unref); |
606 | } |
607 | |
608 | /** |
609 | * gsk_gl_driver_end_frame: |
610 | * @self: a `GskGLDriver` |
611 | * |
612 | * Clean up resources from drawing the current frame. |
613 | * |
614 | * Temporary resources used while drawing will be released. |
615 | */ |
616 | void |
617 | gsk_gl_driver_end_frame (GskGLDriver *self) |
618 | { |
619 | g_return_if_fail (GSK_IS_GL_DRIVER (self)); |
620 | g_return_if_fail (self->in_frame == TRUE); |
621 | |
622 | gsk_gl_command_queue_make_current (self: self->command_queue); |
623 | gsk_gl_command_queue_end_frame (self: self->command_queue); |
624 | |
625 | self->in_frame = FALSE; |
626 | } |
627 | |
628 | /** |
629 | * gsk_gl_driver_after_frame: |
630 | * @self: a `GskGLDriver` |
631 | * |
632 | * This function does post-frame cleanup operations. |
633 | * |
634 | * To reduce the chances of blocking on the driver it is performed |
635 | * after the frame has swapped buffers. |
636 | */ |
637 | void |
638 | gsk_gl_driver_after_frame (GskGLDriver *self) |
639 | { |
640 | g_return_if_fail (GSK_IS_GL_DRIVER (self)); |
641 | g_return_if_fail (self->in_frame == FALSE); |
642 | |
643 | /* Release any render targets (possibly adding them to |
644 | * self->autorelease_framebuffers) so we can release the FBOs immediately |
645 | * afterwards. |
646 | */ |
647 | while (self->render_targets->len > 0) |
648 | { |
649 | GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1); |
650 | |
651 | gsk_gl_driver_autorelease_framebuffer (self, framebuffer_id: render_target->framebuffer_id); |
652 | gsk_gl_driver_autorelease_texture (self, texture_id: render_target->texture_id); |
653 | g_slice_free (GskGLRenderTarget, render_target); |
654 | |
655 | self->render_targets->len--; |
656 | } |
657 | |
658 | /* Now that we have collected render targets, release all the FBOs */ |
659 | if (self->autorelease_framebuffers->len > 0) |
660 | { |
661 | glDeleteFramebuffers (self->autorelease_framebuffers->len, |
662 | (GLuint *)(gpointer)self->autorelease_framebuffers->data); |
663 | self->autorelease_framebuffers->len = 0; |
664 | } |
665 | |
666 | /* Release any cached textures we used during the frame */ |
667 | if (self->texture_pool->len > 0) |
668 | { |
669 | glDeleteTextures (self->texture_pool->len, |
670 | (GLuint *)(gpointer)self->texture_pool->data); |
671 | self->texture_pool->len = 0; |
672 | } |
673 | |
674 | /* Reset command queue to our shared queue incase we have operations |
675 | * that need to be processed outside of a frame (such as callbacks |
676 | * from external systems such as GDK). |
677 | */ |
678 | g_set_object (&self->command_queue, self->shared_command_queue); |
679 | } |
680 | |
681 | GdkGLContext * |
682 | gsk_gl_driver_get_context (GskGLDriver *self) |
683 | { |
684 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
685 | g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), NULL); |
686 | |
687 | return gsk_gl_command_queue_get_context (self: self->command_queue); |
688 | } |
689 | |
690 | /** |
691 | * gsk_gl_driver_cache_texture: |
692 | * @self: a `GskGLDriver` |
693 | * @key: the key for the texture |
694 | * @texture_id: the id of the texture to be cached |
695 | * |
696 | * Inserts @texture_id into the texture cache using @key. |
697 | * |
698 | * Textures can be looked up by @key after calling this function using |
699 | * gsk_gl_driver_lookup_texture(). |
700 | * |
701 | * Textures that have not been used within a number of frames will be |
702 | * purged from the texture cache automatically. |
703 | */ |
704 | void |
705 | gsk_gl_driver_cache_texture (GskGLDriver *self, |
706 | const GskTextureKey *key, |
707 | guint texture_id) |
708 | { |
709 | GskTextureKey *k; |
710 | |
711 | g_assert (GSK_IS_GL_DRIVER (self)); |
712 | g_assert (key != NULL); |
713 | g_assert (texture_id > 0); |
714 | g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id))); |
715 | |
716 | k = g_memdup (mem: key, byte_size: sizeof *key); |
717 | |
718 | g_hash_table_insert (hash_table: self->key_to_texture_id, key: k, GUINT_TO_POINTER (texture_id)); |
719 | g_hash_table_insert (hash_table: self->texture_id_to_key, GUINT_TO_POINTER (texture_id), value: k); |
720 | } |
721 | |
722 | /** |
723 | * gsk_gl_driver_load_texture: |
724 | * @self: a `GdkTexture` |
725 | * @texture: a `GdkTexture` |
726 | * @min_filter: GL_NEAREST or GL_LINEAR |
727 | * @mag_filter: GL_NEAREST or GL_LINEAR |
728 | * |
729 | * Loads a `GdkTexture` by uploading the contents to the GPU when |
730 | * necessary. If @texture is a `GdkGLTexture`, it can be used without |
731 | * uploading contents to the GPU. |
732 | * |
733 | * If the texture has already been uploaded and not yet released |
734 | * from cache, this function returns that texture id without further |
735 | * work. |
736 | * |
737 | * If the texture has not been used for a number of frames, it will |
738 | * be removed from cache. |
739 | * |
740 | * There is no need to release the resulting texture identifier after |
741 | * using it. It will be released automatically. |
742 | * |
743 | * Returns: a texture identifier |
744 | */ |
745 | guint |
746 | gsk_gl_driver_load_texture (GskGLDriver *self, |
747 | GdkTexture *texture, |
748 | int min_filter, |
749 | int mag_filter) |
750 | { |
751 | GdkGLContext *context; |
752 | GdkMemoryTexture *downloaded_texture; |
753 | GskGLTexture *t; |
754 | guint texture_id; |
755 | int height; |
756 | int width; |
757 | int format; |
758 | |
759 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0); |
760 | g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0); |
761 | g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), 0); |
762 | |
763 | context = self->command_queue->context; |
764 | |
765 | format = GL_RGBA8; |
766 | |
767 | if (GDK_IS_GL_TEXTURE (texture)) |
768 | { |
769 | GdkGLTexture *gl_texture = (GdkGLTexture *) texture; |
770 | GdkGLContext *texture_context = gdk_gl_texture_get_context (self: gl_texture); |
771 | |
772 | if (gdk_gl_context_is_shared (self: context, other: texture_context)) |
773 | { |
774 | /* A GL texture from the same GL context is a simple task... */ |
775 | return gdk_gl_texture_get_id (self: gl_texture); |
776 | } |
777 | else |
778 | { |
779 | downloaded_texture = gdk_memory_texture_from_texture (texture, format: gdk_texture_get_format (self: texture)); |
780 | } |
781 | } |
782 | else |
783 | { |
784 | if ((t = gdk_texture_get_render_data (self: texture, key: self))) |
785 | { |
786 | if (t->min_filter == min_filter && t->mag_filter == mag_filter) |
787 | return t->texture_id; |
788 | } |
789 | |
790 | downloaded_texture = gdk_memory_texture_from_texture (texture, format: gdk_texture_get_format (self: texture)); |
791 | } |
792 | |
793 | /* The download_texture() call may have switched the GL context. Make sure |
794 | * the right context is at work again. */ |
795 | gdk_gl_context_make_current (context); |
796 | |
797 | width = gdk_texture_get_width (texture); |
798 | height = gdk_texture_get_height (texture); |
799 | texture_id = gsk_gl_command_queue_upload_texture (self: self->command_queue, |
800 | GDK_TEXTURE (downloaded_texture), |
801 | min_filter, |
802 | mag_filter); |
803 | |
804 | t = gsk_gl_texture_new (texture_id, |
805 | width, height, format, min_filter, mag_filter, |
806 | frame_id: self->current_frame_id); |
807 | |
808 | g_hash_table_insert (hash_table: self->textures, GUINT_TO_POINTER (texture_id), value: t); |
809 | |
810 | if (gdk_texture_set_render_data (self: texture, key: self, data: t, notify: gsk_gl_texture_destroyed)) |
811 | t->user = texture; |
812 | |
813 | gdk_gl_context_label_object_printf (context, GL_TEXTURE, name: t->texture_id, |
814 | format: "GdkTexture<%p> %d" , texture, t->texture_id); |
815 | |
816 | g_clear_object (&downloaded_texture); |
817 | |
818 | return texture_id; |
819 | } |
820 | |
821 | /** |
822 | * gsk_gl_driver_create_texture: |
823 | * @self: a `GskGLDriver` |
824 | * @width: the width of the texture |
825 | * @height: the height of the texture |
826 | * @format: format for the texture |
827 | * @min_filter: GL_NEAREST or GL_LINEAR |
828 | * @mag_filter: GL_NEAREST or GL_FILTER |
829 | * |
830 | * Creates a new texture immediately that can be used by the caller |
831 | * to upload data, map to a framebuffer, or other uses which may |
832 | * modify the texture immediately. |
833 | * |
834 | * Typical examples for @format are GL_RGBA8, GL_RGBA16F or GL_RGBA32F. |
835 | * |
836 | * Use gsk_gl_driver_release_texture() to release this texture back into |
837 | * the pool so it may be reused later in the pipeline. |
838 | * |
839 | * Returns: a `GskGLTexture` which can be returned to the pool with |
840 | * gsk_gl_driver_release_texture(). |
841 | */ |
842 | GskGLTexture * |
843 | gsk_gl_driver_create_texture (GskGLDriver *self, |
844 | float width, |
845 | float height, |
846 | int format, |
847 | int min_filter, |
848 | int mag_filter) |
849 | { |
850 | GskGLTexture *texture; |
851 | guint texture_id; |
852 | |
853 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
854 | |
855 | texture_id = gsk_gl_command_queue_create_texture (self: self->command_queue, |
856 | width, height, |
857 | format, |
858 | min_filter, mag_filter); |
859 | texture = gsk_gl_texture_new (texture_id, |
860 | width, height, |
861 | format, |
862 | min_filter, mag_filter, |
863 | frame_id: self->current_frame_id); |
864 | g_hash_table_insert (hash_table: self->textures, |
865 | GUINT_TO_POINTER (texture->texture_id), |
866 | value: texture); |
867 | |
868 | return texture; |
869 | } |
870 | |
871 | /** |
872 | * gsk_gl_driver_release_texture: |
873 | * @self: a `GskGLDriver` |
874 | * @texture: a `GskGLTexture` |
875 | * |
876 | * Releases @texture back into the pool so that it can be used later |
877 | * in the command stream by future batches. This helps reduce VRAM |
878 | * usage on the GPU. |
879 | * |
880 | * When the frame has completed, pooled textures will be released |
881 | * to free additional VRAM back to the system. |
882 | */ |
883 | void |
884 | gsk_gl_driver_release_texture (GskGLDriver *self, |
885 | GskGLTexture *texture) |
886 | { |
887 | guint texture_id; |
888 | |
889 | g_assert (GSK_IS_GL_DRIVER (self)); |
890 | g_assert (texture != NULL); |
891 | |
892 | texture_id = texture->texture_id; |
893 | texture->texture_id = 0; |
894 | gsk_gl_texture_free (texture); |
895 | |
896 | if (texture_id > 0) |
897 | remove_texture_key_for_id (self, texture_id); |
898 | |
899 | g_hash_table_steal (hash_table: self->textures, GUINT_TO_POINTER (texture_id)); |
900 | gsk_gl_driver_autorelease_texture (self, texture_id); |
901 | } |
902 | |
903 | /** |
904 | * gsk_gl_driver_create_render_target: |
905 | * @self: a `GskGLDriver` |
906 | * @width: the width for the render target |
907 | * @height: the height for the render target |
908 | * @format: the format to use |
909 | * @min_filter: the min filter to use for the texture |
910 | * @mag_filter: the mag filter to use for the texture |
911 | * @out_render_target: (out): a location for the render target |
912 | * |
913 | * Creates a new render target which contains a framebuffer and a texture |
914 | * bound to that framebuffer of the size @width x @height and using the |
915 | * appropriate filters. |
916 | * |
917 | * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F. |
918 | * |
919 | * Use gsk_gl_driver_release_render_target() when you are finished with |
920 | * the render target to release it. You may steal the texture from the |
921 | * render target when releasing it. |
922 | * |
923 | * Returns: %TRUE if successful; otherwise %FALSE and @out_fbo_id and |
924 | * @out_texture_id are undefined. |
925 | */ |
926 | gboolean |
927 | gsk_gl_driver_create_render_target (GskGLDriver *self, |
928 | int width, |
929 | int height, |
930 | int format, |
931 | int min_filter, |
932 | int mag_filter, |
933 | GskGLRenderTarget **out_render_target) |
934 | { |
935 | guint framebuffer_id; |
936 | guint texture_id; |
937 | |
938 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), FALSE); |
939 | g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), FALSE); |
940 | g_return_val_if_fail (out_render_target != NULL, FALSE); |
941 | |
942 | #if 0 |
943 | if (self->render_targets->len > 0) |
944 | { |
945 | for (guint i = self->render_targets->len; i > 0; i--) |
946 | { |
947 | GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1); |
948 | |
949 | if (render_target->width == width && |
950 | render_target->height == height && |
951 | render_target->min_filter == min_filter && |
952 | render_target->mag_filter == mag_filter) |
953 | { |
954 | *out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1); |
955 | return TRUE; |
956 | } |
957 | } |
958 | } |
959 | #endif |
960 | |
961 | if (gsk_gl_command_queue_create_render_target (self: self->command_queue, |
962 | width, height, |
963 | format, |
964 | min_filter, mag_filter, |
965 | out_fbo_id: &framebuffer_id, out_texture_id: &texture_id)) |
966 | { |
967 | GskGLRenderTarget *render_target; |
968 | |
969 | render_target = g_slice_new0 (GskGLRenderTarget); |
970 | render_target->min_filter = min_filter; |
971 | render_target->mag_filter = mag_filter; |
972 | render_target->format = format; |
973 | render_target->width = width; |
974 | render_target->height = height; |
975 | render_target->framebuffer_id = framebuffer_id; |
976 | render_target->texture_id = texture_id; |
977 | |
978 | *out_render_target = render_target; |
979 | |
980 | return TRUE; |
981 | } |
982 | |
983 | *out_render_target = NULL; |
984 | |
985 | return FALSE; |
986 | } |
987 | |
988 | /** |
989 | * gsk_gl_driver_release_render_target: |
990 | * @self: a `GskGLDriver` |
991 | * @render_target: a `GskGLRenderTarget` created with |
992 | * gsk_gl_driver_create_render_target(). |
993 | * @release_texture: if the texture should also be released |
994 | * |
995 | * Releases a render target that was previously created. An attempt may |
996 | * be made to cache the render target so that future creations of render |
997 | * targets are performed faster. |
998 | * |
999 | * If @release_texture is %FALSE, the backing texture id is returned and |
1000 | * the framebuffer is released. Otherwise, both the texture and framebuffer |
1001 | * are released or cached until the end of the frame. |
1002 | * |
1003 | * This may be called when building the render job as the texture or |
1004 | * framebuffer will not be removed immediately. |
1005 | * |
1006 | * Returns: a texture id if @release_texture is %FALSE, otherwise zero. |
1007 | */ |
1008 | guint |
1009 | gsk_gl_driver_release_render_target (GskGLDriver *self, |
1010 | GskGLRenderTarget *render_target, |
1011 | gboolean release_texture) |
1012 | { |
1013 | guint texture_id; |
1014 | |
1015 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0); |
1016 | g_return_val_if_fail (render_target != NULL, 0); |
1017 | |
1018 | if (release_texture) |
1019 | { |
1020 | texture_id = 0; |
1021 | g_ptr_array_add (array: self->render_targets, data: render_target); |
1022 | } |
1023 | else |
1024 | { |
1025 | GskGLTexture *texture; |
1026 | |
1027 | texture_id = render_target->texture_id; |
1028 | |
1029 | texture = gsk_gl_texture_new (texture_id: render_target->texture_id, |
1030 | width: render_target->width, |
1031 | height: render_target->height, |
1032 | format: render_target->format, |
1033 | min_filter: render_target->min_filter, |
1034 | mag_filter: render_target->mag_filter, |
1035 | frame_id: self->current_frame_id); |
1036 | g_hash_table_insert (hash_table: self->textures, |
1037 | GUINT_TO_POINTER (texture_id), |
1038 | g_steal_pointer (&texture)); |
1039 | |
1040 | gsk_gl_driver_autorelease_framebuffer (self, framebuffer_id: render_target->framebuffer_id); |
1041 | g_slice_free (GskGLRenderTarget, render_target); |
1042 | |
1043 | } |
1044 | |
1045 | return texture_id; |
1046 | } |
1047 | |
1048 | /** |
1049 | * gsk_gl_driver_lookup_shader: |
1050 | * @self: a `GskGLDriver` |
1051 | * @shader: the shader to lookup or load |
1052 | * @error: a location for a `GError` |
1053 | * |
1054 | * Attepts to load @shader from the shader cache. |
1055 | * |
1056 | * If it has not been loaded, then it will compile the shader on demand. |
1057 | * |
1058 | * Returns: (nullable) (transfer none): a `GskGLShader` |
1059 | */ |
1060 | GskGLProgram * |
1061 | gsk_gl_driver_lookup_shader (GskGLDriver *self, |
1062 | GskGLShader *shader, |
1063 | GError **error) |
1064 | { |
1065 | GskGLProgram *program; |
1066 | |
1067 | g_return_val_if_fail (self != NULL, NULL); |
1068 | g_return_val_if_fail (shader != NULL, NULL); |
1069 | |
1070 | program = g_hash_table_lookup (hash_table: self->shader_cache, key: shader); |
1071 | |
1072 | if (program == NULL) |
1073 | { |
1074 | const GskGLUniform *uniforms; |
1075 | GskGLCompiler *compiler; |
1076 | GBytes *suffix; |
1077 | int n_required_textures; |
1078 | int n_uniforms; |
1079 | |
1080 | uniforms = gsk_gl_shader_get_uniforms (shader, n_uniforms: &n_uniforms); |
1081 | if (n_uniforms > GSK_GL_PROGRAM_MAX_CUSTOM_ARGS) |
1082 | { |
1083 | g_set_error (err: error, |
1084 | GDK_GL_ERROR, |
1085 | code: GDK_GL_ERROR_UNSUPPORTED_FORMAT, |
1086 | format: "Tried to use %d uniforms, while only %d is supported" , |
1087 | n_uniforms, |
1088 | GSK_GL_PROGRAM_MAX_CUSTOM_ARGS); |
1089 | return NULL; |
1090 | } |
1091 | |
1092 | n_required_textures = gsk_gl_shader_get_n_textures (shader); |
1093 | if (n_required_textures > GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES) |
1094 | { |
1095 | g_set_error (err: error, |
1096 | GDK_GL_ERROR, |
1097 | code: GDK_GL_ERROR_UNSUPPORTED_FORMAT, |
1098 | format: "Tried to use %d textures, while only %d is supported" , |
1099 | n_required_textures, |
1100 | GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES); |
1101 | return NULL; |
1102 | } |
1103 | |
1104 | compiler = gsk_gl_compiler_new (driver: self, FALSE); |
1105 | suffix = gsk_gl_shader_get_source (shader); |
1106 | |
1107 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
1108 | kind: GSK_GL_COMPILER_ALL, |
1109 | resource_path: "/org/gtk/libgsk/gl/preamble.glsl" ); |
1110 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
1111 | kind: GSK_GL_COMPILER_VERTEX, |
1112 | resource_path: "/org/gtk/libgsk/gl/preamble.vs.glsl" ); |
1113 | gsk_gl_compiler_set_preamble_from_resource (self: compiler, |
1114 | kind: GSK_GL_COMPILER_FRAGMENT, |
1115 | resource_path: "/org/gtk/libgsk/gl/preamble.fs.glsl" ); |
1116 | gsk_gl_compiler_set_source_from_resource (self: compiler, |
1117 | kind: GSK_GL_COMPILER_ALL, |
1118 | resource_path: "/org/gtk/libgsk/gl/custom.glsl" ); |
1119 | gsk_gl_compiler_set_suffix (self: compiler, kind: GSK_GL_COMPILER_FRAGMENT, suffix_bytes: suffix); |
1120 | |
1121 | /* Setup attributes that are provided via VBO */ |
1122 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aPosition" , location: 0); |
1123 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aUv" , location: 1); |
1124 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aColor" , location: 2); |
1125 | gsk_gl_compiler_bind_attribute (self: compiler, name: "aColor2" , location: 3); |
1126 | |
1127 | if ((program = gsk_gl_compiler_compile (self: compiler, NULL, clip: "" , error))) |
1128 | { |
1129 | gboolean have_alpha; |
1130 | |
1131 | gsk_gl_program_add_uniform (self: program, name: "u_source" , key: UNIFORM_SHARED_SOURCE); |
1132 | gsk_gl_program_add_uniform (self: program, name: "u_clip_rect" , key: UNIFORM_SHARED_CLIP_RECT); |
1133 | gsk_gl_program_add_uniform (self: program, name: "u_viewport" , key: UNIFORM_SHARED_VIEWPORT); |
1134 | gsk_gl_program_add_uniform (self: program, name: "u_projection" , key: UNIFORM_SHARED_PROJECTION); |
1135 | gsk_gl_program_add_uniform (self: program, name: "u_modelview" , key: UNIFORM_SHARED_MODELVIEW); |
1136 | have_alpha = gsk_gl_program_add_uniform (self: program, name: "u_alpha" , key: UNIFORM_SHARED_ALPHA); |
1137 | |
1138 | gsk_gl_program_add_uniform (self: program, name: "u_size" , key: UNIFORM_CUSTOM_SIZE); |
1139 | gsk_gl_program_add_uniform (self: program, name: "u_texture1" , key: UNIFORM_CUSTOM_TEXTURE1); |
1140 | gsk_gl_program_add_uniform (self: program, name: "u_texture2" , key: UNIFORM_CUSTOM_TEXTURE2); |
1141 | gsk_gl_program_add_uniform (self: program, name: "u_texture3" , key: UNIFORM_CUSTOM_TEXTURE3); |
1142 | gsk_gl_program_add_uniform (self: program, name: "u_texture4" , key: UNIFORM_CUSTOM_TEXTURE4); |
1143 | |
1144 | /* Custom arguments (max is 8) */ |
1145 | for (guint i = 0; i < n_uniforms; i++) |
1146 | gsk_gl_program_add_uniform (self: program, name: uniforms[i].name, key: UNIFORM_CUSTOM_ARG0+i); |
1147 | |
1148 | gsk_gl_program_uniforms_added (self: program, TRUE); |
1149 | |
1150 | if (have_alpha) |
1151 | gsk_gl_program_set_uniform1f (self: program, key: UNIFORM_SHARED_ALPHA, stamp: 0, value0: 1.0f); |
1152 | |
1153 | g_hash_table_insert (hash_table: self->shader_cache, key: shader, value: program); |
1154 | g_object_weak_ref (G_OBJECT (shader), |
1155 | notify: gsk_gl_driver_shader_weak_cb, |
1156 | data: self); |
1157 | } |
1158 | |
1159 | g_object_unref (object: compiler); |
1160 | } |
1161 | |
1162 | return program; |
1163 | } |
1164 | |
1165 | #ifdef G_ENABLE_DEBUG |
1166 | static void |
1167 | write_atlas_to_png (GskGLDriver *driver, |
1168 | GskGLTextureAtlas *atlas, |
1169 | const char *filename) |
1170 | { |
1171 | GdkTexture *texture; |
1172 | |
1173 | texture = gdk_gl_texture_new (context: gsk_gl_driver_get_context (self: driver), |
1174 | id: atlas->texture_id, |
1175 | width: atlas->width, height: atlas->height, |
1176 | NULL, NULL); |
1177 | gdk_texture_save_to_png (texture, filename); |
1178 | g_object_unref (object: texture); |
1179 | } |
1180 | |
1181 | void |
1182 | gsk_gl_driver_save_atlases_to_png (GskGLDriver *self, |
1183 | const char *directory) |
1184 | { |
1185 | g_return_if_fail (GSK_IS_GL_DRIVER (self)); |
1186 | |
1187 | if (directory == NULL) |
1188 | directory = "." ; |
1189 | |
1190 | for (guint i = 0; i < self->atlases->len; i++) |
1191 | { |
1192 | GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i); |
1193 | char *filename = g_strdup_printf (format: "%s%sframe-%d-atlas-%d.png" , |
1194 | directory, |
1195 | G_DIR_SEPARATOR_S, |
1196 | (int)self->current_frame_id, |
1197 | atlas->texture_id); |
1198 | write_atlas_to_png (driver: self, atlas, filename); |
1199 | g_free (mem: filename); |
1200 | } |
1201 | } |
1202 | #endif |
1203 | |
1204 | GskGLCommandQueue * |
1205 | gsk_gl_driver_create_command_queue (GskGLDriver *self, |
1206 | GdkGLContext *context) |
1207 | { |
1208 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
1209 | g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); |
1210 | |
1211 | return gsk_gl_command_queue_new (context, uniforms: self->shared_command_queue->uniforms); |
1212 | } |
1213 | |
1214 | void |
1215 | gsk_gl_driver_add_texture_slices (GskGLDriver *self, |
1216 | GdkTexture *texture, |
1217 | GskGLTextureSlice **out_slices, |
1218 | guint *out_n_slices) |
1219 | { |
1220 | int max_texture_size; |
1221 | GskGLTextureSlice *slices; |
1222 | GskGLTexture *t; |
1223 | guint n_slices; |
1224 | guint cols; |
1225 | guint rows; |
1226 | int tex_width; |
1227 | int tex_height; |
1228 | int x = 0, y = 0; |
1229 | GdkMemoryTexture *memtex; |
1230 | |
1231 | g_assert (GSK_IS_GL_DRIVER (self)); |
1232 | g_assert (GDK_IS_TEXTURE (texture)); |
1233 | g_assert (out_slices != NULL); |
1234 | g_assert (out_n_slices != NULL); |
1235 | |
1236 | /* XXX: Too much? */ |
1237 | max_texture_size = self->command_queue->max_texture_size / 4; |
1238 | |
1239 | tex_width = texture->width; |
1240 | tex_height = texture->height; |
1241 | cols = (texture->width / max_texture_size) + 1; |
1242 | rows = (texture->height / max_texture_size) + 1; |
1243 | |
1244 | if ((t = gdk_texture_get_render_data (self: texture, key: self))) |
1245 | { |
1246 | *out_slices = t->slices; |
1247 | *out_n_slices = t->n_slices; |
1248 | return; |
1249 | } |
1250 | |
1251 | n_slices = cols * rows; |
1252 | slices = g_new0 (GskGLTextureSlice, n_slices); |
1253 | memtex = gdk_memory_texture_from_texture (texture, |
1254 | format: gdk_texture_get_format (self: texture)); |
1255 | |
1256 | for (guint col = 0; col < cols; col ++) |
1257 | { |
1258 | int slice_width = MIN (max_texture_size, texture->width - x); |
1259 | |
1260 | for (guint row = 0; row < rows; row ++) |
1261 | { |
1262 | int slice_height = MIN (max_texture_size, texture->height - y); |
1263 | int slice_index = (col * rows) + row; |
1264 | GdkTexture *subtex; |
1265 | guint texture_id; |
1266 | |
1267 | subtex = gdk_memory_texture_new_subtexture (texture: memtex, |
1268 | x, y, |
1269 | width: slice_width, height: slice_height); |
1270 | texture_id = gsk_gl_command_queue_upload_texture (self: self->command_queue, |
1271 | texture: subtex, |
1272 | GL_NEAREST, GL_NEAREST); |
1273 | g_object_unref (object: subtex); |
1274 | |
1275 | slices[slice_index].rect.x = x; |
1276 | slices[slice_index].rect.y = y; |
1277 | slices[slice_index].rect.width = slice_width; |
1278 | slices[slice_index].rect.height = slice_height; |
1279 | slices[slice_index].texture_id = texture_id; |
1280 | |
1281 | y += slice_height; |
1282 | } |
1283 | |
1284 | y = 0; |
1285 | x += slice_width; |
1286 | } |
1287 | |
1288 | g_object_unref (object: memtex); |
1289 | |
1290 | /* Allocate one Texture for the entire thing. */ |
1291 | t = gsk_gl_texture_new (texture_id: 0, |
1292 | width: tex_width, height: tex_height, |
1293 | GL_RGBA8, |
1294 | GL_NEAREST, GL_NEAREST, |
1295 | frame_id: self->current_frame_id); |
1296 | |
1297 | /* Use gsk_gl_texture_free() as destroy notify here since we are |
1298 | * not inserting this GskGLTexture into self->textures! |
1299 | */ |
1300 | gdk_texture_set_render_data (self: texture, key: self, data: t, |
1301 | notify: (GDestroyNotify)gsk_gl_texture_free); |
1302 | |
1303 | t->slices = *out_slices = slices; |
1304 | t->n_slices = *out_n_slices = n_slices; |
1305 | } |
1306 | |
1307 | GskGLTexture * |
1308 | gsk_gl_driver_mark_texture_permanent (GskGLDriver *self, |
1309 | guint texture_id) |
1310 | { |
1311 | GskGLTexture *t; |
1312 | |
1313 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
1314 | g_return_val_if_fail (texture_id > 0, NULL); |
1315 | |
1316 | if ((t = g_hash_table_lookup (hash_table: self->textures, GUINT_TO_POINTER (texture_id)))) |
1317 | t->permanent = TRUE; |
1318 | |
1319 | return t; |
1320 | } |
1321 | |
1322 | void |
1323 | gsk_gl_driver_release_texture_by_id (GskGLDriver *self, |
1324 | guint texture_id) |
1325 | { |
1326 | GskGLTexture *texture; |
1327 | |
1328 | g_return_if_fail (GSK_IS_GL_DRIVER (self)); |
1329 | g_return_if_fail (texture_id > 0); |
1330 | |
1331 | remove_texture_key_for_id (self, texture_id); |
1332 | |
1333 | if ((texture = g_hash_table_lookup (hash_table: self->textures, GUINT_TO_POINTER (texture_id)))) |
1334 | gsk_gl_driver_release_texture (self, texture); |
1335 | } |
1336 | |
1337 | typedef struct _GskGLTextureState |
1338 | { |
1339 | GdkGLContext *context; |
1340 | GLuint texture_id; |
1341 | } GskGLTextureState; |
1342 | |
1343 | static void |
1344 | create_texture_from_texture_destroy (gpointer data) |
1345 | { |
1346 | GskGLTextureState *state = data; |
1347 | |
1348 | g_assert (state != NULL); |
1349 | g_assert (GDK_IS_GL_CONTEXT (state->context)); |
1350 | |
1351 | gdk_gl_context_make_current (context: state->context); |
1352 | glDeleteTextures (1, &state->texture_id); |
1353 | g_clear_object (&state->context); |
1354 | g_slice_free (GskGLTextureState, state); |
1355 | } |
1356 | |
1357 | GdkTexture * |
1358 | gsk_gl_driver_create_gdk_texture (GskGLDriver *self, |
1359 | guint texture_id) |
1360 | { |
1361 | GskGLTextureState *state; |
1362 | GskGLTexture *texture; |
1363 | int width, height; |
1364 | |
1365 | g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); |
1366 | g_return_val_if_fail (self->command_queue != NULL, NULL); |
1367 | g_return_val_if_fail (GDK_IS_GL_CONTEXT (self->command_queue->context), NULL); |
1368 | g_return_val_if_fail (texture_id > 0, NULL); |
1369 | g_return_val_if_fail (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)), NULL); |
1370 | |
1371 | /* We must be tracking this texture_id already to use it */ |
1372 | if (!(texture = g_hash_table_lookup (hash_table: self->textures, GUINT_TO_POINTER (texture_id)))) |
1373 | g_return_val_if_reached (NULL); |
1374 | |
1375 | state = g_slice_new0 (GskGLTextureState); |
1376 | state->texture_id = texture_id; |
1377 | state->context = g_object_ref (self->command_queue->context); |
1378 | |
1379 | g_hash_table_steal (hash_table: self->textures, GUINT_TO_POINTER (texture_id)); |
1380 | |
1381 | width = texture->width; |
1382 | height = texture->height; |
1383 | |
1384 | texture->texture_id = 0; |
1385 | gsk_gl_texture_free (texture); |
1386 | |
1387 | return gdk_gl_texture_new (context: self->command_queue->context, |
1388 | id: texture_id, |
1389 | width, |
1390 | height, |
1391 | destroy: create_texture_from_texture_destroy, |
1392 | data: state); |
1393 | } |
1394 | |