From 342226a67063ca5563c70ab45fe158af5bbc8317 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 18 Dec 2017 16:33:12 +0100 Subject: [PATCH] Depsgraph: Synchronize flushing with 2.8 branch Not only this helps merges form master to the branch, but also: - Allows us to production-check changes as soon as possible. - Avoids some unnecessary editors update about ID changes. - Adds small optimization on queue size by always keeping one of the pointers outside of the queue. --- .../depsgraph/intern/eval/deg_eval_flush.cc | 329 ++++++++++-------- 1 file changed, 180 insertions(+), 149 deletions(-) diff --git a/source/blender/depsgraph/intern/eval/deg_eval_flush.cc b/source/blender/depsgraph/intern/eval/deg_eval_flush.cc index 79ef0f32dda..219ed981870 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_flush.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_flush.cc @@ -54,12 +54,19 @@ extern "C" { namespace DEG { +enum { + ID_STATE_NONE = 0, + ID_STATE_MODIFIED = 1, +}; + enum { COMPONENT_STATE_NONE = 0, COMPONENT_STATE_SCHEDULED = 1, COMPONENT_STATE_DONE = 2, }; +typedef std::deque FlushQueue; + namespace { // TODO(sergey): De-duplicate with depsgraph_tag,cc @@ -75,176 +82,200 @@ void lib_id_recalc_data_tag(Main *bmain, ID *id) DEG_id_type_tag(bmain, GS(id->name)); } -} /* namespace */ - -typedef std::deque FlushQueue; - -static void flush_init_func(void *data_v, int i) +void flush_init_operation_node_func(void *data_v, int i) { - /* ID node's done flag is used to avoid multiple editors update - * for the same ID. - */ Depsgraph *graph = (Depsgraph *)data_v; OperationDepsNode *node = graph->operations[i]; - ComponentDepsNode *comp_node = node->owner; - IDDepsNode *id_node = comp_node->owner; - id_node->done = 0; - comp_node->done = COMPONENT_STATE_NONE; node->scheduled = false; } +void flush_init_id_node_func(void *data_v, int i) +{ + Depsgraph *graph = (Depsgraph *)data_v; + IDDepsNode *id_node = graph->id_nodes[i]; + id_node->done = ID_STATE_NONE; + GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, id_node->components) + comp_node->done = COMPONENT_STATE_NONE; + GHASH_FOREACH_END(); +} + +BLI_INLINE void flush_prepare(Depsgraph *graph) +{ + const int num_operations = graph->operations.size(); + BLI_task_parallel_range(0, num_operations, + graph, + flush_init_operation_node_func, + (num_operations > 256)); + const int num_id_nodes = graph->id_nodes.size(); + BLI_task_parallel_range(0, num_id_nodes, + graph, + flush_init_id_node_func, + (num_id_nodes > 256)); +} + +BLI_INLINE void flush_schedule_entrypoints(Depsgraph *graph, FlushQueue *queue) +{ + GSET_FOREACH_BEGIN(OperationDepsNode *, op_node, graph->entry_tags) + { + queue->push_back(op_node); + op_node->scheduled = true; + } + GSET_FOREACH_END(); +} + +BLI_INLINE void flush_handle_id_node(IDDepsNode *id_node) +{ + id_node->done = ID_STATE_MODIFIED; +} + +/* TODO(sergey): We can reduce number of arguments here. */ +BLI_INLINE void flush_handle_component_node(Depsgraph * /*graph*/, + IDDepsNode *id_node, + ComponentDepsNode *comp_node, + FlushQueue *queue) +{ + /* We only handle component once. */ + if (comp_node->done == COMPONENT_STATE_DONE) { + return; + } + comp_node->done = COMPONENT_STATE_DONE; + /* Tag all required operations in component for update. */ + foreach (OperationDepsNode *op, comp_node->operations) { + op->flag |= DEPSOP_FLAG_NEEDS_UPDATE; + } + if (GS(id_node->id->name) == ID_OB) { + Object *object = (Object *)id_node->id; + /* This code is used to preserve those areas which does + * direct object update, + * + * Plus it ensures visibility changes and relations and + * layers visibility update has proper flags to work with. + */ + switch (comp_node->type) { + case DEG_NODE_TYPE_UNDEFINED: + case DEG_NODE_TYPE_OPERATION: + case DEG_NODE_TYPE_TIMESOURCE: + case DEG_NODE_TYPE_ID_REF: + case DEG_NODE_TYPE_SEQUENCER: + case NUM_DEG_NODE_TYPES: + /* Ignore, does not translate to object component. */ + BLI_assert(!"This should never happen!"); + break; + case DEG_NODE_TYPE_ANIMATION: + object->recalc |= OB_RECALC_TIME; + break; + case DEG_NODE_TYPE_TRANSFORM: + object->recalc |= OB_RECALC_OB; + break; + case DEG_NODE_TYPE_GEOMETRY: + case DEG_NODE_TYPE_EVAL_POSE: + case DEG_NODE_TYPE_BONE: + case DEG_NODE_TYPE_EVAL_PARTICLES: + case DEG_NODE_TYPE_SHADING: + case DEG_NODE_TYPE_CACHE: + case DEG_NODE_TYPE_PROXY: + object->recalc |= OB_RECALC_DATA; + break; + case DEG_NODE_TYPE_PARAMETERS: + break; + } + } + /* When some target changes bone, we might need to re-run the + * whole IK solver, otherwise result might be unpredictable. + */ + if (comp_node->type == DEG_NODE_TYPE_BONE) { + ComponentDepsNode *pose_comp = + id_node->find_component(DEG_NODE_TYPE_EVAL_POSE); + BLI_assert(pose_comp != NULL); + if (pose_comp->done == COMPONENT_STATE_NONE) { + queue->push_front(pose_comp->get_entry_operation()); + pose_comp->done = COMPONENT_STATE_SCHEDULED; + } + } +} + +/* Schedule children of the given operation node for traversal. + * + * One of the children will by-pass the queue and will be returned as a function + * return value, so it can start being handled right away, without building too + * much of a queue. + */ +BLI_INLINE OperationDepsNode *flush_schedule_children( + OperationDepsNode *op_node, + FlushQueue *queue) +{ + OperationDepsNode *result = NULL; + foreach (DepsRelation *rel, op_node->outlinks) { + OperationDepsNode *to_node = (OperationDepsNode *)rel->to; + if (to_node->scheduled == false) { + if (result != NULL) { + queue->push_front(to_node); + } + else { + result = to_node; + } + to_node->scheduled = true; + } + } + return result; +} + +BLI_INLINE void flush_editors_id_update(Main *bmain, + Depsgraph *graph) +{ + foreach (IDDepsNode *id_node, graph->id_nodes) { + if (id_node->done != ID_STATE_MODIFIED) { + continue; + } + /* TODO(sergey): Do we need to pass original or evaluated ID here? */ + ID *id = id_node->id; + deg_editors_id_update(bmain, id); + lib_id_recalc_tag(bmain, id); + /* TODO(sergey): For until we've got proper data nodes in the graph. */ + lib_id_recalc_data_tag(bmain, id); + } +} + +} // namespace + /* Flush updates from tagged nodes outwards until all affected nodes * are tagged. */ void deg_graph_flush_updates(Main *bmain, Depsgraph *graph) { - /* Sanity check. */ - if (graph == NULL) { - return; - } - + /* Sanity checks. */ + BLI_assert(bmain != NULL); + BLI_assert(graph != NULL); /* Nothing to update, early out. */ if (BLI_gset_size(graph->entry_tags) == 0) { return; } - - /* TODO(sergey): With a bit of flag magic we can get rid of this - * extra loop. - */ - const int num_operations = graph->operations.size(); - const bool do_threads = num_operations > 256; - BLI_task_parallel_range(0, - num_operations, - graph, - flush_init_func, - do_threads); - + /* Reset all flags, get ready for the flush. */ + flush_prepare(graph); + /* Starting from the tagged "entry" nodes, flush outwards. */ FlushQueue queue; - /* Starting from the tagged "entry" nodes, flush outwards... */ - /* NOTE: Also need to ensure that for each of these, there is a path back to - * root, or else they won't be done. - * NOTE: Count how many nodes we need to handle - entry nodes may be - * component nodes which don't count for this purpose! - */ - GSET_FOREACH_BEGIN(OperationDepsNode *, node, graph->entry_tags) - { - queue.push_back(node); - node->scheduled = true; - } - GSET_FOREACH_END(); - - int num_flushed_objects = 0; + flush_schedule_entrypoints(graph, &queue); + /* Do actual flush. */ while (!queue.empty()) { - OperationDepsNode *node = queue.front(); + OperationDepsNode *op_node = queue.front(); queue.pop_front(); - - for (;;) { - node->flag |= DEPSOP_FLAG_NEEDS_UPDATE; - - ComponentDepsNode *comp_node = node->owner; + while (op_node != NULL) { + /* Tag operation as required for update. */ + op_node->flag |= DEPSOP_FLAG_NEEDS_UPDATE; + /* Inform corresponding ID and component nodes about the change. */ + ComponentDepsNode *comp_node = op_node->owner; IDDepsNode *id_node = comp_node->owner; - - ID *id = id_node->id; - if (id_node->done == 0) { - deg_editors_id_update(bmain, id); - lib_id_recalc_tag(bmain, id); - /* TODO(sergey): For until we've got proper data nodes in the graph. */ - lib_id_recalc_data_tag(bmain, id); - } - - if (comp_node->done != COMPONENT_STATE_DONE) { - Object *object = NULL; - if (GS(id->name) == ID_OB) { - object = (Object *)id; - if (id_node->done == 0) { - ++num_flushed_objects; - } - } - foreach (OperationDepsNode *op, comp_node->operations) { - op->flag |= DEPSOP_FLAG_NEEDS_UPDATE; - } - if (object != NULL) { - /* This code is used to preserve those areas which does - * direct object update, - * - * Plus it ensures visibility changes and relations and - * layers visibility update has proper flags to work with. - */ - switch (comp_node->type) { - case DEG_NODE_TYPE_UNDEFINED: - case DEG_NODE_TYPE_OPERATION: - case DEG_NODE_TYPE_TIMESOURCE: - case DEG_NODE_TYPE_ID_REF: - case DEG_NODE_TYPE_SEQUENCER: - case NUM_DEG_NODE_TYPES: - /* Ignore, does not translate to object component. */ - BLI_assert(!"This should never happen!"); - break; - case DEG_NODE_TYPE_ANIMATION: - object->recalc |= OB_RECALC_TIME; - break; - case DEG_NODE_TYPE_TRANSFORM: - object->recalc |= OB_RECALC_OB; - break; - case DEG_NODE_TYPE_GEOMETRY: - case DEG_NODE_TYPE_EVAL_POSE: - case DEG_NODE_TYPE_BONE: - case DEG_NODE_TYPE_EVAL_PARTICLES: - case DEG_NODE_TYPE_SHADING: - case DEG_NODE_TYPE_CACHE: - case DEG_NODE_TYPE_PROXY: - object->recalc |= OB_RECALC_DATA; - break; - case DEG_NODE_TYPE_PARAMETERS: - break; - } - } - /* When some target changes bone, we might need to re-run the - * whole IK solver, otherwise result might be unpredictable. - */ - if (comp_node->type == DEG_NODE_TYPE_BONE) { - ComponentDepsNode *pose_comp = - id_node->find_component(DEG_NODE_TYPE_EVAL_POSE); - BLI_assert(pose_comp != NULL); - if (pose_comp->done == COMPONENT_STATE_NONE) { - queue.push_front(pose_comp->get_entry_operation()); - pose_comp->done = COMPONENT_STATE_SCHEDULED; - } - } - } - - id_node->done = 1; - comp_node->done = COMPONENT_STATE_DONE; - - /* Flush to nodes along links... */ - /* TODO(sergey): This is mainly giving speedup due ot less queue pushes, which - * reduces number of memory allocations. - * - * We should try solve the allocation issue instead of doing crazy things here. - */ - if (node->outlinks.size() == 1) { - OperationDepsNode *to_node = (OperationDepsNode *)node->outlinks[0]->to; - if (to_node->scheduled == false) { - to_node->scheduled = true; - node = to_node; - } - else { - break; - } - } - else { - foreach (DepsRelation *rel, node->outlinks) { - OperationDepsNode *to_node = (OperationDepsNode *)rel->to; - if (to_node->scheduled == false) { - queue.push_front(to_node); - to_node->scheduled = true; - } - } - break; - } + flush_handle_id_node(id_node); + flush_handle_component_node(graph, + id_node, + comp_node, + &queue); + /* Flush to nodes along links. */ + op_node = flush_schedule_children(op_node, &queue); } } - DEG_DEBUG_PRINTF("Update flushed to %d objects\n", num_flushed_objects); + /* Inform editors about all changes. */ + flush_editors_id_update(bmain, graph); } static void graph_clear_func(void *data_v, int i)