From 6cd6da050b786481f52fb7caad1205ba83e4ed39 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 16 Aug 2023 18:46:20 +0300 Subject: [PATCH 1/4] testsuite: Add render node replay tests This takes a render node tree and "replays" it by using the GtkSnapshot machinery. We don't necesserily expect to get back an exactly equal render node tree back, since GtkSnapshot applies various small optimizations where possible, but the original and the replayed nodes should render to identical textures. Signed-off-by: Sergey Bugaev --- testsuite/gsk/compare-render.c | 44 +++ testsuite/gsk/meson.build | 54 ++-- testsuite/gsk/replay-node.c | 488 +++++++++++++++++++++++++++++++++ 3 files changed, 555 insertions(+), 31 deletions(-) create mode 100644 testsuite/gsk/replay-node.c diff --git a/testsuite/gsk/compare-render.c b/testsuite/gsk/compare-render.c index 7ba25ad961..1aeec97afd 100644 --- a/testsuite/gsk/compare-render.c +++ b/testsuite/gsk/compare-render.c @@ -9,6 +9,10 @@ static gboolean flip = FALSE; static gboolean rotate = FALSE; static gboolean repeat = FALSE; static gboolean mask = FALSE; +static gboolean replay = FALSE; + +extern void +replay_node (GskRenderNode *node, GtkSnapshot *snapshot); static const char * get_output_dir (void) @@ -159,6 +163,7 @@ static const GOptionEntry options[] = { { "rotate", 0, 0, G_OPTION_ARG_NONE, &rotate, "Do rotated test", NULL }, { "repeat", 0, 0, G_OPTION_ARG_NONE, &repeat, "Do repeated test", NULL }, { "mask", 0, 0, G_OPTION_ARG_NONE, &mask, "Do masked test", NULL }, + { "replay", 0, 0, G_OPTION_ARG_NONE, &replay, "Do replay test", NULL }, { NULL } }; @@ -487,6 +492,45 @@ main (int argc, char **argv) gsk_render_node_unref (node2); } + if (replay) + { + GskRenderNode *node2; + GdkTexture *rendered_texture2; + graphene_rect_t node_bounds, node2_bounds; + GtkSnapshot *snapshot = gtk_snapshot_new (); + + replay_node (node, snapshot); + node2 = gtk_snapshot_free_to_node (snapshot); + /* If the whole render node tree got eliminated, make sure we have + something to work with nevertheless. */ + if (!node2) + node2 = gsk_container_node_new (NULL, 0); + + gsk_render_node_get_bounds (node, &node_bounds); + gsk_render_node_get_bounds (node2, &node2_bounds); + /* Check that the node didn't grow. */ + success = success && graphene_rect_contains_rect (&node_bounds, &node2_bounds); + + rendered_texture = gsk_renderer_render_texture (renderer, node, &node_bounds); + rendered_texture2 = gsk_renderer_render_texture (renderer, node2, &node_bounds); + g_assert_nonnull (rendered_texture); + g_assert_nonnull (rendered_texture2); + + diff_texture = reftest_compare_textures (rendered_texture, rendered_texture2); + + if (diff_texture) + { + save_image (diff_texture, node_file, "-replayed.diff.png"); + save_node (node2, node_file, "-replayed.node"); + g_object_unref (diff_texture); + success = FALSE; + } + + g_clear_object (&rendered_texture); + g_clear_object (&rendered_texture2); + gsk_render_node_unref (node2); + } + gsk_render_node_unref (node); gsk_renderer_unrealize (renderer); diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index 01c0ec24d5..894bfa0842 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -1,5 +1,5 @@ compare_render = executable('compare-render', - ['compare-render.c', '../reftests/reftest-compare.c'], + ['compare-render.c', '../reftests/reftest-compare.c', 'replay-node.c'], dependencies: libgtk_dep, c_args: common_cflags, ) @@ -148,6 +148,13 @@ foreach renderer : renderers suites += 'wayland_gles_failing' endif + test_env = [ + 'GSK_RENDERER=' + renderer_name, + 'GTK_A11Y=test', + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) + ] + if ((exclude_term == '' or not testname.contains(exclude_term)) and (renderer_name != 'broadway' or broadway_enabled)) test(renderer_name + ' ' + testname, compare_render, @@ -156,12 +163,7 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', testname + '.node'), join_paths(meson.current_source_dir(), 'compare', testname + '.png'), ], - env: [ - 'GSK_RENDERER=' + renderer_name, - 'GTK_A11Y=test', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: suites, ) test(renderer_name + ' ' + testname + ' flipped', compare_render, @@ -171,12 +173,7 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', testname + '.node'), join_paths(meson.current_source_dir(), 'compare', testname + '.png'), ], - env: [ - 'GSK_RENDERER=' + renderer_name, - 'GTK_A11Y=test', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: suites + [ 'gsk-compare-flipped-' + renderer_name ], ) test(renderer_name + ' ' + testname + ' repeated', compare_render, @@ -186,12 +183,7 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', testname + '.node'), join_paths(meson.current_source_dir(), 'compare', testname + '.png'), ], - env: [ - 'GSK_RENDERER=' + renderer_name, - 'GTK_A11Y=test', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: suites + [ 'gsk-compare-repeated-' + renderer_name ], ) test(renderer_name + ' ' + testname + ' rotated', compare_render, @@ -201,12 +193,7 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', testname + '.node'), join_paths(meson.current_source_dir(), 'compare', testname + '.png'), ], - env: [ - 'GSK_RENDERER=' + renderer_name, - 'GTK_A11Y=test', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: suites + [ 'gsk-compare-rotated-' + renderer_name ], ) test(renderer_name + ' ' + testname + ' masked', compare_render, @@ -216,14 +203,19 @@ foreach renderer : renderers join_paths(meson.current_source_dir(), 'compare', testname + '.node'), join_paths(meson.current_source_dir(), 'compare', testname + '.png'), ], - env: [ - 'GSK_RENDERER=' + renderer_name, - 'GTK_A11Y=test', - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) - ], + env: test_env, suite: suites + [ 'gsk-compare-masked-' + renderer_name ], ) + test(renderer_name + ' ' + testname + ' replayed', compare_render, + args: [ + '--replay', + '--output', join_paths(meson.current_build_dir(), 'compare', renderer_name), + join_paths(meson.current_source_dir(), 'compare', testname + '.node'), + join_paths(meson.current_source_dir(), 'compare', testname + '.png'), + ], + env: test_env, + suite: suites + [ 'gsk-compare-replayed-' + renderer_name ], + ) endif endforeach endforeach diff --git a/testsuite/gsk/replay-node.c b/testsuite/gsk/replay-node.c new file mode 100644 index 0000000000..a9b93bfce8 --- /dev/null +++ b/testsuite/gsk/replay-node.c @@ -0,0 +1,488 @@ +#include + +void +replay_node (GskRenderNode *node, GtkSnapshot *snapshot); + +static void +replay_container_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + for (guint i = 0; i < gsk_container_node_get_n_children (node); i++) + replay_node (gsk_container_node_get_child (node, i), snapshot); +} + +static void +replay_cairo_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + cairo_surface_t *surface = gsk_cairo_node_get_surface (node); + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + + cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &bounds); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_paint (cr); +} + +static void +replay_color_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + gtk_snapshot_append_color (snapshot, + gsk_color_node_get_color (node), + &bounds); +} + +static void +replay_linear_gradient_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + graphene_rect_t bounds; + const graphene_point_t *start_point, *end_point; + const GskColorStop *stops; + gsize n_stops; + + gsk_render_node_get_bounds (node, &bounds); + start_point = gsk_linear_gradient_node_get_start (node); + end_point = gsk_linear_gradient_node_get_end (node); + stops = gsk_linear_gradient_node_get_color_stops (node, &n_stops); + + if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE) + gtk_snapshot_append_repeating_linear_gradient (snapshot, &bounds, + start_point, end_point, + stops, n_stops); + else + gtk_snapshot_append_linear_gradient (snapshot, &bounds, + start_point, end_point, + stops, n_stops); +} + +static void +replay_radial_gradient_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + const graphene_point_t *center = gsk_radial_gradient_node_get_center (node); + float hradius = gsk_radial_gradient_node_get_hradius (node); + float vradius = gsk_radial_gradient_node_get_vradius (node); + float start = gsk_radial_gradient_node_get_start (node); + float end = gsk_radial_gradient_node_get_end (node); + gsize n_stops; + const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, + &n_stops); + + if (gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE) + gtk_snapshot_append_repeating_radial_gradient (snapshot, &bounds, center, + hradius, vradius, start, end, + stops, n_stops); + else + gtk_snapshot_append_radial_gradient (snapshot, &bounds, center, + hradius, vradius, start, end, + stops, n_stops); +} + +static void +replay_conic_gradient_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + const graphene_point_t *center = gsk_conic_gradient_node_get_center (node); + float rotation = gsk_conic_gradient_node_get_rotation (node); + gsize n_stops; + const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, + &n_stops); + + gtk_snapshot_append_conic_gradient (snapshot, &bounds, center, + rotation, stops, n_stops); +} + +static void +replay_border_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const GskRoundedRect *outline = gsk_border_node_get_outline (node); + const float *border_width = gsk_border_node_get_widths (node); + const GdkRGBA *border_color = gsk_border_node_get_colors (node); + + gtk_snapshot_append_border (snapshot, outline, border_width, border_color); +} + +static void +replay_texture_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GdkTexture *texture = gsk_texture_node_get_texture (node); + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + + gtk_snapshot_append_texture (snapshot, texture, &bounds); +} + +static void +replay_inset_shadow_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node); + const GdkRGBA *color = gsk_inset_shadow_node_get_color (node); + float dx = gsk_inset_shadow_node_get_dx (node); + float dy = gsk_inset_shadow_node_get_dy (node); + float spread = gsk_inset_shadow_node_get_spread (node); + float blur_radius = gsk_inset_shadow_node_get_blur_radius (node); + + gtk_snapshot_append_inset_shadow (snapshot, outline, color, + dx, dy, spread, blur_radius); +} + +static void +replay_outset_shadow_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node); + const GdkRGBA *color = gsk_outset_shadow_node_get_color (node); + float dx = gsk_outset_shadow_node_get_dx (node); + float dy = gsk_outset_shadow_node_get_dy (node); + float spread = gsk_outset_shadow_node_get_spread (node); + float blur_radius = gsk_outset_shadow_node_get_blur_radius (node); + + gtk_snapshot_append_outset_shadow (snapshot, outline, color, + dx, dy, spread, blur_radius); +} + +static void +replay_transform_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskTransform *transform = gsk_transform_node_get_transform (node); + GskRenderNode *child = gsk_transform_node_get_child (node); + + gtk_snapshot_save (snapshot); + gtk_snapshot_transform (snapshot, transform); + replay_node (child, snapshot); + gtk_snapshot_restore (snapshot); +} + +static void +replay_opacity_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + float opacity = gsk_opacity_node_get_opacity (node); + GskRenderNode *child = gsk_opacity_node_get_child (node); + + gtk_snapshot_push_opacity (snapshot, opacity); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_color_matrix_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node); + const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node); + GskRenderNode *child = gsk_color_matrix_node_get_child (node); + + gtk_snapshot_push_color_matrix (snapshot, matrix, offset); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_repeat_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskRenderNode *child = gsk_repeat_node_get_child (node); + const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node); + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + + gtk_snapshot_push_repeat (snapshot, &bounds, child_bounds); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_clip_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const graphene_rect_t *clip = gsk_clip_node_get_clip (node); + GskRenderNode *child = gsk_clip_node_get_child (node); + + gtk_snapshot_push_clip (snapshot, clip); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_rounded_clip_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const GskRoundedRect *bounds = gsk_rounded_clip_node_get_clip (node); + GskRenderNode *child = gsk_rounded_clip_node_get_child (node); + + gtk_snapshot_push_rounded_clip (snapshot, bounds); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_shadow_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + gsize n_shadows = gsk_shadow_node_get_n_shadows (node); + /* Hack: we know GskShadowNode stores shadows in a contiguous array. */ + const GskShadow *shadow = gsk_shadow_node_get_shadow (node, 0); + GskRenderNode *child = gsk_shadow_node_get_child (node); + + gtk_snapshot_push_shadow (snapshot, shadow, n_shadows); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_blend_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskRenderNode *bottom_child = gsk_blend_node_get_bottom_child (node); + GskRenderNode *top_child = gsk_blend_node_get_top_child (node); + GskBlendMode blend_mode = gsk_blend_node_get_blend_mode (node); + + gtk_snapshot_push_blend (snapshot, blend_mode); + replay_node (bottom_child, snapshot); + gtk_snapshot_pop (snapshot); + replay_node (top_child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_cross_fade_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskRenderNode *start_child = gsk_cross_fade_node_get_start_child (node); + GskRenderNode *end_child = gsk_cross_fade_node_get_end_child (node); + float progress = gsk_cross_fade_node_get_progress (node); + + gtk_snapshot_push_cross_fade (snapshot, progress); + replay_node (start_child, snapshot); + gtk_snapshot_pop (snapshot); + replay_node (end_child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_text_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ +#if 0 + /* The following does not compile, since gtk_snapshot_append_text () is + * not exported. */ + + PangoFont *font = gsk_text_node_get_font (node); + PangoGlyphString glyphs; + guint n_glyphs = 0; + glyphs.glyphs = (PangoGlyphInfo *) gsk_text_node_get_glyphs (node, &n_glyphs); + const GdkRGBA *color = gsk_text_node_get_color (node); + const graphene_point_t *offset = gsk_text_node_get_offset (node); + + glyphs.num_glyphs = n_glyphs; + glyphs.log_clusters = NULL; + + gtk_snapshot_append_text (snapshot, font, glyphs, color, + offset->x, offset->y); +#else + gtk_snapshot_append_node (snapshot, node); +#endif +} + +static void +replay_blur_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + float radius = gsk_blur_node_get_radius (node); + GskRenderNode *child = gsk_blur_node_get_child (node); + + gtk_snapshot_push_blur (snapshot, radius); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_debug_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + const char *message = gsk_debug_node_get_message (node); + GskRenderNode *child = gsk_debug_node_get_child (node); + + gtk_snapshot_push_debug (snapshot, "%s", message); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_gl_shader_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + GskGLShader *shader = gsk_gl_shader_node_get_shader (node); + GBytes *args = gsk_gl_shader_node_get_args (node); + + gtk_snapshot_push_gl_shader (snapshot, shader, &bounds, g_bytes_ref (args)); + for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i++) + { + GskRenderNode *child = gsk_gl_shader_node_get_child (node, i); + replay_node (child, snapshot); + gtk_snapshot_gl_shader_pop_texture (snapshot); + } + gtk_snapshot_pop (snapshot); +} + +static void +replay_texture_scale_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GdkTexture *texture = gsk_texture_scale_node_get_texture (node); + GskScalingFilter filter = gsk_texture_scale_node_get_filter (node); + graphene_rect_t bounds; + gsk_render_node_get_bounds (node, &bounds); + + gtk_snapshot_append_scaled_texture (snapshot, texture, filter, &bounds); +} + +static void +replay_mask_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskMaskMode mask_mode = gsk_mask_node_get_mask_mode (node); + GskRenderNode *source = gsk_mask_node_get_source (node); + GskRenderNode *mask = gsk_mask_node_get_mask (node); + + gtk_snapshot_push_mask (snapshot, mask_mode); + replay_node (mask, snapshot); + gtk_snapshot_pop (snapshot); + replay_node (source, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_fill_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskPath *path = gsk_fill_node_get_path (node); + GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node); + GskRenderNode *child = gsk_fill_node_get_child (node); + + gtk_snapshot_push_fill (snapshot, path, fill_rule); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +replay_stroke_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + GskPath *path = gsk_stroke_node_get_path (node); + const GskStroke *stroke = gsk_stroke_node_get_stroke (node); + GskRenderNode *child = gsk_stroke_node_get_child (node); + + gtk_snapshot_push_stroke (snapshot, path, stroke); + replay_node (child, snapshot); + gtk_snapshot_pop (snapshot); +} + +void +replay_node (GskRenderNode *node, GtkSnapshot *snapshot) +{ + switch (gsk_render_node_get_node_type (node)) + { + case GSK_CONTAINER_NODE: + replay_container_node (node, snapshot); + break; + + case GSK_CAIRO_NODE: + replay_cairo_node (node, snapshot); + break; + + case GSK_COLOR_NODE: + replay_color_node (node, snapshot); + break; + + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + replay_linear_gradient_node (node, snapshot); + break; + + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + replay_radial_gradient_node (node, snapshot); + break; + + case GSK_CONIC_GRADIENT_NODE: + replay_conic_gradient_node (node, snapshot); + break; + + case GSK_BORDER_NODE: + replay_border_node (node, snapshot); + break; + + case GSK_TEXTURE_NODE: + replay_texture_node (node, snapshot); + break; + + case GSK_INSET_SHADOW_NODE: + replay_inset_shadow_node (node, snapshot); + break; + + case GSK_OUTSET_SHADOW_NODE: + replay_outset_shadow_node (node, snapshot); + break; + + case GSK_TRANSFORM_NODE: + replay_transform_node (node, snapshot); + break; + + case GSK_OPACITY_NODE: + replay_opacity_node (node, snapshot); + break; + + case GSK_COLOR_MATRIX_NODE: + replay_color_matrix_node (node, snapshot); + break; + + case GSK_REPEAT_NODE: + replay_repeat_node (node, snapshot); + break; + + case GSK_CLIP_NODE: + replay_clip_node (node, snapshot); + break; + + case GSK_ROUNDED_CLIP_NODE: + replay_rounded_clip_node (node, snapshot); + break; + + case GSK_SHADOW_NODE: + replay_shadow_node (node, snapshot); + break; + + case GSK_BLEND_NODE: + replay_blend_node (node, snapshot); + break; + + case GSK_CROSS_FADE_NODE: + replay_cross_fade_node (node, snapshot); + break; + + case GSK_TEXT_NODE: + replay_text_node (node, snapshot); + break; + + case GSK_BLUR_NODE: + replay_blur_node (node, snapshot); + break; + + case GSK_DEBUG_NODE: + replay_debug_node (node, snapshot); + break; + + case GSK_GL_SHADER_NODE: + replay_gl_shader_node (node, snapshot); + break; + + case GSK_TEXTURE_SCALE_NODE: + replay_texture_scale_node (node, snapshot); + break; + + case GSK_MASK_NODE: + replay_mask_node (node, snapshot); + break; + + case GSK_FILL_NODE: + replay_fill_node (node, snapshot); + break; + + case GSK_STROKE_NODE: + replay_stroke_node (node, snapshot); + break; + + case GSK_NOT_A_RENDER_NODE: + default: + g_assert (FALSE); + } +} From 9f811ccee5e8ea436de48be7656f334fcf290afc Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 17 Aug 2023 12:44:34 +0300 Subject: [PATCH 2/4] testsuite: Add color-matrix-merge testcase This tests the merging of nested color matrix nodes feature of GtkSnapshot, which was broken before commit 082fdfdb241bb5dc38cd2c71c5650fd87ce1dded. Signed-off-by: Sergey Bugaev --- testsuite/gsk/compare/color-matrix-merge.node | 17 +++++++++++++++++ testsuite/gsk/compare/color-matrix-merge.png | Bin 0 -> 144 bytes testsuite/gsk/meson.build | 1 + 3 files changed, 18 insertions(+) create mode 100644 testsuite/gsk/compare/color-matrix-merge.node create mode 100644 testsuite/gsk/compare/color-matrix-merge.png diff --git a/testsuite/gsk/compare/color-matrix-merge.node b/testsuite/gsk/compare/color-matrix-merge.node new file mode 100644 index 0000000000..d4bd7820f2 --- /dev/null +++ b/testsuite/gsk/compare/color-matrix-merge.node @@ -0,0 +1,17 @@ +color-matrix { + matrix: matrix3d(1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1); + offset: -1 0 1 0; + child: color-matrix { + matrix: matrix3d(0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + offset: 1 0 0 0; + child: color { + color: red; + } + } +} diff --git a/testsuite/gsk/compare/color-matrix-merge.png b/testsuite/gsk/compare/color-matrix-merge.png new file mode 100644 index 0000000000000000000000000000000000000000..90927b4981fb7d82105cbe4a80aff56998551ba8 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nETa8DP Date: Wed, 23 Aug 2023 17:39:44 +0300 Subject: [PATCH 3/4] snapshot: Fix push_repeat () with empty child bounds The logic would confuse empty child bounds (in which case nothing should get rendered) with NULL child bounds (in which case the child node's own bounds should get used). In fact, if the child bounds are empty, we can discard the descendant render nodes completely, getting a nice little optimization. Signed-off-by: Sergey Bugaev --- gtk/gtksnapshot.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/gtk/gtksnapshot.c b/gtk/gtksnapshot.c index 9cd4d4ae09..9bedd7f348 100644 --- a/gtk/gtksnapshot.c +++ b/gtk/gtksnapshot.c @@ -717,6 +717,16 @@ gtk_snapshot_collect_repeat (GtkSnapshot *snapshot, return repeat_node; } +static GskRenderNode * +gtk_snapshot_collect_discard_repeat (GtkSnapshot *snapshot, + GtkSnapshotState *state, + GskRenderNode **nodes, + guint n_nodes) +{ + /* Drop the node and return nothing. */ + return NULL; +} + static void gtk_graphene_rect_scale_affine (const graphene_rect_t *rect, float scale_x, @@ -807,17 +817,24 @@ gtk_snapshot_push_repeat (GtkSnapshot *snapshot, const graphene_rect_t *child_bounds) { GtkSnapshotState *state; + gboolean empty_child_bounds = FALSE; graphene_rect_t real_child_bounds = { { 0 } }; float scale_x, scale_y, dx, dy; gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy); if (child_bounds) - gtk_graphene_rect_scale_affine (child_bounds, scale_x, scale_y, dx, dy, &real_child_bounds); + { + gtk_graphene_rect_scale_affine (child_bounds, scale_x, scale_y, dx, dy, &real_child_bounds); + if (real_child_bounds.size.width <= 0 || real_child_bounds.size.height <= 0) + empty_child_bounds = TRUE; + } state = gtk_snapshot_push_state (snapshot, gtk_snapshot_get_current_state (snapshot)->transform, - gtk_snapshot_collect_repeat, + empty_child_bounds + ? gtk_snapshot_collect_discard_repeat + : gtk_snapshot_collect_repeat, NULL); gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.repeat.bounds); From d07a6b85660a13064f5e60df4ba6a076e13a58ef Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 23 Aug 2023 18:13:16 +0300 Subject: [PATCH 4/4] snapshot, gsktransform: Mention that rotation happens around (0, 0) ...and not around the center of the render node, as one could expect given that the render node syntax for rotation, transform: rotate(90);, happens to match the CSS syntax for the same thing, and CSS does rotate around the center by default. Signed-off-by: Sergey Bugaev --- gsk/gsktransform.c | 3 ++- gtk/gtksnapshot.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gsk/gsktransform.c b/gsk/gsktransform.c index 4f7174d349..ada7164e37 100644 --- a/gsk/gsktransform.c +++ b/gsk/gsktransform.c @@ -857,7 +857,8 @@ normalize_angle (float angle) * @next: (nullable) (transfer full): the next transform * @angle: the rotation angle, in degrees (clockwise) * - * Rotates @next @angle degrees in 2D - or in 3D-speak, around the z axis. + * Rotates @next @angle degrees in 2D - or in 3D-speak, around the Z axis. + * The rotation happens around the origin point of (0, 0). * * Returns: (nullable): The new transform */ diff --git a/gtk/gtksnapshot.c b/gtk/gtksnapshot.c index 9bedd7f348..3f009142bf 100644 --- a/gtk/gtksnapshot.c +++ b/gtk/gtksnapshot.c @@ -2068,9 +2068,10 @@ gtk_snapshot_translate_3d (GtkSnapshot *snapshot, * @angle: the rotation angle, in degrees (clockwise) * * Rotates @@snapshot's coordinate system by @angle degrees in 2D space - - * or in 3D speak, rotates around the Z axis. + * or in 3D speak, rotates around the Z axis. The rotation happens around + * the origin point of (0, 0) in the @snapshot's current coordinate system. * - * To rotate around other axes, use [method@Gsk.Transform.rotate_3d]. + * To rotate around axes other than the Z axis, use [method@Gsk.Transform.rotate_3d]. */ void gtk_snapshot_rotate (GtkSnapshot *snapshot,