Geometry Nodes: add socket value logging capability

The node tree evaluator now calls a callback for every used socket with
its corresponding value(s). Right now the callback does nothing.
However, we can use it to collect attribute name hints, socket values
for debugging or data that will be displayed in the spreadsheet.

The main difficulty here was to also call the callback for sockets in
nodes that are not directly executed (such as group nodes, muted
nodes and reroutes).

No functional changes are expected.
This commit is contained in:
Jacques Lucke 2021-04-01 13:10:22 +02:00
parent 328b39335e
commit 2a5c0c3491
5 changed files with 162 additions and 66 deletions

View File

@ -89,6 +89,7 @@ using blender::bke::PersistentCollectionHandle;
using blender::bke::PersistentDataHandleMap;
using blender::bke::PersistentObjectHandle;
using blender::fn::GMutablePointer;
using blender::fn::GPointer;
using blender::fn::GValueMap;
using blender::nodes::GeoNodeExecParams;
using namespace blender::fn::multi_function_types;
@ -253,6 +254,9 @@ static bool isDisabled(const struct Scene *UNUSED(scene),
}
class GeometryNodesEvaluator {
public:
using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>;
private:
blender::LinearAllocator<> allocator_;
Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_;
@ -263,6 +267,7 @@ class GeometryNodesEvaluator {
const Object *self_object_;
const ModifierData *modifier_;
Depsgraph *depsgraph_;
LogSocketValueFn log_socket_value_fn_;
public:
GeometryNodesEvaluator(const Map<DOutputSocket, GMutablePointer> &group_input_data,
@ -271,16 +276,19 @@ class GeometryNodesEvaluator {
const PersistentDataHandleMap &handle_map,
const Object *self_object,
const ModifierData *modifier,
Depsgraph *depsgraph)
Depsgraph *depsgraph,
LogSocketValueFn log_socket_value_fn)
: group_outputs_(std::move(group_outputs)),
mf_by_node_(mf_by_node),
conversions_(blender::nodes::get_implicit_type_conversions()),
handle_map_(handle_map),
self_object_(self_object),
modifier_(modifier),
depsgraph_(depsgraph)
depsgraph_(depsgraph),
log_socket_value_fn_(std::move(log_socket_value_fn))
{
for (auto item : group_input_data.items()) {
this->log_socket_value(item.key, item.value);
this->forward_to_inputs(item.key, item.value);
}
}
@ -290,6 +298,7 @@ class GeometryNodesEvaluator {
Vector<GMutablePointer> results;
for (const DInputSocket &group_output : group_outputs_) {
Vector<GMutablePointer> result = this->get_input_values(group_output);
this->log_socket_value(group_output, result);
results.append(result[0]);
}
for (GMutablePointer value : value_by_input_.values()) {
@ -384,7 +393,9 @@ class GeometryNodesEvaluator {
GValueMap<StringRef> node_inputs_map{allocator_};
for (const InputSocketRef *input_socket : node->inputs()) {
if (input_socket->is_available()) {
Vector<GMutablePointer> values = this->get_input_values({node.context(), input_socket});
DInputSocket dsocket{node.context(), input_socket};
Vector<GMutablePointer> values = this->get_input_values(dsocket);
this->log_socket_value(dsocket, values);
for (int i = 0; i < values.size(); ++i) {
/* Values from Multi Input Sockets are stored in input map with the format
* <identifier>[<index>]. */
@ -404,12 +415,31 @@ class GeometryNodesEvaluator {
/* Forward computed outputs to linked input sockets. */
for (const OutputSocketRef *output_socket : node->outputs()) {
if (output_socket->is_available()) {
const DOutputSocket dsocket{node.context(), output_socket};
GMutablePointer value = node_outputs_map.extract(output_socket->identifier());
this->forward_to_inputs({node.context(), output_socket}, value);
this->log_socket_value(dsocket, value);
this->forward_to_inputs(dsocket, value);
}
}
}
void log_socket_value(const DSocket socket, Span<GPointer> values)
{
if (log_socket_value_fn_) {
log_socket_value_fn_(socket, values);
}
}
void log_socket_value(const DSocket socket, Span<GMutablePointer> values)
{
this->log_socket_value(socket, values.cast<GPointer>());
}
void log_socket_value(const DSocket socket, GPointer value)
{
this->log_socket_value(socket, Span<GPointer>(&value, 1));
}
void execute_node(const DNode node, GeoNodeExecParams params)
{
const bNode &bnode = params.node();
@ -523,8 +553,15 @@ class GeometryNodesEvaluator {
{
/* For all sockets that are linked with the from_socket push the value to their node. */
Vector<DInputSocket> to_sockets_all;
from_socket.foreach_target_socket(
[&](DInputSocket to_socket) { to_sockets_all.append_non_duplicates(to_socket); });
auto handle_target_socket_fn = [&](DInputSocket to_socket) {
to_sockets_all.append_non_duplicates(to_socket);
};
auto handle_skipped_socket_fn = [&, this](DSocket socket) {
this->log_socket_value(socket, value_to_forward);
};
from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn);
const CPPType &from_type = *value_to_forward.type();
Vector<DInputSocket> to_sockets_same_type;
@ -1112,7 +1149,8 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
handle_map,
ctx->object,
(ModifierData *)nmd,
ctx->depsgraph};
ctx->depsgraph,
{}};
Vector<GMutablePointer> results = evaluator.execute();
BLI_assert(results.size() == 1);

View File

@ -59,6 +59,7 @@ class DTreeContext {
const NodeTreeRef *tree_;
/* All the children contexts of this context. */
Map<const NodeRef *, DTreeContext *> children_;
DerivedNodeTree *derived_tree_;
friend DerivedNodeTree;
@ -67,6 +68,7 @@ class DTreeContext {
const DTreeContext *parent_context() const;
const NodeRef *parent_node() const;
const DTreeContext *child_context(const NodeRef &node) const;
const DerivedNodeTree &derived_tree() const;
bool is_root() const;
};
@ -117,6 +119,8 @@ class DSocket {
operator bool() const;
uint64_t hash() const;
DNode node() const;
};
/* A (nullable) reference to an input socket and the context it is in. */
@ -132,7 +136,7 @@ class DInputSocket : public DSocket {
DOutputSocket get_corresponding_group_node_output() const;
Vector<DOutputSocket, 4> get_corresponding_group_input_sockets() const;
void foreach_origin_socket(FunctionRef<void(DSocket)> callback) const;
void foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) const;
};
/* A (nullable) reference to an output socket and the context it is in. */
@ -148,7 +152,8 @@ class DOutputSocket : public DSocket {
DInputSocket get_corresponding_group_node_input() const;
DInputSocket get_active_corresponding_group_output_socket() const;
void foreach_target_socket(FunctionRef<void(DInputSocket)> callback) const;
void foreach_target_socket(FunctionRef<void(DInputSocket)> target_fn,
FunctionRef<void(DSocket)> skipped_fn) const;
};
class DerivedNodeTree {
@ -214,6 +219,11 @@ inline const DTreeContext *DTreeContext::child_context(const NodeRef &node) cons
return children_.lookup_default(&node, nullptr);
}
inline const DerivedNodeTree &DTreeContext::derived_tree() const
{
return *derived_tree_;
}
inline bool DTreeContext::is_root() const
{
return parent_context_ == nullptr;
@ -319,6 +329,12 @@ inline uint64_t DSocket::hash() const
return get_default_hash_2(context_, socket_ref_);
}
inline DNode DSocket::node() const
{
BLI_assert(socket_ref_ != nullptr);
return {context_, &socket_ref_->node()};
}
/* --------------------------------------------------------------------
* DInputSocket inline methods.
*/

View File

@ -81,15 +81,19 @@ class SocketRef : NonCopyable, NonMovable {
Vector<LinkRef *> directly_linked_links_;
/* These sockets are linked directly, i.e. with a single link in between. */
MutableSpan<SocketRef *> directly_linked_sockets_;
MutableSpan<const SocketRef *> directly_linked_sockets_;
/* These sockets are linked when reroutes, muted links and muted nodes have been taken into
* account. */
MutableSpan<SocketRef *> logically_linked_sockets_;
MutableSpan<const SocketRef *> logically_linked_sockets_;
/* These are the sockets that have been skipped when searching for logicaly linked sockets. That
* includes for example the input and output socket of an intermediate reroute node. */
MutableSpan<const SocketRef *> logically_linked_skipped_sockets_;
friend NodeTreeRef;
public:
Span<const SocketRef *> logically_linked_sockets() const;
Span<const SocketRef *> logically_linked_skipped_sockets() const;
Span<const SocketRef *> directly_linked_sockets() const;
Span<const LinkRef *> directly_linked_links() const;
@ -132,12 +136,19 @@ class InputSocketRef final : public SocketRef {
Span<const OutputSocketRef *> directly_linked_sockets() const;
bool is_multi_input_socket() const;
void foreach_logical_origin(FunctionRef<void(const OutputSocketRef &)> origin_fn,
FunctionRef<void(const SocketRef &)> skipped_fn,
bool only_follow_first_input_link = false) const;
};
class OutputSocketRef final : public SocketRef {
public:
Span<const InputSocketRef *> logically_linked_sockets() const;
Span<const InputSocketRef *> directly_linked_sockets() const;
void foreach_logical_target(FunctionRef<void(const InputSocketRef &)> target_fn,
FunctionRef<void(const SocketRef &)> skipped_fn) const;
};
class NodeRef : NonCopyable, NonMovable {
@ -257,12 +268,6 @@ class NodeTreeRef : NonCopyable, NonMovable {
bNodeSocket *bsocket);
void create_linked_socket_caches();
void foreach_logical_origin(InputSocketRef &socket,
FunctionRef<void(OutputSocketRef &)> callback,
bool only_follow_first_input_link = false);
void foreach_logical_target(OutputSocketRef &socket,
FunctionRef<void(InputSocketRef &)> callback);
};
using NodeTreeRefMap = Map<bNodeTree *, std::unique_ptr<const NodeTreeRef>>;
@ -287,6 +292,11 @@ inline Span<const SocketRef *> SocketRef::logically_linked_sockets() const
return logically_linked_sockets_;
}
inline Span<const SocketRef *> SocketRef::logically_linked_skipped_sockets() const
{
return logically_linked_skipped_sockets_;
}
inline Span<const SocketRef *> SocketRef::directly_linked_sockets() const
{
return directly_linked_sockets_;

View File

@ -40,6 +40,7 @@ DTreeContext &DerivedNodeTree::construct_context_recursively(DTreeContext *paren
DTreeContext &context = *allocator_.construct<DTreeContext>().release();
context.parent_context_ = parent_context;
context.parent_node_ = parent_node;
context.derived_tree_ = this;
context.tree_ = &get_tree_ref_from_map(node_tree_refs, btree);
used_node_tree_refs_.add(context.tree_);
@ -167,10 +168,10 @@ DInputSocket DOutputSocket::get_active_corresponding_group_output_socket() const
return {};
}
/* Call the given callback for every "real" origin socket. "Real" means that reroutes, muted nodes
/* Call `origin_fn` for every "real" origin socket. "Real" means that reroutes, muted nodes
* and node groups are handled by this function. Origin sockets are ones where a node gets its
* inputs from. */
void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) const
void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> origin_fn) const
{
BLI_assert(*this);
for (const OutputSocketRef *linked_socket : socket_ref_->as_input().logically_linked_sockets()) {
@ -180,18 +181,18 @@ void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) co
if (linked_node.is_group_input_node()) {
if (context_->is_root()) {
/* This is a group input in the root node group. */
callback(linked_dsocket);
origin_fn(linked_dsocket);
}
else {
DInputSocket socket_in_parent_group = linked_dsocket.get_corresponding_group_node_input();
if (socket_in_parent_group->is_logically_linked()) {
/* Follow the links coming into the corresponding socket on the parent group node. */
socket_in_parent_group.foreach_origin_socket(callback);
socket_in_parent_group.foreach_origin_socket(origin_fn);
}
else {
/* The corresponding input on the parent group node is not connected. Therefore, we use
* the value of that input socket directly. */
callback(socket_in_parent_group);
origin_fn(socket_in_parent_group);
}
}
}
@ -200,27 +201,32 @@ void DInputSocket::foreach_origin_socket(FunctionRef<void(DSocket)> callback) co
if (socket_in_group) {
if (socket_in_group->is_logically_linked()) {
/* Follow the links coming into the group output node of the child node group. */
socket_in_group.foreach_origin_socket(callback);
socket_in_group.foreach_origin_socket(origin_fn);
}
else {
/* The output of the child node group is not connected, so we have to get the value from
* that socket. */
callback(socket_in_group);
origin_fn(socket_in_group);
}
}
}
else {
/* The normal case: just use the value of a linked output socket. */
callback(linked_dsocket);
origin_fn(linked_dsocket);
}
}
}
/* Calls the given callback for every "real" target socket. "Real" means that reroutes, muted nodes
/* Calls `target_fn` for every "real" target socket. "Real" means that reroutes, muted nodes
* and node groups are handled by this function. Target sockets are on the nodes that use the value
* from this socket. */
void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> callback) const
* from this socket. The `skipped_fn` function is called for sockets that have been skipped during
* the search for target sockets (e.g. reroutes). */
void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> target_fn,
FunctionRef<void(DSocket)> skipped_fn) const
{
for (const SocketRef *skipped_socket : socket_ref_->logically_linked_skipped_sockets()) {
skipped_fn.call_safe({context_, skipped_socket});
}
for (const InputSocketRef *linked_socket : socket_ref_->as_output().logically_linked_sockets()) {
const NodeRef &linked_node = linked_socket->node();
DInputSocket linked_dsocket{context_, linked_socket};
@ -228,26 +234,30 @@ void DOutputSocket::foreach_target_socket(FunctionRef<void(DInputSocket)> callba
if (linked_node.is_group_output_node()) {
if (context_->is_root()) {
/* This is a group output in the root node group. */
callback(linked_dsocket);
target_fn(linked_dsocket);
}
else {
/* Follow the links going out of the group node in the parent node group. */
DOutputSocket socket_in_parent_group =
linked_dsocket.get_corresponding_group_node_output();
socket_in_parent_group.foreach_target_socket(callback);
skipped_fn.call_safe(linked_dsocket);
skipped_fn.call_safe(socket_in_parent_group);
socket_in_parent_group.foreach_target_socket(target_fn, skipped_fn);
}
}
else if (linked_node.is_group_node()) {
/* Follow the links within the nested node group. */
Vector<DOutputSocket> sockets_in_group =
linked_dsocket.get_corresponding_group_input_sockets();
skipped_fn.call_safe(linked_dsocket);
for (DOutputSocket socket_in_group : sockets_in_group) {
socket_in_group.foreach_target_socket(callback);
skipped_fn.call_safe(socket_in_group);
socket_in_group.foreach_target_socket(target_fn, skipped_fn);
}
}
else {
/* The normal case: just use the linked input socket as target. */
callback(linked_dsocket);
target_fn(linked_dsocket);
}
}
}

View File

@ -163,7 +163,7 @@ void NodeTreeRef::create_linked_socket_caches()
{
for (InputSocketRef *socket : input_sockets_) {
/* Find directly linked socket based on incident links. */
Vector<SocketRef *> directly_linked_sockets;
Vector<const SocketRef *> directly_linked_sockets;
for (LinkRef *link : socket->directly_linked_links_) {
directly_linked_sockets.append(link->from_);
}
@ -171,9 +171,11 @@ void NodeTreeRef::create_linked_socket_caches()
directly_linked_sockets.as_span());
/* Find logically linked sockets. */
Vector<SocketRef *> logically_linked_sockets;
this->foreach_logical_origin(
*socket, [&](OutputSocketRef &origin) { logically_linked_sockets.append(&origin); });
Vector<const SocketRef *> logically_linked_sockets;
Vector<const SocketRef *> logically_linked_skipped_sockets;
socket->foreach_logical_origin(
[&](const OutputSocketRef &origin) { logically_linked_sockets.append(&origin); },
[&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); });
if (logically_linked_sockets == directly_linked_sockets) {
socket->logically_linked_sockets_ = socket->directly_linked_sockets_;
}
@ -181,11 +183,13 @@ void NodeTreeRef::create_linked_socket_caches()
socket->logically_linked_sockets_ = allocator_.construct_array_copy(
logically_linked_sockets.as_span());
}
socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy(
logically_linked_skipped_sockets.as_span());
}
for (OutputSocketRef *socket : output_sockets_) {
/* Find directly linked socket based on incident links. */
Vector<SocketRef *> directly_linked_sockets;
Vector<const SocketRef *> directly_linked_sockets;
for (LinkRef *link : socket->directly_linked_links_) {
directly_linked_sockets.append(link->to_);
}
@ -193,9 +197,11 @@ void NodeTreeRef::create_linked_socket_caches()
directly_linked_sockets.as_span());
/* Find logically linked sockets. */
Vector<SocketRef *> logically_linked_sockets;
this->foreach_logical_target(
*socket, [&](InputSocketRef &target) { logically_linked_sockets.append(&target); });
Vector<const SocketRef *> logically_linked_sockets;
Vector<const SocketRef *> logically_linked_skipped_sockets;
socket->foreach_logical_target(
[&](const InputSocketRef &target) { logically_linked_sockets.append(&target); },
[&](const SocketRef &socket) { logically_linked_skipped_sockets.append(&socket); });
if (logically_linked_sockets == directly_linked_sockets) {
socket->logically_linked_sockets_ = socket->directly_linked_sockets_;
}
@ -203,61 +209,77 @@ void NodeTreeRef::create_linked_socket_caches()
socket->logically_linked_sockets_ = allocator_.construct_array_copy(
logically_linked_sockets.as_span());
}
socket->logically_linked_skipped_sockets_ = allocator_.construct_array_copy(
logically_linked_skipped_sockets.as_span());
}
}
void NodeTreeRef::foreach_logical_origin(InputSocketRef &socket,
FunctionRef<void(OutputSocketRef &)> callback,
bool only_follow_first_input_link)
void InputSocketRef::foreach_logical_origin(FunctionRef<void(const OutputSocketRef &)> origin_fn,
FunctionRef<void(const SocketRef &)> skipped_fn,
bool only_follow_first_input_link) const
{
Span<LinkRef *> links_to_check = socket.directly_linked_links_;
Span<const LinkRef *> links_to_check = this->directly_linked_links();
if (only_follow_first_input_link) {
links_to_check = links_to_check.take_front(1);
}
for (LinkRef *link : links_to_check) {
for (const LinkRef *link : links_to_check) {
if (link->is_muted()) {
continue;
}
OutputSocketRef *origin = link->from_;
NodeRef *origin_node = origin->node_;
if (origin_node->is_reroute_node()) {
this->foreach_logical_origin(*origin_node->inputs_[0], callback, false);
const OutputSocketRef &origin = link->from();
const NodeRef &origin_node = origin.node();
if (origin_node.is_reroute_node()) {
const InputSocketRef &reroute_input = origin_node.input(0);
const OutputSocketRef &reroute_output = origin_node.output(0);
skipped_fn.call_safe(reroute_input);
skipped_fn.call_safe(reroute_output);
reroute_input.foreach_logical_origin(origin_fn, skipped_fn, false);
}
else if (origin_node->is_muted()) {
for (InternalLinkRef *internal_link : origin_node->internal_links_) {
if (internal_link->to_ == origin) {
this->foreach_logical_origin(*internal_link->from_, callback, true);
else if (origin_node.is_muted()) {
for (const InternalLinkRef *internal_link : origin_node.internal_links()) {
if (&internal_link->to() == &origin) {
const InputSocketRef &mute_input = internal_link->from();
skipped_fn.call_safe(origin);
skipped_fn.call_safe(mute_input);
mute_input.foreach_logical_origin(origin_fn, skipped_fn, true);
break;
}
}
}
else {
callback(*origin);
origin_fn(origin);
}
}
}
void NodeTreeRef::foreach_logical_target(OutputSocketRef &socket,
FunctionRef<void(InputSocketRef &)> callback)
void OutputSocketRef::foreach_logical_target(FunctionRef<void(const InputSocketRef &)> target_fn,
FunctionRef<void(const SocketRef &)> skipped_fn) const
{
for (LinkRef *link : socket.directly_linked_links_) {
for (const LinkRef *link : this->directly_linked_links()) {
if (link->is_muted()) {
continue;
}
InputSocketRef *target = link->to_;
NodeRef *target_node = target->node_;
if (target_node->is_reroute_node()) {
this->foreach_logical_target(*target_node->outputs_[0], callback);
const InputSocketRef &target = link->to();
const NodeRef &target_node = target.node();
if (target_node.is_reroute_node()) {
const OutputSocketRef &reroute_output = target_node.output(0);
skipped_fn.call_safe(target);
skipped_fn.call_safe(reroute_output);
reroute_output.foreach_logical_target(target_fn, skipped_fn);
}
else if (target_node->is_muted()) {
for (InternalLinkRef *internal_link : target_node->internal_links_) {
if (internal_link->from_ == target) {
this->foreach_logical_target(*internal_link->to_, callback);
else if (target_node.is_muted()) {
skipped_fn.call_safe(target);
for (const InternalLinkRef *internal_link : target_node.internal_links()) {
if (&internal_link->from() == &target) {
const OutputSocketRef &mute_output = internal_link->to();
skipped_fn.call_safe(target);
skipped_fn.call_safe(mute_output);
mute_output.foreach_logical_target(target_fn, skipped_fn);
}
}
}
else {
callback(*target);
target_fn(target);
}
}
}