Compare commits
291 Commits
blender-pr
...
temp-geome
Author | SHA1 | Date | |
---|---|---|---|
011a1f3335 | |||
9007489c51 | |||
079b38f8de | |||
3d1244c134 | |||
4ce0711e67 | |||
a0e1d123fc | |||
6c0ec45daf | |||
91a57c9cc2 | |||
90edc4472e | |||
3f9f9f0348 | |||
04c91471e0 | |||
e356fe95fd | |||
6e09d25657 | |||
a77491a6ae | |||
a8a71d6b63 | |||
bf97ce6418 | |||
69ab1bd73c | |||
e599bb1793 | |||
198f8c209f | |||
2f1d60a481 | |||
1c37a515bc | |||
8429f01d8c | |||
3aba2a3f9e | |||
b07660a2b9 | |||
1b6d1fb1d6 | |||
f9434dbe59 | |||
aaf8c0d9d7 | |||
ee3049e508 | |||
091e7fb735 | |||
0b75698519 | |||
cdd045d269 | |||
679a4d218b | |||
35caf245dd | |||
45d36410a7 | |||
7b71ab3fe0 | |||
d98c7a195f | |||
a347508dcc | |||
cc08debc0c | |||
4e9515d23d | |||
e7c95b8fde | |||
c36d8516b0 | |||
2b9e3e4e5a | |||
46f0b0798a | |||
3548c5acfe | |||
68c797c49e | |||
dd5c704ee1 | |||
9e2ff5b11a | |||
a99fddf98e | |||
d73e2d5612 | |||
d71d0e402f | |||
8629bd863a | |||
686d90a824 | |||
f197490901 | |||
d38cc2fc53 | |||
80d6565b93 | |||
75e658e6a0 | |||
4cfa580a68 | |||
db13aa8e43 | |||
d6639cfd00 | |||
6ee52e2e8c | |||
a2a8b9e82f | |||
ad55174715 | |||
7d3f3c6fd7 | |||
280c039908 | |||
8ecd241502 | |||
4f1de2be9d | |||
9f4db143f3 | |||
231cafa911 | |||
5523d90d9c | |||
4d94c1a4e7 | |||
8e2b11badf | |||
5dbbfc8cc3 | |||
cb091658a9 | |||
f3a63be474 | |||
98b5005787 | |||
bf808496f8 | |||
050a9f5f0d | |||
f5652ce6b2 | |||
58bd9230a8 | |||
8f710b104d | |||
69aa085cd8 | |||
dbef967a7b | |||
c2e101facb | |||
f4164f27ab | |||
ff11ece7a1 | |||
d9fc777ce2 | |||
a74097a150 | |||
30ba454cbe | |||
7adefbc04e | |||
2c69f123ca | |||
aaf8b97666 | |||
b6b2e91132 | |||
0f9bfe501b | |||
668f34d346 | |||
b570994b0e | |||
cec0db55df | |||
4c16d01222 | |||
460c970cab | |||
14163d9ce0 | |||
d4110b2728 | |||
7a5eee9710 | |||
9b598f0ab2 | |||
1287fbe88b | |||
464428b002 | |||
090555898c | |||
f1cd0ed6b8 | |||
aa0b2990f6 | |||
9c23df35e4 | |||
ad52352e6f | |||
67f46a8c05 | |||
b29e17bc89 | |||
b10e74d85e | |||
cef9625a2e | |||
0c6d11c1be | |||
4f7ca91df1 | |||
45efd45ee0 | |||
48d97f9abb | |||
0b1c529be3 | |||
0ff96301e9 | |||
ba163fcc30 | |||
f3a412290c | |||
04e2bf544a | |||
1f10276019 | |||
f54ba94974 | |||
cffc2dcae9 | |||
48e709fd84 | |||
e8693f14a6 | |||
d73b216e6a | |||
a2ad563cba | |||
f5454f4a5f | |||
3a6d61b019 | |||
8b97f6414d | |||
28af4afa3d | |||
73f68712d1 | |||
cafd20f998 | |||
7a89bd9ad5 | |||
c83b2e04ac | |||
1468dcb614 | |||
781454b645 | |||
9181b14dc7 | |||
f3bb76b9c1 | |||
1ccb353295 | |||
2c0d794dc4 | |||
955f78d289 | |||
aa4bdf42dd | |||
d1ad588611 | |||
a3f9d535d0 | |||
a51c9a3126 | |||
0bb0a6e7b6 | |||
3dabd0f705 | |||
9f12236be1 | |||
470f63db41 | |||
2f3119a06b | |||
3058283f71 | |||
23abe292bd | |||
20d0c51dd7 | |||
ed2e265f37 | |||
88d035ddd3 | |||
e8520c9949 | |||
5209621e41 | |||
103b1209b5 | |||
c1888b372b | |||
9d37ae1679 | |||
c7076c5185 | |||
93b2206945 | |||
978f69fb2f | |||
10f691efb7 | |||
ee0589d8e2 | |||
5df06ea72f | |||
2c8af68857 | |||
604e74dc8b | |||
b673fe34fa | |||
c1b5ced6ba | |||
f758100275 | |||
52951fefeb | |||
e303aad37c | |||
c15a768a9a | |||
25bf08b337 | |||
51e026786a | |||
73d19fe9f7 | |||
649bdd77e0 | |||
dc75ea3c3f | |||
73bb083747 | |||
09e37da0db | |||
a5f79ecf59 | |||
c4c23b6f8a | |||
1ce9c68e54 | |||
274c98fe50 | |||
a27245f093 | |||
8277bf8c5d | |||
6c291c79ad | |||
d0473dea53 | |||
e65f31a8c4 | |||
b9fc7939f8 | |||
c2ec3f2a44 | |||
1cb24cfe17 | |||
97b5a838fa | |||
6c27edbc26 | |||
5b921ece53 | |||
974e334b16 | |||
63f885e00d | |||
d5b3e5fca9 | |||
b9dfdb7400 | |||
28edae5bac | |||
2a8ccf463a | |||
9f1d871138 | |||
a5a7b19128 | |||
95d26d80ae | |||
32e30a4263 | |||
3ee490b4de | |||
29587d2cb0 | |||
fd919e18ae | |||
d06a4cb4a9 | |||
62ece09597 | |||
80a02e54b8 | |||
f02fdf0ff1 | |||
457f9aa832 | |||
c445bd7486 | |||
48dc18ff2c | |||
d6ebcc7619 | |||
9896169f9a | |||
964fb14ba4 | |||
c8caff0216 | |||
ccec45a2db | |||
8b6ece9fd2 | |||
8b822977b8 | |||
c83224de85 | |||
679ef9a083 | |||
5797238694 | |||
c8ea1f1b4b | |||
20396034c1 | |||
f43f912431 | |||
a6d33079f3 | |||
77b1cf9b68 | |||
3356a58507 | |||
80a590420c | |||
dcf1c2ef9a | |||
0fdd7e59df | |||
0eef8f3747 | |||
645d2643f9 | |||
11a8dc2ef6 | |||
1443cef855 | |||
0d0d7c9b4c | |||
002d849be2 | |||
528ba923bf | |||
3595abb204 | |||
be856674e9 | |||
20acbad7c3 | |||
d2b0ae4f9d | |||
7cd63301d3 | |||
08016656a3 | |||
2c2d3b7bdc | |||
395df6766f | |||
1690446c5e | |||
a14fc38933 | |||
fb443dd0bc | |||
6058a1ffa7 | |||
b031f1ae56 | |||
d3c8885793 | |||
9a65a061c5 | |||
42f2c6a0bb | |||
db425bd997 | |||
735b7e171e | |||
7e871183b7 | |||
e2b4b17d2e | |||
642a461491 | |||
cf8e500a64 | |||
ccd1d65fe8 | |||
65917a1461 | |||
5388195757 | |||
2257a71154 | |||
df4e9a0f29 | |||
54e622c3b0 | |||
754ed19656 | |||
588d14a66d | |||
7c159fef17 | |||
8f7effb585 | |||
1e29c82a5c | |||
63e16a6ee2 | |||
244c7f6ad2 | |||
9f1751914d | |||
1147c0f40d | |||
96b2216cfc | |||
c1c46bc60c | |||
2807625ee5 | |||
04592a81b0 | |||
afb6cea2cf | |||
0f1302d281 | |||
db85b09c70 | |||
12a037c0d1 | |||
9a6259264f |
41
source/blender/blenkernel/BKE_compute_contexts.hh
Normal file
41
source/blender/blenkernel/BKE_compute_contexts.hh
Normal file
@@ -0,0 +1,41 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* This file implements some specific compute contexts for concepts in Blender.
|
||||
*/
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
class ModifierComputeContext : public ComputeContext {
|
||||
private:
|
||||
static constexpr const char *s_static_type = "MODIFIER";
|
||||
|
||||
std::string modifier_name_;
|
||||
|
||||
public:
|
||||
ModifierComputeContext(const ComputeContext *parent, std::string modifier_name);
|
||||
|
||||
private:
|
||||
void print_current_in_line(std::ostream &stream) const override;
|
||||
};
|
||||
|
||||
class NodeGroupComputeContext : public ComputeContext {
|
||||
private:
|
||||
static constexpr const char *s_static_type = "NODE_GROUP";
|
||||
|
||||
std::string node_name_;
|
||||
|
||||
public:
|
||||
NodeGroupComputeContext(const ComputeContext *parent, std::string node_name);
|
||||
|
||||
StringRefNull node_name() const;
|
||||
|
||||
private:
|
||||
void print_current_in_line(std::ostream &stream) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
@@ -21,6 +21,7 @@ struct bNodeType;
|
||||
namespace blender::nodes {
|
||||
struct FieldInferencingInterface;
|
||||
class NodeDeclaration;
|
||||
struct GeometryNodesLazyFunctionGraphInfo;
|
||||
} // namespace blender::nodes
|
||||
|
||||
namespace blender::bke {
|
||||
@@ -48,6 +49,15 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
|
||||
/** Information about how inputs and outputs of the node group interact with fields. */
|
||||
std::unique_ptr<nodes::FieldInferencingInterface> field_inferencing_interface;
|
||||
|
||||
/**
|
||||
* For geometry nodes, a lazy function graph with some additional info is cached. This is used to
|
||||
* evaluate the node group. Caching it here allows us to reuse the preprocessed node tree in case
|
||||
* its used multiple times.
|
||||
*/
|
||||
std::mutex geometry_nodes_lazy_function_graph_info_mutex;
|
||||
std::unique_ptr<nodes::GeometryNodesLazyFunctionGraphInfo>
|
||||
geometry_nodes_lazy_function_graph_info;
|
||||
|
||||
/**
|
||||
* Protects access to all topology cache variables below. This is necessary so that the cache can
|
||||
* be updated on a const #bNodeTree.
|
||||
@@ -148,6 +158,8 @@ class bNodeRuntime : NonCopyable, NonMovable {
|
||||
|
||||
namespace node_tree_runtime {
|
||||
|
||||
void handle_node_tree_output_changed(bNodeTree &tree_cow);
|
||||
|
||||
class AllowUsingOutdatedInfo : NonCopyable, NonMovable {
|
||||
private:
|
||||
const bNodeTree &tree_;
|
||||
@@ -413,7 +425,6 @@ inline blender::Span<const bNodeLink *> bNode::internal_links_span() const
|
||||
|
||||
inline const blender::nodes::NodeDeclaration *bNode::declaration() const
|
||||
{
|
||||
BLI_assert(this->runtime->declaration != nullptr);
|
||||
return this->runtime->declaration;
|
||||
}
|
||||
|
||||
|
@@ -98,6 +98,7 @@ set(SRC
|
||||
intern/collision.c
|
||||
intern/colorband.c
|
||||
intern/colortools.c
|
||||
intern/compute_contexts.cc
|
||||
intern/constraint.c
|
||||
intern/context.c
|
||||
intern/crazyspace.cc
|
||||
@@ -352,6 +353,7 @@ set(SRC
|
||||
BKE_collision.h
|
||||
BKE_colorband.h
|
||||
BKE_colortools.h
|
||||
BKE_compute_contexts.hh
|
||||
BKE_constraint.h
|
||||
BKE_context.h
|
||||
BKE_crazyspace.h
|
||||
|
38
source/blender/blenkernel/intern/compute_contexts.cc
Normal file
38
source/blender/blenkernel/intern/compute_contexts.cc
Normal file
@@ -0,0 +1,38 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_compute_contexts.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
ModifierComputeContext::ModifierComputeContext(const ComputeContext *parent,
|
||||
std::string modifier_name)
|
||||
: ComputeContext(s_static_type, parent), modifier_name_(std::move(modifier_name))
|
||||
{
|
||||
hash_.mix_in(s_static_type, strlen(s_static_type));
|
||||
hash_.mix_in(modifier_name_.data(), modifier_name_.size());
|
||||
}
|
||||
|
||||
void ModifierComputeContext::print_current_in_line(std::ostream &stream) const
|
||||
{
|
||||
stream << "Modifier: " << modifier_name_;
|
||||
}
|
||||
|
||||
NodeGroupComputeContext::NodeGroupComputeContext(const ComputeContext *parent,
|
||||
std::string node_name)
|
||||
: ComputeContext(s_static_type, parent), node_name_(std::move(node_name))
|
||||
{
|
||||
hash_.mix_in(s_static_type, strlen(s_static_type));
|
||||
hash_.mix_in(node_name_.data(), node_name_.size());
|
||||
}
|
||||
|
||||
StringRefNull NodeGroupComputeContext::node_name() const
|
||||
{
|
||||
return node_name_;
|
||||
}
|
||||
|
||||
void NodeGroupComputeContext::print_current_in_line(std::ostream &stream) const
|
||||
{
|
||||
stream << "Node: " << node_name_;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
@@ -71,6 +71,7 @@
|
||||
#include "NOD_composite.h"
|
||||
#include "NOD_function.h"
|
||||
#include "NOD_geometry.h"
|
||||
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
|
||||
#include "NOD_node_declaration.hh"
|
||||
#include "NOD_shader.h"
|
||||
#include "NOD_socket.h"
|
||||
|
@@ -10,8 +10,22 @@
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
|
||||
|
||||
namespace blender::bke::node_tree_runtime {
|
||||
|
||||
void handle_node_tree_output_changed(bNodeTree &tree_cow)
|
||||
{
|
||||
if (tree_cow.type == NTREE_GEOMETRY) {
|
||||
/* Rebuild geometry nodes lazy function graph. */
|
||||
{
|
||||
std::lock_guard lock{tree_cow.runtime->geometry_nodes_lazy_function_graph_info_mutex};
|
||||
tree_cow.runtime->geometry_nodes_lazy_function_graph_info.reset();
|
||||
}
|
||||
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree_cow);
|
||||
}
|
||||
}
|
||||
|
||||
static void double_checked_lock(std::mutex &mutex, bool &data_is_dirty, FunctionRef<void()> fn)
|
||||
{
|
||||
if (!data_is_dirty) {
|
||||
|
173
source/blender/blenlib/BLI_compute_context.hh
Normal file
173
source/blender/blenlib/BLI_compute_context.hh
Normal file
@@ -0,0 +1,173 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bli
|
||||
*
|
||||
* When logging computed values, we generally want to know where the value was computed. For
|
||||
* example, geometry nodes logs socket values so that they can be displayed in the ui. For that we
|
||||
* can combine the logged value with a `ComputeContext`, which identifies the place where the value
|
||||
* was computed.
|
||||
*
|
||||
* This is not a trivial problem because e.g. just storing storing a pointer to the socket a value
|
||||
* belongs to is not enough. That's because the same socket may correspond to many different values
|
||||
* when the socket is used in a node group that is used multiple times. In this case, not only does
|
||||
* the socket has to be stored but also the entire nested node group path that led to the
|
||||
* evaluation of the socket.
|
||||
*
|
||||
* Storing the entire "context path" for every logged value is not feasible, because that path can
|
||||
* become quite long. So that would need much more memory, more compute overhead and makes it
|
||||
* complicated to compare if two contexts are the same. If the identifier for a compute context
|
||||
* would have a variable size, it would also be much harder to create a map from context to values.
|
||||
*
|
||||
* The solution implemented below uses the following key ideas:
|
||||
* - Every compute context can be hashed to a unique fixed size value (`ComputeContextHash`). While
|
||||
* technically there could be hash collisions, the hashing algorithm has to be chosen to make
|
||||
* that practically impossible. This way an entire context path, possibly consisting of many
|
||||
* nested contexts, is represented by a single value that can be stored easily.
|
||||
* - A nested compute context is build as singly linked list, where every compute context has a
|
||||
* pointer to the parent compute context. Note that a link in the other direction is not possible
|
||||
* because the same parent compute context may be used by many different children which possibly
|
||||
* run on different threads.
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_linear_allocator.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/**
|
||||
* A hash that uniquely identifies a specific (non-fixed-size) compute context. The hash has to
|
||||
* have enough bits to make collisions practically impossible.
|
||||
*/
|
||||
struct ComputeContextHash {
|
||||
static constexpr int64_t HashSizeInBytes = 16;
|
||||
uint64_t v1 = 0;
|
||||
uint64_t v2 = 0;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return v1;
|
||||
}
|
||||
|
||||
friend bool operator==(const ComputeContextHash &a, const ComputeContextHash &b)
|
||||
{
|
||||
return a.v1 == b.v1 && a.v2 == b.v2;
|
||||
}
|
||||
|
||||
void mix_in(const void *data, int64_t len);
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash);
|
||||
};
|
||||
|
||||
static_assert(sizeof(ComputeContextHash) == ComputeContextHash::HashSizeInBytes);
|
||||
|
||||
/**
|
||||
* Identifies the context in which a computation happens. This context can be used to identify
|
||||
* values logged during the computation. For more details, see the comment at the top of the file.
|
||||
*
|
||||
* This class should be subclassed to implement specific contexts.
|
||||
*/
|
||||
class ComputeContext {
|
||||
private:
|
||||
/**
|
||||
* Only used for debugging currently.
|
||||
*/
|
||||
const char *static_type_;
|
||||
/**
|
||||
* Pointer to the context that this context is child of. That allows nesting compute contexts.
|
||||
*/
|
||||
const ComputeContext *parent_ = nullptr;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The hash that uniquely identifies this context. It's a combined hash of this context as well
|
||||
* as all the parent contexts.
|
||||
*/
|
||||
ComputeContextHash hash_;
|
||||
|
||||
public:
|
||||
ComputeContext(const char *static_type, const ComputeContext *parent)
|
||||
: static_type_(static_type), parent_(parent)
|
||||
{
|
||||
if (parent != nullptr) {
|
||||
hash_ = parent_->hash_;
|
||||
}
|
||||
}
|
||||
virtual ~ComputeContext() = default;
|
||||
|
||||
const ComputeContextHash &hash() const
|
||||
{
|
||||
return hash_;
|
||||
}
|
||||
|
||||
const char *static_type() const
|
||||
{
|
||||
return static_type_;
|
||||
}
|
||||
|
||||
const ComputeContext *parent() const
|
||||
{
|
||||
return parent_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the entire nested context stack.
|
||||
*/
|
||||
void print_stack(std::ostream &stream, StringRef name) const;
|
||||
|
||||
/**
|
||||
* Print information about this specific context. This has to be implemented by each subclass.
|
||||
*/
|
||||
virtual void print_current_in_line(std::ostream &stream) const = 0;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class to build a context stack in one place. This is typically used to get the hash that
|
||||
* corresponds to a specific nested compute context, in order to look up corresponding logged
|
||||
* values.
|
||||
*/
|
||||
class ComputeContextBuilder {
|
||||
private:
|
||||
LinearAllocator<> allocator_;
|
||||
Stack<destruct_ptr<ComputeContext>> contexts_;
|
||||
|
||||
public:
|
||||
bool is_empty() const
|
||||
{
|
||||
return contexts_.is_empty();
|
||||
}
|
||||
|
||||
const ComputeContext *current() const
|
||||
{
|
||||
if (contexts_.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return contexts_.peek().get();
|
||||
}
|
||||
|
||||
const ComputeContextHash hash() const
|
||||
{
|
||||
BLI_assert(!contexts_.is_empty());
|
||||
return this->current()->hash();
|
||||
}
|
||||
|
||||
template<typename T, typename... Args> void push(Args &&...args)
|
||||
{
|
||||
const ComputeContext *current = this->current();
|
||||
destruct_ptr<T> context = allocator_.construct<T>(current, std::forward<Args>(args)...);
|
||||
contexts_.push(std::move(context));
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
contexts_.pop();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender
|
@@ -114,6 +114,14 @@ template<typename Key, typename Value> class MultiValueMap {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of keys.
|
||||
*/
|
||||
int64_t size() const
|
||||
{
|
||||
return map_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This signature will change when the implementation changes.
|
||||
*/
|
||||
|
@@ -53,6 +53,7 @@ set(SRC
|
||||
intern/bitmap_draw_2d.c
|
||||
intern/boxpack_2d.c
|
||||
intern/buffer.c
|
||||
intern/compute_context.cc
|
||||
intern/convexhull_2d.c
|
||||
intern/cpp_type.cc
|
||||
intern/delaunay_2d.cc
|
||||
@@ -180,6 +181,7 @@ set(SRC
|
||||
BLI_compiler_attrs.h
|
||||
BLI_compiler_compat.h
|
||||
BLI_compiler_typecheck.h
|
||||
BLI_compute_context.hh
|
||||
BLI_console.h
|
||||
BLI_convexhull_2d.h
|
||||
BLI_cpp_type.hh
|
||||
|
48
source/blender/blenlib/intern/compute_context.cc
Normal file
48
source/blender/blenlib/intern/compute_context.cc
Normal file
@@ -0,0 +1,48 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_compute_context.hh"
|
||||
#include "BLI_hash_md5.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
void ComputeContextHash::mix_in(const void *data, int64_t len)
|
||||
{
|
||||
DynamicStackBuffer<> buffer_owner(HashSizeInBytes + len, 8);
|
||||
char *buffer = static_cast<char *>(buffer_owner.buffer());
|
||||
memcpy(buffer, this, HashSizeInBytes);
|
||||
memcpy(buffer + HashSizeInBytes, data, len);
|
||||
|
||||
BLI_hash_md5_buffer(buffer, HashSizeInBytes + len, this);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << hash.v1 << hash.v2;
|
||||
stream << ss.str();
|
||||
return stream;
|
||||
}
|
||||
|
||||
void ComputeContext::print_stack(std::ostream &stream, StringRef name) const
|
||||
{
|
||||
Stack<const ComputeContext *> stack;
|
||||
for (const ComputeContext *current = this; current; current = current->parent_) {
|
||||
stack.push(current);
|
||||
}
|
||||
stream << "Context Stack: " << name << "\n";
|
||||
while (!stack.is_empty()) {
|
||||
const ComputeContext *current = stack.pop();
|
||||
stream << "-> ";
|
||||
current->print_current_in_line(stream);
|
||||
const ComputeContextHash ¤t_hash = current->hash_;
|
||||
stream << " \t(hash: " << current_hash << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context)
|
||||
{
|
||||
compute_context.print_stack(stream, "");
|
||||
return stream;
|
||||
}
|
||||
|
||||
} // namespace blender
|
@@ -26,3 +26,4 @@ BLI_CPP_TYPE_MAKE(ColorGeometry4f, blender::ColorGeometry4f, CPPTypeFlags::Basic
|
||||
BLI_CPP_TYPE_MAKE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType)
|
||||
|
||||
BLI_CPP_TYPE_MAKE(string, std::string, CPPTypeFlags::BasicType)
|
||||
BLI_CPP_TYPE_MAKE(StringVector, blender::Vector<std::string>, CPPTypeFlags::None)
|
||||
|
@@ -1741,7 +1741,14 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)
|
||||
/* Animation, */
|
||||
build_animdata(&ntree->id);
|
||||
/* Output update. */
|
||||
add_operation_node(&ntree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT);
|
||||
ID *id_cow = get_cow_id(&ntree->id);
|
||||
add_operation_node(&ntree->id,
|
||||
NodeType::NTREE_OUTPUT,
|
||||
OperationCode::NTREE_OUTPUT,
|
||||
[id_cow](::Depsgraph * /*depsgraph*/) {
|
||||
bNodeTree *ntree_cow = reinterpret_cast<bNodeTree *>(id_cow);
|
||||
bke::node_tree_runtime::handle_node_tree_output_changed(*ntree_cow);
|
||||
});
|
||||
/* nodetree's nodes... */
|
||||
LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) {
|
||||
build_idproperties(bnode->prop);
|
||||
|
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "UI_resources.h"
|
||||
|
||||
namespace blender::nodes::geometry_nodes_eval_log {
|
||||
namespace blender::nodes::geo_eval_log {
|
||||
struct GeometryAttributeInfo;
|
||||
}
|
||||
|
||||
@@ -44,12 +44,11 @@ void context_path_add_generic(Vector<ContextPathItem> &path,
|
||||
|
||||
void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path);
|
||||
|
||||
void attribute_search_add_items(
|
||||
StringRefNull str,
|
||||
bool can_create_attribute,
|
||||
Span<const nodes::geometry_nodes_eval_log::GeometryAttributeInfo *> infos,
|
||||
uiSearchItems *items,
|
||||
bool is_first);
|
||||
void attribute_search_add_items(StringRefNull str,
|
||||
bool can_create_attribute,
|
||||
Span<const nodes::geo_eval_log::GeometryAttributeInfo *> infos,
|
||||
uiSearchItems *items,
|
||||
bool is_first);
|
||||
|
||||
} // namespace blender::ui
|
||||
|
||||
|
@@ -14,13 +14,15 @@
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.h"
|
||||
|
||||
using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo;
|
||||
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
|
||||
|
||||
namespace blender::ui {
|
||||
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include "DNA_light_types.h"
|
||||
#include "DNA_linestyle_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_screen_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
@@ -29,11 +30,13 @@
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "BKE_compute_contexts.hh"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_idtype.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_node.h"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
@@ -65,7 +68,8 @@
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "NOD_geometry_exec.hh"
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
#include "NOD_node_declaration.hh"
|
||||
#include "NOD_socket_declarations_geometry.hh"
|
||||
|
||||
@@ -74,10 +78,18 @@
|
||||
|
||||
#include "node_intern.hh" /* own include */
|
||||
|
||||
namespace geo_log = blender::nodes::geo_eval_log;
|
||||
|
||||
using blender::GPointer;
|
||||
using blender::Vector;
|
||||
using blender::fn::GField;
|
||||
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
|
||||
using geo_log::eNamedAttrUsage;
|
||||
using geo_log::GeoModifierLog;
|
||||
using geo_log::GeoNodeLog;
|
||||
using geo_log::GeoTreeLog;
|
||||
using geo_log::GeoTreeLogger;
|
||||
using geo_log::NamedAttributeUsage;
|
||||
using geo_log::NodeWarning;
|
||||
using geo_log::NodeWarningType;
|
||||
|
||||
extern "C" {
|
||||
/* XXX interface.h */
|
||||
@@ -85,6 +97,17 @@ extern void ui_draw_dropshadow(
|
||||
const rctf *rct, float radius, float aspect, float alpha, int select);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is passed to many functions which draw the node editor.
|
||||
*/
|
||||
struct TreeDrawContext {
|
||||
/**
|
||||
* Geometry nodes logs various data during execution. The logged data that corresponds to the
|
||||
* currently drawn node tree can be retrieved from the log below.
|
||||
*/
|
||||
GeoTreeLog *geo_tree_log = nullptr;
|
||||
};
|
||||
|
||||
float ED_node_grid_size()
|
||||
{
|
||||
return U.widget_unit;
|
||||
@@ -157,6 +180,12 @@ void ED_node_tag_update_id(ID *id)
|
||||
|
||||
namespace blender::ed::space_node {
|
||||
|
||||
static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx),
|
||||
const bNodeTree *ntree,
|
||||
const bNode *node,
|
||||
const bNodeSocket *sock,
|
||||
uiLayout *layout);
|
||||
|
||||
static bool compare_nodes(const bNode *a, const bNode *b)
|
||||
{
|
||||
/* These tell if either the node or any of the parent nodes is selected.
|
||||
@@ -313,7 +342,11 @@ float2 node_from_view(const bNode &node, const float2 &co)
|
||||
/**
|
||||
* Based on settings and sockets in node, set drawing rect info.
|
||||
*/
|
||||
static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block)
|
||||
static void node_update_basis(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
bNodeTree &ntree,
|
||||
bNode &node,
|
||||
uiBlock &block)
|
||||
{
|
||||
PointerRNA nodeptr;
|
||||
RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr);
|
||||
@@ -374,7 +407,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node,
|
||||
const char *socket_label = nodeSocketLabel(socket);
|
||||
socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label));
|
||||
|
||||
node_socket_add_tooltip(ntree, node, *socket, *row);
|
||||
node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row);
|
||||
|
||||
UI_block_align_end(&block);
|
||||
UI_block_layout_resolve(&block, nullptr, &buty);
|
||||
@@ -506,7 +539,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node,
|
||||
const char *socket_label = nodeSocketLabel(socket);
|
||||
socket->typeinfo->draw((bContext *)&C, row, &sockptr, &nodeptr, IFACE_(socket_label));
|
||||
|
||||
node_socket_add_tooltip(ntree, node, *socket, *row);
|
||||
node_socket_add_tooltip_in_node_editor(&tree_draw_ctx, &ntree, &node, socket, row);
|
||||
|
||||
UI_block_align_end(&block);
|
||||
UI_block_layout_resolve(&block, nullptr, &buty);
|
||||
@@ -823,25 +856,16 @@ static void create_inspection_string_for_generic_value(const GPointer value, std
|
||||
}
|
||||
}
|
||||
|
||||
static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &value_log,
|
||||
std::stringstream &ss)
|
||||
static void create_inspection_string_for_field_info(const geo_log::FieldInfoLog &value_log,
|
||||
std::stringstream &ss)
|
||||
{
|
||||
const CPPType &type = value_log.type();
|
||||
const GField &field = value_log.field();
|
||||
const Span<std::string> input_tooltips = value_log.input_tooltips();
|
||||
const CPPType &type = value_log.type;
|
||||
const Span<std::string> input_tooltips = value_log.input_tooltips;
|
||||
|
||||
if (input_tooltips.is_empty()) {
|
||||
if (field) {
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
blender::fn::evaluate_constant_field(field, buffer);
|
||||
create_inspection_string_for_generic_value({type, buffer}, ss);
|
||||
type.destruct(buffer);
|
||||
}
|
||||
else {
|
||||
/* Constant values should always be logged. */
|
||||
BLI_assert_unreachable();
|
||||
ss << "Value has not been logged";
|
||||
}
|
||||
/* Should have been logged as constant value. */
|
||||
BLI_assert_unreachable();
|
||||
ss << "Value has not been logged";
|
||||
}
|
||||
else {
|
||||
if (type.is<int>()) {
|
||||
@@ -874,11 +898,11 @@ static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &v
|
||||
}
|
||||
}
|
||||
|
||||
static void create_inspection_string_for_geometry(const geo_log::GeometryValueLog &value_log,
|
||||
std::stringstream &ss,
|
||||
const nodes::decl::Geometry *geometry)
|
||||
static void create_inspection_string_for_geometry_info(const geo_log::GeometryInfoLog &value_log,
|
||||
std::stringstream &ss,
|
||||
const nodes::decl::Geometry *socket_decl)
|
||||
{
|
||||
Span<GeometryComponentType> component_types = value_log.component_types();
|
||||
Span<GeometryComponentType> component_types = value_log.component_types;
|
||||
if (component_types.is_empty()) {
|
||||
ss << TIP_("Empty Geometry");
|
||||
return;
|
||||
@@ -895,7 +919,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
const char *line_end = (type == component_types.last()) ? "" : ".\n";
|
||||
switch (type) {
|
||||
case GEO_COMPONENT_TYPE_MESH: {
|
||||
const geo_log::GeometryValueLog::MeshInfo &mesh_info = *value_log.mesh_info;
|
||||
const geo_log::GeometryInfoLog::MeshInfo &mesh_info = *value_log.mesh_info;
|
||||
char line[256];
|
||||
BLI_snprintf(line,
|
||||
sizeof(line),
|
||||
@@ -907,7 +931,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
break;
|
||||
}
|
||||
case GEO_COMPONENT_TYPE_POINT_CLOUD: {
|
||||
const geo_log::GeometryValueLog::PointCloudInfo &pointcloud_info =
|
||||
const geo_log::GeometryInfoLog::PointCloudInfo &pointcloud_info =
|
||||
*value_log.pointcloud_info;
|
||||
char line[256];
|
||||
BLI_snprintf(line,
|
||||
@@ -918,7 +942,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
break;
|
||||
}
|
||||
case GEO_COMPONENT_TYPE_CURVE: {
|
||||
const geo_log::GeometryValueLog::CurveInfo &curve_info = *value_log.curve_info;
|
||||
const geo_log::GeometryInfoLog::CurveInfo &curve_info = *value_log.curve_info;
|
||||
char line[256];
|
||||
BLI_snprintf(line,
|
||||
sizeof(line),
|
||||
@@ -928,7 +952,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
break;
|
||||
}
|
||||
case GEO_COMPONENT_TYPE_INSTANCES: {
|
||||
const geo_log::GeometryValueLog::InstancesInfo &instances_info = *value_log.instances_info;
|
||||
const geo_log::GeometryInfoLog::InstancesInfo &instances_info = *value_log.instances_info;
|
||||
char line[256];
|
||||
BLI_snprintf(line,
|
||||
sizeof(line),
|
||||
@@ -943,7 +967,7 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
}
|
||||
case GEO_COMPONENT_TYPE_EDIT: {
|
||||
if (value_log.edit_data_info.has_value()) {
|
||||
const geo_log::GeometryValueLog::EditDataInfo &edit_info = *value_log.edit_data_info;
|
||||
const geo_log::GeometryInfoLog::EditDataInfo &edit_info = *value_log.edit_data_info;
|
||||
char line[256];
|
||||
BLI_snprintf(line,
|
||||
sizeof(line),
|
||||
@@ -959,11 +983,11 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
|
||||
/* If the geometry declaration is null, as is the case for input to group output,
|
||||
* or it is an output socket don't show supported types. */
|
||||
if (geometry == nullptr || geometry->in_out() == SOCK_OUT) {
|
||||
if (socket_decl == nullptr || socket_decl->in_out() == SOCK_OUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
Span<GeometryComponentType> supported_types = geometry->supported_types();
|
||||
Span<GeometryComponentType> supported_types = socket_decl->supported_types();
|
||||
if (supported_types.is_empty()) {
|
||||
ss << ".\n\n" << TIP_("Supported: All Types");
|
||||
return;
|
||||
@@ -1000,118 +1024,130 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<std::string> create_socket_inspection_string(const bContext &C,
|
||||
const bNode &node,
|
||||
static std::optional<std::string> create_socket_inspection_string(TreeDrawContext &tree_draw_ctx,
|
||||
const bNodeSocket &socket)
|
||||
{
|
||||
const SpaceNode *snode = CTX_wm_space_node(&C);
|
||||
if (snode == nullptr) {
|
||||
return {};
|
||||
};
|
||||
|
||||
const geo_log::SocketLog *socket_log = geo_log::ModifierLog::find_socket_by_node_editor_context(
|
||||
*snode, node, socket);
|
||||
if (socket_log == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const geo_log::ValueLog *value_log = socket_log->value();
|
||||
using namespace blender::nodes::geo_eval_log;
|
||||
tree_draw_ctx.geo_tree_log->ensure_socket_values();
|
||||
ValueLog *value_log = tree_draw_ctx.geo_tree_log->find_socket_value_log(socket);
|
||||
if (value_log == nullptr) {
|
||||
return {};
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
if (const geo_log::GenericValueLog *generic_value_log =
|
||||
dynamic_cast<const geo_log::GenericValueLog *>(value_log)) {
|
||||
create_inspection_string_for_generic_value(generic_value_log->value(), ss);
|
||||
create_inspection_string_for_generic_value(generic_value_log->value, ss);
|
||||
}
|
||||
if (const geo_log::GFieldValueLog *gfield_value_log =
|
||||
dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) {
|
||||
create_inspection_string_for_gfield(*gfield_value_log, ss);
|
||||
else if (const geo_log::FieldInfoLog *gfield_value_log =
|
||||
dynamic_cast<const geo_log::FieldInfoLog *>(value_log)) {
|
||||
create_inspection_string_for_field_info(*gfield_value_log, ss);
|
||||
}
|
||||
else if (const geo_log::GeometryValueLog *geo_value_log =
|
||||
dynamic_cast<const geo_log::GeometryValueLog *>(value_log)) {
|
||||
create_inspection_string_for_geometry(
|
||||
else if (const geo_log::GeometryInfoLog *geo_value_log =
|
||||
dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
|
||||
create_inspection_string_for_geometry_info(
|
||||
*geo_value_log,
|
||||
ss,
|
||||
dynamic_cast<const nodes::decl::Geometry *>(socket.runtime->declaration));
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
std::string str = ss.str();
|
||||
if (str.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool node_socket_has_tooltip(const bNodeTree &ntree, const bNodeSocket &socket)
|
||||
static bool node_socket_has_tooltip(const bNodeTree *ntree, const bNodeSocket *socket)
|
||||
{
|
||||
if (ntree.type == NTREE_GEOMETRY) {
|
||||
if (ntree->type == NTREE_GEOMETRY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (socket.runtime->declaration != nullptr) {
|
||||
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
|
||||
if (socket->runtime->declaration != nullptr) {
|
||||
const nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration;
|
||||
return !socket_decl.description().is_empty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *node_socket_get_tooltip(const bContext &C,
|
||||
const bNodeTree &ntree,
|
||||
const bNode &node,
|
||||
const bNodeSocket &socket)
|
||||
static char *node_socket_get_tooltip(const bContext *C,
|
||||
const bNodeTree *ntree,
|
||||
const bNode *UNUSED(node),
|
||||
const bNodeSocket *socket)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
TreeDrawContext tree_draw_ctx;
|
||||
if (snode != nullptr) {
|
||||
if (ntree->type == NTREE_GEOMETRY) {
|
||||
tree_draw_ctx.geo_tree_log =
|
||||
nodes::geo_eval_log::GeoModifierLog::get_tree_log_for_node_editor(*snode);
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream output;
|
||||
if (socket.runtime->declaration != nullptr) {
|
||||
const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
|
||||
if (socket->runtime->declaration != nullptr) {
|
||||
const blender::nodes::SocketDeclaration &socket_decl = *socket->runtime->declaration;
|
||||
blender::StringRef description = socket_decl.description();
|
||||
if (!description.is_empty()) {
|
||||
output << TIP_(description.data());
|
||||
}
|
||||
}
|
||||
|
||||
if (ntree.type == NTREE_GEOMETRY) {
|
||||
if (ntree->type == NTREE_GEOMETRY && tree_draw_ctx.geo_tree_log != nullptr) {
|
||||
if (!output.str().empty()) {
|
||||
output << ".\n\n";
|
||||
}
|
||||
|
||||
std::optional<std::string> socket_inspection_str = create_socket_inspection_string(
|
||||
C, node, socket);
|
||||
tree_draw_ctx, *socket);
|
||||
if (socket_inspection_str.has_value()) {
|
||||
output << *socket_inspection_str;
|
||||
}
|
||||
else {
|
||||
output << TIP_("The socket value has not been computed yet");
|
||||
output << TIP_("Unknown socket value");
|
||||
}
|
||||
}
|
||||
|
||||
if (output.str().empty()) {
|
||||
output << nodeSocketLabel(&socket);
|
||||
output << nodeSocketLabel(socket);
|
||||
}
|
||||
|
||||
return BLI_strdup(output.str().c_str());
|
||||
}
|
||||
|
||||
static void node_socket_add_tooltip_in_node_editor(TreeDrawContext *UNUSED(tree_draw_ctx),
|
||||
const bNodeTree *ntree,
|
||||
const bNode *node,
|
||||
const bNodeSocket *sock,
|
||||
uiLayout *layout)
|
||||
{
|
||||
if (!node_socket_has_tooltip(ntree, sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SocketTooltipData *data = MEM_cnew<SocketTooltipData>(__func__);
|
||||
data->ntree = ntree;
|
||||
data->node = node;
|
||||
data->socket = sock;
|
||||
|
||||
uiLayoutSetTooltipFunc(
|
||||
layout,
|
||||
[](bContext *C, void *argN, const char *UNUSED(tip)) {
|
||||
SocketTooltipData *data = static_cast<SocketTooltipData *>(argN);
|
||||
return node_socket_get_tooltip(C, data->ntree, data->node, data->socket);
|
||||
},
|
||||
data,
|
||||
MEM_dupallocN,
|
||||
MEM_freeN);
|
||||
}
|
||||
|
||||
void node_socket_add_tooltip(const bNodeTree &ntree,
|
||||
const bNode &node,
|
||||
const bNodeSocket &sock,
|
||||
uiLayout &layout)
|
||||
{
|
||||
if (!node_socket_has_tooltip(ntree, sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SocketTooltipData *data = MEM_new<SocketTooltipData>(__func__);
|
||||
data->ntree = &ntree;
|
||||
data->node = &node;
|
||||
data->socket = &sock;
|
||||
|
||||
uiLayoutSetTooltipFunc(
|
||||
&layout,
|
||||
[](bContext *C, void *argN, const char *UNUSED(tip)) {
|
||||
const SocketTooltipData *data = static_cast<SocketTooltipData *>(argN);
|
||||
return node_socket_get_tooltip(*C, *data->ntree, *data->node, *data->socket);
|
||||
},
|
||||
data,
|
||||
MEM_dupallocN,
|
||||
MEM_freeN);
|
||||
node_socket_add_tooltip_in_node_editor(nullptr, &ntree, &node, &sock, &layout);
|
||||
}
|
||||
|
||||
static void node_socket_draw_nested(const bContext &C,
|
||||
@@ -1146,7 +1182,7 @@ static void node_socket_draw_nested(const bContext &C,
|
||||
size_id,
|
||||
outline_col_id);
|
||||
|
||||
if (!node_socket_has_tooltip(ntree, sock)) {
|
||||
if (!node_socket_has_tooltip(&ntree, &sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1178,7 +1214,7 @@ static void node_socket_draw_nested(const bContext &C,
|
||||
but,
|
||||
[](bContext *C, void *argN, const char *UNUSED(tip)) {
|
||||
SocketTooltipData *data = (SocketTooltipData *)argN;
|
||||
return node_socket_get_tooltip(*C, *data->ntree, *data->node, *data->socket);
|
||||
return node_socket_get_tooltip(C, data->ntree, data->node, data->socket);
|
||||
},
|
||||
data,
|
||||
MEM_freeN);
|
||||
@@ -1537,14 +1573,14 @@ static void node_draw_sockets(const View2D &v2d,
|
||||
}
|
||||
}
|
||||
|
||||
static int node_error_type_to_icon(const geo_log::NodeWarningType type)
|
||||
static int node_error_type_to_icon(const NodeWarningType type)
|
||||
{
|
||||
switch (type) {
|
||||
case geo_log::NodeWarningType::Error:
|
||||
case NodeWarningType::Error:
|
||||
return ICON_ERROR;
|
||||
case geo_log::NodeWarningType::Warning:
|
||||
case NodeWarningType::Warning:
|
||||
return ICON_ERROR;
|
||||
case geo_log::NodeWarningType::Info:
|
||||
case NodeWarningType::Info:
|
||||
return ICON_INFO;
|
||||
}
|
||||
|
||||
@@ -1552,14 +1588,14 @@ static int node_error_type_to_icon(const geo_log::NodeWarningType type)
|
||||
return ICON_ERROR;
|
||||
}
|
||||
|
||||
static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
|
||||
static uint8_t node_error_type_priority(const NodeWarningType type)
|
||||
{
|
||||
switch (type) {
|
||||
case geo_log::NodeWarningType::Error:
|
||||
case NodeWarningType::Error:
|
||||
return 3;
|
||||
case geo_log::NodeWarningType::Warning:
|
||||
case NodeWarningType::Warning:
|
||||
return 2;
|
||||
case geo_log::NodeWarningType::Info:
|
||||
case NodeWarningType::Info:
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1567,11 +1603,11 @@ static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static geo_log::NodeWarningType node_error_highest_priority(Span<geo_log::NodeWarning> warnings)
|
||||
static NodeWarningType node_error_highest_priority(Span<NodeWarning> warnings)
|
||||
{
|
||||
uint8_t highest_priority = 0;
|
||||
geo_log::NodeWarningType highest_priority_type = geo_log::NodeWarningType::Info;
|
||||
for (const geo_log::NodeWarning &warning : warnings) {
|
||||
NodeWarningType highest_priority_type = NodeWarningType::Info;
|
||||
for (const NodeWarning &warning : warnings) {
|
||||
const uint8_t priority = node_error_type_priority(warning.type);
|
||||
if (priority > highest_priority) {
|
||||
highest_priority = priority;
|
||||
@@ -1582,7 +1618,7 @@ static geo_log::NodeWarningType node_error_highest_priority(Span<geo_log::NodeWa
|
||||
}
|
||||
|
||||
struct NodeErrorsTooltipData {
|
||||
Span<geo_log::NodeWarning> warnings;
|
||||
Span<NodeWarning> warnings;
|
||||
};
|
||||
|
||||
static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
|
||||
@@ -1591,7 +1627,7 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
|
||||
|
||||
std::string complete_string;
|
||||
|
||||
for (const geo_log::NodeWarning &warning : data.warnings.drop_back(1)) {
|
||||
for (const NodeWarning &warning : data.warnings.drop_back(1)) {
|
||||
complete_string += warning.message;
|
||||
/* Adding the period is not ideal for multi-line messages, but it is consistent
|
||||
* with other tooltip implementations in Blender, so it is added here. */
|
||||
@@ -1607,28 +1643,27 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
|
||||
|
||||
#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
|
||||
|
||||
static void node_add_error_message_button(
|
||||
const bContext &C, bNode &node, uiBlock &block, const rctf &rect, float &icon_offset)
|
||||
static void node_add_error_message_button(TreeDrawContext &tree_draw_ctx,
|
||||
bNode &node,
|
||||
uiBlock &block,
|
||||
const rctf &rect,
|
||||
float &icon_offset)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(&C);
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(*snode,
|
||||
node);
|
||||
if (node_log == nullptr) {
|
||||
return;
|
||||
Span<NodeWarning> warnings;
|
||||
if (tree_draw_ctx.geo_tree_log) {
|
||||
GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name);
|
||||
if (node_log != nullptr) {
|
||||
warnings = node_log->warnings;
|
||||
}
|
||||
}
|
||||
|
||||
Span<geo_log::NodeWarning> warnings = node_log->warnings();
|
||||
|
||||
if (warnings.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeErrorsTooltipData *tooltip_data = (NodeErrorsTooltipData *)MEM_mallocN(
|
||||
sizeof(NodeErrorsTooltipData), __func__);
|
||||
const NodeWarningType display_type = node_error_highest_priority(warnings);
|
||||
NodeErrorsTooltipData *tooltip_data = MEM_new<NodeErrorsTooltipData>(__func__);
|
||||
tooltip_data->warnings = warnings;
|
||||
|
||||
const geo_log::NodeWarningType display_type = node_error_highest_priority(warnings);
|
||||
|
||||
icon_offset -= NODE_HEADER_ICON_SIZE;
|
||||
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
|
||||
uiBut *but = uiDefIconBut(&block,
|
||||
@@ -1645,90 +1680,70 @@ static void node_add_error_message_button(
|
||||
0,
|
||||
0,
|
||||
nullptr);
|
||||
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, MEM_freeN);
|
||||
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, [](void *arg) {
|
||||
MEM_delete(static_cast<NodeErrorsTooltipData *>(arg));
|
||||
});
|
||||
UI_block_emboss_set(&block, UI_EMBOSS);
|
||||
}
|
||||
|
||||
static void get_exec_time_other_nodes(const bNode &node,
|
||||
const SpaceNode &snode,
|
||||
std::chrono::microseconds &exec_time,
|
||||
int &node_count)
|
||||
static std::optional<std::chrono::nanoseconds> node_get_execution_time(
|
||||
TreeDrawContext &tree_draw_ctx, const bNodeTree &ntree, const bNode &node)
|
||||
{
|
||||
if (node.type == NODE_GROUP) {
|
||||
const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
|
||||
snode);
|
||||
if (root_tree_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name);
|
||||
if (tree_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
exec_time += node_log.execution_time();
|
||||
node_count++;
|
||||
});
|
||||
const GeoTreeLog *tree_log = tree_draw_ctx.geo_tree_log;
|
||||
if (tree_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
else {
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
|
||||
snode, node);
|
||||
if (node_log) {
|
||||
exec_time += node_log->execution_time();
|
||||
node_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::chrono::microseconds node_get_execution_time(const bNodeTree &ntree,
|
||||
const bNode &node,
|
||||
const SpaceNode &snode,
|
||||
int &node_count)
|
||||
{
|
||||
std::chrono::microseconds exec_time = std::chrono::microseconds::zero();
|
||||
if (node.type == NODE_GROUP_OUTPUT) {
|
||||
const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
|
||||
snode);
|
||||
|
||||
if (tree_log == nullptr) {
|
||||
return exec_time;
|
||||
}
|
||||
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
exec_time += node_log.execution_time();
|
||||
node_count++;
|
||||
});
|
||||
return tree_log->run_time_sum;
|
||||
}
|
||||
else if (node.type == NODE_FRAME) {
|
||||
if (node.type == NODE_FRAME) {
|
||||
/* Could be cached in the future if this recursive code turns out to be slow. */
|
||||
std::chrono::nanoseconds run_time{0};
|
||||
bool found_node = false;
|
||||
LISTBASE_FOREACH (bNode *, tnode, &ntree.nodes) {
|
||||
if (tnode->parent != &node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tnode->type == NODE_FRAME) {
|
||||
exec_time += node_get_execution_time(ntree, *tnode, snode, node_count);
|
||||
std::optional<std::chrono::nanoseconds> sub_frame_run_time = node_get_execution_time(
|
||||
tree_draw_ctx, ntree, *tnode);
|
||||
if (sub_frame_run_time.has_value()) {
|
||||
run_time += *sub_frame_run_time;
|
||||
found_node = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
get_exec_time_other_nodes(*tnode, snode, exec_time, node_count);
|
||||
if (const GeoNodeLog *node_log = tree_log->nodes.lookup_ptr_as(tnode->name)) {
|
||||
found_node = true;
|
||||
run_time += node_log->run_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found_node) {
|
||||
return run_time;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else {
|
||||
get_exec_time_other_nodes(node, snode, exec_time, node_count);
|
||||
if (const GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node.name)) {
|
||||
return node_log->run_time;
|
||||
}
|
||||
return exec_time;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string node_get_execution_time_label(const SpaceNode &snode, const bNode &node)
|
||||
static std::string node_get_execution_time_label(TreeDrawContext &tree_draw_ctx,
|
||||
const SpaceNode &snode,
|
||||
const bNode &node)
|
||||
{
|
||||
int node_count = 0;
|
||||
std::chrono::microseconds exec_time = node_get_execution_time(
|
||||
*snode.edittree, node, snode, node_count);
|
||||
const std::optional<std::chrono::nanoseconds> exec_time = node_get_execution_time(
|
||||
tree_draw_ctx, *snode.edittree, node);
|
||||
|
||||
if (node_count == 0) {
|
||||
if (!exec_time.has_value()) {
|
||||
return std::string("");
|
||||
}
|
||||
|
||||
uint64_t exec_time_us = exec_time.count();
|
||||
const uint64_t exec_time_us =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(*exec_time).count();
|
||||
|
||||
/* Don't show time if execution time is 0 microseconds. */
|
||||
if (exec_time_us == 0) {
|
||||
@@ -1763,7 +1778,7 @@ struct NodeExtraInfoRow {
|
||||
};
|
||||
|
||||
struct NamedAttributeTooltipArg {
|
||||
Map<std::string, eNamedAttrUsage> usage_by_attribute;
|
||||
Map<std::string, NamedAttributeUsage> usage_by_attribute;
|
||||
};
|
||||
|
||||
static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
|
||||
@@ -1775,7 +1790,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
|
||||
|
||||
struct NameWithUsage {
|
||||
StringRefNull name;
|
||||
eNamedAttrUsage usage;
|
||||
NamedAttributeUsage usage;
|
||||
};
|
||||
|
||||
Vector<NameWithUsage> sorted_used_attribute;
|
||||
@@ -1790,16 +1805,16 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
|
||||
|
||||
for (const NameWithUsage &attribute : sorted_used_attribute) {
|
||||
const StringRefNull name = attribute.name;
|
||||
const eNamedAttrUsage usage = attribute.usage;
|
||||
const NamedAttributeUsage usage = attribute.usage;
|
||||
ss << " \u2022 \"" << name << "\": ";
|
||||
Vector<std::string> usages;
|
||||
if ((usage & eNamedAttrUsage::Read) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("read"));
|
||||
}
|
||||
if ((usage & eNamedAttrUsage::Write) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("write"));
|
||||
}
|
||||
if ((usage & eNamedAttrUsage::Remove) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("remove"));
|
||||
}
|
||||
for (const int i : usages.index_range()) {
|
||||
@@ -1817,7 +1832,7 @@ static char *named_attribute_tooltip(bContext *UNUSED(C), void *argN, const char
|
||||
}
|
||||
|
||||
static NodeExtraInfoRow row_from_used_named_attribute(
|
||||
const Map<std::string, eNamedAttrUsage> &usage_by_attribute_name)
|
||||
const Map<std::string, NamedAttributeUsage> &usage_by_attribute_name)
|
||||
{
|
||||
const int attributes_num = usage_by_attribute_name.size();
|
||||
|
||||
@@ -1831,32 +1846,11 @@ static NodeExtraInfoRow row_from_used_named_attribute(
|
||||
return row;
|
||||
}
|
||||
|
||||
static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(const SpaceNode &snode,
|
||||
const bNode &node)
|
||||
static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(
|
||||
TreeDrawContext &tree_draw_ctx, const bNode &node)
|
||||
{
|
||||
if (node.type == NODE_GROUP) {
|
||||
const geo_log::TreeLog *root_tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
|
||||
snode);
|
||||
if (root_tree_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const geo_log::TreeLog *tree_log = root_tree_log->lookup_child_log(node.name);
|
||||
if (tree_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Map<std::string, eNamedAttrUsage> usage_by_attribute;
|
||||
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) {
|
||||
usage_by_attribute.lookup_or_add_as(used_attribute.name,
|
||||
used_attribute.usage) |= used_attribute.usage;
|
||||
}
|
||||
});
|
||||
if (usage_by_attribute.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return row_from_used_named_attribute(usage_by_attribute);
|
||||
if (tree_draw_ctx.geo_tree_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (ELEM(node.type,
|
||||
GEO_NODE_STORE_NAMED_ATTRIBUTE,
|
||||
@@ -1865,31 +1859,26 @@ static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(const Sp
|
||||
/* Only show the overlay when the name is passed in from somewhere else. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) {
|
||||
if (STREQ(socket->name, "Name")) {
|
||||
if ((socket->flag & SOCK_IN_USE) == 0) {
|
||||
if (!socket->is_directly_linked()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
|
||||
snode, node.name);
|
||||
if (node_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
Map<std::string, eNamedAttrUsage> usage_by_attribute;
|
||||
for (const geo_log::UsedNamedAttribute &used_attribute : node_log->used_named_attributes()) {
|
||||
usage_by_attribute.lookup_or_add_as(used_attribute.name,
|
||||
used_attribute.usage) |= used_attribute.usage;
|
||||
}
|
||||
if (usage_by_attribute.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return row_from_used_named_attribute(usage_by_attribute);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
tree_draw_ctx.geo_tree_log->ensure_used_named_attributes();
|
||||
GeoNodeLog *node_log = tree_draw_ctx.geo_tree_log->nodes.lookup_ptr(node.name);
|
||||
if (node_log == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (node_log->used_named_attributes.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return row_from_used_named_attribute(node_log->used_named_attributes);
|
||||
}
|
||||
|
||||
static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, const bNode &node)
|
||||
static Vector<NodeExtraInfoRow> node_get_extra_info(TreeDrawContext &tree_draw_ctx,
|
||||
const SpaceNode &snode,
|
||||
const bNode &node)
|
||||
{
|
||||
Vector<NodeExtraInfoRow> rows;
|
||||
if (!(snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS)) {
|
||||
@@ -1898,7 +1887,8 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
|
||||
|
||||
if (snode.overlay.flag & SN_OVERLAY_SHOW_NAMED_ATTRIBUTES &&
|
||||
snode.edittree->type == NTREE_GEOMETRY) {
|
||||
if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(snode, node)) {
|
||||
if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(tree_draw_ctx,
|
||||
node)) {
|
||||
rows.append(std::move(*row));
|
||||
}
|
||||
}
|
||||
@@ -1907,7 +1897,7 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
|
||||
(ELEM(node.typeinfo->nclass, NODE_CLASS_GEOMETRY, NODE_CLASS_GROUP, NODE_CLASS_ATTRIBUTE) ||
|
||||
ELEM(node.type, NODE_FRAME, NODE_GROUP_OUTPUT))) {
|
||||
NodeExtraInfoRow row;
|
||||
row.text = node_get_execution_time_label(snode, node);
|
||||
row.text = node_get_execution_time_label(tree_draw_ctx, snode, node);
|
||||
if (!row.text.empty()) {
|
||||
row.tooltip = TIP_(
|
||||
"The execution time from the node tree's latest evaluation. For frame and group nodes, "
|
||||
@@ -1916,16 +1906,6 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const SpaceNode &snode, cons
|
||||
rows.append(std::move(row));
|
||||
}
|
||||
}
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(snode,
|
||||
node);
|
||||
if (node_log != nullptr) {
|
||||
for (const std::string &message : node_log->debug_messages()) {
|
||||
NodeExtraInfoRow row;
|
||||
row.text = message;
|
||||
row.icon = ICON_INFO;
|
||||
rows.append(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
@@ -1988,9 +1968,12 @@ static void node_draw_extra_info_row(const bNode &node,
|
||||
}
|
||||
}
|
||||
|
||||
static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node, uiBlock &block)
|
||||
static void node_draw_extra_info_panel(TreeDrawContext &tree_draw_ctx,
|
||||
const SpaceNode &snode,
|
||||
const bNode &node,
|
||||
uiBlock &block)
|
||||
{
|
||||
Vector<NodeExtraInfoRow> extra_info_rows = node_get_extra_info(snode, node);
|
||||
Vector<NodeExtraInfoRow> extra_info_rows = node_get_extra_info(tree_draw_ctx, snode, node);
|
||||
|
||||
if (extra_info_rows.size() == 0) {
|
||||
return;
|
||||
@@ -2046,6 +2029,7 @@ static void node_draw_extra_info_panel(const SpaceNode &snode, const bNode &node
|
||||
}
|
||||
|
||||
static void node_draw_basis(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
const View2D &v2d,
|
||||
const SpaceNode &snode,
|
||||
bNodeTree &ntree,
|
||||
@@ -2070,7 +2054,7 @@ static void node_draw_basis(const bContext &C,
|
||||
|
||||
GPU_line_width(1.0f);
|
||||
|
||||
node_draw_extra_info_panel(snode, node, block);
|
||||
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
|
||||
|
||||
/* Header. */
|
||||
{
|
||||
@@ -2165,7 +2149,7 @@ static void node_draw_basis(const bContext &C,
|
||||
UI_block_emboss_set(&block, UI_EMBOSS);
|
||||
}
|
||||
|
||||
node_add_error_message_button(C, node, block, rct, iconofs);
|
||||
node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs);
|
||||
|
||||
/* Title. */
|
||||
if (node.flag & SELECT) {
|
||||
@@ -2338,6 +2322,7 @@ static void node_draw_basis(const bContext &C,
|
||||
}
|
||||
|
||||
static void node_draw_hidden(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
const View2D &v2d,
|
||||
const SpaceNode &snode,
|
||||
bNodeTree &ntree,
|
||||
@@ -2353,7 +2338,7 @@ static void node_draw_hidden(const bContext &C,
|
||||
|
||||
const int color_id = node_get_colorid(node);
|
||||
|
||||
node_draw_extra_info_panel(snode, node, block);
|
||||
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
|
||||
|
||||
/* Shadow. */
|
||||
node_draw_shadow(snode, node, hiddenrad, 1.0f);
|
||||
@@ -2668,6 +2653,7 @@ static void reroute_node_prepare_for_draw(bNode &node)
|
||||
}
|
||||
|
||||
static void node_update_nodetree(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
bNodeTree &ntree,
|
||||
Span<bNode *> nodes,
|
||||
Span<uiBlock *> blocks)
|
||||
@@ -2694,7 +2680,7 @@ static void node_update_nodetree(const bContext &C,
|
||||
node_update_hidden(node, block);
|
||||
}
|
||||
else {
|
||||
node_update_basis(C, ntree, node, block);
|
||||
node_update_basis(C, tree_draw_ctx, ntree, node, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2795,6 +2781,7 @@ static void frame_node_draw_label(const bNodeTree &ntree,
|
||||
}
|
||||
|
||||
static void frame_node_draw(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
const ARegion ®ion,
|
||||
const SpaceNode &snode,
|
||||
bNodeTree &ntree,
|
||||
@@ -2841,7 +2828,7 @@ static void frame_node_draw(const bContext &C,
|
||||
/* label and text */
|
||||
frame_node_draw_label(ntree, node, snode);
|
||||
|
||||
node_draw_extra_info_panel(snode, node, block);
|
||||
node_draw_extra_info_panel(tree_draw_ctx, snode, node, block);
|
||||
|
||||
UI_block_end(&C, &block);
|
||||
UI_block_draw(&C, &block);
|
||||
@@ -2895,6 +2882,7 @@ static void reroute_node_draw(
|
||||
}
|
||||
|
||||
static void node_draw(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
ARegion ®ion,
|
||||
const SpaceNode &snode,
|
||||
bNodeTree &ntree,
|
||||
@@ -2903,7 +2891,7 @@ static void node_draw(const bContext &C,
|
||||
bNodeInstanceKey key)
|
||||
{
|
||||
if (node.type == NODE_FRAME) {
|
||||
frame_node_draw(C, region, snode, ntree, node, block);
|
||||
frame_node_draw(C, tree_draw_ctx, region, snode, ntree, node, block);
|
||||
}
|
||||
else if (node.type == NODE_REROUTE) {
|
||||
reroute_node_draw(C, region, ntree, node, block);
|
||||
@@ -2911,10 +2899,10 @@ static void node_draw(const bContext &C,
|
||||
else {
|
||||
const View2D &v2d = region.v2d;
|
||||
if (node.flag & NODE_HIDDEN) {
|
||||
node_draw_hidden(C, v2d, snode, ntree, node, block);
|
||||
node_draw_hidden(C, tree_draw_ctx, v2d, snode, ntree, node, block);
|
||||
}
|
||||
else {
|
||||
node_draw_basis(C, v2d, snode, ntree, node, block, key);
|
||||
node_draw_basis(C, tree_draw_ctx, v2d, snode, ntree, node, block, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2922,6 +2910,7 @@ static void node_draw(const bContext &C,
|
||||
#define USE_DRAW_TOT_UPDATE
|
||||
|
||||
static void node_draw_nodetree(const bContext &C,
|
||||
TreeDrawContext &tree_draw_ctx,
|
||||
ARegion ®ion,
|
||||
SpaceNode &snode,
|
||||
bNodeTree &ntree,
|
||||
@@ -2946,7 +2935,7 @@ static void node_draw_nodetree(const bContext &C,
|
||||
}
|
||||
|
||||
bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]);
|
||||
node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key);
|
||||
node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key);
|
||||
}
|
||||
|
||||
/* Node lines. */
|
||||
@@ -2976,7 +2965,7 @@ static void node_draw_nodetree(const bContext &C,
|
||||
}
|
||||
|
||||
bNodeInstanceKey key = BKE_node_instance_key(parent_key, &ntree, nodes[i]);
|
||||
node_draw(C, region, snode, ntree, *nodes[i], *blocks[i], key);
|
||||
node_draw(C, tree_draw_ctx, region, snode, ntree, *nodes[i], *blocks[i], key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3035,8 +3024,18 @@ static void draw_nodetree(const bContext &C,
|
||||
|
||||
Array<uiBlock *> blocks = node_uiblocks_init(C, nodes);
|
||||
|
||||
node_update_nodetree(C, ntree, nodes, blocks);
|
||||
node_draw_nodetree(C, region, *snode, ntree, nodes, blocks, parent_key);
|
||||
TreeDrawContext tree_draw_ctx;
|
||||
if (ntree.type == NTREE_GEOMETRY) {
|
||||
tree_draw_ctx.geo_tree_log = nodes::geo_eval_log::GeoModifierLog::get_tree_log_for_node_editor(
|
||||
*snode);
|
||||
if (tree_draw_ctx.geo_tree_log != nullptr) {
|
||||
tree_draw_ctx.geo_tree_log->ensure_node_warnings();
|
||||
tree_draw_ctx.geo_tree_log->ensure_node_run_time();
|
||||
}
|
||||
}
|
||||
|
||||
node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks);
|
||||
node_draw_nodetree(C, tree_draw_ctx, region, *snode, ntree, nodes, blocks, parent_key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
@@ -30,12 +31,11 @@
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
|
||||
#include "node_intern.hh"
|
||||
|
||||
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
|
||||
using geo_log::GeometryAttributeInfo;
|
||||
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
|
||||
|
||||
namespace blender::ed::space_node {
|
||||
|
||||
@@ -50,6 +50,8 @@ BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, "");
|
||||
static Vector<const GeometryAttributeInfo *> get_attribute_info_from_context(
|
||||
const bContext &C, AttributeSearchData &data)
|
||||
{
|
||||
using namespace nodes::geo_eval_log;
|
||||
|
||||
SpaceNode *snode = CTX_wm_space_node(&C);
|
||||
if (!snode) {
|
||||
BLI_assert_unreachable();
|
||||
@@ -65,41 +67,48 @@ static Vector<const GeometryAttributeInfo *> get_attribute_info_from_context(
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
GeoTreeLog *tree_log = GeoModifierLog::get_tree_log_for_node_editor(*snode);
|
||||
if (tree_log == nullptr) {
|
||||
return {};
|
||||
}
|
||||
tree_log->ensure_socket_values();
|
||||
|
||||
/* For the attribute input node, collect attribute information from all nodes in the group. */
|
||||
if (node->type == GEO_NODE_INPUT_NAMED_ATTRIBUTE) {
|
||||
const geo_log::TreeLog *tree_log = geo_log::ModifierLog::find_tree_by_node_editor_context(
|
||||
*snode);
|
||||
if (tree_log == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
tree_log->ensure_existing_attributes();
|
||||
Vector<const GeometryAttributeInfo *> attributes;
|
||||
Set<StringRef> names;
|
||||
tree_log->foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
for (const geo_log::SocketLog &socket_log : node_log.input_logs()) {
|
||||
const geo_log::ValueLog *value_log = socket_log.value();
|
||||
if (const geo_log::GeometryValueLog *geo_value_log =
|
||||
dynamic_cast<const geo_log::GeometryValueLog *>(value_log)) {
|
||||
for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) {
|
||||
if (bke::allow_procedural_attribute_access(attribute.name)) {
|
||||
if (names.add(attribute.name)) {
|
||||
attributes.append(&attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const GeometryAttributeInfo *attribute : tree_log->existing_attributes) {
|
||||
if (bke::allow_procedural_attribute_access(attribute->name)) {
|
||||
attributes.append(attribute);
|
||||
}
|
||||
});
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
|
||||
*snode, data.node_name);
|
||||
GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node->name);
|
||||
if (node_log == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return node_log->lookup_available_attributes();
|
||||
Set<StringRef> names;
|
||||
Vector<const GeometryAttributeInfo *> attributes;
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
if (input_socket->type != SOCK_GEOMETRY) {
|
||||
continue;
|
||||
}
|
||||
const ValueLog *value_log = tree_log->find_socket_value_log(*input_socket);
|
||||
if (value_log == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
|
||||
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
|
||||
if (bke::allow_procedural_attribute_access(attribute.name)) {
|
||||
if (names.add(attribute.name)) {
|
||||
attributes.append(&attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static void attribute_search_update_fn(
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_compute_contexts.hh"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_editmesh.h"
|
||||
@@ -26,7 +27,8 @@
|
||||
#include "ED_curves_sculpt.h"
|
||||
#include "ED_spreadsheet.h"
|
||||
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
@@ -40,8 +42,8 @@
|
||||
#include "spreadsheet_data_source_geometry.hh"
|
||||
#include "spreadsheet_intern.hh"
|
||||
|
||||
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
|
||||
using blender::fn::GField;
|
||||
using blender::nodes::geo_eval_log::ViewerNodeLog;
|
||||
|
||||
namespace blender::ed::spreadsheet {
|
||||
|
||||
@@ -465,19 +467,10 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread
|
||||
}
|
||||
}
|
||||
else {
|
||||
const geo_log::NodeLog *node_log =
|
||||
geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(*sspreadsheet);
|
||||
if (node_log != nullptr) {
|
||||
for (const geo_log::SocketLog &input_log : node_log->input_logs()) {
|
||||
if (const geo_log::GeometryValueLog *geo_value_log =
|
||||
dynamic_cast<const geo_log::GeometryValueLog *>(input_log.value())) {
|
||||
const GeometrySet *full_geometry = geo_value_log->full_geometry();
|
||||
if (full_geometry != nullptr) {
|
||||
geometry_set = *full_geometry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const ViewerNodeLog *viewer_log =
|
||||
nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet(
|
||||
*sspreadsheet)) {
|
||||
geometry_set = viewer_log->geometry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,27 +488,11 @@ static void find_fields_to_evaluate(const SpaceSpreadsheet *sspreadsheet,
|
||||
/* No viewer is currently referenced by the context path. */
|
||||
return;
|
||||
}
|
||||
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(
|
||||
*sspreadsheet);
|
||||
if (node_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const geo_log::SocketLog &socket_log : node_log->input_logs()) {
|
||||
const geo_log::ValueLog *value_log = socket_log.value();
|
||||
if (value_log == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (const geo_log::GFieldValueLog *field_value_log =
|
||||
dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) {
|
||||
const GField &field = field_value_log->field();
|
||||
if (field) {
|
||||
r_fields.add("Viewer", std::move(field));
|
||||
}
|
||||
}
|
||||
if (const geo_log::GenericValueLog *generic_value_log =
|
||||
dynamic_cast<const geo_log::GenericValueLog *>(value_log)) {
|
||||
GPointer value = generic_value_log->value();
|
||||
r_fields.add("Viewer", fn::make_constant_field(*value.type(), value.get()));
|
||||
if (const ViewerNodeLog *viewer_log =
|
||||
nodes::geo_eval_log::GeoModifierLog::find_viewer_node_log_for_spreadsheet(
|
||||
*sspreadsheet)) {
|
||||
if (viewer_log->field) {
|
||||
r_fields.add("Viewer", viewer_log->field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,10 @@ set(INC_SYS
|
||||
set(SRC
|
||||
intern/cpp_types.cc
|
||||
intern/field.cc
|
||||
intern/lazy_function.cc
|
||||
intern/lazy_function_execute.cc
|
||||
intern/lazy_function_graph.cc
|
||||
intern/lazy_function_graph_executor.cc
|
||||
intern/multi_function.cc
|
||||
intern/multi_function_builder.cc
|
||||
intern/multi_function_params.cc
|
||||
@@ -23,6 +27,10 @@ set(SRC
|
||||
|
||||
FN_field.hh
|
||||
FN_field_cpp_type.hh
|
||||
FN_lazy_function.hh
|
||||
FN_lazy_function_execute.hh
|
||||
FN_lazy_function_graph.hh
|
||||
FN_lazy_function_graph_executor.hh
|
||||
FN_multi_function.hh
|
||||
FN_multi_function_builder.hh
|
||||
FN_multi_function_context.hh
|
||||
@@ -61,6 +69,7 @@ blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
tests/FN_field_test.cc
|
||||
tests/FN_lazy_function_test.cc
|
||||
tests/FN_multi_function_procedure_test.cc
|
||||
tests/FN_multi_function_test.cc
|
||||
|
||||
|
@@ -565,6 +565,17 @@ template<typename T> struct ValueOrField {
|
||||
}
|
||||
return this->value;
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const ValueOrField<T> &value_or_field)
|
||||
{
|
||||
if (value_or_field.field) {
|
||||
stream << "ValueOrField<T>";
|
||||
}
|
||||
else {
|
||||
stream << value_or_field.value;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
@@ -59,7 +59,7 @@ class ValueOrFieldCPPType : public CPPType {
|
||||
public:
|
||||
template<typename T>
|
||||
ValueOrFieldCPPType(FieldCPPTypeParam<ValueOrField<T>> /* unused */, StringRef debug_name)
|
||||
: CPPType(CPPTypeParam<ValueOrField<T>, CPPTypeFlags::None>(), debug_name),
|
||||
: CPPType(CPPTypeParam<ValueOrField<T>, CPPTypeFlags::Printable>(), debug_name),
|
||||
base_type_(CPPType::get<T>())
|
||||
{
|
||||
construct_from_value_ = [](void *dst, const void *value_or_field) {
|
||||
|
384
source/blender/functions/FN_lazy_function.hh
Normal file
384
source/blender/functions/FN_lazy_function.hh
Normal file
@@ -0,0 +1,384 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*
|
||||
* A `LazyFunction` encapsulates a computation which has inputs, outputs and potentially side
|
||||
* effects. Most importantly, a `LazyFunction` supports lazyness in its inputs and outputs:
|
||||
* - Only outputs that are actually used have to be computed.
|
||||
* - Inputs can be requested lazily based on which outputs are used or what side effects the
|
||||
* function has.
|
||||
*
|
||||
* A lazy-function that uses lazyness may be executed more than once. The most common example is
|
||||
* the geometry nodes switch node. Depending on a condition input, it decides which one of the
|
||||
* other inputs is actually used. From the perspective of the switch node, its execution works as
|
||||
* follows:
|
||||
* 1. The switch node is first executed. It sees that the output is used. Now it requests the
|
||||
* condition input from the caller and exits.
|
||||
* 2. Once the caller is able to provide the condition input the switch node is executed again.
|
||||
* This time it retrieves the condition and requests one of the other inputs. Then the node
|
||||
* exits again, giving back control to the caller.
|
||||
* 3. When the caller computed the second requested input the switch node executes a last time.
|
||||
* This time it retrieves the new input and forwards it to the output.
|
||||
*
|
||||
* In some sense, a lazy-function can be thought of like a state machine. Every time it is
|
||||
* executed, it advances its state until all required outputs are ready.
|
||||
*
|
||||
* The lazy-function interface is designed to support composition of many such functions into a new
|
||||
* lazy-functions. All while keeping the lazyness working. For example, in geometry nodes a switch
|
||||
* node in a node group should still be able to decide whether a node in the parent group will be
|
||||
* executed or not. This is essential to avoid doing unnecessary work.
|
||||
*
|
||||
* The lazy-function system consists of multiple core components:
|
||||
* - The interface of a lazy-function itself including its calling convention.
|
||||
* - A graph data structure that allows composing many lazy-functions by connecting their inputs
|
||||
* and outputs.
|
||||
* - An executor that allows multi-threaded execution or such a graph.
|
||||
*/
|
||||
|
||||
#include "BLI_cpp_type.hh"
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_linear_allocator.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
enum class ValueUsage {
|
||||
/**
|
||||
* The value is definitely used and therefore has to be computed.
|
||||
*/
|
||||
Used,
|
||||
/**
|
||||
* It's unknown whether this value will be used or not. Computing it is ok but the result may be
|
||||
* discarded.
|
||||
*/
|
||||
Maybe,
|
||||
/**
|
||||
* The value will definitely not be used. It can still be computed but the result will be
|
||||
* discarded in all cases.
|
||||
*/
|
||||
Unused,
|
||||
};
|
||||
|
||||
class LazyFunction;
|
||||
|
||||
/**
|
||||
* This allows passing arbitrary data into a lazy-function during execution. For that, #UserData
|
||||
* has to be subclassed. This mainly exists because it's more type safe than passing a `void *`
|
||||
* with no type information attached.
|
||||
*
|
||||
* Some lazy-functions may expect to find a certain type of user data when executed.
|
||||
*/
|
||||
class UserData {
|
||||
public:
|
||||
virtual ~UserData() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Passed to the lazy-function when it is executed.
|
||||
*/
|
||||
struct Context {
|
||||
/**
|
||||
* If the lazy-function has some state (which only makes sense when it is executed more than once
|
||||
* to finish its job), the state is stored here. This points to memory returned from
|
||||
* #LazyFunction::init_storage.
|
||||
*/
|
||||
void *storage;
|
||||
/**
|
||||
* Custom user data that can be used in the function.
|
||||
*/
|
||||
UserData *user_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the calling convention for a lazy-function. During execution, a lazy-function retrieves
|
||||
* its inputs and sets the outputs through #Params.
|
||||
*/
|
||||
class Params {
|
||||
public:
|
||||
/**
|
||||
* The lazy-function this #Params has been prepared for.
|
||||
*/
|
||||
const LazyFunction &fn_;
|
||||
|
||||
public:
|
||||
Params(const LazyFunction &fn);
|
||||
|
||||
/**
|
||||
* Get a pointer to an input value if the value is available already. Otherwise null is returned.
|
||||
*
|
||||
* The #LazyFunction must leave returned object in an initialized state, but can move from it.
|
||||
*/
|
||||
void *try_get_input_data_ptr(int index) const;
|
||||
|
||||
/**
|
||||
* Same as #try_get_input_data_ptr, but if the data is not yet available, request it. This makes
|
||||
* sure that the data will be available in a future execution of the #LazyFunction.
|
||||
*/
|
||||
void *try_get_input_data_ptr_or_request(int index);
|
||||
|
||||
/**
|
||||
* Get a pointer to where the output value should be stored.
|
||||
* The value at the pointer is in an uninitialized state at first.
|
||||
* The #LazyFunction is responsible for initializing the value.
|
||||
* After the output has been initialized to its final value, #output_set has to be called.
|
||||
*/
|
||||
void *get_output_data_ptr(int index);
|
||||
|
||||
/**
|
||||
* Call this after the output value is initialized. After this is called, the value must not be
|
||||
* touched anymore. It may be moved or destructed immediatly.
|
||||
*/
|
||||
void output_set(int index);
|
||||
|
||||
/**
|
||||
* Allows the #MultiFunction to check whether an output was computed already without keeping
|
||||
* track of it itself.
|
||||
*/
|
||||
bool output_was_set(int index) const;
|
||||
|
||||
/**
|
||||
* Can be used to detect which outputs have to be computed.
|
||||
*/
|
||||
ValueUsage get_output_usage(int index) const;
|
||||
|
||||
/**
|
||||
* Tell the caller of the #LazyFunction that a specific input will definitely not be used.
|
||||
* Only an input that was not #ValueUsage::Used can become unused.
|
||||
*/
|
||||
void set_input_unused(int index);
|
||||
|
||||
/**
|
||||
* Typed utility methods that wrap the methods above.
|
||||
*/
|
||||
template<typename T> T extract_input(int index);
|
||||
template<typename T> const T &get_input(int index);
|
||||
template<typename T> T *try_get_input_data_ptr_or_request(int index);
|
||||
template<typename T> void set_output(int index, T &&value);
|
||||
|
||||
/**
|
||||
* Utility to initialize all outputs that haven't been set yet.
|
||||
*/
|
||||
void set_default_remaining_outputs();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Methods that need to be implemented by subclasses. Those are separate from the non-virtual
|
||||
* methods above to make it easy to insert additional debugging logic on top of the
|
||||
* implementations.
|
||||
*/
|
||||
virtual void *try_get_input_data_ptr_impl(int index) const = 0;
|
||||
virtual void *try_get_input_data_ptr_or_request_impl(int index) = 0;
|
||||
virtual void *get_output_data_ptr_impl(int index) = 0;
|
||||
virtual void output_set_impl(int index) = 0;
|
||||
virtual bool output_was_set_impl(int index) const = 0;
|
||||
virtual ValueUsage get_output_usage_impl(int index) const = 0;
|
||||
virtual void set_input_unused_impl(int index) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes an input of a #LazyFunction.
|
||||
*/
|
||||
struct Input {
|
||||
/**
|
||||
* Name used for debugging purposes. The string has to be static or has to be owned by something
|
||||
* else.
|
||||
*/
|
||||
const char *debug_name;
|
||||
/**
|
||||
* Data type of this input.
|
||||
*/
|
||||
const CPPType *type;
|
||||
/**
|
||||
* Can be used to indicate a caller or this function if this input is used statically before
|
||||
* executing it the first time. This is technically not needed but can improve efficiency because
|
||||
* a round-trip through the `execute` method can be avoided.
|
||||
*
|
||||
* When this is #ValueUsage::Used, the caller has to ensure that the input is definitely
|
||||
* available when the #execute method is first called. The #execute method does not have to check
|
||||
* whether the value is actually available.
|
||||
*/
|
||||
ValueUsage usage;
|
||||
|
||||
Input(const char *debug_name, const CPPType &type, const ValueUsage usage = ValueUsage::Used)
|
||||
: debug_name(debug_name), type(&type), usage(usage)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Output {
|
||||
/**
|
||||
* Name used for debugging purposes. The string has to be static or has to be owned by something
|
||||
* else.
|
||||
*/
|
||||
const char *debug_name;
|
||||
/**
|
||||
* Data type of this output.
|
||||
*/
|
||||
const CPPType *type = nullptr;
|
||||
|
||||
Output(const char *debug_name, const CPPType &type) : debug_name(debug_name), type(&type)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that can compute outputs and request inputs lazily. For more details see the comment
|
||||
* at the top of the file.
|
||||
*/
|
||||
class LazyFunction {
|
||||
protected:
|
||||
const char *debug_name_ = "<unknown>";
|
||||
Vector<Input> inputs_;
|
||||
Vector<Output> outputs_;
|
||||
|
||||
public:
|
||||
virtual ~LazyFunction() = default;
|
||||
|
||||
/**
|
||||
* Get a name of the function or an input or output. This is mainly used for debugging.
|
||||
* These are virtual functions because the names are often not used outside of debugging
|
||||
* workflows. This way the names are only generated when they are actually needed.
|
||||
*/
|
||||
virtual std::string name() const;
|
||||
virtual std::string input_name(int index) const;
|
||||
virtual std::string output_name(int index) const;
|
||||
|
||||
/**
|
||||
* Allocates storage for this function. The storage will be passed to every call to #execute.
|
||||
* If the function does not keep track of any state, this does not have to be implemented.
|
||||
*/
|
||||
virtual void *init_storage(LinearAllocator<> &allocator) const;
|
||||
|
||||
/**
|
||||
* Destruct the storage created in #init_storage.
|
||||
*/
|
||||
virtual void destruct_storage(void *storage) const;
|
||||
|
||||
/**
|
||||
* Inputs of the function.
|
||||
*/
|
||||
Span<Input> inputs() const;
|
||||
/**
|
||||
* Outputs of the function.
|
||||
*/
|
||||
Span<Output> outputs() const;
|
||||
|
||||
/**
|
||||
* During execution the function retrieves inputs and sets outputs in #params. For some
|
||||
* functions, this method is called more than once. After execution, the function either has
|
||||
* computed all required outputs or is waiting for more inputs.
|
||||
*/
|
||||
void execute(Params ¶ms, const Context &context) const;
|
||||
|
||||
/**
|
||||
* Utility to check that the guarantee by #Input::usage is followed.
|
||||
*/
|
||||
bool always_used_inputs_available(const Params ¶ms) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Needs to be implemented by subclasses. This is separate from #execute so that additional
|
||||
* debugging logic can be implemented in #execute.
|
||||
*/
|
||||
virtual void execute_impl(Params ¶ms, const Context &context) const = 0;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #LazyFunction Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline Span<Input> LazyFunction::inputs() const
|
||||
{
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
inline Span<Output> LazyFunction::outputs() const
|
||||
{
|
||||
return outputs_;
|
||||
}
|
||||
|
||||
inline void LazyFunction::execute(Params ¶ms, const Context &context) const
|
||||
{
|
||||
BLI_assert(this->always_used_inputs_available(params));
|
||||
this->execute_impl(params, context);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #Params Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline Params::Params(const LazyFunction &fn) : fn_(fn)
|
||||
{
|
||||
}
|
||||
|
||||
inline void *Params::try_get_input_data_ptr(const int index) const
|
||||
{
|
||||
return this->try_get_input_data_ptr_impl(index);
|
||||
}
|
||||
|
||||
inline void *Params::try_get_input_data_ptr_or_request(const int index)
|
||||
{
|
||||
return this->try_get_input_data_ptr_or_request_impl(index);
|
||||
}
|
||||
|
||||
inline void *Params::get_output_data_ptr(const int index)
|
||||
{
|
||||
return this->get_output_data_ptr_impl(index);
|
||||
}
|
||||
|
||||
inline void Params::output_set(const int index)
|
||||
{
|
||||
this->output_set_impl(index);
|
||||
}
|
||||
|
||||
inline bool Params::output_was_set(const int index) const
|
||||
{
|
||||
return this->output_was_set_impl(index);
|
||||
}
|
||||
|
||||
inline ValueUsage Params::get_output_usage(const int index) const
|
||||
{
|
||||
return this->get_output_usage_impl(index);
|
||||
}
|
||||
|
||||
inline void Params::set_input_unused(const int index)
|
||||
{
|
||||
this->set_input_unused_impl(index);
|
||||
}
|
||||
|
||||
template<typename T> inline T Params::extract_input(const int index)
|
||||
{
|
||||
void *data = this->try_get_input_data_ptr(index);
|
||||
BLI_assert(data != nullptr);
|
||||
T return_value = std::move(*static_cast<T *>(data));
|
||||
return return_value;
|
||||
}
|
||||
|
||||
template<typename T> inline const T &Params::get_input(const int index)
|
||||
{
|
||||
const void *data = this->try_get_input_data_ptr(index);
|
||||
BLI_assert(data != nullptr);
|
||||
return *static_cast<const T *>(data);
|
||||
}
|
||||
|
||||
template<typename T> inline T *Params::try_get_input_data_ptr_or_request(const int index)
|
||||
{
|
||||
return static_cast<T *>(this->try_get_input_data_ptr_or_request(index));
|
||||
}
|
||||
|
||||
template<typename T> inline void Params::set_output(const int index, T &&value)
|
||||
{
|
||||
using DecayT = std::decay_t<T>;
|
||||
void *data = this->get_output_data_ptr(index);
|
||||
new (data) DecayT(std::forward<T>(value));
|
||||
this->output_set(index);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
122
source/blender/functions/FN_lazy_function_execute.hh
Normal file
122
source/blender/functions/FN_lazy_function_execute.hh
Normal file
@@ -0,0 +1,122 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*
|
||||
* This file common utilities for actually executing a lazy-function.
|
||||
*/
|
||||
|
||||
#include "BLI_parameter_pack_utils.hh"
|
||||
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
/**
|
||||
* Most basic implementation of #Params. It does not actually implement any logic for how to
|
||||
* retrieve inputs or set outputs. Instead, code using #BasicParams has to implement that.
|
||||
*/
|
||||
class BasicParams : public Params {
|
||||
private:
|
||||
const Span<GMutablePointer> inputs_;
|
||||
const Span<GMutablePointer> outputs_;
|
||||
MutableSpan<std::optional<ValueUsage>> input_usages_;
|
||||
Span<ValueUsage> output_usages_;
|
||||
MutableSpan<bool> set_outputs_;
|
||||
|
||||
public:
|
||||
BasicParams(const LazyFunction &fn,
|
||||
const Span<GMutablePointer> inputs,
|
||||
const Span<GMutablePointer> outputs,
|
||||
MutableSpan<std::optional<ValueUsage>> input_usages,
|
||||
Span<ValueUsage> output_usages,
|
||||
MutableSpan<bool> set_outputs);
|
||||
|
||||
void *try_get_input_data_ptr_impl(const int index) const override;
|
||||
void *try_get_input_data_ptr_or_request_impl(const int index) override;
|
||||
void *get_output_data_ptr_impl(const int index) override;
|
||||
void output_set_impl(const int index) override;
|
||||
bool output_was_set_impl(const int index) const override;
|
||||
ValueUsage get_output_usage_impl(const int index) const override;
|
||||
void set_input_unused_impl(const int index) override;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Utility to implement #execute_lazy_function_eagerly.
|
||||
*/
|
||||
template<typename... Inputs, typename... Outputs, size_t... InIndices, size_t... OutIndices>
|
||||
inline void execute_lazy_function_eagerly_impl(
|
||||
const LazyFunction &fn,
|
||||
UserData *user_data,
|
||||
std::tuple<Inputs...> &inputs,
|
||||
std::tuple<Outputs *...> &outputs,
|
||||
std::index_sequence<InIndices...> /* in_indices */,
|
||||
std::index_sequence<OutIndices...> /* out_indices */)
|
||||
{
|
||||
constexpr size_t InputsNum = sizeof...(Inputs);
|
||||
constexpr size_t OutputsNum = sizeof...(Outputs);
|
||||
std::array<GMutablePointer, InputsNum> input_pointers;
|
||||
std::array<GMutablePointer, OutputsNum> output_pointers;
|
||||
std::array<std::optional<ValueUsage>, InputsNum> input_usages;
|
||||
std::array<ValueUsage, OutputsNum> output_usages;
|
||||
std::array<bool, OutputsNum> set_outputs;
|
||||
(
|
||||
[&]() {
|
||||
constexpr size_t I = InIndices;
|
||||
using T = Inputs;
|
||||
const CPPType &type = CPPType::get<T>();
|
||||
input_pointers[I] = {type, &std::get<I>(inputs)};
|
||||
}(),
|
||||
...);
|
||||
(
|
||||
[&]() {
|
||||
constexpr size_t I = OutIndices;
|
||||
using T = Outputs;
|
||||
const CPPType &type = CPPType::get<T>();
|
||||
output_pointers[I] = {type, std::get<I>(outputs)};
|
||||
}(),
|
||||
...);
|
||||
output_usages.fill(ValueUsage::Used);
|
||||
set_outputs.fill(false);
|
||||
LinearAllocator<> allocator;
|
||||
Context context;
|
||||
context.user_data = user_data;
|
||||
context.storage = fn.init_storage(allocator);
|
||||
BasicParams params{
|
||||
fn, input_pointers, output_pointers, input_usages, output_usages, set_outputs};
|
||||
fn.execute(params, context);
|
||||
fn.destruct_storage(context.storage);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* In some cases (mainly for tests), the set of inputs and outputs for a lazy-function is known at
|
||||
* compile time and one just wants to compute the outputs based on the inputs, without any
|
||||
* lazyness.
|
||||
*
|
||||
* This function does exactly that. It takes all inputs in a tuple and writes the outputs to points
|
||||
* provided in a second tuple. Since all inputs have to be provided, the lazy-function has to
|
||||
* compute all outputs.
|
||||
*/
|
||||
template<typename... Inputs, typename... Outputs>
|
||||
inline void execute_lazy_function_eagerly(const LazyFunction &fn,
|
||||
UserData *user_data,
|
||||
std::tuple<Inputs...> inputs,
|
||||
std::tuple<Outputs *...> outputs)
|
||||
{
|
||||
BLI_assert(fn.inputs().size() == sizeof...(Inputs));
|
||||
BLI_assert(fn.outputs().size() == sizeof...(Outputs));
|
||||
detail::execute_lazy_function_eagerly_impl(fn,
|
||||
user_data,
|
||||
inputs,
|
||||
outputs,
|
||||
std::make_index_sequence<sizeof...(Inputs)>(),
|
||||
std::make_index_sequence<sizeof...(Outputs)>());
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
421
source/blender/functions/FN_lazy_function_graph.hh
Normal file
421
source/blender/functions/FN_lazy_function_graph.hh
Normal file
@@ -0,0 +1,421 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*
|
||||
* This file contains a graph data structure that allows composing multiple lazy-functions into a
|
||||
* combined lazy-function.
|
||||
*
|
||||
* There are two types of nodes in the graph:
|
||||
* - #FunctionNode: Corresponds to a #LazyFunction. The inputs and outputs of the function become
|
||||
* input and output sockets of the node.
|
||||
* - #DummyNode: Is used to indicate inputs and outputs of the entire graph. It can have an
|
||||
* arbitrary number of sockets.
|
||||
*/
|
||||
|
||||
#include "BLI_linear_allocator.hh"
|
||||
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
class Socket;
|
||||
class InputSocket;
|
||||
class OutputSocket;
|
||||
class Node;
|
||||
class Graph;
|
||||
|
||||
/**
|
||||
* A #Socket is the interface of a #Node. Every #Socket is either an #InputSocket or #OutputSocket.
|
||||
* Links can be created from output sockets to input sockets.
|
||||
*/
|
||||
class Socket : NonCopyable, NonMovable {
|
||||
protected:
|
||||
/**
|
||||
* The node the socket belongs to.
|
||||
*/
|
||||
Node *node_;
|
||||
/**
|
||||
* Data type of the socket. Only sockets with the same type can be linked.
|
||||
*/
|
||||
const CPPType *type_;
|
||||
/**
|
||||
* Indicates whether this is an #InputSocket or #OutputSocket.
|
||||
*/
|
||||
bool is_input_;
|
||||
/**
|
||||
* Index of the socket. E.g. 0 for the first input and the first output socket.
|
||||
*/
|
||||
int index_in_node_;
|
||||
|
||||
friend Graph;
|
||||
|
||||
public:
|
||||
bool is_input() const;
|
||||
bool is_output() const;
|
||||
|
||||
int index() const;
|
||||
|
||||
InputSocket &as_input();
|
||||
OutputSocket &as_output();
|
||||
const InputSocket &as_input() const;
|
||||
const OutputSocket &as_output() const;
|
||||
|
||||
const Node &node() const;
|
||||
Node &node();
|
||||
|
||||
const CPPType &type() const;
|
||||
|
||||
std::string name() const;
|
||||
};
|
||||
|
||||
class InputSocket : public Socket {
|
||||
private:
|
||||
/**
|
||||
* An input can have at most one link connected to it. The linked socket is the "origin" because
|
||||
* it's where the data is coming from. The type of the origin must be the same as the type of
|
||||
* this socket.
|
||||
*/
|
||||
OutputSocket *origin_;
|
||||
/**
|
||||
* Can be null or a non-owning pointer to a value of the type of the socket. This value will be
|
||||
* used when the input is used but not linked.
|
||||
*
|
||||
* This is technically not needed, because one could just create a separate node that just
|
||||
* outputs the value, but that would have more overhead. Especially because it's commonly the
|
||||
* case that most inputs are unlinked.
|
||||
*/
|
||||
const void *default_value_ = nullptr;
|
||||
|
||||
friend Graph;
|
||||
|
||||
public:
|
||||
OutputSocket *origin();
|
||||
const OutputSocket *origin() const;
|
||||
|
||||
const void *default_value() const;
|
||||
void set_default_value(const void *value);
|
||||
};
|
||||
|
||||
class OutputSocket : public Socket {
|
||||
private:
|
||||
/**
|
||||
* An output can be linked to an arbitrary number of inputs of the same type.
|
||||
*/
|
||||
Vector<InputSocket *> targets_;
|
||||
|
||||
friend Graph;
|
||||
|
||||
public:
|
||||
Span<InputSocket *> targets();
|
||||
Span<const InputSocket *> targets() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A #Node has input and output sockets. Every node is either a #FunctionNode or a #DummyNode.
|
||||
*/
|
||||
class Node : NonCopyable, NonMovable {
|
||||
protected:
|
||||
/**
|
||||
* The function this node corresponds to. If this is null, the node is a #DummyNode.
|
||||
* The function is not owned by this #Node nor by the #Graph.
|
||||
*/
|
||||
const LazyFunction *fn_ = nullptr;
|
||||
/**
|
||||
* Input sockets of the node.
|
||||
*/
|
||||
Span<InputSocket *> inputs_;
|
||||
/**
|
||||
* Output sockets of the node.
|
||||
*/
|
||||
Span<OutputSocket *> outputs_;
|
||||
/**
|
||||
* An index that is set when calling #Graph::update_node_indices. This can be used to create
|
||||
* efficient mappings from nodes to other data using just an array instead of a hash map.
|
||||
*
|
||||
* This is technically not necessary but has better performance than always using hash maps.
|
||||
*/
|
||||
int index_in_graph_ = -1;
|
||||
|
||||
friend Graph;
|
||||
|
||||
public:
|
||||
bool is_dummy() const;
|
||||
bool is_function() const;
|
||||
int index_in_graph() const;
|
||||
|
||||
Span<const InputSocket *> inputs() const;
|
||||
Span<const OutputSocket *> outputs() const;
|
||||
Span<InputSocket *> inputs();
|
||||
Span<OutputSocket *> outputs();
|
||||
|
||||
const InputSocket &input(int index) const;
|
||||
const OutputSocket &output(int index) const;
|
||||
InputSocket &input(int index);
|
||||
OutputSocket &output(int index);
|
||||
|
||||
std::string name() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A #Node that corresponds to a specific #LazyFunction.
|
||||
*/
|
||||
class FunctionNode : public Node {
|
||||
public:
|
||||
const LazyFunction &function() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A #Node that does *not* correspond to a #LazyFunction. Instead it can be used to indicate inputs
|
||||
* and outputs of the entire graph. It can have an arbitrary number of inputs and outputs.
|
||||
*/
|
||||
class DummyNode : public Node {
|
||||
private:
|
||||
std::string name_;
|
||||
|
||||
friend Node;
|
||||
};
|
||||
|
||||
/**
|
||||
* A container for an arbitrary number of nodes and links between their sockets.
|
||||
*/
|
||||
class Graph : NonCopyable, NonMovable {
|
||||
private:
|
||||
/**
|
||||
* Used to allocate nodes and sockets in the graph.
|
||||
*/
|
||||
LinearAllocator<> allocator_;
|
||||
/**
|
||||
* Contains all nodes in the graph so that it is efficient to iterate over them.
|
||||
*/
|
||||
Vector<Node *> nodes_;
|
||||
|
||||
public:
|
||||
~Graph();
|
||||
|
||||
/**
|
||||
* Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph.
|
||||
*/
|
||||
Span<const Node *> nodes() const;
|
||||
|
||||
/**
|
||||
* Add a new function node with sockets that match the passed in #LazyFunction.
|
||||
*/
|
||||
FunctionNode &add_function(const LazyFunction &fn);
|
||||
|
||||
/**
|
||||
* Add a new dummy node with the given socket types.
|
||||
*/
|
||||
DummyNode &add_dummy(Span<const CPPType *> input_types, Span<const CPPType *> output_types);
|
||||
|
||||
/**
|
||||
* Add a link between the two given sockets.
|
||||
* This has undefined behavior when the input is linked to something else already.
|
||||
*/
|
||||
void add_link(OutputSocket &from, InputSocket &to);
|
||||
|
||||
/**
|
||||
* Make sure that #Node::index_in_graph is up to date.
|
||||
*/
|
||||
void update_node_indices();
|
||||
|
||||
/**
|
||||
* Can be used to assert that #update_node_indices has been called.
|
||||
*/
|
||||
bool node_indices_are_valid() const;
|
||||
|
||||
/**
|
||||
* Utility to generate a dot graph string for the graph. This can be used for debugging.
|
||||
*/
|
||||
std::string to_dot() const;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #Socket Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline bool Socket::is_input() const
|
||||
{
|
||||
return is_input_;
|
||||
}
|
||||
|
||||
inline bool Socket::is_output() const
|
||||
{
|
||||
return !is_input_;
|
||||
}
|
||||
|
||||
inline int Socket::index() const
|
||||
{
|
||||
return index_in_node_;
|
||||
}
|
||||
|
||||
inline InputSocket &Socket::as_input()
|
||||
{
|
||||
BLI_assert(this->is_input());
|
||||
return *static_cast<InputSocket *>(this);
|
||||
}
|
||||
|
||||
inline OutputSocket &Socket::as_output()
|
||||
{
|
||||
BLI_assert(this->is_output());
|
||||
return *static_cast<OutputSocket *>(this);
|
||||
}
|
||||
|
||||
inline const InputSocket &Socket::as_input() const
|
||||
{
|
||||
BLI_assert(this->is_input());
|
||||
return *static_cast<const InputSocket *>(this);
|
||||
}
|
||||
|
||||
inline const OutputSocket &Socket::as_output() const
|
||||
{
|
||||
BLI_assert(this->is_output());
|
||||
return *static_cast<const OutputSocket *>(this);
|
||||
}
|
||||
|
||||
inline const Node &Socket::node() const
|
||||
{
|
||||
return *node_;
|
||||
}
|
||||
|
||||
inline Node &Socket::node()
|
||||
{
|
||||
return *node_;
|
||||
}
|
||||
|
||||
inline const CPPType &Socket::type() const
|
||||
{
|
||||
return *type_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #InputSocket Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline const OutputSocket *InputSocket::origin() const
|
||||
{
|
||||
return origin_;
|
||||
}
|
||||
|
||||
inline OutputSocket *InputSocket::origin()
|
||||
{
|
||||
return origin_;
|
||||
}
|
||||
|
||||
inline const void *InputSocket::default_value() const
|
||||
{
|
||||
return default_value_;
|
||||
}
|
||||
|
||||
inline void InputSocket::set_default_value(const void *value)
|
||||
{
|
||||
default_value_ = value;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #OutputSocket Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline Span<const InputSocket *> OutputSocket::targets() const
|
||||
{
|
||||
return targets_;
|
||||
}
|
||||
|
||||
inline Span<InputSocket *> OutputSocket::targets()
|
||||
{
|
||||
return targets_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #Node Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline bool Node::is_dummy() const
|
||||
{
|
||||
return fn_ == nullptr;
|
||||
}
|
||||
|
||||
inline bool Node::is_function() const
|
||||
{
|
||||
return fn_ != nullptr;
|
||||
}
|
||||
|
||||
inline int Node::index_in_graph() const
|
||||
{
|
||||
return index_in_graph_;
|
||||
}
|
||||
|
||||
inline Span<const InputSocket *> Node::inputs() const
|
||||
{
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
inline Span<const OutputSocket *> Node::outputs() const
|
||||
{
|
||||
return outputs_;
|
||||
}
|
||||
|
||||
inline Span<InputSocket *> Node::inputs()
|
||||
{
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
inline Span<OutputSocket *> Node::outputs()
|
||||
{
|
||||
return outputs_;
|
||||
}
|
||||
|
||||
inline const InputSocket &Node::input(const int index) const
|
||||
{
|
||||
return *inputs_[index];
|
||||
}
|
||||
|
||||
inline const OutputSocket &Node::output(const int index) const
|
||||
{
|
||||
return *outputs_[index];
|
||||
}
|
||||
|
||||
inline InputSocket &Node::input(const int index)
|
||||
{
|
||||
return *inputs_[index];
|
||||
}
|
||||
|
||||
inline OutputSocket &Node::output(const int index)
|
||||
{
|
||||
return *outputs_[index];
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #FunctionNode Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline const LazyFunction &FunctionNode::function() const
|
||||
{
|
||||
BLI_assert(fn_ != nullptr);
|
||||
return *fn_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #Graph Inline Methods
|
||||
* \{ */
|
||||
|
||||
inline Span<const Node *> Graph::nodes() const
|
||||
{
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
83
source/blender/functions/FN_lazy_function_graph_executor.hh
Normal file
83
source/blender/functions/FN_lazy_function_graph_executor.hh
Normal file
@@ -0,0 +1,83 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*
|
||||
* This file provides means to create a #LazyFunction from #Graph (which could then e.g. be used in
|
||||
* another #Graph again).
|
||||
*/
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
/**
|
||||
* Can be implemented to log values produced during graph evaluation.
|
||||
*/
|
||||
class GraphExecutorLogger {
|
||||
public:
|
||||
virtual ~GraphExecutorLogger() = default;
|
||||
|
||||
virtual void log_socket_value(const Context &context,
|
||||
const Socket &socket,
|
||||
GPointer value) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Has to be implemented when some of the nodes in the graph may have side effects. The
|
||||
* #GraphExecutor has to know about that to make sure that these nodes will be executed even though
|
||||
* their outputs are not needed.
|
||||
*/
|
||||
class GraphExecutorSideEffectProvider {
|
||||
public:
|
||||
virtual ~GraphExecutorSideEffectProvider() = default;
|
||||
virtual Vector<const FunctionNode *> get_nodes_with_side_effects(const Context &context) const;
|
||||
};
|
||||
|
||||
class GraphExecutor : public LazyFunction {
|
||||
public:
|
||||
using Logger = GraphExecutorLogger;
|
||||
using SideEffectProvider = GraphExecutorSideEffectProvider;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The graph that is evaluated.
|
||||
*/
|
||||
const Graph &graph_;
|
||||
/**
|
||||
* Input and output sockets of the entire graph.
|
||||
*/
|
||||
VectorSet<const OutputSocket *> graph_inputs_;
|
||||
VectorSet<const InputSocket *> graph_outputs_;
|
||||
/**
|
||||
* Optional logger for events that happen during execution.
|
||||
*/
|
||||
const Logger *logger_;
|
||||
/**
|
||||
* Optional side effect provider. It knows which nodes have side effects based on the context
|
||||
* during evaluation.
|
||||
*/
|
||||
const SideEffectProvider *side_effect_provider_;
|
||||
|
||||
friend class Executor;
|
||||
|
||||
public:
|
||||
GraphExecutor(const Graph &graph,
|
||||
Span<const OutputSocket *> graph_inputs,
|
||||
Span<const InputSocket *> graph_outputs,
|
||||
const Logger *logger,
|
||||
const SideEffectProvider *side_effect_provider);
|
||||
|
||||
void *init_storage(LinearAllocator<> &allocator) const override;
|
||||
void destruct_storage(void *storage) const override;
|
||||
|
||||
private:
|
||||
void execute_impl(Params ¶ms, const Context &context) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
@@ -157,6 +157,7 @@ namespace multi_function_types {
|
||||
using fn::MFContext;
|
||||
using fn::MFContextBuilder;
|
||||
using fn::MFDataType;
|
||||
using fn::MFParamCategory;
|
||||
using fn::MFParams;
|
||||
using fn::MFParamsBuilder;
|
||||
using fn::MFParamType;
|
||||
|
@@ -16,3 +16,6 @@ MAKE_FIELD_CPP_TYPE(BoolField, bool);
|
||||
MAKE_FIELD_CPP_TYPE(Int8Field, int8_t);
|
||||
MAKE_FIELD_CPP_TYPE(Int32Field, int32_t);
|
||||
MAKE_FIELD_CPP_TYPE(StringField, std::string);
|
||||
BLI_CPP_TYPE_MAKE(StringValueOrFieldVector,
|
||||
blender::Vector<blender::fn::ValueOrField<std::string>>,
|
||||
CPPTypeFlags::None);
|
||||
|
66
source/blender/functions/intern/lazy_function.cc
Normal file
66
source/blender/functions/intern/lazy_function.cc
Normal file
@@ -0,0 +1,66 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
std::string LazyFunction::name() const
|
||||
{
|
||||
return debug_name_;
|
||||
}
|
||||
|
||||
std::string LazyFunction::input_name(int index) const
|
||||
{
|
||||
return inputs_[index].debug_name;
|
||||
}
|
||||
|
||||
std::string LazyFunction::output_name(int index) const
|
||||
{
|
||||
return outputs_[index].debug_name;
|
||||
}
|
||||
|
||||
void *LazyFunction::init_storage(LinearAllocator<> &UNUSED(allocator)) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LazyFunction::destruct_storage(void *storage) const
|
||||
{
|
||||
BLI_assert(storage == nullptr);
|
||||
UNUSED_VARS_NDEBUG(storage);
|
||||
}
|
||||
|
||||
bool LazyFunction::always_used_inputs_available(const Params ¶ms) const
|
||||
{
|
||||
for (const int i : inputs_.index_range()) {
|
||||
const Input &fn_input = inputs_[i];
|
||||
if (fn_input.usage == ValueUsage::Used) {
|
||||
if (params.try_get_input_data_ptr(i) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Params::set_default_remaining_outputs()
|
||||
{
|
||||
for (const int i : fn_.outputs().index_range()) {
|
||||
if (this->output_was_set(i)) {
|
||||
continue;
|
||||
}
|
||||
const Output &fn_output = fn_.outputs()[i];
|
||||
const CPPType &type = *fn_output.type;
|
||||
void *data_ptr = this->get_output_data_ptr(i);
|
||||
type.value_initialize(data_ptr);
|
||||
this->output_set(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
65
source/blender/functions/intern/lazy_function_execute.cc
Normal file
65
source/blender/functions/intern/lazy_function_execute.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_lazy_function_execute.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
BasicParams::BasicParams(const LazyFunction &fn,
|
||||
const Span<GMutablePointer> inputs,
|
||||
const Span<GMutablePointer> outputs,
|
||||
MutableSpan<std::optional<ValueUsage>> input_usages,
|
||||
Span<ValueUsage> output_usages,
|
||||
MutableSpan<bool> set_outputs)
|
||||
: Params(fn),
|
||||
inputs_(inputs),
|
||||
outputs_(outputs),
|
||||
input_usages_(input_usages),
|
||||
output_usages_(output_usages),
|
||||
set_outputs_(set_outputs)
|
||||
{
|
||||
}
|
||||
|
||||
void *BasicParams::try_get_input_data_ptr_impl(const int index) const
|
||||
{
|
||||
return inputs_[index].get();
|
||||
}
|
||||
|
||||
void *BasicParams::try_get_input_data_ptr_or_request_impl(const int index)
|
||||
{
|
||||
void *value = inputs_[index].get();
|
||||
if (value == nullptr) {
|
||||
input_usages_[index] = ValueUsage::Used;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void *BasicParams::get_output_data_ptr_impl(const int index)
|
||||
{
|
||||
return outputs_[index].get();
|
||||
}
|
||||
|
||||
void BasicParams::output_set_impl(const int index)
|
||||
{
|
||||
set_outputs_[index] = true;
|
||||
}
|
||||
|
||||
bool BasicParams::output_was_set_impl(const int index) const
|
||||
{
|
||||
return set_outputs_[index];
|
||||
}
|
||||
|
||||
ValueUsage BasicParams::get_output_usage_impl(const int index) const
|
||||
{
|
||||
return output_usages_[index];
|
||||
}
|
||||
|
||||
void BasicParams::set_input_unused_impl(const int index)
|
||||
{
|
||||
input_usages_[index] = ValueUsage::Unused;
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
181
source/blender/functions/intern/lazy_function_graph.cc
Normal file
181
source/blender/functions/intern/lazy_function_graph.cc
Normal file
@@ -0,0 +1,181 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_dot_export.hh"
|
||||
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
Graph::~Graph()
|
||||
{
|
||||
for (Node *node : nodes_) {
|
||||
for (InputSocket *socket : node->inputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
for (OutputSocket *socket : node->outputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
std::destroy_at(node);
|
||||
}
|
||||
}
|
||||
|
||||
FunctionNode &Graph::add_function(const LazyFunction &fn)
|
||||
{
|
||||
const Span<Input> inputs = fn.inputs();
|
||||
const Span<Output> outputs = fn.outputs();
|
||||
|
||||
FunctionNode &node = *allocator_.construct<FunctionNode>().release();
|
||||
node.fn_ = &fn;
|
||||
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(inputs.size());
|
||||
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(outputs.size());
|
||||
|
||||
for (const int i : inputs.index_range()) {
|
||||
InputSocket &socket = *node.inputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = true;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = inputs[i].type;
|
||||
}
|
||||
for (const int i : outputs.index_range()) {
|
||||
OutputSocket &socket = *node.outputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = false;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = outputs[i].type;
|
||||
}
|
||||
|
||||
nodes_.append(&node);
|
||||
return node;
|
||||
}
|
||||
|
||||
DummyNode &Graph::add_dummy(Span<const CPPType *> input_types, Span<const CPPType *> output_types)
|
||||
{
|
||||
DummyNode &node = *allocator_.construct<DummyNode>().release();
|
||||
node.fn_ = nullptr;
|
||||
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(input_types.size());
|
||||
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(
|
||||
output_types.size());
|
||||
|
||||
for (const int i : input_types.index_range()) {
|
||||
InputSocket &socket = *node.inputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = true;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = input_types[i];
|
||||
}
|
||||
for (const int i : output_types.index_range()) {
|
||||
OutputSocket &socket = *node.outputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = false;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = output_types[i];
|
||||
}
|
||||
|
||||
nodes_.append(&node);
|
||||
return node;
|
||||
}
|
||||
|
||||
void Graph::add_link(OutputSocket &from, InputSocket &to)
|
||||
{
|
||||
BLI_assert(to.origin_ == nullptr);
|
||||
BLI_assert(from.type_ == to.type_);
|
||||
to.origin_ = &from;
|
||||
from.targets_.append(&to);
|
||||
}
|
||||
|
||||
void Graph::update_node_indices()
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
nodes_[i]->index_in_graph_ = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool Graph::node_indices_are_valid() const
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
if (nodes_[i]->index_in_graph_ != i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Socket::name() const
|
||||
{
|
||||
if (node_->is_function()) {
|
||||
const FunctionNode &fn_node = static_cast<const FunctionNode &>(*node_);
|
||||
const LazyFunction &fn = fn_node.function();
|
||||
if (is_input_) {
|
||||
return fn.input_name(index_in_node_);
|
||||
}
|
||||
return fn.output_name(index_in_node_);
|
||||
}
|
||||
return "Unnamed";
|
||||
}
|
||||
|
||||
std::string Node::name() const
|
||||
{
|
||||
if (fn_ == nullptr) {
|
||||
return static_cast<const DummyNode *>(this)->name_;
|
||||
}
|
||||
return fn_->name();
|
||||
}
|
||||
|
||||
std::string Graph::to_dot() const
|
||||
{
|
||||
dot::DirectedGraph digraph;
|
||||
digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
|
||||
|
||||
Map<const Node *, dot::NodeWithSocketsRef> dot_nodes;
|
||||
|
||||
for (const Node *node : nodes_) {
|
||||
dot::Node &dot_node = digraph.new_node("");
|
||||
if (node->is_dummy()) {
|
||||
dot_node.set_background_color("lightblue");
|
||||
}
|
||||
else {
|
||||
dot_node.set_background_color("white");
|
||||
}
|
||||
|
||||
Vector<std::string> input_names;
|
||||
Vector<std::string> output_names;
|
||||
for (const InputSocket *socket : node->inputs()) {
|
||||
input_names.append(socket->name());
|
||||
}
|
||||
for (const OutputSocket *socket : node->outputs()) {
|
||||
output_names.append(socket->name());
|
||||
}
|
||||
|
||||
dot_nodes.add_new(node,
|
||||
dot::NodeWithSocketsRef(dot_node, node->name(), input_names, output_names));
|
||||
}
|
||||
|
||||
for (const Node *node : nodes_) {
|
||||
for (const InputSocket *socket : node->inputs()) {
|
||||
const dot::NodeWithSocketsRef &to_dot_node = dot_nodes.lookup(&socket->node());
|
||||
const dot::NodePort to_dot_port = to_dot_node.input(socket->index());
|
||||
|
||||
if (const OutputSocket *origin = socket->origin()) {
|
||||
dot::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&origin->node());
|
||||
digraph.new_edge(from_dot_node.output(origin->index()), to_dot_port);
|
||||
}
|
||||
else if (const void *default_value = socket->default_value()) {
|
||||
const CPPType &type = socket->type();
|
||||
std::string value_string;
|
||||
if (type.is_printable()) {
|
||||
value_string = type.to_string(default_value);
|
||||
}
|
||||
else {
|
||||
value_string = "<" + type.name() + ">";
|
||||
}
|
||||
dot::Node &default_value_dot_node = digraph.new_node(value_string);
|
||||
default_value_dot_node.set_shape(dot::Attr_shape::Ellipse);
|
||||
digraph.new_edge(default_value_dot_node, to_dot_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return digraph.to_dot_string();
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
1101
source/blender/functions/intern/lazy_function_graph_executor.cc
Normal file
1101
source/blender/functions/intern/lazy_function_graph_executor.cc
Normal file
File diff suppressed because it is too large
Load Diff
238
source/blender/functions/tests/FN_lazy_function_test.cc
Normal file
238
source/blender/functions/tests/FN_lazy_function_test.cc
Normal file
@@ -0,0 +1,238 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_lazy_function_execute.hh"
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
#include "FN_lazy_function_graph_executor.hh"
|
||||
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
namespace blender::fn::lazy_function::tests {
|
||||
|
||||
class AddLazyFunction : public LazyFunction {
|
||||
public:
|
||||
AddLazyFunction()
|
||||
{
|
||||
debug_name_ = "Add";
|
||||
inputs_.append({"A", CPPType::get<int>()});
|
||||
inputs_.append({"B", CPPType::get<int>()});
|
||||
outputs_.append({"Result", CPPType::get<int>()});
|
||||
}
|
||||
|
||||
void execute_impl(Params ¶ms, const Context &UNUSED(context)) const override
|
||||
{
|
||||
const int a = params.get_input<int>(0);
|
||||
const int b = params.get_input<int>(1);
|
||||
params.set_output(0, a + b);
|
||||
}
|
||||
};
|
||||
|
||||
class StoreValueFunction : public LazyFunction {
|
||||
private:
|
||||
int *dst1_;
|
||||
int *dst2_;
|
||||
|
||||
public:
|
||||
StoreValueFunction(int *dst1, int *dst2) : dst1_(dst1), dst2_(dst2)
|
||||
{
|
||||
debug_name_ = "Store Value";
|
||||
inputs_.append({"A", CPPType::get<int>()});
|
||||
inputs_.append({"B", CPPType::get<int>(), ValueUsage::Maybe});
|
||||
}
|
||||
|
||||
void execute_impl(Params ¶ms, const Context &UNUSED(context)) const override
|
||||
{
|
||||
*dst1_ = params.get_input<int>(0);
|
||||
if (int *value = params.try_get_input_data_ptr_or_request<int>(1)) {
|
||||
*dst2_ = *value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleSideEffectProvider : public GraphExecutor::SideEffectProvider {
|
||||
private:
|
||||
Vector<const FunctionNode *> side_effect_nodes_;
|
||||
|
||||
public:
|
||||
SimpleSideEffectProvider(Span<const FunctionNode *> side_effect_nodes)
|
||||
: side_effect_nodes_(side_effect_nodes)
|
||||
{
|
||||
}
|
||||
|
||||
Vector<const FunctionNode *> get_nodes_with_side_effects(
|
||||
const Context &UNUSED(context)) const override
|
||||
{
|
||||
return side_effect_nodes_;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(lazy_function, SideEffects)
|
||||
{
|
||||
BLI_task_scheduler_init();
|
||||
int dst1 = 0;
|
||||
int dst2 = 0;
|
||||
|
||||
const AddLazyFunction add_fn;
|
||||
const StoreValueFunction store_fn{&dst1, &dst2};
|
||||
|
||||
Graph graph;
|
||||
FunctionNode &add_node_1 = graph.add_function(add_fn);
|
||||
FunctionNode &add_node_2 = graph.add_function(add_fn);
|
||||
FunctionNode &store_node = graph.add_function(store_fn);
|
||||
DummyNode &input_node = graph.add_dummy({}, {&CPPType::get<int>()});
|
||||
|
||||
graph.add_link(input_node.output(0), add_node_1.input(0));
|
||||
graph.add_link(input_node.output(0), add_node_2.input(0));
|
||||
graph.add_link(add_node_1.output(0), store_node.input(0));
|
||||
graph.add_link(add_node_2.output(0), store_node.input(1));
|
||||
|
||||
const int value_10 = 10;
|
||||
const int value_100 = 100;
|
||||
add_node_1.input(1).set_default_value(&value_10);
|
||||
add_node_2.input(1).set_default_value(&value_100);
|
||||
|
||||
graph.update_node_indices();
|
||||
|
||||
SimpleSideEffectProvider side_effect_provider{{&store_node}};
|
||||
|
||||
GraphExecutor executor_fn{graph, {&input_node.output(0)}, {}, nullptr, &side_effect_provider};
|
||||
execute_lazy_function_eagerly(executor_fn, nullptr, std::make_tuple(5), std::make_tuple());
|
||||
|
||||
EXPECT_EQ(dst1, 15);
|
||||
EXPECT_EQ(dst2, 105);
|
||||
}
|
||||
|
||||
enum class LazyFunctionEventType {
|
||||
SetInput,
|
||||
RequestOutput,
|
||||
SetOutputUnused,
|
||||
};
|
||||
|
||||
struct LazyFunctionEvent {
|
||||
LazyFunctionEventType type;
|
||||
int index;
|
||||
void *value;
|
||||
};
|
||||
|
||||
static void execute_lazy_function_test(const LazyFunction &fn,
|
||||
const Span<LazyFunctionEvent> events,
|
||||
const Span<GMutablePointer> outputs)
|
||||
{
|
||||
const Span<Input> fn_inputs = fn.inputs();
|
||||
const Span<Output> fn_outputs = fn.outputs();
|
||||
BLI_assert(outputs.size() == fn_outputs.size());
|
||||
|
||||
LinearAllocator<> allocator;
|
||||
Vector<GMutablePointer> inputs(fn_inputs.size());
|
||||
Array<std::optional<ValueUsage>> input_usages(fn_inputs.size());
|
||||
Array<ValueUsage> output_usages(fn_outputs.size(), ValueUsage::Unused);
|
||||
Array<bool> set_outputs(fn_outputs.size(), false);
|
||||
|
||||
void *storage = fn.init_storage(allocator);
|
||||
Context context;
|
||||
context.storage = storage;
|
||||
|
||||
BasicParams params(fn, inputs, outputs, input_usages, output_usages, set_outputs);
|
||||
if (fn.always_used_inputs_available(params)) {
|
||||
fn.execute(params, context);
|
||||
}
|
||||
for (const LazyFunctionEvent &event : events) {
|
||||
switch (event.type) {
|
||||
case LazyFunctionEventType::SetInput: {
|
||||
inputs[event.index] = GMutablePointer{fn_inputs[event.index].type, event.value};
|
||||
break;
|
||||
}
|
||||
case LazyFunctionEventType::RequestOutput: {
|
||||
output_usages[event.index] = ValueUsage::Used;
|
||||
break;
|
||||
}
|
||||
case LazyFunctionEventType::SetOutputUnused: {
|
||||
output_usages[event.index] = ValueUsage::Unused;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fn.always_used_inputs_available(params)) {
|
||||
fn.execute(params, context);
|
||||
}
|
||||
}
|
||||
|
||||
fn.destruct_storage(storage);
|
||||
}
|
||||
|
||||
static Vector<Node *> build_add_node_chain(Graph &graph,
|
||||
const int chain_length,
|
||||
const int *default_value)
|
||||
{
|
||||
static AddLazyFunction fn;
|
||||
Vector<Node *> nodes;
|
||||
for ([[maybe_unused]] const int i : IndexRange(chain_length)) {
|
||||
Node &node = graph.add_function(fn);
|
||||
node.input(0).set_default_value(default_value);
|
||||
node.input(1).set_default_value(default_value);
|
||||
nodes.append(&node);
|
||||
}
|
||||
for (const int i : IndexRange(chain_length - 1)) {
|
||||
Node &n1 = *nodes[i];
|
||||
Node &n2 = *nodes[i + 1];
|
||||
graph.add_link(n1.output(0), n2.input(0));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
struct MultiChainResult {
|
||||
Vector<Node *> first_nodes;
|
||||
Node *last_node = nullptr;
|
||||
};
|
||||
|
||||
static MultiChainResult build_multiple_chains(Graph &graph,
|
||||
const int chain_length,
|
||||
const int chain_num,
|
||||
const int *default_value)
|
||||
{
|
||||
static AddLazyFunction fn;
|
||||
MultiChainResult result;
|
||||
for ([[maybe_unused]] const int i : IndexRange(chain_num)) {
|
||||
Vector<Node *> chain = build_add_node_chain(graph, chain_length, default_value);
|
||||
result.first_nodes.append(chain[0]);
|
||||
if (result.last_node == nullptr) {
|
||||
result.last_node = chain.last();
|
||||
}
|
||||
else {
|
||||
Node &node = graph.add_function(fn);
|
||||
node.input(0).set_default_value(default_value);
|
||||
node.input(1).set_default_value(default_value);
|
||||
graph.add_link(result.last_node->output(0), node.input(0));
|
||||
graph.add_link(chain.last()->output(0), node.input(1));
|
||||
result.last_node = &node;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(lazy_function, Simple)
|
||||
{
|
||||
BLI_task_scheduler_init(); /* Without this, no parallelism. */
|
||||
const int value_1 = 1;
|
||||
Graph graph;
|
||||
MultiChainResult node_chain = build_multiple_chains(graph, 1e4, 24, &value_1);
|
||||
DummyNode &output_node = graph.add_dummy({&CPPType::get<int>()}, {});
|
||||
graph.add_link(node_chain.last_node->output(0), output_node.input(0));
|
||||
graph.update_node_indices();
|
||||
// std::cout << graph.to_dot() << "\n";
|
||||
|
||||
GraphExecutor executor_fn{graph, {}, {&output_node.input(0)}, nullptr, nullptr};
|
||||
|
||||
// SCOPED_TIMER("run");
|
||||
int result;
|
||||
|
||||
for ([[maybe_unused]] const int i : IndexRange(1e2)) {
|
||||
execute_lazy_function_test(executor_fn,
|
||||
{LazyFunctionEvent{LazyFunctionEventType::RequestOutput, 0}},
|
||||
Span<GMutablePointer>{{&result}});
|
||||
}
|
||||
std::cout << "Result: " << result << "\n";
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function::tests
|
@@ -65,7 +65,6 @@ set(SRC
|
||||
intern/MOD_mirror.c
|
||||
intern/MOD_multires.c
|
||||
intern/MOD_nodes.cc
|
||||
intern/MOD_nodes_evaluator.cc
|
||||
intern/MOD_none.c
|
||||
intern/MOD_normal_edit.c
|
||||
intern/MOD_ocean.c
|
||||
@@ -105,7 +104,6 @@ set(SRC
|
||||
MOD_modifiertypes.h
|
||||
MOD_nodes.h
|
||||
intern/MOD_meshcache_util.h
|
||||
intern/MOD_nodes_evaluator.hh
|
||||
intern/MOD_solidify_util.h
|
||||
intern/MOD_ui_common.h
|
||||
intern/MOD_util.h
|
||||
|
@@ -36,6 +36,7 @@
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_compute_contexts.hh"
|
||||
#include "BKE_customdata.h"
|
||||
#include "BKE_geometry_fields.hh"
|
||||
#include "BKE_geometry_set_instances.hh"
|
||||
@@ -73,7 +74,6 @@
|
||||
|
||||
#include "MOD_modifiertypes.h"
|
||||
#include "MOD_nodes.h"
|
||||
#include "MOD_nodes_evaluator.hh"
|
||||
#include "MOD_ui_common.h"
|
||||
|
||||
#include "ED_object.h"
|
||||
@@ -81,15 +81,18 @@
|
||||
#include "ED_spreadsheet.h"
|
||||
#include "ED_undo.h"
|
||||
|
||||
#include "NOD_derived_node_tree.hh"
|
||||
#include "NOD_geometry.h"
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "NOD_geometry_nodes_to_lazy_function_graph.hh"
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_field_cpp_type.hh"
|
||||
#include "FN_lazy_function_execute.hh"
|
||||
#include "FN_lazy_function_graph_executor.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
namespace lf = blender::fn::lazy_function;
|
||||
|
||||
using blender::Array;
|
||||
using blender::ColorGeometry4f;
|
||||
using blender::CPPType;
|
||||
@@ -106,6 +109,7 @@ using blender::MultiValueMap;
|
||||
using blender::MutableSpan;
|
||||
using blender::Set;
|
||||
using blender::Span;
|
||||
using blender::Stack;
|
||||
using blender::StringRef;
|
||||
using blender::StringRefNull;
|
||||
using blender::Vector;
|
||||
@@ -117,11 +121,17 @@ using blender::fn::ValueOrFieldCPPType;
|
||||
using blender::nodes::FieldInferencingInterface;
|
||||
using blender::nodes::GeoNodeExecParams;
|
||||
using blender::nodes::InputSocketFieldType;
|
||||
using blender::nodes::geo_eval_log::GeoModifierLog;
|
||||
using blender::threading::EnumerableThreadSpecific;
|
||||
using namespace blender::fn::multi_function_types;
|
||||
using namespace blender::nodes::derived_node_tree_types;
|
||||
using geo_log::eNamedAttrUsage;
|
||||
using geo_log::GeometryAttributeInfo;
|
||||
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
|
||||
using blender::nodes::geo_eval_log::GeometryInfoLog;
|
||||
using blender::nodes::geo_eval_log::GeoNodeLog;
|
||||
using blender::nodes::geo_eval_log::GeoTreeLog;
|
||||
using blender::nodes::geo_eval_log::NamedAttributeUsage;
|
||||
using blender::nodes::geo_eval_log::NodeWarning;
|
||||
using blender::nodes::geo_eval_log::NodeWarningType;
|
||||
using blender::nodes::geo_eval_log::ValueLog;
|
||||
|
||||
static void initData(ModifierData *md)
|
||||
{
|
||||
@@ -756,36 +766,37 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
|
||||
}
|
||||
|
||||
static void initialize_group_input(NodesModifierData &nmd,
|
||||
const bNodeSocket &socket,
|
||||
const bNodeSocket &interface_socket,
|
||||
const int input_index,
|
||||
void *r_value)
|
||||
{
|
||||
const bNodeSocketType &socket_type = *socket.typeinfo;
|
||||
const bNodeSocket &bsocket = socket;
|
||||
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(bsocket.type);
|
||||
const bNodeSocketType &socket_type = *interface_socket.typeinfo;
|
||||
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(
|
||||
interface_socket.type);
|
||||
if (nmd.settings.properties == nullptr) {
|
||||
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
|
||||
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
|
||||
return;
|
||||
}
|
||||
const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties,
|
||||
socket.identifier);
|
||||
interface_socket.identifier);
|
||||
if (property == nullptr) {
|
||||
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
|
||||
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
|
||||
return;
|
||||
}
|
||||
if (!id_property_type_matches_socket(bsocket, *property)) {
|
||||
socket_type.get_geometry_nodes_cpp_value(bsocket, r_value);
|
||||
if (!id_property_type_matches_socket(interface_socket, *property)) {
|
||||
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input_has_attribute_toggle(*nmd.node_group, socket.runtime->index_in_node)) {
|
||||
if (!input_has_attribute_toggle(*nmd.node_group, input_index)) {
|
||||
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
|
||||
return;
|
||||
}
|
||||
|
||||
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
|
||||
nmd.settings.properties, (socket.identifier + use_attribute_suffix).c_str());
|
||||
nmd.settings.properties, (interface_socket.identifier + use_attribute_suffix).c_str());
|
||||
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
|
||||
nmd.settings.properties, (socket.identifier + attribute_name_suffix).c_str());
|
||||
nmd.settings.properties, (interface_socket.identifier + attribute_name_suffix).c_str());
|
||||
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
|
||||
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
|
||||
return;
|
||||
@@ -831,13 +842,25 @@ static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
|
||||
return spreadsheets;
|
||||
}
|
||||
|
||||
static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet,
|
||||
NodesModifierData *nmd,
|
||||
const ModifierEvalContext *ctx,
|
||||
const DerivedNodeTree &tree,
|
||||
Set<DSocket> &r_sockets_to_preview)
|
||||
static const lf::FunctionNode &find_viewer_lf_node(const bNode &viewer_bnode)
|
||||
{
|
||||
Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path;
|
||||
return *blender::nodes::ensure_geometry_nodes_lazy_function_graph(viewer_bnode.owner_tree())
|
||||
->mapping.viewer_node_map.lookup(&viewer_bnode);
|
||||
}
|
||||
static const lf::FunctionNode &find_group_lf_node(const bNode &group_bnode)
|
||||
{
|
||||
return *blender::nodes::ensure_geometry_nodes_lazy_function_graph(group_bnode.owner_tree())
|
||||
->mapping.group_node_map.lookup(&group_bnode);
|
||||
}
|
||||
|
||||
static void find_side_effect_nodes_for_spreadsheet(
|
||||
const SpaceSpreadsheet &sspreadsheet,
|
||||
const NodesModifierData &nmd,
|
||||
const ModifierEvalContext &ctx,
|
||||
const bNodeTree &root_tree,
|
||||
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
|
||||
{
|
||||
Vector<SpreadsheetContext *> context_path = sspreadsheet.context_path;
|
||||
if (context_path.size() < 3) {
|
||||
return;
|
||||
}
|
||||
@@ -848,11 +871,11 @@ static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadshe
|
||||
return;
|
||||
}
|
||||
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0];
|
||||
if (object_context->object != DEG_get_original_object(ctx->object)) {
|
||||
if (object_context->object != DEG_get_original_object(ctx.object)) {
|
||||
return;
|
||||
}
|
||||
SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context_path[1];
|
||||
if (StringRef(modifier_context->modifier_name) != nmd->modifier.name) {
|
||||
if (StringRef(modifier_context->modifier_name) != nmd.modifier.name) {
|
||||
return;
|
||||
}
|
||||
for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) {
|
||||
@@ -861,61 +884,77 @@ static void find_sockets_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadshe
|
||||
}
|
||||
}
|
||||
|
||||
Span<SpreadsheetContextNode *> nested_group_contexts =
|
||||
context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>();
|
||||
SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last();
|
||||
blender::ComputeContextBuilder compute_context_builder;
|
||||
compute_context_builder.push<blender::bke::ModifierComputeContext>(nmd.modifier.name);
|
||||
|
||||
const DTreeContext *context = &tree.root_context();
|
||||
const Span<SpreadsheetContextNode *> nested_group_contexts =
|
||||
context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>();
|
||||
const SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last();
|
||||
|
||||
Stack<const bNode *> group_node_stack;
|
||||
const bNodeTree *group = &root_tree;
|
||||
for (SpreadsheetContextNode *node_context : nested_group_contexts) {
|
||||
const bNodeTree &btree = context->btree();
|
||||
const bNode *found_node = nullptr;
|
||||
for (const bNode *bnode : btree.all_nodes()) {
|
||||
if (STREQ(bnode->name, node_context->node_name)) {
|
||||
found_node = bnode;
|
||||
for (const bNode *node : group->nodes_by_type("GeometryNodeGroup")) {
|
||||
if (STREQ(node->name, node_context->node_name)) {
|
||||
found_node = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found_node == nullptr) {
|
||||
return;
|
||||
}
|
||||
context = context->child_context(*found_node);
|
||||
if (context == nullptr) {
|
||||
if (found_node->id == nullptr) {
|
||||
return;
|
||||
}
|
||||
group_node_stack.push(found_node);
|
||||
group = reinterpret_cast<const bNodeTree *>(found_node->id);
|
||||
compute_context_builder.push<blender::bke::NodeGroupComputeContext>(node_context->node_name);
|
||||
}
|
||||
|
||||
const bNodeTree &btree = context->btree();
|
||||
for (const bNode *bnode : btree.nodes_by_type("GeometryNodeViewer")) {
|
||||
if (STREQ(bnode->name, last_context->node_name)) {
|
||||
const DNode viewer_node{context, bnode};
|
||||
for (const bNodeSocket *input_socket : bnode->input_sockets()) {
|
||||
if (input_socket->is_available() && input_socket->is_logically_linked()) {
|
||||
r_sockets_to_preview.add(DSocket{context, input_socket});
|
||||
}
|
||||
}
|
||||
const bNode *found_viewer_node = nullptr;
|
||||
for (const bNode *viewer_node : group->nodes_by_type("GeometryNodeViewer")) {
|
||||
if (STREQ(viewer_node->name, last_context->node_name)) {
|
||||
found_viewer_node = viewer_node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found_viewer_node == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Not only mark the viewer node as having side effects, but also all group nodes it is contained
|
||||
* in. */
|
||||
r_side_effect_nodes.add(compute_context_builder.hash(),
|
||||
&find_viewer_lf_node(*found_viewer_node));
|
||||
compute_context_builder.pop();
|
||||
while (!compute_context_builder.is_empty()) {
|
||||
r_side_effect_nodes.add(compute_context_builder.hash(),
|
||||
&find_group_lf_node(*group_node_stack.pop()));
|
||||
compute_context_builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
static void find_sockets_to_preview(NodesModifierData *nmd,
|
||||
const ModifierEvalContext *ctx,
|
||||
const DerivedNodeTree &tree,
|
||||
Set<DSocket> &r_sockets_to_preview)
|
||||
static void find_side_effect_nodes(
|
||||
const NodesModifierData &nmd,
|
||||
const ModifierEvalContext &ctx,
|
||||
const bNodeTree &tree,
|
||||
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
|
||||
{
|
||||
Main *bmain = DEG_get_bmain(ctx->depsgraph);
|
||||
Main *bmain = DEG_get_bmain(ctx.depsgraph);
|
||||
|
||||
/* Based on every visible spreadsheet context path, get a list of sockets that need to have their
|
||||
* intermediate geometries cached for display. */
|
||||
Vector<SpaceSpreadsheet *> spreadsheets = find_spreadsheet_editors(bmain);
|
||||
for (SpaceSpreadsheet *sspreadsheet : spreadsheets) {
|
||||
find_sockets_to_preview_for_spreadsheet(sspreadsheet, nmd, ctx, tree, r_sockets_to_preview);
|
||||
find_side_effect_nodes_for_spreadsheet(*sspreadsheet, nmd, ctx, tree, r_side_effect_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_runtime_data(NodesModifierData *nmd)
|
||||
{
|
||||
if (nmd->runtime_eval_log != nullptr) {
|
||||
delete (geo_log::ModifierLog *)nmd->runtime_eval_log;
|
||||
delete static_cast<GeoModifierLog *>(nmd->runtime_eval_log);
|
||||
nmd->runtime_eval_log = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1079,92 +1118,107 @@ static void store_output_attributes(GeometrySet &geometry,
|
||||
/**
|
||||
* Evaluate a node group to compute the output geometry.
|
||||
*/
|
||||
static GeometrySet compute_geometry(const DerivedNodeTree &tree,
|
||||
Span<const bNode *> group_input_nodes,
|
||||
const bNode &output_node,
|
||||
GeometrySet input_geometry_set,
|
||||
NodesModifierData *nmd,
|
||||
const ModifierEvalContext *ctx)
|
||||
static GeometrySet compute_geometry(
|
||||
const bNodeTree &btree,
|
||||
const blender::nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
|
||||
const bNode &output_node,
|
||||
GeometrySet input_geometry_set,
|
||||
NodesModifierData *nmd,
|
||||
const ModifierEvalContext *ctx)
|
||||
{
|
||||
blender::ResourceScope scope;
|
||||
blender::LinearAllocator<> &allocator = scope.linear_allocator();
|
||||
blender::nodes::NodeMultiFunctions mf_by_node{tree};
|
||||
const blender::nodes::GeometryNodeLazyFunctionMapping &mapping = lf_graph_info.mapping;
|
||||
|
||||
Map<DOutputSocket, GMutablePointer> group_inputs;
|
||||
Vector<const lf::OutputSocket *> graph_inputs;
|
||||
Vector<const lf::InputSocket *> graph_outputs;
|
||||
for (const lf::OutputSocket *socket : mapping.group_input_sockets) {
|
||||
graph_inputs.append(socket);
|
||||
}
|
||||
for (const bNodeSocket *bsocket : output_node.input_sockets().drop_back(1)) {
|
||||
const lf::InputSocket &socket = mapping.dummy_socket_map.lookup(bsocket)->as_input();
|
||||
graph_outputs.append(&socket);
|
||||
}
|
||||
|
||||
const DTreeContext *root_context = &tree.root_context();
|
||||
for (const bNode *group_input_node : group_input_nodes) {
|
||||
Span<const bNodeSocket *> group_input_sockets = group_input_node->output_sockets().drop_back(
|
||||
1);
|
||||
if (group_input_sockets.is_empty()) {
|
||||
Array<GMutablePointer> param_inputs(graph_inputs.size());
|
||||
Array<GMutablePointer> param_outputs(graph_outputs.size());
|
||||
Array<std::optional<lf::ValueUsage>> param_input_usages(graph_inputs.size());
|
||||
Array<lf::ValueUsage> param_output_usages(graph_outputs.size(), lf::ValueUsage::Used);
|
||||
Array<bool> param_set_outputs(graph_outputs.size(), false);
|
||||
|
||||
blender::nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
|
||||
blender::nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider(
|
||||
lf_graph_info);
|
||||
|
||||
lf::GraphExecutor graph_executor{
|
||||
lf_graph_info.graph, graph_inputs, graph_outputs, &lf_logger, &lf_side_effect_provider};
|
||||
|
||||
blender::nodes::GeoNodesModifierData geo_nodes_modifier_data;
|
||||
geo_nodes_modifier_data.depsgraph = ctx->depsgraph;
|
||||
geo_nodes_modifier_data.self_object = ctx->object;
|
||||
auto eval_log = std::make_unique<GeoModifierLog>();
|
||||
if (logging_enabled(ctx)) {
|
||||
geo_nodes_modifier_data.eval_log = eval_log.get();
|
||||
}
|
||||
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> r_side_effect_nodes;
|
||||
find_side_effect_nodes(*nmd, *ctx, btree, r_side_effect_nodes);
|
||||
geo_nodes_modifier_data.side_effect_nodes = &r_side_effect_nodes;
|
||||
blender::nodes::GeoNodesLFUserData user_data;
|
||||
user_data.modifier_data = &geo_nodes_modifier_data;
|
||||
blender::bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
|
||||
user_data.compute_context = &modifier_compute_context;
|
||||
|
||||
blender::LinearAllocator<> allocator;
|
||||
Vector<GMutablePointer> inputs_to_destruct;
|
||||
|
||||
int input_index;
|
||||
LISTBASE_FOREACH_INDEX (bNodeSocket *, interface_socket, &btree.inputs, input_index) {
|
||||
if (interface_socket->type == SOCK_GEOMETRY && input_index == 0) {
|
||||
param_inputs[input_index] = &input_geometry_set;
|
||||
continue;
|
||||
}
|
||||
|
||||
Span<const bNodeSocket *> remaining_input_sockets = group_input_sockets;
|
||||
|
||||
/* If the group expects a geometry as first input, use the geometry that has been passed to
|
||||
* modifier. */
|
||||
const bNodeSocket *first_input_socket = group_input_sockets[0];
|
||||
if (first_input_socket->type == SOCK_GEOMETRY) {
|
||||
GeometrySet *geometry_set_in =
|
||||
allocator.construct<GeometrySet>(input_geometry_set).release();
|
||||
group_inputs.add_new({root_context, first_input_socket}, geometry_set_in);
|
||||
remaining_input_sockets = remaining_input_sockets.drop_front(1);
|
||||
}
|
||||
|
||||
/* Initialize remaining group inputs. */
|
||||
for (const bNodeSocket *socket : remaining_input_sockets) {
|
||||
const CPPType &cpp_type = *socket->typeinfo->geometry_nodes_cpp_type;
|
||||
void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment());
|
||||
initialize_group_input(*nmd, *socket, value_in);
|
||||
group_inputs.add_new({root_context, socket}, {cpp_type, value_in});
|
||||
}
|
||||
const CPPType *type = interface_socket->typeinfo->geometry_nodes_cpp_type;
|
||||
BLI_assert(type != nullptr);
|
||||
void *value = allocator.allocate(type->size(), type->alignment());
|
||||
initialize_group_input(*nmd, *interface_socket, input_index, value);
|
||||
param_inputs[input_index] = {type, value};
|
||||
inputs_to_destruct.append({type, value});
|
||||
}
|
||||
|
||||
Vector<DInputSocket> group_outputs;
|
||||
for (const bNodeSocket *socket_ref : output_node.input_sockets().drop_back(1)) {
|
||||
group_outputs.append({root_context, socket_ref});
|
||||
for (const int i : graph_outputs.index_range()) {
|
||||
const lf::InputSocket &socket = *graph_outputs[i];
|
||||
const CPPType &type = socket.type();
|
||||
void *buffer = allocator.allocate(type.size(), type.alignment());
|
||||
param_outputs[i] = {type, buffer};
|
||||
}
|
||||
|
||||
std::optional<geo_log::GeoLogger> geo_logger;
|
||||
lf::Context lf_context;
|
||||
lf_context.storage = graph_executor.init_storage(allocator);
|
||||
lf_context.user_data = &user_data;
|
||||
lf::BasicParams lf_params{graph_executor,
|
||||
param_inputs,
|
||||
param_outputs,
|
||||
param_input_usages,
|
||||
param_output_usages,
|
||||
param_set_outputs};
|
||||
graph_executor.execute(lf_params, lf_context);
|
||||
graph_executor.destruct_storage(lf_context.storage);
|
||||
|
||||
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
|
||||
for (GMutablePointer &ptr : inputs_to_destruct) {
|
||||
ptr.destruct();
|
||||
}
|
||||
|
||||
GeometrySet output_geometry_set = std::move(*static_cast<GeometrySet *>(param_outputs[0].get()));
|
||||
store_output_attributes(output_geometry_set, *nmd, output_node, param_outputs);
|
||||
|
||||
for (GMutablePointer &ptr : param_outputs) {
|
||||
ptr.destruct();
|
||||
}
|
||||
|
||||
if (logging_enabled(ctx)) {
|
||||
Set<DSocket> preview_sockets;
|
||||
find_sockets_to_preview(nmd, ctx, tree, preview_sockets);
|
||||
eval_params.force_compute_sockets.extend(preview_sockets.begin(), preview_sockets.end());
|
||||
geo_logger.emplace(std::move(preview_sockets));
|
||||
|
||||
geo_logger->log_input_geometry(input_geometry_set);
|
||||
}
|
||||
|
||||
/* Don't keep a reference to the input geometry components to avoid copies during evaluation. */
|
||||
input_geometry_set.clear();
|
||||
|
||||
eval_params.input_values = group_inputs;
|
||||
eval_params.output_sockets = group_outputs;
|
||||
eval_params.mf_by_node = &mf_by_node;
|
||||
eval_params.modifier_ = nmd;
|
||||
eval_params.depsgraph = ctx->depsgraph;
|
||||
eval_params.self_object = ctx->object;
|
||||
eval_params.geo_logger = geo_logger.has_value() ? &*geo_logger : nullptr;
|
||||
blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params);
|
||||
|
||||
GeometrySet output_geometry_set = std::move(*eval_params.r_output_values[0].get<GeometrySet>());
|
||||
|
||||
if (geo_logger.has_value()) {
|
||||
geo_logger->log_output_geometry(output_geometry_set);
|
||||
NodesModifierData *nmd_orig = (NodesModifierData *)BKE_modifier_get_original(ctx->object,
|
||||
&nmd->modifier);
|
||||
clear_runtime_data(nmd_orig);
|
||||
nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger);
|
||||
}
|
||||
|
||||
store_output_attributes(output_geometry_set, *nmd, output_node, eval_params.r_output_values);
|
||||
|
||||
for (GMutablePointer value : eval_params.r_output_values) {
|
||||
value.destruct();
|
||||
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
|
||||
BKE_modifier_get_original(ctx->object, &nmd->modifier));
|
||||
delete static_cast<GeoModifierLog *>(nmd_orig->runtime_eval_log);
|
||||
nmd_orig->runtime_eval_log = eval_log.release();
|
||||
}
|
||||
|
||||
return output_geometry_set;
|
||||
@@ -1225,27 +1279,18 @@ static void modifyGeometry(ModifierData *md,
|
||||
return;
|
||||
}
|
||||
|
||||
const bNodeTree &tree = *nmd->node_group;
|
||||
tree.ensure_topology_cache();
|
||||
check_property_socket_sync(ctx->object, md);
|
||||
|
||||
const bNodeTree &root_tree_ref = *nmd->node_group;
|
||||
DerivedNodeTree tree{root_tree_ref};
|
||||
|
||||
if (tree.has_link_cycles()) {
|
||||
BKE_modifier_set_error(ctx->object, md, "Node group has cycles");
|
||||
const bNode *output_node = tree.group_output_node();
|
||||
if (output_node == nullptr) {
|
||||
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
|
||||
geometry_set.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Span<const bNode *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput");
|
||||
Span<const bNode *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput");
|
||||
if (output_nodes.size() != 1) {
|
||||
BKE_modifier_set_error(ctx->object, md, "Node group must have a single output node");
|
||||
geometry_set.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const bNode &output_node = *output_nodes[0];
|
||||
Span<const bNodeSocket *> group_outputs = output_node.input_sockets().drop_back(1);
|
||||
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
|
||||
if (group_outputs.is_empty()) {
|
||||
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
|
||||
geometry_set.clear();
|
||||
@@ -1259,6 +1304,14 @@ static void modifyGeometry(ModifierData *md,
|
||||
return;
|
||||
}
|
||||
|
||||
const blender::nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
|
||||
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree);
|
||||
if (lf_graph_info == nullptr) {
|
||||
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
|
||||
geometry_set.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
bool use_orig_index_verts = false;
|
||||
bool use_orig_index_edges = false;
|
||||
bool use_orig_index_polys = false;
|
||||
@@ -1270,7 +1323,7 @@ static void modifyGeometry(ModifierData *md,
|
||||
}
|
||||
|
||||
geometry_set = compute_geometry(
|
||||
tree, input_nodes, output_node, std::move(geometry_set), nmd, ctx);
|
||||
tree, *lf_graph_info, *output_node, std::move(geometry_set), nmd, ctx);
|
||||
|
||||
if (geometry_set.has_mesh()) {
|
||||
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
||||
@@ -1342,6 +1395,16 @@ static NodesModifierData *get_modifier_data(Main &bmain,
|
||||
return reinterpret_cast<NodesModifierData *>(md);
|
||||
}
|
||||
|
||||
static GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
|
||||
{
|
||||
if (nmd.runtime_eval_log == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
GeoModifierLog &modifier_log = *static_cast<GeoModifierLog *>(nmd.runtime_eval_log);
|
||||
blender::bke::ModifierComputeContext compute_context{nullptr, nmd.modifier.name};
|
||||
return &modifier_log.get_tree_log(compute_context.hash());
|
||||
}
|
||||
|
||||
static void attribute_search_update_fn(
|
||||
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
||||
{
|
||||
@@ -1350,27 +1413,52 @@ static void attribute_search_update_fn(
|
||||
if (nmd == nullptr) {
|
||||
return;
|
||||
}
|
||||
const geo_log::ModifierLog *modifier_log = static_cast<const geo_log::ModifierLog *>(
|
||||
nmd->runtime_eval_log);
|
||||
if (modifier_log == nullptr) {
|
||||
if (nmd->node_group == nullptr) {
|
||||
return;
|
||||
}
|
||||
const geo_log::GeometryValueLog *geometry_log = data.is_output ?
|
||||
modifier_log->output_geometry_log() :
|
||||
modifier_log->input_geometry_log();
|
||||
if (geometry_log == nullptr) {
|
||||
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
||||
if (tree_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
tree_log->ensure_existing_attributes();
|
||||
nmd->node_group->ensure_topology_cache();
|
||||
|
||||
Span<GeometryAttributeInfo> infos = geometry_log->attributes();
|
||||
|
||||
/* The shared attribute search code expects a span of pointers, so convert to that. */
|
||||
Array<const GeometryAttributeInfo *> info_ptrs(infos.size());
|
||||
for (const int i : infos.index_range()) {
|
||||
info_ptrs[i] = &infos[i];
|
||||
Vector<const bNodeSocket *> sockets_to_check;
|
||||
if (data.is_output) {
|
||||
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
|
||||
for (const bNodeSocket *socket : node->input_sockets()) {
|
||||
if (socket->type == SOCK_GEOMETRY) {
|
||||
sockets_to_check.append(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupInput")) {
|
||||
for (const bNodeSocket *socket : node->output_sockets()) {
|
||||
if (socket->type == SOCK_GEOMETRY) {
|
||||
sockets_to_check.append(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<StringRef> names;
|
||||
Vector<const GeometryAttributeInfo *> attributes;
|
||||
for (const bNodeSocket *socket : sockets_to_check) {
|
||||
const ValueLog *value_log = tree_log->find_socket_value_log(*socket);
|
||||
if (value_log == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
|
||||
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
|
||||
if (names.add(attribute.name)) {
|
||||
attributes.append(&attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
blender::ui::attribute_search_add_items(
|
||||
str, data.is_output, info_ptrs.as_span(), items, is_first);
|
||||
str, data.is_output, attributes.as_span(), items, is_first);
|
||||
}
|
||||
|
||||
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
||||
@@ -1401,8 +1489,7 @@ static void add_attribute_search_button(const bContext &C,
|
||||
const bNodeSocket &socket,
|
||||
const bool is_output)
|
||||
{
|
||||
const geo_log::ModifierLog *log = static_cast<geo_log::ModifierLog *>(nmd.runtime_eval_log);
|
||||
if (log == nullptr) {
|
||||
if (nmd.runtime_eval_log == nullptr) {
|
||||
uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, "", ICON_NONE);
|
||||
return;
|
||||
}
|
||||
@@ -1627,15 +1714,14 @@ static void panel_draw(const bContext *C, Panel *panel)
|
||||
}
|
||||
|
||||
/* Draw node warnings. */
|
||||
if (nmd->runtime_eval_log != nullptr) {
|
||||
const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log);
|
||||
log.foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
for (const geo_log::NodeWarning &warning : node_log.warnings()) {
|
||||
if (warning.type != geo_log::NodeWarningType::Info) {
|
||||
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
|
||||
}
|
||||
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
||||
if (tree_log != nullptr) {
|
||||
tree_log->ensure_node_warnings();
|
||||
for (const NodeWarning &warning : tree_log->all_warnings) {
|
||||
if (warning.type != NodeWarningType::Info) {
|
||||
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modifier_panel_end(layout, ptr);
|
||||
@@ -1672,17 +1758,14 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
|
||||
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
||||
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
|
||||
|
||||
if (nmd->runtime_eval_log == nullptr) {
|
||||
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
|
||||
if (tree_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log);
|
||||
Map<std::string, eNamedAttrUsage> usage_by_attribute;
|
||||
log.foreach_node_log([&](const geo_log::NodeLog &node_log) {
|
||||
for (const geo_log::UsedNamedAttribute &used_attribute : node_log.used_named_attributes()) {
|
||||
usage_by_attribute.lookup_or_add_as(used_attribute.name,
|
||||
used_attribute.usage) |= used_attribute.usage;
|
||||
}
|
||||
});
|
||||
|
||||
tree_log->ensure_used_named_attributes();
|
||||
const Map<std::string, NamedAttributeUsage> &usage_by_attribute =
|
||||
tree_log->used_named_attributes;
|
||||
|
||||
if (usage_by_attribute.is_empty()) {
|
||||
uiItemL(layout, IFACE_("No named attributes used"), ICON_INFO);
|
||||
@@ -1691,7 +1774,7 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
|
||||
|
||||
struct NameWithUsage {
|
||||
StringRefNull name;
|
||||
eNamedAttrUsage usage;
|
||||
NamedAttributeUsage usage;
|
||||
};
|
||||
|
||||
Vector<NameWithUsage> sorted_used_attribute;
|
||||
@@ -1706,20 +1789,20 @@ static void internal_dependencies_panel_draw(const bContext *UNUSED(C), Panel *p
|
||||
|
||||
for (const NameWithUsage &attribute : sorted_used_attribute) {
|
||||
const StringRefNull attribute_name = attribute.name;
|
||||
const eNamedAttrUsage usage = attribute.usage;
|
||||
const NamedAttributeUsage usage = attribute.usage;
|
||||
|
||||
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
|
||||
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
|
||||
|
||||
std::stringstream ss;
|
||||
Vector<std::string> usages;
|
||||
if ((usage & eNamedAttrUsage::Read) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("Read"));
|
||||
}
|
||||
if ((usage & eNamedAttrUsage::Write) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("Write"));
|
||||
}
|
||||
if ((usage & eNamedAttrUsage::Remove) != eNamedAttrUsage::None) {
|
||||
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
|
||||
usages.append(TIP_("Remove"));
|
||||
}
|
||||
for (const int i : usages.index_range()) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,44 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "NOD_derived_node_tree.hh"
|
||||
#include "NOD_geometry_nodes_eval_log.hh"
|
||||
#include "NOD_multi_function.hh"
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
|
||||
|
||||
namespace blender::modifiers::geometry_nodes {
|
||||
|
||||
using namespace nodes::derived_node_tree_types;
|
||||
|
||||
struct GeometryNodesEvaluationParams {
|
||||
blender::LinearAllocator<> allocator;
|
||||
|
||||
Map<DOutputSocket, GMutablePointer> input_values;
|
||||
Vector<DInputSocket> output_sockets;
|
||||
/* These sockets will be computed but are not part of the output. Their value can be retrieved in
|
||||
* `log_socket_value_fn`. These sockets are not part of `output_sockets` because then the
|
||||
* evaluator would have to keep the socket values in memory until the end, which might not be
|
||||
* necessary in all cases. Sometimes `log_socket_value_fn` might just want to look at the value
|
||||
* and then it can be freed. */
|
||||
Vector<DSocket> force_compute_sockets;
|
||||
nodes::NodeMultiFunctions *mf_by_node;
|
||||
const NodesModifierData *modifier_;
|
||||
Depsgraph *depsgraph;
|
||||
Object *self_object;
|
||||
geo_log::GeoLogger *geo_logger;
|
||||
|
||||
Vector<GMutablePointer> r_output_values;
|
||||
};
|
||||
|
||||
void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms);
|
||||
|
||||
} // namespace blender::modifiers::geometry_nodes
|
@@ -40,7 +40,8 @@ set(INC
|
||||
|
||||
set(SRC
|
||||
intern/derived_node_tree.cc
|
||||
intern/geometry_nodes_eval_log.cc
|
||||
intern/geometry_nodes_log.cc
|
||||
intern/geometry_nodes_to_lazy_function_graph.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_to_lazy_function_graph.hh
|
||||
NOD_math_functions.hh
|
||||
NOD_multi_function.hh
|
||||
NOD_node_declaration.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_to_lazy_function_graph.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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -116,20 +59,6 @@ class GeoNodeExecParams {
|
||||
static inline constexpr bool is_field_base_type_v =
|
||||
is_same_any_v<T, float, int, bool, ColorGeometry4f, float3, std::string>;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
@@ -163,27 +92,6 @@ class GeoNodeExecParams {
|
||||
void check_input_geometry_set(StringRef identifier, const GeometrySet &geometry_set) const;
|
||||
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.
|
||||
*/
|
||||
@@ -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,25 +133,37 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the evaluator that a specific input won't be used anymore.
|
||||
*/
|
||||
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
|
||||
|
@@ -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
|
213
source/blender/nodes/NOD_geometry_nodes_log.hh
Normal file
213
source/blender/nodes/NOD_geometry_nodes_log.hh
Normal file
@@ -0,0 +1,213 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#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);
|
||||
|
||||
class ValueLog {
|
||||
public:
|
||||
virtual ~ValueLog() = default;
|
||||
};
|
||||
|
||||
class GenericValueLog : public ValueLog {
|
||||
public:
|
||||
GMutablePointer value;
|
||||
|
||||
GenericValueLog(const GMutablePointer value) : value(value)
|
||||
{
|
||||
}
|
||||
|
||||
~GenericValueLog()
|
||||
{
|
||||
this->value.destruct();
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
class ViewerNodeLog {
|
||||
public:
|
||||
GeometrySet geometry;
|
||||
GField field;
|
||||
};
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
class GeoTreeLogger {
|
||||
public:
|
||||
std::optional<ComputeContextHash> parent_hash;
|
||||
std::optional<std::string> group_node_name;
|
||||
Vector<ComputeContextHash> children_hashes;
|
||||
|
||||
LinearAllocator<> *allocator = nullptr;
|
||||
Vector<std::pair<std::string, NodeWarning>> node_warnings;
|
||||
Vector<destruct_ptr<ValueLog>> socket_values_owner;
|
||||
Vector<std::tuple<std::string, std::string, ValueLog *>> input_socket_values;
|
||||
Vector<std::tuple<std::string, std::string, ValueLog *>> output_socket_values;
|
||||
Vector<std::tuple<std::string, TimePoint, TimePoint>> node_execution_times;
|
||||
Vector<std::tuple<std::string, destruct_ptr<ViewerNodeLog>>, 0> viewer_node_logs_;
|
||||
Vector<std::tuple<std::string, std::string, NamedAttributeUsage>, 0> used_named_attributes_;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
class GeoNodeLog {
|
||||
public:
|
||||
Vector<NodeWarning> warnings;
|
||||
std::chrono::nanoseconds run_time{0};
|
||||
Map<std::string, ValueLog *> input_values_;
|
||||
Map<std::string, ValueLog *> output_values_;
|
||||
Map<std::string, NamedAttributeUsage> used_named_attributes;
|
||||
|
||||
GeoNodeLog();
|
||||
~GeoNodeLog();
|
||||
};
|
||||
|
||||
class GeoModifierLog;
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
ValueLog *find_socket_value_log(const bNodeSocket &query_socket);
|
||||
};
|
||||
|
||||
class GeoModifierLog {
|
||||
private:
|
||||
struct LocalData {
|
||||
LinearAllocator<> allocator;
|
||||
Map<ComputeContextHash, destruct_ptr<GeoTreeLogger>> tree_logger_by_context;
|
||||
};
|
||||
|
||||
threading::EnumerableThreadSpecific<LocalData> data_per_thread_;
|
||||
Map<ComputeContextHash, std::unique_ptr<GeoTreeLog>> tree_logs_;
|
||||
|
||||
public:
|
||||
GeoTreeLogger &get_local_tree_logger(const ComputeContext &compute_context);
|
||||
GeoTreeLog &get_tree_log(const ComputeContextHash &compute_context_hash);
|
||||
|
||||
struct ObjectAndModifier {
|
||||
const Object *object;
|
||||
const NodesModifierData *nmd;
|
||||
};
|
||||
|
||||
static std::optional<ObjectAndModifier> get_modifier_for_node_editor(const SpaceNode &snode);
|
||||
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
|
@@ -0,0 +1,91 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#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;
|
||||
|
||||
struct GeoNodesModifierData {
|
||||
const Object *self_object = nullptr;
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
geo_eval_log::GeoModifierLog *eval_log = nullptr;
|
||||
const MultiValueMap<ComputeContextHash, const lf::FunctionNode *> *side_effect_nodes;
|
||||
};
|
||||
|
||||
struct GeoNodesLFUserData : public lf::UserData {
|
||||
GeoNodesModifierData *modifier_data = nullptr;
|
||||
const ComputeContext *compute_context = nullptr;
|
||||
};
|
||||
|
||||
struct GeometryNodeLazyFunctionMapping {
|
||||
Map<const bNodeSocket *, lf::Socket *> dummy_socket_map;
|
||||
Vector<lf::OutputSocket *> group_input_sockets;
|
||||
MultiValueMap<const lf::Socket *, const bNodeSocket *> bsockets_by_lf_socket_map;
|
||||
Map<const bNode *, const lf::FunctionNode *> group_node_map;
|
||||
Map<const bNode *, const lf::FunctionNode *> viewer_node_map;
|
||||
};
|
||||
|
||||
struct GeometryNodesLazyFunctionGraphInfo {
|
||||
LinearAllocator<> allocator;
|
||||
std::unique_ptr<NodeMultiFunctions> node_multi_functions;
|
||||
Vector<std::unique_ptr<LazyFunction>> functions;
|
||||
Vector<GMutablePointer> values_to_destruct;
|
||||
GeometryNodeLazyFunctionMapping mapping;
|
||||
lf::Graph graph;
|
||||
|
||||
~GeometryNodesLazyFunctionGraphInfo()
|
||||
{
|
||||
for (GMutablePointer &p : this->values_to_destruct) {
|
||||
p.destruct();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GeometryNodesLazyFunctionLogger : public fn::lazy_function::GraphExecutor::Logger {
|
||||
private:
|
||||
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_;
|
||||
|
||||
public:
|
||||
GeometryNodesLazyFunctionLogger(const GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
|
||||
: lf_graph_info_(lf_graph_info)
|
||||
{
|
||||
}
|
||||
|
||||
void log_socket_value(const fn::lazy_function::Context &context,
|
||||
const fn::lazy_function::Socket &lf_socket,
|
||||
GPointer value) const override;
|
||||
};
|
||||
|
||||
class GeometryNodesLazyFunctionSideEffectProvider
|
||||
: public fn::lazy_function::GraphExecutor::SideEffectProvider {
|
||||
private:
|
||||
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info_;
|
||||
|
||||
public:
|
||||
GeometryNodesLazyFunctionSideEffectProvider(
|
||||
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info)
|
||||
: lf_graph_info_(lf_graph_info)
|
||||
{
|
||||
}
|
||||
|
||||
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
|
||||
const lf::Context &context) const override;
|
||||
};
|
||||
|
||||
const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph(
|
||||
const bNodeTree &btree);
|
||||
|
||||
} // namespace blender::nodes
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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>();
|
||||
|
@@ -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:
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
588
source/blender/nodes/intern/geometry_nodes_log.cc
Normal file
588
source/blender/nodes/intern/geometry_nodes_log.cc
Normal file
@@ -0,0 +1,588 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
#include "NOD_geometry_nodes_to_lazy_function_graph.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;
|
||||
|
||||
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. */
|
||||
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, value_log.get()});
|
||||
this->socket_values_owner.append(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 std::pair<std::string, NodeWarning> &warnings : tree_logger->node_warnings) {
|
||||
this->nodes.lookup_or_add_default(warnings.first).warnings.append(warnings.second);
|
||||
this->all_warnings.append(warnings.second);
|
||||
}
|
||||
}
|
||||
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 std::tuple<std::string, TimePoint, TimePoint> &timings :
|
||||
tree_logger->node_execution_times) {
|
||||
const StringRefNull node_name = std::get<0>(timings);
|
||||
const std::chrono::nanoseconds duration = std::get<2>(timings) - std::get<1>(timings);
|
||||
this->nodes.lookup_or_add_default_as(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 std::tuple<std::string, std::string, ValueLog *> &value_log_data :
|
||||
tree_logger->input_socket_values) {
|
||||
this->nodes.lookup_or_add_as(std::get<0>(value_log_data))
|
||||
.input_values_.add(std::get<1>(value_log_data), std::get<2>(value_log_data));
|
||||
}
|
||||
for (const std::tuple<std::string, std::string, ValueLog *> &value_log_data :
|
||||
tree_logger->output_socket_values) {
|
||||
this->nodes.lookup_or_add_as(std::get<0>(value_log_data))
|
||||
.output_values_.add(std::get<1>(value_log_data), std::get<2>(value_log_data));
|
||||
}
|
||||
}
|
||||
reduced_socket_values_ = true;
|
||||
}
|
||||
|
||||
void GeoTreeLog::ensure_viewer_node_logs()
|
||||
{
|
||||
if (reduced_viewer_node_logs_) {
|
||||
return;
|
||||
}
|
||||
for (GeoTreeLogger *tree_logger : tree_loggers_) {
|
||||
for (const std::tuple<std::string, destruct_ptr<ViewerNodeLog>> &viewer_log :
|
||||
tree_logger->viewer_node_logs_) {
|
||||
this->viewer_node_logs.add(std::get<0>(viewer_log), std::get<1>(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 std::tuple<std::string, std::string, NamedAttributeUsage> &item :
|
||||
tree_logger->used_named_attributes_) {
|
||||
add_attribute(std::get<0>(item), std::get<1>(item), std::get<2>(item));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<GeoModifierLog::ObjectAndModifier> GeoModifierLog::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
|
1193
source/blender/nodes/intern/geometry_nodes_to_lazy_function_graph.cc
Normal file
1193
source/blender/nodes/intern/geometry_nodes_to_lazy_function_graph.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
@@ -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_)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user