Fix T99733: Objects with driven visibility are evaluated when not needed

The issue was caused by the fact that objects with driven or animated
visibility were considered visible by the dependency graph evaluation.

This change makes it so the dependency graph evaluation is aware of
visibility which might be changing. This is achieved by evaluating the
path of the graph which affects objects visibility and adjusts to it
before evaluating the rest of the graph.

There is some time penalty to this, but there does not seem to be a
way to fully avoid this penalty.

With the production shot from the heist project the FPS drops by a
tenth of a frame (~9.4 vs ~9.3 fps) when adding a driver to an object
which keeps it visible. Note that this is a bit hard to measure since
the FPS fluctuates quite a bit throughout the playback. On the other
hand, having a driver on a visibility of a heavy object from character
and setting visibility to false gives big speedup.

Also worth noting that there is no penalty at all when there are no
animated visibilities in the scene.

Differential Revision: https://developer.blender.org/D15498
This commit is contained in:
Sergey Sharybin 2022-07-20 10:04:02 +02:00
parent 4089b7b80b
commit 0dcee6a386
Notes: blender-bot 2024-05-30 13:00:56 +02:00
Referenced by commit 4dd409a185, Fix T99976: Animated visibility not rendering properly in viewport
Referenced by issue #99976, Regression: curve with animated visibility not rendering properly in viewport
Referenced by issue #99733, Objects with driven visibility are evaluated when not needed
Referenced by issue #122451, Crash on animated viewport visibility + animated mesh positions
17 changed files with 348 additions and 129 deletions

View file

@ -67,6 +67,8 @@ set(SRC
intern/eval/deg_eval_runtime_backup_sound.cc
intern/eval/deg_eval_runtime_backup_volume.cc
intern/eval/deg_eval_stats.cc
intern/eval/deg_eval_visibility.cc
intern/eval/deg_eval_visibility.h
intern/node/deg_node.cc
intern/node/deg_node_component.cc
intern/node/deg_node_factory.cc

View file

@ -29,6 +29,7 @@
#include "intern/depsgraph_tag.h"
#include "intern/depsgraph_type.h"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/eval/deg_eval_visibility.h"
#include "intern/node/deg_node.h"
#include "intern/node/deg_node_component.h"
#include "intern/node/deg_node_id.h"
@ -71,10 +72,15 @@ bool DepsgraphBuilder::need_pull_base_into_graph(const Base *base)
if (base->flag & base_flag) {
return true;
}
/* More involved check: since we don't support dynamic changes in dependency graph topology and
* all visible objects are to be part of dependency graph, we pull all objects which has animated
* visibility. */
const Object *object = base->object;
return is_object_visibility_animated(base->object);
}
bool DepsgraphBuilder::is_object_visibility_animated(const Object *object)
{
AnimatedPropertyID property_id;
if (graph_->mode == DAG_EVAL_VIEWPORT) {
property_id = AnimatedPropertyID(&object->id, &RNA_Object, "hide_viewport");
@ -127,84 +133,9 @@ bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, cons
/** \name Builder Finalizer.
* \{ */
namespace {
void deg_graph_build_flush_visibility(Depsgraph *graph)
{
enum {
DEG_NODE_VISITED = (1 << 0),
};
BLI_Stack *stack = BLI_stack_new(sizeof(OperationNode *), "DEG flush layers stack");
for (IDNode *id_node : graph->id_nodes) {
for (ComponentNode *comp_node : id_node->components.values()) {
comp_node->affects_directly_visible |= id_node->is_directly_visible;
}
}
for (OperationNode *op_node : graph->operations) {
op_node->custom_flags = 0;
op_node->num_links_pending = 0;
for (Relation *rel : op_node->outlinks) {
if ((rel->from->type == NodeType::OPERATION) && (rel->flag & RELATION_FLAG_CYCLIC) == 0) {
++op_node->num_links_pending;
}
}
if (op_node->num_links_pending == 0) {
BLI_stack_push(stack, &op_node);
op_node->custom_flags |= DEG_NODE_VISITED;
}
}
while (!BLI_stack_is_empty(stack)) {
OperationNode *op_node;
BLI_stack_pop(stack, &op_node);
/* Flush layers to parents. */
for (Relation *rel : op_node->inlinks) {
if (rel->from->type == NodeType::OPERATION) {
OperationNode *op_from = (OperationNode *)rel->from;
ComponentNode *comp_from = op_from->owner;
const bool target_directly_visible = op_node->owner->affects_directly_visible;
/* Visibility component forces all components of the current ID to be considered as
* affecting directly visible. */
if (comp_from->type == NodeType::VISIBILITY) {
if (target_directly_visible) {
IDNode *id_node_from = comp_from->owner;
for (ComponentNode *comp_node : id_node_from->components.values()) {
comp_node->affects_directly_visible |= target_directly_visible;
}
}
}
else {
comp_from->affects_directly_visible |= target_directly_visible;
}
}
}
/* Schedule parent nodes. */
for (Relation *rel : op_node->inlinks) {
if (rel->from->type == NodeType::OPERATION) {
OperationNode *op_from = (OperationNode *)rel->from;
if ((rel->flag & RELATION_FLAG_CYCLIC) == 0) {
BLI_assert(op_from->num_links_pending > 0);
--op_from->num_links_pending;
}
if ((op_from->num_links_pending == 0) && (op_from->custom_flags & DEG_NODE_VISITED) == 0) {
BLI_stack_push(stack, &op_from);
op_from->custom_flags |= DEG_NODE_VISITED;
}
}
}
}
BLI_stack_free(stack);
}
} // namespace
void deg_graph_build_finalize(Main *bmain, Depsgraph *graph)
{
/* Make sure dependencies of visible ID data-blocks are visible. */
deg_graph_build_flush_visibility(graph);
deg_graph_flush_visibility_flags(graph);
deg_graph_remove_unused_noops(graph);
/* Re-tag IDs for update if it was tagged before the relations

View file

@ -24,6 +24,8 @@ class DepsgraphBuilder {
virtual bool need_pull_base_into_graph(const Base *base);
virtual bool is_object_visibility_animated(const Object *object);
virtual bool check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan);
virtual bool check_pchan_has_bbone_segments(const Object *object, const bPoseChannel *pchan);
virtual bool check_pchan_has_bbone_segments(const Object *object, const char *bone_name);

View file

@ -107,6 +107,7 @@
#include "intern/depsgraph_tag.h"
#include "intern/depsgraph_type.h"
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/eval/deg_eval_visibility.h"
#include "intern/node/deg_node.h"
#include "intern/node/deg_node_component.h"
#include "intern/node/deg_node_id.h"
@ -179,11 +180,27 @@ IDNode *DepsgraphNodeBuilder::add_id_node(ID *id)
}
ComponentNode *visibility_component = id_node->add_component(NodeType::VISIBILITY);
OperationNode *visibility_operation = visibility_component->add_operation(
nullptr, OperationCode::VISIBILITY);
OperationNode *visibility_operation;
/* Optimization: currently only objects need a special visibility evaluation. For the rest ID
* types keep the node as a NO-OP so that relations can still be routed, but without penalty
* during the graph evaluation. */
if (id_type == ID_OB) {
visibility_operation = visibility_component->add_operation(
[id_node](::Depsgraph *depsgraph) {
deg_evaluate_object_node_visibility(depsgraph, id_node);
},
OperationCode::VISIBILITY);
}
else {
visibility_operation = visibility_component->add_operation(nullptr,
OperationCode::VISIBILITY);
}
/* Pin the node so that it and its relations are preserved by the unused nodes/relations
* deletion. This is mainly to make it easier to debug visibility. */
visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED;
visibility_operation->flag |= (OperationFlag::DEPSOP_FLAG_PINNED |
OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY);
graph_->operations.append(visibility_operation);
}
return id_node;
@ -652,7 +669,7 @@ void DepsgraphNodeBuilder::build_collection(LayerCollection *from_layer_collecti
IDNode *id_node;
if (built_map_.checkIsBuiltAndTag(collection)) {
id_node = find_id_node(&collection->id);
if (is_collection_visible && id_node->is_directly_visible == false &&
if (is_collection_visible && id_node->is_visible_on_build == false &&
id_node->is_collection_fully_expanded == true) {
/* Collection became visible, make sure nested collections and
* objects are poked with the new visibility flag, since they
@ -669,7 +686,7 @@ void DepsgraphNodeBuilder::build_collection(LayerCollection *from_layer_collecti
else {
/* Collection itself. */
id_node = add_id_node(&collection->id);
id_node->is_directly_visible = is_collection_visible;
id_node->is_visible_on_build = is_collection_visible;
build_idproperties(collection->id.properties);
add_operation_node(&collection->id, NodeType::GEOMETRY, OperationCode::GEOMETRY_EVAL_DONE);
@ -716,7 +733,7 @@ void DepsgraphNodeBuilder::build_object(int base_index,
build_object_flags(base_index, object, linked_state);
}
id_node->linked_state = max(id_node->linked_state, linked_state);
id_node->is_directly_visible |= is_visible;
id_node->is_visible_on_build |= is_visible;
id_node->has_base |= (base_index != -1);
/* There is no relation path which will connect current object with all the ones which come
@ -735,10 +752,10 @@ void DepsgraphNodeBuilder::build_object(int base_index,
* Probably need to assign that to something non-nullptr, but then the logic here will still be
* somewhat weird. */
if (scene_ != nullptr && object == scene_->camera) {
id_node->is_directly_visible = true;
id_node->is_visible_on_build = true;
}
else {
id_node->is_directly_visible = is_visible;
id_node->is_visible_on_build = is_visible;
}
id_node->has_base |= (base_index != -1);
/* Various flags, flushing from bases/collections. */

View file

@ -92,14 +92,20 @@ void DepsgraphNodeBuilder::build_view_layer(Scene *scene,
int base_index = 0;
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
/* object itself */
if (need_pull_base_into_graph(base)) {
/* NOTE: We consider object visible even if it's currently
* restricted by the base/restriction flags. Otherwise its drivers
* will never be evaluated.
*
* TODO(sergey): Need to go more granular on visibility checks. */
build_object(base_index, base->object, linked_state, true);
base_index++;
if (!need_pull_base_into_graph(base)) {
continue;
}
/* NOTE: We consider object visible even if it's currently
* restricted by the base/restriction flags. Otherwise its drivers
* will never be evaluated.
*
* TODO(sergey): Need to go more granular on visibility checks. */
build_object(base_index, base->object, linked_state, true);
base_index++;
if (!graph_->has_animated_visibility) {
graph_->has_animated_visibility |= is_object_visibility_animated(base->object);
}
}
build_layer_collections(&view_layer->layer_collections);

View file

@ -810,6 +810,13 @@ void DepsgraphRelationBuilder::build_object(Object *object)
/* Parameters. */
build_parameters(&object->id);
/* Visibility.
* Evaluate visibility node after the object's base_flags has been updated to the current state
* of collections restrict and object's restrict flags. */
const ComponentKey object_from_layer_entry_key(&object->id, NodeType::OBJECT_FROM_LAYER);
const ComponentKey visibility_key(&object->id, NodeType::VISIBILITY);
add_relation(object_from_layer_entry_key, visibility_key, "Object Visibility");
}
/* NOTE: Implies that the object has base in the current view layer. */

View file

@ -45,7 +45,9 @@ namespace blender::deg {
Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode)
: time_source(nullptr),
has_animated_visibility(false),
need_update_relations(true),
need_update_nodes_visibility(true),
need_tag_id_on_graph_visibility_update(true),
need_tag_id_on_graph_visibility_time_update(false),
bmain(bmain),

View file

@ -88,9 +88,15 @@ struct Depsgraph {
/* Top-level time source node. */
TimeSourceNode *time_source;
/* The graph contains data-blocks whose visibility depends on evaluation (driven or animated). */
bool has_animated_visibility;
/* Indicates whether relations needs to be updated. */
bool need_update_relations;
/* Indicates whether indirect effect of nodes on a directly visible ones needs to be updated. */
bool need_update_nodes_visibility;
/* Indicated whether IDs in this graph are to be tagged as if they first appear visible, with
* an optional tag for their animation (time) update. */
bool need_tag_id_on_graph_visibility_update;

View file

@ -219,7 +219,9 @@ bool deg_iterator_objects_step(DEGObjectIterData *data)
for (; data->id_node_index < data->num_id_nodes; data->id_node_index++) {
deg::IDNode *id_node = deg_graph->id_nodes[data->id_node_index];
if (!id_node->is_directly_visible) {
/* Use the build time visibility so that the ID is not appearing/disappearing throughout
* animation export. */
if (!id_node->is_visible_on_build) {
continue;
}
@ -338,10 +340,13 @@ static void DEG_iterator_ids_step(BLI_Iterator *iter, deg::IDNode *id_node, bool
{
ID *id_cow = id_node->id_cow;
if (!id_node->is_directly_visible) {
/* Use the build time visibility so that the ID is not appearing/disappearing throughout
* animation export. */
if (!id_node->is_visible_on_build) {
iter->skip = true;
return;
}
if (only_updated && !(id_cow->recalc & ID_RECALC_ALL)) {
/* Node-tree is considered part of the data-block. */
bNodeTree *ntree = ntreeFromID(id_cow);

View file

@ -37,6 +37,7 @@
#include "intern/eval/deg_eval_copy_on_write.h"
#include "intern/eval/deg_eval_flush.h"
#include "intern/eval/deg_eval_stats.h"
#include "intern/eval/deg_eval_visibility.h"
#include "intern/node/deg_node.h"
#include "intern/node/deg_node_component.h"
#include "intern/node/deg_node_id.h"
@ -69,6 +70,9 @@ enum class EvaluationStage {
* involved. */
COPY_ON_WRITE,
/* Evaluate actual ID nodes visiblity based on the current state of animation and drivers. */
DYNAMIC_VISIBILITY,
/* Threaded evaluation of all possible operations. */
THREADED_EVALUATION,
@ -83,7 +87,8 @@ struct DepsgraphEvalState {
Depsgraph *graph;
bool do_stats;
EvaluationStage stage;
bool need_single_thread_pass;
bool need_update_pending_parents = true;
bool need_single_thread_pass = false;
};
void evaluate_node(const DepsgraphEvalState *state, OperationNode *operation_node)
@ -122,24 +127,31 @@ void deg_task_run_func(TaskPool *pool, void *taskdata)
schedule_children(state, operation_node, schedule_node_to_pool, pool);
}
bool check_operation_node_visible(OperationNode *op_node)
bool check_operation_node_visible(const DepsgraphEvalState *state, OperationNode *op_node)
{
const ComponentNode *comp_node = op_node->owner;
/* Special exception, copy on write component is to be always evaluated,
* to keep copied "database" in a consistent state. */
/* Special case for copy on write component: it is to be always evaluated, to keep copied
* "database" in a consistent state. */
if (comp_node->type == NodeType::COPY_ON_WRITE) {
return true;
}
return comp_node->affects_directly_visible;
/* Special case for dynamic visiblity pass: the actual visibility is not yet known, so limit to
* only operations which affects visibility. */
if (state->stage == EvaluationStage::DYNAMIC_VISIBILITY) {
return op_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY;
}
return comp_node->affects_visible_id;
}
void calculate_pending_parents_for_node(OperationNode *node)
void calculate_pending_parents_for_node(const DepsgraphEvalState *state, OperationNode *node)
{
/* Update counters, applies for both visible and invisible IDs. */
node->num_links_pending = 0;
node->scheduled = false;
/* Invisible IDs requires no pending operations. */
if (!check_operation_node_visible(node)) {
if (!check_operation_node_visible(state, node)) {
return;
}
/* No need to bother with anything if node is not tagged for update. */
@ -153,7 +165,7 @@ void calculate_pending_parents_for_node(OperationNode *node)
* calculation, but how is it possible that visible object depends
* on an invisible? This is something what is prohibited after
* deg_graph_build_flush_layers(). */
if (!check_operation_node_visible(from)) {
if (!check_operation_node_visible(state, from)) {
continue;
}
/* No need to wait for operation which is up to date. */
@ -165,20 +177,24 @@ void calculate_pending_parents_for_node(OperationNode *node)
}
}
void calculate_pending_parents(Depsgraph *graph)
void calculate_pending_parents_if_needed(DepsgraphEvalState *state)
{
for (OperationNode *node : graph->operations) {
calculate_pending_parents_for_node(node);
if (!state->need_update_pending_parents) {
return;
}
for (OperationNode *node : state->graph->operations) {
calculate_pending_parents_for_node(state, node);
}
state->need_update_pending_parents = false;
}
void initialize_execution(DepsgraphEvalState *state, Depsgraph *graph)
{
const bool do_stats = state->do_stats;
calculate_pending_parents(graph);
/* Clear tags and other things which needs to be clear. */
for (OperationNode *node : graph->operations) {
if (do_stats) {
if (state->do_stats) {
for (OperationNode *node : graph->operations) {
node->stats.reset_current();
}
}
@ -203,11 +219,10 @@ bool need_evaluate_operation_at_stage(DepsgraphEvalState *state,
case EvaluationStage::COPY_ON_WRITE:
return (component_node->type == NodeType::COPY_ON_WRITE);
case EvaluationStage::DYNAMIC_VISIBILITY:
return operation_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY;
case EvaluationStage::THREADED_EVALUATION:
/* 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
* stage). */
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;
@ -233,7 +248,7 @@ void schedule_node(DepsgraphEvalState *state,
ScheduleFunctionArgs... schedule_function_args)
{
/* No need to schedule nodes of invisible ID. */
if (!check_operation_node_visible(node)) {
if (!check_operation_node_visible(state, node)) {
return;
}
/* No need to schedule operations which are not tagged for update, they are
@ -317,6 +332,8 @@ void evaluate_graph_threaded_stage(DepsgraphEvalState *state,
{
state->stage = stage;
calculate_pending_parents_if_needed(state);
schedule_graph(state, schedule_node_to_pool, task_pool);
BLI_task_pool_work_and_wait(task_pool);
}
@ -328,6 +345,8 @@ void evaluate_graph_single_threaded_if_needed(DepsgraphEvalState *state)
return;
}
BLI_assert(!state->need_update_pending_parents);
state->stage = EvaluationStage::SINGLE_THREADED_WORKAROUND;
GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *));
@ -392,7 +411,6 @@ void deg_evaluate_on_refresh(Depsgraph *graph)
DepsgraphEvalState state;
state.graph = graph;
state.do_stats = graph->debug.do_time_debug();
state.need_single_thread_pass = false;
/* Prepare all nodes for evaluation. */
initialize_execution(&state, graph);
@ -403,6 +421,10 @@ void deg_evaluate_on_refresh(Depsgraph *graph)
* that if a dependency graph has a cycle evaluation functions will always "see" valid expanded
* datablock. It might not be evaluated yet, but at least the datablock will be valid.
*
* - If there is potentially dynamically changing visibility in the graph update the actual
* nodes visibilities, so that actual heavy data evaluation can benefit from knowledge that
* something heavy is not currently visible.
*
* - Multi-threaded evaluation of all possible nodes.
* Certain operations (and their subtrees) could be ignored. For example, meta-balls are not
* safe from threading point of view, so the threaded evaluation will stop at the metaball
@ -413,6 +435,24 @@ void deg_evaluate_on_refresh(Depsgraph *graph)
TaskPool *task_pool = deg_evaluate_task_pool_create(&state);
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::COPY_ON_WRITE);
if (graph->has_animated_visibility) {
/* Update pending parents including only the ones which are affecting operations which are
* affecting visibility. */
state.need_update_pending_parents = true;
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::DYNAMIC_VISIBILITY);
deg_graph_flush_visibility_flags_if_needed(graph);
/* Update parents to an updated visibility and evaluation stage.
*
* Need to do it regardless of whether visibility is actually changed or not: current state of
* the pending parents are all zeroes because it was previously calculated for only visibility
* related nodes and those are fully evaluated by now. */
state.need_update_pending_parents = true;
}
evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::THREADED_EVALUATION);
BLI_task_pool_free(task_pool);

View file

@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup depsgraph
*/
#include "intern/eval/deg_eval_visibility.h"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
#include "BLI_assert.h"
#include "BLI_stack.h"
#include "DEG_depsgraph.h"
#include "intern/depsgraph.h"
#include "intern/depsgraph_relation.h"
#include "intern/node/deg_node.h"
#include "intern/node/deg_node_component.h"
#include "intern/node/deg_node_id.h"
#include "intern/node/deg_node_operation.h"
namespace blender::deg {
void deg_evaluate_object_node_visibility(::Depsgraph *depsgraph, IDNode *id_node)
{
BLI_assert(GS(id_node->id_cow->name) == ID_OB);
Depsgraph *graph = reinterpret_cast<Depsgraph *>(depsgraph);
const Object *object = reinterpret_cast<const Object *>(id_node->id_cow);
DEG_debug_print_eval(depsgraph, __func__, object->id.name, &object->id);
const int required_flags = (graph->mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT :
BASE_ENABLED_RENDER;
const bool is_enabled = object->base_flag & required_flags;
if (id_node->is_enabled_on_eval != is_enabled) {
id_node->is_enabled_on_eval = is_enabled;
/* Tag dependency graph for changed visibility, so that it is updated on all dependencies prior
* to a pass of an actual evaluation. */
graph->need_update_nodes_visibility = true;
}
}
void deg_graph_flush_visibility_flags(Depsgraph *graph)
{
enum {
DEG_NODE_VISITED = (1 << 0),
};
for (IDNode *id_node : graph->id_nodes) {
for (ComponentNode *comp_node : id_node->components.values()) {
comp_node->possibly_affects_visible_id = id_node->is_visible_on_build;
comp_node->affects_visible_id = id_node->is_visible_on_build && id_node->is_enabled_on_eval;
}
}
BLI_Stack *stack = BLI_stack_new(sizeof(OperationNode *), "DEG flush layers stack");
for (OperationNode *op_node : graph->operations) {
op_node->custom_flags = 0;
op_node->num_links_pending = 0;
for (Relation *rel : op_node->outlinks) {
if ((rel->from->type == NodeType::OPERATION) && (rel->flag & RELATION_FLAG_CYCLIC) == 0) {
++op_node->num_links_pending;
}
}
if (op_node->num_links_pending == 0) {
BLI_stack_push(stack, &op_node);
op_node->custom_flags |= DEG_NODE_VISITED;
}
}
while (!BLI_stack_is_empty(stack)) {
OperationNode *op_node;
BLI_stack_pop(stack, &op_node);
/* Flush flags to parents. */
for (Relation *rel : op_node->inlinks) {
if (rel->from->type == NodeType::OPERATION) {
const OperationNode *op_to = reinterpret_cast<const OperationNode *>(rel->to);
const ComponentNode *comp_to = op_to->owner;
OperationNode *op_from = reinterpret_cast<OperationNode *>(rel->from);
ComponentNode *comp_from = op_from->owner;
const bool target_possibly_affects_visible_id = comp_to->possibly_affects_visible_id;
const bool target_affects_visible_id = comp_to->affects_visible_id;
op_from->flag |= (op_to->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY);
/* Visibility component forces all components of the current ID to be considered as
* affecting directly visible. */
if (comp_from->type == NodeType::VISIBILITY) {
const IDNode *id_node_from = comp_from->owner;
if (target_possibly_affects_visible_id) {
for (ComponentNode *comp_node : id_node_from->components.values()) {
comp_node->possibly_affects_visible_id |= target_possibly_affects_visible_id;
}
}
if (target_affects_visible_id) {
for (ComponentNode *comp_node : id_node_from->components.values()) {
comp_node->affects_visible_id |= target_affects_visible_id;
}
}
}
else {
comp_from->possibly_affects_visible_id |= target_possibly_affects_visible_id;
comp_from->affects_visible_id |= target_affects_visible_id;
}
}
}
/* Schedule parent nodes. */
for (Relation *rel : op_node->inlinks) {
if (rel->from->type == NodeType::OPERATION) {
OperationNode *op_from = (OperationNode *)rel->from;
if ((rel->flag & RELATION_FLAG_CYCLIC) == 0) {
BLI_assert(op_from->num_links_pending > 0);
--op_from->num_links_pending;
}
if ((op_from->num_links_pending == 0) && (op_from->custom_flags & DEG_NODE_VISITED) == 0) {
BLI_stack_push(stack, &op_from);
op_from->custom_flags |= DEG_NODE_VISITED;
}
}
}
}
BLI_stack_free(stack);
graph->need_update_nodes_visibility = false;
}
void deg_graph_flush_visibility_flags_if_needed(Depsgraph *graph)
{
if (!graph->need_update_nodes_visibility) {
return;
}
deg_graph_flush_visibility_flags(graph);
}
} // namespace blender::deg

View file

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
/** \file
* \ingroup depsgraph
*/
#pragma once
struct Depsgraph;
namespace blender::deg {
struct Depsgraph;
struct IDNode;
/* Evaluate actual node visibility flags based on the current state of object's visibility
* restriction flags. */
void deg_evaluate_object_node_visibility(::Depsgraph *depsgraph, IDNode *id_node);
/* Flush both static and dynamic visibility flags from leaves up to the roots, making it possible
* to know whether a node has affect on something (potentially) visible. */
void deg_graph_flush_visibility_flags(Depsgraph *graph);
void deg_graph_flush_visibility_flags_if_needed(Depsgraph *graph);
} // namespace blender::deg

View file

@ -67,7 +67,10 @@ uint64_t ComponentNode::OperationIDKey::hash() const
}
ComponentNode::ComponentNode()
: entry_operation(nullptr), exit_operation(nullptr), affects_directly_visible(false)
: entry_operation(nullptr),
exit_operation(nullptr),
possibly_affects_visible_id(false),
affects_visible_id(false)
{
operations_map = new Map<ComponentNode::OperationIDKey, OperationNode *>();
}
@ -90,7 +93,7 @@ string ComponentNode::identifier() const
const string idname = this->owner->name;
const string typebuf = "" + to_string(static_cast<int>(type)) + ")";
return typebuf + name + " : " + idname +
"( affects_directly_visible: " + (affects_directly_visible ? "true" : "false") + ")";
"( affects_visible_id: " + (affects_visible_id ? "true" : "false") + ")";
}
OperationNode *ComponentNode::find_operation(OperationIDKey key) const

View file

@ -137,9 +137,17 @@ struct ComponentNode : public Node {
return true;
}
/* Denotes whether this component affects (possibly indirectly) on a
* directly visible object. */
bool affects_directly_visible;
/* The component has (possibly indirect) effect on a data-block whose node has
* is_visible_on_build set to true.
*
* This field is ensured to be up-to-date prior to `IDNode::finalize_build()`. */
bool possibly_affects_visible_id;
/* Denotes whether this component actually affects (possibly indirectly) on a directly visible
* object. Includes possibly run-time visibility update of ID nodes.
*
* NOTE: Is only reliable after `deg_graph_flush_visibility()`. */
bool affects_visible_id;
};
/* ---------------------------------------- */
@ -197,7 +205,7 @@ DEG_COMPONENT_NODE_DECLARE_GENERIC(Dupli);
DEG_COMPONENT_NODE_DECLARE_GENERIC(Audio);
DEG_COMPONENT_NODE_DECLARE_GENERIC(Armature);
DEG_COMPONENT_NODE_DECLARE_GENERIC(GenericDatablock);
DEG_COMPONENT_NODE_DECLARE_NO_COW(Visibility);
DEG_COMPONENT_NODE_DECLARE_NO_COW_TAG_ON_UPDATE(Visibility);
DEG_COMPONENT_NODE_DECLARE_GENERIC(Simulation);
DEG_COMPONENT_NODE_DECLARE_GENERIC(NTreeOutput);
@ -214,7 +222,7 @@ struct SynchronizationComponentNode : public ComponentNode {
* The design is such that the synchronization is supposed to happen whenever any part of the
* ID changed/evaluated. Here we mark the component as "visible" so that genetic recalc flag
* flushing and scheduling will handle the component in a generic manner. */
affects_directly_visible = true;
affects_visible_id = true;
}
DEG_COMPONENT_NODE_DECLARE;

View file

@ -69,7 +69,8 @@ void IDNode::init(const ID *id, const char *UNUSED(subdata))
customdata_masks = DEGCustomDataMeshMasks();
previous_customdata_masks = DEGCustomDataMeshMasks();
linked_state = DEG_ID_LINKED_INDIRECTLY;
is_directly_visible = true;
is_visible_on_build = true;
is_enabled_on_eval = true;
is_collection_fully_expanded = false;
has_base = false;
is_user_modified = false;
@ -138,8 +139,8 @@ string IDNode::identifier() const
BLI_snprintf(orig_ptr, sizeof(orig_ptr), "%p", id_orig);
BLI_snprintf(cow_ptr, sizeof(cow_ptr), "%p", id_cow);
return string(nodeTypeAsString(type)) + " : " + name + " (orig: " + orig_ptr +
", eval: " + cow_ptr + ", is_directly_visible " +
(is_directly_visible ? "true" : "false") + ")";
", eval: " + cow_ptr + ", is_visible_on_build " +
(is_visible_on_build ? "true" : "false") + ")";
}
ComponentNode *IDNode::find_component(NodeType type, const char *name) const
@ -188,7 +189,7 @@ IDComponentsMask IDNode::get_visible_components_mask() const
{
IDComponentsMask result = 0;
for (ComponentNode *comp_node : components.values()) {
if (comp_node->affects_directly_visible) {
if (comp_node->possibly_affects_visible_id) {
const int component_type_as_int = static_cast<int>(comp_node->type);
BLI_assert(component_type_as_int < 64);
result |= (1ULL << component_type_as_int);

View file

@ -91,8 +91,21 @@ struct IDNode : public Node {
eDepsNode_LinkedState_Type linked_state;
/* Indicates the data-block is visible in the evaluated scene. */
bool is_directly_visible;
/* Indicates the data-block is to be considered visible in the evaluated scene.
*
* This flag is set during dependency graph build where check for an actual visibility might not
* be available yet due to driven or animated restriction flags. So it is more of an intent or,
* in other words, plausibility of the data-block to be visible. */
bool is_visible_on_build;
/* Evaluated state of whether evaluation considered this data-block "enabled".
*
* For objects this is derived from the base restriction flags, which might be animated or
* driven. It is set to `BASE_ENABLED_<VIEWPORT, RENDER>` (depending on the graph mode) after
* the object's flags from layer were evaluated.
*
* For other data-types is currently always true. */
bool is_enabled_on_eval;
/* For the collection type of ID, denotes whether collection was fully
* recursed into. */

View file

@ -214,6 +214,9 @@ enum OperationFlag {
* and not for connecting to an operation. */
DEPSOP_FLAG_PINNED = (1 << 3),
/* The operation directly or indirectly affects ID node visibility. */
DEPSOP_FLAG_AFFECTS_VISIBILITY = (1 << 4),
/* Set of flags which gets flushed along the relations. */
DEPSOP_FLAG_FLUSH = (DEPSOP_FLAG_USER_MODIFIED),
};