diff options
Diffstat (limited to 'source/blender/nodes')
19 files changed, 2583 insertions, 1105 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index ff8bd27f8d7..e042458ca19 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -40,7 +40,8 @@ set(INC set(SRC intern/derived_node_tree.cc - intern/geometry_nodes_eval_log.cc + intern/geometry_nodes_lazy_function.cc + intern/geometry_nodes_log.cc intern/math_functions.cc intern/node_common.cc intern/node_declaration.cc @@ -58,7 +59,7 @@ set(SRC NOD_function.h NOD_geometry.h NOD_geometry_exec.hh - NOD_geometry_nodes_eval_log.hh + NOD_geometry_nodes_lazy_function.hh NOD_math_functions.hh NOD_multi_function.hh NOD_node_declaration.hh diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index b5ffd3a317c..16669f7cfce 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -3,6 +3,7 @@ #pragma once #include "FN_field.hh" +#include "FN_lazy_function.hh" #include "FN_multi_function_builder.hh" #include "BKE_geometry_fields.hh" @@ -11,9 +12,8 @@ #include "DNA_node_types.h" #include "NOD_derived_node_tree.hh" -#include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_geometry_nodes_lazy_function.hh" -struct Depsgraph; struct ModifierData; namespace blender::nodes { @@ -40,75 +40,18 @@ using fn::FieldInput; using fn::FieldOperation; using fn::GField; using fn::ValueOrField; -using geometry_nodes_eval_log::eNamedAttrUsage; -using geometry_nodes_eval_log::NodeWarningType; - -/** - * 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 Object *self_object = nullptr; - const ModifierData *modifier = nullptr; - Depsgraph *depsgraph = nullptr; - geometry_nodes_eval_log::GeoLogger *logger = 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<GMutablePointer> 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(const CPPType &type) = 0; - - /** - * The value has been allocated with #alloc_output_value. - */ - virtual void set_output(StringRef identifier, GMutablePointer value) = 0; - - /* A description for these methods is provided in GeoNodeExecParams. */ - virtual void set_input_unused(StringRef identifier) = 0; - virtual bool output_is_required(StringRef identifier) const = 0; - virtual bool lazy_require_input(StringRef identifier) = 0; - virtual bool lazy_output_is_required(StringRef identifier) const = 0; - - virtual void set_default_remaining_outputs() = 0; -}; +using geo_eval_log::NamedAttributeUsage; +using geo_eval_log::NodeWarningType; class GeoNodeExecParams { private: - GeoNodeExecParamsProvider *provider_; + const bNode &node_; + lf::Params ¶ms_; + const lf::Context &lf_context_; public: - GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider) + GeoNodeExecParams(const bNode &node, lf::Params ¶ms, const lf::Context &lf_context) + : node_(node), params_(params), lf_context_(lf_context) { } @@ -119,20 +62,6 @@ class GeoNodeExecParams { /** * Get the input value for the input socket with the given identifier. * - * The node calling becomes responsible for destructing the value before it is done - * executing. This method can only be called once for each identifier. - */ - GMutablePointer extract_input(StringRef identifier) - { -#ifdef DEBUG - this->check_input_access(identifier); -#endif - return provider_->extract_input(identifier); - } - - /** - * Get the input value for the input socket with the given identifier. - * * This method can only be called once for each identifier. */ template<typename T> T extract_input(StringRef identifier) @@ -151,8 +80,8 @@ class GeoNodeExecParams { #ifdef DEBUG this->check_input_access(identifier, &CPPType::get<T>()); #endif - GMutablePointer gvalue = this->extract_input(identifier); - T value = gvalue.relocate_out<T>(); + const int index = this->get_input_index(identifier); + T value = params_.extract_input<T>(index); if constexpr (std::is_same_v<T, GeometrySet>) { this->check_input_geometry_set(identifier, value); } @@ -164,27 +93,6 @@ class GeoNodeExecParams { void check_output_geometry_set(const GeometrySet &geometry_set) const; /** - * Get input as vector for multi input socket with the given identifier. - * - * This method can only be called once for each identifier. - */ - template<typename T> Vector<T> extract_multi_input(StringRef identifier) - { - Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier); - Vector<T> values; - for (GMutablePointer gvalue : gvalues) { - if constexpr (is_field_base_type_v<T>) { - const ValueOrField<T> value_or_field = gvalue.relocate_out<ValueOrField<T>>(); - values.append(value_or_field.as_value()); - } - else { - values.append(gvalue.relocate_out<T>()); - } - } - return values; - } - - /** * Get the input value for the input socket with the given identifier. */ template<typename T> T get_input(StringRef identifier) const @@ -202,9 +110,8 @@ class GeoNodeExecParams { #ifdef DEBUG this->check_input_access(identifier, &CPPType::get<T>()); #endif - GPointer gvalue = provider_->get_input(identifier); - BLI_assert(gvalue.is_type<T>()); - const T &value = *(const T *)gvalue.get(); + const int index = this->get_input_index(identifier); + const T &value = params_.get_input<T>(index); if constexpr (std::is_same_v<T, GeometrySet>) { this->check_input_geometry_set(identifier, value); } @@ -226,17 +133,28 @@ class GeoNodeExecParams { this->set_output(identifier, ValueOrField<BaseType>(std::forward<T>(value))); } else { - const CPPType &type = CPPType::get<StoredT>(); #ifdef DEBUG + const CPPType &type = CPPType::get<StoredT>(); this->check_output_access(identifier, type); #endif if constexpr (std::is_same_v<StoredT, GeometrySet>) { this->check_output_geometry_set(value); } - GMutablePointer gvalue = provider_->alloc_output_value(type); - new (gvalue.get()) StoredT(std::forward<T>(value)); - provider_->set_output(identifier, gvalue); + const int index = this->get_output_index(identifier); + params_.set_output(index, std::forward<T>(value)); + } + } + + geo_eval_log::GeoTreeLogger *get_local_tree_logger() const + { + GeoNodesLFUserData *user_data = this->user_data(); + BLI_assert(user_data != nullptr); + const ComputeContext *compute_context = user_data->compute_context; + BLI_assert(compute_context != nullptr); + if (user_data->modifier_data->eval_log == nullptr) { + return nullptr; } + return &user_data->modifier_data->eval_log->get_local_tree_logger(*compute_context); } /** @@ -244,7 +162,8 @@ class GeoNodeExecParams { */ void set_input_unused(StringRef identifier) { - provider_->set_input_unused(identifier); + const int index = this->get_input_index(identifier); + params_.set_input_unused(index); } /** @@ -254,7 +173,8 @@ class GeoNodeExecParams { */ bool output_is_required(StringRef identifier) const { - return provider_->output_is_required(identifier); + const int index = this->get_output_index(identifier); + return params_.get_output_usage(index) != lf::ValueUsage::Unused; } /** @@ -265,7 +185,8 @@ class GeoNodeExecParams { */ bool lazy_require_input(StringRef identifier) { - return provider_->lazy_require_input(identifier); + const int index = this->get_input_index(identifier); + return params_.try_get_input_data_ptr_or_request(index) == nullptr; } /** @@ -275,7 +196,8 @@ class GeoNodeExecParams { */ bool lazy_output_is_required(StringRef identifier) { - return provider_->lazy_output_is_required(identifier); + const int index = this->get_output_index(identifier); + return params_.get_output_usage(index) == lf::ValueUsage::Used; } /** @@ -283,17 +205,32 @@ class GeoNodeExecParams { */ const bNode &node() const { - return *provider_->dnode; + return node_; } const Object *self_object() const { - return provider_->self_object; + if (const auto *data = this->user_data()) { + if (data->modifier_data) { + return data->modifier_data->self_object; + } + } + return nullptr; } Depsgraph *depsgraph() const { - return provider_->depsgraph; + if (const auto *data = this->user_data()) { + if (data->modifier_data) { + return data->modifier_data->depsgraph; + } + } + return nullptr; + } + + GeoNodesLFUserData *user_data() const + { + return dynamic_cast<GeoNodesLFUserData *>(lf_context_.user_data); } /** @@ -306,7 +243,7 @@ class GeoNodeExecParams { void set_default_remaining_outputs(); - void used_named_attribute(std::string attribute_name, eNamedAttrUsage usage); + void used_named_attribute(std::string attribute_name, NamedAttributeUsage usage); private: /* Utilities for detecting common errors at when using this class. */ @@ -315,6 +252,38 @@ class GeoNodeExecParams { /* Find the active socket with the input name (not the identifier). */ const bNodeSocket *find_available_socket(const StringRef name) const; + + int get_input_index(const StringRef identifier) const + { + int counter = 0; + for (const bNodeSocket *socket : node_.input_sockets()) { + if (!socket->is_available()) { + continue; + } + if (socket->identifier == identifier) { + return counter; + } + counter++; + } + BLI_assert_unreachable(); + return -1; + } + + int get_output_index(const StringRef identifier) const + { + int counter = 0; + for (const bNodeSocket *socket : node_.output_sockets()) { + if (!socket->is_available()) { + continue; + } + if (socket->identifier == identifier) { + return counter; + } + counter++; + } + BLI_assert_unreachable(); + return -1; + } }; } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh deleted file mode 100644 index 46ba72d14d8..00000000000 --- a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh +++ /dev/null @@ -1,411 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -/** - * Many geometry nodes related UI features need access to data produced during evaluation. Not only - * is the final output required but also the intermediate results. Those features include - * attribute search, node warnings, socket inspection and the viewer node. - * - * This file provides the framework for logging data during evaluation and accessing the data after - * evaluation. - * - * During logging every thread gets its own local logger to avoid too much locking (logging - * generally happens for every socket). After geometry nodes evaluation is done, the thread-local - * logging information is combined and post-processed to make it easier for the UI to lookup. - * necessary information. - */ - -#include "BLI_enumerable_thread_specific.hh" -#include "BLI_function_ref.hh" -#include "BLI_generic_pointer.hh" -#include "BLI_linear_allocator.hh" -#include "BLI_map.hh" - -#include "BKE_geometry_set.hh" - -#include "NOD_derived_node_tree.hh" - -#include "FN_field.hh" - -#include <chrono> - -struct SpaceNode; -struct SpaceSpreadsheet; - -namespace blender::nodes::geometry_nodes_eval_log { - -/** Contains information about a value that has been computed during geometry nodes evaluation. */ -class ValueLog { - public: - virtual ~ValueLog() = default; -}; - -/** Contains an owned copy of a value of a generic type. */ -class GenericValueLog : public ValueLog { - private: - GMutablePointer data_; - - public: - GenericValueLog(GMutablePointer data) : data_(data) - { - } - - ~GenericValueLog() - { - data_.destruct(); - } - - GPointer value() const - { - return data_; - } -}; - -class GFieldValueLog : public ValueLog { - private: - fn::GField field_; - const CPPType &type_; - Vector<std::string> input_tooltips_; - - public: - GFieldValueLog(fn::GField field, bool log_full_field); - - const fn::GField &field() const - { - return field_; - } - - Span<std::string> input_tooltips() const - { - return input_tooltips_; - } - - const CPPType &type() const - { - return type_; - } -}; - -struct GeometryAttributeInfo { - std::string name; - /** Can be empty when #name does not actually exist on a geometry yet. */ - std::optional<eAttrDomain> domain; - std::optional<eCustomDataType> data_type; -}; - -/** Contains information about a geometry set. In most cases this does not store the entire - * geometry set as this would require too much memory. */ -class GeometryValueLog : public ValueLog { - private: - Vector<GeometryAttributeInfo> attributes_; - Vector<GeometryComponentType> component_types_; - std::unique_ptr<GeometrySet> full_geometry_; - - public: - struct MeshInfo { - int verts_num, edges_num, faces_num; - }; - struct CurveInfo { - int splines_num; - }; - struct PointCloudInfo { - int points_num; - }; - struct InstancesInfo { - int instances_num; - }; - struct EditDataInfo { - bool has_deformed_positions; - bool has_deform_matrices; - }; - - std::optional<MeshInfo> mesh_info; - std::optional<CurveInfo> curve_info; - std::optional<PointCloudInfo> pointcloud_info; - std::optional<InstancesInfo> instances_info; - std::optional<EditDataInfo> edit_data_info; - - GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry = false); - - Span<GeometryAttributeInfo> attributes() const - { - return attributes_; - } - - Span<GeometryComponentType> component_types() const - { - return component_types_; - } - - const GeometrySet *full_geometry() const - { - return full_geometry_.get(); - } -}; - -enum class NodeWarningType { - Error, - Warning, - Info, -}; - -struct NodeWarning { - NodeWarningType type; - std::string message; -}; - -struct NodeWithWarning { - DNode node; - NodeWarning warning; -}; - -struct NodeWithExecutionTime { - DNode node; - std::chrono::microseconds exec_time; -}; - -struct NodeWithDebugMessage { - DNode node; - std::string message; -}; - -/** The same value can be referenced by multiple sockets when they are linked. */ -struct ValueOfSockets { - Span<DSocket> sockets; - destruct_ptr<ValueLog> value; -}; - -enum class eNamedAttrUsage { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Remove = 1 << 2, -}; -ENUM_OPERATORS(eNamedAttrUsage, eNamedAttrUsage::Remove); - -struct UsedNamedAttribute { - std::string name; - eNamedAttrUsage usage; -}; - -struct NodeWithUsedNamedAttribute { - DNode node; - UsedNamedAttribute attribute; -}; - -class GeoLogger; -class ModifierLog; - -/** Every thread has its own local logger to avoid having to communicate between threads during - * evaluation. After evaluation the individual logs are combined. */ -class LocalGeoLogger { - private: - /* Back pointer to the owner of this local logger. */ - GeoLogger *main_logger_; - /* Allocator for the many small allocations during logging. This is in a `unique_ptr` so that - * ownership can be transferred later on. */ - std::unique_ptr<LinearAllocator<>> allocator_; - Vector<ValueOfSockets> values_; - Vector<NodeWithWarning> node_warnings_; - Vector<NodeWithExecutionTime> node_exec_times_; - Vector<NodeWithDebugMessage> node_debug_messages_; - Vector<NodeWithUsedNamedAttribute> used_named_attributes_; - - friend ModifierLog; - - public: - LocalGeoLogger(GeoLogger &main_logger) : main_logger_(&main_logger) - { - this->allocator_ = std::make_unique<LinearAllocator<>>(); - } - - void log_value_for_sockets(Span<DSocket> sockets, GPointer value); - void log_multi_value_socket(DSocket socket, Span<GPointer> values); - void log_node_warning(DNode node, NodeWarningType type, std::string message); - void log_execution_time(DNode node, std::chrono::microseconds exec_time); - void log_used_named_attribute(DNode node, std::string attribute_name, eNamedAttrUsage usage); - /** - * Log a message that will be displayed in the node editor next to the node. - * This should only be used for debugging purposes and not to display information to users. - */ - void log_debug_message(DNode node, std::string message); -}; - -/** The root logger class. */ -class GeoLogger { - private: - /** - * Log the entire value for these sockets, because they may be inspected afterwards. - * We don't log everything, because that would take up too much memory and cause significant - * slowdowns. - */ - Set<DSocket> log_full_sockets_; - threading::EnumerableThreadSpecific<LocalGeoLogger> threadlocals_; - - /* These are only optional since they don't have a default constructor. */ - std::unique_ptr<GeometryValueLog> input_geometry_log_; - std::unique_ptr<GeometryValueLog> output_geometry_log_; - - friend LocalGeoLogger; - friend ModifierLog; - - public: - GeoLogger(Set<DSocket> log_full_sockets) - : log_full_sockets_(std::move(log_full_sockets)), - threadlocals_([this]() { return LocalGeoLogger(*this); }) - { - } - - void log_input_geometry(const GeometrySet &geometry) - { - input_geometry_log_ = std::make_unique<GeometryValueLog>(geometry); - } - - void log_output_geometry(const GeometrySet &geometry) - { - output_geometry_log_ = std::make_unique<GeometryValueLog>(geometry); - } - - LocalGeoLogger &local() - { - return threadlocals_.local(); - } - - auto begin() - { - return threadlocals_.begin(); - } - - auto end() - { - return threadlocals_.end(); - } -}; - -/** Contains information that has been logged for one specific socket. */ -class SocketLog { - private: - ValueLog *value_ = nullptr; - - friend ModifierLog; - - public: - const ValueLog *value() const - { - return value_; - } -}; - -/** Contains information that has been logged for one specific node. */ -class NodeLog { - private: - Vector<SocketLog> input_logs_; - Vector<SocketLog> output_logs_; - Vector<NodeWarning, 0> warnings_; - Vector<std::string, 0> debug_messages_; - Vector<UsedNamedAttribute, 0> used_named_attributes_; - std::chrono::microseconds exec_time_; - - friend ModifierLog; - - public: - const SocketLog *lookup_socket_log(eNodeSocketInOut in_out, int index) const; - const SocketLog *lookup_socket_log(const bNode &node, const bNodeSocket &socket) const; - void execution_time(std::chrono::microseconds exec_time); - - Span<SocketLog> input_logs() const - { - return input_logs_; - } - - Span<SocketLog> output_logs() const - { - return output_logs_; - } - - Span<NodeWarning> warnings() const - { - return warnings_; - } - - Span<std::string> debug_messages() const - { - return debug_messages_; - } - - Span<UsedNamedAttribute> used_named_attributes() const - { - return used_named_attributes_; - } - - std::chrono::microseconds execution_time() const - { - return exec_time_; - } - - Vector<const GeometryAttributeInfo *> lookup_available_attributes() const; -}; - -/** Contains information that has been logged for one specific tree. */ -class TreeLog { - private: - Map<std::string, destruct_ptr<NodeLog>> node_logs_; - Map<std::string, destruct_ptr<TreeLog>> child_logs_; - - friend ModifierLog; - - public: - const NodeLog *lookup_node_log(StringRef node_name) const; - const NodeLog *lookup_node_log(const bNode &node) const; - const TreeLog *lookup_child_log(StringRef node_name) const; - void foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const; -}; - -/** Contains information about an entire geometry nodes evaluation. */ -class ModifierLog { - private: - LinearAllocator<> allocator_; - /* Allocators of the individual loggers. */ - Vector<std::unique_ptr<LinearAllocator<>>> logger_allocators_; - destruct_ptr<TreeLog> root_tree_logs_; - Vector<destruct_ptr<ValueLog>> logged_values_; - - std::unique_ptr<GeometryValueLog> input_geometry_log_; - std::unique_ptr<GeometryValueLog> output_geometry_log_; - - public: - ModifierLog(GeoLogger &logger); - - const TreeLog &root_tree() const - { - return *root_tree_logs_; - } - - /* Utilities to find logged information for a specific context. */ - static const ModifierLog *find_root_by_node_editor_context(const SpaceNode &snode); - static const TreeLog *find_tree_by_node_editor_context(const SpaceNode &snode); - static const NodeLog *find_node_by_node_editor_context(const SpaceNode &snode, - const bNode &node); - static const NodeLog *find_node_by_node_editor_context(const SpaceNode &snode, - const StringRef node_name); - static const SocketLog *find_socket_by_node_editor_context(const SpaceNode &snode, - const bNode &node, - const bNodeSocket &socket); - static const NodeLog *find_node_by_spreadsheet_editor_context( - const SpaceSpreadsheet &sspreadsheet); - void foreach_node_log(FunctionRef<void(const NodeLog &)> fn) const; - - const GeometryValueLog *input_geometry_log() const; - const GeometryValueLog *output_geometry_log() const; - - private: - using LogByTreeContext = Map<const DTreeContext *, TreeLog *>; - - TreeLog &lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context, - const DTreeContext &tree_context); - NodeLog &lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node); - SocketLog &lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context, DSocket socket); -}; - -} // namespace blender::nodes::geometry_nodes_eval_log diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh new file mode 100644 index 00000000000..3137dc41857 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** + * For evaluation, geometry node groups are converted to a lazy-function graph. The generated graph + * is cached per node group, so it only has to be generated once after a change. + * + * Node groups are *not* inlined into the lazy-function graph. This could be added in the future as + * it might improve performance in some cases, but generally does not seem necessary. Inlining node + * groups also has disadvantages like making per-node-group caches less useful, resulting in more + * overhead. + * + * Instead, group nodes are just like all other nodes in the lazy-function graph. What makes them + * special is that they reference the lazy-function graph of the group they reference. + * + * During lazy-function graph generation, a mapping between the #bNodeTree and + * #lazy_function::Graph is build that can be used when evaluating the graph (e.g. for logging). + */ + +#include "FN_lazy_function_graph.hh" +#include "FN_lazy_function_graph_executor.hh" + +#include "NOD_geometry_nodes_log.hh" +#include "NOD_multi_function.hh" + +#include "BLI_compute_context.hh" + +struct Object; +struct Depsgraph; + +namespace blender::nodes { + +namespace lf = fn::lazy_function; +using lf::LazyFunction; + +/** + * Data that is passed into geometry nodes evaluation from the modifier. + */ +struct GeoNodesModifierData { + /** Object that is currently evaluated. */ + const Object *self_object = nullptr; + /** Depsgraph that is evaluating the modifier. */ + Depsgraph *depsgraph = nullptr; + /** Optional logger. */ + geo_eval_log::GeoModifierLog *eval_log = nullptr; + /** + * Some nodes should be executed even when their output is not used (e.g. active viewer nodes and + * the node groups they are contained in). + */ + const MultiValueMap<ComputeContextHash, const lf::FunctionNode *> *side_effect_nodes; +}; + +/** + * Custom user data that is passed to every geometry nodes related lazy-function evaluation. + */ +struct GeoNodesLFUserData : public lf::UserData { + /** + * Data from the modifier that is being evaluated. + */ + GeoNodesModifierData *modifier_data = nullptr; + /** + * Current compute context. This is different depending in the (nested) node group that is being + * evaluated. + */ + const ComputeContext *compute_context = nullptr; +}; + +/** + * Contains the mapping between the #bNodeTree and the corresponding lazy-function graph. + * This is *not* a one-to-one mapping. + */ +struct GeometryNodeLazyFunctionGraphMapping { + /** + * Contains mapping of sockets for special nodes like group input and group output. + */ + Map<const bNodeSocket *, lf::Socket *> dummy_socket_map; + /** + * The inputs sockets in the graph. Multiple group input nodes are combined into one in the + * lazy-function graph. + */ + Vector<lf::OutputSocket *> group_input_sockets; + /** + * A mapping used for logging intermediate values. + */ + MultiValueMap<const lf::Socket *, const bNodeSocket *> bsockets_by_lf_socket_map; + /** + * Mappings for some special node types. Generally, this mapping does not exist for all node + * types, so better have more specialized mappings for now. + */ + Map<const bNode *, const lf::FunctionNode *> group_node_map; + Map<const bNode *, const lf::FunctionNode *> viewer_node_map; +}; + +/** + * Data that is cached for every #bNodeTree. + */ +struct GeometryNodesLazyFunctionGraphInfo { + /** + * Allocator used for many things contained in this struct. + */ + LinearAllocator<> allocator; + /** + * Many nodes are implemented as multi-functions. So this contains a mapping from nodes to their + * corresponding multi-functions. + */ + std::unique_ptr<NodeMultiFunctions> node_multi_functions; + /** + * Many lazy-functions are build for the lazy-function graph. Since the graph does not own them, + * we have to keep track of them separately. + */ + Vector<std::unique_ptr<LazyFunction>> functions; + /** + * Many sockets have default values. Since those are not owned by the lazy-function graph, we + * have to keep track of them separately. This only owns the values, the memory is owned by the + * allocator above. + */ + Vector<GMutablePointer> values_to_destruct; + /** + * The actual lazy-function graph. + */ + lf::Graph graph; + /** + * Mappings between the lazy-function graph and the #bNodeTree. + */ + GeometryNodeLazyFunctionGraphMapping mapping; + + GeometryNodesLazyFunctionGraphInfo(); + ~GeometryNodesLazyFunctionGraphInfo(); +}; + +/** + * Logs intermediate values from the lazy-function graph evaluation into #GeoModifierLog based on + * the mapping between the lazy-function graph and the corresponding #bNodeTree. + */ +class GeometryNodesLazyFunctionLogger : public fn::lazy_function::GraphExecutor::Logger { + private: + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_; + + public: + GeometryNodesLazyFunctionLogger(const GeometryNodesLazyFunctionGraphInfo &lf_graph_info); + void log_socket_value(const fn::lazy_function::Socket &lf_socket, + GPointer value, + const fn::lazy_function::Context &context) const override; + void dump_when_outputs_are_missing(const lf::FunctionNode &node, + Span<const lf::OutputSocket *> missing_sockets, + const lf::Context &context) const override; + void dump_when_input_is_set_twice(const lf::InputSocket &target_socket, + const lf::OutputSocket &from_socket, + const lf::Context &context) const override; +}; + +/** + * Tells the lazy-function graph evaluator which nodes have side effects based on the current + * context. For example, the same viewer node can have side effects in one context, but not in + * another (depending on e.g. which tree path is currently viewed in the node editor). + */ +class GeometryNodesLazyFunctionSideEffectProvider + : public fn::lazy_function::GraphExecutor::SideEffectProvider { + private: + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_; + + public: + GeometryNodesLazyFunctionSideEffectProvider( + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info); + Vector<const lf::FunctionNode *> get_nodes_with_side_effects( + const lf::Context &context) const override; +}; + +/** + * Main function that converts a #bNodeTree into a lazy-function graph. If the graph has been + * generated already, nothing is done. Under some circumstances a valid graph cannot be created. In + * those cases null is returned. + */ +const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph( + const bNodeTree &btree); + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh new file mode 100644 index 00000000000..f48d38ecbbf --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** + * Many geometry nodes related UI features need access to data produced during evaluation. Not only + * is the final output required but also the intermediate results. Those features include attribute + * search, node warnings, socket inspection and the viewer node. + * + * This file provides the system for logging data during evaluation and accessing the data after + * evaluation. Geometry nodes is executed by a modifier, therefore the "root" of logging is + * #GeoModifierLog which will contain all data generated in a modifier. + * + * The system makes a distinction between "loggers" and the "log": + * - Logger (#GeoTreeLogger): Is used during geometry nodes evaluation. Each thread logs data + * independently to avoid communication between threads. Logging should generally be fast. + * Generally, the logged data is just dumped into simple containers. Any processing of the data + * happens later if necessary. This is important for performance, because in practice, most of + * the logged data is never used again. So any processing of the data is likely to be a waste of + * resources. + * - Log (#GeoTreeLog, #GeoNodeLog): Those are used when accessing logged data in UI code. They + * contain and cache preprocessed data produced during logging. The log combines data from all + * threadlocal loggers to provide simple access. Importantly, the (preprocessed) log is only + * created when it is actually used by UI code. + */ + +#include <chrono> + +#include "BLI_compute_context.hh" +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_generic_pointer.hh" +#include "BLI_multi_value_map.hh" + +#include "BKE_attribute.h" +#include "BKE_geometry_set.hh" + +#include "FN_field.hh" + +#include "DNA_node_types.h" + +struct SpaceNode; +struct SpaceSpreadsheet; +struct NodesModifierData; + +namespace blender::nodes::geo_eval_log { + +using fn::GField; + +enum class NodeWarningType { + Error, + Warning, + Info, +}; + +struct NodeWarning { + NodeWarningType type; + std::string message; +}; + +enum class NamedAttributeUsage { + None = 0, + Read = 1 << 0, + Write = 1 << 1, + Remove = 1 << 2, +}; +ENUM_OPERATORS(NamedAttributeUsage, NamedAttributeUsage::Remove); + +/** + * Values of different types are logged differently. This is necesary because some types are so + * simple that we can log them entirely (e.g. `int`), while we don't want to log all intermediate + * geometries in their entirety. + * + * #ValueLog is a base class for the different ways we log values. + */ +class ValueLog { + public: + virtual ~ValueLog() = default; +}; + +/** + * Simplest logger. It just stores a copy of the entire value. This is used for most simple types + * like `int`. + */ +class GenericValueLog : public ValueLog { + public: + /** + * This is owning the value, but not the memory. + */ + GMutablePointer value; + + GenericValueLog(const GMutablePointer value) : value(value) + { + } + + ~GenericValueLog(); +}; + +/** + * Fields are not logged entirely, because they might contain arbitrarily large data (e.g. + * geometries that are sampled). Instead, only the data needed for ui features is logged. + */ +class FieldInfoLog : public ValueLog { + public: + const CPPType &type; + Vector<std::string> input_tooltips; + + FieldInfoLog(const GField &field); +}; + +struct GeometryAttributeInfo { + std::string name; + /** Can be empty when #name does not actually exist on a geometry yet. */ + std::optional<eAttrDomain> domain; + std::optional<eCustomDataType> data_type; +}; + +/** + * Geometries are not logged entirely, because that would result in a lot of time and memory + * overhead. Instead, only the data needed for ui features is logged. + */ +class GeometryInfoLog : public ValueLog { + public: + Vector<GeometryAttributeInfo> attributes; + Vector<GeometryComponentType> component_types; + + struct MeshInfo { + int verts_num, edges_num, faces_num; + }; + struct CurveInfo { + int splines_num; + }; + struct PointCloudInfo { + int points_num; + }; + struct InstancesInfo { + int instances_num; + }; + struct EditDataInfo { + bool has_deformed_positions; + bool has_deform_matrices; + }; + + std::optional<MeshInfo> mesh_info; + std::optional<CurveInfo> curve_info; + std::optional<PointCloudInfo> pointcloud_info; + std::optional<InstancesInfo> instances_info; + std::optional<EditDataInfo> edit_data_info; + + GeometryInfoLog(const GeometrySet &geometry_set); +}; + +/** + * Data logged by a viewer node when it is executed. In this case, we do want to log the entire + * geometry. + */ +class ViewerNodeLog { + public: + GeometrySet geometry; + GField field; +}; + +using Clock = std::chrono::steady_clock; +using TimePoint = Clock::time_point; + +/** + * Logs all data for a specific geometry node tree in a specific context. When the same node group + * is used in multiple times each instantiation will have a separate logger. + */ +class GeoTreeLogger { + public: + std::optional<ComputeContextHash> parent_hash; + std::optional<std::string> group_node_name; + Vector<ComputeContextHash> children_hashes; + + LinearAllocator<> *allocator = nullptr; + + struct WarningWithNode { + std::string node_name; + NodeWarning warning; + }; + struct SocketValueLog { + std::string node_name; + std::string socket_identifier; + destruct_ptr<ValueLog> value; + }; + struct NodeExecutionTime { + std::string node_name; + TimePoint start; + TimePoint end; + }; + struct ViewerNodeLogWithNode { + std::string node_name; + destruct_ptr<ViewerNodeLog> viewer_log; + }; + struct AttributeUsageWithNode { + std::string node_name; + std::string attribute_name; + NamedAttributeUsage usage; + }; + struct DebugMessage { + std::string node_name; + std::string message; + }; + + Vector<WarningWithNode> node_warnings; + Vector<SocketValueLog> input_socket_values; + Vector<SocketValueLog> output_socket_values; + Vector<NodeExecutionTime> node_execution_times; + Vector<ViewerNodeLogWithNode, 0> viewer_node_logs; + Vector<AttributeUsageWithNode, 0> used_named_attributes; + Vector<DebugMessage, 0> debug_messages; + + GeoTreeLogger(); + ~GeoTreeLogger(); + + void log_value(const bNode &node, const bNodeSocket &socket, GPointer value); + void log_viewer_node(const bNode &viewer_node, const GeometrySet &geometry, const GField &field); +}; + +/** + * Contains data that has been logged for a specific node in a context. So when the node is in a + * node group that is used multiple times, there will be a different #GeoNodeLog for every + * instance. + * + * By default, not all of the info below is valid. A #GeoTreeLog::ensure_* method has to be called + * first. + */ +class GeoNodeLog { + public: + /** Warnings generated for that node. */ + Vector<NodeWarning> warnings; + /** + * Time spend in that node. For node groups this is the sum of the run times of the nodes + * inside. + */ + std::chrono::nanoseconds run_time{0}; + /** Maps from socket identifiers to their values. */ + Map<std::string, ValueLog *> input_values_; + Map<std::string, ValueLog *> output_values_; + /** Maps from attribute name to their usage flags. */ + Map<std::string, NamedAttributeUsage> used_named_attributes; + /** Messages that are used for debugging purposes during development. */ + Vector<std::string> debug_messages; + + GeoNodeLog(); + ~GeoNodeLog(); +}; + +class GeoModifierLog; + +/** + * Contains data that has been logged for a specific node group in a context. If the same node + * group is used multiple times, there will be a different #GeoTreeLog for every instance. + * + * This contains lazily evaluated data. Call the corresponding `ensure_*` methods before accessing + * data. + */ +class GeoTreeLog { + private: + GeoModifierLog *modifier_log_; + Vector<GeoTreeLogger *> tree_loggers_; + VectorSet<ComputeContextHash> children_hashes_; + bool reduced_node_warnings_ = false; + bool reduced_node_run_times_ = false; + bool reduced_socket_values_ = false; + bool reduced_viewer_node_logs_ = false; + bool reduced_existing_attributes_ = false; + bool reduced_used_named_attributes_ = false; + bool reduced_debug_messages_ = false; + + public: + Map<std::string, GeoNodeLog> nodes; + Map<std::string, ViewerNodeLog *, 0> viewer_node_logs; + Vector<NodeWarning> all_warnings; + std::chrono::nanoseconds run_time_sum{0}; + Vector<const GeometryAttributeInfo *> existing_attributes; + Map<std::string, NamedAttributeUsage> used_named_attributes; + + GeoTreeLog(GeoModifierLog *modifier_log, Vector<GeoTreeLogger *> tree_loggers); + ~GeoTreeLog(); + + void ensure_node_warnings(); + void ensure_node_run_time(); + void ensure_socket_values(); + void ensure_viewer_node_logs(); + void ensure_existing_attributes(); + void ensure_used_named_attributes(); + void ensure_debug_messages(); + + ValueLog *find_socket_value_log(const bNodeSocket &query_socket); +}; + +/** + * There is one #GeoModifierLog for every modifier that evaluates geometry nodes. It contains all + * the loggers that are used during evaluation as well as the preprocessed logs that are used by UI + * code. + */ +class GeoModifierLog { + private: + /** Data that is stored for each thread. */ + struct LocalData { + /** Each thread has its own allocator. */ + LinearAllocator<> allocator; + /** + * Store a separate #GeoTreeLogger for each instance of the corresponding node group (e.g. + * when the same node group is used multiple times). + */ + Map<ComputeContextHash, destruct_ptr<GeoTreeLogger>> tree_logger_by_context; + }; + + /** Container for all threadlocal data. */ + threading::EnumerableThreadSpecific<LocalData> data_per_thread_; + /** + * A #GeoTreeLog for every compute context. Those are created lazily when requested by UI code. + */ + Map<ComputeContextHash, std::unique_ptr<GeoTreeLog>> tree_logs_; + + public: + GeoModifierLog(); + ~GeoModifierLog(); + + /** + * Get a threadlocal logger for the current node tree. + */ + GeoTreeLogger &get_local_tree_logger(const ComputeContext &compute_context); + + /** + * Get a log a specific node tree instance. + */ + GeoTreeLog &get_tree_log(const ComputeContextHash &compute_context_hash); + + /** + * Utility accessor to logged data. + */ + static GeoTreeLog *get_tree_log_for_node_editor(const SpaceNode &snode); + static const ViewerNodeLog *find_viewer_node_log_for_spreadsheet( + const SpaceSpreadsheet &sspreadsheet); +}; + +} // namespace blender::nodes::geo_eval_log diff --git a/source/blender/nodes/NOD_multi_function.hh b/source/blender/nodes/NOD_multi_function.hh index 21a94d9192b..676bf03927e 100644 --- a/source/blender/nodes/NOD_multi_function.hh +++ b/source/blender/nodes/NOD_multi_function.hh @@ -6,8 +6,6 @@ #include "DNA_node_types.h" -#include "NOD_derived_node_tree.hh" - namespace blender::nodes { using namespace fn::multi_function_types; @@ -60,9 +58,9 @@ class NodeMultiFunctions { Map<const bNode *, Item> map_; public: - NodeMultiFunctions(const DerivedNodeTree &tree); + NodeMultiFunctions(const bNodeTree &tree); - const Item &try_get(const DNode &node) const; + const Item &try_get(const bNode &node) const; }; /* -------------------------------------------------------------------- */ @@ -107,10 +105,10 @@ inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...ar /** \name #NodeMultiFunctions Inline Methods * \{ */ -inline const NodeMultiFunctions::Item &NodeMultiFunctions::try_get(const DNode &node) const +inline const NodeMultiFunctions::Item &NodeMultiFunctions::try_get(const bNode &node) const { static Item empty_item; - const Item *item = map_.lookup_ptr(node.bnode()); + const Item *item = map_.lookup_ptr(&node); if (item == nullptr) { return empty_item; } diff --git a/source/blender/nodes/geometry/node_geometry_exec.cc b/source/blender/nodes/geometry/node_geometry_exec.cc index 58ded7aadd2..ef4daf94bbe 100644 --- a/source/blender/nodes/geometry/node_geometry_exec.cc +++ b/source/blender/nodes/geometry/node_geometry_exec.cc @@ -4,3 +4,4 @@ #include "NOD_geometry_exec.hh" BLI_CPP_TYPE_MAKE(GeometrySet, GeometrySet, CPPTypeFlags::Printable); +BLI_CPP_TYPE_MAKE(GeometrySetVector, blender::Vector<GeometrySet>, CPPTypeFlags::None); diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index a6af74645b6..c8c58945bce 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -93,7 +93,7 @@ static void node_geo_exec(GeoNodeExecParams params) /* The instance transform matrices are owned by the instance group, so we have to * keep all of them around for use during the boolean operation. */ Vector<bke::GeometryInstanceGroup> set_groups; - Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Mesh 2"); + Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Mesh 2"); for (const GeometrySet &geometry_set : geometry_sets) { bke::geometry_set_gather_instances(geometry_set, set_groups); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc index 1f84f8f288d..8e64209a418 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_geometry_to_instance.cc @@ -12,7 +12,7 @@ static void node_declare(NodeDeclarationBuilder &b) static void node_geo_exec(GeoNodeExecParams params) { - Vector<GeometrySet> geometries = params.extract_multi_input<GeometrySet>("Geometry"); + Vector<GeometrySet> geometries = params.extract_input<Vector<GeometrySet>>("Geometry"); GeometrySet instances_geometry; InstancesComponent &instances_component = instances_geometry.get_component_for_write<InstancesComponent>(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc index 122c7b352c7..da09d3650e3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc @@ -88,7 +88,7 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - params.used_named_attribute(name, eNamedAttrUsage::Read); + params.used_named_attribute(name, NamedAttributeUsage::Read); switch (data_type) { case CD_PROP_FLOAT: diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 023d7a32a61..9fdf7fe7d31 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -177,7 +177,7 @@ static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet static void node_geo_exec(GeoNodeExecParams params) { - Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Geometry"); + Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Geometry"); GeometrySet geometry_set_result; join_component_type<MeshComponent>(geometry_sets, geometry_set_result); diff --git a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc index ee279ba58f9..1b398f63691 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc @@ -55,7 +55,7 @@ static void node_geo_exec(GeoNodeExecParams params) }); if (attribute_exists && !cannot_delete) { - params.used_named_attribute(name, eNamedAttrUsage::Remove); + params.used_named_attribute(name, NamedAttributeUsage::Remove); } if (!attribute_exists) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index c2d6f57ce8a..2a590f5bf4a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -149,7 +149,7 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - params.used_named_attribute(name, eNamedAttrUsage::Write); + params.used_named_attribute(name, NamedAttributeUsage::Write); const NodeGeometryStoreNamedAttribute &storage = node_storage(params.node()); const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type); diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc index bb33430a02f..09c01b8c627 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc @@ -13,12 +13,13 @@ static void node_declare(NodeDeclarationBuilder &b) static void node_geo_exec(GeoNodeExecParams params) { - Vector<std::string> strings = params.extract_multi_input<std::string>("Strings"); + Vector<fn::ValueOrField<std::string>> strings = + params.extract_input<Vector<fn::ValueOrField<std::string>>>("Strings"); const std::string delim = params.extract_input<std::string>("Delimiter"); std::string output; for (const int i : strings.index_range()) { - output += strings[i]; + output += strings[i].as_value(); if (i < (strings.size() - 1)) { output += delim; } 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 89bfa5834e8..00000000000 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ /dev/null @@ -1,520 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "NOD_geometry_nodes_eval_log.hh" - -#include "BKE_curves.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->input_sockets().size()); - node_log->output_logs_.resize(node->output_sockets().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 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()) { - 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; - } - } - } - 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..442b17c6962 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -0,0 +1,1327 @@ +/* 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_lazyness = node.typeinfo->geometry_node_execute_supports_laziness || + node.is_group(); + const lf::ValueUsage input_usage = supports_lazyness ? 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 bNode &node_; + 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) + : node_(node), 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(lf_graph_info); + 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 + { + return graph_executor_->init_storage(allocator); + } + + void destruct_storage(void *storage) const + { + 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()); +} + +GeometryNodesLazyFunctionSideEffectProvider::GeometryNodesLazyFunctionSideEffectProvider( + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info) + : lf_graph_info_(lf_graph_info) +{ +} + +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..2e0ac746ac0 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -0,0 +1,607 @@ +/* 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(last_node_context.node_name); + return viewer_log; +} + +} // namespace blender::nodes::geo_eval_log diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index 953dce035c2..1833774fe33 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -11,34 +11,27 @@ #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).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; @@ -118,7 +111,7 @@ void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_se const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { - for (const bNodeSocket *socket : provider_->dnode->runtime->inputs) { + for (const bNodeSocket *socket : node_.input_sockets()) { if (socket->is_available() && socket->name == name) { return socket; } @@ -129,19 +122,19 @@ 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 { const bNodeSocket *found_socket = nullptr; - for (const bNodeSocket *socket : provider_->dnode->input_sockets()) { + for (const bNodeSocket *socket : node_.input_sockets()) { if (socket->identifier == identifier) { found_socket = socket; break; @@ -151,7 +144,7 @@ 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 bNodeSocket *socket : provider_->dnode->input_sockets()) { + for (const bNodeSocket *socket : node_.input_sockets()) { if (socket->is_available()) { std::cout << "'" << socket->identifier << "', "; } @@ -164,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 '" @@ -183,7 +170,7 @@ void GeoNodeExecParams::check_input_access(StringRef identifier, void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const { const bNodeSocket *found_socket = nullptr; - for (const bNodeSocket *socket : provider_->dnode->output_sockets()) { + for (const bNodeSocket *socket : node_.output_sockets()) { if (socket->identifier == identifier) { found_socket = socket; break; @@ -193,8 +180,8 @@ 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 bNodeSocket *socket : provider_->dnode->output_sockets()) { - if (!(socket->flag & SOCK_UNAVAIL)) { + for (const bNodeSocket *socket : node_.output_sockets()) { + if (socket->is_available()) { std::cout << "'" << socket->identifier << "', "; } } @@ -206,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 1f8397923e9..d731fe8f877 100644 --- a/source/blender/nodes/intern/node_multi_function.cc +++ b/source/blender/nodes/intern/node_multi_function.cc @@ -3,21 +3,21 @@ #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 bNodeTree *btree : tree.used_btrees()) { - for (const bNode *bnode : btree->all_nodes()) { - 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_)}); } } } |