From f502743621cf57b40f737fa5e77b4e54df51a1ea Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 6 May 2024 06:49:40 -0400 Subject: [PATCH] gpu: Never create empty images Creating a Vulkan image with width or height zero is an error, so check at the time when we compute the device pixel rect. The node file in the included test was devised by Benjamin Otte. Fixes: #6691 --- gsk/gpu/gskgpunodeprocessor.c | 54 ++++++++++++++-------- testsuite/gsk/compare/empty-fallback.node | 41 ++++++++++++++++ testsuite/gsk/compare/empty-fallback.png | Bin 0 -> 81 bytes testsuite/gsk/meson.build | 1 + 4 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 testsuite/gsk/compare/empty-fallback.node create mode 100644 testsuite/gsk/compare/empty-fallback.png diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index dce0ddf397..d5498dab6b 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -311,7 +311,7 @@ gsk_gpu_node_processor_add_images (GskGpuNodeProcessor *self, while (desc != self->desc); } -static void +static gboolean G_GNUC_WARN_UNUSED_RESULT rect_round_to_pixels (const graphene_rect_t *src, const graphene_vec2_t *pixel_scale, const graphene_point_t *pixel_offset, @@ -331,6 +331,8 @@ rect_round_to_pixels (const graphene_rect_t *src, y * inv_yscale - pixel_offset->y, (ceilf ((src->origin.x + pixel_offset->x + src->size.width) * xscale) - x) * inv_xscale, (ceilf ((src->origin.y + pixel_offset->y + src->size.height) * yscale) - y) * inv_yscale); + + return ceilf (xscale * dest->size.width) > 0 && ceilf (yscale * dest->size.height) > 0; } static GskGpuImage * @@ -963,7 +965,9 @@ gsk_gpu_node_processor_get_node_as_image (GskGpuNodeProcessor *self, if (!gsk_rect_intersection (clip_bounds, &node->bounds, &clip)) return NULL; } - rect_round_to_pixels (&clip, &self->scale, &self->offset, &clip); + + if (!rect_round_to_pixels (&clip, &self->scale, &self->offset, &clip)) + return NULL; image = gsk_gpu_get_node_as_image (self->frame, &clip, @@ -1020,7 +1024,8 @@ gsk_gpu_node_processor_blur_op (GskGpuNodeProcessor *self, if (!gsk_rect_intersection (rect, &clip_rect, &intermediate_rect)) return; - rect_round_to_pixels (&intermediate_rect, &self->scale, &self->offset, &intermediate_rect); + if (!rect_round_to_pixels (&intermediate_rect, &self->scale, &self->offset, &intermediate_rect)) + return; intermediate = gsk_gpu_node_processor_init_draw (&other, self->frame, @@ -1083,7 +1088,8 @@ gsk_gpu_node_processor_add_fallback_node (GskGpuNodeProcessor *self, if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clipped_bounds)) return; - rect_round_to_pixels (&clipped_bounds, &self->scale, &self->offset, &clipped_bounds); + if (!rect_round_to_pixels (&clipped_bounds, &self->scale, &self->offset, &clipped_bounds)) + return; gsk_gpu_node_processor_sync_globals (self, 0); @@ -1439,7 +1445,9 @@ gsk_gpu_node_processor_add_rounded_clip_node_with_mask (GskGpuNodeProcessor *sel if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds)) return; - rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds); + + if (!rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds)) + return; child_image = gsk_gpu_node_processor_get_node_as_image (self, 0, @@ -2068,7 +2076,9 @@ gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self, gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds); /* first round to pixel boundaries, so we make sure the full pixels are covered */ - rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds); + if (!rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds)) + return; + /* then expand by half a pixel so that pixels needed for eventual linear * filtering are available */ graphene_rect_inset (&clip_bounds, -0.5, -0.5); @@ -2276,7 +2286,9 @@ gsk_gpu_node_processor_add_gradient_node (GskGpuNodeProcessor *self, if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &bounds)) return; - rect_round_to_pixels (&bounds, &self->scale, &self->offset, &bounds); + + if (!rect_round_to_pixels (&bounds, &self->scale, &self->offset, &bounds)) + return; image = gsk_gpu_node_processor_init_draw (&other, self->frame, @@ -3581,7 +3593,9 @@ gsk_gpu_node_processor_add_fill_node (GskGpuNodeProcessor *self, if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds)) return; - rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds); + + if (!rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds)) + return; child = gsk_fill_node_get_child (node); @@ -3678,7 +3692,9 @@ gsk_gpu_node_processor_add_stroke_node (GskGpuNodeProcessor *self, if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds)) return; - rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds); + + if (!rect_round_to_pixels (&clip_bounds, &self->scale, &self->offset, &clip_bounds)) + return; child = gsk_stroke_node_get_child (node); @@ -4111,15 +4127,17 @@ gsk_gpu_node_processor_create_node_pattern (GskGpuPatternWriter *self, gsk_gpu_descriptors_set_size (self->desc, images_before, buffers_before); } - rect_round_to_pixels (&GRAPHENE_RECT_INIT ( - self->bounds.origin.x - self->offset.x, - self->bounds.origin.y - self->offset.y, - self->bounds.size.width, - self->bounds.size.height - ), - &self->scale, - &self->offset, - &bounds); + if (!rect_round_to_pixels (&GRAPHENE_RECT_INIT ( + self->bounds.origin.x - self->offset.x, + self->bounds.origin.y - self->offset.y, + self->bounds.size.width, + self->bounds.size.height + ), + &self->scale, + &self->offset, + &bounds)) + return TRUE; + image = gsk_gpu_get_node_as_image (self->frame, &bounds, &self->scale, diff --git a/testsuite/gsk/compare/empty-fallback.node b/testsuite/gsk/compare/empty-fallback.node new file mode 100644 index 0000000000..c6b2c32632 --- /dev/null +++ b/testsuite/gsk/compare/empty-fallback.node @@ -0,0 +1,41 @@ +clip { + /* Numbers that trigger rounding errors at 125%: + 9, 13, 18, 21, 26, 31, 36, 42, 47, 52, 57, 62, 67, 72, 77, 84, 89, 94, 99, ... + */ + clip: 0 0 9 13; + child: transform { + /* This is a typical fractional scale factor. It is 5/4. + The inverse is 4/5, and that number cannot be represented as + a float. + The renderer does however operate in the scaled coordinate system, + So this will translate the clip rect above to the scaled clip rect + 0 0 7.20000029 10.4000006 + */ + transform: scale(1.25); + child: container { + /* This color node exists just so that the bounds of the clipped node + are large enough. Otherwise the node bounds computation might end + up detecting that the clip is actually empty, because it scales + the other way: It multiplies by 1.25, not by 0.8. + */ + color { + color: black; + bounds: 0 0 50 50; + } + /* This node has bounds of 7.2 0 10 10 - which gets translated to + 7.19999981 0 10.000001 10 during parsing of the path. + + And because 7.19999981 < 7.20000029 this node might not be considered + fully clipped, even though it really should be. + */ + fill { + path: "M 7.2 0 l 10 0 l 0 10 l -10 0 z"; + child: color { + color: red; + bounds: 0 0 50 50; + } + } + } + } +} + diff --git a/testsuite/gsk/compare/empty-fallback.png b/testsuite/gsk/compare/empty-fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..98a196c403c0820513c5f404a68fb197d1a0b5f0 GIT binary patch literal 81 zcmeAS@N?(olHy`uVBq!ia0vp^oFFy_8<4DKZ~;*go-U3d5>t~?5)yu#cVI9O3^G`i caY2iT;cEkzopr0ADE*lK=n! literal 0 HcmV?d00001 diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index 8bbf550570..3d2149ed22 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -73,6 +73,7 @@ compare_render_tests = [ 'empty-shadow', 'empty-texture', 'empty-transform', + 'empty-fallback', 'fill', 'fill2', 'fill-clipped-nogl',