tornavis/source/blender/nodes/NOD_geometry_nodes_eval_log.hh

395 lines
11 KiB
C++

/*
* 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 <chrono>
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<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 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<GeometryAttributeInfo> attributes_;
Vector<GeometryComponentType> component_types_;
std::unique_ptr<GeometrySet> 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<MeshInfo> mesh_info;
std::optional<CurveInfo> curve_info;
std::optional<PointCloudInfo> pointcloud_info;
std::optional<InstancesInfo> instances_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,
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<DSocket> sockets;
destruct_ptr<ValueLog> 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<LinearAllocator<>> allocator_;
Vector<ValueOfSockets> values_;
Vector<NodeWithWarning> node_warnings_;
Vector<NodeWithExecutionTime> node_exec_times_;
Vector<NodeWithDebugMessage> node_debug_messages_;
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);
/**
* 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_;
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_;
}
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 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