From a6079b9b7b3bedb96521ea91b03e41f6206a85ef Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 28 Mar 2018 00:34:23 +0200 Subject: [PATCH] gsk: Implement gsk_render_node_diff() This includes a copy of the diff(1) algorithm used by git diff by Davide Libenzi. It's used for the common case ofcontainer nodes having only very few changes for the few nodes of child widgets that changed (like a button lighting up when hilighted or a spinning spinner). --- gsk/gskdebug.c | 1 + gsk/gskdebugprivate.h | 11 +- gsk/gskdiff.c | 439 +++++++++++++++++++++++++++++ gsk/gskdiffprivate.h | 43 +++ gsk/gskrendernodeimpl.c | 536 +++++++++++++++++++++++++++++++++--- gsk/gskroundedrect.c | 13 + gsk/gskroundedrectprivate.h | 3 + gsk/meson.build | 1 + 8 files changed, 1001 insertions(+), 46 deletions(-) create mode 100644 gsk/gskdiff.c create mode 100644 gsk/gskdiffprivate.h diff --git a/gsk/gskdebug.c b/gsk/gskdebug.c index 40a7969c81..4a347b3563 100644 --- a/gsk/gskdebug.c +++ b/gsk/gskdebug.c @@ -10,6 +10,7 @@ static const GDebugKey gsk_debug_keys[] = { { "vulkan", GSK_DEBUG_VULKAN }, { "fallback", GSK_DEBUG_FALLBACK }, { "glyphcache", GSK_DEBUG_GLYPH_CACHE }, + { "diff", GSK_DEBUG_DIFF }, { "geometry", GSK_DEBUG_GEOMETRY }, { "full-redraw", GSK_DEBUG_FULL_REDRAW}, { "sync", GSK_DEBUG_SYNC }, diff --git a/gsk/gskdebugprivate.h b/gsk/gskdebugprivate.h index 257212e1c1..acf9bf7547 100644 --- a/gsk/gskdebugprivate.h +++ b/gsk/gskdebugprivate.h @@ -14,12 +14,13 @@ typedef enum { GSK_DEBUG_VULKAN = 1 << 5, GSK_DEBUG_FALLBACK = 1 << 6, GSK_DEBUG_GLYPH_CACHE = 1 << 7, + GSK_DEBUG_DIFF = 1 << 8, /* flags below may affect behavior */ - GSK_DEBUG_GEOMETRY = 1 << 8, - GSK_DEBUG_FULL_REDRAW = 1 << 9, - GSK_DEBUG_SYNC = 1 << 10, - GSK_DEBUG_VULKAN_STAGING_IMAGE = 1 << 11, - GSK_DEBUG_VULKAN_STAGING_BUFFER = 1 << 12 + GSK_DEBUG_GEOMETRY = 1 << 9, + GSK_DEBUG_FULL_REDRAW = 1 << 10, + GSK_DEBUG_SYNC = 1 << 11, + GSK_DEBUG_VULKAN_STAGING_IMAGE = 1 << 12, + GSK_DEBUG_VULKAN_STAGING_BUFFER = 1 << 13 } GskDebugFlags; #define GSK_DEBUG_ANY ((1 << 13) - 1) diff --git a/gsk/gskdiff.c b/gsk/gskdiff.c new file mode 100644 index 0000000000..8d6e992a3a --- /dev/null +++ b/gsk/gskdiff.c @@ -0,0 +1,439 @@ +/* + * Copyright © 2003 Davide Libenzi + * 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Davide Libenzi + * Benjamin Otte + */ + +#include "config.h" + +#include "gskdiffprivate.h" + + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX G_MAXSSIZE +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 +#define MAXCOST 20 + +typedef struct _SplitResult { + long i1, i2; + int min_lo, min_hi; +} SplitResult; + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might end up having to expensive + * cases using this algorithm is full, so a little bit of heuristic is needed + * to cut the search and to return a suboptimal point. + */ +static void +split (gconstpointer *elem1, + gssize off1, + gssize lim1, + gconstpointer *elem2, + gssize off2, + gssize lim2, + gssize *kvdf, + gssize *kvdb, + gboolean need_min, + GCompareDataFunc compare_func, + gpointer data, + SplitResult *spl) +{ + gssize dmin = off1 - lim2, dmax = lim1 - off2; + gssize fmid = off1 - off2, bmid = lim1 - lim2; + gboolean odd = (fmid - bmid) & 1; + gssize fmin = fmid, fmax = fmid; + gssize bmin = bmid, bmax = bmid; + gssize ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) + { + gboolean got_snake = FALSE; + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) + { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2; i1++, i2++) + { + if (compare_func (elem1[i1], elem2[i2], data) != 0) + break; + } + if (i1 - prev1 > XDL_SNAKE_CNT) + got_snake = TRUE; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) + { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return; + } + } + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) + { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2; i1--, i2--) + { + if (compare_func (elem1[i1 - 1], elem2[i2 - 1], data) != 0) + break; + } + if (prev1 - i1 > XDL_SNAKE_CNT) + got_snake = TRUE; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) + { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of the, have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > XDL_HEUR_MIN_COST) + { + for (best = 0, d = fmax; d >= fmin; d -= 2) + { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + XDL_SNAKE_CNT <= i1 && i1 < lim1 && + off2 + XDL_SNAKE_CNT <= i2 && i2 < lim2) + { + for (k = 1; ; k++) + { + if (compare_func (elem1[i1 - k], elem2[i2 - k], data) != 0) + break; + if (k == XDL_SNAKE_CNT) + { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + } + if (best > 0) + { + spl->min_lo = 1; + spl->min_hi = 0; + return; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) + { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - XDL_SNAKE_CNT && + off2 < i2 && i2 <= lim2 - XDL_SNAKE_CNT) + { + for (k = 0; ; k++) + { + if (compare_func (elem1[i1 + k], elem2[i2 + k], data) != 0) + break; + + if (k == XDL_SNAKE_CNT - 1) + { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + } + if (best > 0) + { + spl->min_lo = 0; + spl->min_hi = 1; + return; + } + } + + /* + * Enough is enough. We spent too much time here and now we collect + * the furthest reaching path using the (i1 + i2) measure. + */ + if (ec >= MAXCOST) + { + gssize fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) + { + i1 = MIN (kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) + { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) + { + i1 = MAX (off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) + { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) + { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } + else + { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + + return; + } + } +} + +/* + * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling + * the box splitting function. Note that the real job (marking changed lines) + * is done in the two boundary reaching checks. + */ +static void +compare (gconstpointer *elem1, + gssize off1, + gssize lim1, + gconstpointer *elem2, + gssize off2, + gssize lim2, + gssize *kvdf, + gssize *kvdb, + gboolean need_min, + GCompareDataFunc compare_func, + GskKeepFunc keep_func, + GskDeleteFunc delete_func, + GskInsertFunc insert_func, + gpointer data) +{ + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2; off1++, off2++) + { + if (compare_func (elem1[off1], elem2[off2], data) != 0) + break; + + keep_func (elem1[off1], elem2[off2], data); + } + + for (; off1 < lim1 && off2 < lim2; lim1--, lim2--) + { + if (compare_func (elem1[lim1 - 1], elem2[lim2 - 1], data) != 0) + break; + + keep_func (elem1[lim1 - 1], elem2[lim2 - 1], data); + } + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) + { + for (; off2 < lim2; off2++) + { + insert_func (elem2[off2], off2, data); + } + } + else if (off2 == lim2) + { + for (; off1 < lim1; off1++) + { + delete_func (elem1[off1], off1, data); + } + } + else + { + SplitResult spl = { 0, }; + + /* + * Divide ... + */ + split (elem1, off1, lim1, + elem2, off2, lim2, + kvdf, kvdb, need_min, + compare_func, data, + &spl); + /* + * ... et Impera. + */ + compare (elem1, off1, spl.i1, + elem2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, + compare_func, keep_func, delete_func, insert_func, data); + compare (elem1, spl.i1, lim1, + elem2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, + compare_func, keep_func, delete_func, insert_func, data); + } +} + +#if 0 + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; +#endif + +void +gsk_diff (gconstpointer *elem1, + gsize n1, + gconstpointer *elem2, + gsize n2, + GCompareDataFunc compare_func, + GskKeepFunc keep_func, + GskDeleteFunc delete_func, + GskInsertFunc insert_func, + gpointer data) +{ + gsize ndiags; + gssize *kvd, *kvdf, *kvdb; + + ndiags = n1 + n2 + 3; + + kvd = g_new (gssize, 2 * ndiags + 2); + kvdf = kvd; + kvdb = kvd + ndiags; + kvdf += n2 + 1; + kvdb += n2 + 1; + + compare (elem1, 0, n1, + elem2, 0, n2, + kvdf, kvdb, FALSE, + compare_func, keep_func, delete_func, insert_func, data); + + g_free (kvd); +} + diff --git a/gsk/gskdiffprivate.h b/gsk/gskdiffprivate.h new file mode 100644 index 0000000000..1c41e5d02e --- /dev/null +++ b/gsk/gskdiffprivate.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __GSK_DIFF_PRIVATE_H__ +#define __GSK_DIFF_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef void (* GskKeepFunc) (gconstpointer elem1, gconstpointer elem2, gpointer data); +typedef void (* GskDeleteFunc) (gconstpointer elem, gsize idx, gpointer data); +typedef void (* GskInsertFunc) (gconstpointer elem, gsize idx, gpointer data); + +void gsk_diff (gconstpointer *elem1, + gsize n1, + gconstpointer *elem2, + gsize n2, + GCompareDataFunc compare_func, + GskKeepFunc keep_func, + GskDeleteFunc delete_func, + GskInsertFunc insert_func, + gpointer data); + +G_END_DECLS + +#endif /* __GSK_DIFF_PRIVATE_H__ */ diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 15f9479d85..3c32743813 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -22,11 +22,22 @@ #include "gskcairoblurprivate.h" #include "gskdebugprivate.h" +#include "gskdiffprivate.h" #include "gskrendererprivate.h" #include "gskroundedrectprivate.h" #include "gdk/gdktextureprivate.h" +static void +rectangle_init_from_graphene (cairo_rectangle_int_t *cairo, + const graphene_rect_t *graphene) +{ + cairo->x = floorf (graphene->origin.x); + cairo->y = floorf (graphene->origin.y); + cairo->width = ceilf (graphene->origin.x + graphene->size.width) - cairo->x; + cairo->height = ceilf (graphene->origin.y + graphene->size.height) - cairo->y; +} + static gboolean check_variant_type (GVariant *variant, const char *type_string, @@ -44,10 +55,10 @@ check_variant_type (GVariant *variant, } static gboolean -gsk_render_node_can_diff_impossible (GskRenderNode *node1, - GskRenderNode *node2) +gsk_render_node_can_diff_true (GskRenderNode *node1, + GskRenderNode *node2) { - return FALSE; + return TRUE; } /*** GSK_COLOR_NODE ***/ @@ -80,6 +91,21 @@ gsk_color_node_draw (GskRenderNode *node, cairo_fill (cr); } +static void +gsk_color_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskColorNode *self1 = (GskColorNode *) node1; + GskColorNode *self2 = (GskColorNode *) node2; + + if (graphene_rect_equal (&node1->bounds, &node2->bounds) && + gdk_rgba_equal (&self1->color, &self2->color)) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_COLOR_NODE_VARIANT_TYPE "(dddddddd)" static GVariant * @@ -117,8 +143,8 @@ static const GskRenderNodeClass GSK_COLOR_NODE_CLASS = { "GskColorNode", gsk_color_node_finalize, gsk_color_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_color_node_diff, gsk_color_node_serialize, gsk_color_node_deserialize, }; @@ -211,6 +237,39 @@ gsk_linear_gradient_node_draw (GskRenderNode *node, cairo_fill (cr); } +static void +gsk_linear_gradient_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskLinearGradientNode *self1 = (GskLinearGradientNode *) node1; + GskLinearGradientNode *self2 = (GskLinearGradientNode *) node2; + + if (graphene_point_equal (&self1->start, &self2->start) && + graphene_point_equal (&self1->end, &self2->end) && + self1->n_stops == self2->n_stops) + { + gsize i; + + for (i = 0; i < self1->n_stops; i++) + { + GskColorStop *stop1 = &self1->stops[i]; + GskColorStop *stop2 = &self2->stops[i]; + + if (stop1->offset == stop2->offset && + gdk_rgba_equal (&stop1->color, &stop2->color)) + continue; + + gsk_render_node_diff_impossible (node1, node2, region); + return; + } + + return; + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_LINEAR_GRADIENT_NODE_VARIANT_TYPE "(dddddddda(ddddd))" static GVariant * @@ -295,8 +354,8 @@ static const GskRenderNodeClass GSK_LINEAR_GRADIENT_NODE_CLASS = { "GskLinearGradientNode", gsk_linear_gradient_node_finalize, gsk_linear_gradient_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_linear_gradient_node_diff, gsk_linear_gradient_node_serialize, gsk_linear_gradient_node_deserialize, }; @@ -307,8 +366,8 @@ static const GskRenderNodeClass GSK_REPEATING_LINEAR_GRADIENT_NODE_CLASS = { "GskRepeatingLinearGradientNode", gsk_linear_gradient_node_finalize, gsk_linear_gradient_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_linear_gradient_node_diff, gsk_linear_gradient_node_serialize, gsk_repeating_linear_gradient_node_deserialize, }; @@ -533,6 +592,28 @@ gsk_border_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_border_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskBorderNode *self1 = (GskBorderNode *) node1; + GskBorderNode *self2 = (GskBorderNode *) node2; + + if (gsk_rounded_rect_equal (&self1->outline, &self2->outline) && + gdk_rgba_equal (&self1->border_color[0], &self2->border_color[0]) && + gdk_rgba_equal (&self1->border_color[1], &self2->border_color[1]) && + gdk_rgba_equal (&self1->border_color[2], &self2->border_color[2]) && + gdk_rgba_equal (&self1->border_color[3], &self2->border_color[3]) && + self1->border_width[0] == self2->border_width[0] && + self1->border_width[1] == self2->border_width[1] && + self1->border_width[2] == self2->border_width[2] && + self1->border_width[3] == self2->border_width[3]) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_BORDER_NODE_VARIANT_TYPE "(dddddddddddddddddddddddddddddddd)" static GVariant * @@ -598,8 +679,8 @@ static const GskRenderNodeClass GSK_BORDER_NODE_CLASS = { "GskBorderNode", gsk_border_node_finalize, gsk_border_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_border_node_diff, gsk_border_node_serialize, gsk_border_node_deserialize }; @@ -706,6 +787,21 @@ gsk_texture_node_draw (GskRenderNode *node, cairo_surface_destroy (surface); } +static void +gsk_texture_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskTextureNode *self1 = (GskTextureNode *) node1; + GskTextureNode *self2 = (GskTextureNode *) node2; + + if (graphene_rect_equal (&node1->bounds, &node2->bounds) && + self1->texture == self2->texture) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_TEXTURE_NODE_VARIANT_TYPE "(dddduuau)" static GVariant * @@ -787,8 +883,8 @@ static const GskRenderNodeClass GSK_TEXTURE_NODE_CLASS = { "GskTextureNode", gsk_texture_node_finalize, gsk_texture_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_texture_node_diff, gsk_texture_node_serialize, gsk_texture_node_deserialize }; @@ -1240,6 +1336,25 @@ gsk_inset_shadow_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_inset_shadow_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskInsetShadowNode *self1 = (GskInsetShadowNode *) node1; + GskInsetShadowNode *self2 = (GskInsetShadowNode *) node2; + + if (gsk_rounded_rect_equal (&self1->outline, &self2->outline) && + gdk_rgba_equal (&self1->color, &self2->color) && + self1->dx == self2->dx && + self1->dy == self2->dy && + self1->spread == self2->spread && + self1->blur_radius == self2->blur_radius) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_INSET_SHADOW_NODE_VARIANT_TYPE "(dddddddddddddddddddd)" static GVariant * @@ -1295,8 +1410,8 @@ static const GskRenderNodeClass GSK_INSET_SHADOW_NODE_CLASS = { "GskInsetShadowNode", gsk_inset_shadow_node_finalize, gsk_inset_shadow_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_inset_shadow_node_diff, gsk_inset_shadow_node_serialize, gsk_inset_shadow_node_deserialize }; @@ -1543,6 +1658,25 @@ gsk_outset_shadow_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_outset_shadow_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskOutsetShadowNode *self1 = (GskOutsetShadowNode *) node1; + GskOutsetShadowNode *self2 = (GskOutsetShadowNode *) node2; + + if (gsk_rounded_rect_equal (&self1->outline, &self2->outline) && + gdk_rgba_equal (&self1->color, &self2->color) && + self1->dx == self2->dx && + self1->dy == self2->dy && + self1->spread == self2->spread && + self1->blur_radius == self2->blur_radius) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_OUTSET_SHADOW_NODE_VARIANT_TYPE "(dddddddddddddddddddd)" static GVariant * @@ -1598,8 +1732,8 @@ static const GskRenderNodeClass GSK_OUTSET_SHADOW_NODE_CLASS = { "GskOutsetShadowNode", gsk_outset_shadow_node_finalize, gsk_outset_shadow_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_outset_shadow_node_diff, gsk_outset_shadow_node_serialize, gsk_outset_shadow_node_deserialize }; @@ -1867,7 +2001,7 @@ static const GskRenderNodeClass GSK_CAIRO_NODE_CLASS = { "GskCairoNode", gsk_cairo_node_finalize, gsk_cairo_node_draw, - gsk_render_node_can_diff_impossible, + gsk_render_node_can_diff_true, gsk_render_node_diff_impossible, gsk_cairo_node_serialize, gsk_cairo_node_deserialize @@ -2012,6 +2146,60 @@ gsk_container_node_draw (GskRenderNode *node, } } +static gboolean +gsk_container_node_can_diff (GskRenderNode *node1, + GskRenderNode *node2) +{ + return TRUE; +} + +static void +gsk_render_node_add_to_region (GskRenderNode *node, + cairo_region_t *region) +{ + cairo_rectangle_int_t rect; + + rectangle_init_from_graphene (&rect, &node->bounds); + cairo_region_union_rectangle (region, &rect); +} + +static int +gsk_container_node_compare_func (gconstpointer elem1, gconstpointer elem2, gpointer data) +{ + return gsk_render_node_can_diff ((GskRenderNode *) elem1, (GskRenderNode *) elem2) ? 0 : 1; +} + +static void +gsk_container_node_keep_func (gconstpointer elem1, gconstpointer elem2, gpointer data) +{ + gsk_render_node_diff ((GskRenderNode *) elem1, (GskRenderNode *) elem2, data); +} + +static void +gsk_container_node_change_func (gconstpointer elem, gsize idx, gpointer data) +{ + gsk_render_node_add_to_region ((GskRenderNode *) elem, data); +} + +static void +gsk_container_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskContainerNode *self1 = (GskContainerNode *) node1; + GskContainerNode *self2 = (GskContainerNode *) node2; + + gsk_diff ((gconstpointer *) self1->children, + self1->n_children, + (gconstpointer *) self2->children, + self2->n_children, + gsk_container_node_compare_func, + gsk_container_node_keep_func, + gsk_container_node_change_func, + gsk_container_node_change_func, + region); +} + static void gsk_container_node_get_bounds (GskContainerNode *container, graphene_rect_t *bounds) @@ -2098,8 +2286,8 @@ static const GskRenderNodeClass GSK_CONTAINER_NODE_CLASS = { "GskContainerNode", gsk_container_node_finalize, gsk_container_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_container_node_can_diff, + gsk_container_node_diff, gsk_container_node_serialize, gsk_container_node_deserialize }; @@ -2283,7 +2471,7 @@ static const GskRenderNodeClass GSK_TRANSFORM_NODE_CLASS = { "GskTransformNode", gsk_transform_node_finalize, gsk_transform_node_draw, - gsk_render_node_can_diff_impossible, + gsk_render_node_can_diff_true, gsk_render_node_diff_impossible, gsk_transform_node_serialize, gsk_transform_node_deserialize @@ -2378,6 +2566,46 @@ gsk_offset_node_draw (GskRenderNode *node, gsk_render_node_draw (self->child, cr); } +static gboolean +gsk_offset_node_can_diff (GskRenderNode *node1, + GskRenderNode *node2) +{ + GskOffsetNode *self1 = (GskOffsetNode *) node1; + GskOffsetNode *self2 = (GskOffsetNode *) node2; + + return self1->x_offset == self2->x_offset + && self1->y_offset == self2->y_offset + && gsk_render_node_can_diff (self1->child, self2->child); +} + +static void +gsk_offset_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskOffsetNode *self1 = (GskOffsetNode *) node1; + GskOffsetNode *self2 = (GskOffsetNode *) node2; + + if (self1->x_offset == self2->x_offset && + self1->y_offset == self2->y_offset) + { + cairo_region_t *sub; + + if (self1->child == self2->child) + return; + + sub = cairo_region_create (); + gsk_render_node_diff (self1->child, self2->child, sub); + cairo_region_translate (sub, self1->x_offset, self1->y_offset); + cairo_region_union (region, sub); + cairo_region_destroy (sub); + } + else + { + gsk_render_node_diff_impossible (node1, node2, region); + } +} + #define GSK_OFFSET_NODE_VARIANT_TYPE "(dduv)" static GVariant * @@ -2427,8 +2655,8 @@ static const GskRenderNodeClass GSK_OFFSET_NODE_CLASS = { "GskOffsetNode", gsk_offset_node_finalize, gsk_offset_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_offset_node_can_diff, + gsk_offset_node_diff, gsk_offset_node_serialize, gsk_offset_node_deserialize }; @@ -2550,6 +2778,20 @@ gsk_opacity_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_opacity_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskOpacityNode *self1 = (GskOpacityNode *) node1; + GskOpacityNode *self2 = (GskOpacityNode *) node2; + + if (self1->opacity == self2->opacity) + gsk_render_node_diff (self1->child, self2->child, region); + else + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_OPACITY_NODE_VARIANT_TYPE "(duv)" static GVariant * @@ -2598,8 +2840,8 @@ static const GskRenderNodeClass GSK_OPACITY_NODE_CLASS = { "GskOpacityNode", gsk_opacity_node_finalize, gsk_opacity_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_opacity_node_diff, gsk_opacity_node_serialize, gsk_opacity_node_deserialize }; @@ -2835,7 +3077,7 @@ static const GskRenderNodeClass GSK_COLOR_MATRIX_NODE_CLASS = { "GskColorMatrixNode", gsk_color_matrix_node_finalize, gsk_color_matrix_node_draw, - gsk_render_node_can_diff_impossible, + gsk_render_node_can_diff_true, gsk_render_node_diff_impossible, gsk_color_matrix_node_serialize, gsk_color_matrix_node_deserialize @@ -3025,7 +3267,7 @@ static const GskRenderNodeClass GSK_REPEAT_NODE_CLASS = { "GskRepeatNode", gsk_repeat_node_finalize, gsk_repeat_node_draw, - gsk_render_node_can_diff_impossible, + gsk_render_node_can_diff_true, gsk_render_node_diff_impossible, gsk_repeat_node_serialize, gsk_repeat_node_deserialize @@ -3123,6 +3365,30 @@ gsk_clip_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_clip_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskClipNode *self1 = (GskClipNode *) node1; + GskClipNode *self2 = (GskClipNode *) node2; + + if (graphene_rect_equal (&self1->clip, &self2->clip)) + { + cairo_region_t *sub; + cairo_rectangle_int_t clip_rect; + + sub = cairo_region_create(); + gsk_render_node_diff (self1->child, self2->child, sub); + rectangle_init_from_graphene (&clip_rect, &self1->clip); + cairo_region_intersect_rectangle (sub, &clip_rect); + cairo_region_union (region, sub); + cairo_region_destroy (sub); + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_CLIP_NODE_VARIANT_TYPE "(dddduv)" static GVariant * @@ -3172,8 +3438,8 @@ static const GskRenderNodeClass GSK_CLIP_NODE_CLASS = { "GskClipNode", gsk_clip_node_finalize, gsk_clip_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_clip_node_diff, gsk_clip_node_serialize, gsk_clip_node_deserialize }; @@ -3271,6 +3537,30 @@ gsk_rounded_clip_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_rounded_clip_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskRoundedClipNode *self1 = (GskRoundedClipNode *) node1; + GskRoundedClipNode *self2 = (GskRoundedClipNode *) node2; + + if (gsk_rounded_rect_equal (&self1->clip, &self2->clip)) + { + cairo_region_t *sub; + cairo_rectangle_int_t clip_rect; + + sub = cairo_region_create(); + gsk_render_node_diff (self1->child, self2->child, sub); + rectangle_init_from_graphene (&clip_rect, &self1->clip.bounds); + cairo_region_intersect_rectangle (sub, &clip_rect); + cairo_region_union (region, sub); + cairo_region_destroy (sub); + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_ROUNDED_CLIP_NODE_VARIANT_TYPE "(dddddddddddduv)" static GVariant * @@ -3335,8 +3625,8 @@ static const GskRenderNodeClass GSK_ROUNDED_CLIP_NODE_CLASS = { "GskRoundedClipNode", gsk_rounded_clip_node_finalize, gsk_rounded_clip_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_rounded_clip_node_diff, gsk_rounded_clip_node_serialize, gsk_rounded_clip_node_deserialize }; @@ -3457,6 +3747,62 @@ gsk_shadow_node_draw (GskRenderNode *node, cairo_pattern_destroy (pattern); } +static void +gsk_shadow_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskShadowNode *self1 = (GskShadowNode *) node1; + GskShadowNode *self2 = (GskShadowNode *) node2; + int top = 0, right = 0, bottom = 0, left = 0; + cairo_region_t *sub; + cairo_rectangle_int_t rect; + gsize i, n; + + if (self1->n_shadows != self2->n_shadows) + { + gsk_render_node_diff_impossible (node1, node2, region); + return; + } + + for (i = 0; i < self1->n_shadows; i++) + { + GskShadow *shadow1 = &self1->shadows[i]; + GskShadow *shadow2 = &self2->shadows[i]; + float clip_radius; + + if (!gdk_rgba_equal (&shadow1->color, &shadow2->color) || + shadow1->dx != shadow2->dx || + shadow1->dy != shadow2->dy || + shadow1->radius != shadow2->radius) + { + gsk_render_node_diff_impossible (node1, node2, region); + return; + } + + clip_radius = gsk_cairo_blur_compute_pixels (shadow1->radius); + top = MAX (top, ceil (clip_radius - shadow1->dy)); + right = MAX (right, ceil (clip_radius + shadow1->dx)); + bottom = MAX (bottom, ceil (clip_radius + shadow1->dy)); + left = MAX (left, ceil (clip_radius - shadow1->dx)); + } + + sub = cairo_region_create (); + gsk_render_node_diff (self1->child, self2->child, sub); + + n = cairo_region_num_rectangles (sub); + for (i = 0; i < n; i++) + { + cairo_region_get_rectangle (sub, i, &rect); + rect.x -= left; + rect.y -= top; + rect.width += left + right; + rect.height += top + bottom; + cairo_region_union_rectangle (region, &rect); + } + cairo_region_destroy (sub); +} + static void gsk_shadow_node_get_bounds (GskShadowNode *self, graphene_rect_t *bounds) @@ -3560,8 +3906,8 @@ static const GskRenderNodeClass GSK_SHADOW_NODE_CLASS = { "GskShadowNode", gsk_shadow_node_finalize, gsk_shadow_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_shadow_node_diff, gsk_shadow_node_serialize, gsk_shadow_node_deserialize }; @@ -3715,6 +4061,23 @@ gsk_blend_node_draw (GskRenderNode *node, cairo_paint (cr); } +static void +gsk_blend_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskBlendNode *self1 = (GskBlendNode *) node1; + GskBlendNode *self2 = (GskBlendNode *) node2; + + if (self1->blend_mode == self2->blend_mode) + { + gsk_render_node_diff (self1->top, self2->top, region); + gsk_render_node_diff (self1->bottom, self2->bottom, region); + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_BLEND_NODE_VARIANT_TYPE "(uvuvu)" static GVariant * @@ -3776,8 +4139,8 @@ static const GskRenderNodeClass GSK_BLEND_NODE_CLASS = { "GskBlendNode", gsk_blend_node_finalize, gsk_blend_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_blend_node_diff, gsk_blend_node_serialize, gsk_blend_node_deserialize }; @@ -3886,6 +4249,23 @@ gsk_cross_fade_node_draw (GskRenderNode *node, cairo_paint (cr); } +static void +gsk_cross_fade_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskCrossFadeNode *self1 = (GskCrossFadeNode *) node1; + GskCrossFadeNode *self2 = (GskCrossFadeNode *) node2; + + if (self1->progress == self2->progress) + { + gsk_render_node_diff (self1->start, self2->start, region); + gsk_render_node_diff (self1->end, self2->end, region); + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_CROSS_FADE_NODE_VARIANT_TYPE "(uvuvd)" static GVariant * @@ -3948,8 +4328,8 @@ static const GskRenderNodeClass GSK_CROSS_FADE_NODE_CLASS = { "GskCrossFadeNode", gsk_cross_fade_node_finalize, gsk_cross_fade_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_cross_fade_node_diff, gsk_cross_fade_node_serialize, gsk_cross_fade_node_deserialize }; @@ -4068,6 +4448,44 @@ gsk_text_node_draw (GskRenderNode *node, cairo_restore (cr); } +static void +gsk_text_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskTextNode *self1 = (GskTextNode *) node1; + GskTextNode *self2 = (GskTextNode *) node2; + + if (self1->font == self2->font && + gdk_rgba_equal (&self1->color, &self2->color) && + self1->x == self2->x && + self1->y == self2->y && + self1->num_glyphs == self2->num_glyphs) + { + guint i; + + for (i = 0; i < self1->num_glyphs; i++) + { + PangoGlyphInfo *info1 = &self1->glyphs[i]; + PangoGlyphInfo *info2 = &self2->glyphs[i]; + + if (info1->glyph == info2->glyph && + info1->geometry.width == info2->geometry.width && + info1->geometry.x_offset == info2->geometry.x_offset && + info1->geometry.y_offset == info2->geometry.y_offset && + info1->attr.is_cluster_start == info2->attr.is_cluster_start) + continue; + + gsk_render_node_diff_impossible (node1, node2, region); + return; + } + + return; + } + + gsk_render_node_diff_impossible (node1, node2, region); +} + #define GSK_TEXT_NODE_VARIANT_TYPE "(sdddddda(uiiii))" static GVariant * @@ -4171,8 +4589,8 @@ static const GskRenderNodeClass GSK_TEXT_NODE_CLASS = { "GskTextNode", gsk_text_node_finalize, gsk_text_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_text_node_diff, gsk_text_node_serialize, gsk_text_node_deserialize }; @@ -4514,6 +4932,42 @@ gsk_blur_node_draw (GskRenderNode *node, cairo_pattern_destroy (pattern); } +static void +gsk_blur_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskBlurNode *self1 = (GskBlurNode *) node1; + GskBlurNode *self2 = (GskBlurNode *) node2; + + if (self1->radius == self2->radius) + { + cairo_rectangle_int_t rect; + cairo_region_t *sub; + int i, n, clip_radius; + + clip_radius = ceil (gsk_cairo_blur_compute_pixels (self1->radius)); + sub = cairo_region_create (); + gsk_render_node_diff (self1->child, self2->child, sub); + + n = cairo_region_num_rectangles (sub); + for (i = 0; i < n; i++) + { + cairo_region_get_rectangle (sub, i, &rect); + rect.x -= clip_radius; + rect.y -= clip_radius; + rect.width += 2 * clip_radius; + rect.height += 2 * clip_radius; + cairo_region_union_rectangle (region, &rect); + } + cairo_region_destroy (sub); + } + else + { + gsk_render_node_diff_impossible (node1, node2, region); + } +} + #define GSK_BLUR_NODE_VARIANT_TYPE "(duv)" static GVariant * @@ -4558,8 +5012,8 @@ static const GskRenderNodeClass GSK_BLUR_NODE_CLASS = { "GskBlurNode", gsk_blur_node_finalize, gsk_blur_node_draw, - gsk_render_node_can_diff_impossible, - gsk_render_node_diff_impossible, + gsk_render_node_can_diff_true, + gsk_blur_node_diff, gsk_blur_node_serialize, gsk_blur_node_deserialize }; diff --git a/gsk/gskroundedrect.c b/gsk/gskroundedrect.c index d9a4d20f12..4e71e12eab 100644 --- a/gsk/gskroundedrect.c +++ b/gsk/gskroundedrect.c @@ -522,3 +522,16 @@ gsk_rounded_rect_to_float (const GskRoundedRect *self, } } +gboolean +gsk_rounded_rect_equal (gconstpointer rect1, + gconstpointer rect2) +{ + const GskRoundedRect *self1 = rect1; + const GskRoundedRect *self2 = rect2; + + return graphene_rect_equal (&self1->bounds, &self2->bounds) + && graphene_size_equal (&self1->corner[0], &self2->corner[0]) + && graphene_size_equal (&self1->corner[1], &self2->corner[1]) + && graphene_size_equal (&self1->corner[2], &self2->corner[2]) + && graphene_size_equal (&self1->corner[3], &self2->corner[3]); +} diff --git a/gsk/gskroundedrectprivate.h b/gsk/gskroundedrectprivate.h index 5b40cb45bc..52d2276fb6 100644 --- a/gsk/gskroundedrectprivate.h +++ b/gsk/gskroundedrectprivate.h @@ -14,6 +14,9 @@ void gsk_rounded_rect_path (const GskRounde void gsk_rounded_rect_to_float (const GskRoundedRect *self, float rect[12]); +gboolean gsk_rounded_rect_equal (gconstpointer rect1, + gconstpointer rect2); + G_END_DECLS #endif /* __GSK_ROUNDED_RECT_PRIVATE_H__ */ diff --git a/gsk/meson.build b/gsk/meson.build index 0c8b6400ec..2b9115bb2e 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -22,6 +22,7 @@ gsk_private_gl_shaders = [ ] gsk_public_sources = files([ + 'gskdiff.c', 'gskrenderer.c', 'gskrendernode.c', 'gskrendernodeimpl.c',