This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/nodes/NOD_geometry_nodes_log.hh
Jacques Lucke 4d39b6b3f4 Geometry Nodes: skip logging socket values for invisible trees
Geometry nodes used to log all socket values during evaluation.
This allowed the user to hover over any socket (that was evaluated)
to see its last value. The problem is that in large (nested) node trees,
the number of sockets becomes huge, causing a lot of performance
and memory overhead (in extreme cases, more than 70% of the
total execution time).

This patch changes it so, that only socket values are logged that the
user is likely to investigate. The simple heuristic is that socket values
of the currently visible node tree are logged.

The downside is that when the user changes the visible node tree, it
won't have any logged values until it is reevaluated. I updated the
tooltip message for that case to be a bit more precise.
If user feedback suggests that this new behavior is too annoying, we
can always add a UI option to log all socket values again. That shouldn't
be done without an actual need though because it takes up UI space.

Differential Revision: https://developer.blender.org/D16884
2022-12-29 19:36:36 +01:00

342 lines
10 KiB
C++

/* 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
* thread-local 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 "BKE_viewer_path.h"
#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 necessary 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;
};
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<int32_t> group_node_id;
Vector<ComputeContextHash> children_hashes;
LinearAllocator<> *allocator = nullptr;
struct WarningWithNode {
int32_t node_id;
NodeWarning warning;
};
struct SocketValueLog {
int32_t node_id;
int socket_index;
destruct_ptr<ValueLog> value;
};
struct NodeExecutionTime {
int32_t node_id;
TimePoint start;
TimePoint end;
};
struct ViewerNodeLogWithNode {
int32_t node_id;
destruct_ptr<ViewerNodeLog> viewer_log;
};
struct AttributeUsageWithNode {
int32_t node_id;
StringRefNull attribute_name;
NamedAttributeUsage usage;
};
struct DebugMessage {
int32_t node_id;
StringRefNull 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, GeometrySet geometry);
};
/**
* 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 spent in this 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 indices to their values. */
Map<int, ValueLog *> input_values_;
Map<int, ValueLog *> output_values_;
/** Maps from attribute name to their usage flags. */
Map<StringRefNull, NamedAttributeUsage> used_named_attributes;
/** Messages that are used for debugging purposes during development. */
Vector<StringRefNull> 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<int32_t, GeoNodeLog> nodes;
Map<int32_t, ViewerNodeLog *, 0> viewer_node_logs;
Vector<NodeWarning> all_warnings;
std::chrono::nanoseconds run_time_sum{0};
Vector<const GeometryAttributeInfo *> existing_attributes;
Map<StringRefNull, 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 thread-local 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 thread-local 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 std::optional<ComputeContextHash> get_compute_context_hash_for_node_editor(
const SpaceNode &snode, StringRefNull modifier_name);
static GeoTreeLog *get_tree_log_for_node_editor(const SpaceNode &snode);
static const ViewerNodeLog *find_viewer_node_log_for_path(const ViewerPath &viewer_path);
};
} // namespace blender::nodes::geo_eval_log