gsk: Implement gsk_render_node_diff()
authorBenjamin Otte <otte@redhat.com>
Tue, 27 Mar 2018 22:34:23 +0000 (00:34 +0200)
committerBenjamin Otte <otte@redhat.com>
Thu, 5 Apr 2018 12:56:38 +0000 (14:56 +0200)
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
gsk/gskdebugprivate.h
gsk/gskdiff.c [new file with mode: 0644]
gsk/gskdiffprivate.h [new file with mode: 0644]
gsk/gskrendernodeimpl.c
gsk/gskroundedrect.c
gsk/gskroundedrectprivate.h
gsk/meson.build

index 40a7969c810e135ba967f7f9cb18e2a443293d18..4a347b35632a5ae8366a8bd9b9855aaa4a3feb39 100644 (file)
@@ -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 },
index 257212e1c1e3708f3eb37ea6fd308dbbbd6a50cf..acf9bf7547841180e5cfaa074b6bea7e0cc392a4 100644 (file)
@@ -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 (file)
index 0000000..8d6e992
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Davide Libenzi <davidel@xmailserver.org>
+ *          Benjamin Otte <otte@gnome.org>
+ */
+
+#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 (file)
index 0000000..1c41e5d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GSK_DIFF_PRIVATE_H__
+#define __GSK_DIFF_PRIVATE_H__
+
+#include <glib.h>
+
+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__ */
index 15f9479d85ef57f7c835b01f63d9ff4b50abaf55..3c32743813eae7bc6c7f8b1cc2e6a43e93e48ce7 100644 (file)
 
 #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
 };
index d9a4d20f12dece8b742b872cca4e93a969940b7e..4e71e12eab02cd6a64b3bf1bf200ac7997127d2a 100644 (file)
@@ -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]);
+}
index 5b40cb45bc564124c343a3e2dc321fa5a425f37d..52d2276fb6d17487dbb091affe71d5bb0d180883 100644 (file)
@@ -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__ */
index 0c8b6400ec74e79f6636fdee45ccd7767ccd0425..2b9115bb2e8c2a3dbf30ceb02f468e8a8a72ce5b 100644 (file)
@@ -22,6 +22,7 @@ gsk_private_gl_shaders = [
 ]
 
 gsk_public_sources = files([
+  'gskdiff.c',
   'gskrenderer.c',
   'gskrendernode.c',
   'gskrendernodeimpl.c',