[gegl] graph: avoid visiting nodes multiple times during invalidation
- From: N/A <ell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] graph: avoid visiting nodes multiple times during invalidation
- Date: Thu, 9 Nov 2017 23:34:50 +0000 (UTC)
commit 3d5f4fd38f1b60d5774259062fb64abacf10a7b2
Author: Ell <ell_se yahoo com>
Date: Thu Nov 9 17:38:43 2017 -0500
graph: avoid visiting nodes multiple times during invalidation
Don't connect nodes to the INVALIDATED signal of their sources in
order to propgagte invalidation throughout the graph. This has the
same scalability issues outlined in the previous commit.
Instead, when invalidating a node, use GeglVisitor to traverse the
output graph in breadth-first order, firing INVALIDATED signals as
we go, lumping dirty regions along the way.
This commit adds two alternative implementations for the
invalidation alogrithm. One uses GeglRegions, which is more
accurate but has higher overhead, and the other uses
GeglRectangles, which is more coarse but has lower overhead. We
use the GeglRectangle version for now, since it should probably be
sufficient in practice, but keep the GeglRegion version around just
in case.
gegl/graph/gegl-node.c | 217 +++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 186 insertions(+), 31 deletions(-)
---
diff --git a/gegl/graph/gegl-node.c b/gegl/graph/gegl-node.c
index ecc240f..5d997d2 100644
--- a/gegl/graph/gegl-node.c
+++ b/gegl/graph/gegl-node.c
@@ -35,8 +35,11 @@
#include "gegl-visitable.h"
#include "gegl-config.h"
+#include "buffer/gegl-region.h"
+
#include "graph/gegl-visitor.h"
#include "graph/gegl-callback-visitor.h"
+#include "graph/gegl-node-output-visitable.h"
#include "operation/gegl-operation.h"
#include "operation/gegl-operations.h"
@@ -583,36 +586,206 @@ gegl_node_connect_to (GeglNode *source,
return gegl_node_connect_from (sink, sink_pad_name, source, source_pad_name);
}
+/* the implementation of gegl_node_invalidated() can use either GeglRegions
+ * or GeglRectangles (bounding boxes) for calculating the invadilated areas
+ * of the nodes in the graph. The GeglRegion version is more granular,
+ * invalidating exact areas, but has higher overhead, while the GeglRectangle
+ * version in more coarse, potentially over-invalidating (but never under-
+ * invalidating), but has lower overhead.
+ *
+ * TODO: decide if the higher overhead of the GeglRegion version is worth it.
+ */
+/* #define GEGL_NODE_INVALIDATED_USE_REGIONS */
+
+#ifdef GEGL_NODE_INVALIDATED_USE_REGIONS
+
+static gboolean
+gegl_node_invalidated_callback (GeglNode *source,
+ gpointer data)
+{
+ GHashTable *regions = data;
+ GeglRegion *source_region = g_hash_table_lookup (regions, source);
+ GeglRectangle *source_rects;
+ gint n_source_rects;
+ GSList *iter;
+ gint i;
+
+ gegl_region_get_rectangles (source_region,
+ &source_rects, &n_source_rects);
+
+ for (i = 0; i < n_source_rects; i++)
+ {
+ if (source->cache)
+ gegl_cache_invalidate (source->cache, &source_rects[i]);
+
+ source->valid_have_rect = FALSE;
+
+ g_signal_emit (source, gegl_node_signals[INVALIDATED], 0,
+ &source_rects[i], NULL);
+ }
+
+ for (iter = source->priv->sink_connections; iter; iter = g_slist_next (iter))
+ {
+ GeglConnection *connection = iter->data;
+ GeglNode *sink = gegl_connection_get_sink_node (connection);
+ GeglPad *sink_pad = gegl_connection_get_sink_pad (connection);
+ GeglRegion *sink_region = g_hash_table_lookup (regions, sink);
+
+ if (! sink_region)
+ {
+ sink_region = gegl_region_new ();
+
+ g_hash_table_insert (regions, sink, sink_region);
+ }
+
+ if (sink->operation)
+ {
+ for (i = 0; i < n_source_rects; i++)
+ {
+ GeglRectangle invalidated_rect;
+
+ invalidated_rect = gegl_operation_get_invalidated_by_change (
+ sink->operation,
+ gegl_pad_get_name (sink_pad), &source_rects[i]);
+
+ gegl_region_union_with_rect (sink_region, &invalidated_rect);
+ }
+ }
+ else
+ {
+ gegl_region_union (sink_region, source_region);
+ }
+ }
+
+ g_free (source_rects);
+
+ return FALSE;
+}
+
void
gegl_node_invalidated (GeglNode *node,
const GeglRectangle *rect,
gboolean clear_cache)
{
+ GHashTable *regions;
+ GeglVisitor *visitor;
+ GeglVisitable *visitable;
+
g_return_if_fail (GEGL_IS_NODE (node));
if (!rect)
rect = &node->have_rect;
- if (node->cache)
+ if (node->cache && clear_cache)
+ gegl_buffer_clear (GEGL_BUFFER (node->cache), rect);
+
+ regions = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) gegl_region_destroy);
+
+ g_hash_table_insert (regions, node, gegl_region_rectangle (rect));
+
+ visitor = gegl_callback_visitor_new (gegl_node_invalidated_callback,
+ regions);
+ visitable = gegl_node_output_visitable_new (node);
+
+ gegl_visitor_bfs_traverse (visitor, visitable);
+
+ g_object_unref (visitable);
+ g_object_unref (visitor);
+ g_hash_table_unref (regions);
+}
+
+#else /* ! GEGL_NODE_INVALIDATED_USE_REGIONS */
+
+static gboolean
+gegl_node_invalidated_callback (GeglNode *source,
+ gpointer data)
+{
+ GHashTable *rects = data;
+ const GeglRectangle *source_rect = g_hash_table_lookup (rects, source);
+ GSList *iter;
+
+ if (source->cache)
+ gegl_cache_invalidate (source->cache, source_rect);
+
+ source->valid_have_rect = FALSE;
+
+ g_signal_emit (source, gegl_node_signals[INVALIDATED], 0,
+ source_rect, NULL);
+
+ for (iter = source->priv->sink_connections; iter; iter = g_slist_next (iter))
{
- if (rect && clear_cache)
- gegl_buffer_clear (GEGL_BUFFER (node->cache), rect);
+ GeglConnection *connection = iter->data;
+ GeglNode *sink = gegl_connection_get_sink_node (connection);
+ GeglPad *sink_pad = gegl_connection_get_sink_pad (connection);
+ GeglRectangle *sink_rect = g_hash_table_lookup (rects, sink);
+
+ if (! sink_rect)
+ {
+ sink_rect = gegl_rectangle_new (0, 0, 0, 0);
+
+ g_hash_table_insert (rects, sink, sink_rect);
+ }
+
+ if (sink->operation)
+ {
+ GeglRectangle invalidated_rect;
- gegl_cache_invalidate (node->cache, rect);
+ invalidated_rect = gegl_operation_get_invalidated_by_change (
+ sink->operation,
+ gegl_pad_get_name (sink_pad), source_rect);
+
+ gegl_rectangle_bounding_box (sink_rect, sink_rect, &invalidated_rect);
+ }
+ else
+ {
+ gegl_rectangle_bounding_box (sink_rect, sink_rect, source_rect);
+ }
}
- node->valid_have_rect = FALSE;
- g_signal_emit (node, gegl_node_signals[INVALIDATED], 0,
- rect, NULL);
+ return FALSE;
}
+void
+gegl_node_invalidated (GeglNode *node,
+ const GeglRectangle *rect,
+ gboolean clear_cache)
+{
+ GHashTable *rects;
+ GeglVisitor *visitor;
+ GeglVisitable *visitable;
+
+ g_return_if_fail (GEGL_IS_NODE (node));
+
+ if (!rect)
+ rect = &node->have_rect;
+
+ if (node->cache && clear_cache)
+ gegl_buffer_clear (GEGL_BUFFER (node->cache), rect);
+
+ rects = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+
+ g_hash_table_insert (rects, node, g_memdup (rect, sizeof (GeglRectangle)));
+
+ visitor = gegl_callback_visitor_new (gegl_node_invalidated_callback,
+ rects);
+ visitable = gegl_node_output_visitable_new (node);
+
+ gegl_visitor_bfs_traverse (visitor, visitable);
+
+ g_object_unref (visitable);
+ g_object_unref (visitor);
+ g_hash_table_unref (rects);
+}
+
+#endif /* ! GEGL_NODE_INVALIDATED_USE_REGIONS */
+
static void
gegl_node_source_invalidated (GeglNode *source,
- const GeglRectangle *rect,
- gpointer data)
+ GeglPad *destination_pad,
+ const GeglRectangle *rect)
{
- GeglPad *destination_pad = GEGL_PAD (data);
- GeglNode *destination = gegl_pad_get_node (destination_pad);
+ GeglNode *destination = gegl_pad_get_node (destination_pad);
GeglRectangle dirty_rect;
GEGL_NOTE (GEGL_DEBUG_INVALIDATION, "%s.%s is dirtied from %s (%i,%i %i×%i)",
@@ -722,10 +895,7 @@ gegl_node_connect_from (GeglNode *sink,
real_sink->priv->source_connections = g_slist_prepend (real_sink->priv->source_connections,
connection);
real_source->priv->sink_connections = g_slist_prepend (real_source->priv->sink_connections,
connection);
- g_signal_connect (G_OBJECT (real_source), "invalidated",
- G_CALLBACK (gegl_node_source_invalidated), sink_pad);
-
- gegl_node_source_invalidated (real_source, &real_source->have_rect, sink_pad);
+ gegl_node_source_invalidated (real_source, sink_pad, &real_source->have_rect);
return TRUE;
}
@@ -767,20 +937,7 @@ gegl_node_disconnect (GeglNode *sink,
source_pad = gegl_connection_get_source_pad (connection);
source = gegl_connection_get_source_node (connection);
- gegl_node_source_invalidated (source, &source->have_rect, sink_pad);
-
- {
- /* disconnecting dirt propagation */
- gulong handler;
-
- handler = g_signal_handler_find (source, G_SIGNAL_MATCH_DATA,
- gegl_node_signals[INVALIDATED],
- 0, NULL, NULL, sink_pad);
- if (handler)
- {
- g_signal_handler_disconnect (source, handler);
- }
- }
+ gegl_node_source_invalidated (source, sink_pad, &source->have_rect);
gegl_pad_disconnect (sink_pad, source_pad, connection);
@@ -1706,8 +1863,6 @@ gegl_node_insert_before (GeglNode *self,
other = gegl_node_get_producer (self, "input", NULL); /*XXX: handle pad name */
rectangle = gegl_node_get_bounding_box (to_be_inserted);
- g_signal_handlers_block_matched (other, G_SIGNAL_MATCH_FUNC, 0, 0, 0, gegl_node_source_invalidated, NULL);
- /* the blocked handler disappears during the relinking */
gegl_node_link_many (other, to_be_inserted, self, NULL);
/* emit the change ourselves */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]