From 908bb0363062168e16c608e13b9340724510e2cd Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 27 Apr 2021 13:03:40 +0200 Subject: Geometry Nodes: improve geometry nodes evaluator internal api This is a first step towards T87620. It should not have any functional changes. Goals of this refactor: * Move the evaluator out of `MOD_nodes.cc`. That makes it easier to improve it in isolation. * Extract core input/out parameter management out of `GeoNodeExecParams`. Managing this is the responsibility of the evaluator. This separation of concerns will be useful once we have lazy evaluation of certain inputs/outputs. Differential Revision: https://developer.blender.org/D11085 --- source/blender/functions/FN_cpp_type.hh | 2 +- source/blender/functions/FN_generic_pointer.hh | 10 + source/blender/functions/FN_generic_value_map.hh | 5 + source/blender/modifiers/CMakeLists.txt | 2 + source/blender/modifiers/intern/MOD_nodes.cc | 395 +----------------- .../modifiers/intern/MOD_nodes_evaluator.cc | 452 +++++++++++++++++++++ .../modifiers/intern/MOD_nodes_evaluator.hh | 56 +++ source/blender/nodes/NOD_geometry_exec.hh | 142 +++---- source/blender/nodes/intern/node_geometry_exec.cc | 42 +- 9 files changed, 633 insertions(+), 473 deletions(-) create mode 100644 source/blender/modifiers/intern/MOD_nodes_evaluator.cc create mode 100644 source/blender/modifiers/intern/MOD_nodes_evaluator.hh (limited to 'source/blender') diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh index 54ea0103fe5..cd1597a742c 100644 --- a/source/blender/functions/FN_cpp_type.hh +++ b/source/blender/functions/FN_cpp_type.hh @@ -666,7 +666,7 @@ class CPPType : NonCopyable, NonMovable { template bool is() const { - return this == &CPPType::get(); + return this == &CPPType::get>(); } }; diff --git a/source/blender/functions/FN_generic_pointer.hh b/source/blender/functions/FN_generic_pointer.hh index 2bd66daa7fe..f88ff09f916 100644 --- a/source/blender/functions/FN_generic_pointer.hh +++ b/source/blender/functions/FN_generic_pointer.hh @@ -66,6 +66,16 @@ class GMutablePointer { return type_ != nullptr && type_->is(); } + template T relocate_out() + { + BLI_assert(this->is_type()); + T value; + type_->relocate_to_initialized(data_, &value); + data_ = nullptr; + type_ = nullptr; + return value; + } + void destruct() { BLI_assert(data_ != nullptr); diff --git a/source/blender/functions/FN_generic_value_map.hh b/source/blender/functions/FN_generic_value_map.hh index 68cb945f1af..4e7fe298874 100644 --- a/source/blender/functions/FN_generic_value_map.hh +++ b/source/blender/functions/FN_generic_value_map.hh @@ -93,6 +93,11 @@ template class GValueMap { return values_.pop_as(key); } + template GPointer lookup(const ForwardKey &key) const + { + return values_.lookup_as(key); + } + /* Remove the value for the given name from the container and remove it. */ template T extract(const ForwardKey &key) { diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 54caaed9231..6ac2629c006 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC intern/MOD_mirror.c intern/MOD_multires.c intern/MOD_nodes.cc + intern/MOD_nodes_evaluator.cc intern/MOD_none.c intern/MOD_normal_edit.c intern/MOD_ocean.c @@ -118,6 +119,7 @@ set(SRC MOD_modifiertypes.h MOD_nodes.h intern/MOD_meshcache_util.h + intern/MOD_nodes_evaluator.hh intern/MOD_solidify_util.h intern/MOD_ui_common.h intern/MOD_util.h diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index cc8f4a544c0..bc045b39904 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -75,16 +75,14 @@ #include "MOD_modifiertypes.h" #include "MOD_nodes.h" +#include "MOD_nodes_evaluator.hh" #include "MOD_ui_common.h" #include "ED_spreadsheet.h" #include "NOD_derived_node_tree.hh" #include "NOD_geometry.h" -#include "NOD_geometry_exec.hh" #include "NOD_node_tree_multi_function.hh" -#include "NOD_type_callbacks.hh" -#include "NOD_type_conversions.hh" using blender::float3; using blender::FunctionRef; @@ -100,7 +98,6 @@ using blender::bke::PersistentDataHandleMap; using blender::bke::PersistentObjectHandle; using blender::fn::GMutablePointer; using blender::fn::GPointer; -using blender::fn::GValueMap; using blender::nodes::GeoNodeExecParams; using namespace blender::fn::multi_function_types; using namespace blender::nodes::derived_node_tree_types; @@ -268,368 +265,6 @@ static bool logging_enabled(const ModifierEvalContext *ctx) return true; } -class GeometryNodesEvaluator { - public: - using LogSocketValueFn = std::function)>; - - private: - blender::LinearAllocator<> allocator_; - Map, GMutablePointer> value_by_input_; - Vector group_outputs_; - blender::nodes::MultiFunctionByNode &mf_by_node_; - const blender::nodes::DataTypeConversions &conversions_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; - LogSocketValueFn log_socket_value_fn_; - - public: - GeometryNodesEvaluator(const Map &group_input_data, - Vector group_outputs, - blender::nodes::MultiFunctionByNode &mf_by_node, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph, - LogSocketValueFn log_socket_value_fn) - : group_outputs_(std::move(group_outputs)), - mf_by_node_(mf_by_node), - conversions_(blender::nodes::get_implicit_type_conversions()), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph), - log_socket_value_fn_(std::move(log_socket_value_fn)) - { - for (auto item : group_input_data.items()) { - this->log_socket_value(item.key, item.value); - this->forward_to_inputs(item.key, item.value); - } - } - - Vector execute() - { - Vector results; - for (const DInputSocket &group_output : group_outputs_) { - Vector result = this->get_input_values(group_output); - this->log_socket_value(group_output, result); - results.append(result[0]); - } - for (GMutablePointer value : value_by_input_.values()) { - value.destruct(); - } - return results; - } - - private: - Vector get_input_values(const DInputSocket socket_to_compute) - { - Vector from_sockets; - socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); }); - - if (from_sockets.is_empty()) { - /* The input is not connected, use the value from the socket itself. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - return {get_unlinked_input_value(socket_to_compute, type)}; - } - - /* Multi-input sockets contain a vector of inputs. */ - if (socket_to_compute->is_multi_input_socket()) { - return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets); - } - - const DSocket from_socket = from_sockets[0]; - GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket); - return {value}; - } - - Vector get_inputs_from_incoming_links(const DInputSocket socket_to_compute, - const Span from_sockets) - { - Vector values; - for (const int i : from_sockets.index_range()) { - const DSocket from_socket = from_sockets[i]; - const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket); - if (first_occurence == -1) { - values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket)); - } - else { - /* If the same from-socket occurs more than once, we make a copy of the first value. This - * can happen when a node linked to a multi-input-socket is muted. */ - GMutablePointer value = values[first_occurence]; - const CPPType *type = value.type(); - void *copy_buffer = allocator_.allocate(type->size(), type->alignment()); - type->copy_to_uninitialized(value.get(), copy_buffer); - values.append({type, copy_buffer}); - } - } - return values; - } - - GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute, - const DSocket from_socket) - { - if (from_socket->is_output()) { - const DOutputSocket from_output_socket{from_socket}; - const std::pair key = std::make_pair(socket_to_compute, - from_output_socket); - std::optional value = value_by_input_.pop_try(key); - if (value.has_value()) { - /* This input has been computed before, return it directly. */ - return {*value}; - } - - /* Compute the socket now. */ - this->compute_output_and_forward(from_output_socket); - return {value_by_input_.pop(key)}; - } - - /* Get value from an unlinked input socket. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - const DInputSocket from_input_socket{from_socket}; - return {get_unlinked_input_value(from_input_socket, type)}; - } - - void compute_output_and_forward(const DOutputSocket socket_to_compute) - { - const DNode node{socket_to_compute.context(), &socket_to_compute->node()}; - - if (!socket_to_compute->is_available()) { - /* If the output is not available, use a default value. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(type.default_value(), buffer); - this->forward_to_inputs(socket_to_compute, {type, buffer}); - return; - } - - /* Prepare inputs required to execute the node. */ - GValueMap node_inputs_map{allocator_}; - for (const InputSocketRef *input_socket : node->inputs()) { - if (input_socket->is_available()) { - DInputSocket dsocket{node.context(), input_socket}; - Vector values = this->get_input_values(dsocket); - this->log_socket_value(dsocket, values); - for (int i = 0; i < values.size(); ++i) { - /* Values from Multi Input Sockets are stored in input map with the format - * []. */ - blender::StringRefNull key = allocator_.copy_string( - input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : "")); - node_inputs_map.add_new_direct(key, std::move(values[i])); - } - } - } - - /* Execute the node. */ - GValueMap node_outputs_map{allocator_}; - GeoNodeExecParams params{ - node, node_inputs_map, node_outputs_map, handle_map_, self_object_, modifier_, depsgraph_}; - this->execute_node(node, params); - - /* Forward computed outputs to linked input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - if (output_socket->is_available()) { - const DOutputSocket dsocket{node.context(), output_socket}; - GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); - this->log_socket_value(dsocket, value); - this->forward_to_inputs(dsocket, value); - } - } - } - - void log_socket_value(const DSocket socket, Span values) - { - if (log_socket_value_fn_) { - log_socket_value_fn_(socket, values); - } - } - - void log_socket_value(const DSocket socket, Span values) - { - this->log_socket_value(socket, values.cast()); - } - - void log_socket_value(const DSocket socket, GPointer value) - { - this->log_socket_value(socket, Span(&value, 1)); - } - - void execute_node(const DNode node, GeoNodeExecParams params) - { - const bNode &bnode = params.node(); - - /* Use the geometry-node-execute callback if it exists. */ - if (bnode.typeinfo->geometry_node_execute != nullptr) { - bnode.typeinfo->geometry_node_execute(params); - return; - } - - /* Use the multi-function implementation if it exists. */ - const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr); - if (multi_function != nullptr) { - this->execute_multi_function_node(node, params, *multi_function); - return; - } - - /* Just output default values if no implementation exists. */ - this->execute_unknown_node(node, params); - } - - void execute_multi_function_node(const DNode node, - GeoNodeExecParams params, - const MultiFunction &fn) - { - MFContextBuilder fn_context; - MFParamsBuilder fn_params{fn, 1}; - Vector input_data; - for (const InputSocketRef *socket_ref : node->inputs()) { - if (socket_ref->is_available()) { - GMutablePointer data = params.extract_input(socket_ref->identifier()); - fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); - input_data.append(data); - } - } - Vector output_data; - for (const OutputSocketRef *socket_ref : node->outputs()) { - if (socket_ref->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - fn_params.add_uninitialized_single_output(GMutableSpan(type, buffer, 1)); - output_data.append(GMutablePointer(type, buffer)); - } - } - fn.call(IndexRange(1), fn_params, fn_context); - for (GMutablePointer value : input_data) { - value.destruct(); - } - int output_index = 0; - for (const int i : node->outputs().index_range()) { - if (node->output(i).is_available()) { - GMutablePointer value = output_data[output_index]; - params.set_output_by_move(node->output(i).identifier(), value); - value.destruct(); - output_index++; - } - } - } - - void execute_unknown_node(const DNode node, GeoNodeExecParams params) - { - for (const OutputSocketRef *socket : node->outputs()) { - if (socket->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - params.set_output_by_copy(socket->identifier(), {type, type.default_value()}); - } - } - } - - void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward) - { - /* For all sockets that are linked with the from_socket push the value to their node. */ - Vector to_sockets_all; - - auto handle_target_socket_fn = [&](DInputSocket to_socket) { - to_sockets_all.append_non_duplicates(to_socket); - }; - auto handle_skipped_socket_fn = [&, this](DSocket socket) { - this->log_socket_value(socket, value_to_forward); - }; - - from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); - - const CPPType &from_type = *value_to_forward.type(); - Vector to_sockets_same_type; - for (const DInputSocket &to_socket : to_sockets_all) { - const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); - const std::pair key = std::make_pair(to_socket, from_socket); - if (from_type == to_type) { - to_sockets_same_type.append(to_socket); - } - else { - void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); - if (conversions_.is_convertible(from_type, to_type)) { - conversions_.convert_to_uninitialized( - from_type, to_type, value_to_forward.get(), buffer); - } - else { - to_type.copy_to_uninitialized(to_type.default_value(), buffer); - } - add_value_to_input_socket(key, GMutablePointer{to_type, buffer}); - } - } - - if (to_sockets_same_type.size() == 0) { - /* This value is not further used, so destruct it. */ - value_to_forward.destruct(); - } - else if (to_sockets_same_type.size() == 1) { - /* This value is only used on one input socket, no need to copy it. */ - const DInputSocket to_socket = to_sockets_same_type[0]; - const std::pair key = std::make_pair(to_socket, from_socket); - - add_value_to_input_socket(key, value_to_forward); - } - else { - /* Multiple inputs use the value, make a copy for every input except for one. */ - const DInputSocket first_to_socket = to_sockets_same_type[0]; - Span other_to_sockets = to_sockets_same_type.as_span().drop_front(1); - const CPPType &type = *value_to_forward.type(); - const std::pair first_key = std::make_pair(first_to_socket, - from_socket); - add_value_to_input_socket(first_key, value_to_forward); - for (const DInputSocket &to_socket : other_to_sockets) { - const std::pair key = std::make_pair(to_socket, from_socket); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(value_to_forward.get(), buffer); - add_value_to_input_socket(key, GMutablePointer{type, buffer}); - } - } - } - - void add_value_to_input_socket(const std::pair key, - GMutablePointer value) - { - value_by_input_.add_new(key, value); - } - - GMutablePointer get_unlinked_input_value(const DInputSocket &socket, - const CPPType &required_type) - { - bNodeSocket *bsocket = socket->bsocket(); - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - - if (bsocket->type == SOCK_OBJECT) { - Object *object = socket->default_value()->value; - PersistentObjectHandle object_handle = handle_map_.lookup(object); - new (buffer) PersistentObjectHandle(object_handle); - } - else if (bsocket->type == SOCK_COLLECTION) { - Collection *collection = socket->default_value()->value; - PersistentCollectionHandle collection_handle = handle_map_.lookup(collection); - new (buffer) PersistentCollectionHandle(collection_handle); - } - else { - blender::nodes::socket_cpp_value_get(*bsocket, buffer); - } - - if (type == required_type) { - return {type, buffer}; - } - if (conversions_.is_convertible(type, required_type)) { - void *converted_buffer = allocator_.allocate(required_type.size(), - required_type.alignment()); - conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); - type.destruct(buffer); - return {required_type, converted_buffer}; - } - void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment()); - required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); - return {required_type, default_buffer}; - } -}; - /** * This code is responsible for creating the new property and also creating the group of * properties in the prop_ui_container group for the UI info, the mapping for which is @@ -1297,21 +932,19 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, log_ui_hints(socket, values, ctx->object, nmd); }; - GeometryNodesEvaluator evaluator{group_inputs, - group_outputs, - mf_by_node, - handle_map, - ctx->object, - (ModifierData *)nmd, - ctx->depsgraph, - log_socket_value}; - - Vector results = evaluator.execute(); - BLI_assert(results.size() == 1); - GMutablePointer result = results[0]; - - GeometrySet output_geometry = std::move(*(GeometrySet *)result.get()); - return output_geometry; + blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params; + eval_params.input_values = group_inputs; + eval_params.output_sockets = group_outputs; + eval_params.mf_by_node = &mf_by_node; + eval_params.handle_map = &handle_map; + eval_params.modifier_ = nmd; + eval_params.depsgraph = ctx->depsgraph; + eval_params.log_socket_value_fn = log_socket_value; + blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params); + + BLI_assert(eval_params.r_output_values.size() == 1); + GMutablePointer result = eval_params.r_output_values[0]; + return result.relocate_out(); } /** diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc new file mode 100644 index 00000000000..aa35f5c540f --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -0,0 +1,452 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "MOD_nodes_evaluator.hh" + +#include "NOD_geometry_exec.hh" +#include "NOD_type_conversions.hh" + +#include "DEG_depsgraph_query.h" + +#include "FN_generic_value_map.hh" +#include "FN_multi_function.hh" + +namespace blender::modifiers::geometry_nodes { + +using bke::PersistentCollectionHandle; +using bke::PersistentObjectHandle; +using fn::CPPType; +using fn::GValueMap; +using nodes::GeoNodeExecParams; +using namespace fn::multi_function_types; + +class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider { + public: + LinearAllocator<> *allocator; + GValueMap *input_values; + GValueMap *output_values; + + bool can_get_input(StringRef identifier) const override + { + return input_values->contains(identifier); + } + + bool can_set_output(StringRef identifier) const override + { + return !output_values->contains(identifier); + } + + GMutablePointer extract_input(StringRef identifier) override + { + return this->input_values->extract(identifier); + } + + Vector extract_multi_input(StringRef identifier) override + { + Vector values; + int index = 0; + while (true) { + std::string sub_identifier = identifier; + if (index > 0) { + sub_identifier += "[" + std::to_string(index) + "]"; + } + if (!this->input_values->contains(sub_identifier)) { + break; + } + values.append(input_values->extract(sub_identifier)); + index++; + } + return values; + } + + GPointer get_input(StringRef identifier) const override + { + return this->input_values->lookup(identifier); + } + + GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) override + { + void *buffer = this->allocator->allocate(type.size(), type.alignment()); + GMutablePointer ptr{&type, buffer}; + this->output_values->add_new_direct(identifier, ptr); + return ptr; + } +}; + +class GeometryNodesEvaluator { + public: + using LogSocketValueFn = std::function)>; + + private: + blender::LinearAllocator<> &allocator_; + Map, GMutablePointer> value_by_input_; + Vector group_outputs_; + blender::nodes::MultiFunctionByNode &mf_by_node_; + const blender::nodes::DataTypeConversions &conversions_; + const PersistentDataHandleMap &handle_map_; + const Object *self_object_; + const ModifierData *modifier_; + Depsgraph *depsgraph_; + LogSocketValueFn log_socket_value_fn_; + + public: + GeometryNodesEvaluator(GeometryNodesEvaluationParams ¶ms) + : allocator_(params.allocator), + group_outputs_(std::move(params.output_sockets)), + mf_by_node_(*params.mf_by_node), + conversions_(blender::nodes::get_implicit_type_conversions()), + handle_map_(*params.handle_map), + self_object_(params.self_object), + modifier_(¶ms.modifier_->modifier), + depsgraph_(params.depsgraph), + log_socket_value_fn_(std::move(params.log_socket_value_fn)) + { + for (auto item : params.input_values.items()) { + this->log_socket_value(item.key, item.value); + this->forward_to_inputs(item.key, item.value); + } + } + + Vector execute() + { + Vector results; + for (const DInputSocket &group_output : group_outputs_) { + Vector result = this->get_input_values(group_output); + this->log_socket_value(group_output, result); + results.append(result[0]); + } + for (GMutablePointer value : value_by_input_.values()) { + value.destruct(); + } + return results; + } + + private: + Vector get_input_values(const DInputSocket socket_to_compute) + { + Vector from_sockets; + socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); }); + + if (from_sockets.is_empty()) { + /* The input is not connected, use the value from the socket itself. */ + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); + return {get_unlinked_input_value(socket_to_compute, type)}; + } + + /* Multi-input sockets contain a vector of inputs. */ + if (socket_to_compute->is_multi_input_socket()) { + return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets); + } + + const DSocket from_socket = from_sockets[0]; + GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket); + return {value}; + } + + Vector get_inputs_from_incoming_links(const DInputSocket socket_to_compute, + const Span from_sockets) + { + Vector values; + for (const int i : from_sockets.index_range()) { + const DSocket from_socket = from_sockets[i]; + const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket); + if (first_occurence == -1) { + values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket)); + } + else { + /* If the same from-socket occurs more than once, we make a copy of the first value. This + * can happen when a node linked to a multi-input-socket is muted. */ + GMutablePointer value = values[first_occurence]; + const CPPType *type = value.type(); + void *copy_buffer = allocator_.allocate(type->size(), type->alignment()); + type->copy_to_uninitialized(value.get(), copy_buffer); + values.append({type, copy_buffer}); + } + } + return values; + } + + GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute, + const DSocket from_socket) + { + if (from_socket->is_output()) { + const DOutputSocket from_output_socket{from_socket}; + const std::pair key = std::make_pair(socket_to_compute, + from_output_socket); + std::optional value = value_by_input_.pop_try(key); + if (value.has_value()) { + /* This input has been computed before, return it directly. */ + return {*value}; + } + + /* Compute the socket now. */ + this->compute_output_and_forward(from_output_socket); + return {value_by_input_.pop(key)}; + } + + /* Get value from an unlinked input socket. */ + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); + const DInputSocket from_input_socket{from_socket}; + return {get_unlinked_input_value(from_input_socket, type)}; + } + + void compute_output_and_forward(const DOutputSocket socket_to_compute) + { + const DNode node{socket_to_compute.context(), &socket_to_compute->node()}; + + if (!socket_to_compute->is_available()) { + /* If the output is not available, use a default value. */ + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(type.default_value(), buffer); + this->forward_to_inputs(socket_to_compute, {type, buffer}); + return; + } + + /* Prepare inputs required to execute the node. */ + GValueMap node_inputs_map{allocator_}; + for (const InputSocketRef *input_socket : node->inputs()) { + if (input_socket->is_available()) { + DInputSocket dsocket{node.context(), input_socket}; + Vector values = this->get_input_values(dsocket); + this->log_socket_value(dsocket, values); + for (int i = 0; i < values.size(); ++i) { + /* Values from Multi Input Sockets are stored in input map with the format + * []. */ + blender::StringRefNull key = allocator_.copy_string( + input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : "")); + node_inputs_map.add_new_direct(key, std::move(values[i])); + } + } + } + + /* Execute the node. */ + GValueMap node_outputs_map{allocator_}; + NodeParamsProvider params_provider; + params_provider.dnode = node; + params_provider.handle_map = &handle_map_; + params_provider.self_object = self_object_; + params_provider.depsgraph = depsgraph_; + params_provider.allocator = &allocator_; + params_provider.input_values = &node_inputs_map; + params_provider.output_values = &node_outputs_map; + params_provider.modifier = modifier_; + this->execute_node(node, params_provider); + + /* Forward computed outputs to linked input sockets. */ + for (const OutputSocketRef *output_socket : node->outputs()) { + if (output_socket->is_available()) { + const DOutputSocket dsocket{node.context(), output_socket}; + GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); + this->log_socket_value(dsocket, value); + this->forward_to_inputs(dsocket, value); + } + } + } + + void log_socket_value(const DSocket socket, Span values) + { + if (log_socket_value_fn_) { + log_socket_value_fn_(socket, values); + } + } + + void log_socket_value(const DSocket socket, Span values) + { + this->log_socket_value(socket, values.cast()); + } + + void log_socket_value(const DSocket socket, GPointer value) + { + this->log_socket_value(socket, Span(&value, 1)); + } + + void execute_node(const DNode node, NodeParamsProvider ¶ms_provider) + { + const bNode &bnode = *params_provider.dnode->bnode(); + + /* Use the geometry-node-execute callback if it exists. */ + if (bnode.typeinfo->geometry_node_execute != nullptr) { + GeoNodeExecParams params{params_provider}; + bnode.typeinfo->geometry_node_execute(params); + return; + } + + /* Use the multi-function implementation if it exists. */ + const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr); + if (multi_function != nullptr) { + this->execute_multi_function_node(node, params_provider, *multi_function); + return; + } + + /* Just output default values if no implementation exists. */ + this->execute_unknown_node(node, params_provider); + } + + void execute_multi_function_node(const DNode node, + NodeParamsProvider ¶ms_provider, + const MultiFunction &fn) + { + MFContextBuilder fn_context; + MFParamsBuilder fn_params{fn, 1}; + Vector input_data; + for (const InputSocketRef *socket_ref : node->inputs()) { + if (socket_ref->is_available()) { + GMutablePointer data = params_provider.extract_input(socket_ref->identifier()); + fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); + input_data.append(data); + } + } + Vector output_data; + for (const OutputSocketRef *socket_ref : node->outputs()) { + if (socket_ref->is_available()) { + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo()); + GMutablePointer output_value = params_provider.alloc_output_value(socket_ref->identifier(), + type); + fn_params.add_uninitialized_single_output(GMutableSpan{type, output_value.get(), 1}); + output_data.append(output_value); + } + } + fn.call(IndexRange(1), fn_params, fn_context); + for (GMutablePointer value : input_data) { + value.destruct(); + } + } + + void execute_unknown_node(const DNode node, NodeParamsProvider ¶ms_provider) + { + for (const OutputSocketRef *socket : node->outputs()) { + if (socket->is_available()) { + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); + params_provider.output_values->add_new_by_copy(socket->identifier(), + {type, type.default_value()}); + } + } + } + + void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward) + { + /* For all sockets that are linked with the from_socket push the value to their node. */ + Vector to_sockets_all; + + auto handle_target_socket_fn = [&](DInputSocket to_socket) { + to_sockets_all.append_non_duplicates(to_socket); + }; + auto handle_skipped_socket_fn = [&, this](DSocket socket) { + this->log_socket_value(socket, value_to_forward); + }; + + from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); + + const CPPType &from_type = *value_to_forward.type(); + Vector to_sockets_same_type; + for (const DInputSocket &to_socket : to_sockets_all) { + const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); + const std::pair key = std::make_pair(to_socket, from_socket); + if (from_type == to_type) { + to_sockets_same_type.append(to_socket); + } + else { + void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); + if (conversions_.is_convertible(from_type, to_type)) { + conversions_.convert_to_uninitialized( + from_type, to_type, value_to_forward.get(), buffer); + } + else { + to_type.copy_to_uninitialized(to_type.default_value(), buffer); + } + add_value_to_input_socket(key, GMutablePointer{to_type, buffer}); + } + } + + if (to_sockets_same_type.size() == 0) { + /* This value is not further used, so destruct it. */ + value_to_forward.destruct(); + } + else if (to_sockets_same_type.size() == 1) { + /* This value is only used on one input socket, no need to copy it. */ + const DInputSocket to_socket = to_sockets_same_type[0]; + const std::pair key = std::make_pair(to_socket, from_socket); + + add_value_to_input_socket(key, value_to_forward); + } + else { + /* Multiple inputs use the value, make a copy for every input except for one. */ + const DInputSocket first_to_socket = to_sockets_same_type[0]; + Span other_to_sockets = to_sockets_same_type.as_span().drop_front(1); + const CPPType &type = *value_to_forward.type(); + const std::pair first_key = std::make_pair(first_to_socket, + from_socket); + add_value_to_input_socket(first_key, value_to_forward); + for (const DInputSocket &to_socket : other_to_sockets) { + const std::pair key = std::make_pair(to_socket, from_socket); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(value_to_forward.get(), buffer); + add_value_to_input_socket(key, GMutablePointer{type, buffer}); + } + } + } + + void add_value_to_input_socket(const std::pair key, + GMutablePointer value) + { + value_by_input_.add_new(key, value); + } + + GMutablePointer get_unlinked_input_value(const DInputSocket &socket, + const CPPType &required_type) + { + bNodeSocket *bsocket = socket->bsocket(); + const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + + if (bsocket->type == SOCK_OBJECT) { + Object *object = socket->default_value()->value; + PersistentObjectHandle object_handle = handle_map_.lookup(object); + new (buffer) PersistentObjectHandle(object_handle); + } + else if (bsocket->type == SOCK_COLLECTION) { + Collection *collection = socket->default_value()->value; + PersistentCollectionHandle collection_handle = handle_map_.lookup(collection); + new (buffer) PersistentCollectionHandle(collection_handle); + } + else { + blender::nodes::socket_cpp_value_get(*bsocket, buffer); + } + + if (type == required_type) { + return {type, buffer}; + } + if (conversions_.is_convertible(type, required_type)) { + void *converted_buffer = allocator_.allocate(required_type.size(), + required_type.alignment()); + conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); + type.destruct(buffer); + return {required_type, converted_buffer}; + } + void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment()); + required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); + return {required_type, default_buffer}; + } +}; + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms) +{ + GeometryNodesEvaluator evaluator{params}; + params.r_output_values = evaluator.execute(); +} + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.hh b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh new file mode 100644 index 00000000000..3d9a03350f7 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "BLI_map.hh" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_tree_multi_function.hh" + +#include "FN_generic_pointer.hh" + +#include "BKE_persistent_data_handle.hh" + +#include "DNA_modifier_types.h" + +namespace blender::modifiers::geometry_nodes { + +using namespace nodes::derived_node_tree_types; +using bke::PersistentDataHandleMap; +using fn::GMutablePointer; +using fn::GPointer; + +using LogSocketValueFn = std::function)>; + +struct GeometryNodesEvaluationParams { + blender::LinearAllocator<> allocator; + + Map input_values; + Vector output_sockets; + nodes::MultiFunctionByNode *mf_by_node; + const PersistentDataHandleMap *handle_map; + const NodesModifierData *modifier_; + Depsgraph *depsgraph; + Object *self_object; + LogSocketValueFn log_socket_value_fn; + + Vector r_output_values; +}; + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms); + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index f2582600c7d..83f084c2f25 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -56,31 +56,59 @@ using fn::GVMutableArray_GSpan; using fn::GVMutableArray_Typed; using fn::GVMutableArrayPtr; +/** + * This class exists to separate the memory management details of the geometry nodes evaluator from + * the node execution functions and related utilities. + */ +class GeoNodeExecParamsProvider { + public: + DNode dnode; + const PersistentDataHandleMap *handle_map = nullptr; + const Object *self_object = nullptr; + const ModifierData *modifier = nullptr; + Depsgraph *depsgraph = nullptr; + + /** + * Returns true when the node is allowed to get/extract the input value. The identifier is + * expected to be valid. This may return false if the input value has been consumed already. + */ + virtual bool can_get_input(StringRef identifier) const = 0; + + /** + * Returns true when the node is allowed to set the output value. The identifier is expected to + * be valid. This may return false if the output value has been set already. + */ + virtual bool can_set_output(StringRef identifier) const = 0; + + /** + * Take ownership of an input value. The caller is responsible for destructing the value. It does + * not have to be freed, because the memory is managed by the geometry nodes evaluator. + */ + virtual GMutablePointer extract_input(StringRef identifier) = 0; + + /** + * Similar to #extract_input, but has to be used for multi-input sockets. + */ + virtual Vector extract_multi_input(StringRef identifier) = 0; + + /** + * Get the input value for the identifier without taking ownership of it. + */ + virtual GPointer get_input(StringRef identifier) const = 0; + + /** + * Prepare a memory buffer for an output value of the node. The returned memory has to be + * initialized by the caller. The identifier and type are expected to be correct. + */ + virtual GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) = 0; +}; + class GeoNodeExecParams { private: - const DNode node_; - GValueMap &input_values_; - GValueMap &output_values_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; + GeoNodeExecParamsProvider *provider_; public: - GeoNodeExecParams(const DNode node, - GValueMap &input_values, - GValueMap &output_values, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph) - : node_(node), - input_values_(input_values), - output_values_(output_values), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph) + GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider) { } @@ -93,9 +121,9 @@ class GeoNodeExecParams { GMutablePointer extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier); + this->check_input_access(identifier); #endif - return input_values_.extract(identifier); + return provider_->extract_input(identifier); } /** @@ -106,9 +134,10 @@ class GeoNodeExecParams { template T extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get()); + this->check_input_access(identifier, &CPPType::get()); #endif - return input_values_.extract(identifier); + GMutablePointer gvalue = this->extract_input(identifier); + return gvalue.relocate_out(); } /** @@ -118,18 +147,10 @@ class GeoNodeExecParams { */ template Vector extract_multi_input(StringRef identifier) { + Vector gvalues = provider_->extract_multi_input(identifier); Vector values; - int index = 0; - while (true) { - std::string sub_identifier = identifier; - if (index > 0) { - sub_identifier += "[" + std::to_string(index) + "]"; - } - if (!input_values_.contains(sub_identifier)) { - break; - } - values.append(input_values_.extract(sub_identifier)); - index++; + for (GMutablePointer gvalue : gvalues) { + values.append(gvalue.relocate_out()); } return values; } @@ -140,33 +161,11 @@ class GeoNodeExecParams { template const T &get_input(StringRef identifier) const { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get()); -#endif - return input_values_.lookup(identifier); - } - - /** - * Move-construct a new value based on the given value and store it for the given socket - * identifier. - */ - void set_output_by_move(StringRef identifier, GMutablePointer value) - { -#ifdef DEBUG - BLI_assert(value.type() != nullptr); - BLI_assert(value.get() != nullptr); - this->check_set_output(identifier, *value.type()); -#endif - output_values_.add_new_by_move(identifier, value); - } - - void set_output_by_copy(StringRef identifier, GPointer value) - { -#ifdef DEBUG - BLI_assert(value.type() != nullptr); - BLI_assert(value.get() != nullptr); - this->check_set_output(identifier, *value.type()); + this->check_input_access(identifier, &CPPType::get()); #endif - output_values_.add_new_by_copy(identifier, value); + GPointer gvalue = provider_->get_input(identifier); + BLI_assert(gvalue.is_type()); + return *(const T *)gvalue.get(); } /** @@ -174,10 +173,13 @@ class GeoNodeExecParams { */ template void set_output(StringRef identifier, T &&value) { + using StoredT = std::decay_t; + const CPPType &type = CPPType::get>(); #ifdef DEBUG - this->check_set_output(identifier, CPPType::get>()); + this->check_output_access(identifier, type); #endif - output_values_.add_new(identifier, std::forward(value)); + GMutablePointer gvalue = provider_->alloc_output_value(identifier, type); + new (gvalue.get()) StoredT(std::forward(value)); } /** @@ -185,22 +187,22 @@ class GeoNodeExecParams { */ const bNode &node() const { - return *node_->bnode(); + return *provider_->dnode->bnode(); } const PersistentDataHandleMap &handle_map() const { - return handle_map_; + return *provider_->handle_map; } const Object *self_object() const { - return self_object_; + return provider_->self_object; } Depsgraph *depsgraph() const { - return depsgraph_; + return provider_->depsgraph; } /** @@ -247,8 +249,8 @@ class GeoNodeExecParams { private: /* Utilities for detecting common errors at when using this class. */ - void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const; - void check_set_output(StringRef identifier, const CPPType &value_type) const; + void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const; + void check_output_access(StringRef identifier, const CPPType &value_type) const; /* Find the active socket socket with the input name (not the identifier). */ const bNodeSocket *find_available_socket(const StringRef name) const; diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index 08fbe1ac1e6..73a702c753a 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -30,22 +30,22 @@ namespace blender::nodes { void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const { - bNodeTree *btree_cow = node_->btree(); + bNodeTree *btree_cow = provider_->dnode->btree(); BLI_assert(btree_cow != nullptr); if (btree_cow == nullptr) { return; } bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow); - const NodeTreeEvaluationContext context(*self_object_, *modifier_); + const NodeTreeEvaluationContext context(*provider_->self_object, *provider_->modifier); BKE_nodetree_error_message_add( - *btree_original, context, *node_->bnode(), type, std::move(message)); + *btree_original, context, *provider_->dnode->bnode(), type, std::move(message)); } const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available() && socket->name() == name) { return socket->bsocket(); } @@ -183,11 +183,11 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( return default_domain; } -void GeoNodeExecParams::check_extract_input(StringRef identifier, - const CPPType *requested_type) const +void GeoNodeExecParams::check_input_access(StringRef identifier, + const CPPType *requested_type) const { bNodeSocket *found_socket = nullptr; - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -197,39 +197,39 @@ void GeoNodeExecParams::check_extract_input(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 : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (!input_values_.contains(identifier)) { + 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(false); + BLI_assert_unreachable(); } else if (requested_type != nullptr) { const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); if (*requested_type != expected_type) { std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '" << expected_type.name() << "'.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } } } -void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const +void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const { bNodeSocket *found_socket = nullptr; - for (const OutputSocketRef *socket : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -239,29 +239,29 @@ void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &va 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 : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (output_values_.contains(identifier)) { + else if (!provider_->can_set_output(identifier)) { std::cout << "The identifier '" << identifier << "' has been set already.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else { const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); if (value_type != expected_type) { std::cout << "The value type '" << value_type.name() << "' is incorrect. Expected '" << expected_type.name() << "'.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } } } -- cgit v1.2.3