/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once /** * 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_linear_allocator.hh" #include "BLI_map.hh" #include "BKE_geometry_set.hh" #include "FN_generic_pointer.hh" #include "NOD_derived_node_tree.hh" #include struct SpaceNode; struct SpaceSpreadsheet; namespace blender::nodes::geometry_nodes_eval_log { using fn::GMutablePointer; using fn::GPointer; /** 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 fn::CPPType &type_; Vector input_tooltips_; public: GFieldValueLog(fn::GField field, bool log_full_field); const fn::GField &field() const { return field_; } Span input_tooltips() const { return input_tooltips_; } const fn::CPPType &type() const { return type_; } }; struct GeometryAttributeInfo { std::string name; AttributeDomain domain; CustomDataType 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 attributes_; Vector component_types_; std::unique_ptr full_geometry_; public: struct MeshInfo { int tot_verts, tot_edges, tot_faces; }; struct CurveInfo { int tot_splines; }; struct PointCloudInfo { int tot_points; }; struct InstancesInfo { int tot_instances; }; std::optional mesh_info; std::optional curve_info; std::optional pointcloud_info; std::optional instances_info; GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry = false); Span attributes() const { return attributes_; } Span component_types() const { return component_types_; } const GeometrySet *full_geometry() const { return full_geometry_.get(); } }; enum class NodeWarningType { Error, Warning, Info, Legacy, }; 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 sockets; destruct_ptr value; }; 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> allocator_; Vector values_; Vector node_warnings_; Vector node_exec_times_; Vector node_debug_messages_; friend ModifierLog; public: LocalGeoLogger(GeoLogger &main_logger) : main_logger_(&main_logger) { this->allocator_ = std::make_unique>(); } void log_value_for_sockets(Span sockets, GPointer value); void log_multi_value_socket(DSocket socket, Span values); void log_node_warning(DNode node, NodeWarningType type, std::string message); void log_execution_time(DNode node, std::chrono::microseconds exec_time); /** * 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 log_full_sockets_; threading::EnumerableThreadSpecific threadlocals_; /* These are only optional since they don't have a default constructor. */ std::unique_ptr input_geometry_log_; std::unique_ptr output_geometry_log_; friend LocalGeoLogger; friend ModifierLog; public: GeoLogger(Set 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(geometry); } void log_output_geometry(const GeometrySet &geometry) { output_geometry_log_ = std::make_unique(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 input_logs_; Vector output_logs_; Vector warnings_; Vector debug_messages_; 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 input_logs() const { return input_logs_; } Span output_logs() const { return output_logs_; } Span warnings() const { return warnings_; } Span debug_messages() const { return debug_messages_; } std::chrono::microseconds execution_time() const { return exec_time_; } Vector lookup_available_attributes() const; }; /** Contains information that has been logged for one specific tree. */ class TreeLog { private: Map> node_logs_; Map> 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 fn) const; }; /** Contains information about an entire geometry nodes evaluation. */ class ModifierLog { private: LinearAllocator<> allocator_; /* Allocators of the individual loggers. */ Vector>> logger_allocators_; destruct_ptr root_tree_logs_; Vector> logged_values_; std::unique_ptr input_geometry_log_; std::unique_ptr 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 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 fn) const; const GeometryValueLog *input_geometry_log() const; const GeometryValueLog *output_geometry_log() const; private: using LogByTreeContext = Map; 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