Changeset View
Changeset View
Standalone View
Standalone View
source/blender/depsgraph/intern/eval/deg_eval.cc
| Show All 25 Lines | |||||
| #include "intern/eval/deg_eval.h" | #include "intern/eval/deg_eval.h" | ||||
| #include "PIL_time.h" | #include "PIL_time.h" | ||||
| #include "BLI_compiler_attrs.h" | #include "BLI_compiler_attrs.h" | ||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_task.h" | #include "BLI_task.h" | ||||
| #include "BLI_ghash.h" | #include "BLI_ghash.h" | ||||
| #include "BLI_gsqueue.h" | |||||
| #include "BKE_global.h" | #include "BKE_global.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_query.h" | #include "DEG_depsgraph_query.h" | ||||
| Show All 9 Lines | |||||
| #include "intern/node/deg_node_operation.h" | #include "intern/node/deg_node_operation.h" | ||||
| #include "intern/node/deg_node_time.h" | #include "intern/node/deg_node_time.h" | ||||
| #include "intern/depsgraph.h" | #include "intern/depsgraph.h" | ||||
| namespace DEG { | namespace DEG { | ||||
| namespace { | namespace { | ||||
| void schedule_children(TaskPool *pool, Depsgraph *graph, OperationNode *node, const int thread_id); | struct DepsgraphEvalState; | ||||
| void deg_task_run_func(TaskPool *pool, void *taskdata, int thread_id); | |||||
| template<typename ScheduleFunction, typename... ScheduleFunctionArgs> | |||||
| void schedule_children(DepsgraphEvalState *state, | |||||
| OperationNode *node, | |||||
| const int thread_id, | |||||
| ScheduleFunction *schedule_function, | |||||
| ScheduleFunctionArgs... schedule_function_args); | |||||
| void schedule_node_to_pool(OperationNode *node, const int thread_id, TaskPool *pool) | |||||
| { | |||||
| BLI_task_pool_push_from_thread( | |||||
| pool, deg_task_run_func, node, false, TASK_PRIORITY_HIGH, thread_id); | |||||
| } | |||||
| /* Denotes which part of dependency graph is being evaluated. */ | /* Denotes which part of dependency graph is being evaluated. */ | ||||
| enum class EvaluationStage { | enum class EvaluationStage { | ||||
| /* Stage 1: Only Copy-on-Write operations are to be evaluated, prior to anything else. | /* Stage 1: Only Copy-on-Write operations are to be evaluated, prior to anything else. | ||||
| * This allows other operations to access its dependencies when there is a dependency cycle | * This allows other operations to access its dependencies when there is a dependency cycle | ||||
| * involved. */ | * involved. */ | ||||
| COPY_ON_WRITE, | COPY_ON_WRITE, | ||||
| /* Threaded evaluation of all possible operations. */ | /* Threaded evaluation of all possible operations. */ | ||||
| THREADED_EVALUATION, | THREADED_EVALUATION, | ||||
| /* Workaround for areas which can not be evaluated in threads. | |||||
| * | |||||
| * For example, metaballs, which are iterating over all bases and are requesting duplilists | |||||
| * to see whether there are metaballs inside. */ | |||||
| SINGLE_THREADED_WORKAROUND, | |||||
| }; | }; | ||||
| struct DepsgraphEvalState { | struct DepsgraphEvalState { | ||||
| Depsgraph *graph; | Depsgraph *graph; | ||||
| bool do_stats; | bool do_stats; | ||||
| EvaluationStage stage; | EvaluationStage stage; | ||||
| bool need_single_thread_pass; | |||||
| }; | }; | ||||
| void deg_task_run_func(TaskPool *pool, void *taskdata, int thread_id) | void evaluate_node(const DepsgraphEvalState *state, OperationNode *operation_node) | ||||
| { | { | ||||
| void *userdata_v = BLI_task_pool_userdata(pool); | ::Depsgraph *depsgraph = reinterpret_cast<::Depsgraph *>(state->graph); | ||||
| DepsgraphEvalState *state = (DepsgraphEvalState *)userdata_v; | |||||
| OperationNode *node = (OperationNode *)taskdata; | |||||
| /* Sanity checks. */ | /* Sanity checks. */ | ||||
| BLI_assert(!node->is_noop() && "NOOP nodes should not actually be scheduled"); | BLI_assert(!operation_node->is_noop() && "NOOP nodes should not actually be scheduled"); | ||||
| /* Perform operation. */ | /* Perform operation. */ | ||||
| if (state->do_stats) { | if (state->do_stats) { | ||||
| const double start_time = PIL_check_seconds_timer(); | const double start_time = PIL_check_seconds_timer(); | ||||
| node->evaluate((::Depsgraph *)state->graph); | operation_node->evaluate(depsgraph); | ||||
| node->stats.current_time += PIL_check_seconds_timer() - start_time; | operation_node->stats.current_time += PIL_check_seconds_timer() - start_time; | ||||
| } | } | ||||
| else { | else { | ||||
| node->evaluate((::Depsgraph *)state->graph); | operation_node->evaluate(depsgraph); | ||||
| } | |||||
| } | } | ||||
| void deg_task_run_func(TaskPool *pool, void *taskdata, int thread_id) | |||||
| { | |||||
| void *userdata_v = BLI_task_pool_userdata(pool); | |||||
| DepsgraphEvalState *state = (DepsgraphEvalState *)userdata_v; | |||||
| /* Evaluate node. */ | |||||
| OperationNode *operation_node = reinterpret_cast<OperationNode *>(taskdata); | |||||
| evaluate_node(state, operation_node); | |||||
| /* Schedule children. */ | /* Schedule children. */ | ||||
| BLI_task_pool_delayed_push_begin(pool, thread_id); | BLI_task_pool_delayed_push_begin(pool, thread_id); | ||||
| schedule_children(pool, state->graph, node, thread_id); | schedule_children(state, operation_node, thread_id, schedule_node_to_pool, pool); | ||||
| BLI_task_pool_delayed_push_end(pool, thread_id); | BLI_task_pool_delayed_push_end(pool, thread_id); | ||||
| } | } | ||||
| bool check_operation_node_visible(OperationNode *op_node) | bool check_operation_node_visible(OperationNode *op_node) | ||||
| { | { | ||||
| const ComponentNode *comp_node = op_node->owner; | const ComponentNode *comp_node = op_node->owner; | ||||
| /* Special exception, copy on write component is to be always evaluated, | /* Special exception, copy on write component is to be always evaluated, | ||||
| * to keep copied "database" in a consistent state. */ | * to keep copied "database" in a consistent state. */ | ||||
| ▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | void initialize_execution(DepsgraphEvalState *state, Depsgraph *graph) | ||||
| /* Clear tags and other things which needs to be clear. */ | /* Clear tags and other things which needs to be clear. */ | ||||
| for (OperationNode *node : graph->operations) { | for (OperationNode *node : graph->operations) { | ||||
| if (do_stats) { | if (do_stats) { | ||||
| node->stats.reset_current(); | node->stats.reset_current(); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| bool need_evaluate_operation_at_stage(const DepsgraphEvalState *state, | bool is_metaball_object_operation(const OperationNode *operation_node) | ||||
| { | |||||
| const ComponentNode *component_node = operation_node->owner; | |||||
| const IDNode *id_node = component_node->owner; | |||||
| if (GS(id_node->id_cow->name) != ID_OB) { | |||||
| return false; | |||||
| } | |||||
| const Object *object = reinterpret_cast<const Object *>(id_node->id_cow); | |||||
| return object->type == OB_MBALL; | |||||
| } | |||||
| bool need_evaluate_operation_at_stage(DepsgraphEvalState *state, | |||||
| const OperationNode *operation_node) | const OperationNode *operation_node) | ||||
| { | { | ||||
| const ComponentNode *component_node = operation_node->owner; | const ComponentNode *component_node = operation_node->owner; | ||||
| switch (state->stage) { | switch (state->stage) { | ||||
| case EvaluationStage::COPY_ON_WRITE: | case EvaluationStage::COPY_ON_WRITE: | ||||
| return (component_node->type == NodeType::COPY_ON_WRITE); | return (component_node->type == NodeType::COPY_ON_WRITE); | ||||
| case EvaluationStage::THREADED_EVALUATION: | case EvaluationStage::THREADED_EVALUATION: | ||||
| /* Sanity check: copy-on-write node should be evaluated already. This will be indicated by | /* Sanity check: copy-on-write node should be evaluated already. This will be indicated by | ||||
| * scheduled flag (we assume that scheduled operations have been actually handled by previous | * scheduled flag (we assume that scheduled operations have been actually handled by previous | ||||
| * stage). */ | * stage). */ | ||||
| BLI_assert(operation_node->scheduled || component_node->type != NodeType::COPY_ON_WRITE); | BLI_assert(operation_node->scheduled || component_node->type != NodeType::COPY_ON_WRITE); | ||||
| if (is_metaball_object_operation(operation_node)) { | |||||
| state->need_single_thread_pass = true; | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| case EvaluationStage::SINGLE_THREADED_WORKAROUND: | |||||
| return true; | return true; | ||||
| } | } | ||||
| BLI_assert(!"Unhandled evaluation stage, should never happen."); | BLI_assert(!"Unhandled evaluation stage, should never happen."); | ||||
| return false; | return false; | ||||
| } | } | ||||
| /* Schedule a node if it needs evaluation. | /* Schedule a node if it needs evaluation. | ||||
| * dec_parents: Decrement pending parents count, true when child nodes are | * dec_parents: Decrement pending parents count, true when child nodes are | ||||
| * scheduled after a task has been completed. | * scheduled after a task has been completed. | ||||
| */ | */ | ||||
| void schedule_node( | template<typename ScheduleFunction, typename... ScheduleFunctionArgs> | ||||
| TaskPool *pool, Depsgraph *graph, OperationNode *node, bool dec_parents, const int thread_id) | void schedule_node(DepsgraphEvalState *state, | ||||
| OperationNode *node, | |||||
| bool dec_parents, | |||||
| const int thread_id, | |||||
| ScheduleFunction *schedule_function, | |||||
| ScheduleFunctionArgs... schedule_function_args) | |||||
| { | { | ||||
| /* No need to schedule nodes of invisible ID. */ | /* No need to schedule nodes of invisible ID. */ | ||||
| if (!check_operation_node_visible(node)) { | if (!check_operation_node_visible(node)) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* No need to schedule operations which are not tagged for update, they are | /* No need to schedule operations which are not tagged for update, they are | ||||
| * considered to be up to date. */ | * considered to be up to date. */ | ||||
| if ((node->flag & DEPSOP_FLAG_NEEDS_UPDATE) == 0) { | if ((node->flag & DEPSOP_FLAG_NEEDS_UPDATE) == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* TODO(sergey): This is not strictly speaking safe to read | /* TODO(sergey): This is not strictly speaking safe to read | ||||
| * num_links_pending. */ | * num_links_pending. */ | ||||
| if (dec_parents) { | if (dec_parents) { | ||||
| BLI_assert(node->num_links_pending > 0); | BLI_assert(node->num_links_pending > 0); | ||||
| atomic_sub_and_fetch_uint32(&node->num_links_pending, 1); | atomic_sub_and_fetch_uint32(&node->num_links_pending, 1); | ||||
| } | } | ||||
| /* Cal not schedule operation while its dependencies are not yet | /* Cal not schedule operation while its dependencies are not yet | ||||
| * evaluated. */ | * evaluated. */ | ||||
| if (node->num_links_pending != 0) { | if (node->num_links_pending != 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* During the COW stage only schedule COW nodes. */ | /* During the COW stage only schedule COW nodes. */ | ||||
| const DepsgraphEvalState *state = (DepsgraphEvalState *)BLI_task_pool_userdata(pool); | |||||
| if (!need_evaluate_operation_at_stage(state, node)) { | if (!need_evaluate_operation_at_stage(state, node)) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* Actually schedule the node. */ | /* Actually schedule the node. */ | ||||
| bool is_scheduled = atomic_fetch_and_or_uint8((uint8_t *)&node->scheduled, (uint8_t) true); | bool is_scheduled = atomic_fetch_and_or_uint8((uint8_t *)&node->scheduled, (uint8_t) true); | ||||
| if (!is_scheduled) { | if (!is_scheduled) { | ||||
| if (node->is_noop()) { | if (node->is_noop()) { | ||||
| /* skip NOOP node, schedule children right away */ | /* skip NOOP node, schedule children right away */ | ||||
| schedule_children(pool, graph, node, thread_id); | schedule_children(state, node, thread_id, schedule_function, schedule_function_args...); | ||||
| } | } | ||||
| else { | else { | ||||
| /* children are scheduled once this task is completed */ | /* children are scheduled once this task is completed */ | ||||
| BLI_task_pool_push_from_thread( | schedule_function(node, thread_id, schedule_function_args...); | ||||
| pool, deg_task_run_func, node, false, TASK_PRIORITY_HIGH, thread_id); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| void schedule_graph(TaskPool *pool, Depsgraph *graph) | template<typename ScheduleFunction, typename... ScheduleFunctionArgs> | ||||
| void schedule_graph(DepsgraphEvalState *state, | |||||
| ScheduleFunction *schedule_function, | |||||
| ScheduleFunctionArgs... schedule_function_args) | |||||
| { | { | ||||
| for (OperationNode *node : graph->operations) { | for (OperationNode *node : state->graph->operations) { | ||||
| schedule_node(pool, graph, node, false, -1); | schedule_node(state, node, false, -1, schedule_function, schedule_function_args...); | ||||
| } | } | ||||
| } | } | ||||
| void schedule_children(TaskPool *pool, Depsgraph *graph, OperationNode *node, const int thread_id) | template<typename ScheduleFunction, typename... ScheduleFunctionArgs> | ||||
| void schedule_children(DepsgraphEvalState *state, | |||||
| OperationNode *node, | |||||
| const int thread_id, | |||||
| ScheduleFunction *schedule_function, | |||||
| ScheduleFunctionArgs... schedule_function_args) | |||||
| { | { | ||||
| for (Relation *rel : node->outlinks) { | for (Relation *rel : node->outlinks) { | ||||
| OperationNode *child = (OperationNode *)rel->to; | OperationNode *child = (OperationNode *)rel->to; | ||||
| BLI_assert(child->type == NodeType::OPERATION); | BLI_assert(child->type == NodeType::OPERATION); | ||||
| if (child->scheduled) { | if (child->scheduled) { | ||||
| /* Happens when having cyclic dependencies. */ | /* Happens when having cyclic dependencies. */ | ||||
| continue; | continue; | ||||
| } | } | ||||
| schedule_node(pool, graph, child, (rel->flag & RELATION_FLAG_CYCLIC) == 0, thread_id); | schedule_node(state, | ||||
| child, | |||||
| (rel->flag & RELATION_FLAG_CYCLIC) == 0, | |||||
| thread_id, | |||||
| schedule_function, | |||||
| schedule_function_args...); | |||||
| } | } | ||||
| } | } | ||||
| void schedule_node_to_queue(OperationNode *node, | |||||
| const int /*thread_id*/, | |||||
| GSQueue *evaluation_queue) | |||||
| { | |||||
| BLI_gsqueue_push(evaluation_queue, &node); | |||||
| } | |||||
| void evaluate_graph_single_threaded(DepsgraphEvalState *state) | |||||
| { | |||||
| GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *)); | |||||
| schedule_graph(state, schedule_node_to_queue, evaluation_queue); | |||||
| while (!BLI_gsqueue_is_empty(evaluation_queue)) { | |||||
| OperationNode *operation_node; | |||||
| BLI_gsqueue_pop(evaluation_queue, &operation_node); | |||||
| evaluate_node(state, operation_node); | |||||
| schedule_children(state, operation_node, 0, schedule_node_to_queue, evaluation_queue); | |||||
| } | |||||
| BLI_gsqueue_free(evaluation_queue); | |||||
| } | |||||
| void depsgraph_ensure_view_layer(Depsgraph *graph) | void depsgraph_ensure_view_layer(Depsgraph *graph) | ||||
| { | { | ||||
| /* We update copy-on-write scene in the following cases: | /* We update copy-on-write scene in the following cases: | ||||
| * - It was not expanded yet. | * - It was not expanded yet. | ||||
| * - It was tagged for update of CoW component. | * - It was tagged for update of CoW component. | ||||
| * This allows us to have proper view layer pointer. */ | * This allows us to have proper view layer pointer. */ | ||||
| Scene *scene_cow = graph->scene_cow; | Scene *scene_cow = graph->scene_cow; | ||||
| if (!deg_copy_on_write_is_expanded(&scene_cow->id) || | if (!deg_copy_on_write_is_expanded(&scene_cow->id) || | ||||
| Show All 21 Lines | void deg_evaluate_on_refresh(Depsgraph *graph) | ||||
| const bool do_time_debug = ((G.debug & G_DEBUG_DEPSGRAPH_TIME) != 0); | const bool do_time_debug = ((G.debug & G_DEBUG_DEPSGRAPH_TIME) != 0); | ||||
| const double start_time = do_time_debug ? PIL_check_seconds_timer() : 0; | const double start_time = do_time_debug ? PIL_check_seconds_timer() : 0; | ||||
| graph->is_evaluating = true; | graph->is_evaluating = true; | ||||
| depsgraph_ensure_view_layer(graph); | depsgraph_ensure_view_layer(graph); | ||||
| /* Set up evaluation state. */ | /* Set up evaluation state. */ | ||||
| DepsgraphEvalState state; | DepsgraphEvalState state; | ||||
| state.graph = graph; | state.graph = graph; | ||||
| state.do_stats = do_time_debug; | state.do_stats = do_time_debug; | ||||
| state.need_single_thread_pass = false; | |||||
| /* Set up task scheduler and pull for threaded evaluation. */ | /* Set up task scheduler and pull for threaded evaluation. */ | ||||
| TaskScheduler *task_scheduler; | TaskScheduler *task_scheduler; | ||||
| bool need_free_scheduler; | bool need_free_scheduler; | ||||
| if (G.debug & G_DEBUG_DEPSGRAPH_NO_THREADS) { | if (G.debug & G_DEBUG_DEPSGRAPH_NO_THREADS) { | ||||
| task_scheduler = BLI_task_scheduler_create(1); | task_scheduler = BLI_task_scheduler_create(1); | ||||
| need_free_scheduler = true; | need_free_scheduler = true; | ||||
| } | } | ||||
| else { | else { | ||||
| task_scheduler = BLI_task_scheduler_get(); | task_scheduler = BLI_task_scheduler_get(); | ||||
| need_free_scheduler = false; | need_free_scheduler = false; | ||||
| } | } | ||||
| TaskPool *task_pool = BLI_task_pool_create_suspended(task_scheduler, &state); | TaskPool *task_pool = BLI_task_pool_create_suspended(task_scheduler, &state); | ||||
| /* Prepare all nodes for evaluation. */ | /* Prepare all nodes for evaluation. */ | ||||
| initialize_execution(&state, graph); | initialize_execution(&state, graph); | ||||
| /* Do actual evaluation now. */ | /* Do actual evaluation now. */ | ||||
| /* First, process all Copy-On-Write nodes. */ | /* First, process all Copy-On-Write nodes. */ | ||||
| state.stage = EvaluationStage::COPY_ON_WRITE; | state.stage = EvaluationStage::COPY_ON_WRITE; | ||||
| schedule_graph(task_pool, graph); | schedule_graph(&state, schedule_node_to_pool, task_pool); | ||||
| BLI_task_pool_work_wait_and_reset(task_pool); | BLI_task_pool_work_wait_and_reset(task_pool); | ||||
| /* After that, process all other nodes. */ | /* After that, process all other nodes. */ | ||||
| state.stage = EvaluationStage::THREADED_EVALUATION; | state.stage = EvaluationStage::THREADED_EVALUATION; | ||||
| schedule_graph(task_pool, graph); | schedule_graph(&state, schedule_node_to_pool, task_pool); | ||||
| BLI_task_pool_work_and_wait(task_pool); | BLI_task_pool_work_and_wait(task_pool); | ||||
| BLI_task_pool_free(task_pool); | BLI_task_pool_free(task_pool); | ||||
| if (state.need_single_thread_pass) { | |||||
| state.stage = EvaluationStage::SINGLE_THREADED_WORKAROUND; | |||||
| evaluate_graph_single_threaded(&state); | |||||
| } | |||||
| /* Finalize statistics gathering. This is because we only gather single | /* Finalize statistics gathering. This is because we only gather single | ||||
| * operation timing here, without aggregating anything to avoid any extra | * operation timing here, without aggregating anything to avoid any extra | ||||
| * synchronization. */ | * synchronization. */ | ||||
| if (state.do_stats) { | if (state.do_stats) { | ||||
| deg_eval_stats_aggregate(graph); | deg_eval_stats_aggregate(graph); | ||||
| } | } | ||||
| /* Clear any uncleared tags - just in case. */ | /* Clear any uncleared tags - just in case. */ | ||||
| deg_graph_clear_tags(graph); | deg_graph_clear_tags(graph); | ||||
| Show All 10 Lines | |||||