--- /dev/null
+/*
+ * 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);
+}
+
#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,
}
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 ***/
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 *
"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,
};
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 *
"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,
};
"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,
};
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 *
"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
};
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 *
"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
};
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 *
"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
};
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 *
"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
};
"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
}
}
+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)
"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
};
"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
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 *
"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
};
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 *
"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
};
"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
"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
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 *
"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
};
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 *
"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
};
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)
"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
};
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 *
"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
};
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 *
"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
};
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 *
"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
};
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 *
"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
};