Node Tools: Avoid depsgraph evaluation when possible
Currently for node tools we create and evaluate a temporary depsgraph with all the selected object data-blocks and all data-blocks referenced by the node tree. Needless to say, this can be very slow when those data-blocks contain arbitrary procedural operations. Re-evaluating all the selected objects is particularly because it will give a slowdown even in very basic uses of node tools. Originally I hoped that geometry nodes could be made to work with original as well as evaluated data-blocks. But that would require far too many tricky changes and arguably isn't right design-wise anyway. Instead of that, this commit makes node tools dependency graph evaluation more fine-grained in a few ways. 1. Remove the evaluation of selected objects. These are always visible in the viewport and part of the active depsgraph anyway. To protect against cyclic dependencies, we now compare `orig_id` instead of the object pointer itself. 2. Evaluate the node group and its dependencies in a separate depsgraph used only when necessary. This allows using the original node tree without any copies when it doesn't reference any data-blocks. 3. Evaluate IDs from node group inputs (from the redo panel) in the extra depsgraph as well, only when necessary. Pull Request: https://projects.blender.org/blender/blender/pulls/120723
This commit is contained in:
@@ -101,6 +101,12 @@ Collection *BKE_collection_master_add(Scene *scene);
|
||||
bool BKE_collection_has_object(Collection *collection, const Object *ob);
|
||||
bool BKE_collection_has_object_recursive(Collection *collection, Object *ob);
|
||||
bool BKE_collection_has_object_recursive_instanced(Collection *collection, Object *ob);
|
||||
/**
|
||||
* Find whether an evaluated object's original ID is contained or instanced by any object in this
|
||||
* collection. The collection is expected to be an evaluated data-block too.
|
||||
*/
|
||||
bool BKE_collection_has_object_recursive_instanced_orig_id(Collection *collection_eval,
|
||||
Object *object_eval);
|
||||
Collection *BKE_collection_object_find(Main *bmain,
|
||||
Scene *scene,
|
||||
Collection *collection,
|
||||
|
||||
@@ -1055,6 +1055,20 @@ bool BKE_collection_has_object_recursive_instanced(Collection *collection, Objec
|
||||
return BLI_findptr(&objects, ob, offsetof(Base, object));
|
||||
}
|
||||
|
||||
bool BKE_collection_has_object_recursive_instanced_orig_id(Collection *collection_eval,
|
||||
Object *object_eval)
|
||||
{
|
||||
BLI_assert(collection_eval->id.tag & LIB_TAG_COPIED_ON_EVAL);
|
||||
const ID *ob_orig = DEG_get_original_id(&object_eval->id);
|
||||
const ListBase objects = BKE_collection_object_cache_instanced_get(collection_eval);
|
||||
LISTBASE_FOREACH (Base *, base, &objects) {
|
||||
if (DEG_get_original_id(&base->object->id) == ob_orig) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Collection *collection_next_find(Main *bmain, Scene *scene, Collection *collection)
|
||||
{
|
||||
if (scene && collection == scene->master_collection) {
|
||||
|
||||
@@ -128,6 +128,12 @@ bool DEG_is_evaluated_object(const Object *object);
|
||||
*/
|
||||
bool DEG_is_fully_evaluated(const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* Check every component of the data-block is evaluated. For example, an object disabled in the
|
||||
* viewport is not fully evaluated, even though the copy-on-eval data-block is created.
|
||||
*/
|
||||
bool DEG_id_is_fully_evaluated(const Depsgraph *depsgraph, const ID *id_eval);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "intern/depsgraph.hh"
|
||||
#include "intern/eval/deg_eval_copy_on_write.h"
|
||||
#include "intern/node/deg_node_component.hh"
|
||||
#include "intern/node/deg_node_id.hh"
|
||||
|
||||
namespace blender::deg {
|
||||
@@ -340,3 +341,22 @@ bool DEG_is_fully_evaluated(const Depsgraph *depsgraph)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DEG_id_is_fully_evaluated(const Depsgraph *depsgraph, const ID *id_eval)
|
||||
{
|
||||
const deg::Depsgraph *deg_graph = reinterpret_cast<const deg::Depsgraph *>(depsgraph);
|
||||
/* Only us the original ID pointer to look up the IDNode, do not dereference it. */
|
||||
const ID *id_orig = deg::get_original_id(id_eval);
|
||||
const deg::IDNode *id_node = deg_graph->find_id_node(id_orig);
|
||||
if (!id_node) {
|
||||
return false;
|
||||
}
|
||||
for (deg::ComponentNode *component : id_node->components.values()) {
|
||||
for (deg::OperationNode *operation : component->operations) {
|
||||
if (operation->flag & deg::DEPSOP_FLAG_NEEDS_UPDATE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_material.h"
|
||||
#include "BKE_mesh.hh"
|
||||
@@ -274,49 +275,63 @@ static void store_result_geometry(
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dependency graph referencing all data-blocks used by the tree, and all selected
|
||||
* objects. Adding the selected objects is necessary because they are currently compared by pointer
|
||||
* to other evaluated objects inside of geometry nodes.
|
||||
* Gather IDs used by the node group, and the node group itself if there are any. We need to use
|
||||
* *all* IDs because the only mechanism we have to replace the socket ID pointers with their
|
||||
* evaluated counterparts is evaluating the node group data-block itself.
|
||||
*/
|
||||
static Depsgraph *build_depsgraph_from_indirect_ids(Main &bmain,
|
||||
Scene &scene,
|
||||
ViewLayer &view_layer,
|
||||
const bNodeTree &node_tree_orig,
|
||||
const Span<const Object *> objects,
|
||||
const IDProperty &properties)
|
||||
static void gather_node_group_ids(const bNodeTree &node_tree, Set<ID *> &ids)
|
||||
{
|
||||
Set<ID *> ids_for_relations;
|
||||
const int orig_size = ids.size();
|
||||
|
||||
bool needs_own_transform_relation = false;
|
||||
bool needs_scene_camera_relation = false;
|
||||
nodes::find_node_tree_dependencies(node_tree_orig,
|
||||
ids_for_relations,
|
||||
needs_own_transform_relation,
|
||||
needs_scene_camera_relation);
|
||||
nodes::find_node_tree_dependencies(
|
||||
node_tree, ids, needs_own_transform_relation, needs_scene_camera_relation);
|
||||
if (ids.size() != orig_size) {
|
||||
/* Only evaluate the node group if it references data-blocks. In that case it needs to be
|
||||
* evaluated so that ID pointers are switched to point to evaluated data-blocks. */
|
||||
ids.add(const_cast<ID *>(&node_tree.id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather IDs referenced from node group input properties (the redo panel). Skip IDs that are
|
||||
* already fully evaluated in the active depsgraph. In the end, the group input properties will be
|
||||
* copied to contain evaluated data-blocks from the active and/or an extra depsgraph.
|
||||
*/
|
||||
static void gather_input_ids(const Depsgraph &depsgraph_active,
|
||||
const IDProperty &properties,
|
||||
Set<ID *> &ids)
|
||||
{
|
||||
IDP_foreach_property(
|
||||
&const_cast<IDProperty &>(properties), IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
|
||||
if (ID *id = IDP_Id(property)) {
|
||||
ids_for_relations.add(id);
|
||||
if (!DEG_id_is_fully_evaluated(&depsgraph_active, id)) {
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Vector<const ID *> ids;
|
||||
ids.append(&node_tree_orig.id);
|
||||
ids.extend(objects.cast<const ID *>());
|
||||
ids.insert(ids.size(), ids_for_relations.begin(), ids_for_relations.end());
|
||||
|
||||
Depsgraph *depsgraph = DEG_graph_new(&bmain, &scene, &view_layer, DAG_EVAL_VIEWPORT);
|
||||
DEG_graph_build_from_ids(depsgraph, {const_cast<ID **>(ids.data()), ids.size()});
|
||||
static Depsgraph *build_extra_depsgraph(const Depsgraph &depsgraph_active, const Set<ID *> &ids)
|
||||
{
|
||||
Depsgraph *depsgraph = DEG_graph_new(DEG_get_bmain(&depsgraph_active),
|
||||
DEG_get_input_scene(&depsgraph_active),
|
||||
DEG_get_input_view_layer(&depsgraph_active),
|
||||
DEG_get_mode(&depsgraph_active));
|
||||
DEG_graph_build_from_ids(depsgraph, Vector<ID *>(ids.begin(), ids.end()));
|
||||
DEG_evaluate_on_refresh(depsgraph);
|
||||
return depsgraph;
|
||||
}
|
||||
|
||||
static IDProperty *replace_inputs_evaluated_data_blocks(const IDProperty &op_properties,
|
||||
const Depsgraph &depsgraph)
|
||||
static IDProperty *replace_inputs_evaluated_data_blocks(
|
||||
const IDProperty &op_properties, const nodes::GeoNodesOperatorDepsgraphs &depsgraphs)
|
||||
{
|
||||
/* We just create a temporary copy, so don't adjust data-block user count. */
|
||||
IDProperty *properties = IDP_CopyProperty_ex(&op_properties, LIB_ID_CREATE_NO_USER_REFCOUNT);
|
||||
IDP_foreach_property(properties, IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
|
||||
if (ID *id = IDP_Id(property)) {
|
||||
property->data.pointer = DEG_get_evaluated_id(&depsgraph, id);
|
||||
property->data.pointer = const_cast<ID *>(depsgraphs.get_evaluated_id(*id));
|
||||
}
|
||||
});
|
||||
return properties;
|
||||
@@ -378,7 +393,6 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
if (!active_object) {
|
||||
return OPERATOR_CANCELLED;
|
||||
@@ -392,13 +406,26 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
|
||||
|
||||
const Vector<Object *> objects = gather_supported_objects(*C, *bmain, mode);
|
||||
|
||||
Depsgraph *depsgraph = build_depsgraph_from_indirect_ids(
|
||||
*bmain, *scene, *view_layer, *node_tree_orig, objects, *op->properties);
|
||||
DEG_evaluate_on_refresh(depsgraph);
|
||||
BLI_SCOPED_DEFER([&]() { DEG_graph_free(depsgraph); });
|
||||
Depsgraph *depsgraph_active = CTX_data_ensure_evaluated_depsgraph(C);
|
||||
Set<ID *> extra_ids;
|
||||
gather_node_group_ids(*node_tree_orig, extra_ids);
|
||||
gather_input_ids(*depsgraph_active, *op->properties, extra_ids);
|
||||
const nodes::GeoNodesOperatorDepsgraphs depsgraphs{
|
||||
depsgraph_active,
|
||||
extra_ids.is_empty() ? nullptr : build_extra_depsgraph(*depsgraph_active, extra_ids),
|
||||
};
|
||||
|
||||
const bNodeTree *node_tree = reinterpret_cast<const bNodeTree *>(
|
||||
DEG_get_evaluated_id(depsgraph, const_cast<ID *>(&node_tree_orig->id)));
|
||||
IDProperty *properties = replace_inputs_evaluated_data_blocks(*op->properties, depsgraphs);
|
||||
BLI_SCOPED_DEFER([&]() { IDP_FreeProperty_ex(properties, false); });
|
||||
|
||||
const bNodeTree *node_tree = nullptr;
|
||||
if (depsgraphs.extra) {
|
||||
node_tree = reinterpret_cast<const bNodeTree *>(
|
||||
DEG_get_evaluated_id(depsgraphs.extra, const_cast<ID *>(&node_tree_orig->id)));
|
||||
}
|
||||
else {
|
||||
node_tree = node_tree_orig;
|
||||
}
|
||||
|
||||
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
|
||||
nodes::ensure_geometry_nodes_lazy_function_graph(*node_tree);
|
||||
@@ -430,9 +457,6 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
IDProperty *properties = replace_inputs_evaluated_data_blocks(*op->properties, *depsgraph);
|
||||
BLI_SCOPED_DEFER([&]() { IDP_FreeProperty_ex(properties, false); });
|
||||
|
||||
bke::OperatorComputeContext compute_context;
|
||||
Set<ComputeContextHash> socket_log_contexts;
|
||||
GeoOperatorLog &eval_log = get_static_eval_log();
|
||||
@@ -443,9 +467,9 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
|
||||
for (Object *object : objects) {
|
||||
nodes::GeoNodesOperatorData operator_eval_data{};
|
||||
operator_eval_data.mode = mode;
|
||||
operator_eval_data.depsgraph = depsgraph;
|
||||
operator_eval_data.self_object = DEG_get_evaluated_object(depsgraph, object);
|
||||
operator_eval_data.scene = DEG_get_evaluated_scene(depsgraph);
|
||||
operator_eval_data.depsgraphs = &depsgraphs;
|
||||
operator_eval_data.self_object_orig = object;
|
||||
operator_eval_data.scene_orig = scene;
|
||||
|
||||
nodes::GeoNodesCallData call_data{};
|
||||
call_data.operator_data = &operator_eval_data;
|
||||
|
||||
@@ -232,7 +232,7 @@ class GeoNodeExecParams {
|
||||
return data->call_data->modifier_data->depsgraph;
|
||||
}
|
||||
if (data->call_data->operator_data) {
|
||||
return data->call_data->operator_data->depsgraph;
|
||||
return data->call_data->operator_data->depsgraphs->active;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -164,13 +164,32 @@ struct GeoNodesModifierData {
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
};
|
||||
|
||||
struct GeoNodesOperatorDepsgraphs {
|
||||
/** Current evaluated depsgraph from the viewport. Shouldn't be null. */
|
||||
const Depsgraph *active = nullptr;
|
||||
/**
|
||||
* Depsgraph containing IDs referenced by the node tree and the node tree itself and from node
|
||||
* group inputs (the redo panel).
|
||||
*/
|
||||
Depsgraph *extra = nullptr;
|
||||
|
||||
~GeoNodesOperatorDepsgraphs();
|
||||
|
||||
/**
|
||||
* The evaluated data-block might be in the scene's active despgraph, in that case we should use
|
||||
* it directly. Otherwise retrieve it from the extra depsgraph that was built for all other
|
||||
* data-blocks. Return null if it isn't found, generally geometry nodes can handle null ID
|
||||
* pointers.
|
||||
*/
|
||||
const ID *get_evaluated_id(const ID &id_orig) const;
|
||||
};
|
||||
|
||||
struct GeoNodesOperatorData {
|
||||
eObjectMode mode;
|
||||
/** The object currently effected by the operator. */
|
||||
const Object *self_object = nullptr;
|
||||
/** Current evaluated depsgraph. */
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
const Object *self_object_orig = nullptr;
|
||||
const GeoNodesOperatorDepsgraphs *depsgraphs = nullptr;
|
||||
Scene *scene_orig = nullptr;
|
||||
};
|
||||
|
||||
struct GeoNodesCallData {
|
||||
|
||||
@@ -62,7 +62,8 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
const Object *self_object = params.self_object();
|
||||
const bool is_recursive = BKE_collection_has_object_recursive_instanced(
|
||||
/* Compare by `orig_id` because objects may be copied into separate depsgraphs. */
|
||||
const bool is_recursive = BKE_collection_has_object_recursive_instanced_orig_id(
|
||||
collection, const_cast<Object *>(self_object));
|
||||
if (is_recursive) {
|
||||
params.error_message_add(NodeWarningType::Error, TIP_("Collection contains current object"));
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "GEO_transform.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
@@ -70,12 +72,16 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
params.set_output("Scale", scale);
|
||||
|
||||
if (params.output_is_required("Geometry")) {
|
||||
if (object == self_object) {
|
||||
/* Compare by `orig_id` because objects may be copied into separate depsgraphs. */
|
||||
if (DEG_get_original_id(&object->id) ==
|
||||
DEG_get_original_id(const_cast<ID *>(&self_object->id)))
|
||||
{
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
TIP_("Geometry cannot be retrieved from the modifier object"));
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
BLI_assert(object != self_object);
|
||||
|
||||
GeometrySet geometry_set;
|
||||
if (params.get_input<bool>("As Instance")) {
|
||||
|
||||
@@ -28,7 +28,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
if (!check_tool_context_and_error(params)) {
|
||||
return;
|
||||
}
|
||||
const View3DCursor &cursor = params.user_data()->call_data->operator_data->scene->cursor;
|
||||
const View3DCursor &cursor = params.user_data()->call_data->operator_data->scene_orig->cursor;
|
||||
const float4x4 &world_to_object = params.self_object()->world_to_object();
|
||||
|
||||
const float3 location_global(cursor.location);
|
||||
|
||||
@@ -241,7 +241,7 @@ class LazyFunctionForGeometryNode : public LazyFunction {
|
||||
return user_data.call_data->modifier_data->self_object;
|
||||
}
|
||||
if (user_data.call_data->operator_data) {
|
||||
return user_data.call_data->operator_data->self_object;
|
||||
return user_data.call_data->operator_data->self_object_orig;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
@@ -4327,13 +4327,45 @@ std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &u
|
||||
return found;
|
||||
}
|
||||
|
||||
GeoNodesOperatorDepsgraphs::~GeoNodesOperatorDepsgraphs()
|
||||
{
|
||||
if (Depsgraph *graph = this->extra) {
|
||||
DEG_graph_free(graph);
|
||||
}
|
||||
}
|
||||
|
||||
static const ID *get_only_evaluated_id(const Depsgraph &depsgraph, const ID &id_orig)
|
||||
{
|
||||
const ID *id = DEG_get_evaluated_id(&depsgraph, const_cast<ID *>(&id_orig));
|
||||
if (id == &id_orig) {
|
||||
return nullptr;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const ID *GeoNodesOperatorDepsgraphs::get_evaluated_id(const ID &id_orig) const
|
||||
{
|
||||
if (const Depsgraph *graph = this->active) {
|
||||
if (const ID *id = get_only_evaluated_id(*graph, id_orig)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
if (const Depsgraph *graph = this->extra) {
|
||||
if (const ID *id = get_only_evaluated_id(*graph, id_orig)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Object *GeoNodesCallData::self_object() const
|
||||
{
|
||||
if (this->modifier_data) {
|
||||
return this->modifier_data->self_object;
|
||||
}
|
||||
if (this->operator_data) {
|
||||
return this->operator_data->self_object;
|
||||
return DEG_get_evaluated_object(this->operator_data->depsgraphs->active,
|
||||
const_cast<Object *>(this->operator_data->self_object_orig));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user