1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
17 /** \file
18  * \ingroup bli
19  *
20  * Task graph.
21  */
22 
23 #include "MEM_guardedalloc.h"
24 
25 #include "BLI_task.h"
26 
27 #include <memory>
28 #include <vector>
29 
30 #ifdef WITH_TBB
31 /* Quiet top level deprecation message, unrelated to API usage here. */
32 #  define TBB_SUPPRESS_DEPRECATED_MESSAGES 1
33 #  include <tbb/flow_graph.h>
34 #  include <tbb/tbb.h>
35 #endif
36 
37 /* Task Graph */
38 struct TaskGraph {
39 #ifdef WITH_TBB
40   tbb::flow::graph tbb_graph;
41 #endif
42   std::vector<std::unique_ptr<TaskNode>> nodes;
43 
44 #ifdef WITH_CXX_GUARDEDALLOC
45   MEM_CXX_CLASS_ALLOC_FUNCS("task_graph:TaskGraph")
46 #endif
47 };
48 
49 /* TaskNode - a node in the task graph. */
50 struct TaskNode {
51   /* TBB Node. */
52 #ifdef WITH_TBB
53   tbb::flow::continue_node<tbb::flow::continue_msg> tbb_node;
54 #endif
55   /* Successors to execute after this task, for serial execution fallback. */
56   std::vector<TaskNode *> successors;
57 
58   /* User function to be executed with given task data. */
59   TaskGraphNodeRunFunction run_func;
60   void *task_data;
61   /* Optional callback to free task data along with the graph. If task data
62    * is shared between nodes, only a single task node should free the data. */
63   TaskGraphNodeFreeFunction free_func;
64 
TaskNodeTaskNode65   TaskNode(TaskGraph *task_graph,
66            TaskGraphNodeRunFunction run_func,
67            void *task_data,
68            TaskGraphNodeFreeFunction free_func)
69       :
70 #ifdef WITH_TBB
71         tbb_node(task_graph->tbb_graph,
72                  tbb::flow::unlimited,
73                  std::bind(&TaskNode::run, this, std::placeholders::_1)),
74 #endif
75         run_func(run_func),
76         task_data(task_data),
77         free_func(free_func)
78   {
79 #ifndef WITH_TBB
80     UNUSED_VARS(task_graph);
81 #endif
82   }
83 
84   TaskNode(const TaskNode &other) = delete;
85   TaskNode &operator=(const TaskNode &other) = delete;
86 
~TaskNodeTaskNode87   ~TaskNode()
88   {
89     if (task_data && free_func) {
90       free_func(task_data);
91     }
92   }
93 
94 #ifdef WITH_TBB
runTaskNode95   tbb::flow::continue_msg run(const tbb::flow::continue_msg UNUSED(input))
96   {
97     tbb::this_task_arena::isolate([this] { run_func(task_data); });
98     return tbb::flow::continue_msg();
99   }
100 #endif
101 
run_serialTaskNode102   void run_serial()
103   {
104     run_func(task_data);
105     for (TaskNode *successor : successors) {
106       successor->run_serial();
107     }
108   }
109 
110 #ifdef WITH_CXX_GUARDEDALLOC
111   MEM_CXX_CLASS_ALLOC_FUNCS("task_graph:TaskNode")
112 #endif
113 };
114 
BLI_task_graph_create(void)115 TaskGraph *BLI_task_graph_create(void)
116 {
117   return new TaskGraph();
118 }
119 
BLI_task_graph_free(TaskGraph * task_graph)120 void BLI_task_graph_free(TaskGraph *task_graph)
121 {
122   delete task_graph;
123 }
124 
BLI_task_graph_work_and_wait(TaskGraph * task_graph)125 void BLI_task_graph_work_and_wait(TaskGraph *task_graph)
126 {
127 #ifdef WITH_TBB
128   task_graph->tbb_graph.wait_for_all();
129 #else
130   UNUSED_VARS(task_graph);
131 #endif
132 }
133 
BLI_task_graph_node_create(struct TaskGraph * task_graph,TaskGraphNodeRunFunction run,void * user_data,TaskGraphNodeFreeFunction free_func)134 struct TaskNode *BLI_task_graph_node_create(struct TaskGraph *task_graph,
135                                             TaskGraphNodeRunFunction run,
136                                             void *user_data,
137                                             TaskGraphNodeFreeFunction free_func)
138 {
139   TaskNode *task_node = new TaskNode(task_graph, run, user_data, free_func);
140   task_graph->nodes.push_back(std::unique_ptr<TaskNode>(task_node));
141   return task_node;
142 }
143 
BLI_task_graph_node_push_work(struct TaskNode * task_node)144 bool BLI_task_graph_node_push_work(struct TaskNode *task_node)
145 {
146 #ifdef WITH_TBB
147   if (BLI_task_scheduler_num_threads() > 1) {
148     return task_node->tbb_node.try_put(tbb::flow::continue_msg());
149   }
150 #endif
151 
152   task_node->run_serial();
153   return true;
154 }
155 
BLI_task_graph_edge_create(struct TaskNode * from_node,struct TaskNode * to_node)156 void BLI_task_graph_edge_create(struct TaskNode *from_node, struct TaskNode *to_node)
157 {
158 #ifdef WITH_TBB
159   if (BLI_task_scheduler_num_threads() > 1) {
160     tbb::flow::make_edge(from_node->tbb_node, to_node->tbb_node);
161     return;
162   }
163 #endif
164 
165   from_node->successors.push_back(to_node);
166 }
167