diff options
Diffstat (limited to 'source/blender/nodes/intern')
-rw-r--r-- | source/blender/nodes/intern/derived_node_tree.cc | 139 | ||||
-rw-r--r-- | source/blender/nodes/intern/geometry_nodes_eval_log.cc | 508 | ||||
-rw-r--r-- | source/blender/nodes/intern/geometry_nodes_lazy_function.cc | 1320 | ||||
-rw-r--r-- | source/blender/nodes/intern/geometry_nodes_log.cc | 608 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_common.cc | 15 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_geometry_exec.cc | 80 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_multi_function.cc | 26 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_tree_ref.cc | 678 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_util.c | 2 |
9 files changed, 2069 insertions, 1307 deletions
diff --git a/source/blender/nodes/intern/derived_node_tree.cc b/source/blender/nodes/intern/derived_node_tree.cc index e589da09b16..e8e0f0fa61c 100644 --- a/source/blender/nodes/intern/derived_node_tree.cc +++ b/source/blender/nodes/intern/derived_node_tree.cc @@ -2,38 +2,38 @@ #include "NOD_derived_node_tree.hh" +#include "BKE_node.h" + #include "BLI_dot_export.hh" namespace blender::nodes { -DerivedNodeTree::DerivedNodeTree(bNodeTree &btree, NodeTreeRefMap &node_tree_refs) +DerivedNodeTree::DerivedNodeTree(const bNodeTree &btree) { /* Construct all possible contexts immediately. This is significantly cheaper than inlining all * node groups. If it still becomes a performance issue in the future, contexts could be * constructed lazily when they are needed. */ - root_context_ = &this->construct_context_recursively(nullptr, nullptr, btree, node_tree_refs); + root_context_ = &this->construct_context_recursively(nullptr, nullptr, btree); } DTreeContext &DerivedNodeTree::construct_context_recursively(DTreeContext *parent_context, - const NodeRef *parent_node, - bNodeTree &btree, - NodeTreeRefMap &node_tree_refs) + const bNode *parent_node, + const bNodeTree &btree) { + btree.ensure_topology_cache(); DTreeContext &context = *allocator_.construct<DTreeContext>().release(); context.parent_context_ = parent_context; context.parent_node_ = parent_node; context.derived_tree_ = this; - context.tree_ = &get_tree_ref_from_map(node_tree_refs, btree); - used_node_tree_refs_.add(context.tree_); + context.btree_ = &btree; + used_btrees_.add(context.btree_); - for (const NodeRef *node : context.tree_->nodes()) { - if (node->is_group_node()) { - bNode *bnode = node->bnode(); + for (const bNode *bnode : context.btree_->all_nodes()) { + if (bnode->is_group()) { bNodeTree *child_btree = reinterpret_cast<bNodeTree *>(bnode->id); if (child_btree != nullptr) { - DTreeContext &child = this->construct_context_recursively( - &context, node, *child_btree, node_tree_refs); - context.children_.add_new(node, &child); + DTreeContext &child = this->construct_context_recursively(&context, bnode, *child_btree); + context.children_.add_new(bnode, &child); } } } @@ -57,8 +57,8 @@ void DerivedNodeTree::destruct_context_recursively(DTreeContext *context) bool DerivedNodeTree::has_link_cycles() const { - for (const NodeTreeRef *tree_ref : used_node_tree_refs_) { - if (tree_ref->has_link_cycles()) { + for (const bNodeTree *btree : used_btrees_) { + if (btree->has_link_cycle()) { return true; } } @@ -67,8 +67,8 @@ bool DerivedNodeTree::has_link_cycles() const bool DerivedNodeTree::has_undefined_nodes_or_sockets() const { - for (const NodeTreeRef *tree_ref : used_node_tree_refs_) { - if (tree_ref->has_undefined_nodes_or_sockets()) { + for (const bNodeTree *btree : used_btrees_) { + if (btree->has_undefined_nodes_or_sockets()) { return true; } } @@ -83,8 +83,8 @@ void DerivedNodeTree::foreach_node(FunctionRef<void(DNode)> callback) const void DerivedNodeTree::foreach_node_in_context_recursive(const DTreeContext &context, FunctionRef<void(DNode)> callback) const { - for (const NodeRef *node_ref : context.tree_->nodes()) { - callback(DNode(&context, node_ref)); + for (const bNode *bnode : context.btree_->all_nodes()) { + callback(DNode(&context, bnode)); } for (const DTreeContext *child_context : context.children_.values()) { this->foreach_node_in_context_recursive(*child_context, callback); @@ -94,32 +94,32 @@ void DerivedNodeTree::foreach_node_in_context_recursive(const DTreeContext &cont DOutputSocket DInputSocket::get_corresponding_group_node_output() const { BLI_assert(*this); - BLI_assert(socket_ref_->node().is_group_output_node()); - BLI_assert(socket_ref_->index() < socket_ref_->node().inputs().size() - 1); + BLI_assert(bsocket_->owner_node().is_group_output()); + BLI_assert(bsocket_->index() < bsocket_->owner_node().input_sockets().size() - 1); const DTreeContext *parent_context = context_->parent_context(); - const NodeRef *parent_node = context_->parent_node(); + const bNode *parent_node = context_->parent_node(); BLI_assert(parent_context != nullptr); BLI_assert(parent_node != nullptr); - const int socket_index = socket_ref_->index(); - return {parent_context, &parent_node->output(socket_index)}; + const int socket_index = bsocket_->index(); + return {parent_context, &parent_node->output_socket(socket_index)}; } Vector<DOutputSocket> DInputSocket::get_corresponding_group_input_sockets() const { BLI_assert(*this); - BLI_assert(socket_ref_->node().is_group_node()); + BLI_assert(bsocket_->owner_node().is_group()); - const DTreeContext *child_context = context_->child_context(socket_ref_->node()); + const DTreeContext *child_context = context_->child_context(bsocket_->owner_node()); BLI_assert(child_context != nullptr); - const NodeTreeRef &child_tree = child_context->tree(); - Span<const NodeRef *> group_input_nodes = child_tree.nodes_by_type("NodeGroupInput"); - const int socket_index = socket_ref_->index(); + const bNodeTree &child_tree = child_context->btree(); + Span<const bNode *> group_input_nodes = child_tree.nodes_by_type("NodeGroupInput"); + const int socket_index = bsocket_->index(); Vector<DOutputSocket> sockets; - for (const NodeRef *group_input_node : group_input_nodes) { - sockets.append(DOutputSocket(child_context, &group_input_node->output(socket_index))); + for (const bNode *group_input_node : group_input_nodes) { + sockets.append(DOutputSocket(child_context, &group_input_node->output_socket(socket_index))); } return sockets; } @@ -127,36 +127,36 @@ Vector<DOutputSocket> DInputSocket::get_corresponding_group_input_sockets() cons DInputSocket DOutputSocket::get_corresponding_group_node_input() const { BLI_assert(*this); - BLI_assert(socket_ref_->node().is_group_input_node()); - BLI_assert(socket_ref_->index() < socket_ref_->node().outputs().size() - 1); + BLI_assert(bsocket_->owner_node().is_group_input()); + BLI_assert(bsocket_->index() < bsocket_->owner_node().output_sockets().size() - 1); const DTreeContext *parent_context = context_->parent_context(); - const NodeRef *parent_node = context_->parent_node(); + const bNode *parent_node = context_->parent_node(); BLI_assert(parent_context != nullptr); BLI_assert(parent_node != nullptr); - const int socket_index = socket_ref_->index(); - return {parent_context, &parent_node->input(socket_index)}; + const int socket_index = bsocket_->index(); + return {parent_context, &parent_node->input_socket(socket_index)}; } DInputSocket DOutputSocket::get_active_corresponding_group_output_socket() const { BLI_assert(*this); - BLI_assert(socket_ref_->node().is_group_node()); + BLI_assert(bsocket_->owner_node().is_group()); - const DTreeContext *child_context = context_->child_context(socket_ref_->node()); + const DTreeContext *child_context = context_->child_context(bsocket_->owner_node()); if (child_context == nullptr) { /* Can happen when the group node references a non-existent group (e.g. when the group is * linked but the original file is not found). */ return {}; } - const NodeTreeRef &child_tree = child_context->tree(); - Span<const NodeRef *> group_output_nodes = child_tree.nodes_by_type("NodeGroupOutput"); - const int socket_index = socket_ref_->index(); - for (const NodeRef *group_output_node : group_output_nodes) { - if (group_output_node->bnode()->flag & NODE_DO_OUTPUT || group_output_nodes.size() == 1) { - return {child_context, &group_output_node->input(socket_index)}; + const bNodeTree &child_tree = child_context->btree(); + Span<const bNode *> group_output_nodes = child_tree.nodes_by_type("NodeGroupOutput"); + const int socket_index = bsocket_->index(); + for (const bNode *group_output_node : group_output_nodes) { + if (group_output_node->flag & NODE_DO_OUTPUT || group_output_nodes.size() == 1) { + return {child_context, &group_output_node->input_socket(socket_index)}; } } return {}; @@ -165,11 +165,11 @@ DInputSocket DOutputSocket::get_active_corresponding_group_output_socket() const void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) const { BLI_assert(*this); - for (const OutputSocketRef *linked_socket : socket_ref_->as_input().logically_linked_sockets()) { - const NodeRef &linked_node = linked_socket->node(); + for (const bNodeSocket *linked_socket : bsocket_->logically_linked_sockets()) { + const bNode &linked_node = linked_socket->owner_node(); DOutputSocket linked_dsocket{context_, linked_socket}; - if (linked_node.is_group_input_node()) { + if (linked_node.is_group_input()) { if (context_->is_root()) { /* This is a group input in the root node group. */ origin_fn(linked_dsocket); @@ -187,7 +187,7 @@ void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) c } } } - else if (linked_node.is_group_node()) { + else if (linked_node.is_group()) { DInputSocket socket_in_group = linked_dsocket.get_active_corresponding_group_output_socket(); if (socket_in_group) { if (socket_in_group->is_logically_linked()) { @@ -217,16 +217,16 @@ void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn) const void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn, TargetSocketPathInfo &path_info) const { - for (const LinkRef *link : socket_ref_->as_output().directly_linked_links()) { + for (const bNodeLink *link : bsocket_->directly_linked_links()) { if (link->is_muted()) { continue; } - const DInputSocket &linked_socket{context_, &link->to()}; + const DInputSocket &linked_socket{context_, link->tosock}; if (!linked_socket->is_available()) { continue; } const DNode linked_node = linked_socket.node(); - if (linked_node->is_reroute_node()) { + if (linked_node->is_reroute()) { const DInputSocket reroute_input = linked_socket; const DOutputSocket reroute_output = linked_node.output(0); path_info.sockets.append(reroute_input); @@ -236,18 +236,18 @@ void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn, path_info.sockets.pop_last(); } else if (linked_node->is_muted()) { - for (const InternalLinkRef *internal_link : linked_node->internal_links()) { - if (&internal_link->from() != linked_socket.socket_ref()) { + for (const bNodeLink *internal_link : linked_node->internal_links_span()) { + if (internal_link->fromsock != linked_socket.bsocket()) { continue; } /* The internal link only forwards the first incoming link. */ - if (linked_socket->is_multi_input_socket()) { + if (linked_socket->is_multi_input()) { if (linked_socket->directly_linked_links()[0] != link) { continue; } } const DInputSocket mute_input = linked_socket; - const DOutputSocket mute_output{context_, &internal_link->to()}; + const DOutputSocket mute_output{context_, internal_link->tosock}; path_info.sockets.append(mute_input); path_info.sockets.append(mute_output); mute_output.foreach_target_socket(target_fn, path_info); @@ -255,8 +255,8 @@ void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn, path_info.sockets.pop_last(); } } - else if (linked_node->is_group_output_node()) { - if (linked_node.node_ref() != context_->tree().group_output_node()) { + else if (linked_node->is_group_output()) { + if (linked_node.bnode() != context_->btree().group_output_node()) { continue; } if (context_->is_root()) { @@ -276,7 +276,7 @@ void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn, path_info.sockets.pop_last(); } } - else if (linked_node->is_group_node()) { + else if (linked_node->is_group()) { /* Follow the links within the nested node group. */ path_info.sockets.append(linked_socket); const Vector<DOutputSocket> sockets_in_group = @@ -310,7 +310,8 @@ static dot::Cluster *get_dot_cluster_for_context( } dot::Cluster *parent_cluster = get_dot_cluster_for_context( digraph, parent_context, dot_clusters); - std::string cluster_name = context->tree().name() + " / " + context->parent_node()->name(); + std::string cluster_name = StringRef(context->btree().id.name + 2) + " / " + + context->parent_node()->name; dot::Cluster &cluster = digraph.new_cluster(cluster_name); cluster.set_parent_cluster(parent_cluster); return &cluster; @@ -328,11 +329,11 @@ std::string DerivedNodeTree::to_dot() const this->foreach_node([&](DNode node) { /* Ignore nodes that should not show up in the final output. */ - if (node->is_muted() || node->is_group_node() || node->is_reroute_node() || node->is_frame()) { + if (node->is_muted() || node->is_group() || node->is_reroute() || node->is_frame()) { return; } if (!node.context()->is_root()) { - if (node->is_group_input_node() || node->is_group_output_node()) { + if (node->is_group_input() || node->is_group_output()) { return; } } @@ -345,22 +346,22 @@ std::string DerivedNodeTree::to_dot() const Vector<std::string> input_names; Vector<std::string> output_names; - for (const InputSocketRef *socket : node->inputs()) { + for (const bNodeSocket *socket : node->input_sockets()) { if (socket->is_available()) { - input_names.append(socket->name()); + input_names.append(socket->name); } } - for (const OutputSocketRef *socket : node->outputs()) { + for (const bNodeSocket *socket : node->output_sockets()) { if (socket->is_available()) { - output_names.append(socket->name()); + output_names.append(socket->name); } } dot::NodeWithSocketsRef dot_node_with_sockets = dot::NodeWithSocketsRef( - dot_node, node->name(), input_names, output_names); + dot_node, node->name, input_names, output_names); int input_index = 0; - for (const InputSocketRef *socket : node->inputs()) { + for (const bNodeSocket *socket : node->input_sockets()) { if (socket->is_available()) { dot_input_sockets.add_new(DInputSocket{node.context(), socket}, dot_node_with_sockets.input(input_index)); @@ -368,7 +369,7 @@ std::string DerivedNodeTree::to_dot() const } } int output_index = 0; - for (const OutputSocketRef *socket : node->outputs()) { + for (const bNodeSocket *socket : node->output_sockets()) { if (socket->is_available()) { dot_output_sockets.add_new(DOutputSocket{node.context(), socket}, dot_node_with_sockets.output(output_index)); @@ -392,7 +393,7 @@ std::string DerivedNodeTree::to_dot() const } } dot::Node &dot_node = *dot_floating_inputs.lookup_or_add_cb(from_socket, [&]() { - dot::Node &dot_node = digraph.new_node(from_socket->name()); + dot::Node &dot_node = digraph.new_node(from_socket->name); dot_node.set_background_color("white"); dot_node.set_shape(dot::Attr_shape::Ellipse); dot_node.set_parent_cluster( diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc deleted file mode 100644 index 85dfdf03b82..00000000000 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ /dev/null @@ -1,508 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "NOD_geometry_nodes_eval_log.hh" - -#include "BKE_geometry_set_instances.hh" - -#include "DNA_modifier_types.h" -#include "DNA_space_types.h" - -#include "FN_field_cpp_type.hh" - -#include "BLT_translation.h" - -#include <chrono> - -namespace blender::nodes::geometry_nodes_eval_log { - -using fn::FieldCPPType; -using fn::FieldInput; -using fn::GField; -using fn::ValueOrFieldCPPType; - -ModifierLog::ModifierLog(GeoLogger &logger) - : input_geometry_log_(std::move(logger.input_geometry_log_)), - output_geometry_log_(std::move(logger.output_geometry_log_)) -{ - root_tree_logs_ = allocator_.construct<TreeLog>(); - - LogByTreeContext log_by_tree_context; - - /* Combine all the local loggers that have been used by separate threads. */ - for (LocalGeoLogger &local_logger : logger) { - /* Take ownership of the allocator. */ - logger_allocators_.append(std::move(local_logger.allocator_)); - - for (ValueOfSockets &value_of_sockets : local_logger.values_) { - ValueLog *value_log = value_of_sockets.value.get(); - - /* Take centralized ownership of the logged value. It might be referenced by multiple - * sockets. */ - logged_values_.append(std::move(value_of_sockets.value)); - - for (const DSocket &socket : value_of_sockets.sockets) { - SocketLog &socket_log = this->lookup_or_add_socket_log(log_by_tree_context, socket); - socket_log.value_ = value_log; - } - } - - for (NodeWithWarning &node_with_warning : local_logger.node_warnings_) { - NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, - node_with_warning.node); - node_log.warnings_.append(node_with_warning.warning); - } - - for (NodeWithExecutionTime &node_with_exec_time : local_logger.node_exec_times_) { - NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, - node_with_exec_time.node); - node_log.exec_time_ = node_with_exec_time.exec_time; - } - - for (NodeWithDebugMessage &debug_message : local_logger.node_debug_messages_) { - NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, debug_message.node); - node_log.debug_messages_.append(debug_message.message); - } - - for (NodeWithUsedNamedAttribute &node_with_attribute_name : - local_logger.used_named_attributes_) { - NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, - node_with_attribute_name.node); - node_log.used_named_attributes_.append(std::move(node_with_attribute_name.attribute)); - } - } -} - -TreeLog &ModifierLog::lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context, - const DTreeContext &tree_context) -{ - TreeLog *tree_log = log_by_tree_context.lookup_default(&tree_context, nullptr); - if (tree_log != nullptr) { - return *tree_log; - } - - const DTreeContext *parent_context = tree_context.parent_context(); - if (parent_context == nullptr) { - return *root_tree_logs_.get(); - } - TreeLog &parent_log = this->lookup_or_add_tree_log(log_by_tree_context, *parent_context); - destruct_ptr<TreeLog> owned_tree_log = allocator_.construct<TreeLog>(); - tree_log = owned_tree_log.get(); - log_by_tree_context.add_new(&tree_context, tree_log); - parent_log.child_logs_.add_new(tree_context.parent_node()->name(), std::move(owned_tree_log)); - return *tree_log; -} - -NodeLog &ModifierLog::lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node) -{ - TreeLog &tree_log = this->lookup_or_add_tree_log(log_by_tree_context, *node.context()); - NodeLog &node_log = *tree_log.node_logs_.lookup_or_add_cb(node->name(), [&]() { - destruct_ptr<NodeLog> node_log = allocator_.construct<NodeLog>(); - node_log->input_logs_.resize(node->inputs().size()); - node_log->output_logs_.resize(node->outputs().size()); - return node_log; - }); - return node_log; -} - -SocketLog &ModifierLog::lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context, - DSocket socket) -{ - NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, socket.node()); - MutableSpan<SocketLog> socket_logs = socket->is_input() ? node_log.input_logs_ : - node_log.output_logs_; - SocketLog &socket_log = socket_logs[socket->index()]; - return socket_log; -} - -void ModifierLog::foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const -{ - if (root_tree_logs_) { - root_tree_logs_->foreach_node_log(fn); - } -} - -const GeometryValueLog *ModifierLog::input_geometry_log() const -{ - return input_geometry_log_.get(); -} -const GeometryValueLog *ModifierLog::output_geometry_log() const -{ - return output_geometry_log_.get(); -} - -const NodeLog *TreeLog::lookup_node_log(StringRef node_name) const -{ - const destruct_ptr<NodeLog> *node_log = node_logs_.lookup_ptr_as(node_name); - if (node_log == nullptr) { - return nullptr; - } - return node_log->get(); -} - -const NodeLog *TreeLog::lookup_node_log(const bNode &node) const -{ - return this->lookup_node_log(node.name); -} - -const TreeLog *TreeLog::lookup_child_log(StringRef node_name) const -{ - const destruct_ptr<TreeLog> *tree_log = child_logs_.lookup_ptr_as(node_name); - if (tree_log == nullptr) { - return nullptr; - } - return tree_log->get(); -} - -void TreeLog::foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const -{ - for (auto node_log : node_logs_.items()) { - fn(*node_log.value); - } - - for (auto child : child_logs_.items()) { - child.value->foreach_node_log(fn); - } -} - -const SocketLog *NodeLog::lookup_socket_log(eNodeSocketInOut in_out, int index) const -{ - BLI_assert(index >= 0); - Span<SocketLog> socket_logs = (in_out == SOCK_IN) ? input_logs_ : output_logs_; - if (index >= socket_logs.size()) { - return nullptr; - } - return &socket_logs[index]; -} - -const SocketLog *NodeLog::lookup_socket_log(const bNode &node, const bNodeSocket &socket) const -{ - ListBase sockets = socket.in_out == SOCK_IN ? node.inputs : node.outputs; - int index = BLI_findindex(&sockets, &socket); - return this->lookup_socket_log((eNodeSocketInOut)socket.in_out, index); -} - -GFieldValueLog::GFieldValueLog(fn::GField field, bool log_full_field) : type_(field.cpp_type()) -{ - const std::shared_ptr<const fn::FieldInputs> &field_input_nodes = field.node().field_inputs(); - - /* Put the deduplicated field inputs into a vector so that they can be sorted below. */ - Vector<std::reference_wrapper<const FieldInput>> field_inputs; - if (field_input_nodes) { - field_inputs.extend(field_input_nodes->deduplicated_nodes.begin(), - field_input_nodes->deduplicated_nodes.end()); - } - - std::sort( - field_inputs.begin(), field_inputs.end(), [](const FieldInput &a, const FieldInput &b) { - const int index_a = (int)a.category(); - const int index_b = (int)b.category(); - if (index_a == index_b) { - return a.socket_inspection_name().size() < b.socket_inspection_name().size(); - } - return index_a < index_b; - }); - - for (const FieldInput &field_input : field_inputs) { - input_tooltips_.append(field_input.socket_inspection_name()); - } - - if (log_full_field) { - field_ = std::move(field); - } -} - -GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry) -{ - static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE, - GEO_COMPONENT_TYPE_INSTANCES, - GEO_COMPONENT_TYPE_MESH, - GEO_COMPONENT_TYPE_POINT_CLOUD, - GEO_COMPONENT_TYPE_VOLUME}; - - /* Keep track handled attribute names to make sure that we do not return the same name twice. - * Currently #GeometrySet::attribute_foreach does not do that. Note that this will merge - * attributes with the same name but different domains or data types on separate components. */ - Set<StringRef> names; - - geometry_set.attribute_foreach( - all_component_types, - true, - [&](const bke::AttributeIDRef &attribute_id, - const AttributeMetaData &meta_data, - const GeometryComponent &UNUSED(component)) { - if (attribute_id.is_named() && names.add(attribute_id.name())) { - this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); - } - }); - - for (const GeometryComponent *component : geometry_set.get_components_for_read()) { - component_types_.append(component->type()); - switch (component->type()) { - case GEO_COMPONENT_TYPE_MESH: { - const MeshComponent &mesh_component = *(const MeshComponent *)component; - MeshInfo &info = this->mesh_info.emplace(); - info.verts_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_POINT); - info.edges_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_EDGE); - info.faces_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_FACE); - break; - } - case GEO_COMPONENT_TYPE_CURVE: { - const CurveComponent &curve_component = *(const CurveComponent *)component; - CurveInfo &info = this->curve_info.emplace(); - info.splines_num = curve_component.attribute_domain_num(ATTR_DOMAIN_CURVE); - break; - } - case GEO_COMPONENT_TYPE_POINT_CLOUD: { - const PointCloudComponent &pointcloud_component = *(const PointCloudComponent *)component; - PointCloudInfo &info = this->pointcloud_info.emplace(); - info.points_num = pointcloud_component.attribute_domain_num(ATTR_DOMAIN_POINT); - break; - } - case GEO_COMPONENT_TYPE_INSTANCES: { - const InstancesComponent &instances_component = *(const InstancesComponent *)component; - InstancesInfo &info = this->instances_info.emplace(); - info.instances_num = instances_component.instances_num(); - break; - } - case GEO_COMPONENT_TYPE_VOLUME: { - break; - } - } - } - if (log_full_geometry) { - full_geometry_ = std::make_unique<GeometrySet>(geometry_set); - full_geometry_->ensure_owns_direct_data(); - } -} - -Vector<const GeometryAttributeInfo *> NodeLog::lookup_available_attributes() const -{ - Vector<const GeometryAttributeInfo *> attributes; - Set<StringRef> names; - for (const SocketLog &socket_log : input_logs_) { - const ValueLog *value_log = socket_log.value(); - if (const GeometryValueLog *geo_value_log = dynamic_cast<const GeometryValueLog *>( - value_log)) { - for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) { - if (names.add(attribute.name)) { - attributes.append(&attribute); - } - } - } - } - return attributes; -} - -const ModifierLog *ModifierLog::find_root_by_node_editor_context(const SpaceNode &snode) -{ - if (snode.id == nullptr) { - return nullptr; - } - if (GS(snode.id->name) != ID_OB) { - return nullptr; - } - Object *object = (Object *)snode.id; - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = (NodesModifierData *)md; - if (nmd->node_group == snode.nodetree) { - return (ModifierLog *)nmd->runtime_eval_log; - } - } - } - return nullptr; -} - -const TreeLog *ModifierLog::find_tree_by_node_editor_context(const SpaceNode &snode) -{ - const ModifierLog *eval_log = ModifierLog::find_root_by_node_editor_context(snode); - if (eval_log == nullptr) { - return nullptr; - } - Vector<bNodeTreePath *> tree_path_vec = snode.treepath; - if (tree_path_vec.is_empty()) { - return nullptr; - } - TreeLog *current = eval_log->root_tree_logs_.get(); - for (bNodeTreePath *path : tree_path_vec.as_span().drop_front(1)) { - destruct_ptr<TreeLog> *tree_log = current->child_logs_.lookup_ptr_as(path->node_name); - if (tree_log == nullptr) { - return nullptr; - } - current = tree_log->get(); - } - return current; -} - -const NodeLog *ModifierLog::find_node_by_node_editor_context(const SpaceNode &snode, - const bNode &node) -{ - const TreeLog *tree_log = ModifierLog::find_tree_by_node_editor_context(snode); - if (tree_log == nullptr) { - return nullptr; - } - return tree_log->lookup_node_log(node); -} - -const NodeLog *ModifierLog::find_node_by_node_editor_context(const SpaceNode &snode, - const StringRef node_name) -{ - const TreeLog *tree_log = ModifierLog::find_tree_by_node_editor_context(snode); - if (tree_log == nullptr) { - return nullptr; - } - return tree_log->lookup_node_log(node_name); -} - -const SocketLog *ModifierLog::find_socket_by_node_editor_context(const SpaceNode &snode, - const bNode &node, - const bNodeSocket &socket) -{ - const NodeLog *node_log = ModifierLog::find_node_by_node_editor_context(snode, node); - if (node_log == nullptr) { - return nullptr; - } - return node_log->lookup_socket_log(node, socket); -} - -const NodeLog *ModifierLog::find_node_by_spreadsheet_editor_context( - const SpaceSpreadsheet &sspreadsheet) -{ - Vector<SpreadsheetContext *> context_path = sspreadsheet.context_path; - if (context_path.size() <= 2) { - return nullptr; - } - if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { - return nullptr; - } - if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) { - return nullptr; - } - for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) { - if (context->type != SPREADSHEET_CONTEXT_NODE) { - return nullptr; - } - } - Span<SpreadsheetContextNode *> node_contexts = - context_path.as_span().drop_front(2).cast<SpreadsheetContextNode *>(); - - Object *object = ((SpreadsheetContextObject *)context_path[0])->object; - StringRefNull modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name; - if (object == nullptr) { - return nullptr; - } - - const ModifierLog *eval_log = nullptr; - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - if (md->name == modifier_name) { - NodesModifierData *nmd = (NodesModifierData *)md; - eval_log = (const ModifierLog *)nmd->runtime_eval_log; - break; - } - } - } - if (eval_log == nullptr) { - return nullptr; - } - - const TreeLog *tree_log = &eval_log->root_tree(); - for (SpreadsheetContextNode *context : node_contexts.drop_back(1)) { - tree_log = tree_log->lookup_child_log(context->node_name); - if (tree_log == nullptr) { - return nullptr; - } - } - const NodeLog *node_log = tree_log->lookup_node_log(node_contexts.last()->node_name); - return node_log; -} - -void LocalGeoLogger::log_value_for_sockets(Span<DSocket> sockets, GPointer value) -{ - const CPPType &type = *value.type(); - Span<DSocket> copied_sockets = allocator_->construct_array_copy(sockets); - if (type.is<GeometrySet>()) { - bool log_full_geometry = false; - for (const DSocket &socket : sockets) { - if (main_logger_->log_full_sockets_.contains(socket)) { - log_full_geometry = true; - break; - } - } - - const GeometrySet &geometry_set = *value.get<GeometrySet>(); - destruct_ptr<GeometryValueLog> value_log = allocator_->construct<GeometryValueLog>( - geometry_set, log_full_geometry); - values_.append({copied_sockets, std::move(value_log)}); - } - else if (const ValueOrFieldCPPType *value_or_field_type = - dynamic_cast<const ValueOrFieldCPPType *>(&type)) { - const void *value_or_field = value.get(); - if (value_or_field_type->is_field(value_or_field)) { - GField field = *value_or_field_type->get_field_ptr(value_or_field); - bool log_full_field = false; - if (!field.node().depends_on_input()) { - /* Always log constant fields so that their value can be shown in socket inspection. - * In the future we can also evaluate the field here and only store the value. */ - log_full_field = true; - } - if (!log_full_field) { - for (const DSocket &socket : sockets) { - if (main_logger_->log_full_sockets_.contains(socket)) { - log_full_field = true; - break; - } - } - } - destruct_ptr<GFieldValueLog> value_log = allocator_->construct<GFieldValueLog>( - std::move(field), log_full_field); - values_.append({copied_sockets, std::move(value_log)}); - } - else { - const CPPType &base_type = value_or_field_type->base_type(); - const void *value = value_or_field_type->get_value_ptr(value_or_field); - void *buffer = allocator_->allocate(base_type.size(), base_type.alignment()); - base_type.copy_construct(value, buffer); - destruct_ptr<GenericValueLog> value_log = allocator_->construct<GenericValueLog>( - GMutablePointer{base_type, buffer}); - values_.append({copied_sockets, std::move(value_log)}); - } - } - else { - void *buffer = allocator_->allocate(type.size(), type.alignment()); - type.copy_construct(value.get(), buffer); - destruct_ptr<GenericValueLog> value_log = allocator_->construct<GenericValueLog>( - GMutablePointer{type, buffer}); - values_.append({copied_sockets, std::move(value_log)}); - } -} - -void LocalGeoLogger::log_multi_value_socket(DSocket socket, Span<GPointer> values) -{ - /* Doesn't have to be logged currently. */ - UNUSED_VARS(socket, values); -} - -void LocalGeoLogger::log_node_warning(DNode node, NodeWarningType type, std::string message) -{ - node_warnings_.append({node, {type, std::move(message)}}); -} - -void LocalGeoLogger::log_execution_time(DNode node, std::chrono::microseconds exec_time) -{ - node_exec_times_.append({node, exec_time}); -} - -void LocalGeoLogger::log_used_named_attribute(DNode node, - std::string attribute_name, - eNamedAttrUsage usage) -{ - used_named_attributes_.append({node, {std::move(attribute_name), usage}}); -} - -void LocalGeoLogger::log_debug_message(DNode node, std::string message) -{ - node_debug_messages_.append({node, std::move(message)}); -} - -} // namespace blender::nodes::geometry_nodes_eval_log diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc new file mode 100644 index 00000000000..b0b7449b889 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -0,0 +1,1320 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * This file mainly converts a #bNodeTree into a lazy-function graph. This generally works by + * creating a lazy-function for every node, which is then put into the lazy-function graph. Then + * the nodes in the new graph are linked based on links in the original #bNodeTree. Some additional + * nodes are inserted for things like type conversions and multi-input sockets. + * + * Currently, lazy-functions are even created for nodes that don't strictly require it, like + * reroutes or muted nodes. In the future we could avoid that at the cost of additional code + * complexity. So far, this does not seem to be a performance issue. + */ + +#include "NOD_geometry_exec.hh" +#include "NOD_geometry_nodes_lazy_function.hh" +#include "NOD_multi_function.hh" +#include "NOD_node_declaration.hh" + +#include "BLI_map.hh" + +#include "DNA_ID.h" + +#include "BKE_compute_contexts.hh" +#include "BKE_geometry_set.hh" +#include "BKE_type_conversions.hh" + +#include "FN_field_cpp_type.hh" +#include "FN_lazy_function_graph_executor.hh" + +namespace blender::nodes { + +using fn::ValueOrField; +using fn::ValueOrFieldCPPType; +using namespace fn::multi_function_types; + +static const CPPType *get_socket_cpp_type(const bNodeSocketType &typeinfo) +{ + const CPPType *type = typeinfo.geometry_nodes_cpp_type; + if (type == nullptr) { + return nullptr; + } + BLI_assert(type->has_special_member_functions()); + return type; +} + +static const CPPType *get_socket_cpp_type(const bNodeSocket &socket) +{ + return get_socket_cpp_type(*socket.typeinfo); +} + +static const CPPType *get_vector_type(const CPPType &type) +{ + /* This could be generalized in the future. For now we only support a small set of vectors. */ + if (type.is<GeometrySet>()) { + return &CPPType::get<Vector<GeometrySet>>(); + } + if (type.is<ValueOrField<std::string>>()) { + return &CPPType::get<Vector<ValueOrField<std::string>>>(); + } + return nullptr; +} + +/** + * Checks which sockets of the node are available and creates corresponding inputs/outputs on the + * lazy-function. + */ +static void lazy_function_interface_from_node(const bNode &node, + Vector<const bNodeSocket *> &r_used_inputs, + Vector<const bNodeSocket *> &r_used_outputs, + Vector<lf::Input> &r_inputs, + Vector<lf::Output> &r_outputs) +{ + const bool is_muted = node.is_muted(); + const bool supports_laziness = node.typeinfo->geometry_node_execute_supports_laziness || + node.is_group(); + const lf::ValueUsage input_usage = supports_laziness ? lf::ValueUsage::Maybe : + lf::ValueUsage::Used; + for (const bNodeSocket *socket : node.input_sockets()) { + if (!socket->is_available()) { + continue; + } + const CPPType *type = get_socket_cpp_type(*socket); + if (type == nullptr) { + continue; + } + if (socket->is_multi_input() && !is_muted) { + type = get_vector_type(*type); + } + r_inputs.append({socket->identifier, *type, input_usage}); + r_used_inputs.append(socket); + } + for (const bNodeSocket *socket : node.output_sockets()) { + if (!socket->is_available()) { + continue; + } + const CPPType *type = get_socket_cpp_type(*socket); + if (type == nullptr) { + continue; + } + r_outputs.append({socket->identifier, *type}); + r_used_outputs.append(socket); + } +} + +/** + * Used for most normal geometry nodes like Subdivision Surface and Set Position. + */ +class LazyFunctionForGeometryNode : public LazyFunction { + private: + const bNode &node_; + + public: + LazyFunctionForGeometryNode(const bNode &node, + Vector<const bNodeSocket *> &r_used_inputs, + Vector<const bNodeSocket *> &r_used_outputs) + : node_(node) + { + BLI_assert(node.typeinfo->geometry_node_execute != nullptr); + debug_name_ = node.name; + lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + + GeoNodeExecParams geo_params{node_, params, context}; + + geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now(); + node_.typeinfo->geometry_node_execute(geo_params); + geo_eval_log::TimePoint end_time = geo_eval_log::Clock::now(); + + if (geo_eval_log::GeoModifierLog *modifier_log = user_data->modifier_data->eval_log) { + geo_eval_log::GeoTreeLogger &tree_logger = modifier_log->get_local_tree_logger( + *user_data->compute_context); + tree_logger.node_execution_times.append({node_.name, start_time, end_time}); + } + } +}; + +/** + * Used to gather all inputs of a multi-input socket. A separate node is necessary, because + * multi-inputs are not supported in lazy-function graphs. + */ +class LazyFunctionForMultiInput : public LazyFunction { + private: + const CPPType *base_type_; + + public: + LazyFunctionForMultiInput(const bNodeSocket &socket) + { + debug_name_ = "Multi Input"; + base_type_ = get_socket_cpp_type(socket); + BLI_assert(base_type_ != nullptr); + BLI_assert(socket.is_multi_input()); + for (const bNodeLink *link : socket.directly_linked_links()) { + if (!link->is_muted()) { + inputs_.append({"Input", *base_type_}); + } + } + const CPPType *vector_type = get_vector_type(*base_type_); + BLI_assert(vector_type != nullptr); + outputs_.append({"Output", *vector_type}); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + /* Currently we only have multi-inputs for geometry and string sockets. This could be + * generalized in the future. */ + base_type_->to_static_type_tag<GeometrySet, ValueOrField<std::string>>([&](auto type_tag) { + using T = typename decltype(type_tag)::type; + if constexpr (std::is_void_v<T>) { + /* This type is not support in this node for now. */ + BLI_assert_unreachable(); + } + else { + void *output_ptr = params.get_output_data_ptr(0); + Vector<T> &values = *new (output_ptr) Vector<T>(); + for (const int i : inputs_.index_range()) { + values.append(params.extract_input<T>(i)); + } + params.output_set(0); + } + }); + } +}; + +/** + * Simple lazy-function that just forwards the input. + */ +class LazyFunctionForRerouteNode : public LazyFunction { + public: + LazyFunctionForRerouteNode(const CPPType &type) + { + debug_name_ = "Reroute"; + inputs_.append({"Input", type}); + outputs_.append({"Output", type}); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + void *input_value = params.try_get_input_data_ptr(0); + void *output_value = params.get_output_data_ptr(0); + BLI_assert(input_value != nullptr); + BLI_assert(output_value != nullptr); + const CPPType &type = *inputs_[0].type; + type.move_construct(input_value, output_value); + params.output_set(0); + } +}; + +/** + * Executes a multi-function. If all inputs are single values, the results will also be single + * values. If any input is a field, the outputs will also be fields. + */ +static void execute_multi_function_on_value_or_field( + const MultiFunction &fn, + const std::shared_ptr<MultiFunction> &owned_fn, + const Span<const ValueOrFieldCPPType *> input_types, + const Span<const ValueOrFieldCPPType *> output_types, + const Span<const void *> input_values, + const Span<void *> output_values) +{ + BLI_assert(fn.param_amount() == input_types.size() + output_types.size()); + BLI_assert(input_types.size() == input_values.size()); + BLI_assert(output_types.size() == output_values.size()); + + /* Check if any input is a field. */ + bool any_input_is_field = false; + for (const int i : input_types.index_range()) { + const ValueOrFieldCPPType &type = *input_types[i]; + const void *value_or_field = input_values[i]; + if (type.is_field(value_or_field)) { + any_input_is_field = true; + break; + } + } + + if (any_input_is_field) { + /* Convert all inputs into fields, so that they can be used as input in the new field. */ + Vector<GField> input_fields; + for (const int i : input_types.index_range()) { + const ValueOrFieldCPPType &type = *input_types[i]; + const void *value_or_field = input_values[i]; + input_fields.append(type.as_field(value_or_field)); + } + + /* Construct the new field node. */ + std::shared_ptr<fn::FieldOperation> operation; + if (owned_fn) { + operation = std::make_shared<fn::FieldOperation>(owned_fn, std::move(input_fields)); + } + else { + operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields)); + } + + /* Store the new fields in the output. */ + for (const int i : output_types.index_range()) { + const ValueOrFieldCPPType &type = *output_types[i]; + void *value_or_field = output_values[i]; + type.construct_from_field(value_or_field, GField{operation, i}); + } + } + else { + /* In this case, the multi-function is evaluated directly. */ + MFParamsBuilder params{fn, 1}; + MFContextBuilder context; + + for (const int i : input_types.index_range()) { + const ValueOrFieldCPPType &type = *input_types[i]; + const CPPType &base_type = type.base_type(); + const void *value_or_field = input_values[i]; + const void *value = type.get_value_ptr(value_or_field); + params.add_readonly_single_input(GVArray::ForSingleRef(base_type, 1, value)); + } + for (const int i : output_types.index_range()) { + const ValueOrFieldCPPType &type = *output_types[i]; + const CPPType &base_type = type.base_type(); + void *value_or_field = output_values[i]; + type.default_construct(value_or_field); + void *value = type.get_value_ptr(value_or_field); + base_type.destruct(value); + params.add_uninitialized_single_output(GMutableSpan{base_type, value, 1}); + } + fn.call(IndexRange(1), params, context); + } +} + +/** + * Behavior of muted nodes: + * - Some inputs are forwarded to outputs without changes. + * - Some inputs are converted to a different type which becomes the output. + * - Some outputs are value initialized because they don't have a corresponding input. + */ +class LazyFunctionForMutedNode : public LazyFunction { + private: + Array<int> input_by_output_index_; + + public: + LazyFunctionForMutedNode(const bNode &node, + Vector<const bNodeSocket *> &r_used_inputs, + Vector<const bNodeSocket *> &r_used_outputs) + { + debug_name_ = "Muted"; + lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); + for (lf::Input &fn_input : inputs_) { + fn_input.usage = lf::ValueUsage::Maybe; + } + + for (lf::Input &fn_input : inputs_) { + fn_input.usage = lf::ValueUsage::Unused; + } + + input_by_output_index_.reinitialize(outputs_.size()); + input_by_output_index_.fill(-1); + for (const bNodeLink *internal_link : node.internal_links_span()) { + const int input_i = r_used_inputs.first_index_of_try(internal_link->fromsock); + const int output_i = r_used_outputs.first_index_of_try(internal_link->tosock); + if (ELEM(-1, input_i, output_i)) { + continue; + } + input_by_output_index_[output_i] = input_i; + inputs_[input_i].usage = lf::ValueUsage::Maybe; + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + for (const int output_i : outputs_.index_range()) { + if (params.output_was_set(output_i)) { + continue; + } + const CPPType &output_type = *outputs_[output_i].type; + void *output_value = params.get_output_data_ptr(output_i); + const int input_i = input_by_output_index_[output_i]; + if (input_i == -1) { + /* The output does not have a corresponding input. */ + output_type.value_initialize(output_value); + params.output_set(output_i); + continue; + } + const void *input_value = params.try_get_input_data_ptr_or_request(input_i); + if (input_value == nullptr) { + continue; + } + const CPPType &input_type = *inputs_[input_i].type; + if (input_type == output_type) { + /* Forward the value as is. */ + input_type.copy_construct(input_value, output_value); + params.output_set(output_i); + continue; + } + /* Perform a type conversion and then format the value. */ + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + const auto *from_field_type = dynamic_cast<const ValueOrFieldCPPType *>(&input_type); + const auto *to_field_type = dynamic_cast<const ValueOrFieldCPPType *>(&output_type); + if (from_field_type != nullptr && to_field_type != nullptr) { + const CPPType &from_base_type = from_field_type->base_type(); + const CPPType &to_base_type = to_field_type->base_type(); + if (conversions.is_convertible(from_base_type, to_base_type)) { + const MultiFunction &multi_fn = *conversions.get_conversion_multi_function( + MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type)); + execute_multi_function_on_value_or_field( + multi_fn, {}, {from_field_type}, {to_field_type}, {input_value}, {output_value}); + } + params.output_set(output_i); + continue; + } + /* Use a value initialization if the conversion does not work. */ + output_type.value_initialize(output_value); + params.output_set(output_i); + } + } +}; + +/** + * Type conversions are generally implemented as multi-functions. This node checks if the input is + * a field or single value and outputs a field or single value respectively. + */ +class LazyFunctionForMultiFunctionConversion : public LazyFunction { + private: + const MultiFunction &fn_; + const ValueOrFieldCPPType &from_type_; + const ValueOrFieldCPPType &to_type_; + const Vector<const bNodeSocket *> target_sockets_; + + public: + LazyFunctionForMultiFunctionConversion(const MultiFunction &fn, + const ValueOrFieldCPPType &from, + const ValueOrFieldCPPType &to, + Vector<const bNodeSocket *> &&target_sockets) + : fn_(fn), from_type_(from), to_type_(to), target_sockets_(std::move(target_sockets)) + { + debug_name_ = "Convert"; + inputs_.append({"From", from}); + outputs_.append({"To", to}); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + const void *from_value = params.try_get_input_data_ptr(0); + void *to_value = params.get_output_data_ptr(0); + BLI_assert(from_value != nullptr); + BLI_assert(to_value != nullptr); + + execute_multi_function_on_value_or_field( + fn_, {}, {&from_type_}, {&to_type_}, {from_value}, {to_value}); + + params.output_set(0); + } +}; + +/** + * This lazy-function wraps nodes that are implemented as multi-function (mostly math nodes). + */ +class LazyFunctionForMultiFunctionNode : public LazyFunction { + private: + const NodeMultiFunctions::Item fn_item_; + Vector<const ValueOrFieldCPPType *> input_types_; + Vector<const ValueOrFieldCPPType *> output_types_; + Vector<const bNodeSocket *> output_sockets_; + + public: + LazyFunctionForMultiFunctionNode(const bNode &node, + NodeMultiFunctions::Item fn_item, + Vector<const bNodeSocket *> &r_used_inputs, + Vector<const bNodeSocket *> &r_used_outputs) + : fn_item_(std::move(fn_item)) + { + BLI_assert(fn_item_.fn != nullptr); + debug_name_ = node.name; + lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); + for (const lf::Input &fn_input : inputs_) { + input_types_.append(dynamic_cast<const ValueOrFieldCPPType *>(fn_input.type)); + } + for (const lf::Output &fn_output : outputs_) { + output_types_.append(dynamic_cast<const ValueOrFieldCPPType *>(fn_output.type)); + } + output_sockets_ = r_used_outputs; + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + Vector<const void *> input_values(inputs_.size()); + Vector<void *> output_values(outputs_.size()); + for (const int i : inputs_.index_range()) { + input_values[i] = params.try_get_input_data_ptr(i); + } + for (const int i : outputs_.index_range()) { + output_values[i] = params.get_output_data_ptr(i); + } + execute_multi_function_on_value_or_field( + *fn_item_.fn, fn_item_.owned_fn, input_types_, output_types_, input_values, output_values); + for (const int i : outputs_.index_range()) { + params.output_set(i); + } + } +}; + +/** + * Some sockets have non-trivial implicit inputs (e.g. the Position input of the Set Position + * node). Those are implemented as a separate node that outputs the value. + */ +class LazyFunctionForImplicitInput : public LazyFunction { + private: + /** + * The function that generates the implicit input. The passed in memory is uninitialized. + */ + std::function<void(void *)> init_fn_; + + public: + LazyFunctionForImplicitInput(const CPPType &type, std::function<void(void *)> init_fn) + : init_fn_(std::move(init_fn)) + { + debug_name_ = "Input"; + outputs_.append({"Output", type}); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &UNUSED(context)) const override + { + void *value = params.get_output_data_ptr(0); + init_fn_(value); + params.output_set(0); + } +}; + +/** + * The viewer node does not have outputs. Instead it is executed because the executor knows that it + * has side effects. The side effect is that the inputs to the viewer are logged. + */ +class LazyFunctionForViewerNode : public LazyFunction { + private: + const bNode &bnode_; + /** The field is only logged when it is linked. */ + bool use_field_input_ = true; + + public: + LazyFunctionForViewerNode(const bNode &bnode, Vector<const bNodeSocket *> &r_used_inputs) + : bnode_(bnode) + { + debug_name_ = "Viewer"; + Vector<const bNodeSocket *> dummy_used_outputs; + lazy_function_interface_from_node(bnode, r_used_inputs, dummy_used_outputs, inputs_, outputs_); + if (!r_used_inputs[1]->is_directly_linked()) { + use_field_input_ = false; + r_used_inputs.pop_last(); + inputs_.pop_last(); + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + + GeometrySet geometry = params.extract_input<GeometrySet>(0); + + GField field; + if (use_field_input_) { + const void *value_or_field = params.try_get_input_data_ptr(1); + BLI_assert(value_or_field != nullptr); + const ValueOrFieldCPPType &value_or_field_type = static_cast<const ValueOrFieldCPPType &>( + *inputs_[1].type); + field = value_or_field_type.as_field(value_or_field); + } + + geo_eval_log::GeoTreeLogger &tree_logger = + user_data->modifier_data->eval_log->get_local_tree_logger(*user_data->compute_context); + tree_logger.log_viewer_node(bnode_, geometry, field); + } +}; + +/** + * This lazy-function wraps a group node. Internally it just executes the lazy-function graph of + * the referenced group. + */ +class LazyFunctionForGroupNode : public LazyFunction { + private: + const bNode &group_node_; + std::optional<GeometryNodesLazyFunctionLogger> lf_logger_; + std::optional<GeometryNodesLazyFunctionSideEffectProvider> lf_side_effect_provider_; + std::optional<lf::GraphExecutor> graph_executor_; + + public: + LazyFunctionForGroupNode(const bNode &group_node, + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info, + Vector<const bNodeSocket *> &r_used_inputs, + Vector<const bNodeSocket *> &r_used_outputs) + : group_node_(group_node) + { + debug_name_ = group_node.name; + lazy_function_interface_from_node( + group_node, r_used_inputs, r_used_outputs, inputs_, outputs_); + + bNodeTree *group_btree = reinterpret_cast<bNodeTree *>(group_node_.id); + BLI_assert(group_btree != nullptr); + + Vector<const lf::OutputSocket *> graph_inputs; + for (const lf::OutputSocket *socket : lf_graph_info.mapping.group_input_sockets) { + if (socket != nullptr) { + graph_inputs.append(socket); + } + } + Vector<const lf::InputSocket *> graph_outputs; + if (const bNode *group_output_bnode = group_btree->group_output_node()) { + for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) { + const lf::Socket *socket = lf_graph_info.mapping.dummy_socket_map.lookup_default(bsocket, + nullptr); + if (socket != nullptr) { + graph_outputs.append(&socket->as_input()); + } + } + } + + lf_logger_.emplace(lf_graph_info); + lf_side_effect_provider_.emplace(); + graph_executor_.emplace(lf_graph_info.graph, + std::move(graph_inputs), + std::move(graph_outputs), + &*lf_logger_, + &*lf_side_effect_provider_); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + + /* The compute context changes when entering a node group. */ + bke::NodeGroupComputeContext compute_context{user_data->compute_context, group_node_.name}; + GeoNodesLFUserData group_user_data = *user_data; + group_user_data.compute_context = &compute_context; + + lf::Context group_context = context; + group_context.user_data = &group_user_data; + + graph_executor_->execute(params, group_context); + } + + void *init_storage(LinearAllocator<> &allocator) const override + { + return graph_executor_->init_storage(allocator); + } + + void destruct_storage(void *storage) const override + { + graph_executor_->destruct_storage(storage); + } +}; + +static GMutablePointer get_socket_default_value(LinearAllocator<> &allocator, + const bNodeSocket &bsocket) +{ + const bNodeSocketType &typeinfo = *bsocket.typeinfo; + const CPPType *type = get_socket_cpp_type(typeinfo); + if (type == nullptr) { + return {}; + } + void *buffer = allocator.allocate(type->size(), type->alignment()); + typeinfo.get_geometry_nodes_cpp_value(bsocket, buffer); + return {type, buffer}; +} + +/** + * Utility class to build a lazy-function graph based on a geometry nodes tree. + * This is mainly a separate class because it makes it easier to have variables that can be + * accessed by many functions. + */ +struct GeometryNodesLazyFunctionGraphBuilder { + private: + const bNodeTree &btree_; + GeometryNodesLazyFunctionGraphInfo *lf_graph_info_; + lf::Graph *lf_graph_; + GeometryNodeLazyFunctionGraphMapping *mapping_; + MultiValueMap<const bNodeSocket *, lf::InputSocket *> input_socket_map_; + Map<const bNodeSocket *, lf::OutputSocket *> output_socket_map_; + Map<const bNodeSocket *, lf::Node *> multi_input_socket_nodes_; + const bke::DataTypeConversions *conversions_; + + /** + * All group input nodes are combined into one dummy node in the lazy-function graph. + * If some input has an invalid type, it is ignored in the new graph. In this case null and -1 is + * used in the vectors below. + */ + Vector<const CPPType *> group_input_types_; + Vector<int> group_input_indices_; + lf::DummyNode *group_input_lf_node_; + + /** + * The output types or null if an output is invalid. Each group output node gets a separate + * corresponding dummy node in the new graph. + */ + Vector<const CPPType *> group_output_types_; + Vector<int> group_output_indices_; + + public: + GeometryNodesLazyFunctionGraphBuilder(const bNodeTree &btree, + GeometryNodesLazyFunctionGraphInfo &lf_graph_info) + : btree_(btree), lf_graph_info_(&lf_graph_info) + { + } + + void build() + { + btree_.ensure_topology_cache(); + + lf_graph_ = &lf_graph_info_->graph; + mapping_ = &lf_graph_info_->mapping; + conversions_ = &bke::get_implicit_type_conversions(); + + this->prepare_node_multi_functions(); + this->prepare_group_inputs(); + this->prepare_group_outputs(); + this->build_group_input_node(); + this->handle_nodes(); + this->handle_links(); + this->add_default_inputs(); + + lf_graph_->update_node_indices(); + } + + private: + void prepare_node_multi_functions() + { + lf_graph_info_->node_multi_functions = std::make_unique<NodeMultiFunctions>(btree_); + } + + void prepare_group_inputs() + { + LISTBASE_FOREACH (const bNodeSocket *, interface_bsocket, &btree_.inputs) { + const CPPType *type = get_socket_cpp_type(*interface_bsocket->typeinfo); + if (type != nullptr) { + const int index = group_input_types_.append_and_get_index(type); + group_input_indices_.append(index); + } + else { + group_input_indices_.append(-1); + } + } + } + + void prepare_group_outputs() + { + LISTBASE_FOREACH (const bNodeSocket *, interface_bsocket, &btree_.outputs) { + const CPPType *type = get_socket_cpp_type(*interface_bsocket->typeinfo); + if (type != nullptr) { + const int index = group_output_types_.append_and_get_index(type); + group_output_indices_.append(index); + } + else { + group_output_indices_.append(-1); + } + } + } + + void build_group_input_node() + { + /* Create a dummy node for the group inputs. */ + group_input_lf_node_ = &lf_graph_->add_dummy({}, group_input_types_); + for (const int group_input_index : group_input_indices_) { + if (group_input_index == -1) { + mapping_->group_input_sockets.append(nullptr); + } + else { + mapping_->group_input_sockets.append(&group_input_lf_node_->output(group_input_index)); + } + } + } + + void handle_nodes() + { + /* Insert all nodes into the lazy function graph. */ + for (const bNode *bnode : btree_.all_nodes()) { + const bNodeType *node_type = bnode->typeinfo; + if (node_type == nullptr) { + continue; + } + if (bnode->is_muted()) { + this->handle_muted_node(*bnode); + continue; + } + switch (node_type->type) { + case NODE_FRAME: { + /* Ignored. */ + break; + } + case NODE_REROUTE: { + this->handle_reroute_node(*bnode); + break; + } + case NODE_GROUP_INPUT: { + this->handle_group_input_node(*bnode); + break; + } + case NODE_GROUP_OUTPUT: { + this->handle_group_output_node(*bnode); + break; + } + case NODE_CUSTOM_GROUP: + case NODE_GROUP: { + this->handle_group_node(*bnode); + break; + } + case GEO_NODE_VIEWER: { + this->handle_viewer_node(*bnode); + break; + } + default: { + if (node_type->geometry_node_execute) { + this->handle_geometry_node(*bnode); + break; + } + const NodeMultiFunctions::Item &fn_item = lf_graph_info_->node_multi_functions->try_get( + *bnode); + if (fn_item.fn != nullptr) { + this->handle_multi_function_node(*bnode, fn_item); + } + /* Nodes that don't match any of the criteria above are just ignored. */ + break; + } + } + } + } + + void handle_muted_node(const bNode &bnode) + { + Vector<const bNodeSocket *> used_inputs; + Vector<const bNodeSocket *> used_outputs; + auto lazy_function = std::make_unique<LazyFunctionForMutedNode>( + bnode, used_inputs, used_outputs); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + for (const int i : used_inputs.index_range()) { + const bNodeSocket &bsocket = *used_inputs[i]; + lf::InputSocket &lf_socket = lf_node.input(i); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : used_outputs.index_range()) { + const bNodeSocket &bsocket = *used_outputs[i]; + lf::OutputSocket &lf_socket = lf_node.output(i); + output_socket_map_.add_new(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_reroute_node(const bNode &bnode) + { + const bNodeSocket &input_bsocket = bnode.input_socket(0); + const bNodeSocket &output_bsocket = bnode.output_socket(0); + const CPPType *type = get_socket_cpp_type(input_bsocket); + if (type == nullptr) { + return; + } + + auto lazy_function = std::make_unique<LazyFunctionForRerouteNode>(*type); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + lf::InputSocket &lf_input = lf_node.input(0); + lf::OutputSocket &lf_output = lf_node.output(0); + input_socket_map_.add(&input_bsocket, &lf_input); + output_socket_map_.add_new(&output_bsocket, &lf_output); + mapping_->bsockets_by_lf_socket_map.add(&lf_input, &input_bsocket); + mapping_->bsockets_by_lf_socket_map.add(&lf_output, &output_bsocket); + } + + void handle_group_input_node(const bNode &bnode) + { + for (const int btree_index : group_input_indices_.index_range()) { + const int lf_index = group_input_indices_[btree_index]; + if (lf_index == -1) { + continue; + } + const bNodeSocket &bsocket = bnode.output_socket(btree_index); + lf::OutputSocket &lf_socket = group_input_lf_node_->output(lf_index); + output_socket_map_.add_new(&bsocket, &lf_socket); + mapping_->dummy_socket_map.add_new(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_group_output_node(const bNode &bnode) + { + lf::DummyNode &group_output_lf_node = lf_graph_->add_dummy(group_output_types_, {}); + for (const int btree_index : group_output_indices_.index_range()) { + const int lf_index = group_output_indices_[btree_index]; + if (lf_index == -1) { + continue; + } + const bNodeSocket &bsocket = bnode.input_socket(btree_index); + lf::InputSocket &lf_socket = group_output_lf_node.input(lf_index); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->dummy_socket_map.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_group_node(const bNode &bnode) + { + const bNodeTree *group_btree = reinterpret_cast<bNodeTree *>(bnode.id); + if (group_btree == nullptr) { + return; + } + const GeometryNodesLazyFunctionGraphInfo *group_lf_graph_info = + ensure_geometry_nodes_lazy_function_graph(*group_btree); + if (group_lf_graph_info == nullptr) { + return; + } + + Vector<const bNodeSocket *> used_inputs; + Vector<const bNodeSocket *> used_outputs; + auto lazy_function = std::make_unique<LazyFunctionForGroupNode>( + bnode, *group_lf_graph_info, used_inputs, used_outputs); + lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + for (const int i : used_inputs.index_range()) { + const bNodeSocket &bsocket = *used_inputs[i]; + BLI_assert(!bsocket.is_multi_input()); + lf::InputSocket &lf_socket = lf_node.input(i); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : used_outputs.index_range()) { + const bNodeSocket &bsocket = *used_outputs[i]; + lf::OutputSocket &lf_socket = lf_node.output(i); + output_socket_map_.add_new(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + mapping_->group_node_map.add(&bnode, &lf_node); + } + + void handle_geometry_node(const bNode &bnode) + { + Vector<const bNodeSocket *> used_inputs; + Vector<const bNodeSocket *> used_outputs; + auto lazy_function = std::make_unique<LazyFunctionForGeometryNode>( + bnode, used_inputs, used_outputs); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const int i : used_inputs.index_range()) { + const bNodeSocket &bsocket = *used_inputs[i]; + lf::InputSocket &lf_socket = lf_node.input(i); + + if (bsocket.is_multi_input()) { + auto multi_input_lazy_function = std::make_unique<LazyFunctionForMultiInput>(bsocket); + lf::Node &lf_multi_input_node = lf_graph_->add_function(*multi_input_lazy_function); + lf_graph_info_->functions.append(std::move(multi_input_lazy_function)); + lf_graph_->add_link(lf_multi_input_node.output(0), lf_socket); + multi_input_socket_nodes_.add_new(&bsocket, &lf_multi_input_node); + for (lf::InputSocket *lf_multi_input_socket : lf_multi_input_node.inputs()) { + mapping_->bsockets_by_lf_socket_map.add(lf_multi_input_socket, &bsocket); + } + } + else { + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + for (const int i : used_outputs.index_range()) { + const bNodeSocket &bsocket = *used_outputs[i]; + lf::OutputSocket &lf_socket = lf_node.output(i); + output_socket_map_.add_new(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_multi_function_node(const bNode &bnode, const NodeMultiFunctions::Item &fn_item) + { + Vector<const bNodeSocket *> used_inputs; + Vector<const bNodeSocket *> used_outputs; + auto lazy_function = std::make_unique<LazyFunctionForMultiFunctionNode>( + bnode, fn_item, used_inputs, used_outputs); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const int i : used_inputs.index_range()) { + const bNodeSocket &bsocket = *used_inputs[i]; + BLI_assert(!bsocket.is_multi_input()); + lf::InputSocket &lf_socket = lf_node.input(i); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : used_outputs.index_range()) { + const bNodeSocket &bsocket = *used_outputs[i]; + lf::OutputSocket &lf_socket = lf_node.output(i); + output_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + } + + void handle_viewer_node(const bNode &bnode) + { + Vector<const bNodeSocket *> used_inputs; + auto lazy_function = std::make_unique<LazyFunctionForViewerNode>(bnode, used_inputs); + lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const int i : used_inputs.index_range()) { + const bNodeSocket &bsocket = *used_inputs[i]; + lf::InputSocket &lf_socket = lf_node.input(i); + input_socket_map_.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + + mapping_->viewer_node_map.add(&bnode, &lf_node); + } + + void handle_links() + { + for (const auto item : output_socket_map_.items()) { + this->insert_links_from_socket(*item.key, *item.value); + } + } + + void insert_links_from_socket(const bNodeSocket &from_bsocket, lf::OutputSocket &from_lf_socket) + { + const Span<const bNodeLink *> links_from_bsocket = from_bsocket.directly_linked_links(); + + struct TypeWithLinks { + const CPPType *type; + Vector<const bNodeLink *> links; + }; + + /* Group available target sockets by type so that they can be handled together. */ + Vector<TypeWithLinks> types_with_links; + for (const bNodeLink *link : links_from_bsocket) { + if (link->is_muted()) { + continue; + } + const bNodeSocket &to_bsocket = *link->tosock; + if (!to_bsocket.is_available()) { + continue; + } + const CPPType *to_type = get_socket_cpp_type(to_bsocket); + if (to_type == nullptr) { + continue; + } + bool inserted = false; + for (TypeWithLinks &types_with_links : types_with_links) { + if (types_with_links.type == to_type) { + types_with_links.links.append(link); + inserted = true; + break; + } + } + if (inserted) { + continue; + } + types_with_links.append({to_type, {link}}); + } + + for (const TypeWithLinks &type_with_links : types_with_links) { + const CPPType &to_type = *type_with_links.type; + const Span<const bNodeLink *> links = type_with_links.links; + + Vector<const bNodeSocket *> target_bsockets; + for (const bNodeLink *link : links) { + target_bsockets.append(link->tosock); + } + + lf::OutputSocket *converted_from_lf_socket = this->insert_type_conversion_if_necessary( + from_lf_socket, to_type, std::move(target_bsockets)); + + auto make_input_link_or_set_default = [&](lf::InputSocket &to_lf_socket) { + if (converted_from_lf_socket == nullptr) { + const void *default_value = to_type.default_value(); + to_lf_socket.set_default_value(default_value); + } + else { + lf_graph_->add_link(*converted_from_lf_socket, to_lf_socket); + } + }; + + for (const bNodeLink *link : links) { + const bNodeSocket &to_bsocket = *link->tosock; + if (to_bsocket.is_multi_input()) { + /* TODO: Cache this index on the link. */ + int link_index = 0; + for (const bNodeLink *multi_input_link : to_bsocket.directly_linked_links()) { + if (multi_input_link == link) { + break; + } + if (!multi_input_link->is_muted()) { + link_index++; + } + } + if (to_bsocket.owner_node().is_muted()) { + if (link_index == 0) { + for (lf::InputSocket *to_lf_socket : input_socket_map_.lookup(&to_bsocket)) { + make_input_link_or_set_default(*to_lf_socket); + } + } + } + else { + lf::Node *multi_input_lf_node = multi_input_socket_nodes_.lookup_default(&to_bsocket, + nullptr); + if (multi_input_lf_node == nullptr) { + continue; + } + make_input_link_or_set_default(multi_input_lf_node->input(link_index)); + } + } + else { + for (lf::InputSocket *to_lf_socket : input_socket_map_.lookup(&to_bsocket)) { + make_input_link_or_set_default(*to_lf_socket); + } + } + } + } + } + + lf::OutputSocket *insert_type_conversion_if_necessary( + lf::OutputSocket &from_socket, + const CPPType &to_type, + Vector<const bNodeSocket *> &&target_sockets) + { + const CPPType &from_type = from_socket.type(); + if (from_type == to_type) { + return &from_socket; + } + const auto *from_field_type = dynamic_cast<const ValueOrFieldCPPType *>(&from_type); + const auto *to_field_type = dynamic_cast<const ValueOrFieldCPPType *>(&to_type); + if (from_field_type != nullptr && to_field_type != nullptr) { + const CPPType &from_base_type = from_field_type->base_type(); + const CPPType &to_base_type = to_field_type->base_type(); + if (conversions_->is_convertible(from_base_type, to_base_type)) { + const MultiFunction &multi_fn = *conversions_->get_conversion_multi_function( + MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type)); + auto fn = std::make_unique<LazyFunctionForMultiFunctionConversion>( + multi_fn, *from_field_type, *to_field_type, std::move(target_sockets)); + lf::Node &conversion_node = lf_graph_->add_function(*fn); + lf_graph_info_->functions.append(std::move(fn)); + lf_graph_->add_link(from_socket, conversion_node.input(0)); + return &conversion_node.output(0); + } + } + return nullptr; + } + + void add_default_inputs() + { + for (auto item : input_socket_map_.items()) { + const bNodeSocket &bsocket = *item.key; + const Span<lf::InputSocket *> lf_sockets = item.value; + for (lf::InputSocket *lf_socket : lf_sockets) { + if (lf_socket->origin() != nullptr) { + /* Is linked already. */ + continue; + } + this->add_default_input(bsocket, *lf_socket); + } + } + } + + void add_default_input(const bNodeSocket &input_bsocket, lf::InputSocket &input_lf_socket) + { + if (this->try_add_implicit_input(input_bsocket, input_lf_socket)) { + return; + } + GMutablePointer value = get_socket_default_value(lf_graph_info_->allocator, input_bsocket); + if (value.get() == nullptr) { + /* Not possible to add a default value. */ + return; + } + input_lf_socket.set_default_value(value.get()); + if (!value.type()->is_trivially_destructible()) { + lf_graph_info_->values_to_destruct.append(value); + } + } + + bool try_add_implicit_input(const bNodeSocket &input_bsocket, lf::InputSocket &input_lf_socket) + { + const bNode &bnode = input_bsocket.owner_node(); + const NodeDeclaration *node_declaration = bnode.declaration(); + if (node_declaration == nullptr) { + return false; + } + const SocketDeclaration &socket_declaration = + *node_declaration->inputs()[input_bsocket.index()]; + if (socket_declaration.input_field_type() != InputSocketFieldType::Implicit) { + return false; + } + const CPPType &type = input_lf_socket.type(); + std::function<void(void *)> init_fn = this->get_implicit_input_init_function(bnode, + input_bsocket); + if (!init_fn) { + return false; + } + + auto lazy_function = std::make_unique<LazyFunctionForImplicitInput>(type, std::move(init_fn)); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + lf_graph_->add_link(lf_node.output(0), input_lf_socket); + return true; + } + + std::function<void(void *)> get_implicit_input_init_function(const bNode &bnode, + const bNodeSocket &bsocket) + { + const bNodeSocketType &socket_type = *bsocket.typeinfo; + if (socket_type.type == SOCK_VECTOR) { + if (bnode.type == GEO_NODE_SET_CURVE_HANDLES) { + StringRef side = ((NodeGeometrySetCurveHandlePositions *)bnode.storage)->mode == + GEO_NODE_CURVE_HANDLE_LEFT ? + "handle_left" : + "handle_right"; + return [side](void *r_value) { + new (r_value) ValueOrField<float3>(bke::AttributeFieldInput::Create<float3>(side)); + }; + } + else if (bnode.type == GEO_NODE_EXTRUDE_MESH) { + return [](void *r_value) { + new (r_value) + ValueOrField<float3>(Field<float3>(std::make_shared<bke::NormalFieldInput>())); + }; + } + else { + return [](void *r_value) { + new (r_value) ValueOrField<float3>(bke::AttributeFieldInput::Create<float3>("position")); + }; + } + } + else if (socket_type.type == SOCK_INT) { + if (ELEM(bnode.type, FN_NODE_RANDOM_VALUE, GEO_NODE_INSTANCE_ON_POINTS)) { + return [](void *r_value) { + new (r_value) + ValueOrField<int>(Field<int>(std::make_shared<bke::IDAttributeFieldInput>())); + }; + } + else { + return [](void *r_value) { + new (r_value) ValueOrField<int>(Field<int>(std::make_shared<fn::IndexFieldInput>())); + }; + } + } + return {}; + } +}; + +const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph( + const bNodeTree &btree) +{ + btree.ensure_topology_cache(); + if (btree.has_link_cycle()) { + return nullptr; + } + + std::unique_ptr<GeometryNodesLazyFunctionGraphInfo> &lf_graph_info_ptr = + btree.runtime->geometry_nodes_lazy_function_graph_info; + + if (lf_graph_info_ptr) { + return lf_graph_info_ptr.get(); + } + std::lock_guard lock{btree.runtime->geometry_nodes_lazy_function_graph_info_mutex}; + if (lf_graph_info_ptr) { + return lf_graph_info_ptr.get(); + } + + auto lf_graph_info = std::make_unique<GeometryNodesLazyFunctionGraphInfo>(); + GeometryNodesLazyFunctionGraphBuilder builder{btree, *lf_graph_info}; + builder.build(); + + lf_graph_info_ptr = std::move(lf_graph_info); + return lf_graph_info_ptr.get(); +} + +GeometryNodesLazyFunctionLogger::GeometryNodesLazyFunctionLogger( + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info) + : lf_graph_info_(lf_graph_info) +{ +} + +void GeometryNodesLazyFunctionLogger::log_socket_value( + const fn::lazy_function::Socket &lf_socket, + const GPointer value, + const fn::lazy_function::Context &context) const +{ + const Span<const bNodeSocket *> bsockets = + lf_graph_info_.mapping.bsockets_by_lf_socket_map.lookup(&lf_socket); + if (bsockets.is_empty()) { + return; + } + + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + if (user_data->modifier_data->eval_log == nullptr) { + return; + } + geo_eval_log::GeoTreeLogger &tree_logger = + user_data->modifier_data->eval_log->get_local_tree_logger(*user_data->compute_context); + for (const bNodeSocket *bsocket : bsockets) { + /* Avoid logging to some sockets when the same value will also be logged to a linked socket. + * This reduces the number of logged values without losing information. */ + if (bsocket->is_input() && bsocket->is_directly_linked()) { + continue; + } + const bNode &bnode = bsocket->owner_node(); + if (bnode.is_reroute()) { + continue; + } + tree_logger.log_value(bsocket->owner_node(), *bsocket, value); + } +} + +static std::mutex dump_error_context_mutex; + +void GeometryNodesLazyFunctionLogger::dump_when_outputs_are_missing( + const lf::FunctionNode &node, + Span<const lf::OutputSocket *> missing_sockets, + const lf::Context &context) const +{ + std::lock_guard lock{dump_error_context_mutex}; + + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + user_data->compute_context->print_stack(std::cout, node.name()); + std::cout << "Missing outputs:\n"; + for (const lf::OutputSocket *socket : missing_sockets) { + std::cout << " " << socket->name() << "\n"; + } +} + +void GeometryNodesLazyFunctionLogger::dump_when_input_is_set_twice( + const lf::InputSocket &target_socket, + const lf::OutputSocket &from_socket, + const lf::Context &context) const +{ + std::lock_guard lock{dump_error_context_mutex}; + + std::stringstream ss; + ss << from_socket.node().name() << ":" << from_socket.name() << " -> " + << target_socket.node().name() << ":" << target_socket.name(); + + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + user_data->compute_context->print_stack(std::cout, ss.str()); +} + +Vector<const lf::FunctionNode *> GeometryNodesLazyFunctionSideEffectProvider:: + get_nodes_with_side_effects(const lf::Context &context) const +{ + GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data); + BLI_assert(user_data != nullptr); + const ComputeContextHash &context_hash = user_data->compute_context->hash(); + const GeoNodesModifierData &modifier_data = *user_data->modifier_data; + return modifier_data.side_effect_nodes->lookup(context_hash); +} + +GeometryNodesLazyFunctionGraphInfo::GeometryNodesLazyFunctionGraphInfo() = default; +GeometryNodesLazyFunctionGraphInfo::~GeometryNodesLazyFunctionGraphInfo() +{ + for (GMutablePointer &p : this->values_to_destruct) { + p.destruct(); + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc new file mode 100644 index 00000000000..350b199cd60 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -0,0 +1,608 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_geometry_nodes_lazy_function.hh" +#include "NOD_geometry_nodes_log.hh" + +#include "BKE_compute_contexts.hh" +#include "BKE_curves.hh" +#include "BKE_node_runtime.hh" + +#include "FN_field_cpp_type.hh" + +#include "DNA_modifier_types.h" +#include "DNA_space_types.h" + +namespace blender::nodes::geo_eval_log { + +using fn::FieldInput; +using fn::FieldInputs; + +GenericValueLog::~GenericValueLog() +{ + this->value.destruct(); +} + +FieldInfoLog::FieldInfoLog(const GField &field) : type(field.cpp_type()) +{ + const std::shared_ptr<const fn::FieldInputs> &field_input_nodes = field.node().field_inputs(); + + /* Put the deduplicated field inputs into a vector so that they can be sorted below. */ + Vector<std::reference_wrapper<const FieldInput>> field_inputs; + if (field_input_nodes) { + field_inputs.extend(field_input_nodes->deduplicated_nodes.begin(), + field_input_nodes->deduplicated_nodes.end()); + } + + std::sort( + field_inputs.begin(), field_inputs.end(), [](const FieldInput &a, const FieldInput &b) { + const int index_a = (int)a.category(); + const int index_b = (int)b.category(); + if (index_a == index_b) { + return a.socket_inspection_name().size() < b.socket_inspection_name().size(); + } + return index_a < index_b; + }); + + for (const FieldInput &field_input : field_inputs) { + this->input_tooltips.append(field_input.socket_inspection_name()); + } +} + +GeometryInfoLog::GeometryInfoLog(const GeometrySet &geometry_set) +{ + static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES, + GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_VOLUME}; + + /* Keep track handled attribute names to make sure that we do not return the same name twice. + * Currently #GeometrySet::attribute_foreach does not do that. Note that this will merge + * attributes with the same name but different domains or data types on separate components. */ + Set<StringRef> names; + + geometry_set.attribute_foreach( + all_component_types, + true, + [&](const bke::AttributeIDRef &attribute_id, + const bke::AttributeMetaData &meta_data, + const GeometryComponent &UNUSED(component)) { + if (attribute_id.is_named() && names.add(attribute_id.name())) { + this->attributes.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); + } + }); + + for (const GeometryComponent *component : geometry_set.get_components_for_read()) { + this->component_types.append(component->type()); + switch (component->type()) { + case GEO_COMPONENT_TYPE_MESH: { + const MeshComponent &mesh_component = *(const MeshComponent *)component; + MeshInfo &info = this->mesh_info.emplace(); + info.verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); + info.edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE); + info.faces_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_FACE); + break; + } + case GEO_COMPONENT_TYPE_CURVE: { + const CurveComponent &curve_component = *(const CurveComponent *)component; + CurveInfo &info = this->curve_info.emplace(); + info.splines_num = curve_component.attribute_domain_size(ATTR_DOMAIN_CURVE); + break; + } + case GEO_COMPONENT_TYPE_POINT_CLOUD: { + const PointCloudComponent &pointcloud_component = *(const PointCloudComponent *)component; + PointCloudInfo &info = this->pointcloud_info.emplace(); + info.points_num = pointcloud_component.attribute_domain_size(ATTR_DOMAIN_POINT); + break; + } + case GEO_COMPONENT_TYPE_INSTANCES: { + const InstancesComponent &instances_component = *(const InstancesComponent *)component; + InstancesInfo &info = this->instances_info.emplace(); + info.instances_num = instances_component.instances_num(); + break; + } + case GEO_COMPONENT_TYPE_EDIT: { + const GeometryComponentEditData &edit_component = *( + const GeometryComponentEditData *)component; + if (const bke::CurvesEditHints *curve_edit_hints = + edit_component.curves_edit_hints_.get()) { + EditDataInfo &info = this->edit_data_info.emplace(); + info.has_deform_matrices = curve_edit_hints->deform_mats.has_value(); + info.has_deformed_positions = curve_edit_hints->positions.has_value(); + } + break; + } + case GEO_COMPONENT_TYPE_VOLUME: { + break; + } + } + } +} + +/* Avoid generating these in every translation unit. */ +GeoModifierLog::GeoModifierLog() = default; +GeoModifierLog::~GeoModifierLog() = default; + +GeoTreeLogger::GeoTreeLogger() = default; +GeoTreeLogger::~GeoTreeLogger() = default; + +GeoNodeLog::GeoNodeLog() = default; +GeoNodeLog::~GeoNodeLog() = default; + +GeoTreeLog::GeoTreeLog(GeoModifierLog *modifier_log, Vector<GeoTreeLogger *> tree_loggers) + : modifier_log_(modifier_log), tree_loggers_(std::move(tree_loggers)) +{ + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const ComputeContextHash &hash : tree_logger->children_hashes) { + children_hashes_.add(hash); + } + } +} + +GeoTreeLog::~GeoTreeLog() = default; + +void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, const GPointer value) +{ + const CPPType &type = *value.type(); + + auto store_logged_value = [&](destruct_ptr<ValueLog> value_log) { + auto &socket_values = socket.in_out == SOCK_IN ? this->input_socket_values : + this->output_socket_values; + socket_values.append({node.name, socket.identifier, std::move(value_log)}); + }; + + auto log_generic_value = [&](const CPPType &type, const void *value) { + void *buffer = this->allocator->allocate(type.size(), type.alignment()); + type.copy_construct(value, buffer); + store_logged_value(this->allocator->construct<GenericValueLog>(GMutablePointer{type, buffer})); + }; + + if (type.is<GeometrySet>()) { + const GeometrySet &geometry = *value.get<GeometrySet>(); + store_logged_value(this->allocator->construct<GeometryInfoLog>(geometry)); + } + else if (const auto *value_or_field_type = dynamic_cast<const fn::ValueOrFieldCPPType *>( + &type)) { + const void *value_or_field = value.get(); + const CPPType &base_type = value_or_field_type->base_type(); + if (value_or_field_type->is_field(value_or_field)) { + const GField *field = value_or_field_type->get_field_ptr(value_or_field); + if (field->node().depends_on_input()) { + store_logged_value(this->allocator->construct<FieldInfoLog>(*field)); + } + else { + BUFFER_FOR_CPP_TYPE_VALUE(base_type, value); + fn::evaluate_constant_field(*field, value); + log_generic_value(base_type, value); + } + } + else { + const void *value = value_or_field_type->get_value_ptr(value_or_field); + log_generic_value(base_type, value); + } + } + else { + log_generic_value(type, value.get()); + } +} + +void GeoTreeLogger::log_viewer_node(const bNode &viewer_node, + const GeometrySet &geometry, + const GField &field) +{ + destruct_ptr<ViewerNodeLog> log = this->allocator->construct<ViewerNodeLog>(); + log->geometry = geometry; + log->field = field; + log->geometry.ensure_owns_direct_data(); + this->viewer_node_logs.append({viewer_node.name, std::move(log)}); +} + +void GeoTreeLog::ensure_node_warnings() +{ + if (reduced_node_warnings_) { + return; + } + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::WarningWithNode &warnings : tree_logger->node_warnings) { + this->nodes.lookup_or_add_default(warnings.node_name).warnings.append(warnings.warning); + this->all_warnings.append(warnings.warning); + } + } + for (const ComputeContextHash &child_hash : children_hashes_) { + GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash); + child_log.ensure_node_warnings(); + const std::optional<std::string> &group_node_name = + child_log.tree_loggers_[0]->group_node_name; + if (group_node_name.has_value()) { + this->nodes.lookup_or_add_default(*group_node_name).warnings.extend(child_log.all_warnings); + } + this->all_warnings.extend(child_log.all_warnings); + } + reduced_node_warnings_ = true; +} + +void GeoTreeLog::ensure_node_run_time() +{ + if (reduced_node_run_times_) { + return; + } + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::NodeExecutionTime &timings : tree_logger->node_execution_times) { + const std::chrono::nanoseconds duration = timings.end - timings.start; + this->nodes.lookup_or_add_default_as(timings.node_name).run_time += duration; + this->run_time_sum += duration; + } + } + for (const ComputeContextHash &child_hash : children_hashes_) { + GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash); + child_log.ensure_node_run_time(); + const std::optional<std::string> &group_node_name = + child_log.tree_loggers_[0]->group_node_name; + if (group_node_name.has_value()) { + this->nodes.lookup_or_add_default(*group_node_name).run_time += child_log.run_time_sum; + } + this->run_time_sum += child_log.run_time_sum; + } + reduced_node_run_times_ = true; +} + +void GeoTreeLog::ensure_socket_values() +{ + if (reduced_socket_values_) { + return; + } + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::SocketValueLog &value_log_data : tree_logger->input_socket_values) { + this->nodes.lookup_or_add_as(value_log_data.node_name) + .input_values_.add(value_log_data.socket_identifier, value_log_data.value.get()); + } + for (const GeoTreeLogger::SocketValueLog &value_log_data : tree_logger->output_socket_values) { + this->nodes.lookup_or_add_as(value_log_data.node_name) + .output_values_.add(value_log_data.socket_identifier, value_log_data.value.get()); + } + } + reduced_socket_values_ = true; +} + +void GeoTreeLog::ensure_viewer_node_logs() +{ + if (reduced_viewer_node_logs_) { + return; + } + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::ViewerNodeLogWithNode &viewer_log : tree_logger->viewer_node_logs) { + this->viewer_node_logs.add(viewer_log.node_name, viewer_log.viewer_log.get()); + } + } + reduced_viewer_node_logs_ = true; +} + +void GeoTreeLog::ensure_existing_attributes() +{ + if (reduced_existing_attributes_) { + return; + } + this->ensure_socket_values(); + + Set<StringRef> names; + + auto handle_value_log = [&](const ValueLog &value_log) { + const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(&value_log); + if (geo_log == nullptr) { + return; + } + for (const GeometryAttributeInfo &attribute : geo_log->attributes) { + if (names.add(attribute.name)) { + this->existing_attributes.append(&attribute); + } + } + }; + + for (const GeoNodeLog &node_log : this->nodes.values()) { + for (const ValueLog *value_log : node_log.input_values_.values()) { + handle_value_log(*value_log); + } + for (const ValueLog *value_log : node_log.output_values_.values()) { + handle_value_log(*value_log); + } + } + reduced_existing_attributes_ = true; +} + +void GeoTreeLog::ensure_used_named_attributes() +{ + if (reduced_used_named_attributes_) { + return; + } + + auto add_attribute = [&](const StringRef node_name, + const StringRef attribute_name, + const NamedAttributeUsage &usage) { + this->nodes.lookup_or_add_as(node_name).used_named_attributes.lookup_or_add_as(attribute_name, + usage) |= usage; + this->used_named_attributes.lookup_or_add_as(attribute_name, usage) |= usage; + }; + + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::AttributeUsageWithNode &item : tree_logger->used_named_attributes) { + add_attribute(item.node_name, item.attribute_name, item.usage); + } + } + for (const ComputeContextHash &child_hash : children_hashes_) { + GeoTreeLog &child_log = modifier_log_->get_tree_log(child_hash); + child_log.ensure_used_named_attributes(); + if (const std::optional<std::string> &group_node_name = + child_log.tree_loggers_[0]->group_node_name) { + for (const auto &item : child_log.used_named_attributes.items()) { + add_attribute(*group_node_name, item.key, item.value); + } + } + } + reduced_used_named_attributes_ = true; +} + +void GeoTreeLog::ensure_debug_messages() +{ + if (reduced_debug_messages_) { + return; + } + for (GeoTreeLogger *tree_logger : tree_loggers_) { + for (const GeoTreeLogger::DebugMessage &debug_message : tree_logger->debug_messages) { + this->nodes.lookup_or_add_as(debug_message.node_name) + .debug_messages.append(debug_message.message); + } + } + reduced_debug_messages_ = true; +} + +ValueLog *GeoTreeLog::find_socket_value_log(const bNodeSocket &query_socket) +{ + /** + * Geometry nodes does not log values for every socket. That would produce a lot of redundant + * data,because often many linked sockets have the same value. To find the logged value for a + * socket one might have to look at linked sockets as well. + */ + + BLI_assert(reduced_socket_values_); + if (query_socket.is_multi_input()) { + /* Not supported currently. */ + return nullptr; + } + + Set<const bNodeSocket *> added_sockets; + Stack<const bNodeSocket *> sockets_to_check; + sockets_to_check.push(&query_socket); + added_sockets.add(&query_socket); + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + const bNode &node = socket.owner_node(); + if (GeoNodeLog *node_log = this->nodes.lookup_ptr(node.name)) { + ValueLog *value_log = socket.is_input() ? + node_log->input_values_.lookup_default(socket.identifier, + nullptr) : + node_log->output_values_.lookup_default(socket.identifier, + nullptr); + if (value_log != nullptr) { + return value_log; + } + } + + if (socket.is_input()) { + const Span<const bNodeLink *> links = socket.directly_linked_links(); + for (const bNodeLink *link : links) { + const bNodeSocket &from_socket = *link->fromsock; + if (added_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + else { + if (node.is_reroute()) { + const bNodeSocket &input_socket = node.input_socket(0); + if (added_sockets.add(&input_socket)) { + sockets_to_check.push(&input_socket); + } + const Span<const bNodeLink *> links = input_socket.directly_linked_links(); + for (const bNodeLink *link : links) { + const bNodeSocket &from_socket = *link->fromsock; + if (added_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + else if (node.is_muted()) { + if (const bNodeSocket *input_socket = socket.internal_link_input()) { + if (added_sockets.add(input_socket)) { + sockets_to_check.push(input_socket); + } + const Span<const bNodeLink *> links = input_socket->directly_linked_links(); + for (const bNodeLink *link : links) { + const bNodeSocket &from_socket = *link->fromsock; + if (added_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + } + } + } + + return nullptr; +} + +GeoTreeLogger &GeoModifierLog::get_local_tree_logger(const ComputeContext &compute_context) +{ + LocalData &local_data = data_per_thread_.local(); + Map<ComputeContextHash, destruct_ptr<GeoTreeLogger>> &local_tree_loggers = + local_data.tree_logger_by_context; + destruct_ptr<GeoTreeLogger> &tree_logger_ptr = local_tree_loggers.lookup_or_add_default( + compute_context.hash()); + if (tree_logger_ptr) { + return *tree_logger_ptr; + } + tree_logger_ptr = local_data.allocator.construct<GeoTreeLogger>(); + GeoTreeLogger &tree_logger = *tree_logger_ptr; + tree_logger.allocator = &local_data.allocator; + const ComputeContext *parent_compute_context = compute_context.parent(); + if (parent_compute_context != nullptr) { + tree_logger.parent_hash = parent_compute_context->hash(); + GeoTreeLogger &parent_logger = this->get_local_tree_logger(*parent_compute_context); + parent_logger.children_hashes.append(compute_context.hash()); + } + if (const bke::NodeGroupComputeContext *node_group_compute_context = + dynamic_cast<const bke::NodeGroupComputeContext *>(&compute_context)) { + tree_logger.group_node_name.emplace(node_group_compute_context->node_name()); + } + return tree_logger; +} + +GeoTreeLog &GeoModifierLog::get_tree_log(const ComputeContextHash &compute_context_hash) +{ + GeoTreeLog &reduced_tree_log = *tree_logs_.lookup_or_add_cb(compute_context_hash, [&]() { + Vector<GeoTreeLogger *> tree_logs; + for (LocalData &local_data : data_per_thread_) { + destruct_ptr<GeoTreeLogger> *tree_log = local_data.tree_logger_by_context.lookup_ptr( + compute_context_hash); + if (tree_log != nullptr) { + tree_logs.append(tree_log->get()); + } + } + return std::make_unique<GeoTreeLog>(this, std::move(tree_logs)); + }); + return reduced_tree_log; +} + +struct ObjectAndModifier { + const Object *object; + const NodesModifierData *nmd; +}; + +static std::optional<ObjectAndModifier> get_modifier_for_node_editor(const SpaceNode &snode) +{ + if (snode.id == nullptr) { + return std::nullopt; + } + if (GS(snode.id->name) != ID_OB) { + return std::nullopt; + } + const Object *object = reinterpret_cast<Object *>(snode.id); + const NodesModifierData *used_modifier = nullptr; + if (snode.flag & SNODE_PIN) { + LISTBASE_FOREACH (const ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md); + /* Would be good to store the name of the pinned modifier in the node editor. */ + if (nmd->node_group == snode.nodetree) { + used_modifier = nmd; + break; + } + } + } + } + else { + LISTBASE_FOREACH (const ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md); + if (nmd->node_group == snode.nodetree) { + if (md->flag & eModifierFlag_Active) { + used_modifier = nmd; + break; + } + } + } + } + } + if (used_modifier == nullptr) { + return std::nullopt; + } + return ObjectAndModifier{object, used_modifier}; +} + +GeoTreeLog *GeoModifierLog::get_tree_log_for_node_editor(const SpaceNode &snode) +{ + std::optional<ObjectAndModifier> object_and_modifier = get_modifier_for_node_editor(snode); + if (!object_and_modifier) { + return nullptr; + } + GeoModifierLog *modifier_log = static_cast<GeoModifierLog *>( + object_and_modifier->nmd->runtime_eval_log); + if (modifier_log == nullptr) { + return nullptr; + } + Vector<const bNodeTreePath *> tree_path = snode.treepath; + if (tree_path.is_empty()) { + return nullptr; + } + ComputeContextBuilder compute_context_builder; + compute_context_builder.push<bke::ModifierComputeContext>( + object_and_modifier->nmd->modifier.name); + for (const bNodeTreePath *path_item : tree_path.as_span().drop_front(1)) { + compute_context_builder.push<bke::NodeGroupComputeContext>(path_item->node_name); + } + return &modifier_log->get_tree_log(compute_context_builder.hash()); +} + +const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_spreadsheet( + const SpaceSpreadsheet &sspreadsheet) +{ + Vector<const SpreadsheetContext *> context_path = sspreadsheet.context_path; + if (context_path.size() < 3) { + return nullptr; + } + if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { + return nullptr; + } + if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) { + return nullptr; + } + const SpreadsheetContextObject *object_context = + reinterpret_cast<const SpreadsheetContextObject *>(context_path[0]); + const SpreadsheetContextModifier *modifier_context = + reinterpret_cast<const SpreadsheetContextModifier *>(context_path[1]); + if (object_context->object == nullptr) { + return nullptr; + } + NodesModifierData *nmd = nullptr; + LISTBASE_FOREACH (ModifierData *, md, &object_context->object->modifiers) { + if (STREQ(md->name, modifier_context->modifier_name)) { + if (md->type == eModifierType_Nodes) { + nmd = reinterpret_cast<NodesModifierData *>(md); + } + } + } + if (nmd == nullptr) { + return nullptr; + } + if (nmd->runtime_eval_log == nullptr) { + return nullptr; + } + nodes::geo_eval_log::GeoModifierLog *modifier_log = + static_cast<nodes::geo_eval_log::GeoModifierLog *>(nmd->runtime_eval_log); + + ComputeContextBuilder compute_context_builder; + compute_context_builder.push<bke::ModifierComputeContext>(modifier_context->modifier_name); + for (const SpreadsheetContext *context : context_path.as_span().drop_front(2).drop_back(1)) { + if (context->type != SPREADSHEET_CONTEXT_NODE) { + return nullptr; + } + const SpreadsheetContextNode &node_context = *reinterpret_cast<const SpreadsheetContextNode *>( + context); + compute_context_builder.push<bke::NodeGroupComputeContext>(node_context.node_name); + } + const ComputeContextHash context_hash = compute_context_builder.hash(); + nodes::geo_eval_log::GeoTreeLog &tree_log = modifier_log->get_tree_log(context_hash); + tree_log.ensure_viewer_node_logs(); + + const SpreadsheetContext *last_context = context_path.last(); + if (last_context->type != SPREADSHEET_CONTEXT_NODE) { + return nullptr; + } + const SpreadsheetContextNode &last_node_context = + *reinterpret_cast<const SpreadsheetContextNode *>(last_context); + const ViewerNodeLog *viewer_log = tree_log.viewer_node_logs.lookup_default( + last_node_context.node_name, nullptr); + return viewer_log; +} + +} // namespace blender::nodes::geo_eval_log diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index b7c5f9570e4..6402ec3f3d6 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -37,6 +37,7 @@ using blender::MultiValueMap; using blender::Set; using blender::Stack; using blender::StringRef; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name Node Group @@ -160,6 +161,7 @@ static void group_verify_socket_list(bNodeTree &node_tree, const bool ensure_extend_socket_exists) { ListBase old_sockets = verify_lb; + Vector<bNodeSocket *> ordered_old_sockets = old_sockets; BLI_listbase_clear(&verify_lb); LISTBASE_FOREACH (const bNodeSocket *, interface_socket, &interface_sockets) { @@ -193,6 +195,19 @@ static void group_verify_socket_list(bNodeTree &node_tree, LISTBASE_FOREACH_MUTABLE (bNodeSocket *, unused_socket, &old_sockets) { nodeRemoveSocket(&node_tree, &node, unused_socket); } + + { + /* Check if new sockets match the old sockets. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, new_socket, &verify_lb, index) { + if (index < ordered_old_sockets.size()) { + if (ordered_old_sockets[index] != new_socket) { + BKE_ntree_update_tag_interface(&node_tree); + break; + } + } + } + } } void node_group_update(struct bNodeTree *ntree, struct bNode *node) diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index 56e9c9f0496..1833774fe33 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -4,40 +4,34 @@ #include "DEG_depsgraph_query.h" +#include "BKE_curves.hh" #include "BKE_type_conversions.hh" #include "NOD_geometry_exec.hh" #include "node_geometry_util.hh" -using blender::nodes::geometry_nodes_eval_log::LocalGeoLogger; - namespace blender::nodes { void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const { - if (provider_->logger == nullptr) { - return; + if (geo_eval_log::GeoTreeLogger *tree_logger = this->get_local_tree_logger()) { + tree_logger->node_warnings.append({node_.name, {type, std::move(message)}}); } - LocalGeoLogger &local_logger = provider_->logger->local(); - local_logger.log_node_warning(provider_->dnode, type, std::move(message)); } void GeoNodeExecParams::used_named_attribute(std::string attribute_name, - const eNamedAttrUsage usage) + const NamedAttributeUsage usage) { - if (provider_->logger == nullptr) { - return; + if (geo_eval_log::GeoTreeLogger *tree_logger = this->get_local_tree_logger()) { + tree_logger->used_named_attributes.append({node_.name, std::move(attribute_name), usage}); } - LocalGeoLogger &local_logger = provider_->logger->local(); - local_logger.log_used_named_attribute(provider_->dnode, std::move(attribute_name), usage); } void GeoNodeExecParams::check_input_geometry_set(StringRef identifier, const GeometrySet &geometry_set) const { - const SocketDeclaration &decl = - *provider_->dnode->input_by_identifier(identifier).bsocket()->runtime->declaration; + const SocketDeclaration &decl = *node_.input_by_identifier(identifier).runtime->declaration; const decl::Geometry *geo_decl = dynamic_cast<const decl::Geometry *>(&decl); if (geo_decl == nullptr) { return; @@ -94,16 +88,32 @@ void GeoNodeExecParams::check_input_geometry_set(StringRef identifier, message += TIP_("Curve"); break; } + case GEO_COMPONENT_TYPE_EDIT: { + continue; + } } this->error_message_add(NodeWarningType::Info, std::move(message)); } } +void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_set) const +{ + UNUSED_VARS_NDEBUG(geometry_set); +#ifdef DEBUG + if (const bke::CurvesEditHints *curve_edit_hints = + geometry_set.get_curve_edit_hints_for_read()) { + /* If this is not valid, it's likely that the number of stored deformed points does not match + * the number of points in the original data. */ + BLI_assert(curve_edit_hints->is_valid()); + } +#endif +} + const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { - for (const InputSocketRef *socket : provider_->dnode->inputs()) { - if (socket->is_available() && socket->name() == name) { - return socket->bsocket(); + for (const bNodeSocket *socket : node_.input_sockets()) { + if (socket->is_available() && socket->name == name) { + return socket; } } @@ -112,21 +122,21 @@ const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name std::string GeoNodeExecParams::attribute_producer_name() const { - return provider_->dnode->label_or_name() + TIP_(" node"); + return node_.label_or_name() + TIP_(" node"); } void GeoNodeExecParams::set_default_remaining_outputs() { - provider_->set_default_remaining_outputs(); + params_.set_default_remaining_outputs(); } void GeoNodeExecParams::check_input_access(StringRef identifier, const CPPType *requested_type) const { - bNodeSocket *found_socket = nullptr; - for (const InputSocketRef *socket : provider_->dnode->inputs()) { - if (socket->identifier() == identifier) { - found_socket = socket->bsocket(); + const bNodeSocket *found_socket = nullptr; + for (const bNodeSocket *socket : node_.input_sockets()) { + if (socket->identifier == identifier) { + found_socket = socket; break; } } @@ -134,9 +144,9 @@ void GeoNodeExecParams::check_input_access(StringRef identifier, if (found_socket == nullptr) { std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const InputSocketRef *socket : provider_->dnode->inputs()) { + for (const bNodeSocket *socket : node_.input_sockets()) { if (socket->is_available()) { - std::cout << "'" << socket->identifier() << "', "; + std::cout << "'" << socket->identifier << "', "; } } std::cout << "\n"; @@ -147,13 +157,7 @@ void GeoNodeExecParams::check_input_access(StringRef identifier, << "' is disabled.\n"; BLI_assert_unreachable(); } - else if (!provider_->can_get_input(identifier)) { - std::cout << "The identifier '" << identifier - << "' is valid, but there is no value for it anymore.\n"; - std::cout << "Most likely it has been extracted before.\n"; - BLI_assert_unreachable(); - } - else if (requested_type != nullptr) { + else if (requested_type != nullptr && (found_socket->flag & SOCK_MULTI_INPUT) == 0) { const CPPType &expected_type = *found_socket->typeinfo->geometry_nodes_cpp_type; if (*requested_type != expected_type) { std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '" @@ -165,10 +169,10 @@ void GeoNodeExecParams::check_input_access(StringRef identifier, void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const { - bNodeSocket *found_socket = nullptr; - for (const OutputSocketRef *socket : provider_->dnode->outputs()) { - if (socket->identifier() == identifier) { - found_socket = socket->bsocket(); + const bNodeSocket *found_socket = nullptr; + for (const bNodeSocket *socket : node_.output_sockets()) { + if (socket->identifier == identifier) { + found_socket = socket; break; } } @@ -176,9 +180,9 @@ void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType if (found_socket == nullptr) { std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const OutputSocketRef *socket : provider_->dnode->outputs()) { + for (const bNodeSocket *socket : node_.output_sockets()) { if (socket->is_available()) { - std::cout << "'" << socket->identifier() << "', "; + std::cout << "'" << socket->identifier << "', "; } } std::cout << "\n"; @@ -189,7 +193,7 @@ void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType << "' is disabled.\n"; BLI_assert_unreachable(); } - else if (!provider_->can_set_output(identifier)) { + else if (params_.output_was_set(this->get_output_index(identifier))) { std::cout << "The identifier '" << identifier << "' has been set already.\n"; BLI_assert_unreachable(); } diff --git a/source/blender/nodes/intern/node_multi_function.cc b/source/blender/nodes/intern/node_multi_function.cc index 13bfdfbfac1..d731fe8f877 100644 --- a/source/blender/nodes/intern/node_multi_function.cc +++ b/source/blender/nodes/intern/node_multi_function.cc @@ -2,22 +2,22 @@ #include "NOD_multi_function.hh" +#include "BKE_node.h" +#include "BKE_node_runtime.hh" + namespace blender::nodes { -NodeMultiFunctions::NodeMultiFunctions(const DerivedNodeTree &tree) +NodeMultiFunctions::NodeMultiFunctions(const bNodeTree &tree) { - for (const NodeTreeRef *tree_ref : tree.used_node_tree_refs()) { - bNodeTree *btree = tree_ref->btree(); - for (const NodeRef *node : tree_ref->nodes()) { - bNode *bnode = node->bnode(); - if (bnode->typeinfo->build_multi_function == nullptr) { - continue; - } - NodeMultiFunctionBuilder builder{*bnode, *btree}; - bnode->typeinfo->build_multi_function(builder); - if (builder.built_fn_ != nullptr) { - map_.add_new(bnode, {builder.built_fn_, std::move(builder.owned_built_fn_)}); - } + tree.ensure_topology_cache(); + for (const bNode *bnode : tree.all_nodes()) { + if (bnode->typeinfo->build_multi_function == nullptr) { + continue; + } + NodeMultiFunctionBuilder builder{*bnode, tree}; + bnode->typeinfo->build_multi_function(builder); + if (builder.built_fn_ != nullptr) { + map_.add_new(bnode, {builder.built_fn_, std::move(builder.owned_built_fn_)}); } } } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc deleted file mode 100644 index 64a8690a869..00000000000 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ /dev/null @@ -1,678 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include <mutex> - -#include "NOD_node_tree_ref.hh" - -#include "BLI_dot_export.hh" -#include "BLI_stack.hh" - -#include "RNA_prototypes.h" - -namespace blender::nodes { - -NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) -{ - Map<bNode *, NodeRef *> node_mapping; - - LISTBASE_FOREACH (bNode *, bnode, &btree->nodes) { - NodeRef &node = *allocator_.construct<NodeRef>().release(); - - node.tree_ = this; - node.bnode_ = bnode; - node.id_ = nodes_by_id_.append_and_get_index(&node); - - LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->inputs) { - InputSocketRef &socket = *allocator_.construct<InputSocketRef>().release(); - socket.node_ = &node; - socket.index_ = node.inputs_.append_and_get_index(&socket); - socket.is_input_ = true; - socket.bsocket_ = bsocket; - socket.id_ = sockets_by_id_.append_and_get_index(&socket); - } - - LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->outputs) { - OutputSocketRef &socket = *allocator_.construct<OutputSocketRef>().release(); - socket.node_ = &node; - socket.index_ = node.outputs_.append_and_get_index(&socket); - socket.is_input_ = false; - socket.bsocket_ = bsocket; - socket.id_ = sockets_by_id_.append_and_get_index(&socket); - } - - LISTBASE_FOREACH (bNodeLink *, blink, &bnode->internal_links) { - InternalLinkRef &internal_link = *allocator_.construct<InternalLinkRef>().release(); - internal_link.blink_ = blink; - for (InputSocketRef *socket_ref : node.inputs_) { - if (socket_ref->bsocket_ == blink->fromsock) { - internal_link.from_ = socket_ref; - break; - } - } - for (OutputSocketRef *socket_ref : node.outputs_) { - if (socket_ref->bsocket_ == blink->tosock) { - internal_link.to_ = socket_ref; - break; - } - } - BLI_assert(internal_link.from_ != nullptr); - BLI_assert(internal_link.to_ != nullptr); - node.internal_links_.append(&internal_link); - } - - input_sockets_.extend(node.inputs_.as_span()); - output_sockets_.extend(node.outputs_.as_span()); - - node_mapping.add_new(bnode, &node); - } - - LISTBASE_FOREACH (bNodeLink *, blink, &btree->links) { - OutputSocketRef &from_socket = this->find_output_socket( - node_mapping, blink->fromnode, blink->fromsock); - InputSocketRef &to_socket = this->find_input_socket( - node_mapping, blink->tonode, blink->tosock); - - LinkRef &link = *allocator_.construct<LinkRef>().release(); - link.from_ = &from_socket; - link.to_ = &to_socket; - link.blink_ = blink; - - links_.append(&link); - - from_socket.directly_linked_links_.append(&link); - to_socket.directly_linked_links_.append(&link); - } - - for (InputSocketRef *input_socket : input_sockets_) { - if (input_socket->is_multi_input_socket()) { - std::sort(input_socket->directly_linked_links_.begin(), - input_socket->directly_linked_links_.end(), - [&](const LinkRef *a, const LinkRef *b) -> bool { - int index_a = a->blink()->multi_input_socket_index; - int index_b = b->blink()->multi_input_socket_index; - return index_a > index_b; - }); - } - } - - this->create_socket_identifier_maps(); - this->create_linked_socket_caches(); - - for (NodeRef *node : nodes_by_id_) { - const bNodeType *nodetype = node->bnode_->typeinfo; - nodes_by_type_.add(nodetype, node); - } - - const Span<const NodeRef *> group_output_nodes = this->nodes_by_type("NodeGroupOutput"); - if (group_output_nodes.is_empty()) { - group_output_node_ = nullptr; - } - else if (group_output_nodes.size() == 1) { - group_output_node_ = group_output_nodes.first(); - } - else { - for (const NodeRef *group_output : group_output_nodes) { - if (group_output->bnode_->flag & NODE_DO_OUTPUT) { - group_output_node_ = group_output; - break; - } - } - } -} - -NodeTreeRef::~NodeTreeRef() -{ - /* The destructor has to be called manually, because these types are allocated in a linear - * allocator. */ - for (NodeRef *node : nodes_by_id_) { - node->~NodeRef(); - } - for (InputSocketRef *socket : input_sockets_) { - socket->~InputSocketRef(); - } - for (OutputSocketRef *socket : output_sockets_) { - socket->~OutputSocketRef(); - } - for (LinkRef *link : links_) { - link->~LinkRef(); - } -} - -InputSocketRef &NodeTreeRef::find_input_socket(Map<bNode *, NodeRef *> &node_mapping, - bNode *bnode, - bNodeSocket *bsocket) -{ - NodeRef *node = node_mapping.lookup(bnode); - for (InputSocketRef *socket : node->inputs_) { - if (socket->bsocket_ == bsocket) { - return *socket; - } - } - BLI_assert_unreachable(); - return *node->inputs_[0]; -} - -OutputSocketRef &NodeTreeRef::find_output_socket(Map<bNode *, NodeRef *> &node_mapping, - bNode *bnode, - bNodeSocket *bsocket) -{ - NodeRef *node = node_mapping.lookup(bnode); - for (OutputSocketRef *socket : node->outputs_) { - if (socket->bsocket_ == bsocket) { - return *socket; - } - } - BLI_assert_unreachable(); - return *node->outputs_[0]; -} - -void NodeTreeRef::create_linked_socket_caches() -{ - for (InputSocketRef *socket : input_sockets_) { - /* Find directly linked socket based on incident links. */ - Vector<const SocketRef *> directly_linked_sockets; - for (LinkRef *link : socket->directly_linked_links_) { - directly_linked_sockets.append(link->from_); - } - socket->directly_linked_sockets_ = allocator_.construct_array_copy( - directly_linked_sockets.as_span()); - - /* Find logically linked sockets. */ - Vector<const SocketRef *> logically_linked_sockets; - Vector<const SocketRef *> logically_linked_skipped_sockets; - Vector<const InputSocketRef *> seen_sockets_stack; - socket->foreach_logical_origin( - [&](const OutputSocketRef &origin) { logically_linked_sockets.append(&origin); }, - [&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); }, - false, - seen_sockets_stack); - if (logically_linked_sockets == directly_linked_sockets) { - socket->logically_linked_sockets_ = socket->directly_linked_sockets_; - } - else { - socket->logically_linked_sockets_ = allocator_.construct_array_copy( - logically_linked_sockets.as_span()); - } - socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy( - logically_linked_skipped_sockets.as_span()); - } - - for (OutputSocketRef *socket : output_sockets_) { - /* Find directly linked socket based on incident links. */ - Vector<const SocketRef *> directly_linked_sockets; - for (LinkRef *link : socket->directly_linked_links_) { - directly_linked_sockets.append(link->to_); - } - socket->directly_linked_sockets_ = allocator_.construct_array_copy( - directly_linked_sockets.as_span()); - - /* Find logically linked sockets. */ - Vector<const SocketRef *> logically_linked_sockets; - Vector<const SocketRef *> logically_linked_skipped_sockets; - Vector<const OutputSocketRef *> handled_sockets; - socket->foreach_logical_target( - [&](const InputSocketRef &target) { logically_linked_sockets.append(&target); }, - [&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); }, - handled_sockets); - if (logically_linked_sockets == directly_linked_sockets) { - socket->logically_linked_sockets_ = socket->directly_linked_sockets_; - } - else { - socket->logically_linked_sockets_ = allocator_.construct_array_copy( - logically_linked_sockets.as_span()); - } - socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy( - logically_linked_skipped_sockets.as_span()); - } -} - -void InputSocketRef::foreach_logical_origin( - FunctionRef<void(const OutputSocketRef &)> origin_fn, - FunctionRef<void(const SocketRef &)> skipped_fn, - bool only_follow_first_input_link, - Vector<const InputSocketRef *> &seen_sockets_stack) const -{ - /* Protect against loops. */ - if (seen_sockets_stack.contains(this)) { - return; - } - seen_sockets_stack.append(this); - - Span<const LinkRef *> links_to_check = this->directly_linked_links(); - if (only_follow_first_input_link) { - links_to_check = links_to_check.take_front(1); - } - for (const LinkRef *link : links_to_check) { - if (link->is_muted()) { - continue; - } - const OutputSocketRef &origin = link->from(); - const NodeRef &origin_node = origin.node(); - if (!origin.is_available()) { - /* Non available sockets are ignored. */ - } - else if (origin_node.is_reroute_node()) { - const InputSocketRef &reroute_input = origin_node.input(0); - const OutputSocketRef &reroute_output = origin_node.output(0); - skipped_fn.call_safe(reroute_input); - skipped_fn.call_safe(reroute_output); - reroute_input.foreach_logical_origin(origin_fn, skipped_fn, false, seen_sockets_stack); - } - else if (origin_node.is_muted()) { - for (const InternalLinkRef *internal_link : origin_node.internal_links()) { - if (&internal_link->to() == &origin) { - const InputSocketRef &mute_input = internal_link->from(); - skipped_fn.call_safe(origin); - skipped_fn.call_safe(mute_input); - mute_input.foreach_logical_origin(origin_fn, skipped_fn, true, seen_sockets_stack); - } - } - } - else { - origin_fn(origin); - } - } - - seen_sockets_stack.pop_last(); -} - -void OutputSocketRef::foreach_logical_target( - FunctionRef<void(const InputSocketRef &)> target_fn, - FunctionRef<void(const SocketRef &)> skipped_fn, - Vector<const OutputSocketRef *> &seen_sockets_stack) const -{ - /* Protect against loops. */ - if (seen_sockets_stack.contains(this)) { - return; - } - seen_sockets_stack.append(this); - - for (const LinkRef *link : this->directly_linked_links()) { - if (link->is_muted()) { - continue; - } - const InputSocketRef &target = link->to(); - const NodeRef &target_node = target.node(); - if (!target.is_available()) { - /* Non available sockets are ignored. */ - } - else if (target_node.is_reroute_node()) { - const OutputSocketRef &reroute_output = target_node.output(0); - skipped_fn.call_safe(target); - skipped_fn.call_safe(reroute_output); - reroute_output.foreach_logical_target(target_fn, skipped_fn, seen_sockets_stack); - } - else if (target_node.is_muted()) { - skipped_fn.call_safe(target); - for (const InternalLinkRef *internal_link : target_node.internal_links()) { - if (&internal_link->from() == &target) { - /* The internal link only forwards the first incoming link. */ - if (target.is_multi_input_socket()) { - if (target.directly_linked_links()[0] != link) { - continue; - } - } - const OutputSocketRef &mute_output = internal_link->to(); - skipped_fn.call_safe(target); - skipped_fn.call_safe(mute_output); - mute_output.foreach_logical_target(target_fn, skipped_fn, seen_sockets_stack); - } - } - } - else { - target_fn(target); - } - } - - seen_sockets_stack.pop_last(); -} - -namespace { -struct SocketByIdentifierMap { - SocketIndexByIdentifierMap *map = nullptr; - std::unique_ptr<SocketIndexByIdentifierMap> owned_map; -}; -} // namespace - -static std::unique_ptr<SocketIndexByIdentifierMap> create_identifier_map(const ListBase &sockets) -{ - std::unique_ptr<SocketIndexByIdentifierMap> map = std::make_unique<SocketIndexByIdentifierMap>(); - int index; - LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &sockets, index) { - map->add_new(socket->identifier, index); - } - return map; -} - -/* This function is not threadsafe. */ -static SocketByIdentifierMap get_or_create_identifier_map( - const bNode &node, const ListBase &sockets, const bNodeSocketTemplate *sockets_template) -{ - SocketByIdentifierMap map; - if (sockets_template == nullptr) { - if (BLI_listbase_is_empty(&sockets)) { - static SocketIndexByIdentifierMap empty_map; - map.map = &empty_map; - } - else if (node.type == NODE_REROUTE) { - if (&node.inputs == &sockets) { - static SocketIndexByIdentifierMap reroute_input_map = [] { - SocketIndexByIdentifierMap map; - map.add_new("Input", 0); - return map; - }(); - map.map = &reroute_input_map; - } - else { - static SocketIndexByIdentifierMap reroute_output_map = [] { - SocketIndexByIdentifierMap map; - map.add_new("Output", 0); - return map; - }(); - map.map = &reroute_output_map; - } - } - else { - /* The node has a dynamic amount of sockets. Therefore we need to create a new map. */ - map.owned_map = create_identifier_map(sockets); - map.map = &*map.owned_map; - } - } - else { - /* Cache only one map for nodes that have the same sockets. */ - static Map<const bNodeSocketTemplate *, std::unique_ptr<SocketIndexByIdentifierMap>> maps; - map.map = &*maps.lookup_or_add_cb(sockets_template, - [&]() { return create_identifier_map(sockets); }); - } - return map; -} - -void NodeTreeRef::create_socket_identifier_maps() -{ - /* `get_or_create_identifier_map` is not threadsafe, therefore we have to hold a lock here. */ - static std::mutex mutex; - std::lock_guard lock{mutex}; - - for (NodeRef *node : nodes_by_id_) { - bNode &bnode = *node->bnode_; - SocketByIdentifierMap inputs_map = get_or_create_identifier_map( - bnode, bnode.inputs, bnode.typeinfo->inputs); - SocketByIdentifierMap outputs_map = get_or_create_identifier_map( - bnode, bnode.outputs, bnode.typeinfo->outputs); - node->input_index_by_identifier_ = inputs_map.map; - node->output_index_by_identifier_ = outputs_map.map; - if (inputs_map.owned_map) { - owned_identifier_maps_.append(std::move(inputs_map.owned_map)); - } - if (outputs_map.owned_map) { - owned_identifier_maps_.append(std::move(outputs_map.owned_map)); - } - } -} - -static bool has_link_cycles_recursive(const NodeRef &node, - MutableSpan<bool> visited, - MutableSpan<bool> is_in_stack) -{ - const int node_id = node.id(); - if (is_in_stack[node_id]) { - return true; - } - if (visited[node_id]) { - return false; - } - - visited[node_id] = true; - is_in_stack[node_id] = true; - - for (const OutputSocketRef *from_socket : node.outputs()) { - if (!from_socket->is_available()) { - continue; - } - for (const InputSocketRef *to_socket : from_socket->directly_linked_sockets()) { - if (!to_socket->is_available()) { - continue; - } - const NodeRef &to_node = to_socket->node(); - if (has_link_cycles_recursive(to_node, visited, is_in_stack)) { - return true; - } - } - } - - is_in_stack[node_id] = false; - return false; -} - -bool NodeTreeRef::has_link_cycles() const -{ - const int node_amount = nodes_by_id_.size(); - Array<bool> visited(node_amount, false); - Array<bool> is_in_stack(node_amount, false); - - for (const NodeRef *node : nodes_by_id_) { - if (has_link_cycles_recursive(*node, visited, is_in_stack)) { - return true; - } - } - return false; -} - -bool NodeTreeRef::has_undefined_nodes_or_sockets() const -{ - for (const NodeRef *node : nodes_by_id_) { - if (node->is_undefined()) { - return true; - } - } - for (const SocketRef *socket : sockets_by_id_) { - if (socket->is_undefined()) { - return true; - } - } - return false; -} - -bool NodeRef::any_input_is_directly_linked() const -{ - for (const SocketRef *socket : inputs_) { - if (!socket->directly_linked_sockets().is_empty()) { - return true; - } - } - return false; -} - -bool NodeRef::any_output_is_directly_linked() const -{ - for (const SocketRef *socket : outputs_) { - if (!socket->directly_linked_sockets().is_empty()) { - return true; - } - } - return false; -} - -bool NodeRef::any_socket_is_directly_linked(eNodeSocketInOut in_out) const -{ - if (in_out == SOCK_IN) { - return this->any_input_is_directly_linked(); - } - return this->any_output_is_directly_linked(); -} - -struct ToposortNodeState { - bool is_done = false; - bool is_in_stack = false; -}; - -static void toposort_from_start_node(const NodeTreeRef::ToposortDirection direction, - const NodeRef &start_node, - MutableSpan<ToposortNodeState> node_states, - NodeTreeRef::ToposortResult &result) -{ - struct Item { - const NodeRef *node; - /* Index of the next socket that is checked in the depth-first search. */ - int socket_index = 0; - /* Link index in the next socket that is checked in the depth-first search. */ - int link_index = 0; - }; - - /* Do a depth-first search to sort nodes topologically. */ - Stack<Item, 64> nodes_to_check; - nodes_to_check.push({&start_node}); - while (!nodes_to_check.is_empty()) { - Item &item = nodes_to_check.peek(); - const NodeRef &node = *item.node; - const Span<const SocketRef *> sockets = node.sockets( - direction == NodeTreeRef::ToposortDirection::LeftToRight ? SOCK_IN : SOCK_OUT); - - while (true) { - if (item.socket_index == sockets.size()) { - /* All sockets have already been visited. */ - break; - } - const SocketRef &socket = *sockets[item.socket_index]; - const Span<const SocketRef *> linked_sockets = socket.directly_linked_sockets(); - if (item.link_index == linked_sockets.size()) { - /* All links connected to this socket have already been visited. */ - item.socket_index++; - item.link_index = 0; - continue; - } - const SocketRef &linked_socket = *linked_sockets[item.link_index]; - const NodeRef &linked_node = linked_socket.node(); - ToposortNodeState &linked_node_state = node_states[linked_node.id()]; - if (linked_node_state.is_done) { - /* The linked node has already been visited. */ - item.link_index++; - continue; - } - if (linked_node_state.is_in_stack) { - result.has_cycle = true; - } - else { - nodes_to_check.push({&linked_node}); - linked_node_state.is_in_stack = true; - } - break; - } - - /* If no other element has been pushed, the current node can be pushed to the sorted list. */ - if (&item == &nodes_to_check.peek()) { - ToposortNodeState &node_state = node_states[node.id()]; - node_state.is_done = true; - node_state.is_in_stack = false; - result.sorted_nodes.append(&node); - nodes_to_check.pop(); - } - } -} - -NodeTreeRef::ToposortResult NodeTreeRef::toposort(const ToposortDirection direction) const -{ - ToposortResult result; - result.sorted_nodes.reserve(nodes_by_id_.size()); - - Array<ToposortNodeState> node_states(nodes_by_id_.size()); - - for (const NodeRef *node : nodes_by_id_) { - if (node_states[node->id()].is_done) { - /* Ignore nodes that are done already. */ - continue; - } - if (node->any_socket_is_directly_linked( - direction == ToposortDirection::LeftToRight ? SOCK_OUT : SOCK_IN)) { - /* Ignore non-start nodes. */ - continue; - } - - toposort_from_start_node(direction, *node, node_states, result); - } - - /* Check if the loop above forgot some nodes because there is a cycle. */ - if (result.sorted_nodes.size() < nodes_by_id_.size()) { - result.has_cycle = true; - for (const NodeRef *node : nodes_by_id_) { - if (node_states[node->id()].is_done) { - /* Ignore nodes that are done already. */ - continue; - } - /* Start toposort at this node which is somewhere in the middle of a loop. */ - toposort_from_start_node(direction, *node, node_states, result); - } - } - - BLI_assert(result.sorted_nodes.size() == nodes_by_id_.size()); - return result; -} - -const NodeRef *NodeTreeRef::find_node(const bNode &bnode) const -{ - for (const NodeRef *node : this->nodes_by_type(bnode.typeinfo)) { - if (node->bnode_ == &bnode) { - return node; - } - } - return nullptr; -} - -std::string NodeTreeRef::to_dot() const -{ - dot::DirectedGraph digraph; - digraph.set_rankdir(dot::Attr_rankdir::LeftToRight); - - Map<const NodeRef *, dot::NodeWithSocketsRef> dot_nodes; - - for (const NodeRef *node : nodes_by_id_) { - dot::Node &dot_node = digraph.new_node(""); - dot_node.set_background_color("white"); - - Vector<std::string> input_names; - Vector<std::string> output_names; - for (const InputSocketRef *socket : node->inputs()) { - input_names.append(socket->name()); - } - for (const OutputSocketRef *socket : node->outputs()) { - output_names.append(socket->name()); - } - - dot_nodes.add_new(node, - dot::NodeWithSocketsRef(dot_node, node->name(), input_names, output_names)); - } - - for (const OutputSocketRef *from_socket : output_sockets_) { - for (const InputSocketRef *to_socket : from_socket->directly_linked_sockets()) { - dot::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&from_socket->node()); - dot::NodeWithSocketsRef &to_dot_node = dot_nodes.lookup(&to_socket->node()); - - digraph.new_edge(from_dot_node.output(from_socket->index()), - to_dot_node.input(to_socket->index())); - } - } - - return digraph.to_dot_string(); -} - -const NodeTreeRef &get_tree_ref_from_map(NodeTreeRefMap &node_tree_refs, bNodeTree &btree) -{ - return *node_tree_refs.lookup_or_add_cb(&btree, - [&]() { return std::make_unique<NodeTreeRef>(&btree); }); -} - -PointerRNA NodeRef::rna() const -{ - PointerRNA rna; - RNA_pointer_create(&tree_->btree()->id, &RNA_Node, bnode_, &rna); - return rna; -} - -PointerRNA SocketRef::rna() const -{ - PointerRNA rna; - RNA_pointer_create(&this->tree().btree()->id, &RNA_NodeSocket, bsocket_, &rna); - return rna; -} - -} // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_util.c b/source/blender/nodes/intern/node_util.c index e8be093c606..ddab455509d 100644 --- a/source/blender/nodes/intern/node_util.c +++ b/source/blender/nodes/intern/node_util.c @@ -200,7 +200,7 @@ void node_math_label(const bNodeTree *UNUSED(ntree), const bNode *node, char *la if (!enum_label) { name = "Unknown"; } - BLI_strncpy(label, IFACE_(name), maxlen); + BLI_strncpy(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), maxlen); } void node_vector_math_label(const bNodeTree *UNUSED(ntree), |