diff options
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 32 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node_tree_multi_function.hh | 355 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node_tree_multi_function.cc | 233 |
4 files changed, 624 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index bdcbe2129c8..acd4c75befd 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -101,6 +101,30 @@ typedef struct bNodeSocketTemplate { char identifier[64]; /* generated from name */ } bNodeSocketTemplate; +/* Use `void *` for callbacks that require C++. This is rather ugly, but works well for now. This + * would not be necessary if we would use bNodeSocketType and bNodeType only in C++ code. + * However, achieving this requires quite a few changes currently. */ +#ifdef __cplusplus +namespace blender { +namespace bke { +class SocketMFNetworkBuilder; +class NodeMFNetworkBuilder; +} // namespace bke +namespace fn { +class MFDataType; +} +} // namespace blender + +using NodeExpandInMFNetworkFunction = void (*)(blender::bke::NodeMFNetworkBuilder &builder); +using SocketGetMFDataTypeFunction = blender::fn::MFDataType (*)(); +using SocketExpandInMFNetworkFunction = void (*)(blender::bke::SocketMFNetworkBuilder &builder); + +#else +typedef void *NodeExpandInMFNetworkFunction; +typedef void *SocketGetMFDataTypeFunction; +typedef void *SocketExpandInMFNetworkFunction; +#endif + /** * \brief Defines a socket type. * @@ -153,6 +177,11 @@ typedef struct bNodeSocketType { /* Callback to free the socket type. */ void (*free_self)(struct bNodeSocketType *stype); + + /* Returns the multi-function data type of this socket type. */ + SocketGetMFDataTypeFunction get_mf_data_type; + /* Expands the socket into a multi-function node that outputs the socket value. */ + SocketExpandInMFNetworkFunction expand_in_mf_network; } bNodeSocketType; typedef void *(*NodeInitExecFunction)(struct bNodeExecContext *context, @@ -267,6 +296,9 @@ typedef struct bNodeType { /* gpu */ NodeGPUExecFunction gpufunc; + /* Expands the bNode into nodes in a multi-function network, which will be evaluated later on. */ + NodeExpandInMFNetworkFunction expand_in_mf_network; + /* RNA integration */ ExtensionRNA rna_ext; } bNodeType; diff --git a/source/blender/blenkernel/BKE_node_tree_multi_function.hh b/source/blender/blenkernel/BKE_node_tree_multi_function.hh new file mode 100644 index 00000000000..34809a5f506 --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_multi_function.hh @@ -0,0 +1,355 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BKE_NODE_TREE_FUNCTION_HH__ +#define __BKE_NODE_TREE_FUNCTION_HH__ + +/** \file + * \ingroup bke + * + * This file allows you to generate a multi-function network from a user-generated node tree. + */ + +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_network.hh" + +#include "BKE_derived_node_tree.hh" + +#include "BLI_resource_collector.hh" + +namespace blender { +namespace bke { + +/* Maybe this should be moved to BKE_node.h. */ +inline bool is_multi_function_data_socket(const bNodeSocket *bsocket) +{ + if (bsocket->typeinfo->get_mf_data_type != nullptr) { + BLI_assert(bsocket->typeinfo->expand_in_mf_network != nullptr); + return true; + } + return false; +} + +/** + * A MFNetworkTreeMap maps various components of a bke::DerivedNodeTree to components of a + * fn::MFNetwork. This is necessary for further processing of a multi-function network that has + * been generated from a node tree. + */ +class MFNetworkTreeMap { + private: + /** + * Store by id instead of using a hash table to avoid unnecessary hash table lookups. + * + * Input sockets in a node tree can have multiple corresponding sockets in the generated + * MFNetwork. This is because nodes are allowed to expand into multiple multi-function nodes. + */ + Array<Vector<fn::MFSocket *, 1>> m_sockets_by_dsocket_id; + Array<fn::MFOutputSocket *> m_socket_by_group_input_id; + + public: + MFNetworkTreeMap(const DerivedNodeTree &tree) + : m_sockets_by_dsocket_id(tree.sockets().size()), + m_socket_by_group_input_id(tree.group_inputs().size(), nullptr) + { + } + + void add(const DSocket &dsocket, fn::MFSocket &socket) + { + BLI_assert(dsocket.is_input() == socket.is_input()); + m_sockets_by_dsocket_id[dsocket.id()].append(&socket); + } + + void add(const DInputSocket &dsocket, fn::MFInputSocket &socket) + { + m_sockets_by_dsocket_id[dsocket.id()].append(&socket); + } + + void add(const DOutputSocket &dsocket, fn::MFOutputSocket &socket) + { + m_sockets_by_dsocket_id[dsocket.id()].append(&socket); + } + + void add(Span<const DInputSocket *> dsockets, Span<fn::MFInputSocket *> sockets) + { + assert_same_size(dsockets, sockets); + for (uint i : dsockets.index_range()) { + this->add(*dsockets[i], *sockets[i]); + } + } + + void add(Span<const DOutputSocket *> dsockets, Span<fn::MFOutputSocket *> sockets) + { + assert_same_size(dsockets, sockets); + for (uint i : dsockets.index_range()) { + this->add(*dsockets[i], *sockets[i]); + } + } + + void add(const DGroupInput &group_input, fn::MFOutputSocket &socket) + { + BLI_assert(m_socket_by_group_input_id[group_input.id()] == nullptr); + m_socket_by_group_input_id[group_input.id()] = &socket; + } + + void add_try_match(const DNode &dnode, fn::MFNode &node) + { + this->add_try_match(dnode.inputs(), node.inputs()); + this->add_try_match(dnode.outputs(), node.outputs()); + } + + void add_try_match(Span<const DSocket *> dsockets, Span<fn::MFSocket *> sockets) + { + uint used_sockets = 0; + for (const DSocket *dsocket : dsockets) { + if (!dsocket->is_available()) { + continue; + } + if (!is_multi_function_data_socket(dsocket->bsocket())) { + continue; + } + fn::MFSocket *socket = sockets[used_sockets]; + this->add(*dsocket, *socket); + used_sockets++; + } + } + + fn::MFOutputSocket &lookup(const DGroupInput &group_input) + { + fn::MFOutputSocket *socket = m_socket_by_group_input_id[group_input.id()]; + BLI_assert(socket != nullptr); + return *socket; + } + + fn::MFOutputSocket &lookup(const DOutputSocket &dsocket) + { + auto &sockets = m_sockets_by_dsocket_id[dsocket.id()]; + BLI_assert(sockets.size() == 1); + return sockets[0]->as_output(); + } + + Span<fn::MFInputSocket *> lookup(const DInputSocket &dsocket) + { + return m_sockets_by_dsocket_id[dsocket.id()].as_span().cast<fn::MFInputSocket *>(); + } + + fn::MFInputSocket &lookup_dummy(const DInputSocket &dsocket) + { + Span<fn::MFInputSocket *> sockets = this->lookup(dsocket); + BLI_assert(sockets.size() == 1); + fn::MFInputSocket &socket = *sockets[0]; + BLI_assert(socket.node().is_dummy()); + return socket; + } + + fn::MFOutputSocket &lookup_dummy(const DOutputSocket &dsocket) + { + fn::MFOutputSocket &socket = this->lookup(dsocket); + BLI_assert(socket.node().is_dummy()); + return socket; + } + + bool is_mapped(const DSocket &dsocket) const + { + return m_sockets_by_dsocket_id[dsocket.id()].size() >= 1; + } +}; + +/** + * This data is necessary throughout the generation of a MFNetwork from a node tree. + */ +struct CommonMFNetworkBuilderData { + ResourceCollector &resources; + fn::MFNetwork &network; + MFNetworkTreeMap &network_map; + const DerivedNodeTree &tree; +}; + +class MFNetworkBuilderBase { + protected: + CommonMFNetworkBuilderData &m_common; + + public: + MFNetworkBuilderBase(CommonMFNetworkBuilderData &common) : m_common(common) + { + } + + /** + * Returns the network that is currently being built. + */ + fn::MFNetwork &network() + { + return m_common.network; + } + + /** + * Returns the map between the node tree and the multi-function network that is being built. + */ + MFNetworkTreeMap &network_map() + { + return m_common.network_map; + } + + /** + * Returns a resource collector that will only be destructed after the multi-function network is + * destructed. + */ + ResourceCollector &resources() + { + return m_common.resources; + } + + /** + * Constructs a new function that will live at least as long as the MFNetwork. + */ + template<typename T, typename... Args> T &construct_fn(Args &&... args) + { + BLI_STATIC_ASSERT((std::is_base_of_v<fn::MultiFunction, T>), ""); + void *buffer = m_common.resources.linear_allocator().allocate(sizeof(T), alignof(T)); + T *fn = new (buffer) T(std::forward<Args>(args)...); + m_common.resources.add(destruct_ptr<T>(fn), fn->name().data()); + return *fn; + } +}; + +/** + * This class is used by socket implementations to define how an unlinked input socket is handled + * in a multi-function network. + */ +class SocketMFNetworkBuilder : public MFNetworkBuilderBase { + private: + const DSocket *m_dsocket = nullptr; + const DGroupInput *m_group_input = nullptr; + bNodeSocket *m_bsocket; + fn::MFOutputSocket *m_built_socket = nullptr; + + public: + SocketMFNetworkBuilder(CommonMFNetworkBuilderData &common, const DSocket &dsocket) + : MFNetworkBuilderBase(common), m_dsocket(&dsocket), m_bsocket(dsocket.bsocket()) + { + } + + SocketMFNetworkBuilder(CommonMFNetworkBuilderData &common, const DGroupInput &group_input) + : MFNetworkBuilderBase(common), m_group_input(&group_input), m_bsocket(group_input.bsocket()) + { + } + + /** + * Returns the socket that is currently being built. + */ + bNodeSocket &bsocket() + { + return *m_bsocket; + } + + /** + * Utility method that returns bsocket->default_value for the current socket. + */ + template<typename T> T *socket_default_value() + { + return (T *)m_bsocket->default_value; + } + + /** + * Builds a function node for that socket that outputs the given constant value. + */ + template<typename T> void set_constant_value(T value) + { + const fn::MultiFunction &fn = this->construct_fn<fn::CustomMF_Constant<T>>(std::move(value)); + this->set_generator_fn(fn); + } + + /** + * Uses the first output of the given multi-function as value of the socket. + */ + void set_generator_fn(const fn::MultiFunction &fn) + { + fn::MFFunctionNode &node = m_common.network.add_function(fn); + this->set_socket(node.output(0)); + } + + /** + * Define a multi-function socket that outputs the value of the bsocket. + */ + void set_socket(fn::MFOutputSocket &socket) + { + m_built_socket = &socket; + } + + fn::MFOutputSocket *built_socket() + { + return m_built_socket; + } +}; + +/** + * This class is used by node implementations to define how a user-level node expands into + * multi-function nodes internally. + */ +class NodeMFNetworkBuilder : public MFNetworkBuilderBase { + private: + const DNode &m_node; + + public: + NodeMFNetworkBuilder(CommonMFNetworkBuilderData &common, const DNode &node) + : MFNetworkBuilderBase(common), m_node(node) + { + } + + /** + * Tells the builder to build a function that corresponds to the node that is being built. It + * will try to match up sockets. + */ + template<typename T, typename... Args> void construct_and_set_matching_fn(Args &&... args) + { + const fn::MultiFunction &function = this->construct_fn<T>(std::forward<Args>(args)...); + this->set_matching_fn(function); + } + + /** + * Tells the builder that the given function corresponds to the node that is being built. It will + * try to match up sockets. For that it skips unavailable and non-data sockets. + */ + void set_matching_fn(const fn::MultiFunction &function) + { + fn::MFFunctionNode &node = m_common.network.add_function(function); + m_common.network_map.add_try_match(m_node, node); + } + + /** + * Returns the node that is currently being built. + */ + bNode &bnode() + { + return *m_node.node_ref().bnode(); + } + + /** + * Returns the node that is currently being built. + */ + const DNode &dnode() const + { + return m_node; + } +}; + +MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, + const DerivedNodeTree &tree, + ResourceCollector &resources); + +} // namespace bke +} // namespace blender + +#endif /* __BKE_NODE_TREE_FUNCTION_HH__ */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index b9054d29752..b70476b8590 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -27,6 +27,7 @@ set(INC ../bmesh ../depsgraph ../draw + ../functions ../gpencil_modifiers ../gpu ../ikplugin @@ -187,6 +188,7 @@ set(SRC intern/multires_unsubdivide.c intern/nla.c intern/node.c + intern/node_tree_multi_function.cc intern/node_tree_ref.cc intern/object.c intern/object_deform.c @@ -356,6 +358,7 @@ set(SRC BKE_multires.h BKE_nla.h BKE_node.h + BKE_node_tree_multi_function.hh BKE_node_tree_ref.hh BKE_object.h BKE_object_deform.h @@ -424,6 +427,7 @@ set(LIB bf_bmesh bf_depsgraph bf_draw + bf_functions bf_gpencil_modifiers bf_gpu bf_ikplugin diff --git a/source/blender/blenkernel/intern/node_tree_multi_function.cc b/source/blender/blenkernel/intern/node_tree_multi_function.cc new file mode 100644 index 00000000000..7beff6b15c1 --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_multi_function.cc @@ -0,0 +1,233 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_node_tree_multi_function.hh" + +namespace blender { +namespace bke { + +/* Maybe this should be moved to BKE_node.h. */ +static std::optional<fn::MFDataType> try_get_multi_function_data_type_of_socket( + const bNodeSocket *bsocket) +{ + if (bsocket->typeinfo->get_mf_data_type == nullptr) { + return {}; + } + return bsocket->typeinfo->get_mf_data_type(); +} + +static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &dnode) +{ + constexpr uint stack_capacity = 10; + + Vector<fn::MFDataType, stack_capacity> input_types; + Vector<StringRef, stack_capacity> input_names; + Vector<const DInputSocket *, stack_capacity> input_dsockets; + + for (const DInputSocket *dsocket : dnode.inputs()) { + if (dsocket->is_available()) { + std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( + dsocket->bsocket()); + if (data_type.has_value()) { + input_types.append(*data_type); + input_names.append(dsocket->name()); + input_dsockets.append(dsocket); + } + } + } + + Vector<fn::MFDataType, stack_capacity> output_types; + Vector<StringRef, stack_capacity> output_names; + Vector<const DOutputSocket *, stack_capacity> output_dsockets; + + for (const DOutputSocket *dsocket : dnode.outputs()) { + if (dsocket->is_available()) { + std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( + dsocket->bsocket()); + if (data_type.has_value()) { + output_types.append(*data_type); + output_names.append(dsocket->name()); + output_dsockets.append(dsocket); + } + } + } + + fn::MFDummyNode &dummy_node = common.network.add_dummy( + dnode.name(), input_types, output_types, input_names, output_names); + + common.network_map.add(input_dsockets, dummy_node.inputs()); + common.network_map.add(output_dsockets, dummy_node.outputs()); +} + +static bool has_data_sockets(const DNode &dnode) +{ + for (const DInputSocket *socket : dnode.inputs()) { + if (is_multi_function_data_socket(socket->bsocket())) { + return true; + } + } + for (const DOutputSocket *socket : dnode.outputs()) { + if (is_multi_function_data_socket(socket->bsocket())) { + return true; + } + } + return false; +} + +/** + * Expands all function nodes in the multi-function network. Nodes that don't have an expand + * function, but do have data sockets, will get corresponding dummy nodes. + */ +static void insert_nodes(CommonMFNetworkBuilderData &common) +{ + for (const DNode *dnode : common.tree.nodes()) { + const bNodeType *node_type = dnode->node_ref().bnode()->typeinfo; + if (node_type->expand_in_mf_network != nullptr) { + NodeMFNetworkBuilder builder{common, *dnode}; + node_type->expand_in_mf_network(builder); + } + else if (has_data_sockets(*dnode)) { + insert_dummy_node(common, *dnode); + } + } +} + +static void insert_group_inputs(CommonMFNetworkBuilderData &common) +{ + for (const DGroupInput *group_input : common.tree.group_inputs()) { + bNodeSocket *bsocket = group_input->bsocket(); + if (is_multi_function_data_socket(bsocket)) { + bNodeSocketType *socktype = bsocket->typeinfo; + BLI_assert(socktype->expand_in_mf_network != nullptr); + + SocketMFNetworkBuilder builder{common, *group_input}; + socktype->expand_in_mf_network(builder); + + fn::MFOutputSocket *from_socket = builder.built_socket(); + BLI_assert(from_socket != nullptr); + common.network_map.add(*group_input, *from_socket); + } + } +} + +static fn::MFOutputSocket *try_find_origin(CommonMFNetworkBuilderData &common, + const DInputSocket &to_dsocket) +{ + Span<const DOutputSocket *> from_dsockets = to_dsocket.linked_sockets(); + Span<const DGroupInput *> from_group_inputs = to_dsocket.linked_group_inputs(); + uint total_linked_amount = from_dsockets.size() + from_group_inputs.size(); + BLI_assert(total_linked_amount <= 1); + + if (total_linked_amount == 0) { + return nullptr; + } + + if (from_dsockets.size() == 1) { + return &common.network_map.lookup(*from_dsockets[0]); + } + else { + return &common.network_map.lookup(*from_group_inputs[0]); + } +} + +static void insert_links(CommonMFNetworkBuilderData &common) +{ + for (const DInputSocket *to_dsocket : common.tree.input_sockets()) { + if (!to_dsocket->is_available()) { + continue; + } + + if (!is_multi_function_data_socket(to_dsocket->bsocket())) { + continue; + } + + fn::MFOutputSocket *from_socket = try_find_origin(common, *to_dsocket); + if (from_socket == nullptr) { + continue; + } + + Span<fn::MFInputSocket *> to_sockets = common.network_map.lookup(*to_dsocket); + BLI_assert(to_sockets.size() >= 1); + + fn::MFDataType from_type = from_socket->data_type(); + fn::MFDataType to_type = to_sockets[0]->data_type(); + + if (from_type != to_type) { + /* Todo: Try inserting implicit conversion. */ + } + + for (fn::MFInputSocket *to_socket : to_sockets) { + common.network.add_link(*from_socket, *to_socket); + } + } +} + +static void insert_unlinked_input(CommonMFNetworkBuilderData &common, const DInputSocket &dsocket) +{ + bNodeSocket *bsocket = dsocket.bsocket(); + bNodeSocketType *socktype = bsocket->typeinfo; + BLI_assert(socktype->expand_in_mf_network != nullptr); + + SocketMFNetworkBuilder builder{common, dsocket}; + socktype->expand_in_mf_network(builder); + + fn::MFOutputSocket *from_socket = builder.built_socket(); + BLI_assert(from_socket != nullptr); + + for (fn::MFInputSocket *to_socket : common.network_map.lookup(dsocket)) { + common.network.add_link(*from_socket, *to_socket); + } +} + +static void insert_unlinked_inputs(CommonMFNetworkBuilderData &common) +{ + Vector<const DInputSocket *> unlinked_data_inputs; + for (const DInputSocket *dsocket : common.tree.input_sockets()) { + if (dsocket->is_available()) { + if (is_multi_function_data_socket(dsocket->bsocket())) { + if (!dsocket->is_linked()) { + insert_unlinked_input(common, *dsocket); + } + } + } + } +} + +/** + * Expands all function nodes contained in the given node tree within the given multi-function + * network. + * + * Returns a mapping between the original node tree and the generated nodes/sockets for further + * processing. + */ +MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, + const DerivedNodeTree &tree, + ResourceCollector &resources) +{ + MFNetworkTreeMap network_map{tree}; + + CommonMFNetworkBuilderData common{resources, network, network_map, tree}; + + insert_nodes(common); + insert_group_inputs(common); + insert_links(common); + insert_unlinked_inputs(common); + + return network_map; +} + +} // namespace bke +} // namespace blender |