From 98eac8ac830d6c98d3cb187831555b007aaa932b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Feb 2023 01:08:53 +0100 Subject: [PATCH 1/7] Fixes for gdk_memory_texture_new_subtexture checks There were several mistakes here. --- gdk/gdkmemorytexture.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gdk/gdkmemorytexture.c b/gdk/gdkmemorytexture.c index 641386b786..85d9ba0a25 100644 --- a/gdk/gdkmemorytexture.c +++ b/gdk/gdkmemorytexture.c @@ -178,10 +178,10 @@ gdk_memory_texture_new_subtexture (GdkMemoryTexture *source, GBytes *bytes; g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE (source), NULL); - g_return_val_if_fail (x >= 0 || x < GDK_TEXTURE (source)->width, NULL); - g_return_val_if_fail (y >= 0 || y < GDK_TEXTURE (source)->height, NULL); - g_return_val_if_fail (width > 0 || x + width <= GDK_TEXTURE (source)->width, NULL); - g_return_val_if_fail (height > 0 || y + height <= GDK_TEXTURE (source)->height, NULL); + g_return_val_if_fail (x >= 0 && x < GDK_TEXTURE (source)->width, NULL); + g_return_val_if_fail (y >= 0 && y < GDK_TEXTURE (source)->height, NULL); + g_return_val_if_fail (width > 0 && x + width <= GDK_TEXTURE (source)->width, NULL); + g_return_val_if_fail (height > 0 && y + height <= GDK_TEXTURE (source)->height, NULL); texture = GDK_TEXTURE (source); bpp = gdk_memory_format_bytes_per_pixel (texture->format); From 88dd64551cbb82983f780c3e8c9b6b6ebcb1ae7d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Feb 2023 01:23:28 +0100 Subject: [PATCH 2/7] rendernode: Don't do unnecessary stuff Cairo surfaces are created transparent. And even if they weren't, overdrawing with transparency wouldn't erase what's in the surface because it's a no-op. It would require CAIRO_OPERATOR_CLEAR or CAIRO_OPERATOR_SOURCE. --- gsk/gskrendernodeimpl.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 089b2e9ead..a84085c9c6 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -1631,9 +1631,6 @@ gsk_texture_scale_node_draw (GskRenderNode *node, (int) ceilf (node->bounds.size.height)); cr2 = cairo_create (surface2); - cairo_set_source_rgba (cr2, 0, 0, 0, 0); - cairo_paint (cr2); - surface = gdk_texture_download_surface (self->texture); pattern = cairo_pattern_create_for_surface (surface); cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); From 718b5d5fe7ce9ea56f693ce16b822aa8852e0b73 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Feb 2023 01:31:12 +0100 Subject: [PATCH 3/7] rendernode: Clarify some Cairo stuff Split out a function to make it more obvious what's going on. --- gsk/gskrendernodeimpl.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index a84085c9c6..d0bd24b724 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -58,6 +58,16 @@ rectangle_init_from_graphene (cairo_rectangle_int_t *cairo, cairo->height = ceilf (graphene->origin.y + graphene->size.height) - cairo->y; } +static void +_graphene_rect_init_from_clip_extents (graphene_rect_t *rect, + cairo_t *cr) +{ + double x1c, y1c, x2c, y2c; + + cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c); + graphene_rect_init (rect, x1c, y1c, x2c - x1c, y2c - y1c); +} + /* {{{ GSK_COLOR_NODE */ /** @@ -2069,15 +2079,15 @@ gsk_inset_shadow_node_draw (GskRenderNode *node, GskInsetShadowNode *self = (GskInsetShadowNode *) node; GskRoundedRect box, clip_box; int clip_radius; - double x1c, y1c, x2c, y2c; + graphene_rect_t clip_rect; double blur_radius; /* We don't need to draw invisible shadows */ if (gdk_rgba_is_clear (&self->color)) return; - cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c); - if (!gsk_rounded_rect_intersects_rect (&self->outline, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c))) + _graphene_rect_init_from_clip_extents (&clip_rect, cr); + if (!gsk_rounded_rect_intersects_rect (&self->outline, &clip_rect)) return; blur_radius = self->blur_radius / 2; @@ -2365,7 +2375,7 @@ gsk_outset_shadow_node_draw (GskRenderNode *node, GskOutsetShadowNode *self = (GskOutsetShadowNode *) node; GskRoundedRect box, clip_box; int clip_radius; - double x1c, y1c, x2c, y2c; + graphene_rect_t clip_rect; float top, right, bottom, left; double blur_radius; @@ -2373,8 +2383,8 @@ gsk_outset_shadow_node_draw (GskRenderNode *node, if (gdk_rgba_is_clear (&self->color)) return; - cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c); - if (gsk_rounded_rect_contains_rect (&self->outline, &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c))) + _graphene_rect_init_from_clip_extents (&clip_rect, cr); + if (!gsk_rounded_rect_intersects_rect (&self->outline, &clip_rect)) return; blur_radius = self->blur_radius / 2; From 22ba6b1f33dcbcab8d42d133b7f333ca6ff44679 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Feb 2023 01:44:48 +0100 Subject: [PATCH 4/7] rendernode: Respect clip when drawing scale nodes Scale nodes can use large scale factors and we don't want to create insanely huge Cairo surfaces. A subsequent commit will add the texture-scale-magnify-10000x test which fails without this fix. --- gsk/gskrendernodeimpl.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index d0bd24b724..5d1f19da5b 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -1635,10 +1635,19 @@ gsk_texture_scale_node_draw (GskRenderNode *node, }; cairo_t *cr2; cairo_surface_t *surface2; + graphene_rect_t clip_rect; + + /* Make sure we draw the minimum region by using the clip */ + gsk_cairo_rectangle (cr, &node->bounds); + cairo_clip (cr); + _graphene_rect_init_from_clip_extents (&clip_rect, cr); + if (clip_rect.size.width <= 0 || clip_rect.size.height <= 0) + return; surface2 = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - (int) ceilf (node->bounds.size.width), - (int) ceilf (node->bounds.size.height)); + (int) ceilf (clip_rect.size.width), + (int) ceilf (clip_rect.size.height)); + cairo_surface_set_device_offset (surface2, -clip_rect.origin.x, -clip_rect.origin.y); cr2 = cairo_create (surface2); surface = gdk_texture_download_surface (self->texture); From 3a0152b65fd75f631b76e036a53ed7aca209dfa0 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 15 Feb 2023 20:10:58 -0500 Subject: [PATCH 5/7] gl: Respect clip wehn drawing scale nodes Use the same approach and only create an offscreen that is big enough for the clipped part of the scaled texture. If the clipped part is still too large for a single texture, we give up and just render the texture without filters (using the regular texture rendering code path which supports slicing). The following commit will add the texture-scale-magnify-10000x test which fails without this fix. --- gsk/gl/gskglrenderjob.c | 196 +++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 105 deletions(-) diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index 83f928ba5d..1fcd13a1e2 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -872,6 +872,21 @@ gsk_gl_render_job_transform_bounds (GskGLRenderJob *job, } } +static inline void +gsk_gl_render_job_untransform_bounds (GskGLRenderJob *job, + const graphene_rect_t *rect, + graphene_rect_t *out_rect) +{ + GskTransform *transform; + + transform = gsk_transform_invert (gsk_transform_ref (job->current_modelview->transform)); + + gsk_transform_transform_bounds (transform, rect, out_rect); + + out_rect->origin.x -= job->offset_x; + out_rect->origin.y -= job->offset_y; +} + static inline void gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job, const GskRoundedRect *rect, @@ -3615,127 +3630,98 @@ gsk_gl_render_job_visit_texture_scale_node (GskGLRenderJob *job, int min_filter = min_filters[scaling_filter]; int mag_filter = mag_filters[scaling_filter]; int max_texture_size = job->command_queue->max_texture_size; + graphene_rect_t clip_rect; + GskGLRenderTarget *render_target; + GskGLRenderOffscreen offscreen = {0}; + graphene_rect_t viewport; + graphene_rect_t prev_viewport; + graphene_matrix_t prev_projection; + float prev_alpha; + guint prev_fbo; + guint texture_id; + float u0, u1, v0, v1; - if (scaling_filter == GSK_SCALING_FILTER_LINEAR) + gsk_gl_render_job_untransform_bounds (job, &job->current_clip->rect.bounds, &clip_rect); + if (!graphene_rect_intersection (bounds, &clip_rect, &clip_rect)) + return; + + if G_UNLIKELY (clip_rect.size.width > max_texture_size || + clip_rect.size.height > max_texture_size) { gsk_gl_render_job_visit_texture (job, texture, bounds); return; } - if G_LIKELY (texture->width <= max_texture_size && - texture->height <= max_texture_size) + viewport = GRAPHENE_RECT_INIT (0, 0, + clip_rect.size.width, + clip_rect.size.height); + + if (!gsk_gl_driver_create_render_target (job->driver, + (int) ceilf (clip_rect.size.width), + (int) ceilf (clip_rect.size.height), + get_target_format (job, node), + GL_LINEAR, GL_LINEAR, + &render_target)) { - GskGLRenderTarget *render_target; - GskGLRenderOffscreen offscreen = {0}; - graphene_rect_t viewport; - graphene_rect_t prev_viewport; - graphene_matrix_t prev_projection; - float prev_alpha; - guint prev_fbo; - guint texture_id; - - viewport = GRAPHENE_RECT_INIT (0, 0, - bounds->size.width, - bounds->size.height); - - if (!gsk_gl_driver_create_render_target (job->driver, - (int) ceilf (viewport.size.width), - (int) ceilf (viewport.size.height), - get_target_format (job, node), - GL_LINEAR, GL_LINEAR, - &render_target)) - { - /* viewport is too big, slice the texture and try again */ - goto slice; - } - - gsk_gl_render_job_upload_texture (job, texture, min_filter, mag_filter, &offscreen); - - g_assert (offscreen.texture_id); - g_assert (offscreen.was_offscreen == FALSE); - - gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); - gsk_gl_render_job_set_projection_from_rect (job, &viewport, &prev_projection); - gsk_gl_render_job_set_modelview (job, NULL); - prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); - gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (viewport)); - - prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); - gsk_gl_command_queue_clear (job->command_queue, 0, &viewport); - - gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); - gsk_gl_program_set_uniform_texture (job->current_program, - UNIFORM_SHARED_SOURCE, 0, - GL_TEXTURE_2D, - GL_TEXTURE0, - offscreen.texture_id); - gsk_gl_render_job_draw_offscreen (job, &viewport, &offscreen); - gsk_gl_render_job_end_draw (job); - - gsk_gl_render_job_pop_clip (job); - gsk_gl_render_job_pop_modelview (job); - gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); - gsk_gl_render_job_set_projection (job, &prev_projection); - gsk_gl_render_job_set_alpha (job, prev_alpha); - gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); - - texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE); - - gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); - gsk_gl_program_set_uniform_texture (job->current_program, - UNIFORM_SHARED_SOURCE, 0, - GL_TEXTURE_2D, - GL_TEXTURE0, - texture_id); - gsk_gl_render_job_draw_offscreen_rect (job, bounds); - gsk_gl_render_job_end_draw (job); + gsk_gl_render_job_visit_texture (job, texture, bounds); + return; } - else -slice: - { - float min_x = bounds->origin.x; - float min_y = bounds->origin.y; - float max_x = min_x + bounds->size.width; - float max_y = min_y + bounds->size.height; - float scale_x = (max_x - min_x) / texture->width; - float scale_y = (max_y - min_y) / texture->height; - GskGLTextureSlice *slices = NULL; - guint n_slices = 0; - GdkGLContext *context = gsk_gl_driver_get_context (job->driver); - guint rows, cols; - /* Slice enough that neither the original texture nor the scaled texture - * exceed the texture size limit - */ - cols = (int)(MAX (bounds->size.width, texture->width) / (max_texture_size / 4)) + 1; - rows = (int)(MAX (bounds->size.height, texture->height) / (max_texture_size / 4)) + 1; + gsk_gl_render_job_upload_texture (job, texture, min_filter, mag_filter, &offscreen); - gsk_gl_driver_slice_texture (job->driver, texture, GL_NEAREST, GL_NEAREST, cols, rows, &slices, &n_slices); + g_assert (offscreen.texture_id); + g_assert (offscreen.was_offscreen == FALSE); - g_assert (slices != NULL); - g_assert (n_slices > 0); + u0 = (clip_rect.origin.x - bounds->origin.x) / bounds->size.width; + v0 = (clip_rect.origin.y - bounds->origin.y) / bounds->size.height; + u1 = (clip_rect.origin.x + clip_rect.size.width - bounds->origin.x) / bounds->size.width; + v1 = (clip_rect.origin.y + clip_rect.size.height - bounds->origin.y) / bounds->size.height; - for (guint i = 0; i < n_slices; i ++) - { - const GskGLTextureSlice *slice = &slices[i]; - float x1, x2, y1, y2; - GdkTexture *sub_texture; - GskRenderNode *sub_node; + gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); + gsk_gl_render_job_set_projection_from_rect (job, &viewport, &prev_projection); + gsk_gl_render_job_set_modelview (job, NULL); + prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (viewport)); - x1 = min_x + (scale_x * slice->rect.x); - x2 = x1 + (slice->rect.width * scale_x); - y1 = min_y + (scale_y * slice->rect.y); - y2 = y1 + (slice->rect.height * scale_y); + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &viewport); - sub_texture = gdk_gl_texture_new (context, slice->texture_id, slice->rect.width, slice->rect.height, NULL, NULL); + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + gsk_gl_render_job_draw_coords (job, + 0, 0, clip_rect.size.width, clip_rect.size.height, + u0, v0, u1, v1, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); + gsk_gl_render_job_end_draw (job); - sub_node = gsk_texture_scale_node_new (sub_texture, &GRAPHENE_RECT_INIT (x1, y1, x2 - x1, y2 - y1), scaling_filter); + gsk_gl_render_job_pop_clip (job); + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_render_job_set_projection (job, &prev_projection); + gsk_gl_render_job_set_alpha (job, prev_alpha); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); - gsk_gl_render_job_visit_node (job, sub_node); - gsk_render_node_unref (sub_node); - g_object_unref (sub_texture); - } - } + texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE); + + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + gsk_gl_render_job_draw_coords (job, + job->offset_x + clip_rect.origin.x, + job->offset_y + clip_rect.origin.y, + job->offset_x + clip_rect.origin.x + clip_rect.size.width, + job->offset_y + clip_rect.origin.y + clip_rect.size.height, + 0, clip_rect.size.width / ceilf (clip_rect.size.width), + clip_rect.size.height / ceilf (clip_rect.size.height), 0, + (guint16[]){ FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } ); + gsk_gl_render_job_end_draw (job); } static inline void From 8bbf220fdf2f650ca5e76418ec258cf5c818a591 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Feb 2023 01:44:18 +0100 Subject: [PATCH 6/7] testsuite: Add a test for large scale nodes This test fails if we naively create fullscale intermediate offscreens. This was fixed in the previous commits. This tests the fixes in 22ba6b1f33dcbcab8d (for cairo) and 3a0152b65fd75f631b76 (for GL). --- .../compare/texture-scale-magnify-10000x.node | 8 ++++++++ .../gsk/compare/texture-scale-magnify-10000x.png | Bin 0 -> 97 bytes testsuite/gsk/meson.build | 15 ++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 testsuite/gsk/compare/texture-scale-magnify-10000x.node create mode 100644 testsuite/gsk/compare/texture-scale-magnify-10000x.png diff --git a/testsuite/gsk/compare/texture-scale-magnify-10000x.node b/testsuite/gsk/compare/texture-scale-magnify-10000x.node new file mode 100644 index 0000000000..de74a00437 --- /dev/null +++ b/testsuite/gsk/compare/texture-scale-magnify-10000x.node @@ -0,0 +1,8 @@ +clip { + clip: 24995 24995 10 10; + child: texture-scale { + texture: url("data:;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKElEQVQYlWNkYGD4z4AG/v/HEGJgwhDBAQZQIQs2hzMyMtLBauorBACQUgcSISWLRgAAAABJRU5ErkJggg=="); + bounds: 0 0 50000 50000; + filter: nearest; + } +} diff --git a/testsuite/gsk/compare/texture-scale-magnify-10000x.png b/testsuite/gsk/compare/texture-scale-magnify-10000x.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb8412f0e30e4a4551723d77debc8d102a5f648 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4X?VIghDc0JPDx1kalV1=-~ZzR uNePDp4l=R@C~}*%8ygv2I+*oJn}uOQ5WA3~YPTCu4}+(xpUXO@geCxLG#W4f literal 0 HcmV?d00001 diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index d5343da62e..ce007e667b 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -66,19 +66,20 @@ compare_render_tests = [ 'outset_shadow_offset_y', 'outset_shadow_rounded_top', 'outset_shadow_simple', - 'scaled-cairo', - 'scale-textures-negative-ngl', - 'scale-up-down', - 'shadow-in-opacity', - 'texture-url', 'repeat', 'repeat-no-repeat', 'repeat-negative-coords', 'repeat-texture', + 'rounded-clip-in-clip-3d', # not really 3d, but cairo fails it + 'scale-textures-negative-ngl', + 'scale-up-down', + 'scaled-cairo', + 'scaled-texture', + 'shadow-in-opacity', + 'texture-scale-magnify-10000x', + 'texture-url', 'transform-in-transform', 'transform-in-transform-in-transform', - 'rounded-clip-in-clip-3d', # not really 3d, but cairo fails it - 'scaled-texture', ] # these are too sensitive to differences in the renderers From 0581e38b09aae10b918c24cd00d4dff2304e36b1 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 4 Mar 2023 02:50:38 +0100 Subject: [PATCH 7/7] testsuite: Add a test for stripes The GL renderer was creating sripes for nodes that were scaled in particular ways, probably due to rounding errors. This testsuite focuses on one of those stripes to make sure they are gone. --- testsuite/gsk/compare/texture-scale-stripes.node | 8 ++++++++ testsuite/gsk/compare/texture-scale-stripes.png | Bin 0 -> 313 bytes testsuite/gsk/meson.build | 1 + 3 files changed, 9 insertions(+) create mode 100644 testsuite/gsk/compare/texture-scale-stripes.node create mode 100644 testsuite/gsk/compare/texture-scale-stripes.png diff --git a/testsuite/gsk/compare/texture-scale-stripes.node b/testsuite/gsk/compare/texture-scale-stripes.node new file mode 100644 index 0000000000..acd72b2e44 --- /dev/null +++ b/testsuite/gsk/compare/texture-scale-stripes.node @@ -0,0 +1,8 @@ +clip { + clip: 3950 3950 100 100; + child: texture-scale { + bounds: 0 0 19991 19991; + filter: nearest; + texture: url('data:,'); + } +} \ No newline at end of file diff --git a/testsuite/gsk/compare/texture-scale-stripes.png b/testsuite/gsk/compare/texture-scale-stripes.png new file mode 100644 index 0000000000000000000000000000000000000000..e46858c416f2f7592cb5570c9c02159018723197 GIT binary patch literal 313 zcmeAS@N?(olHy`uVBq!ia0vp^DImfep<6 z=RNFY`j#Q8bJNN={lty^%oUPC$sRq5hHXbAh9Bb$>l+`YWBxn0+XI7y!PC{xWt~$( F69DarWmf