1 | /* gskglcommandqueue.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 <string.h> |
27 | |
28 | #include <gdk/gdkglcontextprivate.h> |
29 | #include <gdk/gdkmemoryformatprivate.h> |
30 | #include <gdk/gdkmemorytextureprivate.h> |
31 | #include <gdk/gdkprofilerprivate.h> |
32 | #include <gsk/gskdebugprivate.h> |
33 | #include <gsk/gskroundedrectprivate.h> |
34 | |
35 | #include "gskglattachmentstateprivate.h" |
36 | #include "gskglbufferprivate.h" |
37 | #include "gskglcommandqueueprivate.h" |
38 | #include "gskgluniformstateprivate.h" |
39 | |
40 | #include "inlinearray.h" |
41 | |
42 | G_DEFINE_TYPE (GskGLCommandQueue, gsk_gl_command_queue, G_TYPE_OBJECT) |
43 | |
44 | G_GNUC_UNUSED static inline void |
45 | print_uniform (GskGLUniformFormat format, |
46 | guint array_count, |
47 | gconstpointer valueptr) |
48 | { |
49 | const union { |
50 | graphene_matrix_t matrix[0]; |
51 | GskRoundedRect rounded_rect[0]; |
52 | float fval[0]; |
53 | int ival[0]; |
54 | guint uval[0]; |
55 | } *data = valueptr; |
56 | |
57 | switch (format) |
58 | { |
59 | case GSK_GL_UNIFORM_FORMAT_1F: |
60 | g_printerr (format: "1f<%f>" , data->fval[0]); |
61 | break; |
62 | |
63 | case GSK_GL_UNIFORM_FORMAT_2F: |
64 | g_printerr (format: "2f<%f,%f>" , data->fval[0], data->fval[1]); |
65 | break; |
66 | |
67 | case GSK_GL_UNIFORM_FORMAT_3F: |
68 | g_printerr (format: "3f<%f,%f,%f>" , data->fval[0], data->fval[1], data->fval[2]); |
69 | break; |
70 | |
71 | case GSK_GL_UNIFORM_FORMAT_4F: |
72 | g_printerr (format: "4f<%f,%f,%f,%f>" , data->fval[0], data->fval[1], data->fval[2], data->fval[3]); |
73 | break; |
74 | |
75 | case GSK_GL_UNIFORM_FORMAT_1I: |
76 | case GSK_GL_UNIFORM_FORMAT_TEXTURE: |
77 | g_printerr (format: "1i<%d>" , data->ival[0]); |
78 | break; |
79 | |
80 | case GSK_GL_UNIFORM_FORMAT_1UI: |
81 | g_printerr (format: "1ui<%u>" , data->uval[0]); |
82 | break; |
83 | |
84 | case GSK_GL_UNIFORM_FORMAT_COLOR: { |
85 | char *str = gdk_rgba_to_string (rgba: valueptr); |
86 | g_printerr (format: "%s" , str); |
87 | g_free (mem: str); |
88 | break; |
89 | } |
90 | |
91 | case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: { |
92 | char *str = gsk_rounded_rect_to_string (self: valueptr); |
93 | g_printerr (format: "%s" , str); |
94 | g_free (mem: str); |
95 | break; |
96 | } |
97 | |
98 | case GSK_GL_UNIFORM_FORMAT_MATRIX: { |
99 | float mat[16]; |
100 | graphene_matrix_to_float (m: &data->matrix[0], v: mat); |
101 | g_printerr (format: "matrix<" ); |
102 | for (guint i = 0; i < G_N_ELEMENTS (mat)-1; i++) |
103 | g_printerr (format: "%f," , mat[i]); |
104 | g_printerr (format: "%f>" , mat[G_N_ELEMENTS (mat)-1]); |
105 | break; |
106 | } |
107 | |
108 | case GSK_GL_UNIFORM_FORMAT_1FV: |
109 | case GSK_GL_UNIFORM_FORMAT_2FV: |
110 | case GSK_GL_UNIFORM_FORMAT_3FV: |
111 | case GSK_GL_UNIFORM_FORMAT_4FV: |
112 | /* non-V variants are -4 from V variants */ |
113 | format -= 4; |
114 | g_printerr (format: "[" ); |
115 | for (guint i = 0; i < array_count; i++) |
116 | { |
117 | print_uniform (format, array_count: 0, valueptr); |
118 | if (i + 1 != array_count) |
119 | g_printerr (format: "," ); |
120 | valueptr = ((guint8*)valueptr + gsk_gl_uniform_format_size (format)); |
121 | } |
122 | g_printerr (format: "]" ); |
123 | break; |
124 | |
125 | case GSK_GL_UNIFORM_FORMAT_2I: |
126 | g_printerr (format: "2i<%d,%d>" , data->ival[0], data->ival[1]); |
127 | break; |
128 | |
129 | case GSK_GL_UNIFORM_FORMAT_3I: |
130 | g_printerr (format: "3i<%d,%d,%d>" , data->ival[0], data->ival[1], data->ival[2]); |
131 | break; |
132 | |
133 | case GSK_GL_UNIFORM_FORMAT_4I: |
134 | g_printerr (format: "3i<%d,%d,%d,%d>" , data->ival[0], data->ival[1], data->ival[2], data->ival[3]); |
135 | break; |
136 | |
137 | case GSK_GL_UNIFORM_FORMAT_LAST: |
138 | default: |
139 | g_assert_not_reached (); |
140 | } |
141 | } |
142 | |
143 | G_GNUC_UNUSED static inline void |
144 | gsk_gl_command_queue_print_batch (GskGLCommandQueue *self, |
145 | const GskGLCommandBatch *batch) |
146 | { |
147 | static const char *command_kinds[] = { "Clear" , "Draw" , }; |
148 | guint framebuffer_id; |
149 | |
150 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
151 | g_assert (batch != NULL); |
152 | |
153 | if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) |
154 | framebuffer_id = batch->clear.framebuffer; |
155 | else if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW) |
156 | framebuffer_id = batch->draw.framebuffer; |
157 | else |
158 | return; |
159 | |
160 | g_printerr (format: "Batch {\n" ); |
161 | g_printerr (format: " Kind: %s\n" , command_kinds[batch->any.kind]); |
162 | g_printerr (format: " Viewport: %dx%d\n" , batch->any.viewport.width, batch->any.viewport.height); |
163 | g_printerr (format: " Framebuffer: %d\n" , framebuffer_id); |
164 | |
165 | if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW) |
166 | { |
167 | g_printerr (format: " Program: %d\n" , batch->any.program); |
168 | g_printerr (format: " Vertices: %d\n" , batch->draw.vbo_count); |
169 | |
170 | for (guint i = 0; i < batch->draw.bind_count; i++) |
171 | { |
172 | const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i]; |
173 | g_printerr (format: " Bind[%d]: %u\n" , bind->texture, bind->id); |
174 | } |
175 | |
176 | for (guint i = 0; i < batch->draw.uniform_count; i++) |
177 | { |
178 | const GskGLCommandUniform *uniform = &self->batch_uniforms.items[batch->draw.uniform_offset + i]; |
179 | g_printerr (format: " Uniform[%02d]: " , uniform->location); |
180 | print_uniform (format: uniform->info.format, |
181 | array_count: uniform->info.array_count, |
182 | gsk_gl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset)); |
183 | g_printerr (format: "\n" ); |
184 | } |
185 | } |
186 | else if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) |
187 | { |
188 | g_printerr (format: " Bits: 0x%x\n" , batch->clear.bits); |
189 | } |
190 | |
191 | g_printerr (format: "}\n" ); |
192 | } |
193 | |
194 | G_GNUC_UNUSED static inline void |
195 | gsk_gl_command_queue_capture_png (GskGLCommandQueue *self, |
196 | const char *filename, |
197 | guint width, |
198 | guint height, |
199 | gboolean flip_y) |
200 | { |
201 | guint stride; |
202 | guint8 *data; |
203 | GBytes *bytes; |
204 | GdkTexture *texture; |
205 | |
206 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
207 | g_assert (filename != NULL); |
208 | |
209 | stride = cairo_format_stride_for_width (format: CAIRO_FORMAT_ARGB32, width); |
210 | data = g_malloc_n (n_blocks: height, n_block_bytes: stride); |
211 | |
212 | glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data); |
213 | |
214 | if (flip_y) |
215 | { |
216 | guint8 *flipped = g_malloc_n (n_blocks: height, n_block_bytes: stride); |
217 | |
218 | for (guint i = 0; i < height; i++) |
219 | memcpy (dest: flipped + (height * stride) - ((i + 1) * stride), |
220 | src: data + (stride * i), |
221 | n: stride); |
222 | |
223 | g_free (mem: data); |
224 | data = flipped; |
225 | } |
226 | |
227 | bytes = g_bytes_new_take (data, size: height * stride); |
228 | texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride); |
229 | g_bytes_unref (bytes); |
230 | |
231 | gdk_texture_save_to_png (texture, filename); |
232 | g_object_unref (object: texture); |
233 | } |
234 | |
235 | static inline gboolean |
236 | will_ignore_batch (GskGLCommandQueue *self) |
237 | { |
238 | if G_LIKELY (self->batches.len < G_MAXINT16) |
239 | return FALSE; |
240 | |
241 | if (!self->have_truncated) |
242 | { |
243 | self->have_truncated = TRUE; |
244 | g_critical ("GL command queue too large, truncating further batches." ); |
245 | } |
246 | |
247 | return TRUE; |
248 | } |
249 | |
250 | static inline guint |
251 | snapshot_attachments (const GskGLAttachmentState *state, |
252 | GskGLCommandBinds *array) |
253 | { |
254 | GskGLCommandBind *bind = gsk_gl_command_binds_append_n (ar: array, G_N_ELEMENTS (state->textures)); |
255 | guint count = 0; |
256 | |
257 | for (guint i = 0; i < G_N_ELEMENTS (state->textures); i++) |
258 | { |
259 | if (state->textures[i].id) |
260 | { |
261 | bind[count].id = state->textures[i].id; |
262 | bind[count].texture = state->textures[i].texture; |
263 | count++; |
264 | } |
265 | } |
266 | |
267 | if (count != G_N_ELEMENTS (state->textures)) |
268 | array->len -= G_N_ELEMENTS (state->textures) - count; |
269 | |
270 | return count; |
271 | } |
272 | |
273 | static inline guint |
274 | snapshot_uniforms (GskGLUniformState *state, |
275 | GskGLUniformProgram *program, |
276 | GskGLCommandUniforms *array) |
277 | { |
278 | GskGLCommandUniform *uniform = gsk_gl_command_uniforms_append_n (ar: array, n: program->n_mappings); |
279 | guint count = 0; |
280 | |
281 | for (guint i = 0; i < program->n_mappings; i++) |
282 | { |
283 | const GskGLUniformMapping *mapping = &program->mappings[i]; |
284 | |
285 | if (!mapping->info.initial && mapping->location > -1) |
286 | { |
287 | uniform[count].location = mapping->location; |
288 | uniform[count].info = mapping->info; |
289 | count++; |
290 | } |
291 | } |
292 | |
293 | if (count != program->n_mappings) |
294 | array->len -= program->n_mappings - count; |
295 | |
296 | return count; |
297 | } |
298 | |
299 | static inline gboolean |
300 | snapshots_equal (GskGLCommandQueue *self, |
301 | GskGLCommandBatch *first, |
302 | GskGLCommandBatch *second) |
303 | { |
304 | if (first->draw.bind_count != second->draw.bind_count || |
305 | first->draw.uniform_count != second->draw.uniform_count) |
306 | return FALSE; |
307 | |
308 | for (guint i = 0; i < first->draw.bind_count; i++) |
309 | { |
310 | const GskGLCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i]; |
311 | const GskGLCommandBind *sb = &self->batch_binds.items[second->draw.bind_offset+i]; |
312 | |
313 | if (fb->id != sb->id || fb->texture != sb->texture) |
314 | return FALSE; |
315 | } |
316 | |
317 | for (guint i = 0; i < first->draw.uniform_count; i++) |
318 | { |
319 | const GskGLCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i]; |
320 | const GskGLCommandUniform *su = &self->batch_uniforms.items[second->draw.uniform_offset+i]; |
321 | gconstpointer fdata; |
322 | gconstpointer sdata; |
323 | gsize len; |
324 | |
325 | /* Short circuit if we'd end up with the same memory */ |
326 | if (fu->info.offset == su->info.offset) |
327 | continue; |
328 | |
329 | if (fu->info.format != su->info.format || |
330 | fu->info.array_count != su->info.array_count) |
331 | return FALSE; |
332 | |
333 | fdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset); |
334 | sdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, su->info.offset); |
335 | |
336 | switch (fu->info.format) |
337 | { |
338 | case GSK_GL_UNIFORM_FORMAT_1F: |
339 | case GSK_GL_UNIFORM_FORMAT_1FV: |
340 | case GSK_GL_UNIFORM_FORMAT_1I: |
341 | case GSK_GL_UNIFORM_FORMAT_TEXTURE: |
342 | case GSK_GL_UNIFORM_FORMAT_1UI: |
343 | len = 4; |
344 | break; |
345 | |
346 | case GSK_GL_UNIFORM_FORMAT_2F: |
347 | case GSK_GL_UNIFORM_FORMAT_2FV: |
348 | case GSK_GL_UNIFORM_FORMAT_2I: |
349 | len = 8; |
350 | break; |
351 | |
352 | case GSK_GL_UNIFORM_FORMAT_3F: |
353 | case GSK_GL_UNIFORM_FORMAT_3FV: |
354 | case GSK_GL_UNIFORM_FORMAT_3I: |
355 | len = 12; |
356 | break; |
357 | |
358 | case GSK_GL_UNIFORM_FORMAT_4F: |
359 | case GSK_GL_UNIFORM_FORMAT_4FV: |
360 | case GSK_GL_UNIFORM_FORMAT_4I: |
361 | len = 16; |
362 | break; |
363 | |
364 | |
365 | case GSK_GL_UNIFORM_FORMAT_MATRIX: |
366 | len = sizeof (float) * 16; |
367 | break; |
368 | |
369 | case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: |
370 | len = sizeof (float) * 12; |
371 | break; |
372 | |
373 | case GSK_GL_UNIFORM_FORMAT_COLOR: |
374 | len = sizeof (float) * 4; |
375 | break; |
376 | |
377 | default: |
378 | g_assert_not_reached (); |
379 | } |
380 | |
381 | len *= fu->info.array_count; |
382 | |
383 | if (memcmp (s1: fdata, s2: sdata, n: len) != 0) |
384 | return FALSE; |
385 | } |
386 | |
387 | return TRUE; |
388 | } |
389 | |
390 | static void |
391 | gsk_gl_command_queue_dispose (GObject *object) |
392 | { |
393 | GskGLCommandQueue *self = (GskGLCommandQueue *)object; |
394 | |
395 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
396 | |
397 | g_clear_object (&self->profiler); |
398 | g_clear_object (&self->gl_profiler); |
399 | g_clear_object (&self->context); |
400 | g_clear_pointer (&self->attachments, gsk_gl_attachment_state_unref); |
401 | g_clear_pointer (&self->uniforms, gsk_gl_uniform_state_unref); |
402 | |
403 | gsk_gl_command_batches_clear (ar: &self->batches); |
404 | gsk_gl_command_binds_clear (ar: &self->batch_binds); |
405 | gsk_gl_command_uniforms_clear (ar: &self->batch_uniforms); |
406 | |
407 | gsk_gl_buffer_destroy (buffer: &self->vertices); |
408 | |
409 | G_OBJECT_CLASS (gsk_gl_command_queue_parent_class)->dispose (object); |
410 | } |
411 | |
412 | static void |
413 | gsk_gl_command_queue_class_init (GskGLCommandQueueClass *klass) |
414 | { |
415 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
416 | |
417 | object_class->dispose = gsk_gl_command_queue_dispose; |
418 | } |
419 | |
420 | static void |
421 | gsk_gl_command_queue_init (GskGLCommandQueue *self) |
422 | { |
423 | self->max_texture_size = -1; |
424 | |
425 | gsk_gl_command_batches_init (ar: &self->batches, initial_size: 128); |
426 | gsk_gl_command_binds_init (ar: &self->batch_binds, initial_size: 1024); |
427 | gsk_gl_command_uniforms_init (ar: &self->batch_uniforms, initial_size: 2048); |
428 | |
429 | gsk_gl_buffer_init (self: &self->vertices, GL_ARRAY_BUFFER, element_size: sizeof (GskGLDrawVertex)); |
430 | } |
431 | |
432 | GskGLCommandQueue * |
433 | gsk_gl_command_queue_new (GdkGLContext *context, |
434 | GskGLUniformState *uniforms) |
435 | { |
436 | GskGLCommandQueue *self; |
437 | |
438 | g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); |
439 | |
440 | self = g_object_new (GSK_TYPE_GL_COMMAND_QUEUE, NULL); |
441 | self->context = g_object_ref (context); |
442 | self->attachments = gsk_gl_attachment_state_new (); |
443 | |
444 | /* Use shared uniform state if we're provided one */ |
445 | if (uniforms != NULL) |
446 | self->uniforms = gsk_gl_uniform_state_ref (state: uniforms); |
447 | else |
448 | self->uniforms = gsk_gl_uniform_state_new (); |
449 | |
450 | /* Determine max texture size immediately and restore context */ |
451 | gdk_gl_context_make_current (context); |
452 | glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size); |
453 | |
454 | return g_steal_pointer (&self); |
455 | } |
456 | |
457 | static inline GskGLCommandBatch * |
458 | begin_next_batch (GskGLCommandQueue *self) |
459 | { |
460 | GskGLCommandBatch *batch; |
461 | |
462 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
463 | |
464 | /* GskGLCommandBatch contains an embedded linked list using integers into the |
465 | * self->batches array. We can't use pointer because the batches could be |
466 | * realloc()'d at runtime. |
467 | * |
468 | * Before we execute the command queue, we sort the batches by framebuffer but |
469 | * leave the batches in place as we can just tweak the links via prev/next. |
470 | * |
471 | * Generally we only traverse forwards, so we could ignore the previous field. |
472 | * But to optimize the reordering of batches by framebuffer we walk backwards |
473 | * so we sort by most-recently-seen framebuffer to ensure draws happen in the |
474 | * proper order. |
475 | */ |
476 | |
477 | batch = gsk_gl_command_batches_append (ar: &self->batches); |
478 | batch->any.next_batch_index = -1; |
479 | batch->any.prev_batch_index = self->tail_batch_index; |
480 | |
481 | return batch; |
482 | } |
483 | |
484 | static void |
485 | enqueue_batch (GskGLCommandQueue *self) |
486 | { |
487 | guint index; |
488 | |
489 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
490 | g_assert (self->batches.len > 0); |
491 | |
492 | /* Batches are linked lists but using indexes into the batches array instead |
493 | * of pointers. This is for two main reasons. First, 16-bit indexes allow us |
494 | * to store the information in 4 bytes, where as two pointers would take 16 |
495 | * bytes. Furthermore, we have an array here so pointers would get |
496 | * invalidated if we realloc()'d (and that can happen from time to time). |
497 | */ |
498 | |
499 | index = self->batches.len - 1; |
500 | |
501 | if (self->head_batch_index == -1) |
502 | self->head_batch_index = index; |
503 | |
504 | if (self->tail_batch_index != -1) |
505 | { |
506 | GskGLCommandBatch *prev = &self->batches.items[self->tail_batch_index]; |
507 | |
508 | prev->any.next_batch_index = index; |
509 | } |
510 | |
511 | self->tail_batch_index = index; |
512 | } |
513 | |
514 | static void |
515 | discard_batch (GskGLCommandQueue *self) |
516 | { |
517 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
518 | g_assert (self->batches.len > 0); |
519 | |
520 | self->batches.len--; |
521 | } |
522 | |
523 | void |
524 | gsk_gl_command_queue_begin_draw (GskGLCommandQueue *self, |
525 | GskGLUniformProgram *program, |
526 | guint width, |
527 | guint height) |
528 | { |
529 | GskGLCommandBatch *batch; |
530 | |
531 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
532 | g_assert (self->in_draw == FALSE); |
533 | g_assert (width <= G_MAXUINT16); |
534 | g_assert (height <= G_MAXUINT16); |
535 | |
536 | /* Our internal links use 16-bits, so that is our max number |
537 | * of batches we can have in one frame. |
538 | */ |
539 | if (will_ignore_batch (self)) |
540 | return; |
541 | |
542 | self->program_info = program; |
543 | |
544 | batch = begin_next_batch (self); |
545 | batch->any.kind = GSK_GL_COMMAND_KIND_DRAW; |
546 | batch->any.program = program->program_id; |
547 | batch->any.next_batch_index = -1; |
548 | batch->any.viewport.width = width; |
549 | batch->any.viewport.height = height; |
550 | batch->draw.framebuffer = 0; |
551 | batch->draw.uniform_count = 0; |
552 | batch->draw.uniform_offset = self->batch_uniforms.len; |
553 | batch->draw.bind_count = 0; |
554 | batch->draw.bind_offset = self->batch_binds.len; |
555 | batch->draw.vbo_count = 0; |
556 | batch->draw.vbo_offset = gsk_gl_buffer_get_offset (buffer: &self->vertices); |
557 | |
558 | self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer); |
559 | |
560 | self->in_draw = TRUE; |
561 | } |
562 | |
563 | void |
564 | gsk_gl_command_queue_end_draw (GskGLCommandQueue *self) |
565 | { |
566 | GskGLCommandBatch *last_batch; |
567 | GskGLCommandBatch *batch; |
568 | |
569 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
570 | g_assert (self->batches.len > 0); |
571 | |
572 | if (will_ignore_batch (self)) |
573 | return; |
574 | |
575 | batch = gsk_gl_command_batches_tail (ar: &self->batches); |
576 | |
577 | g_assert (self->in_draw == TRUE); |
578 | g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW); |
579 | |
580 | if G_UNLIKELY (batch->draw.vbo_count == 0) |
581 | { |
582 | discard_batch (self); |
583 | self->in_draw = FALSE; |
584 | return; |
585 | } |
586 | |
587 | /* Track the destination framebuffer in case it changed */ |
588 | batch->draw.framebuffer = self->attachments->fbo.id; |
589 | self->attachments->fbo.changed = FALSE; |
590 | self->fbo_max = MAX (self->fbo_max, self->attachments->fbo.id); |
591 | |
592 | /* Save our full uniform state for this draw so we can possibly |
593 | * reorder the draw later. |
594 | */ |
595 | batch->draw.uniform_offset = self->batch_uniforms.len; |
596 | batch->draw.uniform_count = snapshot_uniforms (state: self->uniforms, program: self->program_info, array: &self->batch_uniforms); |
597 | |
598 | /* Track the bind attachments that changed */ |
599 | if (self->program_info->has_attachments) |
600 | { |
601 | batch->draw.bind_offset = self->batch_binds.len; |
602 | batch->draw.bind_count = snapshot_attachments (state: self->attachments, array: &self->batch_binds); |
603 | } |
604 | else |
605 | { |
606 | batch->draw.bind_offset = 0; |
607 | batch->draw.bind_count = 0; |
608 | } |
609 | |
610 | if (self->batches.len > 1) |
611 | last_batch = &self->batches.items[self->batches.len - 2]; |
612 | else |
613 | last_batch = NULL; |
614 | |
615 | /* Do simple chaining of draw to last batch. */ |
616 | if (last_batch != NULL && |
617 | last_batch->any.kind == GSK_GL_COMMAND_KIND_DRAW && |
618 | last_batch->any.program == batch->any.program && |
619 | last_batch->any.viewport.width == batch->any.viewport.width && |
620 | last_batch->any.viewport.height == batch->any.viewport.height && |
621 | last_batch->draw.framebuffer == batch->draw.framebuffer && |
622 | last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset && |
623 | last_batch->draw.vbo_count + batch->draw.vbo_count <= 0xffff && |
624 | snapshots_equal (self, first: last_batch, second: batch)) |
625 | { |
626 | last_batch->draw.vbo_count += batch->draw.vbo_count; |
627 | discard_batch (self); |
628 | } |
629 | else |
630 | { |
631 | enqueue_batch (self); |
632 | } |
633 | |
634 | self->in_draw = FALSE; |
635 | self->program_info = NULL; |
636 | } |
637 | |
638 | /** |
639 | * gsk_gl_command_queue_split_draw: |
640 | * @self a `GskGLCommandQueue` |
641 | * |
642 | * This function is like calling gsk_gl_command_queue_end_draw() followed by |
643 | * a gsk_gl_command_queue_begin_draw() with the same parameters as a |
644 | * previous begin draw (if shared uniforms where not changed further). |
645 | * |
646 | * This is useful to avoid comparisons inside of loops where we know shared |
647 | * uniforms are not changing. |
648 | * |
649 | * This generally should just be called from gsk_gl_program_split_draw() |
650 | * as that is where the begin/end flow happens from the render job. |
651 | */ |
652 | void |
653 | gsk_gl_command_queue_split_draw (GskGLCommandQueue *self) |
654 | { |
655 | GskGLCommandBatch *batch; |
656 | GskGLUniformProgram *program; |
657 | guint width; |
658 | guint height; |
659 | |
660 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
661 | g_assert (self->batches.len > 0); |
662 | g_assert (self->in_draw == TRUE); |
663 | |
664 | program = self->program_info; |
665 | |
666 | batch = gsk_gl_command_batches_tail (ar: &self->batches); |
667 | |
668 | g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW); |
669 | |
670 | width = batch->any.viewport.width; |
671 | height = batch->any.viewport.height; |
672 | |
673 | gsk_gl_command_queue_end_draw (self); |
674 | gsk_gl_command_queue_begin_draw (self, program, width, height); |
675 | } |
676 | |
677 | void |
678 | gsk_gl_command_queue_clear (GskGLCommandQueue *self, |
679 | guint clear_bits, |
680 | const graphene_rect_t *viewport) |
681 | { |
682 | GskGLCommandBatch *batch; |
683 | |
684 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
685 | g_assert (self->in_draw == FALSE); |
686 | |
687 | if (will_ignore_batch (self)) |
688 | return; |
689 | |
690 | if (clear_bits == 0) |
691 | clear_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; |
692 | |
693 | batch = begin_next_batch (self); |
694 | batch->any.kind = GSK_GL_COMMAND_KIND_CLEAR; |
695 | batch->any.viewport.width = viewport->size.width; |
696 | batch->any.viewport.height = viewport->size.height; |
697 | batch->clear.bits = clear_bits; |
698 | batch->clear.framebuffer = self->attachments->fbo.id; |
699 | batch->any.next_batch_index = -1; |
700 | batch->any.program = 0; |
701 | |
702 | self->fbo_max = MAX (self->fbo_max, batch->clear.framebuffer); |
703 | |
704 | enqueue_batch (self); |
705 | |
706 | self->attachments->fbo.changed = FALSE; |
707 | } |
708 | |
709 | GdkGLContext * |
710 | gsk_gl_command_queue_get_context (GskGLCommandQueue *self) |
711 | { |
712 | g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL); |
713 | |
714 | return self->context; |
715 | } |
716 | |
717 | void |
718 | gsk_gl_command_queue_make_current (GskGLCommandQueue *self) |
719 | { |
720 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
721 | g_assert (GDK_IS_GL_CONTEXT (self->context)); |
722 | |
723 | gdk_gl_context_make_current (context: self->context); |
724 | } |
725 | |
726 | void |
727 | gsk_gl_command_queue_delete_program (GskGLCommandQueue *self, |
728 | guint program) |
729 | { |
730 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
731 | |
732 | glDeleteProgram (program); |
733 | } |
734 | |
735 | static inline void |
736 | apply_viewport (guint *current_width, |
737 | guint *current_height, |
738 | guint width, |
739 | guint height) |
740 | { |
741 | if G_UNLIKELY (*current_width != width || *current_height != height) |
742 | { |
743 | *current_width = width; |
744 | *current_height = height; |
745 | glViewport (0, 0, width, height); |
746 | } |
747 | } |
748 | |
749 | static inline void |
750 | apply_scissor (gboolean *state, |
751 | guint framebuffer, |
752 | const graphene_rect_t *scissor, |
753 | gboolean has_scissor, |
754 | guint default_framebuffer) |
755 | { |
756 | g_assert (framebuffer != (guint)-1); |
757 | |
758 | if (framebuffer != default_framebuffer || !has_scissor) |
759 | { |
760 | if (*state != FALSE) |
761 | { |
762 | glDisable (GL_SCISSOR_TEST); |
763 | *state = FALSE; |
764 | } |
765 | } |
766 | else |
767 | { |
768 | if (*state != TRUE) |
769 | { |
770 | glEnable (GL_SCISSOR_TEST); |
771 | glScissor (scissor->origin.x, |
772 | scissor->origin.y, |
773 | scissor->size.width, |
774 | scissor->size.height); |
775 | *state = TRUE; |
776 | } |
777 | } |
778 | } |
779 | |
780 | static inline gboolean |
781 | apply_framebuffer (int *framebuffer, |
782 | guint new_framebuffer) |
783 | { |
784 | if G_UNLIKELY (new_framebuffer != *framebuffer) |
785 | { |
786 | *framebuffer = new_framebuffer; |
787 | glBindFramebuffer (GL_FRAMEBUFFER, new_framebuffer); |
788 | return TRUE; |
789 | } |
790 | |
791 | return FALSE; |
792 | } |
793 | |
794 | static inline void |
795 | gsk_gl_command_queue_unlink (GskGLCommandQueue *self, |
796 | GskGLCommandBatch *batch) |
797 | { |
798 | if (batch->any.prev_batch_index == -1) |
799 | self->head_batch_index = batch->any.next_batch_index; |
800 | else |
801 | self->batches.items[batch->any.prev_batch_index].any.next_batch_index = batch->any.next_batch_index; |
802 | |
803 | if (batch->any.next_batch_index == -1) |
804 | self->tail_batch_index = batch->any.prev_batch_index; |
805 | else |
806 | self->batches.items[batch->any.next_batch_index].any.prev_batch_index = batch->any.prev_batch_index; |
807 | |
808 | batch->any.prev_batch_index = -1; |
809 | batch->any.next_batch_index = -1; |
810 | } |
811 | |
812 | static inline void |
813 | gsk_gl_command_queue_insert_before (GskGLCommandQueue *self, |
814 | GskGLCommandBatch *batch, |
815 | GskGLCommandBatch *sibling) |
816 | { |
817 | int sibling_index; |
818 | int index; |
819 | |
820 | g_assert (batch >= self->batches.items); |
821 | g_assert (batch < &self->batches.items[self->batches.len]); |
822 | g_assert (sibling >= self->batches.items); |
823 | g_assert (sibling < &self->batches.items[self->batches.len]); |
824 | |
825 | index = gsk_gl_command_batches_index_of (ar: &self->batches, element: batch); |
826 | sibling_index = gsk_gl_command_batches_index_of (ar: &self->batches, element: sibling); |
827 | |
828 | batch->any.next_batch_index = sibling_index; |
829 | batch->any.prev_batch_index = sibling->any.prev_batch_index; |
830 | |
831 | if (batch->any.prev_batch_index > -1) |
832 | self->batches.items[batch->any.prev_batch_index].any.next_batch_index = index; |
833 | |
834 | sibling->any.prev_batch_index = index; |
835 | |
836 | if (batch->any.prev_batch_index == -1) |
837 | self->head_batch_index = index; |
838 | } |
839 | |
840 | static void |
841 | gsk_gl_command_queue_sort_batches (GskGLCommandQueue *self) |
842 | { |
843 | int *seen; |
844 | int *seen_free = NULL; |
845 | int index; |
846 | |
847 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
848 | g_assert (self->tail_batch_index >= 0); |
849 | g_assert (self->fbo_max >= 0); |
850 | |
851 | /* Create our seen list with most recent index set to -1, |
852 | * meaning we haven't yet seen that framebuffer. |
853 | */ |
854 | if (self->fbo_max < 1024) |
855 | seen = g_alloca (sizeof (int) * (self->fbo_max + 1)); |
856 | else |
857 | seen = seen_free = g_new0 (int, (self->fbo_max + 1)); |
858 | for (int i = 0; i <= self->fbo_max; i++) |
859 | seen[i] = -1; |
860 | |
861 | /* Walk in reverse, and if we've seen that framebuffer before, we want to |
862 | * delay this operation until right before the last batch we saw for that |
863 | * framebuffer. |
864 | * |
865 | * We can do this because we don't use a framebuffer's texture until it has |
866 | * been completely drawn. |
867 | */ |
868 | index = self->tail_batch_index; |
869 | |
870 | while (index >= 0) |
871 | { |
872 | GskGLCommandBatch *batch = &self->batches.items[index]; |
873 | int cur_index = index; |
874 | int fbo = -1; |
875 | |
876 | g_assert (index > -1); |
877 | g_assert (index < self->batches.len); |
878 | |
879 | switch (batch->any.kind) |
880 | { |
881 | case GSK_GL_COMMAND_KIND_DRAW: |
882 | fbo = batch->draw.framebuffer; |
883 | break; |
884 | |
885 | case GSK_GL_COMMAND_KIND_CLEAR: |
886 | fbo = batch->clear.framebuffer; |
887 | break; |
888 | |
889 | default: |
890 | g_assert_not_reached (); |
891 | } |
892 | |
893 | index = batch->any.prev_batch_index; |
894 | |
895 | g_assert (index >= -1); |
896 | g_assert (index < (int)self->batches.len); |
897 | g_assert (fbo >= -1); |
898 | |
899 | if (fbo == -1) |
900 | continue; |
901 | |
902 | g_assert (fbo <= self->fbo_max); |
903 | g_assert (seen[fbo] >= -1); |
904 | g_assert (seen[fbo] < (int)self->batches.len); |
905 | |
906 | if (seen[fbo] != -1 && seen[fbo] != batch->any.next_batch_index) |
907 | { |
908 | int mru_index = seen[fbo]; |
909 | GskGLCommandBatch *mru = &self->batches.items[mru_index]; |
910 | |
911 | g_assert (mru_index > -1); |
912 | |
913 | gsk_gl_command_queue_unlink (self, batch); |
914 | |
915 | g_assert (batch->any.prev_batch_index == -1); |
916 | g_assert (batch->any.next_batch_index == -1); |
917 | |
918 | gsk_gl_command_queue_insert_before (self, batch, sibling: mru); |
919 | |
920 | g_assert (batch->any.prev_batch_index > -1 || |
921 | self->head_batch_index == cur_index); |
922 | g_assert (batch->any.next_batch_index == seen[fbo]); |
923 | } |
924 | |
925 | g_assert (cur_index > -1); |
926 | g_assert (seen[fbo] >= -1); |
927 | |
928 | seen[fbo] = cur_index; |
929 | } |
930 | |
931 | g_free (mem: seen_free); |
932 | } |
933 | |
934 | /** |
935 | * gsk_gl_command_queue_execute: |
936 | * @self: a `GskGLCommandQueue` |
937 | * @surface_height: the height of the backing surface |
938 | * @scale_factor: the scale factor of the backing surface |
939 | * @scissor: (nullable): the scissor clip if any |
940 | * @default_framebuffer: the default framebuffer id if not zero |
941 | * |
942 | * Executes all of the batches in the command queue. |
943 | * |
944 | * Typically, the scissor rect is only applied when rendering to the default |
945 | * framebuffer (zero in most cases). However, if @default_framebuffer is not |
946 | * zero, it will be checked to see if the rendering target matches so that |
947 | * the scissor rect is applied. This should be used in cases where rendering |
948 | * to the backbuffer for display is not the default GL framebuffer of zero. |
949 | * Currently, this happens when rendering on macOS using IOSurface. |
950 | */ |
951 | void |
952 | gsk_gl_command_queue_execute (GskGLCommandQueue *self, |
953 | guint surface_height, |
954 | guint scale_factor, |
955 | const cairo_region_t *scissor, |
956 | guint default_framebuffer) |
957 | { |
958 | G_GNUC_UNUSED guint count = 0; |
959 | graphene_rect_t scissor_test; |
960 | gboolean has_scissor = scissor != NULL; |
961 | gboolean scissor_state = -1; |
962 | guint program = 0; |
963 | guint width = 0; |
964 | guint height = 0; |
965 | G_GNUC_UNUSED guint n_binds = 0; |
966 | guint n_fbos = 0; |
967 | G_GNUC_UNUSED guint n_uniforms = 0; |
968 | guint n_programs = 0; |
969 | guint vao_id; |
970 | guint vbo_id; |
971 | int textures[4]; |
972 | int framebuffer = -1; |
973 | int next_batch_index; |
974 | int active = -1; |
975 | |
976 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
977 | g_assert (self->in_draw == FALSE); |
978 | |
979 | if (self->batches.len == 0) |
980 | return; |
981 | |
982 | for (guint i = 0; i < G_N_ELEMENTS (textures); i++) |
983 | textures[i] = -1; |
984 | |
985 | gsk_gl_command_queue_sort_batches (self); |
986 | |
987 | gsk_gl_command_queue_make_current (self); |
988 | |
989 | #ifdef G_ENABLE_DEBUG |
990 | gsk_gl_profiler_begin_gpu_region (profiler: self->gl_profiler); |
991 | gsk_profiler_timer_begin (profiler: self->profiler, timer_id: self->metrics.cpu_time); |
992 | #endif |
993 | |
994 | glEnable (GL_DEPTH_TEST); |
995 | glDepthFunc (GL_LEQUAL); |
996 | |
997 | /* Pre-multiplied alpha */ |
998 | glEnable (GL_BLEND); |
999 | glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
1000 | glBlendEquation (GL_FUNC_ADD); |
1001 | |
1002 | glGenVertexArrays (1, &vao_id); |
1003 | glBindVertexArray (vao_id); |
1004 | |
1005 | vbo_id = gsk_gl_buffer_submit (buffer: &self->vertices); |
1006 | |
1007 | /* 0 = position location */ |
1008 | glEnableVertexAttribArray (0); |
1009 | glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, |
1010 | sizeof (GskGLDrawVertex), |
1011 | (void *) G_STRUCT_OFFSET (GskGLDrawVertex, position)); |
1012 | |
1013 | /* 1 = texture coord location */ |
1014 | glEnableVertexAttribArray (1); |
1015 | glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, |
1016 | sizeof (GskGLDrawVertex), |
1017 | (void *) G_STRUCT_OFFSET (GskGLDrawVertex, uv)); |
1018 | |
1019 | /* 2 = color location */ |
1020 | glEnableVertexAttribArray (2); |
1021 | glVertexAttribPointer (2, 4, GL_HALF_FLOAT, GL_FALSE, |
1022 | sizeof (GskGLDrawVertex), |
1023 | (void *) G_STRUCT_OFFSET (GskGLDrawVertex, color)); |
1024 | |
1025 | /* 3 = color2 location */ |
1026 | glEnableVertexAttribArray (3); |
1027 | glVertexAttribPointer (3, 4, GL_HALF_FLOAT, GL_FALSE, |
1028 | sizeof (GskGLDrawVertex), |
1029 | (void *) G_STRUCT_OFFSET (GskGLDrawVertex, color2)); |
1030 | |
1031 | /* Setup initial scissor clip */ |
1032 | if (scissor != NULL) |
1033 | { |
1034 | cairo_rectangle_int_t r; |
1035 | |
1036 | g_assert (cairo_region_num_rectangles (scissor) == 1); |
1037 | cairo_region_get_rectangle (region: scissor, nth: 0, rectangle: &r); |
1038 | |
1039 | scissor_test.origin.x = r.x * scale_factor; |
1040 | scissor_test.origin.y = surface_height - (r.height * scale_factor) - (r.y * scale_factor); |
1041 | scissor_test.size.width = r.width * scale_factor; |
1042 | scissor_test.size.height = r.height * scale_factor; |
1043 | } |
1044 | |
1045 | next_batch_index = self->head_batch_index; |
1046 | |
1047 | while (next_batch_index >= 0) |
1048 | { |
1049 | const GskGLCommandBatch *batch = &self->batches.items[next_batch_index]; |
1050 | |
1051 | g_assert (next_batch_index >= 0); |
1052 | g_assert (next_batch_index < self->batches.len); |
1053 | g_assert (batch->any.next_batch_index != next_batch_index); |
1054 | |
1055 | count++; |
1056 | |
1057 | switch (batch->any.kind) |
1058 | { |
1059 | case GSK_GL_COMMAND_KIND_CLEAR: |
1060 | if (apply_framebuffer (framebuffer: &framebuffer, new_framebuffer: batch->clear.framebuffer)) |
1061 | { |
1062 | apply_scissor (state: &scissor_state, framebuffer, scissor: &scissor_test, has_scissor, default_framebuffer); |
1063 | n_fbos++; |
1064 | } |
1065 | |
1066 | apply_viewport (current_width: &width, |
1067 | current_height: &height, |
1068 | width: batch->any.viewport.width, |
1069 | height: batch->any.viewport.height); |
1070 | |
1071 | glClearColor (0, 0, 0, 0); |
1072 | glClear (batch->clear.bits); |
1073 | break; |
1074 | |
1075 | case GSK_GL_COMMAND_KIND_DRAW: |
1076 | if (batch->any.program != program) |
1077 | { |
1078 | program = batch->any.program; |
1079 | glUseProgram (program); |
1080 | |
1081 | n_programs++; |
1082 | } |
1083 | |
1084 | if (apply_framebuffer (framebuffer: &framebuffer, new_framebuffer: batch->draw.framebuffer)) |
1085 | { |
1086 | apply_scissor (state: &scissor_state, framebuffer, scissor: &scissor_test, has_scissor, default_framebuffer); |
1087 | n_fbos++; |
1088 | } |
1089 | |
1090 | apply_viewport (current_width: &width, |
1091 | current_height: &height, |
1092 | width: batch->any.viewport.width, |
1093 | height: batch->any.viewport.height); |
1094 | |
1095 | if G_UNLIKELY (batch->draw.bind_count > 0) |
1096 | { |
1097 | const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset]; |
1098 | |
1099 | for (guint i = 0; i < batch->draw.bind_count; i++) |
1100 | { |
1101 | if (textures[bind->texture] != bind->id) |
1102 | { |
1103 | if (active != bind->texture) |
1104 | { |
1105 | active = bind->texture; |
1106 | glActiveTexture (GL_TEXTURE0 + bind->texture); |
1107 | } |
1108 | |
1109 | glBindTexture (GL_TEXTURE_2D, bind->id); |
1110 | textures[bind->texture] = bind->id; |
1111 | } |
1112 | |
1113 | bind++; |
1114 | } |
1115 | |
1116 | n_binds += batch->draw.bind_count; |
1117 | } |
1118 | |
1119 | if (batch->draw.uniform_count > 0) |
1120 | { |
1121 | const GskGLCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset]; |
1122 | |
1123 | for (guint i = 0; i < batch->draw.uniform_count; i++, u++) |
1124 | gsk_gl_uniform_state_apply (state: self->uniforms, program, location: u->location, info: u->info); |
1125 | |
1126 | n_uniforms += batch->draw.uniform_count; |
1127 | } |
1128 | |
1129 | glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count); |
1130 | |
1131 | break; |
1132 | |
1133 | default: |
1134 | g_assert_not_reached (); |
1135 | } |
1136 | |
1137 | #if 0 |
1138 | if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW || |
1139 | batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) |
1140 | { |
1141 | char filename[128]; |
1142 | g_snprintf (filename, sizeof filename, |
1143 | "capture%03u_batch%03d_kind%u_program%u_u%u_b%u_fb%u_ctx%p.png" , |
1144 | count, next_batch_index, |
1145 | batch->any.kind, batch->any.program, |
1146 | batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0, |
1147 | batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0, |
1148 | framebuffer, |
1149 | gdk_gl_context_get_current ()); |
1150 | gsk_gl_command_queue_capture_png (self, filename, width, height, TRUE); |
1151 | gsk_gl_command_queue_print_batch (self, batch); |
1152 | } |
1153 | #endif |
1154 | |
1155 | next_batch_index = batch->any.next_batch_index; |
1156 | } |
1157 | |
1158 | glDeleteBuffers (1, &vbo_id); |
1159 | glDeleteVertexArrays (1, &vao_id); |
1160 | |
1161 | gdk_profiler_set_int_counter (self->metrics.n_binds, n_binds); |
1162 | gdk_profiler_set_int_counter (self->metrics.n_uniforms, n_uniforms); |
1163 | gdk_profiler_set_int_counter (self->metrics.n_fbos, n_fbos); |
1164 | gdk_profiler_set_int_counter (self->metrics.n_programs, n_programs); |
1165 | gdk_profiler_set_int_counter (self->metrics.n_uploads, self->n_uploads); |
1166 | gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len); |
1167 | |
1168 | #ifdef G_ENABLE_DEBUG |
1169 | { |
1170 | gint64 start_time G_GNUC_UNUSED = gsk_profiler_timer_get_start (profiler: self->profiler, timer_id: self->metrics.cpu_time); |
1171 | gint64 cpu_time = gsk_profiler_timer_end (profiler: self->profiler, timer_id: self->metrics.cpu_time); |
1172 | gint64 gpu_time = gsk_gl_profiler_end_gpu_region (profiler: self->gl_profiler); |
1173 | |
1174 | gsk_profiler_timer_set (profiler: self->profiler, timer_id: self->metrics.gpu_time, value: gpu_time); |
1175 | gsk_profiler_timer_set (profiler: self->profiler, timer_id: self->metrics.cpu_time, value: cpu_time); |
1176 | gsk_profiler_counter_inc (profiler: self->profiler, counter_id: self->metrics.n_frames); |
1177 | |
1178 | gsk_profiler_push_samples (profiler: self->profiler); |
1179 | } |
1180 | #endif |
1181 | } |
1182 | |
1183 | void |
1184 | gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self) |
1185 | { |
1186 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1187 | g_assert (self->batches.len == 0); |
1188 | |
1189 | gsk_gl_command_queue_make_current (self); |
1190 | |
1191 | self->fbo_max = 0; |
1192 | self->tail_batch_index = -1; |
1193 | self->head_batch_index = -1; |
1194 | self->in_frame = TRUE; |
1195 | } |
1196 | |
1197 | /** |
1198 | * gsk_gl_command_queue_end_frame: |
1199 | * @self: a `GskGLCommandQueue` |
1200 | * |
1201 | * This function performs cleanup steps that need to be done after |
1202 | * a frame has finished. This is not performed as part of the command |
1203 | * queue execution to allow for the frame to be submitted as soon |
1204 | * as possible. |
1205 | * |
1206 | * However, it should be executed after the draw contexts end_frame |
1207 | * has been called to swap the OpenGL framebuffers. |
1208 | */ |
1209 | void |
1210 | gsk_gl_command_queue_end_frame (GskGLCommandQueue *self) |
1211 | { |
1212 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1213 | |
1214 | gsk_gl_command_queue_make_current (self); |
1215 | gsk_gl_uniform_state_end_frame (state: self->uniforms); |
1216 | |
1217 | /* Reset attachments so we don't hold on to any textures |
1218 | * that might be released after the frame. |
1219 | */ |
1220 | for (guint i = 0; i < G_N_ELEMENTS (self->attachments->textures); i++) |
1221 | { |
1222 | if (self->attachments->textures[i].id != 0) |
1223 | { |
1224 | glActiveTexture (GL_TEXTURE0 + i); |
1225 | glBindTexture (GL_TEXTURE_2D, 0); |
1226 | |
1227 | self->attachments->textures[i].id = 0; |
1228 | self->attachments->textures[i].changed = FALSE; |
1229 | self->attachments->textures[i].initial = TRUE; |
1230 | } |
1231 | } |
1232 | |
1233 | self->batches.len = 0; |
1234 | self->batch_binds.len = 0; |
1235 | self->batch_uniforms.len = 0; |
1236 | self->n_uploads = 0; |
1237 | self->tail_batch_index = -1; |
1238 | self->in_frame = FALSE; |
1239 | } |
1240 | |
1241 | gboolean |
1242 | gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self, |
1243 | int width, |
1244 | int height, |
1245 | int format, |
1246 | int min_filter, |
1247 | int mag_filter, |
1248 | guint *out_fbo_id, |
1249 | guint *out_texture_id) |
1250 | { |
1251 | GLuint fbo_id = 0; |
1252 | GLint texture_id; |
1253 | |
1254 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1255 | g_assert (width > 0); |
1256 | g_assert (height > 0); |
1257 | g_assert (out_fbo_id != NULL); |
1258 | g_assert (out_texture_id != NULL); |
1259 | |
1260 | texture_id = gsk_gl_command_queue_create_texture (self, |
1261 | width, height, |
1262 | format, |
1263 | min_filter, mag_filter); |
1264 | |
1265 | if (texture_id == -1) |
1266 | { |
1267 | *out_fbo_id = 0; |
1268 | *out_texture_id = 0; |
1269 | return FALSE; |
1270 | } |
1271 | |
1272 | fbo_id = gsk_gl_command_queue_create_framebuffer (self); |
1273 | |
1274 | glBindFramebuffer (GL_FRAMEBUFFER, fbo_id); |
1275 | glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0); |
1276 | g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE); |
1277 | |
1278 | *out_fbo_id = fbo_id; |
1279 | *out_texture_id = texture_id; |
1280 | |
1281 | return TRUE; |
1282 | } |
1283 | |
1284 | int |
1285 | gsk_gl_command_queue_create_texture (GskGLCommandQueue *self, |
1286 | int width, |
1287 | int height, |
1288 | int format, |
1289 | int min_filter, |
1290 | int mag_filter) |
1291 | { |
1292 | GLuint texture_id = 0; |
1293 | |
1294 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1295 | |
1296 | if G_UNLIKELY (self->max_texture_size == -1) |
1297 | glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size); |
1298 | |
1299 | if (width > self->max_texture_size || height > self->max_texture_size) |
1300 | return -1; |
1301 | |
1302 | glGenTextures (1, &texture_id); |
1303 | |
1304 | glActiveTexture (GL_TEXTURE0); |
1305 | glBindTexture (GL_TEXTURE_2D, texture_id); |
1306 | |
1307 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter); |
1308 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); |
1309 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
1310 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
1311 | |
1312 | switch (format) |
1313 | { |
1314 | case GL_RGBA8: |
1315 | glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); |
1316 | break; |
1317 | case GL_RGBA16F: |
1318 | glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL); |
1319 | break; |
1320 | case GL_RGBA32F: |
1321 | glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_FLOAT, NULL); |
1322 | break; |
1323 | default: |
1324 | /* If you add new formats, make sure to set the correct format and type here |
1325 | * so that GLES doesn't barf invalid operations at you. |
1326 | * Because it is very important that these 3 values match when data is set to |
1327 | * NULL, do you hear me? |
1328 | */ |
1329 | g_assert_not_reached (); |
1330 | break; |
1331 | } |
1332 | |
1333 | /* Restore the previous texture if it was set */ |
1334 | if (self->attachments->textures[0].id != 0) |
1335 | glBindTexture (GL_TEXTURE_2D, self->attachments->textures[0].id); |
1336 | |
1337 | return (int)texture_id; |
1338 | } |
1339 | |
1340 | guint |
1341 | gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self) |
1342 | { |
1343 | GLuint fbo_id; |
1344 | |
1345 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1346 | |
1347 | glGenFramebuffers (1, &fbo_id); |
1348 | |
1349 | return fbo_id; |
1350 | } |
1351 | |
1352 | static void |
1353 | gsk_gl_command_queue_do_upload_texture (GskGLCommandQueue *self, |
1354 | GdkTexture *texture) |
1355 | { |
1356 | GdkGLContext *context; |
1357 | const guchar *data; |
1358 | gsize stride; |
1359 | GdkMemoryTexture *memtex; |
1360 | GdkMemoryFormat data_format; |
1361 | int width, height; |
1362 | GLenum gl_internalformat; |
1363 | GLenum gl_format; |
1364 | GLenum gl_type; |
1365 | gsize bpp; |
1366 | gboolean use_es; |
1367 | |
1368 | context = gdk_gl_context_get_current (); |
1369 | use_es = gdk_gl_context_get_use_es (context); |
1370 | data_format = gdk_texture_get_format (self: texture); |
1371 | width = gdk_texture_get_width (texture); |
1372 | height = gdk_texture_get_height (texture); |
1373 | |
1374 | if (!gdk_memory_format_gl_format (format: data_format, |
1375 | gles: use_es, |
1376 | out_internal_format: &gl_internalformat, |
1377 | out_format: &gl_format, |
1378 | out_type: &gl_type)) |
1379 | { |
1380 | if (gdk_memory_format_prefers_high_depth (format: data_format)) |
1381 | data_format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED; |
1382 | else |
1383 | data_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; |
1384 | if (!gdk_memory_format_gl_format (format: data_format, |
1385 | gles: use_es, |
1386 | out_internal_format: &gl_internalformat, |
1387 | out_format: &gl_format, |
1388 | out_type: &gl_type)) |
1389 | { |
1390 | g_assert_not_reached (); |
1391 | } |
1392 | } |
1393 | |
1394 | memtex = gdk_memory_texture_from_texture (texture, format: data_format); |
1395 | data = gdk_memory_texture_get_data (self: memtex); |
1396 | stride = gdk_memory_texture_get_stride (self: memtex); |
1397 | bpp = gdk_memory_format_bytes_per_pixel (format: data_format); |
1398 | |
1399 | glPixelStorei (GL_UNPACK_ALIGNMENT, gdk_memory_format_alignment (format: data_format)); |
1400 | |
1401 | /* GL_UNPACK_ROW_LENGTH is available on desktop GL, OpenGL ES >= 3.0, or if |
1402 | * the GL_EXT_unpack_subimage extension for OpenGL ES 2.0 is available |
1403 | */ |
1404 | if (stride == width * bpp) |
1405 | { |
1406 | glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, data); |
1407 | } |
1408 | else if (stride % bpp == 0 && |
1409 | (gdk_gl_context_check_version (context, required_gl_major: 0, required_gl_minor: 0, required_gles_major: 3, required_gles_minor: 0) || gdk_gl_context_has_unpack_subimage (context))) |
1410 | { |
1411 | glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp); |
1412 | |
1413 | glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, data); |
1414 | |
1415 | glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); |
1416 | } |
1417 | else |
1418 | { |
1419 | int i; |
1420 | glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, NULL); |
1421 | for (i = 0; i < height; i++) |
1422 | glTexSubImage2D (GL_TEXTURE_2D, 0, 0, i, width, 1, gl_format, gl_type, data + (i * stride)); |
1423 | } |
1424 | glPixelStorei (GL_UNPACK_ALIGNMENT, 4); |
1425 | |
1426 | g_object_unref (object: memtex); |
1427 | } |
1428 | |
1429 | int |
1430 | gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, |
1431 | GdkTexture *texture, |
1432 | int min_filter, |
1433 | int mag_filter) |
1434 | { |
1435 | G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; |
1436 | cairo_surface_t *surface = NULL; |
1437 | int width, height; |
1438 | int texture_id; |
1439 | |
1440 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1441 | g_assert (!GDK_IS_GL_TEXTURE (texture)); |
1442 | g_assert (min_filter == GL_LINEAR || min_filter == GL_NEAREST); |
1443 | g_assert (mag_filter == GL_LINEAR || min_filter == GL_NEAREST); |
1444 | |
1445 | width = gdk_texture_get_width (texture); |
1446 | height = gdk_texture_get_height (texture); |
1447 | if (width > self->max_texture_size || height > self->max_texture_size) |
1448 | { |
1449 | g_warning ("Attempt to create texture of size %ux%u but max size is %d. " |
1450 | "Clipping will occur." , |
1451 | width, height, self->max_texture_size); |
1452 | width = MAX (width, self->max_texture_size); |
1453 | height = MAX (height, self->max_texture_size); |
1454 | } |
1455 | texture_id = gsk_gl_command_queue_create_texture (self, width, height, GL_RGBA8, min_filter, mag_filter); |
1456 | if (texture_id == -1) |
1457 | return texture_id; |
1458 | |
1459 | self->n_uploads++; |
1460 | |
1461 | /* Switch to texture0 as 2D. We'll restore it later. */ |
1462 | glActiveTexture (GL_TEXTURE0); |
1463 | glBindTexture (GL_TEXTURE_2D, texture_id); |
1464 | |
1465 | gsk_gl_command_queue_do_upload_texture (self, texture); |
1466 | |
1467 | /* Restore previous texture state if any */ |
1468 | if (self->attachments->textures[0].id > 0) |
1469 | glBindTexture (self->attachments->textures[0].target, |
1470 | self->attachments->textures[0].id); |
1471 | |
1472 | g_clear_pointer (&surface, cairo_surface_destroy); |
1473 | |
1474 | if (gdk_profiler_is_running ()) |
1475 | gdk_profiler_add_markf (start_time, GDK_PROFILER_CURRENT_TIME-start_time, |
1476 | "Upload Texture" , |
1477 | "Size %dx%d" , width, height); |
1478 | |
1479 | return texture_id; |
1480 | } |
1481 | |
1482 | void |
1483 | gsk_gl_command_queue_set_profiler (GskGLCommandQueue *self, |
1484 | GskProfiler *profiler) |
1485 | { |
1486 | #ifdef G_ENABLE_DEBUG |
1487 | g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); |
1488 | g_assert (GSK_IS_PROFILER (profiler)); |
1489 | |
1490 | if (g_set_object (&self->profiler, profiler)) |
1491 | { |
1492 | self->gl_profiler = gsk_gl_profiler_new (context: self->context); |
1493 | |
1494 | self->metrics.n_frames = gsk_profiler_add_counter (profiler, counter_name: "frames" , description: "Frames" , FALSE); |
1495 | self->metrics.cpu_time = gsk_profiler_add_timer (profiler, timer_name: "cpu-time" , description: "CPU Time" , FALSE, TRUE); |
1496 | self->metrics.gpu_time = gsk_profiler_add_timer (profiler, timer_name: "gpu-time" , description: "GPU Time" , FALSE, TRUE); |
1497 | |
1498 | self->metrics.n_binds = gdk_profiler_define_int_counter ("attachments" , "Number of texture attachments" ); |
1499 | self->metrics.n_fbos = gdk_profiler_define_int_counter ("fbos" , "Number of framebuffers attached" ); |
1500 | self->metrics.n_uniforms = gdk_profiler_define_int_counter ("uniforms" , "Number of uniforms changed" ); |
1501 | self->metrics.n_uploads = gdk_profiler_define_int_counter ("uploads" , "Number of texture uploads" ); |
1502 | self->metrics.n_programs = gdk_profiler_define_int_counter ("programs" , "Number of program changes" ); |
1503 | self->metrics.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth" , "Depth of GL command batches" ); |
1504 | } |
1505 | #endif |
1506 | } |
1507 | |