1/* gskglrenderjob.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 <gdk/gdkglcontextprivate.h>
27#include <gdk/gdkprofilerprivate.h>
28#include <gdk/gdkrgbaprivate.h>
29#include <gsk/gskrendernodeprivate.h>
30#include <gsk/gskglshaderprivate.h>
31#include <gdk/gdktextureprivate.h>
32#include <gsk/gsktransformprivate.h>
33#include <gsk/gskroundedrectprivate.h>
34#include <math.h>
35#include <string.h>
36
37#include "gskglcommandqueueprivate.h"
38#include "gskgldriverprivate.h"
39#include "gskglglyphlibraryprivate.h"
40#include "gskgliconlibraryprivate.h"
41#include "gskglprogramprivate.h"
42#include "gskglrenderjobprivate.h"
43#include "gskglshadowlibraryprivate.h"
44
45#include "ninesliceprivate.h"
46#include "fp16private.h"
47
48#define ORTHO_NEAR_PLANE -10000
49#define ORTHO_FAR_PLANE 10000
50#define MAX_GRADIENT_STOPS 6
51#define SHADOW_EXTRA_SIZE 4
52
53/* Make sure gradient stops fits in packed array_count */
54G_STATIC_ASSERT ((MAX_GRADIENT_STOPS * 5) < (1 << GSK_GL_UNIFORM_ARRAY_BITS));
55
56#define rounded_rect_top_left(r) \
57 (GRAPHENE_RECT_INIT(r->bounds.origin.x, \
58 r->bounds.origin.y, \
59 r->corner[0].width, r->corner[0].height))
60#define rounded_rect_top_right(r) \
61 (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[1].width, \
62 r->bounds.origin.y, \
63 r->corner[1].width, r->corner[1].height))
64#define rounded_rect_bottom_right(r) \
65 (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[2].width, \
66 r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
67 r->corner[2].width, r->corner[2].height))
68#define rounded_rect_bottom_left(r) \
69 (GRAPHENE_RECT_INIT(r->bounds.origin.x, \
70 r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
71 r->corner[3].width, r->corner[3].height))
72#define rounded_rect_corner0(r) rounded_rect_top_left(r)
73#define rounded_rect_corner1(r) rounded_rect_top_right(r)
74#define rounded_rect_corner2(r) rounded_rect_bottom_right(r)
75#define rounded_rect_corner3(r) rounded_rect_bottom_left(r)
76#define rounded_rect_corner(r, i) (rounded_rect_corner##i(r))
77#define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff))
78#define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha)
79
80typedef struct _GskGLRenderClip
81{
82 GskRoundedRect rect;
83 guint is_rectilinear : 1;
84 guint is_fully_contained : 1;
85} GskGLRenderClip;
86
87typedef struct _GskGLRenderModelview
88{
89 GskTransform *transform;
90 float scale_x;
91 float scale_y;
92 float dx;
93 float dy;
94 float offset_x_before;
95 float offset_y_before;
96 graphene_matrix_t matrix;
97} GskGLRenderModelview;
98
99struct _GskGLRenderJob
100{
101 /* The context containing the framebuffer we are drawing to. Generally this
102 * is the context of the surface but may be a shared context if rendering to
103 * an offscreen texture such as gsk_gl_renderer_render_texture().
104 */
105 GdkGLContext *context;
106
107 /* The driver to be used. This is shared among all the renderers on a given
108 * GdkDisplay and uses the shared GL context to send commands.
109 */
110 GskGLDriver *driver;
111
112 /* The command queue (which is just a faster pointer to the driver's
113 * command queue.
114 */
115 GskGLCommandQueue *command_queue;
116
117 /* The region that we are clipping. Normalized to a single rectangle region. */
118 cairo_region_t *region;
119
120 /* The framebuffer to draw to in the @context GL context. So 0 would be the
121 * default framebuffer of @context. This is important to note as many other
122 * operations could be done using objects shared from the command queues
123 * GL context.
124 */
125 guint framebuffer;
126 guint default_framebuffer;
127
128 /* The viewport we are using. This state is updated as we process render
129 * nodes in the specific visitor callbacks.
130 */
131 graphene_rect_t viewport;
132
133 /* The current projection, updated as we process nodes */
134 graphene_matrix_t projection;
135
136 /* An array of GskGLRenderModelview updated as nodes are processed. The
137 * current modelview is the last element.
138 */
139 GArray *modelview;
140
141 /* An array of GskGLRenderClip updated as nodes are processed. The
142 * current clip is the last element.
143 */
144 GArray *clip;
145
146 /* Our current alpha state as we process nodes */
147 float alpha;
148
149 /* Offset (delta x,y) as we process nodes. Occasionally this is merged into
150 * a transform that is referenced from child transform nodes.
151 */
152 float offset_x;
153 float offset_y;
154
155 /* The scale we are processing, possibly updated by transforms */
156 float scale_x;
157 float scale_y;
158
159 /* Cached pointers */
160 const GskGLRenderClip *current_clip;
161 const GskGLRenderModelview *current_modelview;
162 GskGLProgram *current_program;
163
164 /* If we should be rendering red zones over fallback nodes */
165 guint debug_fallback : 1;
166
167 /* In some cases we might want to avoid clearing the framebuffer
168 * because we're going to render over the existing contents.
169 */
170 guint clear_framebuffer : 1;
171
172 /* Format we want to use for intermediate textures, determined by
173 * looking at the format of the framebuffer we are rendering on.
174 */
175 int target_format;
176};
177
178typedef struct _GskGLRenderOffscreen
179{
180 /* The bounds to render */
181 const graphene_rect_t *bounds;
182
183 /* Return location for texture coordinates */
184 struct {
185 float x;
186 float y;
187 float x2;
188 float y2;
189 } area;
190
191 /* Return location for texture ID */
192 guint texture_id;
193
194 /* Whether to force creating a new texture, even if the
195 * input already is a texture
196 */
197 guint force_offscreen : 1;
198 guint reset_clip : 1;
199 guint do_not_cache : 1;
200 guint linear_filter : 1;
201
202 /* Return location for whether we created a texture */
203 guint was_offscreen : 1;
204} GskGLRenderOffscreen;
205
206static void gsk_gl_render_job_visit_node (GskGLRenderJob *job,
207 const GskRenderNode *node);
208static gboolean gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job,
209 const GskRenderNode *node,
210 GskGLRenderOffscreen *offscreen);
211
212static inline int
213get_target_format (GskGLRenderJob *job,
214 const GskRenderNode *node)
215{
216 if (gsk_render_node_prefers_high_depth (node))
217 return job->target_format;
218
219 return GL_RGBA8;
220}
221
222static inline void
223init_full_texture_region (GskGLRenderOffscreen *offscreen)
224{
225 offscreen->area.x = 0;
226 offscreen->area.y = 0;
227 offscreen->area.x2 = 1;
228 offscreen->area.y2 = 1;
229}
230
231static inline gboolean G_GNUC_PURE
232node_is_invisible (const GskRenderNode *node)
233{
234 return node->bounds.size.width == 0.0f ||
235 node->bounds.size.height == 0.0f;
236}
237
238static inline gboolean G_GNUC_PURE
239rounded_rect_equal (const GskRoundedRect *r1,
240 const GskRoundedRect *r2)
241{
242 return memcmp (s1: r1, s2: r2, n: sizeof (GskRoundedRect)) == 0;
243}
244
245static inline void
246gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self)
247{
248 self->bounds.size.width = MAX (self->corner[0].width + self->corner[1].width,
249 self->corner[3].width + self->corner[2].width);
250 self->bounds.size.height = MAX (self->corner[0].height + self->corner[3].height,
251 self->corner[1].height + self->corner[2].height);
252}
253
254static inline gboolean G_GNUC_PURE
255node_supports_2d_transform (const GskRenderNode *node)
256{
257 switch ((int)gsk_render_node_get_node_type (node))
258 {
259 case GSK_COLOR_NODE:
260 case GSK_OPACITY_NODE:
261 case GSK_COLOR_MATRIX_NODE:
262 case GSK_TEXTURE_NODE:
263 case GSK_CROSS_FADE_NODE:
264 case GSK_LINEAR_GRADIENT_NODE:
265 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
266 case GSK_CONIC_GRADIENT_NODE:
267 case GSK_RADIAL_GRADIENT_NODE:
268 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
269 case GSK_DEBUG_NODE:
270 case GSK_TEXT_NODE:
271 case GSK_CAIRO_NODE:
272 case GSK_BLEND_NODE:
273 case GSK_BLUR_NODE:
274 return TRUE;
275
276 case GSK_SHADOW_NODE:
277 return node_supports_2d_transform (node: gsk_shadow_node_get_child (node));
278
279 case GSK_TRANSFORM_NODE:
280 return node_supports_2d_transform (node: gsk_transform_node_get_child (node));
281
282 case GSK_CONTAINER_NODE:
283 for (guint i = 0, p = gsk_container_node_get_n_children (node); i < p; i++)
284 {
285 if (!node_supports_2d_transform (node: gsk_container_node_get_child (node, idx: i)))
286 return FALSE;
287 }
288 return TRUE;
289
290 default:
291 return FALSE;
292 }
293}
294
295static inline gboolean G_GNUC_PURE
296node_supports_transform (const GskRenderNode *node)
297{
298 /* Some nodes can't handle non-trivial transforms without being
299 * rendered to a texture (e.g. rotated clips, etc.). Some however work
300 * just fine, mostly because they already draw their child to a
301 * texture and just render the texture manipulated in some way, think
302 * opacity or color matrix.
303 */
304
305 switch ((int)gsk_render_node_get_node_type (node))
306 {
307 case GSK_COLOR_NODE:
308 case GSK_OPACITY_NODE:
309 case GSK_COLOR_MATRIX_NODE:
310 case GSK_TEXTURE_NODE:
311 case GSK_CROSS_FADE_NODE:
312 case GSK_DEBUG_NODE:
313 case GSK_TEXT_NODE:
314 case GSK_CAIRO_NODE:
315 case GSK_BLEND_NODE:
316 case GSK_BLUR_NODE:
317 return TRUE;
318
319 case GSK_SHADOW_NODE:
320 return node_supports_transform (node: gsk_shadow_node_get_child (node));
321
322 case GSK_TRANSFORM_NODE:
323 return node_supports_transform (node: gsk_transform_node_get_child (node));
324
325 default:
326 return FALSE;
327 }
328}
329
330static inline gboolean G_GNUC_PURE
331color_matrix_modifies_alpha (const GskRenderNode *node)
332{
333 const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node);
334 const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node);
335 graphene_vec4_t row3;
336
337 if (graphene_vec4_get_w (v: offset) != 0.0f)
338 return TRUE;
339
340 graphene_matrix_get_row (m: matrix, index_: 3, res: &row3);
341
342 return !graphene_vec4_equal (v1: graphene_vec4_w_axis (), v2: &row3);
343}
344
345static inline gboolean G_GNUC_PURE
346rect_contains_rect (const graphene_rect_t *r1,
347 const graphene_rect_t *r2)
348{
349 return r2->origin.x >= r1->origin.x &&
350 (r2->origin.x + r2->size.width) <= (r1->origin.x + r1->size.width) &&
351 r2->origin.y >= r1->origin.y &&
352 (r2->origin.y + r2->size.height) <= (r1->origin.y + r1->size.height);
353}
354
355static inline gboolean
356rounded_inner_rect_contains_rect (const GskRoundedRect *rounded,
357 const graphene_rect_t *rect)
358{
359 const graphene_rect_t *rounded_bounds = &rounded->bounds;
360 graphene_rect_t inner;
361 float offset_x;
362 float offset_y;
363
364 /* TODO: This is pretty conservative and we could go further,
365 * more fine-grained checks to avoid offscreen drawing.
366 */
367
368 offset_x = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].width,
369 rounded->corner[GSK_CORNER_BOTTOM_LEFT].width);
370 offset_y = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].height,
371 rounded->corner[GSK_CORNER_TOP_RIGHT].height);
372
373 inner.origin.x = rounded_bounds->origin.x + offset_x;
374 inner.origin.y = rounded_bounds->origin.y + offset_y;
375 inner.size.width = rounded_bounds->size.width - offset_x -
376 MAX (rounded->corner[GSK_CORNER_TOP_RIGHT].width,
377 rounded->corner[GSK_CORNER_BOTTOM_RIGHT].width);
378 inner.size.height = rounded_bounds->size.height - offset_y -
379 MAX (rounded->corner[GSK_CORNER_BOTTOM_LEFT].height,
380 rounded->corner[GSK_CORNER_BOTTOM_RIGHT].height);
381
382 return rect_contains_rect (r1: &inner, r2: rect);
383}
384
385static inline gboolean G_GNUC_PURE
386rect_intersects (const graphene_rect_t *r1,
387 const graphene_rect_t *r2)
388{
389 /* Assume both rects are already normalized, as they usually are */
390 if (r1->origin.x > (r2->origin.x + r2->size.width) ||
391 (r1->origin.x + r1->size.width) < r2->origin.x)
392 return FALSE;
393 else if (r1->origin.y > (r2->origin.y + r2->size.height) ||
394 (r1->origin.y + r1->size.height) < r2->origin.y)
395 return FALSE;
396 else
397 return TRUE;
398}
399
400static inline gboolean
401rounded_rect_has_corner (const GskRoundedRect *r,
402 guint i)
403{
404 return r->corner[i].width > 0 && r->corner[i].height > 0;
405}
406
407/* Current clip is NOT rounded but new one is definitely! */
408static inline gboolean
409intersect_rounded_rectilinear (const graphene_rect_t *non_rounded,
410 const GskRoundedRect *rounded,
411 GskRoundedRect *result)
412{
413 gboolean corners[4];
414
415 /* Intersects with top left corner? */
416 corners[0] = rounded_rect_has_corner (r: rounded, i: 0) &&
417 rect_intersects (r1: non_rounded,
418 r2: &rounded_rect_corner (rounded, 0));
419 if (corners[0] && !rect_contains_rect (r1: non_rounded,
420 r2: &rounded_rect_corner (rounded, 0)))
421 return FALSE;
422
423 /* top right ? */
424 corners[1] = rounded_rect_has_corner (r: rounded, i: 1) &&
425 rect_intersects (r1: non_rounded,
426 r2: &rounded_rect_corner (rounded, 1));
427 if (corners[1] && !rect_contains_rect (r1: non_rounded,
428 r2: &rounded_rect_corner (rounded, 1)))
429 return FALSE;
430
431 /* bottom right ? */
432 corners[2] = rounded_rect_has_corner (r: rounded, i: 2) &&
433 rect_intersects (r1: non_rounded,
434 r2: &rounded_rect_corner (rounded, 2));
435 if (corners[2] && !rect_contains_rect (r1: non_rounded,
436 r2: &rounded_rect_corner (rounded, 2)))
437 return FALSE;
438
439 /* bottom left ? */
440 corners[3] = rounded_rect_has_corner (r: rounded, i: 3) &&
441 rect_intersects (r1: non_rounded,
442 r2: &rounded_rect_corner (rounded, 3));
443 if (corners[3] && !rect_contains_rect (r1: non_rounded,
444 r2: &rounded_rect_corner (rounded, 3)))
445 return FALSE;
446
447 /* We do intersect with at least one of the corners, but in such a way that the
448 * intersection between the two clips can still be represented by a single rounded
449 * rect in a trivial way. do that.
450 */
451 graphene_rect_intersection (a: non_rounded, b: &rounded->bounds, res: &result->bounds);
452
453 for (guint i = 0; i < 4; i++)
454 {
455 if (corners[i])
456 result->corner[i] = rounded->corner[i];
457 else
458 result->corner[i].width = result->corner[i].height = 0;
459 }
460
461 return TRUE;
462}
463
464static inline void
465init_projection_matrix (graphene_matrix_t *projection,
466 const graphene_rect_t *viewport)
467{
468 graphene_matrix_init_ortho (m: projection,
469 left: viewport->origin.x,
470 right: viewport->origin.x + viewport->size.width,
471 top: viewport->origin.y,
472 bottom: viewport->origin.y + viewport->size.height,
473 ORTHO_NEAR_PLANE,
474 ORTHO_FAR_PLANE);
475 graphene_matrix_scale (m: projection, factor_x: 1, factor_y: -1, factor_z: 1);
476}
477
478static inline float
479gsk_gl_render_job_set_alpha (GskGLRenderJob *job,
480 float alpha)
481{
482 if (job->alpha != alpha)
483 {
484 float ret = job->alpha;
485 job->alpha = alpha;
486 job->driver->stamps[UNIFORM_SHARED_ALPHA]++;
487 return ret;
488 }
489
490 return alpha;
491}
492
493static void
494extract_matrix_metadata (GskGLRenderModelview *modelview)
495{
496 gsk_transform_to_matrix (self: modelview->transform, out_matrix: &modelview->matrix);
497
498 switch (gsk_transform_get_category (modelview->transform))
499 {
500 case GSK_TRANSFORM_CATEGORY_IDENTITY:
501 modelview->scale_x = 1;
502 modelview->scale_y = 1;
503 modelview->dx = 0;
504 modelview->dy = 0;
505 break;
506
507 case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
508 modelview->scale_x = 1;
509 modelview->scale_y = 1;
510 gsk_transform_to_translate (self: modelview->transform,
511 out_dx: &modelview->dx, out_dy: &modelview->dy);
512 break;
513
514 case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
515 gsk_transform_to_affine (self: modelview->transform,
516 out_scale_x: &modelview->scale_x, out_scale_y: &modelview->scale_y,
517 out_dx: &modelview->dx, out_dy: &modelview->dy);
518 break;
519
520 case GSK_TRANSFORM_CATEGORY_2D:
521 {
522 float xx, xy, yx, yy, dx, dy;
523
524 gsk_transform_to_2d (self: modelview->transform,
525 out_xx: &xx, out_yx: &xy, out_xy: &yx, out_yy: &yy, out_dx: &dx, out_dy: &dy);
526
527 modelview->scale_x = sqrtf (x: xx * xx + xy * xy);
528 modelview->scale_y = sqrtf (x: yx * yx + yy * yy);
529 }
530 break;
531
532 case GSK_TRANSFORM_CATEGORY_UNKNOWN:
533 case GSK_TRANSFORM_CATEGORY_ANY:
534 case GSK_TRANSFORM_CATEGORY_3D:
535 {
536 graphene_vec3_t col1;
537 graphene_vec3_t col2;
538
539 /* TODO: 90% sure this is incorrect. But we should never hit this code
540 * path anyway. */
541 graphene_vec3_init (v: &col1,
542 x: graphene_matrix_get_value (m: &modelview->matrix, row: 0, col: 0),
543 y: graphene_matrix_get_value (m: &modelview->matrix, row: 1, col: 0),
544 z: graphene_matrix_get_value (m: &modelview->matrix, row: 2, col: 0));
545
546 graphene_vec3_init (v: &col2,
547 x: graphene_matrix_get_value (m: &modelview->matrix, row: 0, col: 1),
548 y: graphene_matrix_get_value (m: &modelview->matrix, row: 1, col: 1),
549 z: graphene_matrix_get_value (m: &modelview->matrix, row: 2, col: 1));
550
551 modelview->scale_x = graphene_vec3_length (v: &col1);
552 modelview->scale_y = graphene_vec3_length (v: &col2);
553 modelview->dx = 0;
554 modelview->dy = 0;
555 }
556 break;
557
558 default:
559 break;
560 }
561}
562
563static void
564gsk_gl_render_job_set_modelview (GskGLRenderJob *job,
565 GskTransform *transform)
566{
567 GskGLRenderModelview *modelview;
568
569 g_assert (job != NULL);
570 g_assert (job->modelview != NULL);
571
572 job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
573
574 g_array_set_size (array: job->modelview, length: job->modelview->len + 1);
575
576 modelview = &g_array_index (job->modelview,
577 GskGLRenderModelview,
578 job->modelview->len - 1);
579
580 modelview->transform = transform;
581
582 modelview->offset_x_before = job->offset_x;
583 modelview->offset_y_before = job->offset_y;
584
585 extract_matrix_metadata (modelview);
586
587 job->offset_x = 0;
588 job->offset_y = 0;
589 job->scale_x = modelview->scale_x;
590 job->scale_y = modelview->scale_y;
591
592 job->current_modelview = modelview;
593}
594
595static void
596gsk_gl_render_job_push_modelview (GskGLRenderJob *job,
597 GskTransform *transform)
598{
599 GskGLRenderModelview *modelview;
600
601 g_assert (job != NULL);
602 g_assert (job->modelview != NULL);
603 g_assert (transform != NULL);
604
605 job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
606
607 g_array_set_size (array: job->modelview, length: job->modelview->len + 1);
608
609 modelview = &g_array_index (job->modelview,
610 GskGLRenderModelview,
611 job->modelview->len - 1);
612
613 if G_LIKELY (job->modelview->len > 1)
614 {
615 GskGLRenderModelview *last;
616 GskTransform *t = NULL;
617
618 last = &g_array_index (job->modelview,
619 GskGLRenderModelview,
620 job->modelview->len - 2);
621
622 /* Multiply given matrix with our previous modelview */
623 t = gsk_transform_translate (next: gsk_transform_ref (self: last->transform),
624 point: &(graphene_point_t) {
625 job->offset_x,
626 job->offset_y
627 });
628 t = gsk_transform_transform (next: t, other: transform);
629 modelview->transform = t;
630 }
631 else
632 {
633 modelview->transform = gsk_transform_ref (self: transform);
634 }
635
636 modelview->offset_x_before = job->offset_x;
637 modelview->offset_y_before = job->offset_y;
638
639 extract_matrix_metadata (modelview);
640
641 job->offset_x = 0;
642 job->offset_y = 0;
643 job->scale_x = modelview->scale_x;
644 job->scale_y = modelview->scale_y;
645
646 job->current_modelview = modelview;
647}
648
649static void
650gsk_gl_render_job_pop_modelview (GskGLRenderJob *job)
651{
652 const GskGLRenderModelview *head;
653
654 g_assert (job != NULL);
655 g_assert (job->modelview);
656 g_assert (job->modelview->len > 0);
657
658 job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
659
660 head = job->current_modelview;
661
662 job->offset_x = head->offset_x_before;
663 job->offset_y = head->offset_y_before;
664
665 gsk_transform_unref (self: head->transform);
666
667 job->modelview->len--;
668
669 if (job->modelview->len >= 1)
670 {
671 head = &g_array_index (job->modelview, GskGLRenderModelview, job->modelview->len - 1);
672
673 job->scale_x = head->scale_x;
674 job->scale_y = head->scale_y;
675
676 job->current_modelview = head;
677 }
678 else
679 {
680 job->current_modelview = NULL;
681 }
682}
683
684static void
685gsk_gl_render_job_push_clip (GskGLRenderJob *job,
686 const GskRoundedRect *rect)
687{
688 GskGLRenderClip *clip;
689
690 g_assert (job != NULL);
691 g_assert (job->clip != NULL);
692 g_assert (rect != NULL);
693
694 job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
695
696 g_array_set_size (array: job->clip, length: job->clip->len + 1);
697
698 clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
699 memcpy (dest: &clip->rect, src: rect, n: sizeof *rect);
700 clip->is_rectilinear = gsk_rounded_rect_is_rectilinear (self: rect);
701 clip->is_fully_contained = FALSE;
702
703 job->current_clip = clip;
704}
705
706static void
707gsk_gl_render_job_push_contained_clip (GskGLRenderJob *job)
708{
709 GskGLRenderClip *clip;
710 GskGLRenderClip *old_clip;
711
712 g_assert (job != NULL);
713 g_assert (job->clip != NULL);
714 g_assert (job->clip->len > 0);
715
716 job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
717
718 old_clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
719
720 g_array_set_size (array: job->clip, length: job->clip->len + 1);
721
722 clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
723 memcpy (dest: &clip->rect.bounds, src: &old_clip->rect.bounds, n: sizeof (graphene_rect_t));
724 memset (s: clip->rect.corner, c: 0, n: sizeof clip->rect.corner);
725 clip->is_rectilinear = TRUE;
726 clip->is_fully_contained = TRUE;
727
728 job->current_clip = clip;
729}
730
731static void
732gsk_gl_render_job_pop_clip (GskGLRenderJob *job)
733{
734 g_assert (job != NULL);
735 g_assert (job->clip != NULL);
736 g_assert (job->clip->len > 0);
737
738 job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
739 job->current_clip--;
740 job->clip->len--;
741}
742
743static inline void
744gsk_gl_render_job_offset (GskGLRenderJob *job,
745 float offset_x,
746 float offset_y)
747{
748 if (offset_x || offset_y)
749 {
750 job->offset_x += offset_x;
751 job->offset_y += offset_y;
752 }
753}
754
755static inline void
756gsk_gl_render_job_set_projection (GskGLRenderJob *job,
757 const graphene_matrix_t *projection)
758{
759 memcpy (dest: &job->projection, src: projection, n: sizeof job->projection);
760 job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
761}
762
763static inline void
764gsk_gl_render_job_set_projection_from_rect (GskGLRenderJob *job,
765 const graphene_rect_t *rect,
766 graphene_matrix_t *prev_projection)
767{
768 if (prev_projection)
769 memcpy (dest: prev_projection, src: &job->projection, n: sizeof *prev_projection);
770 init_projection_matrix (projection: &job->projection, viewport: rect);
771 job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
772}
773
774static inline void
775gsk_gl_render_job_set_projection_for_size (GskGLRenderJob *job,
776 float width,
777 float height,
778 graphene_matrix_t *prev_projection)
779{
780 if (prev_projection)
781 memcpy (dest: prev_projection, src: &job->projection, n: sizeof *prev_projection);
782 graphene_matrix_init_ortho (m: &job->projection, left: 0, right: width, top: 0, bottom: height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
783 graphene_matrix_scale (m: &job->projection, factor_x: 1, factor_y: -1, factor_z: 1);
784 job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
785}
786
787static inline void
788gsk_gl_render_job_set_viewport (GskGLRenderJob *job,
789 const graphene_rect_t *viewport,
790 graphene_rect_t *prev_viewport)
791{
792 if (prev_viewport)
793 memcpy (dest: prev_viewport, src: &job->viewport, n: sizeof *prev_viewport);
794 memcpy (dest: &job->viewport, src: viewport, n: sizeof job->viewport);
795 job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
796}
797
798static inline void
799gsk_gl_render_job_set_viewport_for_size (GskGLRenderJob *job,
800 float width,
801 float height,
802 graphene_rect_t *prev_viewport)
803{
804 if (prev_viewport)
805 memcpy (dest: prev_viewport, src: &job->viewport, n: sizeof *prev_viewport);
806 job->viewport.origin.x = 0;
807 job->viewport.origin.y = 0;
808 job->viewport.size.width = width;
809 job->viewport.size.height = height;
810 job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
811}
812
813static inline void
814gsk_gl_render_job_transform_bounds (GskGLRenderJob *job,
815 const graphene_rect_t *rect,
816 graphene_rect_t *out_rect)
817{
818 GskTransform *transform;
819 GskTransformCategory category;
820
821 g_assert (job != NULL);
822 g_assert (job->modelview->len > 0);
823 g_assert (rect != NULL);
824 g_assert (out_rect != NULL);
825
826 transform = job->current_modelview->transform;
827 category = gsk_transform_get_category (transform);
828
829 /* Our most common transform is 2d-affine, so inline it.
830 * Both identity and 2d-translate are virtually unseen here.
831 */
832 if G_LIKELY (category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE)
833 {
834 float scale_x = job->current_modelview->scale_x;
835 float scale_y = job->current_modelview->scale_y;
836 float dx = job->current_modelview->dx;
837 float dy = job->current_modelview->dy;
838
839 /* Init directly into out rect */
840 out_rect->origin.x = ((rect->origin.x + job->offset_x) * scale_x) + dx;
841 out_rect->origin.y = ((rect->origin.y + job->offset_y) * scale_y) + dy;
842 out_rect->size.width = rect->size.width * scale_x;
843 out_rect->size.height = rect->size.height * scale_y;
844
845 /* Normalize in place */
846 if (out_rect->size.width < 0.f)
847 {
848 float size = fabsf (x: out_rect->size.width);
849
850 out_rect->origin.x -= size;
851 out_rect->size.width = size;
852 }
853
854 if (out_rect->size.height < 0.f)
855 {
856 float size = fabsf (x: out_rect->size.height);
857
858 out_rect->origin.y -= size;
859 out_rect->size.height = size;
860 }
861 }
862 else
863 {
864 graphene_rect_t r;
865
866 r.origin.x = rect->origin.x + job->offset_x;
867 r.origin.y = rect->origin.y + job->offset_y;
868 r.size.width = rect->size.width;
869 r.size.height = rect->size.height;
870
871 gsk_transform_transform_bounds (self: transform, rect: &r, out_rect);
872 }
873}
874
875static inline void
876gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job,
877 const GskRoundedRect *rect,
878 GskRoundedRect *out_rect)
879{
880 out_rect->bounds.origin.x = job->offset_x + rect->bounds.origin.x;
881 out_rect->bounds.origin.y = job->offset_y + rect->bounds.origin.y;
882 out_rect->bounds.size.width = rect->bounds.size.width;
883 out_rect->bounds.size.height = rect->bounds.size.height;
884 memcpy (dest: out_rect->corner, src: rect->corner, n: sizeof rect->corner);
885}
886
887static inline void
888rounded_rect_get_inner (const GskRoundedRect *rect,
889 graphene_rect_t *inner)
890{
891 float left = MAX (rect->corner[GSK_CORNER_TOP_LEFT].width, rect->corner[GSK_CORNER_BOTTOM_LEFT].width);
892 float right = MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->corner[GSK_CORNER_BOTTOM_RIGHT].width);
893 float top = MAX (rect->corner[GSK_CORNER_TOP_LEFT].height, rect->corner[GSK_CORNER_TOP_RIGHT].height);
894 float bottom = MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height, rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
895
896 inner->origin.x = rect->bounds.origin.x + left;
897 inner->size.width = rect->bounds.size.width - (left + right);
898
899 inner->origin.y = rect->bounds.origin.y + top;
900 inner->size.height = rect->bounds.size.height - (top + bottom);
901}
902
903static inline gboolean
904interval_contains (float p1, float w1,
905 float p2, float w2)
906{
907 if (p2 < p1)
908 return FALSE;
909
910 if (p2 + w2 > p1 + w1)
911 return FALSE;
912
913 return TRUE;
914}
915
916static inline gboolean
917gsk_gl_render_job_update_clip (GskGLRenderJob *job,
918 const graphene_rect_t *bounds,
919 gboolean *pushed_clip)
920{
921 graphene_rect_t transformed_bounds;
922 gboolean no_clip = FALSE;
923 gboolean rect_clip = FALSE;
924
925 *pushed_clip = FALSE;
926
927 if (job->current_clip->is_fully_contained)
928 {
929 /* Already fully contained - no further checks needed */
930 return TRUE;
931 }
932
933 gsk_gl_render_job_transform_bounds (job, rect: bounds, out_rect: &transformed_bounds);
934
935 if (!rect_intersects (r1: &job->current_clip->rect.bounds, r2: &transformed_bounds))
936 {
937 /* Completely clipped away */
938 return FALSE;
939 }
940
941 if (job->current_clip->is_rectilinear)
942 {
943 if (rect_contains_rect (r1: &job->current_clip->rect.bounds, r2: &transformed_bounds))
944 no_clip = TRUE;
945 else
946 rect_clip = TRUE;
947 }
948 else if (gsk_rounded_rect_contains_rect (self: &job->current_clip->rect, rect: &transformed_bounds))
949 {
950 no_clip = TRUE;
951 }
952 else
953 {
954 graphene_rect_t inner;
955
956 rounded_rect_get_inner (rect: &job->current_clip->rect, inner: &inner);
957
958 if (interval_contains (p1: inner.origin.x, w1: inner.size.width,
959 p2: transformed_bounds.origin.x, w2: transformed_bounds.size.width) ||
960 interval_contains (p1: inner.origin.y, w1: inner.size.height,
961 p2: transformed_bounds.origin.y, w2: transformed_bounds.size.height))
962 rect_clip = TRUE;
963 }
964
965 if (no_clip)
966 {
967 /* This node is completely contained inside the clip.
968 * Record this fact on the clip stack, so we don't do more work
969 * for child nodes.
970 */
971
972 gsk_gl_render_job_push_contained_clip (job);
973
974 *pushed_clip = TRUE;
975 }
976 else if (rect_clip && !job->current_clip->is_rectilinear)
977 {
978 graphene_rect_t rect;
979
980 /* The clip gets simpler for this node */
981
982 graphene_rect_intersection (a: &job->current_clip->rect.bounds, b: &transformed_bounds, res: &rect);
983 gsk_gl_render_job_push_clip (job, rect: &GSK_ROUNDED_RECT_INIT_FROM_RECT (rect));
984
985 *pushed_clip = TRUE;
986 }
987
988 return TRUE;
989}
990
991static inline void
992rgba_to_half (const GdkRGBA *rgba,
993 guint16 h[4])
994{
995 float_to_half4 (f: (const float *)rgba, h);
996}
997
998/* fill_vertex_data */
999static void
1000gsk_gl_render_job_draw_coords (GskGLRenderJob *job,
1001 float min_x,
1002 float min_y,
1003 float max_x,
1004 float max_y,
1005 float min_u,
1006 float min_v,
1007 float max_u,
1008 float max_v,
1009 guint16 c[4])
1010{
1011 GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1012
1013 vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { min_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
1014 vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
1015 vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
1016 vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { max_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
1017 vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
1018 vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
1019}
1020
1021/* load_vertex_data_with_region */
1022static inline void
1023gsk_gl_render_job_draw_offscreen_with_color (GskGLRenderJob *job,
1024 const graphene_rect_t *bounds,
1025 const GskGLRenderOffscreen *offscreen,
1026 guint16 color[4])
1027{
1028 float min_x = job->offset_x + bounds->origin.x;
1029 float min_y = job->offset_y + bounds->origin.y;
1030 float max_x = min_x + bounds->size.width;
1031 float max_y = min_y + bounds->size.height;
1032 float y1 = offscreen->was_offscreen ? offscreen->area.y2 : offscreen->area.y;
1033 float y2 = offscreen->was_offscreen ? offscreen->area.y : offscreen->area.y2;
1034
1035 gsk_gl_render_job_draw_coords (job,
1036 min_x, min_y, max_x, max_y,
1037 min_u: offscreen->area.x, min_v: y1, max_u: offscreen->area.x2, max_v: y2,
1038 c: color);
1039}
1040
1041static inline void
1042gsk_gl_render_job_draw_offscreen (GskGLRenderJob *job,
1043 const graphene_rect_t *bounds,
1044 const GskGLRenderOffscreen *offscreen)
1045{
1046 gsk_gl_render_job_draw_offscreen_with_color (job, bounds, offscreen,
1047 color: (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
1048}
1049
1050/* load_float_vertex_data */
1051static inline void
1052gsk_gl_render_job_draw_with_color (GskGLRenderJob *job,
1053 float x,
1054 float y,
1055 float width,
1056 float height,
1057 guint16 color[4])
1058{
1059 float min_x = job->offset_x + x;
1060 float min_y = job->offset_y + y;
1061 float max_x = min_x + width;
1062 float max_y = min_y + height;
1063
1064 gsk_gl_render_job_draw_coords (job, min_x, min_y, max_x, max_y, min_u: 0, min_v: 0, max_u: 1, max_v: 1, c: color);
1065}
1066
1067static inline void
1068gsk_gl_render_job_draw (GskGLRenderJob *job,
1069 float x,
1070 float y,
1071 float width,
1072 float height)
1073{
1074 gsk_gl_render_job_draw_with_color (job, x, y, width, height,
1075 color: (guint16[]) { FP_ZERO, FP_ZERO, FP_ZERO, FP_ZERO });
1076}
1077
1078/* load_vertex_data */
1079static inline void
1080gsk_gl_render_job_draw_rect_with_color (GskGLRenderJob *job,
1081 const graphene_rect_t *bounds,
1082 guint16 color[4])
1083{
1084 gsk_gl_render_job_draw_with_color (job,
1085 x: bounds->origin.x,
1086 y: bounds->origin.y,
1087 width: bounds->size.width,
1088 height: bounds->size.height,
1089 color);
1090}
1091static inline void
1092gsk_gl_render_job_draw_rect (GskGLRenderJob *job,
1093 const graphene_rect_t *bounds)
1094{
1095 gsk_gl_render_job_draw (job,
1096 x: bounds->origin.x,
1097 y: bounds->origin.y,
1098 width: bounds->size.width,
1099 height: bounds->size.height);
1100}
1101
1102/* load_offscreen_vertex_data */
1103static inline void
1104gsk_gl_render_job_draw_offscreen_rect (GskGLRenderJob *job,
1105 const graphene_rect_t *bounds)
1106{
1107 float min_x = job->offset_x + bounds->origin.x;
1108 float min_y = job->offset_y + bounds->origin.y;
1109 float max_x = min_x + bounds->size.width;
1110 float max_y = min_y + bounds->size.height;
1111 guint16 color[4] = { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO };
1112
1113 gsk_gl_render_job_draw_coords (job,
1114 min_x, min_y, max_x, max_y,
1115 min_u: 0, min_v: 1, max_u: 1, max_v: 0,
1116 c: color);
1117}
1118
1119static inline void
1120gsk_gl_render_job_begin_draw (GskGLRenderJob *job,
1121 GskGLProgram *program)
1122{
1123 job->current_program = program;
1124
1125 gsk_gl_command_queue_begin_draw (self: job->command_queue,
1126 program_info: program->program_info,
1127 width: job->viewport.size.width,
1128 height: job->viewport.size.height);
1129
1130 gsk_gl_uniform_state_set4fv (state: program->uniforms,
1131 program: program->program_info,
1132 key: UNIFORM_SHARED_VIEWPORT,
1133 stamp: job->driver->stamps[UNIFORM_SHARED_VIEWPORT],
1134 count: 1,
1135 value: (const float *)&job->viewport);
1136
1137 gsk_gl_uniform_state_set_matrix (state: program->uniforms,
1138 program: program->program_info,
1139 key: UNIFORM_SHARED_MODELVIEW,
1140 stamp: job->driver->stamps[UNIFORM_SHARED_MODELVIEW],
1141 matrix: &job->current_modelview->matrix);
1142
1143 gsk_gl_uniform_state_set_matrix (state: program->uniforms,
1144 program: program->program_info,
1145 key: UNIFORM_SHARED_PROJECTION,
1146 stamp: job->driver->stamps[UNIFORM_SHARED_PROJECTION],
1147 matrix: &job->projection);
1148
1149 gsk_gl_uniform_state_set_rounded_rect (state: program->uniforms,
1150 program: program->program_info,
1151 key: UNIFORM_SHARED_CLIP_RECT,
1152 stamp: job->driver->stamps[UNIFORM_SHARED_CLIP_RECT],
1153 rounded_rect: &job->current_clip->rect);
1154
1155 gsk_gl_uniform_state_set1f (state: program->uniforms,
1156 program: program->program_info,
1157 key: UNIFORM_SHARED_ALPHA,
1158 stamp: job->driver->stamps[UNIFORM_SHARED_ALPHA],
1159 value0: job->alpha);
1160}
1161
1162#define CHOOSE_PROGRAM(job,name) \
1163 (job->current_clip->is_fully_contained \
1164 ? job->driver->name ## _no_clip \
1165 : (job->current_clip->is_rectilinear \
1166 ? job->driver->name ## _rect_clip \
1167 : job->driver->name))
1168
1169static inline void
1170gsk_gl_render_job_split_draw (GskGLRenderJob *job)
1171{
1172 gsk_gl_command_queue_split_draw (self: job->command_queue);
1173}
1174
1175static inline void
1176gsk_gl_render_job_end_draw (GskGLRenderJob *job)
1177{
1178 gsk_gl_command_queue_end_draw (self: job->command_queue);
1179
1180 job->current_program = NULL;
1181}
1182
1183static inline void
1184gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job,
1185 const GskRenderNode *node)
1186{
1187 float scale_x = job->scale_x;
1188 float scale_y = job->scale_y;
1189 int surface_width = ceilf (x: node->bounds.size.width * scale_x);
1190 int surface_height = ceilf (x: node->bounds.size.height * scale_y);
1191 GdkTexture *texture;
1192 cairo_surface_t *surface;
1193 cairo_surface_t *rendered_surface;
1194 cairo_t *cr;
1195 int cached_id;
1196 int texture_id;
1197 GskTextureKey key;
1198
1199 if (surface_width <= 0 || surface_height <= 0)
1200 return;
1201
1202 key.pointer = node;
1203 key.pointer_is_child = FALSE;
1204 key.scale_x = scale_x;
1205 key.scale_y = scale_y;
1206 key.filter = GL_NEAREST;
1207
1208 cached_id = gsk_gl_driver_lookup_texture (self: job->driver, key: &key);
1209
1210 if (cached_id != 0)
1211 {
1212 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
1213 gsk_gl_program_set_uniform_texture (self: job->current_program,
1214 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1215 GL_TEXTURE_2D, GL_TEXTURE0, texture_id: cached_id);
1216 gsk_gl_render_job_draw_offscreen_rect (job, bounds: &node->bounds);
1217 gsk_gl_render_job_end_draw (job);
1218 return;
1219 }
1220
1221 /* We first draw the recording surface on an image surface,
1222 * just because the scaleY(-1) later otherwise screws up the
1223 * rendering... */
1224 {
1225 rendered_surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32,
1226 width: surface_width,
1227 height: surface_height);
1228
1229 cairo_surface_set_device_scale (surface: rendered_surface, x_scale: scale_x, y_scale: scale_y);
1230 cr = cairo_create (target: rendered_surface);
1231
1232 cairo_save (cr);
1233 cairo_translate (cr, tx: - floorf (x: node->bounds.origin.x), ty: - floorf (x: node->bounds.origin.y));
1234 /* Render nodes don't modify state, so casting away the const is fine here */
1235 gsk_render_node_draw (node: (GskRenderNode *)node, cr);
1236 cairo_restore (cr);
1237 cairo_destroy (cr);
1238 }
1239
1240 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32,
1241 width: surface_width,
1242 height: surface_height);
1243 cairo_surface_set_device_scale (surface, x_scale: scale_x, y_scale: scale_y);
1244 cr = cairo_create (target: surface);
1245
1246 /* We draw upside down here, so it matches what GL does. */
1247 cairo_save (cr);
1248 cairo_scale (cr, sx: 1, sy: -1);
1249 cairo_translate (cr, tx: 0, ty: - surface_height / scale_y);
1250 cairo_set_source_surface (cr, surface: rendered_surface, x: 0, y: 0);
1251 cairo_rectangle (cr, x: 0, y: 0, width: surface_width / scale_x, height: surface_height / scale_y);
1252 cairo_fill (cr);
1253 cairo_restore (cr);
1254
1255#ifdef G_ENABLE_DEBUG
1256 if (job->debug_fallback)
1257 {
1258 cairo_move_to (cr, x: 0, y: 0);
1259 cairo_rectangle (cr, x: 0, y: 0, width: node->bounds.size.width, height: node->bounds.size.height);
1260 if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
1261 cairo_set_source_rgba (cr, red: 0.3, green: 0, blue: 1, alpha: 0.25);
1262 else
1263 cairo_set_source_rgba (cr, red: 1, green: 0, blue: 0, alpha: 0.25);
1264 cairo_fill_preserve (cr);
1265 if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
1266 cairo_set_source_rgba (cr, red: 0.3, green: 0, blue: 1, alpha: 1);
1267 else
1268 cairo_set_source_rgba (cr, red: 1, green: 0, blue: 0, alpha: 1);
1269 cairo_stroke (cr);
1270 }
1271#endif
1272 cairo_destroy (cr);
1273
1274 /* Create texture to upload */
1275 texture = gdk_texture_new_for_surface (surface);
1276 texture_id = gsk_gl_driver_load_texture (self: job->driver, texture,
1277 GL_NEAREST, GL_NEAREST);
1278
1279 if (gdk_gl_context_has_debug (self: job->command_queue->context))
1280 gdk_gl_context_label_object_printf (context: job->command_queue->context, GL_TEXTURE, name: texture_id,
1281 format: "Fallback %s %d",
1282 g_type_name_from_instance (instance: (GTypeInstance *) node),
1283 texture_id);
1284
1285 g_object_unref (object: texture);
1286 cairo_surface_destroy (surface);
1287 cairo_surface_destroy (surface: rendered_surface);
1288
1289 gsk_gl_driver_cache_texture (self: job->driver, key: &key, texture_id);
1290
1291 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
1292 gsk_gl_program_set_uniform_texture (self: job->current_program,
1293 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1294 GL_TEXTURE_2D,
1295 GL_TEXTURE0,
1296 texture_id);
1297 gsk_gl_render_job_draw_offscreen_rect (job, bounds: &node->bounds);
1298 gsk_gl_render_job_end_draw (job);
1299}
1300
1301static guint
1302blur_offscreen (GskGLRenderJob *job,
1303 GskGLRenderOffscreen *offscreen,
1304 int texture_to_blur_width,
1305 int texture_to_blur_height,
1306 float blur_radius_x,
1307 float blur_radius_y)
1308{
1309 const GskRoundedRect new_clip = GSK_ROUNDED_RECT_INIT (0, 0, texture_to_blur_width, texture_to_blur_height);
1310 GskGLRenderTarget *pass1;
1311 GskGLRenderTarget *pass2;
1312 graphene_matrix_t prev_projection;
1313 graphene_rect_t prev_viewport;
1314 guint prev_fbo;
1315
1316 g_assert (blur_radius_x > 0);
1317 g_assert (blur_radius_y > 0);
1318 g_assert (offscreen->texture_id > 0);
1319 g_assert (offscreen->area.x2 > offscreen->area.x);
1320 g_assert (offscreen->area.y2 > offscreen->area.y);
1321
1322 if (!gsk_gl_driver_create_render_target (self: job->driver,
1323 MAX (texture_to_blur_width, 1),
1324 MAX (texture_to_blur_height, 1),
1325 format: job->target_format,
1326 GL_NEAREST, GL_NEAREST,
1327 render_target: &pass1))
1328 return 0;
1329
1330 if (texture_to_blur_width <= 0 || texture_to_blur_height <= 0)
1331 return gsk_gl_driver_release_render_target (self: job->driver, render_target: pass1, FALSE);
1332
1333 if (!gsk_gl_driver_create_render_target (self: job->driver,
1334 width: texture_to_blur_width,
1335 height: texture_to_blur_height,
1336 format: job->target_format,
1337 GL_NEAREST, GL_NEAREST,
1338 render_target: &pass2))
1339 return gsk_gl_driver_release_render_target (self: job->driver, render_target: pass1, FALSE);
1340
1341 gsk_gl_render_job_set_viewport (job, viewport: &new_clip.bounds, prev_viewport: &prev_viewport);
1342 gsk_gl_render_job_set_projection_from_rect (job, rect: &new_clip.bounds, prev_projection: &prev_projection);
1343 gsk_gl_render_job_set_modelview (job, NULL);
1344 gsk_gl_render_job_push_clip (job, rect: &new_clip);
1345
1346 /* Bind new framebuffer and clear it */
1347 prev_fbo = gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: pass1->framebuffer_id);
1348 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
1349
1350 /* Begin drawing the first horizontal pass, using offscreen as the
1351 * source texture for the program.
1352 */
1353 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
1354 gsk_gl_program_set_uniform_texture (self: job->current_program,
1355 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1356 GL_TEXTURE_2D,
1357 GL_TEXTURE0,
1358 texture_id: offscreen->texture_id);
1359 gsk_gl_program_set_uniform1f (self: job->current_program,
1360 key: UNIFORM_BLUR_RADIUS, stamp: 0,
1361 value0: blur_radius_x);
1362 gsk_gl_program_set_uniform2f (self: job->current_program,
1363 key: UNIFORM_BLUR_SIZE, stamp: 0,
1364 value0: texture_to_blur_width,
1365 value1: texture_to_blur_height);
1366 gsk_gl_program_set_uniform2f (self: job->current_program,
1367 key: UNIFORM_BLUR_DIR, stamp: 0,
1368 value0: 1, value1: 0);
1369 gsk_gl_render_job_draw_coords (job,
1370 min_x: 0, min_y: 0, max_x: texture_to_blur_width, max_y: texture_to_blur_height,
1371 min_u: 0, min_v: 1, max_u: 1, max_v: 0,
1372 c: (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
1373 gsk_gl_render_job_end_draw (job);
1374
1375 /* Bind second pass framebuffer and clear it */
1376 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: pass2->framebuffer_id);
1377 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
1378
1379 /* Draw using blur program with first pass as source texture */
1380 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
1381 gsk_gl_program_set_uniform_texture (self: job->current_program,
1382 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1383 GL_TEXTURE_2D,
1384 GL_TEXTURE0,
1385 texture_id: pass1->texture_id);
1386 gsk_gl_program_set_uniform1f (self: job->current_program,
1387 key: UNIFORM_BLUR_RADIUS, stamp: 0,
1388 value0: blur_radius_y);
1389 gsk_gl_program_set_uniform2f (self: job->current_program,
1390 key: UNIFORM_BLUR_SIZE, stamp: 0,
1391 value0: texture_to_blur_width,
1392 value1: texture_to_blur_height);
1393 gsk_gl_program_set_uniform2f (self: job->current_program,
1394 key: UNIFORM_BLUR_DIR, stamp: 0,
1395 value0: 0, value1: 1);
1396 gsk_gl_render_job_draw_coords (job,
1397 min_x: 0, min_y: 0, max_x: texture_to_blur_width, max_y: texture_to_blur_height,
1398 min_u: 0, min_v: 1, max_u: 1, max_v: 0,
1399 c: (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
1400 gsk_gl_render_job_end_draw (job);
1401
1402 gsk_gl_render_job_pop_modelview (job);
1403 gsk_gl_render_job_pop_clip (job);
1404 gsk_gl_render_job_set_viewport (job, viewport: &prev_viewport, NULL);
1405 gsk_gl_render_job_set_projection (job, projection: &prev_projection);
1406 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: prev_fbo);
1407
1408 gsk_gl_driver_release_render_target (self: job->driver, render_target: pass1, TRUE);
1409
1410 return gsk_gl_driver_release_render_target (self: job->driver, render_target: pass2, FALSE);
1411}
1412
1413static void
1414blur_node (GskGLRenderJob *job,
1415 GskGLRenderOffscreen *offscreen,
1416 const GskRenderNode *node,
1417 float blur_radius,
1418 float *min_x,
1419 float *max_x,
1420 float *min_y,
1421 float *max_y)
1422{
1423 const float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
1424 const float half_blur_extra = (blur_extra / 2.0);
1425 float scale_x = job->scale_x;
1426 float scale_y = job->scale_y;
1427 float texture_width;
1428 float texture_height;
1429
1430 g_assert (blur_radius > 0);
1431
1432 /* Increase texture size for the given blur radius and scale it */
1433 texture_width = ceilf (x: (node->bounds.size.width + blur_extra));
1434 texture_height = ceilf (x: (node->bounds.size.height + blur_extra));
1435
1436 /* Only blur this if the out region has no texture id yet */
1437 if (offscreen->texture_id == 0)
1438 {
1439 const graphene_rect_t bounds = GRAPHENE_RECT_INIT (node->bounds.origin.x - half_blur_extra,
1440 node->bounds.origin.y - half_blur_extra,
1441 texture_width, texture_height);
1442
1443 offscreen->bounds = &bounds;
1444 offscreen->reset_clip = TRUE;
1445 offscreen->force_offscreen = TRUE;
1446
1447 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node, offscreen))
1448 g_assert_not_reached ();
1449
1450 /* Ensure that we actually got a real texture_id */
1451 g_assert (offscreen->texture_id != 0);
1452
1453 offscreen->texture_id = blur_offscreen (job,
1454 offscreen,
1455 texture_to_blur_width: texture_width * scale_x,
1456 texture_to_blur_height: texture_height * scale_y,
1457 blur_radius_x: blur_radius * scale_x,
1458 blur_radius_y: blur_radius * scale_y);
1459 init_full_texture_region (offscreen);
1460 }
1461
1462 *min_x = job->offset_x + node->bounds.origin.x - half_blur_extra;
1463 *max_x = job->offset_x + node->bounds.origin.x + node->bounds.size.width + half_blur_extra;
1464 *min_y = job->offset_y + node->bounds.origin.y - half_blur_extra;
1465 *max_y = job->offset_y + node->bounds.origin.y + node->bounds.size.height + half_blur_extra;
1466}
1467
1468#define ATLAS_SIZE 512
1469
1470static inline void
1471gsk_gl_render_job_visit_color_node (GskGLRenderJob *job,
1472 const GskRenderNode *node)
1473{
1474 const GdkRGBA *rgba;
1475 guint16 color[4];
1476 GskGLProgram *program;
1477 GskGLCommandBatch *batch;
1478
1479 rgba = gsk_color_node_get_color (node);
1480 if (RGBA_IS_CLEAR (rgba))
1481 return;
1482
1483 rgba_to_half (rgba, h: color);
1484
1485 /* Avoid switching away from the coloring program for
1486 * rendering a solid color.
1487 */
1488 program = CHOOSE_PROGRAM (job, coloring);
1489 batch = gsk_gl_command_queue_get_batch (self: job->command_queue);
1490
1491 /* Limit the size, or we end up with a coordinate overflow somwhere. */
1492 if (node->bounds.size.width < 300 &&
1493 node->bounds.size.height < 300 &&
1494 batch->any.kind == GSK_GL_COMMAND_KIND_DRAW &&
1495 batch->any.program == program->id)
1496 {
1497 GskGLRenderOffscreen offscreen = {0};
1498
1499 gsk_gl_render_job_begin_draw (job, program);
1500
1501 /* The top left few pixels in our atlases are always
1502 * solid white, so we can use it here, without
1503 * having to choose any particular atlas texture.
1504 */
1505 offscreen.was_offscreen = FALSE;
1506 offscreen.area.x = 1.f / ATLAS_SIZE;
1507 offscreen.area.y = 1.f / ATLAS_SIZE;
1508 offscreen.area.x2 = 2.f / ATLAS_SIZE;
1509 offscreen.area.y2 = 2.f / ATLAS_SIZE;
1510
1511 gsk_gl_render_job_draw_offscreen_with_color (job,
1512 bounds: &node->bounds,
1513 offscreen: &offscreen,
1514 color);
1515
1516 gsk_gl_render_job_end_draw (job);
1517 }
1518 else
1519 {
1520 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
1521 gsk_gl_render_job_draw_rect_with_color (job, bounds: &node->bounds, color);
1522 gsk_gl_render_job_end_draw (job);
1523 }
1524}
1525
1526static inline void
1527gsk_gl_render_job_visit_linear_gradient_node (GskGLRenderJob *job,
1528 const GskRenderNode *node)
1529{
1530 const GskColorStop *stops = gsk_linear_gradient_node_get_color_stops (node, NULL);
1531 const graphene_point_t *start = gsk_linear_gradient_node_get_start (node);
1532 const graphene_point_t *end = gsk_linear_gradient_node_get_end (node);
1533 int n_color_stops = gsk_linear_gradient_node_get_n_color_stops (node);
1534 gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE;
1535 float x1 = job->offset_x + start->x;
1536 float x2 = job->offset_x + end->x;
1537 float y1 = job->offset_y + start->y;
1538 float y2 = job->offset_y + end->y;
1539
1540 g_assert (n_color_stops < MAX_GRADIENT_STOPS);
1541
1542 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, linear_gradient));
1543 gsk_gl_program_set_uniform1i (self: job->current_program,
1544 key: UNIFORM_LINEAR_GRADIENT_NUM_COLOR_STOPS, stamp: 0,
1545 value0: n_color_stops);
1546 gsk_gl_program_set_uniform1fv (self: job->current_program,
1547 key: UNIFORM_LINEAR_GRADIENT_COLOR_STOPS, stamp: 0,
1548 count: n_color_stops * 5,
1549 values: (const float *)stops);
1550 gsk_gl_program_set_uniform4f (self: job->current_program,
1551 key: UNIFORM_LINEAR_GRADIENT_POINTS, stamp: 0,
1552 value0: x1, value1: y1, value2: x2 - x1, value3: y2 - y1);
1553 gsk_gl_program_set_uniform1i (self: job->current_program,
1554 key: UNIFORM_LINEAR_GRADIENT_REPEAT, stamp: 0,
1555 value0: repeat);
1556 gsk_gl_render_job_draw_rect (job, bounds: &node->bounds);
1557 gsk_gl_render_job_end_draw (job);
1558}
1559
1560static inline void
1561gsk_gl_render_job_visit_conic_gradient_node (GskGLRenderJob *job,
1562 const GskRenderNode *node)
1563{
1564 static const float scale = 0.5f * M_1_PI;
1565
1566 const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, NULL);
1567 const graphene_point_t *center = gsk_conic_gradient_node_get_center (node);
1568 int n_color_stops = gsk_conic_gradient_node_get_n_color_stops (node);
1569 float angle = gsk_conic_gradient_node_get_angle (node);
1570 float bias = angle * scale + 2.0f;
1571
1572 g_assert (n_color_stops < MAX_GRADIENT_STOPS);
1573
1574 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, conic_gradient));
1575 gsk_gl_program_set_uniform1i (self: job->current_program,
1576 key: UNIFORM_CONIC_GRADIENT_NUM_COLOR_STOPS, stamp: 0,
1577 value0: n_color_stops);
1578 gsk_gl_program_set_uniform1fv (self: job->current_program,
1579 key: UNIFORM_CONIC_GRADIENT_COLOR_STOPS, stamp: 0,
1580 count: n_color_stops * 5,
1581 values: (const float *)stops);
1582 gsk_gl_program_set_uniform4f (self: job->current_program,
1583 key: UNIFORM_CONIC_GRADIENT_GEOMETRY, stamp: 0,
1584 value0: job->offset_x + center->x,
1585 value1: job->offset_y + center->y,
1586 value2: scale,
1587 value3: bias);
1588 gsk_gl_render_job_draw_rect (job, bounds: &node->bounds);
1589 gsk_gl_render_job_end_draw (job);
1590}
1591
1592static inline void
1593gsk_gl_render_job_visit_radial_gradient_node (GskGLRenderJob *job,
1594 const GskRenderNode *node)
1595{
1596 int n_color_stops = gsk_radial_gradient_node_get_n_color_stops (node);
1597 const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, NULL);
1598 const graphene_point_t *center = gsk_radial_gradient_node_get_center (node);
1599 float start = gsk_radial_gradient_node_get_start (node);
1600 float end = gsk_radial_gradient_node_get_end (node);
1601 float hradius = gsk_radial_gradient_node_get_hradius (node);
1602 float vradius = gsk_radial_gradient_node_get_vradius (node);
1603 gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE;
1604 float scale = 1.0f / (end - start);
1605 float bias = -start * scale;
1606
1607 g_assert (n_color_stops < MAX_GRADIENT_STOPS);
1608
1609 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, radial_gradient));
1610 gsk_gl_program_set_uniform1i (self: job->current_program,
1611 key: UNIFORM_RADIAL_GRADIENT_NUM_COLOR_STOPS, stamp: 0,
1612 value0: n_color_stops);
1613 gsk_gl_program_set_uniform1fv (self: job->current_program,
1614 key: UNIFORM_RADIAL_GRADIENT_COLOR_STOPS, stamp: 0,
1615 count: n_color_stops * 5,
1616 values: (const float *)stops);
1617 gsk_gl_program_set_uniform1i (self: job->current_program,
1618 key: UNIFORM_RADIAL_GRADIENT_REPEAT, stamp: 0,
1619 value0: repeat);
1620 gsk_gl_program_set_uniform2f (self: job->current_program,
1621 key: UNIFORM_RADIAL_GRADIENT_RANGE, stamp: 0,
1622 value0: scale, value1: bias);
1623 gsk_gl_program_set_uniform4f (self: job->current_program,
1624 key: UNIFORM_RADIAL_GRADIENT_GEOMETRY, stamp: 0,
1625 value0: job->offset_x + center->x,
1626 value1: job->offset_y + center->y,
1627 value2: 1.0f / (hradius * job->scale_x),
1628 value3: 1.0f / (vradius * job->scale_y));
1629 gsk_gl_render_job_draw_rect (job, bounds: &node->bounds);
1630 gsk_gl_render_job_end_draw (job);
1631}
1632
1633static inline void
1634gsk_gl_render_job_visit_clipped_child (GskGLRenderJob *job,
1635 const GskRenderNode *child,
1636 const graphene_rect_t *clip)
1637{
1638 graphene_rect_t transformed_clip;
1639 GskRoundedRect intersection;
1640
1641 gsk_gl_render_job_transform_bounds (job, rect: clip, out_rect: &transformed_clip);
1642
1643 if (job->current_clip->is_rectilinear)
1644 {
1645 memset (s: &intersection.corner, c: 0, n: sizeof intersection.corner);
1646 graphene_rect_intersection (a: &transformed_clip,
1647 b: &job->current_clip->rect.bounds,
1648 res: &intersection.bounds);
1649
1650 gsk_gl_render_job_push_clip (job, rect: &intersection);
1651 gsk_gl_render_job_visit_node (job, node: child);
1652 gsk_gl_render_job_pop_clip (job);
1653 }
1654 else if (intersect_rounded_rectilinear (non_rounded: &transformed_clip,
1655 rounded: &job->current_clip->rect,
1656 result: &intersection))
1657 {
1658 gsk_gl_render_job_push_clip (job, rect: &intersection);
1659 gsk_gl_render_job_visit_node (job, node: child);
1660 gsk_gl_render_job_pop_clip (job);
1661 }
1662 else
1663 {
1664 GskGLRenderOffscreen offscreen = {0};
1665
1666 offscreen.bounds = clip;
1667 offscreen.force_offscreen = TRUE;
1668 offscreen.reset_clip = TRUE;
1669 offscreen.do_not_cache = TRUE;
1670
1671 gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen);
1672
1673 g_assert (offscreen.texture_id);
1674
1675 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
1676 gsk_gl_program_set_uniform_texture (self: job->current_program,
1677 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1678 GL_TEXTURE_2D,
1679 GL_TEXTURE0,
1680 texture_id: offscreen.texture_id);
1681 gsk_gl_render_job_draw_offscreen_rect (job, bounds: clip);
1682 gsk_gl_render_job_end_draw (job);
1683 }
1684}
1685
1686static inline void
1687gsk_gl_render_job_visit_clip_node (GskGLRenderJob *job,
1688 const GskRenderNode *node)
1689{
1690 const graphene_rect_t *clip = gsk_clip_node_get_clip (node);
1691 const GskRenderNode *child = gsk_clip_node_get_child (node);
1692
1693 gsk_gl_render_job_visit_clipped_child (job, child, clip);
1694}
1695
1696static inline void
1697gsk_gl_render_job_visit_rounded_clip_node (GskGLRenderJob *job,
1698 const GskRenderNode *node)
1699{
1700 const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
1701 const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node);
1702 GskRoundedRect transformed_clip;
1703 float scale_x = job->scale_x;
1704 float scale_y = job->scale_y;
1705 gboolean need_offscreen;
1706
1707 if (node_is_invisible (node: child))
1708 return;
1709
1710 gsk_gl_render_job_transform_bounds (job, rect: &clip->bounds, out_rect: &transformed_clip.bounds);
1711
1712 for (guint i = 0; i < G_N_ELEMENTS (transformed_clip.corner); i++)
1713 {
1714 transformed_clip.corner[i].width = clip->corner[i].width * scale_x;
1715 transformed_clip.corner[i].height = clip->corner[i].height * scale_y;
1716 }
1717
1718 if (job->current_clip->is_rectilinear)
1719 {
1720 GskRoundedRect intersected_clip;
1721
1722 if (intersect_rounded_rectilinear (non_rounded: &job->current_clip->rect.bounds,
1723 rounded: &transformed_clip,
1724 result: &intersected_clip))
1725 {
1726 gsk_gl_render_job_push_clip (job, rect: &intersected_clip);
1727 gsk_gl_render_job_visit_node (job, node: child);
1728 gsk_gl_render_job_pop_clip (job);
1729 return;
1730 }
1731 }
1732
1733 /* After this point we are really working with a new and a current clip
1734 * which both have rounded corners.
1735 */
1736
1737 if (job->clip->len <= 1)
1738 need_offscreen = FALSE;
1739 else if (rounded_inner_rect_contains_rect (rounded: &job->current_clip->rect, rect: &transformed_clip.bounds))
1740 need_offscreen = FALSE;
1741 else
1742 need_offscreen = TRUE;
1743
1744 if (!need_offscreen)
1745 {
1746 /* If the new clip entirely contains the current clip, the intersection is simply
1747 * the current clip, so we can ignore the new one.
1748 */
1749 if (rounded_inner_rect_contains_rect (rounded: &transformed_clip, rect: &job->current_clip->rect.bounds))
1750 {
1751 gsk_gl_render_job_visit_node (job, node: child);
1752 return;
1753 }
1754
1755 gsk_gl_render_job_push_clip (job, rect: &transformed_clip);
1756 gsk_gl_render_job_visit_node (job, node: child);
1757 gsk_gl_render_job_pop_clip (job);
1758 }
1759 else
1760 {
1761 GskGLRenderOffscreen offscreen = {0};
1762
1763 offscreen.bounds = &node->bounds;
1764 offscreen.force_offscreen = TRUE;
1765 offscreen.reset_clip = FALSE;
1766
1767 gsk_gl_render_job_push_clip (job, rect: &transformed_clip);
1768 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen))
1769 g_assert_not_reached ();
1770 gsk_gl_render_job_pop_clip (job);
1771
1772 g_assert (offscreen.texture_id);
1773
1774 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
1775 gsk_gl_program_set_uniform_texture (self: job->current_program,
1776 key: UNIFORM_SHARED_SOURCE, stamp: 0,
1777 GL_TEXTURE_2D,
1778 GL_TEXTURE0,
1779 texture_id: offscreen.texture_id);
1780 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
1781 gsk_gl_render_job_end_draw (job);
1782 }
1783}
1784
1785static inline void
1786gsk_gl_render_job_visit_rect_border_node (GskGLRenderJob *job,
1787 const GskRenderNode *node)
1788{
1789 const GdkRGBA *colors = gsk_border_node_get_colors (node);
1790 const float *widths = gsk_border_node_get_widths (node);
1791 const graphene_point_t *origin = &node->bounds.origin;
1792 const graphene_size_t *size = &node->bounds.size;
1793 guint16 color[4];
1794
1795 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
1796
1797 if (widths[0] > 0)
1798 {
1799 rgba_to_half (rgba: &colors[0], h: color);
1800 gsk_gl_render_job_draw_rect_with_color (job,
1801 bounds: &GRAPHENE_RECT_INIT (origin->x, origin->y, size->width - widths[1], widths[0]),
1802 color);
1803 }
1804
1805 if (widths[1] > 0)
1806 {
1807 rgba_to_half (rgba: &colors[1], h: color);
1808 gsk_gl_render_job_draw_rect_with_color (job,
1809 bounds: &GRAPHENE_RECT_INIT (origin->x + size->width - widths[1], origin->y, widths[1], size->height - widths[2]),
1810 color);
1811 }
1812
1813 if (widths[2] > 0)
1814 {
1815 rgba_to_half (rgba: &colors[2], h: color);
1816 gsk_gl_render_job_draw_rect_with_color (job,
1817 bounds: &GRAPHENE_RECT_INIT (origin->x + widths[3], origin->y + size->height - widths[2], size->width - widths[3], widths[2]),
1818 color);
1819 }
1820
1821 if (widths[3] > 0)
1822 {
1823 rgba_to_half (rgba: &colors[3], h: color);
1824 gsk_gl_render_job_draw_rect_with_color (job,
1825 bounds: &GRAPHENE_RECT_INIT (origin->x, origin->y + widths[0], widths[3], size->height - widths[0]),
1826 color);
1827 }
1828
1829 gsk_gl_render_job_end_draw (job);
1830}
1831
1832static inline void
1833gsk_gl_render_job_visit_border_node (GskGLRenderJob *job,
1834 const GskRenderNode *node)
1835{
1836 const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node);
1837 const GdkRGBA *colors = gsk_border_node_get_colors (node);
1838 const float *widths = gsk_border_node_get_widths (node);
1839 struct {
1840 float w;
1841 float h;
1842 } sizes[4];
1843 float min_x = job->offset_x + node->bounds.origin.x;
1844 float min_y = job->offset_y + node->bounds.origin.y;
1845 float max_x = min_x + node->bounds.size.width;
1846 float max_y = min_y + node->bounds.size.height;
1847 GskRoundedRect outline;
1848 guint16 color[4];
1849
1850 memset (s: sizes, c: 0, n: sizeof sizes);
1851
1852 if (widths[0] > 0)
1853 {
1854 sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height);
1855 sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height);
1856 }
1857
1858 if (widths[1] > 0)
1859 {
1860 sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width);
1861 sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width);
1862 }
1863
1864 if (widths[2] > 0)
1865 {
1866 sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height);
1867 sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height);
1868 }
1869
1870 if (widths[3] > 0)
1871 {
1872 sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width);
1873 sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width);
1874 }
1875
1876 gsk_gl_render_job_transform_rounded_rect (job, rect: rounded_outline, out_rect: &outline);
1877
1878 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, border));
1879
1880 gsk_gl_program_set_uniform4fv (self: job->current_program,
1881 key: UNIFORM_BORDER_WIDTHS, stamp: 0,
1882 count: 1,
1883 values: widths);
1884 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
1885 key: UNIFORM_BORDER_OUTLINE_RECT, stamp: 0,
1886 rounded_rect: &outline);
1887
1888 if (widths[0] > 0)
1889 {
1890 GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1891
1892 rgba_to_half (rgba: &colors[0], h: color);
1893
1894 vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1895 vertices[1] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1896 vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1897
1898 vertices[3] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1899 vertices[4] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1900 vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1901 }
1902
1903 if (widths[1] > 0)
1904 {
1905 GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1906
1907 rgba_to_half (rgba: &colors[1], h: color);
1908
1909 vertices[0] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1910 vertices[1] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1911 vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1912
1913 vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1914 vertices[4] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1915 vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1916 }
1917
1918 if (widths[2] > 0)
1919 {
1920 GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1921
1922 rgba_to_half (rgba: &colors[2], h: color);
1923
1924 vertices[0] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1925 vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1926 vertices[2] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1927
1928 vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1929 vertices[4] = (GskGLDrawVertex) { .position = { min_x , max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1930 vertices[5] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1931 }
1932
1933 if (widths[3] > 0)
1934 {
1935 GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1936
1937 rgba_to_half (rgba: &colors[3], h: color);
1938
1939 vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1940 vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1941 vertices[2] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1942
1943 vertices[3] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1944 vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
1945 vertices[5] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
1946 }
1947
1948 gsk_gl_render_job_end_draw (job);
1949}
1950
1951/* A special case for a pattern that occurs frequently with CSS
1952 * backgrounds: two sibling nodes, the first of which is a rounded
1953 * clip node with a color node as child, and the second one is a
1954 * border node, with the same outline as the clip node. We render
1955 * this using the filled_border shader.
1956 */
1957static void
1958gsk_gl_render_job_visit_css_background (GskGLRenderJob *job,
1959 const GskRenderNode *node,
1960 const GskRenderNode *node2)
1961{
1962 const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
1963 const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node: node2);
1964 const float *widths = gsk_border_node_get_widths (node: node2);
1965 float min_x = job->offset_x + node2->bounds.origin.x;
1966 float min_y = job->offset_y + node2->bounds.origin.y;
1967 float max_x = min_x + node2->bounds.size.width;
1968 float max_y = min_y + node2->bounds.size.height;
1969 GskRoundedRect outline;
1970 GskGLDrawVertex *vertices;
1971 guint16 color[4];
1972 guint16 color2[4];
1973
1974 if (node_is_invisible (node: node2))
1975 return;
1976
1977 rgba_to_half (rgba: &gsk_border_node_get_colors (node: node2)[0], h: color);
1978 rgba_to_half (rgba: gsk_color_node_get_color (node: child), h: color2);
1979
1980 gsk_gl_render_job_transform_rounded_rect (job, rect: rounded_outline, out_rect: &outline);
1981
1982 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border));
1983
1984 gsk_gl_program_set_uniform4fv (self: job->current_program,
1985 key: UNIFORM_FILLED_BORDER_WIDTHS, stamp: 0,
1986 count: 1,
1987 values: widths);
1988 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
1989 key: UNIFORM_FILLED_BORDER_OUTLINE_RECT, stamp: 0,
1990 rounded_rect: &outline);
1991
1992 vertices = gsk_gl_command_queue_add_vertices (self: job->command_queue);
1993
1994 vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
1995 vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
1996 vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
1997 vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
1998 vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
1999 vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
2000
2001 gsk_gl_render_job_end_draw (job);
2002}
2003
2004/* Returns TRUE if applying @transform to @bounds
2005 * yields an axis-aligned rectangle
2006 */
2007static gboolean
2008result_is_axis_aligned (GskTransform *transform,
2009 const graphene_rect_t *bounds)
2010{
2011 graphene_matrix_t m;
2012 graphene_quad_t q;
2013 graphene_rect_t b;
2014 graphene_point_t b1, b2;
2015 const graphene_point_t *p;
2016
2017 gsk_transform_to_matrix (self: transform, out_matrix: &m);
2018 gsk_matrix_transform_rect (m: &m, r: bounds, res: &q);
2019 graphene_quad_bounds (q: &q, r: &b);
2020 graphene_rect_get_top_left (r: &b, p: &b1);
2021 graphene_rect_get_bottom_right (r: &b, p: &b2);
2022
2023 for (guint i = 0; i < 4; i++)
2024 {
2025 p = graphene_quad_get_point (q: &q, index_: i);
2026 if (fabs (x: p->x - b1.x) > FLT_EPSILON && fabs (x: p->x - b2.x) > FLT_EPSILON)
2027 return FALSE;
2028 if (fabs (x: p->y - b1.y) > FLT_EPSILON && fabs (x: p->y - b2.y) > FLT_EPSILON)
2029 return FALSE;
2030 }
2031
2032 return TRUE;
2033}
2034
2035static inline void
2036gsk_gl_render_job_visit_transform_node (GskGLRenderJob *job,
2037 const GskRenderNode *node)
2038{
2039 GskTransform *transform = gsk_transform_node_get_transform (node);
2040 const GskTransformCategory category = gsk_transform_get_category (transform);
2041 const GskRenderNode *child = gsk_transform_node_get_child (node);
2042
2043 switch (category)
2044 {
2045 case GSK_TRANSFORM_CATEGORY_IDENTITY:
2046 gsk_gl_render_job_visit_node (job, node: child);
2047 break;
2048
2049 case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
2050 {
2051 float dx, dy;
2052
2053 gsk_transform_node_get_translate (node, dx: &dx, dy: &dy);
2054 gsk_gl_render_job_offset (job, offset_x: dx, offset_y: dy);
2055 gsk_gl_render_job_visit_node (job, node: child);
2056 gsk_gl_render_job_offset (job, offset_x: -dx, offset_y: -dy);
2057 }
2058 break;
2059
2060 case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
2061 {
2062 gsk_gl_render_job_push_modelview (job, transform);
2063 gsk_gl_render_job_visit_node (job, node: child);
2064 gsk_gl_render_job_pop_modelview (job);
2065 }
2066 break;
2067
2068 case GSK_TRANSFORM_CATEGORY_2D:
2069 if (node_supports_2d_transform (node: child))
2070 {
2071 gsk_gl_render_job_push_modelview (job, transform);
2072 gsk_gl_render_job_visit_node (job, node: child);
2073 gsk_gl_render_job_pop_modelview (job);
2074 return;
2075 }
2076 G_GNUC_FALLTHROUGH;
2077 case GSK_TRANSFORM_CATEGORY_3D:
2078 case GSK_TRANSFORM_CATEGORY_ANY:
2079 case GSK_TRANSFORM_CATEGORY_UNKNOWN:
2080 if (node_supports_transform (node: child))
2081 {
2082 gsk_gl_render_job_push_modelview (job, transform);
2083 gsk_gl_render_job_visit_node (job, node: child);
2084 gsk_gl_render_job_pop_modelview (job);
2085 }
2086 else
2087 {
2088 GskGLRenderOffscreen offscreen = {0};
2089 float sx = 1, sy = 1;
2090
2091 offscreen.bounds = &child->bounds;
2092 offscreen.force_offscreen = FALSE;
2093 offscreen.reset_clip = TRUE;
2094
2095 if (!result_is_axis_aligned (transform, bounds: &child->bounds))
2096 offscreen.linear_filter = TRUE;
2097
2098 if (category == GSK_TRANSFORM_CATEGORY_2D)
2099 {
2100 graphene_matrix_t m;
2101 double a, b, c, d, tx, ty;
2102
2103 g_assert (transform != NULL);
2104 gsk_transform_to_matrix (self: transform, out_matrix: &m);
2105 if (graphene_matrix_to_2d (m: &m, xx: &a, yx: &b, xy: &c, yy: &d, x_0: &tx, y_0: &ty))
2106 {
2107 sx = sqrt (x: a * a + b * b);
2108 sy = sqrt (x: c * c + d * d);
2109 }
2110 else
2111 sx = sy = 1;
2112
2113 if (sx != 1 || sy != 1)
2114 {
2115 GskTransform *scale;
2116
2117 scale = gsk_transform_translate (next: gsk_transform_scale (NULL, factor_x: sx, factor_y: sy), point: &GRAPHENE_POINT_INIT (tx, ty));
2118 gsk_gl_render_job_push_modelview (job, transform: scale);
2119 transform = gsk_transform_transform (next: gsk_transform_invert (self: scale), other: transform);
2120 }
2121 }
2122
2123 if (gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen))
2124 {
2125 /* For non-trivial transforms, we draw everything on a texture and then
2126 * draw the texture transformed. */
2127 if (transform)
2128 gsk_gl_render_job_push_modelview (job, transform);
2129
2130 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
2131 gsk_gl_program_set_uniform_texture (self: job->current_program,
2132 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2133 GL_TEXTURE_2D,
2134 GL_TEXTURE0,
2135 texture_id: offscreen.texture_id);
2136 gsk_gl_render_job_draw_offscreen (job, bounds: &child->bounds, offscreen: &offscreen);
2137 gsk_gl_render_job_end_draw (job);
2138
2139 if (transform)
2140 gsk_gl_render_job_pop_modelview (job);
2141 }
2142
2143 if (category == GSK_TRANSFORM_CATEGORY_2D)
2144 {
2145 if (sx != 1 || sy != 1)
2146 {
2147 gsk_gl_render_job_pop_modelview (job);
2148 gsk_transform_unref (self: transform);
2149 }
2150 }
2151 }
2152 break;
2153
2154 default:
2155 g_assert_not_reached ();
2156 }
2157}
2158
2159static inline void
2160gsk_gl_render_job_visit_unblurred_inset_shadow_node (GskGLRenderJob *job,
2161 const GskRenderNode *node)
2162{
2163 const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node);
2164 GskRoundedRect transformed_outline;
2165 guint16 color[4];
2166
2167 gsk_gl_render_job_transform_rounded_rect (job, rect: outline, out_rect: &transformed_outline);
2168
2169 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
2170 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
2171 key: UNIFORM_INSET_SHADOW_OUTLINE_RECT, stamp: 0,
2172 rounded_rect: &transformed_outline);
2173 gsk_gl_program_set_uniform1f (self: job->current_program,
2174 key: UNIFORM_INSET_SHADOW_SPREAD, stamp: 0,
2175 value0: gsk_inset_shadow_node_get_spread (node));
2176 gsk_gl_program_set_uniform2f (self: job->current_program,
2177 key: UNIFORM_INSET_SHADOW_OFFSET, stamp: 0,
2178 value0: gsk_inset_shadow_node_get_dx (node),
2179 value1: gsk_inset_shadow_node_get_dy (node));
2180 rgba_to_half (rgba: gsk_inset_shadow_node_get_color (node), h: color);
2181 gsk_gl_render_job_draw_rect_with_color (job, bounds: &node->bounds, color);
2182 gsk_gl_render_job_end_draw (job);
2183}
2184
2185static inline void
2186gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob *job,
2187 const GskRenderNode *node)
2188{
2189 const GskRoundedRect *node_outline = gsk_inset_shadow_node_get_outline (node);
2190 float blur_radius = gsk_inset_shadow_node_get_blur_radius (node);
2191 float offset_x = gsk_inset_shadow_node_get_dx (node);
2192 float offset_y = gsk_inset_shadow_node_get_dy (node);
2193 float scale_x = job->scale_x;
2194 float scale_y = job->scale_y;
2195 float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
2196 float half_blur_extra = blur_radius;
2197 float texture_width;
2198 float texture_height;
2199 int blurred_texture_id;
2200 GskTextureKey key;
2201 GskGLRenderOffscreen offscreen = {0};
2202 guint16 color[4];
2203
2204 g_assert (blur_radius > 0);
2205
2206 texture_width = ceilf (x: (node_outline->bounds.size.width + blur_extra) * scale_x);
2207 texture_height = ceilf (x: (node_outline->bounds.size.height + blur_extra) * scale_y);
2208
2209 key.pointer = node;
2210 key.pointer_is_child = FALSE;
2211 key.scale_x = scale_x;
2212 key.scale_y = scale_y;
2213 key.filter = GL_NEAREST;
2214
2215 blurred_texture_id = gsk_gl_driver_lookup_texture (self: job->driver, key: &key);
2216
2217 if (blurred_texture_id == 0)
2218 {
2219 float spread = gsk_inset_shadow_node_get_spread (node) + half_blur_extra;
2220 GskRoundedRect transformed_outline;
2221 GskRoundedRect outline_to_blur;
2222 GskGLRenderTarget *render_target;
2223 graphene_matrix_t prev_projection;
2224 graphene_rect_t prev_viewport;
2225 guint prev_fbo;
2226
2227 /* TODO: In the following code, we have to be careful about where we apply the scale.
2228 * We're manually scaling stuff (e.g. the outline) so we can later use texture_width
2229 * and texture_height (which are already scaled) as the geometry and keep the modelview
2230 * at a scale of 1. That's kinda complicated though... */
2231
2232 /* Outline of what we actually want to blur later.
2233 * Spread grows inside, so we don't need to account for that. But the blur will need
2234 * to read outside of the inset shadow, so we need to draw some color in there. */
2235 outline_to_blur = *node_outline;
2236 gsk_rounded_rect_shrink (self: &outline_to_blur,
2237 top: -half_blur_extra,
2238 right: -half_blur_extra,
2239 bottom: -half_blur_extra,
2240 left: -half_blur_extra);
2241
2242 /* Fit to our texture */
2243 outline_to_blur.bounds.origin.x = 0;
2244 outline_to_blur.bounds.origin.y = 0;
2245 outline_to_blur.bounds.size.width *= scale_x;
2246 outline_to_blur.bounds.size.height *= scale_y;
2247
2248 for (guint i = 0; i < 4; i ++)
2249 {
2250 outline_to_blur.corner[i].width *= scale_x;
2251 outline_to_blur.corner[i].height *= scale_y;
2252 }
2253
2254 if (!gsk_gl_driver_create_render_target (self: job->driver,
2255 width: texture_width, height: texture_height,
2256 format: get_target_format (job, node),
2257 GL_NEAREST, GL_NEAREST,
2258 render_target: &render_target))
2259 g_assert_not_reached ();
2260
2261 gsk_gl_render_job_set_viewport_for_size (job, width: texture_width, height: texture_height, prev_viewport: &prev_viewport);
2262 gsk_gl_render_job_set_projection_for_size (job, width: texture_width, height: texture_height, prev_projection: &prev_projection);
2263 gsk_gl_render_job_set_modelview (job, NULL);
2264 gsk_gl_render_job_push_clip (job, rect: &GSK_ROUNDED_RECT_INIT (0, 0, texture_width, texture_height));
2265
2266 prev_fbo = gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: render_target->framebuffer_id);
2267 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
2268
2269 gsk_gl_render_job_transform_rounded_rect (job, rect: &outline_to_blur, out_rect: &transformed_outline);
2270
2271 /* Actual inset shadow outline drawing */
2272 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
2273 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
2274 key: UNIFORM_INSET_SHADOW_OUTLINE_RECT, stamp: 0,
2275 rounded_rect: &transformed_outline);
2276 gsk_gl_program_set_uniform1f (self: job->current_program,
2277 key: UNIFORM_INSET_SHADOW_SPREAD, stamp: 0,
2278 value0: spread * MAX (scale_x, scale_y));
2279 gsk_gl_program_set_uniform2f (self: job->current_program,
2280 key: UNIFORM_INSET_SHADOW_OFFSET, stamp: 0,
2281 value0: offset_x * scale_x,
2282 value1: offset_y * scale_y);
2283 rgba_to_half (rgba: gsk_inset_shadow_node_get_color (node), h: color);
2284 gsk_gl_render_job_draw_with_color (job,
2285 x: 0, y: 0, width: texture_width, height: texture_height,
2286 color);
2287 gsk_gl_render_job_end_draw (job);
2288
2289 gsk_gl_render_job_pop_modelview (job);
2290 gsk_gl_render_job_pop_clip (job);
2291 gsk_gl_render_job_set_projection (job, projection: &prev_projection);
2292 gsk_gl_render_job_set_viewport (job, viewport: &prev_viewport, NULL);
2293 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: prev_fbo);
2294
2295 offscreen.texture_id = render_target->texture_id;
2296 init_full_texture_region (offscreen: &offscreen);
2297
2298 blurred_texture_id = blur_offscreen (job,
2299 offscreen: &offscreen,
2300 texture_to_blur_width: texture_width,
2301 texture_to_blur_height: texture_height,
2302 blur_radius_x: blur_radius * scale_x,
2303 blur_radius_y: blur_radius * scale_y);
2304
2305 gsk_gl_driver_release_render_target (self: job->driver, render_target, TRUE);
2306
2307 gsk_gl_driver_cache_texture (self: job->driver, key: &key, texture_id: blurred_texture_id);
2308 }
2309
2310 g_assert (blurred_texture_id != 0);
2311
2312 /* Blur the rendered unblurred inset shadow */
2313 /* Use a clip to cut away the unwanted parts outside of the original outline */
2314 {
2315 const gboolean needs_clip = !gsk_rounded_rect_is_rectilinear (self: node_outline);
2316 const float tx1 = half_blur_extra * scale_x / texture_width;
2317 const float tx2 = 1.0 - tx1;
2318 const float ty1 = half_blur_extra * scale_y / texture_height;
2319 const float ty2 = 1.0 - ty1;
2320
2321 if (needs_clip)
2322 {
2323 GskRoundedRect node_clip;
2324
2325 gsk_gl_render_job_transform_bounds (job, rect: &node_outline->bounds, out_rect: &node_clip.bounds);
2326
2327 for (guint i = 0; i < 4; i ++)
2328 {
2329 node_clip.corner[i].width = node_outline->corner[i].width * scale_x;
2330 node_clip.corner[i].height = node_outline->corner[i].height * scale_y;
2331 }
2332
2333 gsk_gl_render_job_push_clip (job, rect: &node_clip);
2334 }
2335
2336 offscreen.was_offscreen = TRUE;
2337 offscreen.area.x = tx1;
2338 offscreen.area.y = ty1;
2339 offscreen.area.x2 = tx2;
2340 offscreen.area.y2 = ty2;
2341
2342 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
2343 gsk_gl_program_set_uniform_texture (self: job->current_program,
2344 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2345 GL_TEXTURE_2D,
2346 GL_TEXTURE0,
2347 texture_id: blurred_texture_id);
2348 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
2349 gsk_gl_render_job_end_draw (job);
2350
2351 if (needs_clip)
2352 gsk_gl_render_job_pop_clip (job);
2353 }
2354}
2355
2356static inline void
2357gsk_gl_render_job_visit_unblurred_outset_shadow_node (GskGLRenderJob *job,
2358 const GskRenderNode *node)
2359{
2360 const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
2361 GskRoundedRect transformed_outline;
2362 float x = node->bounds.origin.x;
2363 float y = node->bounds.origin.y;
2364 float w = node->bounds.size.width;
2365 float h = node->bounds.size.height;
2366 float spread = gsk_outset_shadow_node_get_spread (node);
2367 float dx = gsk_outset_shadow_node_get_dx (node);
2368 float dy = gsk_outset_shadow_node_get_dy (node);
2369 guint16 color[4];
2370 const float edge_sizes[] = { // Top, right, bottom, left
2371 spread - dy, spread + dx, spread + dy, spread - dx
2372 };
2373 const float corner_sizes[][2] = { // top left, top right, bottom right, bottom left
2374 { outline->corner[0].width + spread - dx, outline->corner[0].height + spread - dy },
2375 { outline->corner[1].width + spread + dx, outline->corner[1].height + spread - dy },
2376 { outline->corner[2].width + spread + dx, outline->corner[2].height + spread + dy },
2377 { outline->corner[3].width + spread - dx, outline->corner[3].height + spread + dy },
2378 };
2379
2380 rgba_to_half (rgba: gsk_outset_shadow_node_get_color (node), h: color);
2381
2382 gsk_gl_render_job_transform_rounded_rect (job, rect: outline, out_rect: &transformed_outline);
2383
2384 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, unblurred_outset_shadow));
2385 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
2386 key: UNIFORM_UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, stamp: 0,
2387 rounded_rect: &transformed_outline);
2388 gsk_gl_program_set_uniform1f (self: job->current_program,
2389 key: UNIFORM_UNBLURRED_OUTSET_SHADOW_SPREAD, stamp: 0,
2390 value0: spread);
2391 gsk_gl_program_set_uniform2f (self: job->current_program,
2392 key: UNIFORM_UNBLURRED_OUTSET_SHADOW_OFFSET, stamp: 0,
2393 value0: dx, value1: dy);
2394
2395 /* Corners... */
2396 if (corner_sizes[0][0] > 0 && corner_sizes[0][1] > 0) /* Top left */
2397 gsk_gl_render_job_draw_with_color (job,
2398 x, y, width: corner_sizes[0][0], height: corner_sizes[0][1],
2399 color);
2400 if (corner_sizes[1][0] > 0 && corner_sizes[1][1] > 0) /* Top right */
2401 gsk_gl_render_job_draw_with_color (job,
2402 x: x + w - corner_sizes[1][0], y,
2403 width: corner_sizes[1][0], height: corner_sizes[1][1],
2404 color);
2405 if (corner_sizes[2][0] > 0 && corner_sizes[2][1] > 0) /* Bottom right */
2406 gsk_gl_render_job_draw_with_color (job,
2407 x: x + w - corner_sizes[2][0], y: y + h - corner_sizes[2][1],
2408 width: corner_sizes[2][0], height: corner_sizes[2][1],
2409 color);
2410 if (corner_sizes[3][0] > 0 && corner_sizes[3][1] > 0) /* Bottom left */
2411 gsk_gl_render_job_draw_with_color (job,
2412 x, y: y + h - corner_sizes[3][1],
2413 width: corner_sizes[3][0], height: corner_sizes[3][1],
2414 color);
2415 /* Edges... */;
2416 if (edge_sizes[0] > 0) /* Top */
2417 gsk_gl_render_job_draw_with_color (job,
2418 x: x + corner_sizes[0][0], y,
2419 width: w - corner_sizes[0][0] - corner_sizes[1][0], height: edge_sizes[0],
2420 color);
2421 if (edge_sizes[1] > 0) /* Right */
2422 gsk_gl_render_job_draw_with_color (job,
2423 x: x + w - edge_sizes[1], y: y + corner_sizes[1][1],
2424 width: edge_sizes[1], height: h - corner_sizes[1][1] - corner_sizes[2][1],
2425 color);
2426 if (edge_sizes[2] > 0) /* Bottom */
2427 gsk_gl_render_job_draw_with_color (job,
2428 x: x + corner_sizes[3][0], y: y + h - edge_sizes[2],
2429 width: w - corner_sizes[3][0] - corner_sizes[2][0], height: edge_sizes[2],
2430 color);
2431 if (edge_sizes[3] > 0) /* Left */
2432 gsk_gl_render_job_draw_with_color (job,
2433 x, y: y + corner_sizes[0][1],
2434 width: edge_sizes[3], height: h - corner_sizes[0][1] - corner_sizes[3][1],
2435 color);
2436
2437 gsk_gl_render_job_end_draw (job);
2438}
2439
2440static inline void
2441gsk_gl_render_job_visit_blurred_outset_shadow_node (GskGLRenderJob *job,
2442 const GskRenderNode *node)
2443{
2444 const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
2445 float scale_x = job->scale_x;
2446 float scale_y = job->scale_y;
2447 float blur_radius = gsk_outset_shadow_node_get_blur_radius (node);
2448 float blur_extra = blur_radius * 2.0f; /* 2.0 = shader radius_multiplier */
2449 float half_blur_extra = blur_extra / 2.0f;
2450 int extra_blur_pixels_x = ceilf (x: half_blur_extra * scale_x);
2451 int extra_blur_pixels_y = ceilf (x: half_blur_extra * scale_y);
2452 float spread = gsk_outset_shadow_node_get_spread (node);
2453 float dx = gsk_outset_shadow_node_get_dx (node);
2454 float dy = gsk_outset_shadow_node_get_dy (node);
2455 GskRoundedRect scaled_outline;
2456 GskRoundedRect transformed_outline;
2457 GskGLRenderOffscreen offscreen = {0};
2458 int texture_width, texture_height;
2459 int blurred_texture_id;
2460 int cached_tid;
2461 gboolean do_slicing;
2462 guint16 color[4];
2463 float half_width = outline->bounds.size.width / 2;
2464 float half_height = outline->bounds.size.height / 2;
2465
2466 rgba_to_half (rgba: gsk_outset_shadow_node_get_color (node), h: color);
2467
2468 /* scaled_outline is the minimal outline we need to draw the given drop shadow,
2469 * enlarged by the spread and offset by the blur radius. */
2470 scaled_outline = *outline;
2471
2472 if (outline->bounds.size.width < blur_extra ||
2473 outline->bounds.size.height < blur_extra ||
2474 outline->corner[0].width >= half_width ||
2475 outline->corner[1].width >= half_width ||
2476 outline->corner[2].width >= half_width ||
2477 outline->corner[3].width >= half_width ||
2478 outline->corner[0].height >= half_height ||
2479 outline->corner[1].height >= half_height ||
2480 outline->corner[2].height >= half_height ||
2481 outline->corner[3].height >= half_height)
2482 {
2483 do_slicing = FALSE;
2484 gsk_rounded_rect_shrink (self: &scaled_outline, top: -spread, right: -spread, bottom: -spread, left: -spread);
2485 }
2486 else
2487 {
2488 /* Shrink our outline to the minimum size that can still hold all the border radii */
2489 gsk_rounded_rect_shrink_to_minimum (self: &scaled_outline);
2490 /* Increase by the spread */
2491 gsk_rounded_rect_shrink (self: &scaled_outline, top: -spread, right: -spread, bottom: -spread, left: -spread);
2492 /* Grow bounds but don't grow corners */
2493 graphene_rect_inset (r: &scaled_outline.bounds, d_x: - blur_extra / 2.0, d_y: - blur_extra / 2.0);
2494 /* For the center part, we add a few pixels */
2495 scaled_outline.bounds.size.width += SHADOW_EXTRA_SIZE;
2496 scaled_outline.bounds.size.height += SHADOW_EXTRA_SIZE;
2497
2498 do_slicing = TRUE;
2499 }
2500
2501 texture_width = (int)ceil (x: (scaled_outline.bounds.size.width + blur_extra) * scale_x);
2502 texture_height = (int)ceil (x: (scaled_outline.bounds.size.height + blur_extra) * scale_y);
2503
2504 scaled_outline.bounds.origin.x = extra_blur_pixels_x;
2505 scaled_outline.bounds.origin.y = extra_blur_pixels_y;
2506 scaled_outline.bounds.size.width = texture_width - (extra_blur_pixels_x * 2);
2507 scaled_outline.bounds.size.height = texture_height - (extra_blur_pixels_y * 2);
2508
2509 for (guint i = 0; i < G_N_ELEMENTS (scaled_outline.corner); i++)
2510 {
2511 scaled_outline.corner[i].width *= scale_x;
2512 scaled_outline.corner[i].height *= scale_y;
2513 }
2514
2515 cached_tid = gsk_gl_shadow_library_lookup (self: job->driver->shadows, outline: &scaled_outline, blur_radius);
2516
2517 if (cached_tid == 0)
2518 {
2519 GdkGLContext *context = job->command_queue->context;
2520 GskGLRenderTarget *render_target;
2521 graphene_matrix_t prev_projection;
2522 graphene_rect_t prev_viewport;
2523 guint prev_fbo;
2524
2525 gsk_gl_driver_create_render_target (self: job->driver,
2526 width: texture_width, height: texture_height,
2527 format: get_target_format (job, node),
2528 GL_NEAREST, GL_NEAREST,
2529 render_target: &render_target);
2530
2531 if (gdk_gl_context_has_debug (self: context))
2532 {
2533 gdk_gl_context_label_object_printf (context,
2534 GL_TEXTURE,
2535 name: render_target->texture_id,
2536 format: "Outset Shadow Temp %d",
2537 render_target->texture_id);
2538 gdk_gl_context_label_object_printf (context,
2539 GL_FRAMEBUFFER,
2540 name: render_target->framebuffer_id,
2541 format: "Outset Shadow FB Temp %d",
2542 render_target->framebuffer_id);
2543 }
2544
2545 /* Change state for offscreen */
2546 gsk_gl_render_job_set_projection_for_size (job, width: texture_width, height: texture_height, prev_projection: &prev_projection);
2547 gsk_gl_render_job_set_viewport_for_size (job, width: texture_width, height: texture_height, prev_viewport: &prev_viewport);
2548 gsk_gl_render_job_set_modelview (job, NULL);
2549 gsk_gl_render_job_push_clip (job, rect: &scaled_outline);
2550
2551 /* Bind render target and clear it */
2552 prev_fbo = gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: render_target->framebuffer_id);
2553 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
2554
2555 /* Draw the outline using color program */
2556 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
2557 gsk_gl_render_job_draw_with_color (job, x: 0, y: 0, width: texture_width, height: texture_height,
2558 color: (guint16[]){ FP16_ONE, FP16_ONE, FP16_ONE, FP16_ONE });
2559 gsk_gl_render_job_end_draw (job);
2560
2561 /* Reset state from offscreen */
2562 gsk_gl_render_job_pop_clip (job);
2563 gsk_gl_render_job_pop_modelview (job);
2564 gsk_gl_render_job_set_viewport (job, viewport: &prev_viewport, NULL);
2565 gsk_gl_render_job_set_projection (job, projection: &prev_projection);
2566
2567 /* Now blur the outline */
2568 init_full_texture_region (offscreen: &offscreen);
2569 offscreen.texture_id = gsk_gl_driver_release_render_target (self: job->driver, render_target, FALSE);
2570 blurred_texture_id = blur_offscreen (job,
2571 offscreen: &offscreen,
2572 texture_to_blur_width: texture_width,
2573 texture_to_blur_height: texture_height,
2574 blur_radius_x: blur_radius * scale_x,
2575 blur_radius_y: blur_radius * scale_y);
2576
2577 gsk_gl_shadow_library_insert (self: job->driver->shadows,
2578 outline: &scaled_outline,
2579 blur_radius,
2580 texture_id: blurred_texture_id);
2581
2582 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: prev_fbo);
2583 }
2584 else
2585 {
2586 blurred_texture_id = cached_tid;
2587 }
2588
2589 gsk_gl_render_job_transform_rounded_rect (job, rect: outline, out_rect: &transformed_outline);
2590
2591 if (!do_slicing)
2592 {
2593 float min_x = floorf (x: outline->bounds.origin.x - spread - half_blur_extra + dx);
2594 float min_y = floorf (x: outline->bounds.origin.y - spread - half_blur_extra + dy);
2595
2596 offscreen.was_offscreen = TRUE;
2597 offscreen.texture_id = blurred_texture_id;
2598 init_full_texture_region (offscreen: &offscreen);
2599
2600 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
2601 gsk_gl_program_set_uniform_texture (self: job->current_program,
2602 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2603 GL_TEXTURE_2D,
2604 GL_TEXTURE0,
2605 texture_id: blurred_texture_id);
2606 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
2607 key: UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, stamp: 0,
2608 rounded_rect: &transformed_outline);
2609 gsk_gl_render_job_draw_offscreen_with_color (job,
2610 bounds: &GRAPHENE_RECT_INIT (min_x,
2611 min_y,
2612 texture_width / scale_x,
2613 texture_height / scale_y),
2614 offscreen: &offscreen,
2615 color);
2616 gsk_gl_render_job_end_draw (job);
2617
2618 return;
2619 }
2620
2621 /* slicing */
2622
2623 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
2624 gsk_gl_program_set_uniform_texture (self: job->current_program,
2625 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2626 GL_TEXTURE_2D,
2627 GL_TEXTURE0,
2628 texture_id: blurred_texture_id);
2629 gsk_gl_program_set_uniform_rounded_rect (self: job->current_program,
2630 key: UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, stamp: 0,
2631 rounded_rect: &transformed_outline);
2632
2633 {
2634 float min_x = floorf (x: outline->bounds.origin.x - spread - half_blur_extra + dx);
2635 float min_y = floorf (x: outline->bounds.origin.y - spread - half_blur_extra + dy);
2636 float max_x = ceilf (x: outline->bounds.origin.x + outline->bounds.size.width +
2637 half_blur_extra + dx + spread);
2638 float max_y = ceilf (x: outline->bounds.origin.y + outline->bounds.size.height +
2639 half_blur_extra + dy + spread);
2640 const GskGLTextureNineSlice *slices;
2641 float left_width, center_width, right_width;
2642 float top_height, center_height, bottom_height;
2643 GskGLTexture *texture;
2644
2645 texture = gsk_gl_driver_get_texture_by_id (self: job->driver, texture_id: blurred_texture_id);
2646 slices = gsk_gl_texture_get_nine_slice (texture, outline: &scaled_outline, extra_pixels_x: extra_blur_pixels_x, extra_pixels_y: extra_blur_pixels_y);
2647
2648 offscreen.was_offscreen = TRUE;
2649
2650 /* Our texture coordinates MUST be scaled, while the actual vertex coords
2651 * MUST NOT be scaled.
2652 */
2653
2654 left_width = slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x;
2655 right_width = slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x;
2656 center_width = (max_x - min_x) - (left_width + right_width);
2657
2658 top_height = slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y;
2659 bottom_height = slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y;
2660 center_height = (max_y - min_y) - (top_height + bottom_height);
2661
2662 /* Top left */
2663 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_TOP_LEFT]))
2664 {
2665 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_TOP_LEFT].area, n: sizeof offscreen.area);
2666 gsk_gl_render_job_draw_offscreen_with_color (job,
2667 bounds: &GRAPHENE_RECT_INIT (min_x,
2668 min_y,
2669 left_width,
2670 top_height),
2671 offscreen: &offscreen,
2672 color);
2673 }
2674
2675 /* Top center */
2676 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_TOP_CENTER]))
2677 {
2678 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_TOP_CENTER].area, n: sizeof offscreen.area);
2679 gsk_gl_render_job_draw_offscreen_with_color (job,
2680 bounds: &GRAPHENE_RECT_INIT (min_x + left_width,
2681 min_y,
2682 center_width,
2683 top_height),
2684 offscreen: &offscreen,
2685 color);
2686 }
2687
2688 /* Top right */
2689 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_TOP_RIGHT]))
2690 {
2691 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_TOP_RIGHT].area, n: sizeof offscreen.area);
2692 gsk_gl_render_job_draw_offscreen_with_color (job,
2693 bounds: &GRAPHENE_RECT_INIT (max_x - right_width,
2694 min_y,
2695 right_width,
2696 top_height),
2697 offscreen: &offscreen,
2698 color);
2699 }
2700
2701 /* Bottom right */
2702 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_BOTTOM_RIGHT]))
2703 {
2704 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_BOTTOM_RIGHT].area, n: sizeof offscreen.area);
2705 gsk_gl_render_job_draw_offscreen_with_color (job,
2706 bounds: &GRAPHENE_RECT_INIT (max_x - right_width,
2707 max_y - bottom_height,
2708 right_width,
2709 bottom_height),
2710 offscreen: &offscreen,
2711 color);
2712 }
2713
2714 /* Bottom left */
2715 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_BOTTOM_LEFT]))
2716 {
2717 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_BOTTOM_LEFT].area, n: sizeof offscreen.area);
2718 gsk_gl_render_job_draw_offscreen_with_color (job,
2719 bounds: &GRAPHENE_RECT_INIT (min_x,
2720 max_y - bottom_height,
2721 left_width,
2722 bottom_height),
2723 offscreen: &offscreen,
2724 color);
2725 }
2726
2727 /* Left side */
2728 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_LEFT_CENTER]))
2729 {
2730 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_LEFT_CENTER].area, n: sizeof offscreen.area);
2731 gsk_gl_render_job_draw_offscreen_with_color (job,
2732 bounds: &GRAPHENE_RECT_INIT (min_x,
2733 min_y + top_height,
2734 left_width,
2735 center_height),
2736 offscreen: &offscreen,
2737 color);
2738 }
2739
2740 /* Right side */
2741 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_RIGHT_CENTER]))
2742 {
2743 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_RIGHT_CENTER].area, n: sizeof offscreen.area);
2744 gsk_gl_render_job_draw_offscreen_with_color (job,
2745 bounds: &GRAPHENE_RECT_INIT (max_x - right_width,
2746 min_y + top_height,
2747 right_width,
2748 center_height),
2749 offscreen: &offscreen,
2750 color);
2751 }
2752
2753 /* Bottom side */
2754 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_BOTTOM_CENTER]))
2755 {
2756 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_BOTTOM_CENTER].area, n: sizeof offscreen.area);
2757 gsk_gl_render_job_draw_offscreen_with_color (job,
2758 bounds: &GRAPHENE_RECT_INIT (min_x + left_width,
2759 max_y - bottom_height,
2760 center_width,
2761 bottom_height),
2762 offscreen: &offscreen,
2763 color);
2764 }
2765
2766 /* Middle */
2767 if (nine_slice_is_visible (slice: &slices[NINE_SLICE_CENTER]))
2768 {
2769 if (!gsk_rounded_rect_contains_rect (self: outline, rect: &GRAPHENE_RECT_INIT (min_x + left_width,
2770 min_y + top_height,
2771 center_width,
2772 center_height)))
2773 {
2774 memcpy (dest: &offscreen.area, src: &slices[NINE_SLICE_CENTER].area, n: sizeof offscreen.area);
2775 gsk_gl_render_job_draw_offscreen_with_color (job,
2776 bounds: &GRAPHENE_RECT_INIT (min_x + left_width,
2777 min_y + top_height,
2778 center_width,
2779 center_height),
2780 offscreen: &offscreen,
2781 color);
2782 }
2783 }
2784 }
2785
2786 gsk_gl_render_job_end_draw (job);
2787}
2788
2789static inline gboolean G_GNUC_PURE
2790equal_texture_nodes (const GskRenderNode *node1,
2791 const GskRenderNode *node2)
2792{
2793 if (gsk_render_node_get_node_type (node: node1) != GSK_TEXTURE_NODE ||
2794 gsk_render_node_get_node_type (node: node2) != GSK_TEXTURE_NODE)
2795 return FALSE;
2796
2797 if (gsk_texture_node_get_texture (node: node1) !=
2798 gsk_texture_node_get_texture (node: node2))
2799 return FALSE;
2800
2801 return graphene_rect_equal (a: &node1->bounds, b: &node2->bounds);
2802}
2803
2804static inline void
2805gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob *job,
2806 const GskRenderNode *node)
2807{
2808 const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
2809 const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
2810 float progress = gsk_cross_fade_node_get_progress (node);
2811 GskGLRenderOffscreen offscreen_start = {0};
2812 GskGLRenderOffscreen offscreen_end = {0};
2813
2814 g_assert (progress > 0.0);
2815 g_assert (progress < 1.0);
2816
2817 offscreen_start.force_offscreen = TRUE;
2818 offscreen_start.reset_clip = TRUE;
2819 offscreen_start.bounds = &node->bounds;
2820
2821 offscreen_end.force_offscreen = TRUE;
2822 offscreen_end.reset_clip = TRUE;
2823 offscreen_end.bounds = &node->bounds;
2824
2825 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: start_node, offscreen: &offscreen_start))
2826 {
2827 gsk_gl_render_job_visit_node (job, node: end_node);
2828 return;
2829 }
2830
2831 g_assert (offscreen_start.texture_id);
2832
2833 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: end_node, offscreen: &offscreen_end))
2834 {
2835 float prev_alpha = gsk_gl_render_job_set_alpha (job, alpha: job->alpha * progress);
2836 gsk_gl_render_job_visit_node (job, node: start_node);
2837 gsk_gl_render_job_set_alpha (job, alpha: prev_alpha);
2838 return;
2839 }
2840
2841 g_assert (offscreen_end.texture_id);
2842
2843 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, cross_fade));
2844 gsk_gl_program_set_uniform_texture (self: job->current_program,
2845 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2846 GL_TEXTURE_2D,
2847 GL_TEXTURE0,
2848 texture_id: offscreen_start.texture_id);
2849 gsk_gl_program_set_uniform_texture (self: job->current_program,
2850 key: UNIFORM_CROSS_FADE_SOURCE2, stamp: 0,
2851 GL_TEXTURE_2D,
2852 GL_TEXTURE1,
2853 texture_id: offscreen_end.texture_id);
2854 gsk_gl_program_set_uniform1f (self: job->current_program,
2855 key: UNIFORM_CROSS_FADE_PROGRESS, stamp: 0,
2856 value0: progress);
2857 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen_end);
2858 gsk_gl_render_job_end_draw (job);
2859}
2860
2861static gboolean
2862is_non_branching (const GskRenderNode *node)
2863{
2864 switch ((int)gsk_render_node_get_node_type (node))
2865 {
2866 case GSK_COLOR_NODE:
2867 case GSK_LINEAR_GRADIENT_NODE:
2868 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
2869 case GSK_RADIAL_GRADIENT_NODE:
2870 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
2871 case GSK_CONIC_GRADIENT_NODE:
2872 case GSK_BORDER_NODE:
2873 case GSK_TEXTURE_NODE:
2874 case GSK_INSET_SHADOW_NODE:
2875 case GSK_OUTSET_SHADOW_NODE:
2876 case GSK_TEXT_NODE:
2877 case GSK_CAIRO_NODE:
2878 return TRUE;
2879
2880 case GSK_TRANSFORM_NODE:
2881 return is_non_branching (node: gsk_transform_node_get_child (node));
2882
2883 case GSK_OPACITY_NODE:
2884 return is_non_branching (node: gsk_opacity_node_get_child (node));
2885
2886 case GSK_COLOR_MATRIX_NODE:
2887 return is_non_branching (node: gsk_color_matrix_node_get_child (node));
2888
2889 case GSK_CLIP_NODE:
2890 return is_non_branching (node: gsk_clip_node_get_child (node));
2891
2892 case GSK_ROUNDED_CLIP_NODE:
2893 return is_non_branching (node: gsk_rounded_clip_node_get_child (node));
2894
2895 case GSK_SHADOW_NODE:
2896 return is_non_branching (node: gsk_shadow_node_get_child (node));
2897
2898 case GSK_BLUR_NODE:
2899 return is_non_branching (node: gsk_shadow_node_get_child (node));
2900
2901 case GSK_DEBUG_NODE:
2902 return is_non_branching (node: gsk_debug_node_get_child (node));
2903
2904 case GSK_CONTAINER_NODE:
2905 return gsk_container_node_get_n_children (node) == 1 &&
2906 is_non_branching (node: gsk_container_node_get_child (node, idx: 0));
2907
2908 default:
2909 return FALSE;
2910 }
2911}
2912
2913static inline void
2914gsk_gl_render_job_visit_opacity_node (GskGLRenderJob *job,
2915 const GskRenderNode *node)
2916{
2917 const GskRenderNode *child = gsk_opacity_node_get_child (node);
2918 float opacity = gsk_opacity_node_get_opacity (node);
2919 float new_alpha = job->alpha * opacity;
2920
2921 if (!ALPHA_IS_CLEAR (new_alpha))
2922 {
2923 float prev_alpha = gsk_gl_render_job_set_alpha (job, alpha: new_alpha);
2924
2925 /* Handle a few easy cases without offscreen. We bail out
2926 * as soon as we see nodes with multiple children - in theory,
2927 * we would only need offscreens for overlapping children.
2928 */
2929 if (is_non_branching (node: child))
2930 {
2931 gsk_gl_render_job_visit_node (job, node: child);
2932 gsk_gl_render_job_set_alpha (job, alpha: prev_alpha);
2933 }
2934 else
2935 {
2936 GskGLRenderOffscreen offscreen = {0};
2937
2938 offscreen.bounds = &child->bounds;
2939 offscreen.force_offscreen = TRUE;
2940 offscreen.reset_clip = TRUE;
2941
2942 /* Note: offscreen rendering resets alpha to 1.0 */
2943 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen))
2944 return;
2945
2946 g_assert (offscreen.texture_id);
2947
2948 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
2949 gsk_gl_program_set_uniform_texture (self: job->current_program,
2950 key: UNIFORM_SHARED_SOURCE, stamp: 0,
2951 GL_TEXTURE_2D,
2952 GL_TEXTURE0,
2953 texture_id: offscreen.texture_id);
2954 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
2955 gsk_gl_render_job_end_draw (job);
2956 }
2957
2958 gsk_gl_render_job_set_alpha (job, alpha: prev_alpha);
2959 }
2960}
2961
2962static inline int
2963compute_phase_and_pos (float value, float *pos)
2964{
2965 float v;
2966
2967 *pos = floorf (x: value);
2968
2969 v = value - *pos;
2970
2971 if (v < 0.125)
2972 return 0;
2973 else if (v < 0.375)
2974 return 1;
2975 else if (v < 0.625)
2976 return 2;
2977 else if (v < 0.875)
2978 return 3;
2979 else
2980 {
2981 *pos += 1;
2982 return 0;
2983 }
2984}
2985
2986static inline void
2987gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
2988 const GskRenderNode *node,
2989 const GdkRGBA *color,
2990 gboolean force_color)
2991{
2992 const PangoFont *font = gsk_text_node_get_font (node);
2993 const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
2994 const graphene_point_t *offset = gsk_text_node_get_offset (node);
2995 float text_scale = MAX (job->scale_x, job->scale_y); /* TODO: Fix for uneven scales? */
2996 guint num_glyphs = gsk_text_node_get_num_glyphs (node);
2997 float x = offset->x + job->offset_x;
2998 float y = offset->y + job->offset_y;
2999 GskGLGlyphLibrary *library = job->driver->glyphs;
3000 GskGLCommandBatch *batch;
3001 int x_position = 0;
3002 GskGLGlyphKey lookup;
3003 guint last_texture = 0;
3004 GskGLDrawVertex *vertices;
3005 guint used = 0;
3006 guint16 nc[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE };
3007 guint16 cc[4];
3008 const guint16 *c;
3009 const PangoGlyphInfo *gi;
3010 guint i;
3011 int yshift;
3012 float ypos;
3013
3014 if (num_glyphs == 0)
3015 return;
3016
3017 if ((force_color || !gsk_text_node_has_color_glyphs (node)) &&
3018 RGBA_IS_CLEAR (color))
3019 return;
3020
3021 rgba_to_half (rgba: color, h: cc);
3022
3023 lookup.font = (PangoFont *)font;
3024 lookup.scale = (guint) (text_scale * 1024);
3025
3026 yshift = compute_phase_and_pos (value: y, pos: &ypos);
3027
3028 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
3029
3030 batch = gsk_gl_command_queue_get_batch (self: job->command_queue);
3031 vertices = gsk_gl_command_queue_add_n_vertices (self: job->command_queue, count: num_glyphs);
3032
3033 /* We use one quad per character */
3034 for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++)
3035 {
3036 const GskGLGlyphValue *glyph;
3037 float glyph_x, glyph_y, glyph_x2, glyph_y2;
3038 float tx, ty, tx2, ty2;
3039 float cx;
3040 float cy;
3041 guint texture_id;
3042
3043 lookup.glyph = gi->glyph;
3044
3045 /* If the glyph has color, we don't need to recolor anything.
3046 * We tell the shader by setting the color to vec4(-1).
3047 */
3048 if (!force_color && gi->attr.is_color)
3049 c = nc;
3050 else
3051 c = cc;
3052
3053 cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
3054 lookup.xshift = compute_phase_and_pos (value: x + cx, pos: &cx);
3055
3056 if G_UNLIKELY (gi->geometry.y_offset != 0)
3057 {
3058 cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
3059 lookup.yshift = compute_phase_and_pos (value: y + cy, pos: &cy);
3060 }
3061 else
3062 {
3063 lookup.yshift = yshift;
3064 cy = ypos;
3065 }
3066
3067 x_position += gi->geometry.width;
3068
3069 texture_id = gsk_gl_glyph_library_lookup_or_add (self: library, key: &lookup, out_value: &glyph);
3070 if G_UNLIKELY (texture_id == 0)
3071 continue;
3072
3073 if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff)
3074 {
3075 if G_LIKELY (last_texture != 0)
3076 {
3077 guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count;
3078
3079 /* Since we have batched added our VBO vertices to avoid repeated
3080 * calls to the buffer, we need to manually tweak the vbo offset
3081 * of the new batch as otherwise it will point at the end of our
3082 * vbo array.
3083 */
3084 gsk_gl_render_job_split_draw (job);
3085 batch = gsk_gl_command_queue_get_batch (self: job->command_queue);
3086 batch->draw.vbo_offset = vbo_offset;
3087 }
3088
3089 gsk_gl_program_set_uniform_texture (self: job->current_program,
3090 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3091 GL_TEXTURE_2D,
3092 GL_TEXTURE0,
3093 texture_id);
3094 last_texture = texture_id;
3095 }
3096
3097 tx = glyph->entry.area.x;
3098 ty = glyph->entry.area.y;
3099 tx2 = glyph->entry.area.x2;
3100 ty2 = glyph->entry.area.y2;
3101
3102 glyph_x = cx + glyph->ink_rect.x;
3103 glyph_y = cy + glyph->ink_rect.y;
3104 glyph_x2 = glyph_x + glyph->ink_rect.width;
3105 glyph_y2 = glyph_y + glyph->ink_rect.height;
3106
3107 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y }, .uv = { tx, ty }, .color = { c[0], c[1], c[2], c[3] } };
3108 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y2 }, .uv = { tx, ty2 }, .color = { c[0], c[1], c[2], c[3] } };
3109 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y }, .uv = { tx2, ty }, .color = { c[0], c[1], c[2], c[3] } };
3110
3111 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y2 }, .uv = { tx2, ty2 }, .color = { c[0], c[1], c[2], c[3] } };
3112 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y2 }, .uv = { tx, ty2 }, .color = { c[0], c[1], c[2], c[3] } };
3113 *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y }, .uv = { tx2, ty }, .color = { c[0], c[1], c[2], c[3] } };
3114
3115 batch->draw.vbo_count += GSK_GL_N_VERTICES;
3116 used++;
3117 }
3118
3119 if (used != num_glyphs)
3120 gsk_gl_command_queue_retract_n_vertices (self: job->command_queue, count: num_glyphs - used);
3121
3122 gsk_gl_render_job_end_draw (job);
3123}
3124
3125static inline void
3126gsk_gl_render_job_visit_shadow_node (GskGLRenderJob *job,
3127 const GskRenderNode *node)
3128{
3129 const gsize n_shadows = gsk_shadow_node_get_n_shadows (node);
3130 const GskRenderNode *original_child = gsk_shadow_node_get_child (node);
3131 const GskRenderNode *shadow_child = original_child;
3132
3133 /* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact.
3134 * If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */
3135 if (gsk_render_node_get_node_type (node: shadow_child) == GSK_COLOR_MATRIX_NODE &&
3136 !color_matrix_modifies_alpha (node: shadow_child))
3137 shadow_child = gsk_color_matrix_node_get_child (node: shadow_child);
3138
3139 for (guint i = 0; i < n_shadows; i++)
3140 {
3141 const GskShadow *shadow = gsk_shadow_node_get_shadow (node, i);
3142 const float dx = shadow->dx;
3143 const float dy = shadow->dy;
3144 GskGLRenderOffscreen offscreen = {0};
3145 graphene_rect_t bounds;
3146 guint16 color[4];
3147
3148 if (RGBA_IS_CLEAR (&shadow->color))
3149 continue;
3150
3151 if (node_is_invisible (node: shadow_child))
3152 continue;
3153
3154 if (shadow->radius == 0 &&
3155 gsk_render_node_get_node_type (node: shadow_child) == GSK_TEXT_NODE)
3156 {
3157 if (dx != 0 || dy != 0)
3158 {
3159 gsk_gl_render_job_offset (job, offset_x: dx, offset_y: dy);
3160 gsk_gl_render_job_visit_text_node (job, node: shadow_child, color: &shadow->color, TRUE);
3161 gsk_gl_render_job_offset (job, offset_x: -dx, offset_y: -dy);
3162 }
3163 continue;
3164 }
3165
3166 if (shadow->radius > 0)
3167 {
3168 float min_x;
3169 float min_y;
3170 float max_x;
3171 float max_y;
3172
3173 offscreen.do_not_cache = TRUE;
3174
3175 blur_node (job,
3176 offscreen: &offscreen,
3177 node: shadow_child,
3178 blur_radius: shadow->radius,
3179 min_x: &min_x, max_x: &max_x,
3180 min_y: &min_y, max_y: &max_y);
3181
3182 bounds.origin.x = min_x - job->offset_x;
3183 bounds.origin.y = min_y - job->offset_y;
3184 bounds.size.width = max_x - min_x;
3185 bounds.size.height = max_y - min_y;
3186
3187 offscreen.was_offscreen = TRUE;
3188 }
3189 else if (dx == 0 && dy == 0)
3190 {
3191 continue; /* Invisible anyway */
3192 }
3193 else
3194 {
3195 offscreen.bounds = &shadow_child->bounds;
3196 offscreen.reset_clip = TRUE;
3197 offscreen.do_not_cache = TRUE;
3198
3199 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: shadow_child, offscreen: &offscreen))
3200 g_assert_not_reached ();
3201
3202 bounds = shadow_child->bounds;
3203 }
3204
3205 gsk_gl_render_job_offset (job, offset_x: dx, offset_y: dy);
3206 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
3207 gsk_gl_program_set_uniform_texture (self: job->current_program,
3208 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3209 GL_TEXTURE_2D,
3210 GL_TEXTURE0,
3211 texture_id: offscreen.texture_id);
3212 rgba_to_half (rgba: &shadow->color, h: color);
3213 gsk_gl_render_job_draw_offscreen_with_color (job, bounds: &bounds, offscreen: &offscreen, color);
3214 gsk_gl_render_job_end_draw (job);
3215 gsk_gl_render_job_offset (job, offset_x: -dx, offset_y: -dy);
3216 }
3217
3218 /* Now draw the child normally */
3219 gsk_gl_render_job_visit_node (job, node: original_child);
3220}
3221
3222static inline void
3223gsk_gl_render_job_visit_blur_node (GskGLRenderJob *job,
3224 const GskRenderNode *node)
3225{
3226 const GskRenderNode *child = gsk_blur_node_get_child (node);
3227 float blur_radius = gsk_blur_node_get_radius (node);
3228 GskGLRenderOffscreen offscreen = {0};
3229 GskTextureKey key;
3230 gboolean cache_texture;
3231 float min_x;
3232 float max_x;
3233 float min_y;
3234 float max_y;
3235
3236 g_assert (blur_radius > 0);
3237
3238 if (node_is_invisible (node: child))
3239 return;
3240
3241 key.pointer = node;
3242 key.pointer_is_child = FALSE;
3243 key.scale_x = job->scale_x;
3244 key.scale_y = job->scale_y;
3245 key.filter = GL_NEAREST;
3246
3247 offscreen.texture_id = gsk_gl_driver_lookup_texture (self: job->driver, key: &key);
3248 cache_texture = offscreen.texture_id == 0;
3249
3250 blur_node (job,
3251 offscreen: &offscreen,
3252 node: child,
3253 blur_radius,
3254 min_x: &min_x, max_x: &max_x, min_y: &min_y, max_y: &max_y);
3255
3256 g_assert (offscreen.texture_id != 0);
3257
3258 if (cache_texture)
3259 gsk_gl_driver_cache_texture (self: job->driver, key: &key, texture_id: offscreen.texture_id);
3260
3261 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
3262 gsk_gl_program_set_uniform_texture (self: job->current_program,
3263 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3264 GL_TEXTURE_2D,
3265 GL_TEXTURE0,
3266 texture_id: offscreen.texture_id);
3267 gsk_gl_render_job_draw_coords (job,
3268 min_x, min_y, max_x, max_y,
3269 min_u: 0, min_v: 1, max_u: 1, max_v: 0,
3270 c: (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } );
3271 gsk_gl_render_job_end_draw (job);
3272}
3273
3274static inline void
3275gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job,
3276 const GskRenderNode *node)
3277{
3278 const GskRenderNode *top_child = gsk_blend_node_get_top_child (node);
3279 const GskRenderNode *bottom_child = gsk_blend_node_get_bottom_child (node);
3280 GskGLRenderOffscreen top_offscreen = {0};
3281 GskGLRenderOffscreen bottom_offscreen = {0};
3282
3283 top_offscreen.bounds = &node->bounds;
3284 top_offscreen.force_offscreen = TRUE;
3285 top_offscreen.reset_clip = TRUE;
3286
3287 bottom_offscreen.bounds = &node->bounds;
3288 bottom_offscreen.force_offscreen = TRUE;
3289 bottom_offscreen.reset_clip = TRUE;
3290
3291 /* TODO: We create 2 textures here as big as the blend node, but both the
3292 * start and the end node might be a lot smaller than that. */
3293 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: bottom_child, offscreen: &bottom_offscreen))
3294 {
3295 gsk_gl_render_job_visit_node (job, node: top_child);
3296 return;
3297 }
3298
3299 g_assert (bottom_offscreen.was_offscreen);
3300
3301 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: top_child, offscreen: &top_offscreen))
3302 {
3303 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
3304 gsk_gl_program_set_uniform_texture (self: job->current_program,
3305 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3306 GL_TEXTURE_2D,
3307 GL_TEXTURE0,
3308 texture_id: bottom_offscreen.texture_id);
3309 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &bottom_offscreen);
3310 gsk_gl_render_job_end_draw (job);
3311 return;
3312 }
3313
3314 g_assert (top_offscreen.was_offscreen);
3315
3316 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blend));
3317 gsk_gl_program_set_uniform_texture (self: job->current_program,
3318 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3319 GL_TEXTURE_2D,
3320 GL_TEXTURE0,
3321 texture_id: bottom_offscreen.texture_id);
3322 gsk_gl_program_set_uniform_texture (self: job->current_program,
3323 key: UNIFORM_BLEND_SOURCE2, stamp: 0,
3324 GL_TEXTURE_2D,
3325 GL_TEXTURE1,
3326 texture_id: top_offscreen.texture_id);
3327 gsk_gl_program_set_uniform1i (self: job->current_program,
3328 key: UNIFORM_BLEND_MODE, stamp: 0,
3329 value0: gsk_blend_node_get_blend_mode (node));
3330 gsk_gl_render_job_draw_offscreen_rect (job, bounds: &node->bounds);
3331 gsk_gl_render_job_end_draw (job);
3332}
3333
3334static inline void
3335gsk_gl_render_job_visit_color_matrix_node (GskGLRenderJob *job,
3336 const GskRenderNode *node)
3337{
3338 const GskRenderNode *child = gsk_color_matrix_node_get_child (node);
3339 GskGLRenderOffscreen offscreen = {0};
3340 float offset[4];
3341
3342 if (node_is_invisible (node: child))
3343 return;
3344
3345 offscreen.bounds = &node->bounds;
3346 offscreen.reset_clip = TRUE;
3347
3348 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen))
3349 g_assert_not_reached ();
3350
3351 g_assert (offscreen.texture_id > 0);
3352
3353 graphene_vec4_to_float (v: gsk_color_matrix_node_get_color_offset (node), dest: offset);
3354
3355 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color_matrix));
3356 gsk_gl_program_set_uniform_texture (self: job->current_program,
3357 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3358 GL_TEXTURE_2D,
3359 GL_TEXTURE0,
3360 texture_id: offscreen.texture_id);
3361 gsk_gl_program_set_uniform_matrix (self: job->current_program,
3362 key: UNIFORM_COLOR_MATRIX_COLOR_MATRIX, stamp: 0,
3363 matrix: gsk_color_matrix_node_get_color_matrix (node));
3364 gsk_gl_program_set_uniform4fv (self: job->current_program,
3365 key: UNIFORM_COLOR_MATRIX_COLOR_OFFSET, stamp: 0,
3366 count: 1,
3367 values: offset);
3368 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
3369 gsk_gl_render_job_end_draw (job);
3370}
3371
3372static inline void
3373gsk_gl_render_job_visit_gl_shader_node_fallback (GskGLRenderJob *job,
3374 const GskRenderNode *node)
3375{
3376 guint16 pink[4] = { 15360, 13975, 14758, 15360 }; /* 255 105 180 */
3377
3378 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
3379 gsk_gl_render_job_draw_rect_with_color (job, bounds: &node->bounds, color: pink);
3380 gsk_gl_render_job_end_draw (job);
3381}
3382
3383static inline void
3384gsk_gl_render_job_visit_gl_shader_node (GskGLRenderJob *job,
3385 const GskRenderNode *node)
3386{
3387 GError *error = NULL;
3388 GskGLShader *shader;
3389 GskGLProgram *program;
3390 int n_children;
3391
3392 shader = gsk_gl_shader_node_get_shader (node);
3393 program = gsk_gl_driver_lookup_shader (self: job->driver, shader, error: &error);
3394 n_children = gsk_gl_shader_node_get_n_children (node);
3395
3396 if G_UNLIKELY (program == NULL)
3397 {
3398 if (g_object_get_data (G_OBJECT (shader), key: "gsk-did-warn") == NULL)
3399 {
3400 g_object_set_data (G_OBJECT (shader), key: "gsk-did-warn", GUINT_TO_POINTER (1));
3401 g_warning ("Failed to compile gl shader: %s", error->message);
3402 }
3403 gsk_gl_render_job_visit_gl_shader_node_fallback (job, node);
3404 g_clear_error (err: &error);
3405 }
3406 else
3407 {
3408 GskGLRenderOffscreen offscreens[4] = {{0}};
3409 const GskGLUniform *uniforms;
3410 const guint8 *base;
3411 GBytes *args;
3412 int n_uniforms;
3413
3414 g_assert (n_children < G_N_ELEMENTS (offscreens));
3415
3416 for (guint i = 0; i < n_children; i++)
3417 {
3418 const GskRenderNode *child = gsk_gl_shader_node_get_child (node, idx: i);
3419
3420 offscreens[i].bounds = &node->bounds;
3421 offscreens[i].force_offscreen = TRUE;
3422 offscreens[i].reset_clip = TRUE;
3423
3424 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreens[i]))
3425 return;
3426 }
3427
3428 args = gsk_gl_shader_node_get_args (node);
3429 base = g_bytes_get_data (bytes: args, NULL);
3430 uniforms = gsk_gl_shader_get_uniforms (shader, n_uniforms: &n_uniforms);
3431
3432 gsk_gl_render_job_begin_draw (job, program);
3433 for (guint i = 0; i < n_children; i++)
3434 gsk_gl_program_set_uniform_texture (self: program,
3435 key: UNIFORM_CUSTOM_TEXTURE1 + i, stamp: 0,
3436 GL_TEXTURE_2D,
3437 GL_TEXTURE0 + i,
3438 texture_id: offscreens[i].texture_id);
3439 gsk_gl_program_set_uniform2f (self: program,
3440 key: UNIFORM_CUSTOM_SIZE, stamp: 0,
3441 value0: node->bounds.size.width,
3442 value1: node->bounds.size.height);
3443 for (guint i = 0; i < n_uniforms; i++)
3444 {
3445 const GskGLUniform *u = &uniforms[i];
3446 const guint8 *data = base + u->offset;
3447
3448 switch (u->type)
3449 {
3450 default:
3451 case GSK_GL_UNIFORM_TYPE_NONE:
3452 break;
3453 case GSK_GL_UNIFORM_TYPE_FLOAT:
3454 gsk_gl_uniform_state_set1fv (state: job->command_queue->uniforms,
3455 program: program->program_info,
3456 key: UNIFORM_CUSTOM_ARG0 + i,
3457 stamp: 0, count: 1, value: (const float *)data);
3458 break;
3459 case GSK_GL_UNIFORM_TYPE_INT:
3460 gsk_gl_uniform_state_set1i (state: job->command_queue->uniforms,
3461 program: program->program_info,
3462 key: UNIFORM_CUSTOM_ARG0 + i,
3463 stamp: 0, value0: *(const gint32 *)data);
3464 break;
3465 case GSK_GL_UNIFORM_TYPE_UINT:
3466 case GSK_GL_UNIFORM_TYPE_BOOL:
3467 gsk_gl_uniform_state_set1ui (state: job->command_queue->uniforms,
3468 program: program->program_info,
3469 key: UNIFORM_CUSTOM_ARG0 + i,
3470 stamp: 0, value0: *(const guint32 *)data);
3471 break;
3472 case GSK_GL_UNIFORM_TYPE_VEC2:
3473 gsk_gl_uniform_state_set2fv (state: job->command_queue->uniforms,
3474 program: program->program_info,
3475 key: UNIFORM_CUSTOM_ARG0 + i,
3476 stamp: 0, count: 1, value: (const float *)data);
3477 break;
3478 case GSK_GL_UNIFORM_TYPE_VEC3:
3479 gsk_gl_uniform_state_set3fv (state: job->command_queue->uniforms,
3480 program: program->program_info,
3481 key: UNIFORM_CUSTOM_ARG0 + i,
3482 stamp: 0, count: 1, value: (const float *)data);
3483 break;
3484 case GSK_GL_UNIFORM_TYPE_VEC4:
3485 gsk_gl_uniform_state_set4fv (state: job->command_queue->uniforms,
3486 program: program->program_info,
3487 key: UNIFORM_CUSTOM_ARG0 + i,
3488 stamp: 0, count: 1, value: (const float *)data);
3489 break;
3490 }
3491 }
3492 gsk_gl_render_job_draw_offscreen_rect (job, bounds: &node->bounds);
3493 gsk_gl_render_job_end_draw (job);
3494 }
3495}
3496
3497static void
3498gsk_gl_render_job_upload_texture (GskGLRenderJob *job,
3499 GdkTexture *texture,
3500 GskGLRenderOffscreen *offscreen)
3501{
3502 if (gsk_gl_texture_library_can_cache (self: (GskGLTextureLibrary *)job->driver->icons,
3503 width: texture->width,
3504 height: texture->height) &&
3505 !GDK_IS_GL_TEXTURE (texture))
3506 {
3507 const GskGLIconData *icon_data;
3508
3509 gsk_gl_icon_library_lookup_or_add (self: job->driver->icons, key: texture, out_value: &icon_data);
3510 offscreen->texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (d: icon_data);
3511 memcpy (dest: &offscreen->area, src: &icon_data->entry.area, n: sizeof offscreen->area);
3512 }
3513 else
3514 {
3515 offscreen->texture_id = gsk_gl_driver_load_texture (self: job->driver, texture, GL_LINEAR, GL_LINEAR);
3516 init_full_texture_region (offscreen);
3517 }
3518}
3519
3520static inline void
3521gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job,
3522 const GskRenderNode *node)
3523{
3524 GdkTexture *texture = gsk_texture_node_get_texture (node);
3525 int max_texture_size = job->command_queue->max_texture_size;
3526
3527 if G_LIKELY (texture->width <= max_texture_size &&
3528 texture->height <= max_texture_size)
3529 {
3530 GskGLRenderOffscreen offscreen = {0};
3531
3532 gsk_gl_render_job_upload_texture (job, texture, offscreen: &offscreen);
3533
3534 g_assert (offscreen.texture_id);
3535 g_assert (offscreen.was_offscreen == FALSE);
3536
3537 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
3538 gsk_gl_program_set_uniform_texture (self: job->current_program,
3539 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3540 GL_TEXTURE_2D,
3541 GL_TEXTURE0,
3542 texture_id: offscreen.texture_id);
3543 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
3544 gsk_gl_render_job_end_draw (job);
3545 }
3546 else
3547 {
3548 float min_x = job->offset_x + node->bounds.origin.x;
3549 float min_y = job->offset_y + node->bounds.origin.y;
3550 float max_x = min_x + node->bounds.size.width;
3551 float max_y = min_y + node->bounds.size.height;
3552 float scale_x = (max_x - min_x) / texture->width;
3553 float scale_y = (max_y - min_y) / texture->height;
3554 GskGLTextureSlice *slices = NULL;
3555 guint n_slices = 0;
3556
3557 gsk_gl_driver_slice_texture (self: job->driver, texture, out_slices: &slices, out_n_slices: &n_slices);
3558
3559 g_assert (slices != NULL);
3560 g_assert (n_slices > 0);
3561
3562 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
3563
3564 for (guint i = 0; i < n_slices; i ++)
3565 {
3566 const GskGLTextureSlice *slice = &slices[i];
3567 float x1, x2, y1, y2;
3568
3569 x1 = min_x + (scale_x * slice->rect.x);
3570 x2 = x1 + (slice->rect.width * scale_x);
3571 y1 = min_y + (scale_y * slice->rect.y);
3572 y2 = y1 + (slice->rect.height * scale_y);
3573
3574 if (i > 0)
3575 gsk_gl_render_job_split_draw (job);
3576 gsk_gl_program_set_uniform_texture (self: job->current_program,
3577 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3578 GL_TEXTURE_2D,
3579 GL_TEXTURE0,
3580 texture_id: slice->texture_id);
3581
3582 gsk_gl_render_job_draw_coords (job,
3583 min_x: x1, min_y: y1, max_x: x2, max_y: y2,
3584 min_u: 0, min_v: 0, max_u: 1, max_v: 1,
3585 c: (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
3586 }
3587
3588 gsk_gl_render_job_end_draw (job);
3589 }
3590}
3591
3592static inline void
3593gsk_gl_render_job_visit_repeat_node (GskGLRenderJob *job,
3594 const GskRenderNode *node)
3595{
3596 const GskRenderNode *child = gsk_repeat_node_get_child (node);
3597 const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node);
3598 GskGLRenderOffscreen offscreen = {0};
3599
3600 if (node_is_invisible (node: child))
3601 return;
3602
3603 if (!graphene_rect_equal (a: child_bounds, b: &child->bounds))
3604 {
3605 /* TODO: implement these repeat nodes. */
3606 gsk_gl_render_job_visit_as_fallback (job, node);
3607 return;
3608 }
3609
3610 /* If the size of the repeat node is smaller than the size of the
3611 * child node, we don't repeat at all and can just draw that part
3612 * of the child texture... */
3613 if (rect_contains_rect (r1: child_bounds, r2: &node->bounds))
3614 {
3615 gsk_gl_render_job_visit_clipped_child (job, child, clip: &node->bounds);
3616 return;
3617 }
3618
3619 offscreen.bounds = &child->bounds;
3620 offscreen.reset_clip = TRUE;
3621
3622 if (!gsk_gl_render_job_visit_node_with_offscreen (job, node: child, offscreen: &offscreen))
3623 g_assert_not_reached ();
3624
3625 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, repeat));
3626 gsk_gl_program_set_uniform_texture (self: job->current_program,
3627 key: UNIFORM_SHARED_SOURCE, stamp: 0,
3628 GL_TEXTURE_2D,
3629 GL_TEXTURE0,
3630 texture_id: offscreen.texture_id);
3631 gsk_gl_program_set_uniform4f (self: job->current_program,
3632 key: UNIFORM_REPEAT_CHILD_BOUNDS, stamp: 0,
3633 value0: (node->bounds.origin.x - child_bounds->origin.x) / child_bounds->size.width,
3634 value1: (node->bounds.origin.y - child_bounds->origin.y) / child_bounds->size.height,
3635 value2: node->bounds.size.width / child_bounds->size.width,
3636 value3: node->bounds.size.height / child_bounds->size.height);
3637 gsk_gl_program_set_uniform4f (self: job->current_program,
3638 key: UNIFORM_REPEAT_TEXTURE_RECT, stamp: 0,
3639 value0: offscreen.area.x,
3640 value1: offscreen.was_offscreen ? offscreen.area.y2 : offscreen.area.y,
3641 value2: offscreen.area.x2,
3642 value3: offscreen.was_offscreen ? offscreen.area.y : offscreen.area.y2);
3643 gsk_gl_render_job_draw_offscreen (job, bounds: &node->bounds, offscreen: &offscreen);
3644 gsk_gl_render_job_end_draw (job);
3645}
3646
3647static void
3648gsk_gl_render_job_visit_node (GskGLRenderJob *job,
3649 const GskRenderNode *node)
3650{
3651 gboolean has_clip;
3652
3653 g_assert (job != NULL);
3654 g_assert (node != NULL);
3655 g_assert (GSK_IS_GL_DRIVER (job->driver));
3656 g_assert (GSK_IS_GL_COMMAND_QUEUE (job->command_queue));
3657
3658 if (node_is_invisible (node))
3659 return;
3660
3661 if (!gsk_gl_render_job_update_clip (job, bounds: &node->bounds, pushed_clip: &has_clip))
3662 return;
3663
3664 switch (gsk_render_node_get_node_type (node))
3665 {
3666 case GSK_BLEND_NODE:
3667 gsk_gl_render_job_visit_blend_node (job, node);
3668 break;
3669
3670 case GSK_BLUR_NODE:
3671 if (gsk_blur_node_get_radius (node) > 0)
3672 gsk_gl_render_job_visit_blur_node (job, node);
3673 else
3674 gsk_gl_render_job_visit_node (job, node: gsk_blur_node_get_child (node));
3675 break;
3676
3677 case GSK_BORDER_NODE:
3678 if (gsk_border_node_get_uniform_color (self: node) &&
3679 gsk_rounded_rect_is_rectilinear (self: gsk_border_node_get_outline (node)))
3680 gsk_gl_render_job_visit_rect_border_node (job, node);
3681 else
3682 gsk_gl_render_job_visit_border_node (job, node);
3683 break;
3684
3685 case GSK_CLIP_NODE:
3686 gsk_gl_render_job_visit_clip_node (job, node);
3687 break;
3688
3689 case GSK_COLOR_NODE:
3690 gsk_gl_render_job_visit_color_node (job, node);
3691 break;
3692
3693 case GSK_COLOR_MATRIX_NODE:
3694 gsk_gl_render_job_visit_color_matrix_node (job, node);
3695 break;
3696
3697 case GSK_CONIC_GRADIENT_NODE:
3698 if (gsk_conic_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
3699 gsk_gl_render_job_visit_conic_gradient_node (job, node);
3700 else
3701 gsk_gl_render_job_visit_as_fallback (job, node);
3702 break;
3703
3704 case GSK_CONTAINER_NODE:
3705 {
3706 GskRenderNode **children;
3707 guint n_children;
3708
3709 children = gsk_container_node_get_children (node, n_children: &n_children);
3710
3711 for (guint i = 0; i < n_children; i++)
3712 {
3713 const GskRenderNode *child = children[i];
3714
3715 if (i + 1 < n_children &&
3716 job->current_clip->is_fully_contained &&
3717 gsk_render_node_get_node_type (node: child) == GSK_ROUNDED_CLIP_NODE)
3718 {
3719 const GskRenderNode *grandchild = gsk_rounded_clip_node_get_child (node: child);
3720 const GskRenderNode *child2 = children[i + 1];
3721 if (gsk_render_node_get_node_type (node: grandchild) == GSK_COLOR_NODE &&
3722 gsk_render_node_get_node_type (node: child2) == GSK_BORDER_NODE &&
3723 gsk_border_node_get_uniform_color (self: child2) &&
3724 rounded_rect_equal (r1: gsk_rounded_clip_node_get_clip (node: child),
3725 r2: gsk_border_node_get_outline (node: child2)))
3726 {
3727 gsk_gl_render_job_visit_css_background (job, node: child, node2: child2);
3728 i++; /* skip the border node */
3729 continue;
3730 }
3731 }
3732
3733 gsk_gl_render_job_visit_node (job, node: child);
3734 }
3735 }
3736 break;
3737
3738 case GSK_CROSS_FADE_NODE:
3739 {
3740 const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
3741 const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
3742 float progress = gsk_cross_fade_node_get_progress (node);
3743
3744 if (progress <= 0.0f)
3745 gsk_gl_render_job_visit_node (job, node: gsk_cross_fade_node_get_start_child (node));
3746 else if (progress >= 1.0f || equal_texture_nodes (node1: start_node, node2: end_node))
3747 gsk_gl_render_job_visit_node (job, node: gsk_cross_fade_node_get_end_child (node));
3748 else
3749 gsk_gl_render_job_visit_cross_fade_node (job, node);
3750 }
3751 break;
3752
3753 case GSK_DEBUG_NODE:
3754 /* Debug nodes are ignored because draws get reordered anyway */
3755 gsk_gl_render_job_visit_node (job, node: gsk_debug_node_get_child (node));
3756 break;
3757
3758 case GSK_GL_SHADER_NODE:
3759 gsk_gl_render_job_visit_gl_shader_node (job, node);
3760 break;
3761
3762 case GSK_INSET_SHADOW_NODE:
3763 if (gsk_inset_shadow_node_get_blur_radius (node) > 0)
3764 gsk_gl_render_job_visit_blurred_inset_shadow_node (job, node);
3765 else
3766 gsk_gl_render_job_visit_unblurred_inset_shadow_node (job, node);
3767 break;
3768
3769 case GSK_LINEAR_GRADIENT_NODE:
3770 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
3771 if (gsk_linear_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
3772 gsk_gl_render_job_visit_linear_gradient_node (job, node);
3773 else
3774 gsk_gl_render_job_visit_as_fallback (job, node);
3775 break;
3776
3777 case GSK_OPACITY_NODE:
3778 gsk_gl_render_job_visit_opacity_node (job, node);
3779 break;
3780
3781 case GSK_OUTSET_SHADOW_NODE:
3782 if (gsk_outset_shadow_node_get_blur_radius (node) > 0)
3783 gsk_gl_render_job_visit_blurred_outset_shadow_node (job, node);
3784 else
3785 gsk_gl_render_job_visit_unblurred_outset_shadow_node (job, node);
3786 break;
3787
3788 case GSK_RADIAL_GRADIENT_NODE:
3789 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
3790 if (gsk_radial_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
3791 gsk_gl_render_job_visit_radial_gradient_node (job, node);
3792 else
3793 gsk_gl_render_job_visit_as_fallback (job, node);
3794 break;
3795
3796 case GSK_REPEAT_NODE:
3797 gsk_gl_render_job_visit_repeat_node (job, node);
3798 break;
3799
3800 case GSK_ROUNDED_CLIP_NODE:
3801 gsk_gl_render_job_visit_rounded_clip_node (job, node);
3802 break;
3803
3804 case GSK_SHADOW_NODE:
3805 gsk_gl_render_job_visit_shadow_node (job, node);
3806 break;
3807
3808 case GSK_TEXT_NODE:
3809 gsk_gl_render_job_visit_text_node (job,
3810 node,
3811 color: gsk_text_node_get_color (node),
3812 FALSE);
3813 break;
3814
3815 case GSK_TEXTURE_NODE:
3816 gsk_gl_render_job_visit_texture_node (job, node);
3817 break;
3818
3819 case GSK_TRANSFORM_NODE:
3820 gsk_gl_render_job_visit_transform_node (job, node);
3821 break;
3822
3823 case GSK_CAIRO_NODE:
3824 gsk_gl_render_job_visit_as_fallback (job, node);
3825 break;
3826
3827 case GSK_NOT_A_RENDER_NODE:
3828 default:
3829 g_assert_not_reached ();
3830 break;
3831 }
3832
3833 if (has_clip)
3834 gsk_gl_render_job_pop_clip (job);
3835}
3836
3837static gboolean
3838gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job,
3839 const GskRenderNode *node,
3840 GskGLRenderOffscreen *offscreen)
3841{
3842 GskTextureKey key;
3843 guint cached_id;
3844 int filter;
3845
3846 g_assert (job != NULL);
3847 g_assert (node != NULL);
3848 g_assert (offscreen != NULL);
3849 g_assert (offscreen->texture_id == 0);
3850 g_assert (offscreen->bounds != NULL);
3851
3852 if (node_is_invisible (node))
3853 {
3854 /* Just to be safe. */
3855 offscreen->texture_id = 0;
3856 init_full_texture_region (offscreen);
3857 offscreen->was_offscreen = FALSE;
3858 return FALSE;
3859 }
3860
3861 if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE &&
3862 offscreen->force_offscreen == FALSE)
3863 {
3864 GdkTexture *texture = gsk_texture_node_get_texture (node);
3865 gsk_gl_render_job_upload_texture (job, texture, offscreen);
3866 g_assert (offscreen->was_offscreen == FALSE);
3867 return TRUE;
3868 }
3869
3870 filter = offscreen->linear_filter ? GL_LINEAR : GL_NEAREST;
3871
3872 key.pointer = node;
3873 key.pointer_is_child = TRUE; /* Don't conflict with the child using the cache too */
3874 key.parent_rect = *offscreen->bounds;
3875 key.scale_x = job->scale_x;
3876 key.scale_y = job->scale_y;
3877 key.filter = filter;
3878
3879 float offset_x = job->offset_x;
3880 float offset_y = job->offset_y;
3881 gboolean flipped_x = job->scale_x < 0;
3882 gboolean flipped_y = job->scale_y < 0;
3883 graphene_rect_t viewport;
3884
3885 if (flipped_x || flipped_y)
3886 {
3887 GskTransform *transform = gsk_transform_scale (NULL,
3888 factor_x: flipped_x ? -1 : 1,
3889 factor_y: flipped_y ? -1 : 1);
3890 gsk_gl_render_job_push_modelview (job, transform);
3891 }
3892
3893 gsk_gl_render_job_transform_bounds (job, rect: offscreen->bounds, out_rect: &viewport);
3894
3895 float aligned_x = floorf (x: viewport.origin.x);
3896 float padding_left = viewport.origin.x - aligned_x;
3897 float aligned_width = ceilf (x: viewport.size.width + padding_left);
3898 float padding_right = aligned_width - viewport.size.width - padding_left;
3899
3900 float aligned_y = floorf (x: viewport.origin.y);
3901 float padding_top = viewport.origin.y - aligned_y;
3902 float aligned_height = ceilf (x: viewport.size.height + padding_top);
3903 float padding_bottom = aligned_height - viewport.size.height - padding_top;
3904
3905 /* Tweak the scale factor so that the required texture doesn't
3906 * exceed the max texture limit. This will render with a lower
3907 * resolution, but this is better than clipping.
3908 */
3909
3910 g_assert (job->command_queue->max_texture_size > 0);
3911
3912 float downscale_x = 1;
3913 float downscale_y = 1;
3914 int texture_width;
3915 int texture_height;
3916 int max_texture_size = job->command_queue->max_texture_size;
3917
3918 if (aligned_width > max_texture_size)
3919 downscale_x = (float)max_texture_size / viewport.size.width;
3920
3921 if (aligned_height > max_texture_size)
3922 downscale_y = (float)max_texture_size / viewport.size.height;
3923
3924 if (downscale_x != 1 || downscale_y != 1)
3925 {
3926 GskTransform *transform = gsk_transform_scale (NULL, factor_x: downscale_x, factor_y: downscale_y);
3927 gsk_gl_render_job_push_modelview (job, transform);
3928 gsk_gl_render_job_transform_bounds (job, rect: offscreen->bounds, out_rect: &viewport);
3929 }
3930
3931 if (downscale_x == 1)
3932 {
3933 viewport.origin.x = aligned_x;
3934 viewport.size.width = aligned_width;
3935 offscreen->area.x = padding_left / aligned_width;
3936 offscreen->area.x2 = 1.0f - (padding_right / aligned_width);
3937 texture_width = aligned_width;
3938 }
3939 else
3940 {
3941 offscreen->area.x = 0;
3942 offscreen->area.x2 = 1;
3943 texture_width = max_texture_size;
3944 }
3945
3946 if (downscale_y == 1)
3947 {
3948 viewport.origin.y = aligned_y;
3949 viewport.size.height = aligned_height;
3950 offscreen->area.y = padding_bottom / aligned_height;
3951 offscreen->area.y2 = 1.0f - padding_top / aligned_height;
3952 texture_height = aligned_height;
3953 }
3954 else
3955 {
3956 offscreen->area.y = 0;
3957 offscreen->area.y2 = 1;
3958 texture_height = max_texture_size;
3959 }
3960
3961 /* Check if we've already cached the drawn texture. */
3962 cached_id = gsk_gl_driver_lookup_texture (self: job->driver, key: &key);
3963
3964 if (cached_id != 0)
3965 {
3966 if (downscale_x != 1 || downscale_y != 1)
3967 gsk_gl_render_job_pop_modelview (job);
3968 if (flipped_x || flipped_y)
3969 gsk_gl_render_job_pop_modelview (job);
3970 offscreen->texture_id = cached_id;
3971 /* We didn't render it offscreen, but hand out an offscreen texture id */
3972 offscreen->was_offscreen = TRUE;
3973 return TRUE;
3974 }
3975
3976 GskGLRenderTarget *render_target;
3977 graphene_matrix_t prev_projection;
3978 graphene_rect_t prev_viewport;
3979 float prev_alpha;
3980 guint prev_fbo;
3981
3982 if (!gsk_gl_driver_create_render_target (self: job->driver,
3983 width: texture_width, height: texture_height,
3984 format: get_target_format (job, node),
3985 min_filter: filter, mag_filter: filter,
3986 render_target: &render_target))
3987 g_assert_not_reached ();
3988
3989 if (gdk_gl_context_has_debug (self: job->command_queue->context))
3990 {
3991 gdk_gl_context_label_object_printf (context: job->command_queue->context,
3992 GL_TEXTURE,
3993 name: render_target->texture_id,
3994 format: "Offscreen<%s> %d",
3995 g_type_name_from_instance (instance: (GTypeInstance *) node),
3996 render_target->texture_id);
3997 gdk_gl_context_label_object_printf (context: job->command_queue->context,
3998 GL_FRAMEBUFFER,
3999 name: render_target->framebuffer_id,
4000 format: "Offscreen<%s> FB %d",
4001 g_type_name_from_instance (instance: (GTypeInstance *) node),
4002 render_target->framebuffer_id);
4003 }
4004
4005 gsk_gl_render_job_set_viewport (job, viewport: &viewport, prev_viewport: &prev_viewport);
4006 gsk_gl_render_job_set_projection_from_rect (job, rect: &job->viewport, prev_projection: &prev_projection);
4007 prev_alpha = gsk_gl_render_job_set_alpha (job, alpha: 1.0f);
4008
4009 prev_fbo = gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: render_target->framebuffer_id);
4010 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
4011
4012 if (offscreen->reset_clip)
4013 gsk_gl_render_job_push_clip (job, rect: &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport));
4014
4015 gsk_gl_render_job_visit_node (job, node);
4016
4017 if (offscreen->reset_clip)
4018 gsk_gl_render_job_pop_clip (job);
4019
4020 if (downscale_x != 1 || downscale_y != 1)
4021 gsk_gl_render_job_pop_modelview (job);
4022
4023 if (flipped_x || flipped_y)
4024 gsk_gl_render_job_pop_modelview (job);
4025
4026 gsk_gl_render_job_set_viewport (job, viewport: &prev_viewport, NULL);
4027 gsk_gl_render_job_set_projection (job, projection: &prev_projection);
4028 gsk_gl_render_job_set_alpha (job, alpha: prev_alpha);
4029 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: prev_fbo);
4030
4031 job->offset_x = offset_x;
4032 job->offset_y = offset_y;
4033
4034 offscreen->was_offscreen = TRUE;
4035 offscreen->texture_id = gsk_gl_driver_release_render_target (self: job->driver,
4036 render_target,
4037 FALSE);
4038
4039 if (!offscreen->do_not_cache)
4040 gsk_gl_driver_cache_texture (self: job->driver, key: &key, texture_id: offscreen->texture_id);
4041
4042 return TRUE;
4043}
4044
4045void
4046gsk_gl_render_job_render_flipped (GskGLRenderJob *job,
4047 GskRenderNode *root)
4048{
4049 graphene_matrix_t proj;
4050 guint framebuffer_id;
4051 guint texture_id;
4052 guint surface_height;
4053
4054 g_return_if_fail (job != NULL);
4055 g_return_if_fail (root != NULL);
4056 g_return_if_fail (GSK_IS_GL_DRIVER (job->driver));
4057
4058 surface_height = job->viewport.size.height;
4059
4060 graphene_matrix_init_ortho (m: &proj,
4061 left: job->viewport.origin.x,
4062 right: job->viewport.origin.x + job->viewport.size.width,
4063 top: job->viewport.origin.y,
4064 bottom: job->viewport.origin.y + job->viewport.size.height,
4065 ORTHO_NEAR_PLANE,
4066 ORTHO_FAR_PLANE);
4067 graphene_matrix_scale (m: &proj, factor_x: 1, factor_y: -1, factor_z: 1);
4068
4069 if (!gsk_gl_command_queue_create_render_target (self: job->command_queue,
4070 MAX (1, job->viewport.size.width),
4071 MAX (1, job->viewport.size.height),
4072 format: job->target_format,
4073 GL_NEAREST, GL_NEAREST,
4074 out_fbo_id: &framebuffer_id, out_texture_id: &texture_id))
4075 return;
4076
4077 /* Setup drawing to our offscreen texture/framebuffer which is flipped */
4078 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: framebuffer_id);
4079 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
4080
4081 /* Visit all nodes creating batches */
4082 gdk_gl_context_push_debug_group (context: job->command_queue->context, message: "Building command queue");
4083 gsk_gl_render_job_visit_node (job, node: root);
4084 gdk_gl_context_pop_debug_group (context: job->command_queue->context);
4085
4086 /* Now draw to our real destination, but flipped */
4087 gsk_gl_render_job_set_alpha (job, alpha: 1.0f);
4088 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: job->framebuffer);
4089 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
4090 gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
4091 gsk_gl_program_set_uniform_texture (self: job->current_program,
4092 key: UNIFORM_SHARED_SOURCE, stamp: 0,
4093 GL_TEXTURE_2D,
4094 GL_TEXTURE0,
4095 texture_id);
4096 gsk_gl_render_job_draw_rect (job, bounds: &job->viewport);
4097 gsk_gl_render_job_end_draw (job);
4098
4099 gdk_gl_context_push_debug_group (context: job->command_queue->context, message: "Executing command queue");
4100 gsk_gl_command_queue_execute (self: job->command_queue, surface_height, scale_factor: 1, NULL, default_framebuffer: job->default_framebuffer);
4101 gdk_gl_context_pop_debug_group (context: job->command_queue->context);
4102
4103 glDeleteFramebuffers (1, &framebuffer_id);
4104 glDeleteTextures (1, &texture_id);
4105}
4106
4107void
4108gsk_gl_render_job_render (GskGLRenderJob *job,
4109 GskRenderNode *root)
4110{
4111 G_GNUC_UNUSED gint64 start_time;
4112 guint scale_factor;
4113 guint surface_height;
4114
4115 g_return_if_fail (job != NULL);
4116 g_return_if_fail (root != NULL);
4117 g_return_if_fail (GSK_IS_GL_DRIVER (job->driver));
4118
4119 scale_factor = MAX (job->scale_x, job->scale_y);
4120 surface_height = job->viewport.size.height;
4121
4122 gsk_gl_command_queue_make_current (self: job->command_queue);
4123
4124 /* Build the command queue using the shared GL context for all renderers
4125 * on the same display.
4126 */
4127 start_time = GDK_PROFILER_CURRENT_TIME;
4128 gdk_gl_context_push_debug_group (context: job->command_queue->context, message: "Building command queue");
4129 gsk_gl_command_queue_bind_framebuffer (self: job->command_queue, framebuffer: job->framebuffer);
4130 if (job->clear_framebuffer)
4131 gsk_gl_command_queue_clear (self: job->command_queue, clear_bits: 0, viewport: &job->viewport);
4132 gsk_gl_render_job_visit_node (job, node: root);
4133 gdk_gl_context_pop_debug_group (context: job->command_queue->context);
4134 gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Build GL command queue", "");
4135
4136#if 0
4137 /* At this point the atlases have uploaded content while we processed
4138 * nodes but have not necessarily been used by the commands in the queue.
4139 */
4140 gsk_gl_driver_save_atlases_to_png (job->driver, NULL);
4141#endif
4142
4143 /* But now for executing the command queue, we want to use the context
4144 * that was provided to us when creating the render job as framebuffer 0
4145 * is bound to that context.
4146 */
4147 start_time = GDK_PROFILER_CURRENT_TIME;
4148 gsk_gl_command_queue_make_current (self: job->command_queue);
4149 gdk_gl_context_push_debug_group (context: job->command_queue->context, message: "Executing command queue");
4150 gsk_gl_command_queue_execute (self: job->command_queue, surface_height, scale_factor, scissor: job->region, default_framebuffer: job->default_framebuffer);
4151 gdk_gl_context_pop_debug_group (context: job->command_queue->context);
4152 gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Execute GL command queue", "");
4153}
4154
4155void
4156gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
4157 gboolean debug_fallback)
4158{
4159 g_return_if_fail (job != NULL);
4160
4161 job->debug_fallback = !!debug_fallback;
4162}
4163
4164static int
4165get_framebuffer_format (GdkGLContext *context,
4166 guint framebuffer)
4167{
4168 int size;
4169
4170 if (!gdk_gl_context_check_version (context, required_gl_major: 0, required_gl_minor: 0, required_gles_major: 3, required_gles_minor: 0))
4171 return GL_RGBA8;
4172
4173 glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
4174 glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
4175 framebuffer ? GL_COLOR_ATTACHMENT0
4176 : gdk_gl_context_get_use_es (context) ? GL_BACK
4177 : GL_BACK_LEFT,
4178 GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &size);
4179
4180 if (size > 16)
4181 return GL_RGBA32F;
4182 else if (size > 8)
4183 return GL_RGBA16F;
4184 else
4185 return GL_RGBA8;
4186}
4187
4188GskGLRenderJob *
4189gsk_gl_render_job_new (GskGLDriver *driver,
4190 const graphene_rect_t *viewport,
4191 float scale_factor,
4192 const cairo_region_t *region,
4193 guint framebuffer,
4194 gboolean clear_framebuffer)
4195{
4196 const graphene_rect_t *clip_rect = viewport;
4197 graphene_rect_t transformed_extents;
4198 GskGLRenderJob *job;
4199 GdkGLContext *context;
4200 GLint default_framebuffer = 0;
4201
4202 g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
4203 g_return_val_if_fail (viewport != NULL, NULL);
4204 g_return_val_if_fail (scale_factor > 0, NULL);
4205
4206 /* Check for non-standard framebuffer binding as we might not be using
4207 * the default framebuffer on systems like macOS where we've bound an
4208 * IOSurface to a GL_TEXTURE_RECTANGLE. Otherwise, no scissor clip will
4209 * be applied in the command queue causing overdrawing.
4210 */
4211 context = driver->command_queue->context;
4212 default_framebuffer = GDK_GL_CONTEXT_GET_CLASS (context)->get_default_framebuffer (context);
4213 if (framebuffer == 0 && default_framebuffer != 0)
4214 framebuffer = default_framebuffer;
4215
4216 job = g_slice_new0 (GskGLRenderJob);
4217 job->driver = g_object_ref (driver);
4218 job->command_queue = job->driver->command_queue;
4219 job->clip = g_array_sized_new (FALSE, FALSE, element_size: sizeof (GskGLRenderClip), reserved_size: 16);
4220 job->modelview = g_array_sized_new (FALSE, FALSE, element_size: sizeof (GskGLRenderModelview), reserved_size: 16);
4221 job->framebuffer = framebuffer;
4222 job->clear_framebuffer = !!clear_framebuffer;
4223 job->default_framebuffer = default_framebuffer;
4224 job->offset_x = 0;
4225 job->offset_y = 0;
4226 job->scale_x = scale_factor;
4227 job->scale_y = scale_factor;
4228 job->viewport = *viewport;
4229 job->target_format = get_framebuffer_format (context: job->command_queue->context, framebuffer);
4230
4231 gsk_gl_render_job_set_alpha (job, alpha: 1.0f);
4232 gsk_gl_render_job_set_projection_from_rect (job, rect: viewport, NULL);
4233 gsk_gl_render_job_set_modelview (job, transform: gsk_transform_scale (NULL, factor_x: scale_factor, factor_y: scale_factor));
4234
4235 /* Setup our initial clip. If region is NULL then we are drawing the
4236 * whole viewport. Otherwise, we need to convert the region to a
4237 * bounding box and clip based on that.
4238 */
4239
4240 if (region != NULL)
4241 {
4242 cairo_rectangle_int_t extents;
4243
4244 cairo_region_get_extents (region, extents: &extents);
4245 gsk_gl_render_job_transform_bounds (job,
4246 rect: &GRAPHENE_RECT_INIT (extents.x,
4247 extents.y,
4248 extents.width,
4249 extents.height),
4250 out_rect: &transformed_extents);
4251 clip_rect = &transformed_extents;
4252 job->region = cairo_region_create_rectangle (rectangle: &extents);
4253 }
4254
4255 gsk_gl_render_job_push_clip (job,
4256 rect: &GSK_ROUNDED_RECT_INIT (clip_rect->origin.x,
4257 clip_rect->origin.y,
4258 clip_rect->size.width,
4259 clip_rect->size.height));
4260
4261 return job;
4262}
4263
4264void
4265gsk_gl_render_job_free (GskGLRenderJob *job)
4266{
4267 job->current_modelview = NULL;
4268 job->current_clip = NULL;
4269
4270 while (job->modelview->len > 0)
4271 {
4272 GskGLRenderModelview *modelview = &g_array_index (job->modelview, GskGLRenderModelview, job->modelview->len-1);
4273 g_clear_pointer (&modelview->transform, gsk_transform_unref);
4274 job->modelview->len--;
4275 }
4276
4277 g_clear_object (&job->driver);
4278 g_clear_pointer (&job->region, cairo_region_destroy);
4279 g_clear_pointer (&job->modelview, g_array_unref);
4280 g_clear_pointer (&job->clip, g_array_unref);
4281 g_slice_free (GskGLRenderJob, job);
4282}
4283

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