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).
This commit is contained in:
Benjamin Otte 2018-03-28 00:34:23 +02:00
parent c0db4091cf
commit a6079b9b7b
8 changed files with 1001 additions and 46 deletions

View 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 },

View 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)

439
gsk/gskdiff.c Normal file
View File

@ -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);
}

43
gsk/gskdiffprivate.h Normal file
View File

@ -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__ */

View File

@ -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
};

View 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]);
}

View 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__ */

View File

@ -22,6 +22,7 @@ gsk_private_gl_shaders = [
]
gsk_public_sources = files([
'gskdiff.c',
'gskrenderer.c',
'gskrendernode.c',
'gskrendernodeimpl.c',