Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenlib/BLI_task.h76
-rw-r--r--source/blender/blenlib/CMakeLists.txt1
-rw-r--r--source/blender/blenlib/intern/task_graph.cc161
-rw-r--r--tests/gtests/blenlib/BLI_task_graph_test.cc188
-rw-r--r--tests/gtests/blenlib/CMakeLists.txt1
5 files changed, 427 insertions, 0 deletions
diff --git a/source/blender/blenlib/BLI_task.h b/source/blender/blenlib/BLI_task.h
index 64dfdc2ad25..a4a855c354b 100644
--- a/source/blender/blenlib/BLI_task.h
+++ b/source/blender/blenlib/BLI_task.h
@@ -237,6 +237,82 @@ BLI_INLINE void BLI_parallel_range_settings_defaults(TaskParallelSettings *setti
* Only here for code to be removed. */
int BLI_task_parallel_thread_id(const TaskParallelTLS *tls);
+/* Task Graph Scheduling */
+/* Task Graphs can be used to create a forest of directional trees and schedule work to any tree.
+ * The nodes in the graph can be run in separate threads.
+ *
+ * +---- [root] ----+
+ * | |
+ * v v
+ * [node_1] +---- [node_2] ----+
+ * | |
+ * v v
+ * [node_3] [node_4]
+ *
+ * TaskGraph *task_graph = BLI_task_graph_create();
+ * TaskNode *root = BLI_task_graph_node_create(task_graph, root_exec, NULL, NULL);
+ * TaskNode *node_1 = BLI_task_graph_node_create(task_graph, node_exec, NULL, NULL);
+ * TaskNode *node_2 = BLI_task_graph_node_create(task_graph, node_exec, NULL, NULL);
+ * TaskNode *node_3 = BLI_task_graph_node_create(task_graph, node_exec, NULL, NULL);
+ * TaskNode *node_4 = BLI_task_graph_node_create(task_graph, node_exec, NULL, NULL);
+ *
+ * BLI_task_graph_edge_create(root, node_1);
+ * BLI_task_graph_edge_create(root, node_2);
+ * BLI_task_graph_edge_create(node_2, node_3);
+ * BLI_task_graph_edge_create(node_2, node_4);
+ *
+ * Any node can be triggered to start a chain of tasks. Normally you would trigger a root node but
+ * it is supported to start the chain of tasks anywhere in the forest or tree. When a node
+ * completes, the execution flow is forwarded via the created edges.
+ * When a child node has multiple parents the child node will be triggered once for each parent.
+ *
+ * BLI_task_graph_node_push_work(root);
+ *
+ * In this example After `root` is finished, `node_1` and `node_2` will be started.
+ * Only after `node_2` is finished `node_3` and `node_4` will be started.
+ *
+ * After scheduling work we need to wait until all the tasks have been finished.
+ *
+ * BLI_task_graph_work_and_wait();
+ *
+ * When finished you can clean up all the resources by freeing the task_graph. Nodes are owned by
+ * the graph and are freed task_data will only be freed if a free_func was given.
+ *
+ * BLI_task_graph_free(task_graph);
+ *
+ * Work can enter a tree on any node. Normally this would be the root_node.
+ * A `task_graph` can be reused, but the caller needs to make sure the task_data is reset.
+ *
+ * ** Task-Data **
+ *
+ * Typically you want give a task data to work on.
+ * Task data can be shared with other nodes, but be carefull not to free the data multiple times.
+ * Task data is freed when calling `BLI_task_graph_free`.
+ *
+ * MyData *task_data = MEM_callocN(sizeof(MyData), __func__);
+ * TaskNode *root = BLI_task_graph_node_create(task_graph, root_exec, task_data, MEM_freeN);
+ * TaskNode *node_1 = BLI_task_graph_node_create(task_graph, node_exec, task_data, NULL);
+ * TaskNode *node_2 = BLI_task_graph_node_create(task_graph, node_exec, task_data, NULL);
+ * TaskNode *node_3 = BLI_task_graph_node_create(task_graph, node_exec, task_data, NULL);
+ * TaskNode *node_4 = BLI_task_graph_node_create(task_graph, node_exec, task_data, NULL);
+ *
+ */
+struct TaskGraph;
+struct TaskNode;
+
+typedef void (*TaskGraphNodeRunFunction)(void *__restrict task_data);
+typedef void (*TaskGraphNodeFreeFunction)(void *task_data);
+
+struct TaskGraph *BLI_task_graph_create(void);
+void BLI_task_graph_work_and_wait(struct TaskGraph *task_graph);
+void BLI_task_graph_free(struct TaskGraph *task_graph);
+struct TaskNode *BLI_task_graph_node_create(struct TaskGraph *task_graph,
+ TaskGraphNodeRunFunction run,
+ void *task_data,
+ TaskGraphNodeFreeFunction free_func);
+bool BLI_task_graph_node_push_work(struct TaskNode *task_node);
+void BLI_task_graph_edge_create(struct TaskNode *from_node, struct TaskNode *to_node);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 18d58cdcaf3..8f1511ca118 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -119,6 +119,7 @@ set(SRC
intern/string_utf8.c
intern/string_utils.c
intern/system.c
+ intern/task_graph.cc
intern/task_iterator.c
intern/task_pool.cc
intern/task_range.cc
diff --git a/source/blender/blenlib/intern/task_graph.cc b/source/blender/blenlib/intern/task_graph.cc
new file mode 100644
index 00000000000..9a7f18c8348
--- /dev/null
+++ b/source/blender/blenlib/intern/task_graph.cc
@@ -0,0 +1,161 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ *
+ * Task graph.
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_task.h"
+
+#include <memory>
+#include <vector>
+
+#ifdef WITH_TBB
+/* Quiet top level deprecation message, unrelated to API usage here. */
+# define TBB_SUPPRESS_DEPRECATED_MESSAGES 1
+# include <tbb/flow_graph.h>
+# include <tbb/tbb.h>
+#endif
+
+/* Task Graph */
+struct TaskGraph {
+#ifdef WITH_TBB
+ tbb::flow::graph tbb_graph;
+#endif
+ std::vector<std::unique_ptr<TaskNode>> nodes;
+
+#ifdef WITH_CXX_GUARDEDALLOC
+ MEM_CXX_CLASS_ALLOC_FUNCS("task_graph:TaskGraph")
+#endif
+};
+
+/* TaskNode - a node in the task graph. */
+struct TaskNode {
+ /* TBB Node. */
+#ifdef WITH_TBB
+ tbb::flow::continue_node<tbb::flow::continue_msg> tbb_node;
+#endif
+ /* Successors to execute after this task, for serial execution fallback. */
+ std::vector<TaskNode *> successors;
+
+ /* User function to be executed with given task data. */
+ TaskGraphNodeRunFunction run_func;
+ void *task_data;
+ /* Optional callback to free task data along with the graph. If task data
+ * is shared between nodes, only a single task node should free the data. */
+ TaskGraphNodeFreeFunction free_func;
+
+ TaskNode(TaskGraph *task_graph,
+ TaskGraphNodeRunFunction run_func,
+ void *task_data,
+ TaskGraphNodeFreeFunction free_func)
+ :
+#ifdef WITH_TBB
+ tbb_node(task_graph->tbb_graph,
+ tbb::flow::unlimited,
+ std::bind(&TaskNode::run, this, std::placeholders::_1)),
+#endif
+ run_func(run_func),
+ task_data(task_data),
+ free_func(free_func)
+ {
+ }
+
+ TaskNode(const TaskNode &other) = delete;
+ TaskNode &operator=(const TaskNode &other) = delete;
+
+ ~TaskNode()
+ {
+ if (task_data && free_func) {
+ free_func(task_data);
+ }
+ }
+
+#ifdef WITH_TBB
+ tbb::flow::continue_msg run(const tbb::flow::continue_msg UNUSED(input))
+ {
+ tbb::this_task_arena::isolate([this] { run_func(task_data); });
+ return tbb::flow::continue_msg();
+ }
+#endif
+
+ void run_serial()
+ {
+ run_func(task_data);
+ for (TaskNode *successor : successors) {
+ successor->run_serial();
+ }
+ }
+
+#ifdef WITH_CXX_GUARDEDALLOC
+ MEM_CXX_CLASS_ALLOC_FUNCS("task_graph:TaskNode")
+#endif
+};
+
+TaskGraph *BLI_task_graph_create(void)
+{
+ return new TaskGraph();
+}
+
+void BLI_task_graph_free(TaskGraph *task_graph)
+{
+ delete task_graph;
+}
+
+void BLI_task_graph_work_and_wait(TaskGraph *task_graph)
+{
+#ifdef WITH_TBB
+ task_graph->tbb_graph.wait_for_all();
+#endif
+}
+
+struct TaskNode *BLI_task_graph_node_create(struct TaskGraph *task_graph,
+ TaskGraphNodeRunFunction run,
+ void *user_data,
+ TaskGraphNodeFreeFunction free_func)
+{
+ TaskNode *task_node = new TaskNode(task_graph, run, user_data, free_func);
+ task_graph->nodes.push_back(std::unique_ptr<TaskNode>(task_node));
+ return task_node;
+}
+
+bool BLI_task_graph_node_push_work(struct TaskNode *task_node)
+{
+#ifdef WITH_TBB
+ if (BLI_task_scheduler_num_threads() > 1) {
+ return task_node->tbb_node.try_put(tbb::flow::continue_msg());
+ }
+#endif
+
+ task_node->run_serial();
+ return true;
+}
+
+void BLI_task_graph_edge_create(struct TaskNode *from_node, struct TaskNode *to_node)
+{
+#ifdef WITH_TBB
+ if (BLI_task_scheduler_num_threads() > 1) {
+ tbb::flow::make_edge(from_node->tbb_node, to_node->tbb_node);
+ return;
+ }
+#endif
+
+ from_node->successors.push_back(to_node);
+}
diff --git a/tests/gtests/blenlib/BLI_task_graph_test.cc b/tests/gtests/blenlib/BLI_task_graph_test.cc
new file mode 100644
index 00000000000..efcbf923625
--- /dev/null
+++ b/tests/gtests/blenlib/BLI_task_graph_test.cc
@@ -0,0 +1,188 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_task.h"
+
+struct TaskData {
+ int value;
+ int store;
+};
+
+static void TaskData_increase_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value += 1;
+}
+static void TaskData_decrease_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value -= 1;
+}
+static void TaskData_multiply_by_two_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value *= 2;
+}
+
+static void TaskData_multiply_by_two_store(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->store *= 2;
+}
+
+static void TaskData_store_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->store = data->value;
+}
+
+static void TaskData_square_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value *= data->value;
+}
+
+/* Sequential Test for using `BLI_task_graph` */
+TEST(task, GraphSequential)
+{
+ TaskData data = {0};
+ TaskGraph *graph = BLI_task_graph_create();
+
+ /* 0 => 1 */
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ /* 1 => 2 */
+ TaskNode *node_b = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_value, &data, NULL);
+ /* 2 => 1 */
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_decrease_value, &data, NULL);
+ /* 2 => 1 */
+ TaskNode *node_d = BLI_task_graph_node_create(graph, TaskData_square_value, &data, NULL);
+ /* 1 => 1 */
+ TaskNode *node_e = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ /* 1 => 2 */
+ const int expected_value = 2;
+
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_c, node_d);
+ BLI_task_graph_edge_create(node_d, node_e);
+
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(expected_value, data.value);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphStartAtAnyNode)
+{
+ TaskData data = {4};
+ TaskGraph *graph = BLI_task_graph_create();
+
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_b = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_value, &data, NULL);
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_decrease_value, &data, NULL);
+ TaskNode *node_d = BLI_task_graph_node_create(graph, TaskData_square_value, &data, NULL);
+ TaskNode *node_e = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+
+ // ((4 - 1) * (4 - 1)) + 1
+ const int expected_value = 10;
+
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_c, node_d);
+ BLI_task_graph_edge_create(node_d, node_e);
+
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_c));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(expected_value, data.value);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphSplit)
+{
+ TaskData data = {1};
+
+ TaskGraph *graph = BLI_task_graph_create();
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data, NULL);
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data, NULL);
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_b, node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(3, data.value);
+ EXPECT_EQ(4, data.store);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphForest)
+{
+ TaskData data1 = {1};
+ TaskData data2 = {3};
+
+ TaskGraph *graph = BLI_task_graph_create();
+
+ {
+ TaskNode *tree1_node_a = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data1, NULL);
+ TaskNode *tree1_node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data1, NULL);
+ TaskNode *tree1_node_c = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data1, NULL);
+ TaskNode *tree1_node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data1, NULL);
+ BLI_task_graph_edge_create(tree1_node_a, tree1_node_b);
+ BLI_task_graph_edge_create(tree1_node_b, tree1_node_c);
+ BLI_task_graph_edge_create(tree1_node_b, tree1_node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(tree1_node_a));
+ }
+
+ {
+ TaskNode *tree2_node_a = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data2, NULL);
+ TaskNode *tree2_node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data2, NULL);
+ TaskNode *tree2_node_c = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data2, NULL);
+ TaskNode *tree2_node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data2, NULL);
+ BLI_task_graph_edge_create(tree2_node_a, tree2_node_b);
+ BLI_task_graph_edge_create(tree2_node_b, tree2_node_c);
+ BLI_task_graph_edge_create(tree2_node_b, tree2_node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(tree2_node_a));
+ }
+
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(3, data1.value);
+ EXPECT_EQ(4, data1.store);
+ EXPECT_EQ(5, data2.value);
+ EXPECT_EQ(8, data2.store);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphTaskData)
+{
+ TaskData data = {0};
+ TaskGraph *graph = BLI_task_graph_create();
+ TaskNode *node_a = BLI_task_graph_node_create(
+ graph, TaskData_store_value, &data, TaskData_increase_value);
+ TaskNode *node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data, NULL);
+ BLI_task_graph_edge_create(node_a, node_b);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+ EXPECT_EQ(0, data.value);
+ EXPECT_EQ(0, data.store);
+ BLI_task_graph_free(graph);
+ /* data should be freed once */
+ EXPECT_EQ(1, data.value);
+ EXPECT_EQ(0, data.store);
+}
diff --git a/tests/gtests/blenlib/CMakeLists.txt b/tests/gtests/blenlib/CMakeLists.txt
index a0621448630..6fbb304581d 100644
--- a/tests/gtests/blenlib/CMakeLists.txt
+++ b/tests/gtests/blenlib/CMakeLists.txt
@@ -72,6 +72,7 @@ BLENDER_TEST(BLI_string_map "bf_blenlib")
BLENDER_TEST(BLI_string_ref "bf_blenlib")
BLENDER_TEST(BLI_string_utf8 "bf_blenlib")
BLENDER_TEST(BLI_task "bf_blenlib;bf_intern_numaapi")
+BLENDER_TEST(BLI_task_graph "bf_blenlib;bf_intern_numaapi")
BLENDER_TEST(BLI_vector "bf_blenlib")
BLENDER_TEST(BLI_vector_set "bf_blenlib")