From b55bddde40db3eda3531d98caa99be9a8e88a8ee Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 3 Nov 2021 10:54:17 +0100 Subject: [PATCH] Fix T91862: do type conversion when data enters or exists node group The geometry node evaluator now has access to the entire socket path from the node that produces a value to the node that uses it. This allows the evaluator to make decisions about at which points in the path the value should be converted. Multiple conversions may be necessary under some circumstances with nested node groups. Differential Revision: https://developer.blender.org/D13034 --- .../modifiers/intern/MOD_nodes_evaluator.cc | 111 +++++++++--------- source/blender/nodes/NOD_derived_node_tree.hh | 15 ++- .../blender/nodes/intern/derived_node_tree.cc | 95 +++++++++++---- 3 files changed, 137 insertions(+), 84 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index a312872f5d9..70d2bd9c7f5 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -568,15 +568,15 @@ class GeometryNodesEvaluator { } /* Count the number of potential users for this socket. */ socket.foreach_target_socket( - [&, this](const DInputSocket target_socket) { + [&, this](const DInputSocket target_socket, + const DOutputSocket::TargetSocketPathInfo &UNUSED(path_info)) { const DNode target_node = target_socket.node(); if (!this->node_states_.contains_as(target_node)) { /* The target node is not computed because it is not computed to the output. */ return; } output_state.potential_users += 1; - }, - {}); + }); if (output_state.potential_users == 0) { /* If it does not have any potential users, it is unused. It might become required again in * `schedule_initial_nodes`. */ @@ -1257,43 +1257,61 @@ class GeometryNodesEvaluator { { BLI_assert(value_to_forward.get() != nullptr); - Vector sockets_to_log_to; - sockets_to_log_to.append(from_socket); - - Vector to_sockets; - auto handle_target_socket_fn = [&, this](const DInputSocket to_socket) { - if (this->should_forward_to_socket(to_socket)) { - to_sockets.append(to_socket); - } - }; - auto handle_skipped_socket_fn = [&](const DSocket socket) { - sockets_to_log_to.append(socket); - }; - from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); - LinearAllocator<> &allocator = local_allocators_.local(); - const CPPType &from_type = *value_to_forward.type(); - Vector to_sockets_same_type; - for (const DInputSocket &to_socket : to_sockets) { - const CPPType &to_type = *get_socket_cpp_type(to_socket); - if (from_type == to_type) { - /* All target sockets that do not need a conversion will be handled afterwards. */ - to_sockets_same_type.append(to_socket); - /* Multi input socket values are logged once all values are available. */ - if (!to_socket->is_multi_input_socket()) { - sockets_to_log_to.append(to_socket); - } - continue; - } - this->forward_to_socket_with_different_type( - allocator, value_to_forward, from_socket, to_socket, to_type); - } - - this->log_socket_value(sockets_to_log_to, value_to_forward); + Vector log_original_value_sockets; + Vector forward_original_value_sockets; + log_original_value_sockets.append(from_socket); + from_socket.foreach_target_socket( + [&](const DInputSocket to_socket, const DOutputSocket::TargetSocketPathInfo &path_info) { + if (!this->should_forward_to_socket(to_socket)) { + return; + } + BLI_assert(to_socket == path_info.sockets.last()); + GMutablePointer current_value = value_to_forward; + for (const DSocket &next_socket : path_info.sockets) { + const DNode next_node = next_socket.node(); + const bool is_last_socket = to_socket == next_socket; + const bool do_conversion_if_necessary = is_last_socket || + next_node->is_group_output_node() || + (next_node->is_group_node() && + !next_node->is_muted()); + if (do_conversion_if_necessary) { + const CPPType &next_type = *get_socket_cpp_type(next_socket); + if (*current_value.type() != next_type) { + void *buffer = allocator.allocate(next_type.size(), next_type.alignment()); + this->convert_value(*current_value.type(), next_type, current_value.get(), buffer); + if (current_value.get() != value_to_forward.get()) { + current_value.destruct(); + } + current_value = {next_type, buffer}; + } + } + if (current_value.get() == value_to_forward.get()) { + /* Log the original value at the current socket. */ + log_original_value_sockets.append(next_socket); + } + else { + /* Multi-input sockets are logged when all values are available. */ + if (!(next_socket->is_input() && next_socket->as_input().is_multi_input_socket())) { + /* Log the converted value at the socket. */ + this->log_socket_value({next_socket}, current_value); + } + } + } + if (current_value.get() == value_to_forward.get()) { + /* The value has not been converted, so forward the original value. */ + forward_original_value_sockets.append(to_socket); + } + else { + /* The value has been converted. */ + this->add_value_to_input_socket(to_socket, from_socket, current_value); + } + }); + this->log_socket_value(log_original_value_sockets, value_to_forward); this->forward_to_sockets_with_same_type( - allocator, to_sockets_same_type, value_to_forward, from_socket); + allocator, forward_original_value_sockets, value_to_forward, from_socket); } bool should_forward_to_socket(const DInputSocket socket) @@ -1312,27 +1330,6 @@ class GeometryNodesEvaluator { return target_input_state.usage != ValueUsage::Unused; } - void forward_to_socket_with_different_type(LinearAllocator<> &allocator, - const GPointer value_to_forward, - const DOutputSocket from_socket, - const DInputSocket to_socket, - const CPPType &to_type) - { - const CPPType &from_type = *value_to_forward.type(); - - /* Allocate a buffer for the converted value. */ - void *buffer = allocator.allocate(to_type.size(), to_type.alignment()); - GMutablePointer value{to_type, buffer}; - - this->convert_value(from_type, to_type, value_to_forward.get(), buffer); - - /* Multi input socket values are logged once all values are available. */ - if (!to_socket->is_multi_input_socket()) { - this->log_socket_value({to_socket}, value); - } - this->add_value_to_input_socket(to_socket, from_socket, value); - } - void forward_to_sockets_with_same_type(LinearAllocator<> &allocator, Span to_sockets, GMutablePointer value_to_forward, diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index e903e3c9255..895f7ef6d5b 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -158,8 +158,19 @@ class DOutputSocket : public DSocket { DInputSocket get_corresponding_group_node_input() const; DInputSocket get_active_corresponding_group_output_socket() const; - void foreach_target_socket(FunctionRef target_fn, - FunctionRef skipped_fn) const; + struct TargetSocketPathInfo { + /** All sockets on the path from the current to the final target sockets, excluding `this`. */ + Vector sockets; + }; + + using ForeachTargetSocketFn = + FunctionRef; + + void foreach_target_socket(ForeachTargetSocketFn target_fn) const; + + private: + void foreach_target_socket(ForeachTargetSocketFn target_fn, + TargetSocketPathInfo &path_info) const; }; class DerivedNodeTree { diff --git a/source/blender/nodes/intern/derived_node_tree.cc b/source/blender/nodes/intern/derived_node_tree.cc index f7279cf7524..fb12157f147 100644 --- a/source/blender/nodes/intern/derived_node_tree.cc +++ b/source/blender/nodes/intern/derived_node_tree.cc @@ -231,45 +231,90 @@ void DInputSocket::foreach_origin_socket(FunctionRef origin_fn) c /* Calls `target_fn` for every "real" target socket. "Real" means that reroutes, muted nodes * and node groups are handled by this function. Target sockets are on the nodes that use the value - * from this socket. The `skipped_fn` function is called for sockets that have been skipped during - * the search for target sockets (e.g. reroutes). */ -void DOutputSocket::foreach_target_socket(FunctionRef target_fn, - FunctionRef skipped_fn) const + * from this socket. */ +void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn) const { - for (const SocketRef *skipped_socket : socket_ref_->logically_linked_skipped_sockets()) { - skipped_fn.call_safe({context_, skipped_socket}); - } - for (const InputSocketRef *linked_socket : socket_ref_->as_output().logically_linked_sockets()) { - const NodeRef &linked_node = linked_socket->node(); - DInputSocket linked_dsocket{context_, linked_socket}; + TargetSocketPathInfo path_info; + this->foreach_target_socket(target_fn, path_info); +} - if (linked_node.is_group_output_node()) { +void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn, + TargetSocketPathInfo &path_info) const +{ + for (const LinkRef *link : socket_ref_->as_output().directly_linked_links()) { + if (link->is_muted()) { + continue; + } + const DInputSocket &linked_socket{context_, &link->to()}; + if (!linked_socket->is_available()) { + continue; + } + const DNode linked_node = linked_socket.node(); + if (linked_node->is_reroute_node()) { + const DInputSocket reroute_input = linked_socket; + const DOutputSocket reroute_output = linked_node.output(0); + path_info.sockets.append(reroute_input); + path_info.sockets.append(reroute_output); + reroute_output.foreach_target_socket(target_fn, path_info); + path_info.sockets.pop_last(); + 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()) { + continue; + } + /* The internal link only forwards the first incoming link. */ + if (linked_socket->is_multi_input_socket()) { + if (linked_socket->directly_linked_links()[0] == link) { + continue; + } + } + const DInputSocket mute_input = linked_socket; + const DOutputSocket mute_output{context_, &internal_link->to()}; + path_info.sockets.append(mute_input); + path_info.sockets.append(mute_output); + mute_output.foreach_target_socket(target_fn, path_info); + path_info.sockets.pop_last(); + path_info.sockets.pop_last(); + break; + } + } + else if (linked_node->is_group_output_node()) { if (context_->is_root()) { /* This is a group output in the root node group. */ - target_fn(linked_dsocket); + path_info.sockets.append(linked_socket); + target_fn(linked_socket, path_info); + path_info.sockets.pop_last(); } else { /* Follow the links going out of the group node in the parent node group. */ - DOutputSocket socket_in_parent_group = - linked_dsocket.get_corresponding_group_node_output(); - skipped_fn.call_safe(linked_dsocket); - skipped_fn.call_safe(socket_in_parent_group); - socket_in_parent_group.foreach_target_socket(target_fn, skipped_fn); + const DOutputSocket socket_in_parent_group = + linked_socket.get_corresponding_group_node_output(); + path_info.sockets.append(linked_socket); + path_info.sockets.append(socket_in_parent_group); + socket_in_parent_group.foreach_target_socket(target_fn, path_info); + path_info.sockets.pop_last(); + path_info.sockets.pop_last(); } } - else if (linked_node.is_group_node()) { + else if (linked_node->is_group_node()) { /* Follow the links within the nested node group. */ - Vector sockets_in_group = - linked_dsocket.get_corresponding_group_input_sockets(); - skipped_fn.call_safe(linked_dsocket); - for (DOutputSocket socket_in_group : sockets_in_group) { - skipped_fn.call_safe(socket_in_group); - socket_in_group.foreach_target_socket(target_fn, skipped_fn); + path_info.sockets.append(linked_socket); + const Vector sockets_in_group = + linked_socket.get_corresponding_group_input_sockets(); + for (const DOutputSocket &socket_in_group : sockets_in_group) { + path_info.sockets.append(socket_in_group); + socket_in_group.foreach_target_socket(target_fn, path_info); + path_info.sockets.pop_last(); } + path_info.sockets.pop_last(); } else { /* The normal case: just use the linked input socket as target. */ - target_fn(linked_dsocket); + path_info.sockets.append(linked_socket); + target_fn(linked_socket, path_info); + path_info.sockets.pop_last(); } } }