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 */ |
54 | G_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 | |
80 | typedef struct _GskGLRenderClip |
81 | { |
82 | GskRoundedRect rect; |
83 | guint is_rectilinear : 1; |
84 | guint is_fully_contained : 1; |
85 | } GskGLRenderClip; |
86 | |
87 | typedef 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 | |
99 | struct _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 | |
178 | typedef 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 | |
206 | static void gsk_gl_render_job_visit_node (GskGLRenderJob *job, |
207 | const GskRenderNode *node); |
208 | static gboolean gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, |
209 | const GskRenderNode *node, |
210 | GskGLRenderOffscreen *offscreen); |
211 | |
212 | static inline int |
213 | get_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 | |
222 | static inline void |
223 | init_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 | |
231 | static inline gboolean G_GNUC_PURE |
232 | node_is_invisible (const GskRenderNode *node) |
233 | { |
234 | return node->bounds.size.width == 0.0f || |
235 | node->bounds.size.height == 0.0f; |
236 | } |
237 | |
238 | static inline gboolean G_GNUC_PURE |
239 | rounded_rect_equal (const GskRoundedRect *r1, |
240 | const GskRoundedRect *r2) |
241 | { |
242 | return memcmp (s1: r1, s2: r2, n: sizeof (GskRoundedRect)) == 0; |
243 | } |
244 | |
245 | static inline void |
246 | gsk_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 | |
254 | static inline gboolean G_GNUC_PURE |
255 | node_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 | |
295 | static inline gboolean G_GNUC_PURE |
296 | node_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 | |
330 | static inline gboolean G_GNUC_PURE |
331 | color_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 | |
345 | static inline gboolean G_GNUC_PURE |
346 | rect_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 | |
355 | static inline gboolean |
356 | rounded_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 | |
385 | static inline gboolean G_GNUC_PURE |
386 | rect_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 | |
400 | static inline gboolean |
401 | rounded_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! */ |
408 | static inline gboolean |
409 | intersect_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 | |
464 | static inline void |
465 | init_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 | |
478 | static inline float |
479 | gsk_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 | |
493 | static void |
494 | (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 | |
563 | static void |
564 | gsk_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 | |
595 | static void |
596 | gsk_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 | |
649 | static void |
650 | gsk_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 | |
684 | static void |
685 | gsk_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 | |
706 | static void |
707 | gsk_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 | |
731 | static void |
732 | gsk_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 | |
743 | static inline void |
744 | gsk_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 | |
755 | static inline void |
756 | gsk_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 | |
763 | static inline void |
764 | gsk_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 | |
774 | static inline void |
775 | gsk_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 | |
787 | static inline void |
788 | gsk_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 | |
798 | static inline void |
799 | gsk_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 | |
813 | static inline void |
814 | gsk_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 | |
875 | static inline void |
876 | gsk_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 | |
887 | static inline void |
888 | rounded_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 | |
903 | static inline gboolean |
904 | interval_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 | |
916 | static inline gboolean |
917 | gsk_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 | |
991 | static inline void |
992 | rgba_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 */ |
999 | static void |
1000 | gsk_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 */ |
1022 | static inline void |
1023 | gsk_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 | |
1041 | static inline void |
1042 | gsk_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 */ |
1051 | static inline void |
1052 | gsk_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 | |
1067 | static inline void |
1068 | gsk_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 */ |
1079 | static inline void |
1080 | gsk_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 | } |
1091 | static inline void |
1092 | gsk_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 */ |
1103 | static inline void |
1104 | gsk_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 | |
1119 | static inline void |
1120 | gsk_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 | |
1169 | static inline void |
1170 | gsk_gl_render_job_split_draw (GskGLRenderJob *job) |
1171 | { |
1172 | gsk_gl_command_queue_split_draw (self: job->command_queue); |
1173 | } |
1174 | |
1175 | static inline void |
1176 | gsk_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 | |
1183 | static inline void |
1184 | gsk_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 | |
1301 | static guint |
1302 | blur_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 | |
1413 | static void |
1414 | blur_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_radius * 2.0; /* 2.0 = shader radius_multiplier */ |
1424 | const float = (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 | |
1470 | static inline void |
1471 | gsk_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 | |
1526 | static inline void |
1527 | gsk_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 | |
1560 | static inline void |
1561 | gsk_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 | |
1592 | static inline void |
1593 | gsk_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 | |
1633 | static inline void |
1634 | gsk_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 | |
1686 | static inline void |
1687 | gsk_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 | |
1696 | static inline void |
1697 | gsk_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 | |
1785 | static inline void |
1786 | gsk_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 | |
1832 | static inline void |
1833 | gsk_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 | */ |
1957 | static void |
1958 | gsk_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 | */ |
2007 | static gboolean |
2008 | result_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 | |
2035 | static inline void |
2036 | gsk_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 | |
2159 | static inline void |
2160 | gsk_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 | |
2185 | static inline void |
2186 | gsk_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_radius * 2.0; /* 2.0 = shader radius_multiplier */ |
2196 | float = 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 | |
2356 | static inline void |
2357 | gsk_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 | |
2440 | static inline void |
2441 | gsk_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_radius * 2.0f; /* 2.0 = shader radius_multiplier */ |
2449 | float = blur_extra / 2.0f; |
2450 | int = ceilf (x: half_blur_extra * scale_x); |
2451 | int = 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 | |
2789 | static inline gboolean G_GNUC_PURE |
2790 | equal_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 | |
2804 | static inline void |
2805 | gsk_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 | |
2861 | static gboolean |
2862 | is_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 | |
2913 | static inline void |
2914 | gsk_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 | |
2962 | static inline int |
2963 | compute_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 | |
2986 | static inline void |
2987 | gsk_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 | |
3125 | static inline void |
3126 | gsk_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 | |
3222 | static inline void |
3223 | gsk_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 | |
3274 | static inline void |
3275 | gsk_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 | |
3334 | static inline void |
3335 | gsk_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 | |
3372 | static inline void |
3373 | gsk_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 | |
3383 | static inline void |
3384 | gsk_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 | |
3497 | static void |
3498 | gsk_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 | |
3520 | static inline void |
3521 | gsk_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 | |
3592 | static inline void |
3593 | gsk_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 | |
3647 | static void |
3648 | gsk_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 | |
3837 | static gboolean |
3838 | gsk_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 | |
4045 | void |
4046 | gsk_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 | |
4107 | void |
4108 | gsk_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 | |
4155 | void |
4156 | gsk_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 | |
4164 | static int |
4165 | get_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 | |
4188 | GskGLRenderJob * |
4189 | gsk_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 | |
4264 | void |
4265 | gsk_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 | |