diff options
author | Julian Eisel <julian@blender.org> | 2020-07-01 18:13:57 +0300 |
---|---|---|
committer | Julian Eisel <julian@blender.org> | 2020-07-01 18:13:57 +0300 |
commit | 0829cebeb024095c268f190c34daa8ae9a5a224c (patch) | |
tree | 12ee5a4a1c2a32e12eff47c8eb9bb0ed217791c1 /source/blender/functions | |
parent | cfde6ebf450594faa57c4bfeaecff10fe512c91b (diff) | |
parent | 42be3964eb201180f6b0fa1ff6ce43b8c3845bc2 (diff) |
Merge branch 'master' into asset-uuid--archivedasset-uuid--archived
Diffstat (limited to 'source/blender/functions')
21 files changed, 4227 insertions, 39 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index 9ce1d3ac2fe..703d3c393e8 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -27,10 +27,26 @@ set(INC_SYS ) set(SRC + intern/attributes_ref.cc intern/cpp_types.cc + intern/multi_function.cc + intern/multi_function_network.cc + intern/multi_function_network_evaluation.cc + FN_array_spans.hh + FN_attributes_ref.hh FN_cpp_type.hh FN_cpp_types.hh + FN_multi_function.hh + FN_multi_function_builder.hh + FN_multi_function_context.hh + FN_multi_function_data_type.hh + FN_multi_function_network.hh + FN_multi_function_network_evaluation.hh + FN_multi_function_param_type.hh + FN_multi_function_params.hh + FN_multi_function_signature.hh + FN_spans.hh ) set(LIB diff --git a/source/blender/functions/FN_array_spans.hh b/source/blender/functions/FN_array_spans.hh new file mode 100644 index 00000000000..acd3e921b50 --- /dev/null +++ b/source/blender/functions/FN_array_spans.hh @@ -0,0 +1,209 @@ +/* + * 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 __FN_ARRAY_SPANS_HH__ +#define __FN_ARRAY_SPANS_HH__ + +/** \file + * \ingroup fn + * + * An ArraySpan is a span where every element contains an array (instead of a single element as is + * the case in a normal span). It's main use case is to reference many small arrays. + */ + +#include "FN_spans.hh" + +namespace blender { +namespace fn { + +/** + * Depending on the use case, the referenced data might have a different structure. More + * categories can be added when necessary. + */ +enum class VArraySpanCategory { + SingleArray, + StartsAndSizes, +}; + +template<typename T> class VArraySpanBase { + protected: + uint m_virtual_size; + VArraySpanCategory m_category; + + union { + struct { + const T *start; + uint size; + } single_array; + struct { + const T *const *starts; + const uint *sizes; + } starts_and_sizes; + } m_data; + + public: + bool is_single_array() const + { + switch (m_category) { + case VArraySpanCategory::SingleArray: + return true; + case VArraySpanCategory::StartsAndSizes: + return m_virtual_size == 1; + } + BLI_assert(false); + return false; + } + + bool is_empty() const + { + return this->m_virtual_size == 0; + } + + uint size() const + { + return this->m_virtual_size; + } +}; + +/** + * A virtual array span. Every element of this span contains a virtual span. So it behaves like + * a blender::Span, but might not be backed up by an actual array. + */ +template<typename T> class VArraySpan : public VArraySpanBase<T> { + private: + friend class GVArraySpan; + + VArraySpan(const VArraySpanBase<void> &other) + { + memcpy(this, &other, sizeof(VArraySpanBase<void>)); + } + + public: + VArraySpan() + { + this->m_virtual_size = 0; + this->m_category = VArraySpanCategory::StartsAndSizes; + this->m_data.starts_and_sizes.starts = nullptr; + this->m_data.starts_and_sizes.sizes = nullptr; + } + + VArraySpan(Span<T> span, uint virtual_size) + { + this->m_virtual_size = virtual_size; + this->m_category = VArraySpanCategory::SingleArray; + this->m_data.single_array.start = span.data(); + this->m_data.single_array.size = span.size(); + } + + VArraySpan(Span<const T *> starts, Span<uint> sizes) + { + BLI_assert(starts.size() == sizes.size()); + this->m_virtual_size = starts.size(); + this->m_category = VArraySpanCategory::StartsAndSizes; + this->m_data.starts_and_sizes.starts = starts.begin(); + this->m_data.starts_and_sizes.sizes = sizes.begin(); + } + + VSpan<T> operator[](uint index) const + { + BLI_assert(index < this->m_virtual_size); + switch (this->m_category) { + case VArraySpanCategory::SingleArray: + return VSpan<T>(Span<T>(this->m_data.single_array.start, this->m_data.single_array.size)); + case VArraySpanCategory::StartsAndSizes: + return VSpan<T>(Span<T>(this->m_data.starts_and_sizes.starts[index], + this->m_data.starts_and_sizes.sizes[index])); + } + BLI_assert(false); + return {}; + } +}; + +/** + * A generic virtual array span. It's just like a VArraySpan, but the type is only known at + * run-time. + */ +class GVArraySpan : public VArraySpanBase<void> { + private: + const CPPType *m_type; + + GVArraySpan() = default; + + public: + GVArraySpan(const CPPType &type) + { + this->m_type = &type; + this->m_virtual_size = 0; + this->m_category = VArraySpanCategory::StartsAndSizes; + this->m_data.starts_and_sizes.starts = nullptr; + this->m_data.starts_and_sizes.sizes = nullptr; + } + + GVArraySpan(GSpan array, uint virtual_size) + { + this->m_type = &array.type(); + this->m_virtual_size = virtual_size; + this->m_category = VArraySpanCategory::SingleArray; + this->m_data.single_array.start = array.buffer(); + this->m_data.single_array.size = array.size(); + } + + GVArraySpan(const CPPType &type, Span<const void *> starts, Span<uint> sizes) + { + BLI_assert(starts.size() == sizes.size()); + this->m_type = &type; + this->m_virtual_size = starts.size(); + this->m_category = VArraySpanCategory::StartsAndSizes; + this->m_data.starts_and_sizes.starts = (void **)starts.begin(); + this->m_data.starts_and_sizes.sizes = sizes.begin(); + } + + template<typename T> GVArraySpan(VArraySpan<T> other) + { + this->m_type = &CPPType::get<T>(); + memcpy(this, &other, sizeof(VArraySpanBase<void>)); + } + + const CPPType &type() const + { + return *this->m_type; + } + + template<typename T> VArraySpan<T> typed() const + { + BLI_assert(m_type->is<T>()); + return VArraySpan<T>(*this); + } + + GVSpan operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case VArraySpanCategory::SingleArray: + return GVSpan(GSpan(*m_type, m_data.single_array.start, m_data.single_array.size)); + case VArraySpanCategory::StartsAndSizes: + return GVSpan(GSpan( + *m_type, m_data.starts_and_sizes.starts[index], m_data.starts_and_sizes.sizes[index])); + } + BLI_assert(false); + return GVSpan(*m_type); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_ARRAY_SPANS_HH__ */ diff --git a/source/blender/functions/FN_attributes_ref.hh b/source/blender/functions/FN_attributes_ref.hh new file mode 100644 index 00000000000..383b26330bf --- /dev/null +++ b/source/blender/functions/FN_attributes_ref.hh @@ -0,0 +1,248 @@ +/* + * 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 __FN_ATTRIBUTES_REF_HH__ +#define __FN_ATTRIBUTES_REF_HH__ + +/** \file + * \ingroup fn + * + * An AttributesRef references multiple arrays of equal length. Each array has a corresponding name + * and index. + */ + +#include <optional> + +#include "FN_spans.hh" + +#include "BLI_linear_allocator.hh" +#include "BLI_map.hh" +#include "BLI_utility_mixins.hh" +#include "BLI_vector_set.hh" + +namespace blender { +namespace fn { + +class AttributesInfo; + +class AttributesInfoBuilder : NonCopyable, NonMovable { + private: + LinearAllocator<> m_allocator; + VectorSet<std::string> m_names; + Vector<const CPPType *> m_types; + Vector<void *> m_defaults; + + friend AttributesInfo; + + public: + AttributesInfoBuilder() = default; + ~AttributesInfoBuilder(); + + template<typename T> void add(StringRef name, const T &default_value) + { + this->add(name, CPPType::get<T>(), (const void *)&default_value); + } + + void add(StringRef name, const CPPType &type, const void *default_value = nullptr); +}; + +/** + * Stores which attributes are in an AttributesRef. Every attribute has a unique index, a unique + * name, a type and a default value. + */ +class AttributesInfo : NonCopyable, NonMovable { + private: + LinearAllocator<> m_allocator; + Map<StringRefNull, uint> m_index_by_name; + Vector<StringRefNull> m_name_by_index; + Vector<const CPPType *> m_type_by_index; + Vector<void *> m_defaults; + + public: + AttributesInfo() = default; + AttributesInfo(const AttributesInfoBuilder &builder); + ~AttributesInfo(); + + uint size() const + { + return m_name_by_index.size(); + } + + IndexRange index_range() const + { + return m_name_by_index.index_range(); + } + + StringRefNull name_of(uint index) const + { + return m_name_by_index[index]; + } + + uint index_of(StringRef name) const + { + return m_index_by_name.lookup_as(name); + } + + const void *default_of(uint index) const + { + return m_defaults[index]; + } + + const void *default_of(StringRef name) const + { + return this->default_of(this->index_of(name)); + } + + template<typename T> const T &default_of(uint index) const + { + BLI_assert(m_type_by_index[index]->is<T>()); + return *(T *)m_defaults[index]; + } + + template<typename T> const T &default_of(StringRef name) const + { + return this->default_of<T>(this->index_of(name)); + } + + const CPPType &type_of(uint index) const + { + return *m_type_by_index[index]; + } + + const CPPType &type_of(StringRef name) const + { + return this->type_of(this->index_of(name)); + } + + bool has_attribute(StringRef name, const CPPType &type) const + { + return this->try_index_of(name, type) >= 0; + } + + int try_index_of(StringRef name) const + { + return (int)m_index_by_name.lookup_default_as(name, -1); + } + + int try_index_of(StringRef name, const CPPType &type) const + { + int index = this->try_index_of(name); + if (index == -1) { + return -1; + } + else if (this->type_of((uint)index) == type) { + return index; + } + else { + return -1; + } + } +}; + +/** + * References multiple arrays that match the description of an AttributesInfo instance. This class + * is supposed to be relatively cheap to copy. It does not own any of the arrays itself. + */ +class MutableAttributesRef { + private: + const AttributesInfo *m_info; + Span<void *> m_buffers; + IndexRange m_range; + + public: + MutableAttributesRef(const AttributesInfo &info, Span<void *> buffers, uint size) + : MutableAttributesRef(info, buffers, IndexRange(size)) + { + } + + MutableAttributesRef(const AttributesInfo &info, Span<void *> buffers, IndexRange range) + : m_info(&info), m_buffers(buffers), m_range(range) + { + } + + uint size() const + { + return m_range.size(); + } + + const AttributesInfo &info() const + { + return *m_info; + } + + GMutableSpan get(uint index) const + { + const CPPType &type = m_info->type_of(index); + void *ptr = POINTER_OFFSET(m_buffers[index], type.size() * m_range.start()); + return GMutableSpan(type, ptr, m_range.size()); + } + + GMutableSpan get(StringRef name) const + { + return this->get(m_info->index_of(name)); + } + + template<typename T> MutableSpan<T> get(uint index) const + { + BLI_assert(m_info->type_of(index).is<T>()); + return MutableSpan<T>((T *)m_buffers[index] + m_range.start(), m_range.size()); + } + + template<typename T> MutableSpan<T> get(StringRef name) const + { + return this->get<T>(m_info->index_of(name)); + } + + std::optional<GMutableSpan> try_get(StringRef name, const CPPType &type) const + { + int index = m_info->try_index_of(name, type); + if (index == -1) { + return {}; + } + else { + return this->get((uint)index); + } + } + + template<typename T> std::optional<MutableSpan<T>> try_get(StringRef name) const + { + int index = m_info->try_index_of(name); + if (index == -1) { + return {}; + } + else if (m_info->type_of((uint)index).is<T>()) { + return this->get<T>((uint)index); + } + else { + return {}; + } + } + + MutableAttributesRef slice(IndexRange range) const + { + return this->slice(range.start(), range.size()); + } + + MutableAttributesRef slice(uint start, uint size) const + { + return MutableAttributesRef(*m_info, m_buffers, m_range.slice(start, size)); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_ATTRIBUTES_REF_HH__ */ diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh index 1dc72e16e77..706ff85bdf3 100644 --- a/source/blender/functions/FN_cpp_type.hh +++ b/source/blender/functions/FN_cpp_type.hh @@ -21,7 +21,7 @@ * \ingroup functions * * The CPPType class is the core of the runtime-type-system used by the functions system. An - * instance of this class can represent any C++ type, that is default-constructable, destructable, + * instance of this class can represent any C++ type, that is default-constructible, destructible, * movable and copyable. Therefore it also works for all C types. This restrictions might need to * be removed in the future, but for now every required type has these properties. * @@ -71,11 +71,7 @@ #include "BLI_string_ref.hh" namespace blender { -namespace FN { - -using blender::IndexMask; -using blender::StringRef; -using blender::StringRefNull; +namespace fn { class CPPType { public: @@ -241,14 +237,14 @@ class CPPType { void construct_default_n(void *ptr, uint n) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_construct_default_n(ptr, n); } void construct_default_indices(void *ptr, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_construct_default_indices(ptr, index_mask); } @@ -270,14 +266,14 @@ class CPPType { void destruct_n(void *ptr, uint n) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_destruct_n(ptr, n); } void destruct_indices(void *ptr, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_destruct_indices(ptr, index_mask); } @@ -300,8 +296,8 @@ class CPPType { void copy_to_initialized_n(const void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_initialized_n(src, dst, n); } @@ -309,8 +305,8 @@ class CPPType { void copy_to_initialized_indices(const void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_initialized_indices(src, dst, index_mask); } @@ -335,8 +331,8 @@ class CPPType { void copy_to_uninitialized_n(const void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_uninitialized_n(src, dst, n); } @@ -344,8 +340,8 @@ class CPPType { void copy_to_uninitialized_indices(const void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_uninitialized_indices(src, dst, index_mask); } @@ -370,8 +366,8 @@ class CPPType { void relocate_to_initialized_n(void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_initialized_n(src, dst, n); } @@ -379,8 +375,8 @@ class CPPType { void relocate_to_initialized_indices(void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_initialized_indices(src, dst, index_mask); } @@ -405,8 +401,8 @@ class CPPType { void relocate_to_uninitialized_n(void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_uninitialized_n(src, dst, n); } @@ -414,8 +410,8 @@ class CPPType { void relocate_to_uninitialized_indices(void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_uninitialized_indices(src, dst, index_mask); } @@ -435,8 +431,8 @@ class CPPType { void fill_initialized_indices(const void *value, void *dst, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(value)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_fill_initialized_indices(value, dst, index_mask); } @@ -456,8 +452,8 @@ class CPPType { void fill_uninitialized_indices(const void *value, void *dst, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(value)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_fill_uninitialized_indices(value, dst, index_mask); } @@ -487,6 +483,11 @@ class CPPType { template<typename T> static const CPPType &get(); + template<typename T> bool is() const + { + return this == &CPPType::get<T>(); + } + private: uint m_size; uint m_alignment; @@ -719,16 +720,18 @@ static std::unique_ptr<const CPPType> create_cpp_type(StringRef name, const T &d return std::unique_ptr<const CPPType>(type); } -} // namespace FN +} // namespace fn } // namespace blender #define MAKE_CPP_TYPE(IDENTIFIER, TYPE_NAME) \ static TYPE_NAME default_value_##IDENTIFIER; \ - static std::unique_ptr<const blender::FN::CPPType> CPPTYPE_##IDENTIFIER##_owner = \ - blender::FN::create_cpp_type<TYPE_NAME>(STRINGIFY(IDENTIFIER), default_value_##IDENTIFIER); \ - const blender::FN::CPPType &CPPType_##IDENTIFIER = *CPPTYPE_##IDENTIFIER##_owner; \ - template<> const blender::FN::CPPType &blender::FN::CPPType::get<TYPE_NAME>() \ + static std::unique_ptr<const blender::fn::CPPType> CPPTYPE_##IDENTIFIER##_owner = \ + blender::fn::create_cpp_type<TYPE_NAME>(STRINGIFY(IDENTIFIER), default_value_##IDENTIFIER); \ + const blender::fn::CPPType &CPPType_##IDENTIFIER = *CPPTYPE_##IDENTIFIER##_owner; \ + template<> const blender::fn::CPPType &blender::fn::CPPType::get<TYPE_NAME>() \ { \ + /* This can happen when trying to access a CPPType during static storage initialization. */ \ + BLI_assert(CPPTYPE_##IDENTIFIER##_owner.get() != nullptr); \ return CPPType_##IDENTIFIER; \ } diff --git a/source/blender/functions/FN_cpp_types.hh b/source/blender/functions/FN_cpp_types.hh index 6ee8788cd52..c39b284d5c4 100644 --- a/source/blender/functions/FN_cpp_types.hh +++ b/source/blender/functions/FN_cpp_types.hh @@ -27,7 +27,7 @@ #include "FN_cpp_type.hh" namespace blender { -namespace FN { +namespace fn { extern const CPPType &CPPType_bool; @@ -44,7 +44,7 @@ extern const CPPType &CPPType_Color4b; extern const CPPType &CPPType_string; -} // namespace FN +} // namespace fn } // namespace blender #endif /* __FN_CPP_TYPES_HH__ */ diff --git a/source/blender/functions/FN_generic_vector_array.hh b/source/blender/functions/FN_generic_vector_array.hh new file mode 100644 index 00000000000..6be1b68da4d --- /dev/null +++ b/source/blender/functions/FN_generic_vector_array.hh @@ -0,0 +1,208 @@ +/* + * 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 __FN_GENERIC_VECTOR_ARRAY_HH__ +#define __FN_GENERIC_VECTOR_ARRAY_HH__ + +/** \file + * \ingroup fn + * + * A `GVectorArray` is a container for a fixed amount of dynamically growing arrays with a generic + * type. Its main use case is to store many small vectors with few separate allocations. Using this + * structure is generally more efficient than allocating each small vector separately. + * + * `GVectorArrayRef<T>` is a typed reference to a GVectorArray and makes it easier and safer to + * work with the class when the type is known at compile time. + */ + +#include "FN_array_spans.hh" +#include "FN_cpp_type.hh" + +#include "BLI_array.hh" +#include "BLI_linear_allocator.hh" +#include "BLI_utility_mixins.hh" + +namespace blender { +namespace fn { + +template<typename T> class GVectorArrayRef; + +class GVectorArray : NonCopyable, NonMovable { + private: + const CPPType &m_type; + uint m_element_size; + Array<void *, 1> m_starts; + Array<uint, 1> m_lengths; + Array<uint, 1> m_capacities; + LinearAllocator<> m_allocator; + + template<typename T> friend class GVectorArrayRef; + + public: + GVectorArray() = delete; + + GVectorArray(const CPPType &type, uint array_size) + : m_type(type), + m_element_size(type.size()), + m_starts(array_size), + m_lengths(array_size), + m_capacities(array_size) + { + m_starts.fill(nullptr); + m_lengths.fill(0); + m_capacities.fill(0); + } + + ~GVectorArray() + { + if (m_type.is_trivially_destructible()) { + return; + } + + for (uint i : m_starts.index_range()) { + m_type.destruct_n(m_starts[i], m_lengths[i]); + } + } + + operator GVArraySpan() const + { + return GVArraySpan(m_type, m_starts.as_span(), m_lengths); + } + + bool is_empty() const + { + return m_starts.size() == 0; + } + + uint size() const + { + return m_starts.size(); + } + + const CPPType &type() const + { + return m_type; + } + + Span<const void *> starts() const + { + return m_starts.as_span(); + } + + Span<uint> lengths() const + { + return m_lengths; + } + + void append(uint index, const void *src) + { + uint old_length = m_lengths[index]; + if (old_length == m_capacities[index]) { + this->grow_at_least_one(index); + } + + void *dst = POINTER_OFFSET(m_starts[index], m_element_size * old_length); + m_type.copy_to_uninitialized(src, dst); + m_lengths[index]++; + } + + void extend(uint index, GVSpan span) + { + BLI_assert(m_type == span.type()); + for (uint i = 0; i < span.size(); i++) { + this->append(index, span[i]); + } + } + + void extend(IndexMask mask, GVArraySpan array_span) + { + BLI_assert(m_type == array_span.type()); + BLI_assert(mask.min_array_size() <= array_span.size()); + for (uint i : mask) { + this->extend(i, array_span[i]); + } + } + + GMutableSpan operator[](uint index) + { + BLI_assert(index < m_starts.size()); + return GMutableSpan(m_type, m_starts[index], m_lengths[index]); + } + template<typename T> GVectorArrayRef<T> typed() + { + return GVectorArrayRef<T>(*this); + } + + private: + void grow_at_least_one(uint index) + { + BLI_assert(m_lengths[index] == m_capacities[index]); + uint new_capacity = m_lengths[index] * 2 + 1; + + void *new_buffer = m_allocator.allocate(m_element_size * new_capacity, m_type.alignment()); + m_type.relocate_to_uninitialized_n(m_starts[index], new_buffer, m_lengths[index]); + + m_starts[index] = new_buffer; + m_capacities[index] = new_capacity; + } +}; + +template<typename T> class GVectorArrayRef { + private: + GVectorArray *m_vector_array; + + public: + GVectorArrayRef(GVectorArray &vector_array) : m_vector_array(&vector_array) + { + BLI_assert(vector_array.m_type.is<T>()); + } + + void append(uint index, const T &value) + { + m_vector_array->append(index, &value); + } + + void extend(uint index, Span<T> values) + { + m_vector_array->extend(index, values); + } + + void extend(uint index, VSpan<T> values) + { + m_vector_array->extend(index, GVSpan(values)); + } + + MutableSpan<T> operator[](uint index) + { + BLI_assert(index < m_vector_array->m_starts.size()); + return MutableSpan<T>((T *)m_vector_array->m_starts[index], m_vector_array->m_lengths[index]); + } + + uint size() const + { + return m_vector_array->size(); + } + + bool is_empty() const + { + return m_vector_array->is_empty(); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_GENERIC_VECTOR_ARRAY_HH__ */ diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh new file mode 100644 index 00000000000..a7e964b6651 --- /dev/null +++ b/source/blender/functions/FN_multi_function.hh @@ -0,0 +1,108 @@ +/* + * 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 __FN_MULTI_FUNCTION_HH__ +#define __FN_MULTI_FUNCTION_HH__ + +/** \file + * \ingroup fn + * + * A `MultiFunction` encapsulates a function that is optimized for throughput (instead of latency). + * The throughput is optimized by always processing many elements at once, instead of each element + * separately. This is ideal for functions that are evaluated often (e.g. for every particle). + * + * By processing a lot of data at once, individual functions become easier to optimize for humans + * and for the compiler. Furthermore, performance profiles become easier to understand and show + * better where bottlenecks are. + * + * Every multi-function has a name and an ordered list of parameters. Parameters are used for input + * and output. In fact, there are three kinds of parameters: inputs, outputs and mutable (which is + * combination of input and output). + * + * To call a multi-function, one has to provide three things: + * - `MFParams`: This references the input and output arrays that the function works with. The + * arrays are not owned by MFParams. + * - `IndexMask`: An array of indices indicating which indices in the provided arrays should be + * touched/processed. + * - `MFContext`: Further information for the called function. + * + * A new multi-function is generally implemented as follows: + * 1. Create a new subclass of MultiFunction. + * 2. Implement a constructor that initialized the signature of the function. + * 3. Override the `call` function. + */ + +#include "FN_multi_function_context.hh" +#include "FN_multi_function_params.hh" + +namespace blender { +namespace fn { + +class MultiFunction { + private: + MFSignature m_signature; + + public: + virtual ~MultiFunction() + { + } + + virtual void call(IndexMask mask, MFParams params, MFContext context) const = 0; + + IndexRange param_indices() const + { + return m_signature.param_types.index_range(); + } + + MFParamType param_type(uint param_index) const + { + return m_signature.param_types[param_index]; + } + + StringRefNull param_name(uint param_index) const + { + return m_signature.param_names[param_index]; + } + + StringRefNull name() const + { + return m_signature.function_name; + } + + const MFSignature &signature() const + { + return m_signature; + } + + protected: + MFSignatureBuilder get_builder(std::string function_name) + { + m_signature.function_name = std::move(function_name); + return MFSignatureBuilder(m_signature); + } +}; + +inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, uint min_array_size) + : MFParamsBuilder(fn.signature(), min_array_size) +{ +} + +extern const MultiFunction &dummy_multi_function; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_HH__ */ diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh new file mode 100644 index 00000000000..9fcf31443b2 --- /dev/null +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -0,0 +1,232 @@ +/* + * 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 __FN_MULTI_FUNCTION_BUILDER_HH__ +#define __FN_MULTI_FUNCTION_BUILDER_HH__ + +/** \file + * \ingroup fn + * + * This file contains several utilities to create multi-functions with less redundant code. + */ + +#include <functional> + +#include "FN_multi_function.hh" + +namespace blender { +namespace fn { + +/** + * Generates a multi-function with the following parameters: + * 1. single input (SI) of type In1 + * 2. single output (SO) of type Out1 + * + * This example creates a function that adds 10 to the incoming values: + * CustomMF_SI_SO<int, int> fn("add 10", [](int value) { return value + 10; }); + */ +template<typename In1, typename Out1> class CustomMF_SI_SO : public MultiFunction { + private: + using FunctionT = std::function<void(IndexMask, VSpan<In1>, MutableSpan<Out1>)>; + FunctionT m_function; + + public: + CustomMF_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_input<In1>("In1"); + signature.single_output<Out1>("Out1"); + } + + template<typename ElementFuncT> + CustomMF_SI_SO(StringRef name, ElementFuncT element_fn) + : CustomMF_SI_SO(name, CustomMF_SI_SO::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, VSpan<In1> in1, MutableSpan<Out1> out1) { + mask.foreach_index([&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i])); }); + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<In1> in1 = params.readonly_single_input<In1>(0); + MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(1); + m_function(mask, in1, out1); + } +}; + +/** + * Generates a multi-function with the following parameters: + * 1. single input (SI) of type In1 + * 2. single input (SI) of type In2 + * 3. single output (SO) of type Out1 + */ +template<typename In1, typename In2, typename Out1> +class CustomMF_SI_SI_SO : public MultiFunction { + private: + using FunctionT = std::function<void(IndexMask, VSpan<In1>, VSpan<In2>, MutableSpan<Out1>)>; + FunctionT m_function; + + public: + CustomMF_SI_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_input<In1>("In1"); + signature.single_input<In2>("In2"); + signature.single_output<Out1>("Out1"); + } + + template<typename ElementFuncT> + CustomMF_SI_SI_SO(StringRef name, ElementFuncT element_fn) + : CustomMF_SI_SI_SO(name, CustomMF_SI_SI_SO::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, VSpan<In1> in1, VSpan<In2> in2, MutableSpan<Out1> out1) { + mask.foreach_index([&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i], in2[i])); }); + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<In1> in1 = params.readonly_single_input<In1>(0); + VSpan<In2> in2 = params.readonly_single_input<In2>(1); + MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(2); + m_function(mask, in1, in2, out1); + } +}; + +/** + * Generates a multi-function with the following parameters: + * 1. single input (SI) of type In1 + * 2. single input (SI) of type In2 + * 3. single input (SI) of type In3 + * 4. single output (SO) of type Out1 + */ +template<typename In1, typename In2, typename In3, typename Out1> +class CustomMF_SI_SI_SI_SO : public MultiFunction { + private: + using FunctionT = + std::function<void(IndexMask, VSpan<In1>, VSpan<In2>, VSpan<In3>, MutableSpan<Out1>)>; + FunctionT m_function; + + public: + CustomMF_SI_SI_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_input<In1>("In1"); + signature.single_input<In2>("In2"); + signature.single_input<In3>("In3"); + signature.single_output<Out1>("Out1"); + } + + template<typename ElementFuncT> + CustomMF_SI_SI_SI_SO(StringRef name, ElementFuncT element_fn) + : CustomMF_SI_SI_SI_SO(name, CustomMF_SI_SI_SI_SO::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, + VSpan<In1> in1, + VSpan<In2> in2, + VSpan<In3> in3, + MutableSpan<Out1> out1) { + mask.foreach_index( + [&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i], in2[i], in3[i])); }); + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<In1> in1 = params.readonly_single_input<In1>(0); + VSpan<In2> in2 = params.readonly_single_input<In2>(1); + VSpan<In3> in3 = params.readonly_single_input<In3>(2); + MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(3); + m_function(mask, in1, in2, in3, out1); + } +}; + +/** + * Generates a multi-function with the following parameters: + * 1. single mutable (SM) of type Mut1 + */ +template<typename Mut1> class CustomMF_SM : public MultiFunction { + private: + using FunctionT = std::function<void(IndexMask, MutableSpan<Mut1>)>; + FunctionT m_function; + + public: + CustomMF_SM(StringRef name, FunctionT function) : m_function(std::move(function)) + { + MFSignatureBuilder signature = this->get_builder(name); + signature.single_mutable<Mut1>("Mut1"); + } + + template<typename ElementFuncT> + CustomMF_SM(StringRef name, ElementFuncT element_fn) + : CustomMF_SM(name, CustomMF_SM::create_function(element_fn)) + { + } + + template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn) + { + return [=](IndexMask mask, MutableSpan<Mut1> mut1) { + mask.foreach_index([&](uint i) { element_fn(mut1[i]); }); + }; + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + MutableSpan<Mut1> mut1 = params.single_mutable<Mut1>(0); + m_function(mask, mut1); + } +}; + +/** + * Generates a multi-function that outputs a constant value. + */ +template<typename T> class CustomMF_Constant : public MultiFunction { + private: + T m_value; + + public: + template<typename U> CustomMF_Constant(U &&value) : m_value(std::forward<U>(value)) + { + MFSignatureBuilder signature = this->get_builder("Constant"); + std::stringstream ss; + ss << m_value; + signature.single_output<T>(ss.str()); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + MutableSpan<T> output = params.uninitialized_single_output<T>(0); + mask.foreach_index([&](uint i) { new (&output[i]) T(m_value); }); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_BUILDER_HH__ */ diff --git a/source/blender/functions/FN_multi_function_context.hh b/source/blender/functions/FN_multi_function_context.hh new file mode 100644 index 00000000000..2fb1cc94812 --- /dev/null +++ b/source/blender/functions/FN_multi_function_context.hh @@ -0,0 +1,48 @@ +/* + * 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 __FN_MULTI_FUNCTION_CONTEXT_HH__ +#define __FN_MULTI_FUNCTION_CONTEXT_HH__ + +/** \file + * \ingroup fn + * + * An #MFContext is passed along with every call to a multi-function. Right now it does nothing, + * but it can be used for the following purposes: + * - Pass debug information up and down the function call stack. + * - Pass reusable memory buffers to sub-functions to increase performance. + * - Pass cached data to called functions. + */ + +#include "BLI_utildefines.h" + +namespace blender { +namespace fn { + +class MFContextBuilder { +}; + +class MFContext { + public: + MFContext(MFContextBuilder &UNUSED(builder)) + { + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_CONTEXT_HH__ */ diff --git a/source/blender/functions/FN_multi_function_data_type.hh b/source/blender/functions/FN_multi_function_data_type.hh new file mode 100644 index 00000000000..1a7b179c6ae --- /dev/null +++ b/source/blender/functions/FN_multi_function_data_type.hh @@ -0,0 +1,127 @@ +/* + * 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 __FN_MULTI_FUNCTION_DATA_TYPE_HH__ +#define __FN_MULTI_FUNCTION_DATA_TYPE_HH__ + +/** \file + * \ingroup fn + * + * A MFDataType describes what type of data a multi-function gets as input, outputs or mutates. + * Currently, only individual elements or vectors of elements are supported. Adding more data types + * is possible when necessary. + */ + +#include "FN_cpp_type.hh" + +namespace blender { +namespace fn { + +class MFDataType { + public: + enum Category { + Single, + Vector, + }; + + private: + Category m_category; + const CPPType *m_type; + + MFDataType(Category category, const CPPType &type) : m_category(category), m_type(&type) + { + } + + public: + MFDataType() = default; + + static MFDataType ForSingle(const CPPType &type) + { + return MFDataType(Single, type); + } + + static MFDataType ForVector(const CPPType &type) + { + return MFDataType(Vector, type); + } + + template<typename T> static MFDataType ForSingle() + { + return MFDataType::ForSingle(CPPType::get<T>()); + } + + template<typename T> static MFDataType ForVector() + { + return MFDataType::ForVector(CPPType::get<T>()); + } + + bool is_single() const + { + return m_category == Single; + } + + bool is_vector() const + { + return m_category == Vector; + } + + Category category() const + { + return m_category; + } + + const CPPType &single_type() const + { + BLI_assert(this->is_single()); + return *m_type; + } + + const CPPType &vector_base_type() const + { + BLI_assert(this->is_vector()); + return *m_type; + } + + friend bool operator==(const MFDataType &a, const MFDataType &b); + friend bool operator!=(const MFDataType &a, const MFDataType &b); + + std::string to_string() const + { + switch (m_category) { + case Single: + return m_type->name(); + case Vector: + return m_type->name() + " Vector"; + } + BLI_assert(false); + return ""; + } +}; + +inline bool operator==(const MFDataType &a, const MFDataType &b) +{ + return a.m_category == b.m_category && a.m_type == b.m_type; +} + +inline bool operator!=(const MFDataType &a, const MFDataType &b) +{ + return !(a == b); +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_DATA_TYPE_HH__ */ diff --git a/source/blender/functions/FN_multi_function_network.hh b/source/blender/functions/FN_multi_function_network.hh new file mode 100644 index 00000000000..bb0c870746b --- /dev/null +++ b/source/blender/functions/FN_multi_function_network.hh @@ -0,0 +1,495 @@ +/* + * 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 __FN_MULTI_FUNCTION_NETWORK_HH__ +#define __FN_MULTI_FUNCTION_NETWORK_HH__ + +/** \file + * \ingroup fn + * + * A multi-function network (`MFNetwork`) allows you to connect multiple multi-functions. The + * `MFNetworkEvaluator` is a multi-function that wraps an entire network into a new multi-function + * (which can be used in another network and so on). + * + * A MFNetwork is a graph data structure with two kinds of nodes: + * - MFFunctionNode: Represents a multi-function. Its input and output sockets correspond to + * parameters of the referenced multi-function. + * - MFDummyNode: Does not reference a multi-function. Instead it just has sockets that can be + * used to represent node group inputs and outputs. + * + * Links represent data flow. Unlinked input sockets have no value. In order to execute a function + * node, all its inputs have to be connected to something. + * + * Links are only allowed between sockets with the exact same MFDataType. There are no implicit + * conversions. + * + * Every input and output parameter of a multi-function corresponds to exactly one input or output + * socket respectively. A multiple parameter belongs to exactly one input AND one output socket. + * + * There is an .to_dot() method that generates a graph in dot format for debugging purposes. + */ + +#include "FN_multi_function.hh" + +#include "BLI_vector_set.hh" + +namespace blender { +namespace fn { + +class MFNode; +class MFFunctionNode; +class MFDummyNode; +class MFSocket; +class MFInputSocket; +class MFOutputSocket; +class MFNetwork; + +class MFNode : NonCopyable, NonMovable { + protected: + MFNetwork *m_network; + Span<MFInputSocket *> m_inputs; + Span<MFOutputSocket *> m_outputs; + bool m_is_dummy; + uint m_id; + + friend MFNetwork; + + public: + StringRefNull name() const; + + uint id() const; + + MFNetwork &network(); + const MFNetwork &network() const; + + bool is_dummy() const; + bool is_function() const; + + MFDummyNode &as_dummy(); + const MFDummyNode &as_dummy() const; + + MFFunctionNode &as_function(); + const MFFunctionNode &as_function() const; + + MFInputSocket &input(uint index); + const MFInputSocket &input(uint index) const; + + MFOutputSocket &output(uint index); + const MFOutputSocket &output(uint index) const; + + Span<MFInputSocket *> inputs(); + Span<const MFInputSocket *> inputs() const; + + Span<MFOutputSocket *> outputs(); + Span<const MFOutputSocket *> outputs() const; + + template<typename FuncT> void foreach_origin_socket(const FuncT &func) const; + + bool all_inputs_have_origin() const; + + private: + void destruct_sockets(); +}; + +class MFFunctionNode : public MFNode { + private: + const MultiFunction *m_function; + Span<uint> m_input_param_indices; + Span<uint> m_output_param_indices; + + friend MFNetwork; + + public: + StringRefNull name() const; + + const MultiFunction &function() const; + + const MFInputSocket &input_for_param(uint param_index) const; + const MFOutputSocket &output_for_param(uint param_index) const; +}; + +class MFDummyNode : public MFNode { + private: + StringRefNull m_name; + MutableSpan<StringRefNull> m_input_names; + MutableSpan<StringRefNull> m_output_names; + + friend MFNetwork; + + public: + StringRefNull name() const; + + Span<StringRefNull> input_names() const; + Span<StringRefNull> output_names() const; +}; + +class MFSocket : NonCopyable, NonMovable { + protected: + MFNode *m_node; + bool m_is_output; + uint m_index; + MFDataType m_data_type; + uint m_id; + StringRefNull m_name; + + friend MFNetwork; + + public: + StringRefNull name() const; + + uint id() const; + + const MFDataType &data_type() const; + + MFNode &node(); + const MFNode &node() const; + + bool is_input() const; + bool is_output() const; + + MFInputSocket &as_input(); + const MFInputSocket &as_input() const; + + MFOutputSocket &as_output(); + const MFOutputSocket &as_output() const; +}; + +class MFInputSocket : public MFSocket { + private: + MFOutputSocket *m_origin; + + friend MFNetwork; + + public: + MFOutputSocket *origin(); + const MFOutputSocket *origin() const; +}; + +class MFOutputSocket : public MFSocket { + private: + Vector<MFInputSocket *, 1> m_targets; + + friend MFNetwork; + + public: + Span<MFInputSocket *> targets(); + Span<const MFInputSocket *> targets() const; +}; + +class MFNetwork : NonCopyable, NonMovable { + private: + LinearAllocator<> m_allocator; + + VectorSet<MFFunctionNode *> m_function_nodes; + VectorSet<MFDummyNode *> m_dummy_nodes; + + Vector<MFNode *> m_node_or_null_by_id; + Vector<MFSocket *> m_socket_or_null_by_id; + + public: + MFNetwork() = default; + ~MFNetwork(); + + MFFunctionNode &add_function(const MultiFunction &function); + MFDummyNode &add_dummy(StringRef name, + Span<MFDataType> input_types, + Span<MFDataType> output_types, + Span<StringRef> input_names, + Span<StringRef> output_names); + void add_link(MFOutputSocket &from, MFInputSocket &to); + + MFOutputSocket &add_input(StringRef name, MFDataType data_type); + MFInputSocket &add_output(StringRef name, MFDataType data_type); + + void relink(MFOutputSocket &old_output, MFOutputSocket &new_output); + + void remove(MFNode &node); + + uint max_socket_id() const; + + std::string to_dot() const; +}; + +/* -------------------------------------------------------------------- + * MFNode inline methods. + */ + +inline StringRefNull MFNode::name() const +{ + if (m_is_dummy) { + return this->as_dummy().name(); + } + else { + return this->as_function().name(); + } +} + +inline uint MFNode::id() const +{ + return m_id; +} + +inline MFNetwork &MFNode::network() +{ + return *m_network; +} + +inline const MFNetwork &MFNode::network() const +{ + return *m_network; +} + +inline bool MFNode::is_dummy() const +{ + return m_is_dummy; +} + +inline bool MFNode::is_function() const +{ + return !m_is_dummy; +} + +inline MFDummyNode &MFNode::as_dummy() +{ + BLI_assert(m_is_dummy); + return *(MFDummyNode *)this; +} + +inline const MFDummyNode &MFNode::as_dummy() const +{ + BLI_assert(m_is_dummy); + return *(const MFDummyNode *)this; +} + +inline MFFunctionNode &MFNode::as_function() +{ + BLI_assert(!m_is_dummy); + return *(MFFunctionNode *)this; +} + +inline const MFFunctionNode &MFNode::as_function() const +{ + BLI_assert(!m_is_dummy); + return *(const MFFunctionNode *)this; +} + +inline MFInputSocket &MFNode::input(uint index) +{ + return *m_inputs[index]; +} + +inline const MFInputSocket &MFNode::input(uint index) const +{ + return *m_inputs[index]; +} + +inline MFOutputSocket &MFNode::output(uint index) +{ + return *m_outputs[index]; +} + +inline const MFOutputSocket &MFNode::output(uint index) const +{ + return *m_outputs[index]; +} + +inline Span<MFInputSocket *> MFNode::inputs() +{ + return m_inputs; +} + +inline Span<const MFInputSocket *> MFNode::inputs() const +{ + return m_inputs; +} + +inline Span<MFOutputSocket *> MFNode::outputs() +{ + return m_outputs; +} + +inline Span<const MFOutputSocket *> MFNode::outputs() const +{ + return m_outputs; +} + +template<typename FuncT> void MFNode::foreach_origin_socket(const FuncT &func) const +{ + for (const MFInputSocket *socket : m_inputs) { + const MFOutputSocket *origin = socket->origin(); + if (origin != nullptr) { + func(*origin); + } + } +} + +inline bool MFNode::all_inputs_have_origin() const +{ + for (const MFInputSocket *socket : m_inputs) { + if (socket->origin() == nullptr) { + return false; + } + } + return true; +} + +/* -------------------------------------------------------------------- + * MFFunctionNode inline methods. + */ + +inline StringRefNull MFFunctionNode::name() const +{ + return m_function->name(); +} + +inline const MultiFunction &MFFunctionNode::function() const +{ + return *m_function; +} + +inline const MFInputSocket &MFFunctionNode::input_for_param(uint param_index) const +{ + return this->input(m_input_param_indices.first_index(param_index)); +} + +inline const MFOutputSocket &MFFunctionNode::output_for_param(uint param_index) const +{ + return this->output(m_output_param_indices.first_index(param_index)); +} + +/* -------------------------------------------------------------------- + * MFDummyNode inline methods. + */ + +inline StringRefNull MFDummyNode::name() const +{ + return m_name; +} + +inline Span<StringRefNull> MFDummyNode::input_names() const +{ + return m_input_names; +} + +inline Span<StringRefNull> MFDummyNode::output_names() const +{ + return m_output_names; +} + +/* -------------------------------------------------------------------- + * MFSocket inline methods. + */ + +inline StringRefNull MFSocket::name() const +{ + return m_name; +} + +inline uint MFSocket::id() const +{ + return m_id; +} + +inline const MFDataType &MFSocket::data_type() const +{ + return m_data_type; +} + +inline MFNode &MFSocket::node() +{ + return *m_node; +} + +inline const MFNode &MFSocket::node() const +{ + return *m_node; +} + +inline bool MFSocket::is_input() const +{ + return !m_is_output; +} + +inline bool MFSocket::is_output() const +{ + return m_is_output; +} + +inline MFInputSocket &MFSocket::as_input() +{ + BLI_assert(this->is_input()); + return *(MFInputSocket *)this; +} + +inline const MFInputSocket &MFSocket::as_input() const +{ + BLI_assert(this->is_input()); + return *(const MFInputSocket *)this; +} + +inline MFOutputSocket &MFSocket::as_output() +{ + BLI_assert(this->is_output()); + return *(MFOutputSocket *)this; +} + +inline const MFOutputSocket &MFSocket::as_output() const +{ + BLI_assert(this->is_output()); + return *(const MFOutputSocket *)this; +} + +/* -------------------------------------------------------------------- + * MFInputSocket inline methods. + */ + +inline MFOutputSocket *MFInputSocket::origin() +{ + return m_origin; +} + +inline const MFOutputSocket *MFInputSocket::origin() const +{ + return m_origin; +} + +/* -------------------------------------------------------------------- + * MFOutputSocket inline methods. + */ + +inline Span<MFInputSocket *> MFOutputSocket::targets() +{ + return m_targets; +} + +inline Span<const MFInputSocket *> MFOutputSocket::targets() const +{ + return m_targets.as_span(); +} + +/* -------------------------------------------------------------------- + * MFNetwork inline methods. + */ + +inline uint MFNetwork::max_socket_id() const +{ + return m_socket_or_null_by_id.size() - 1; +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_NETWORK_HH__ */ diff --git a/source/blender/functions/FN_multi_function_network_evaluation.hh b/source/blender/functions/FN_multi_function_network_evaluation.hh new file mode 100644 index 00000000000..85ccd1361ef --- /dev/null +++ b/source/blender/functions/FN_multi_function_network_evaluation.hh @@ -0,0 +1,66 @@ +/* + * 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 __FN_MULTI_FUNCTION_NETWORK_EVALUATION_HH__ +#define __FN_MULTI_FUNCTION_NETWORK_EVALUATION_HH__ + +/** \file + * \ingroup fn + */ + +#include "FN_multi_function_network.hh" + +namespace blender { +namespace fn { + +class MFNetworkEvaluationStorage; + +class MFNetworkEvaluator : public MultiFunction { + private: + Vector<const MFOutputSocket *> m_inputs; + Vector<const MFInputSocket *> m_outputs; + + public: + MFNetworkEvaluator(Vector<const MFOutputSocket *> inputs, Vector<const MFInputSocket *> outputs); + + void call(IndexMask mask, MFParams params, MFContext context) const override; + + private: + using Storage = MFNetworkEvaluationStorage; + + void copy_inputs_to_storage(MFParams params, Storage &storage) const; + void copy_outputs_to_storage( + MFParams params, + Storage &storage, + Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const; + + void evaluate_network_to_compute_outputs(MFContext &global_context, Storage &storage) const; + + void evaluate_function(MFContext &global_context, + const MFFunctionNode &function_node, + Storage &storage) const; + + bool can_do_single_value_evaluation(const MFFunctionNode &function_node, Storage &storage) const; + + void initialize_remaining_outputs(MFParams params, + Storage &storage, + Span<const MFInputSocket *> remaining_outputs) const; +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_NETWORK_EVALUATION_HH__ */ diff --git a/source/blender/functions/FN_multi_function_param_type.hh b/source/blender/functions/FN_multi_function_param_type.hh new file mode 100644 index 00000000000..d89c13505f9 --- /dev/null +++ b/source/blender/functions/FN_multi_function_param_type.hh @@ -0,0 +1,165 @@ +/* + * 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 __FN_MULTI_FUNCTION_PARAM_TYPE_HH__ +#define __FN_MULTI_FUNCTION_PARAM_TYPE_HH__ + +/** \file + * \ingroup fn + * + * A multi-function has an arbitrary amount of parameters. Every parameter belongs to one of three + * interface types: + * - Input: An input parameter is readonly inside the function. The values have to be provided by + * the caller. + * - Output: An output parameter has to be initialized by the function. However, the caller + * provides the memory where the data has to be constructed. + * - Mutable: A mutable parameter can be considered to be an input and output. The caller has to + * initialize the data, but the function is allowed to modify it. + * + * Furthermore, every parameter has a MFDataType that describes what kind of data is being passed + * around. + */ + +#include "FN_multi_function_data_type.hh" + +namespace blender { +namespace fn { + +class MFParamType { + public: + enum InterfaceType { + Input, + Output, + Mutable, + }; + + enum Category { + SingleInput, + VectorInput, + SingleOutput, + VectorOutput, + SingleMutable, + VectorMutable, + }; + + private: + InterfaceType m_interface_type; + MFDataType m_data_type; + + public: + MFParamType(InterfaceType interface_type, MFDataType data_type) + : m_interface_type(interface_type), m_data_type(data_type) + { + } + + static MFParamType ForSingleInput(const CPPType &type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorInput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForVector(base_type)); + } + + static MFParamType ForSingleOutput(const CPPType &type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorOutput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForVector(base_type)); + } + + static MFParamType ForMutableSingle(const CPPType &type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForSingle(type)); + } + + static MFParamType ForMutableVector(const CPPType &base_type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForVector(base_type)); + } + + MFDataType data_type() const + { + return m_data_type; + } + + InterfaceType interface_type() const + { + return m_interface_type; + } + + Category category() const + { + switch (m_data_type.category()) { + case MFDataType::Single: { + switch (m_interface_type) { + case Input: + return SingleInput; + case Output: + return SingleOutput; + case Mutable: + return SingleMutable; + } + break; + } + case MFDataType::Vector: { + switch (m_interface_type) { + case Input: + return VectorInput; + case Output: + return VectorOutput; + case Mutable: + return VectorMutable; + } + break; + } + } + BLI_assert(false); + return SingleInput; + } + + bool is_input_or_mutable() const + { + return ELEM(m_interface_type, Input, Mutable); + } + + bool is_output_or_mutable() const + { + return ELEM(m_interface_type, Output, Mutable); + } + + friend bool operator==(const MFParamType &a, const MFParamType &b); + friend bool operator!=(const MFParamType &a, const MFParamType &b); +}; + +inline bool operator==(const MFParamType &a, const MFParamType &b) +{ + return a.m_interface_type == b.m_interface_type && a.m_data_type == b.m_data_type; +} + +inline bool operator!=(const MFParamType &a, const MFParamType &b) +{ + return !(a == b); +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_PARAM_TYPE_HH__ */ diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh new file mode 100644 index 00000000000..6a0eb698250 --- /dev/null +++ b/source/blender/functions/FN_multi_function_params.hh @@ -0,0 +1,240 @@ +/* + * 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 __FN_MULTI_FUNCTION_PARAMS_HH__ +#define __FN_MULTI_FUNCTION_PARAMS_HH__ + +/** \file + * \ingroup fn + * + * This file provides an MFParams and MFParamsBuilder structure. + * + * `MFParamsBuilder` is used by a function caller to be prepare all parameters that are passed into + * the function. `MFParams` is then used inside the called function to access the parameters. + */ + +#include "FN_generic_vector_array.hh" +#include "FN_multi_function_signature.hh" + +namespace blender { +namespace fn { + +class MFParamsBuilder { + private: + const MFSignature *m_signature; + uint m_min_array_size; + Vector<GVSpan> m_virtual_spans; + Vector<GMutableSpan> m_mutable_spans; + Vector<GVArraySpan> m_virtual_array_spans; + Vector<GVectorArray *> m_vector_arrays; + + friend class MFParams; + + public: + MFParamsBuilder(const MFSignature &signature, uint min_array_size) + : m_signature(&signature), m_min_array_size(min_array_size) + { + } + + MFParamsBuilder(const class MultiFunction &fn, uint min_array_size); + + template<typename T> void add_readonly_single_input(const T *value) + { + this->add_readonly_single_input( + GVSpan::FromSingle(CPPType::get<T>(), value, m_min_array_size)); + } + void add_readonly_single_input(GVSpan ref) + { + this->assert_current_param_type(MFParamType::ForSingleInput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_virtual_spans.append(ref); + } + + void add_readonly_vector_input(GVArraySpan ref) + { + this->assert_current_param_type(MFParamType::ForVectorInput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_virtual_array_spans.append(ref); + } + + void add_uninitialized_single_output(GMutableSpan ref) + { + this->assert_current_param_type(MFParamType::ForSingleOutput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_mutable_spans.append(ref); + } + + void add_vector_output(GVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForVectorOutput(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + void add_single_mutable(GMutableSpan ref) + { + this->assert_current_param_type(MFParamType::ForMutableSingle(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_mutable_spans.append(ref); + } + + void add_vector_mutable(GVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForMutableVector(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + GMutableSpan computed_array(uint param_index) + { + BLI_assert(ELEM(m_signature->param_types[param_index].category(), + MFParamType::SingleOutput, + MFParamType::SingleMutable)); + uint data_index = m_signature->data_index(param_index); + return m_mutable_spans[data_index]; + } + + GVectorArray &computed_vector_array(uint param_index) + { + BLI_assert(ELEM(m_signature->param_types[param_index].category(), + MFParamType::VectorOutput, + MFParamType::VectorMutable)); + uint data_index = m_signature->data_index(param_index); + return *m_vector_arrays[data_index]; + } + + private: + void assert_current_param_type(MFParamType param_type) + { + UNUSED_VARS_NDEBUG(param_type); +#ifdef DEBUG + uint param_index = this->current_param_index(); + MFParamType expected_type = m_signature->param_types[param_index]; + BLI_assert(expected_type == param_type); +#endif + } + + uint current_param_index() const + { + return m_virtual_spans.size() + m_mutable_spans.size() + m_virtual_array_spans.size() + + m_vector_arrays.size(); + } +}; + +class MFParams { + private: + MFParamsBuilder *m_builder; + + public: + MFParams(MFParamsBuilder &builder) : m_builder(&builder) + { + } + + template<typename T> VSpan<T> readonly_single_input(uint param_index, StringRef name = "") + { + return this->readonly_single_input(param_index, name).typed<T>(); + } + GVSpan readonly_single_input(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleInput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_virtual_spans[data_index]; + } + + template<typename T> + MutableSpan<T> uninitialized_single_output(uint param_index, StringRef name = "") + { + return this->uninitialized_single_output(param_index, name).typed<T>(); + } + GMutableSpan uninitialized_single_output(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleOutput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_mutable_spans[data_index]; + } + + template<typename T> VArraySpan<T> readonly_vector_input(uint param_index, StringRef name = "") + { + return this->readonly_vector_input(param_index, name).typed<T>(); + } + GVArraySpan readonly_vector_input(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorInput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_virtual_array_spans[data_index]; + } + + template<typename T> GVectorArrayRef<T> vector_output(uint param_index, StringRef name = "") + { + return this->vector_output(param_index, name).typed<T>(); + } + GVectorArray &vector_output(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorOutput); + uint data_index = m_builder->m_signature->data_index(param_index); + return *m_builder->m_vector_arrays[data_index]; + } + + template<typename T> MutableSpan<T> single_mutable(uint param_index, StringRef name = "") + { + return this->single_mutable(param_index, name).typed<T>(); + } + GMutableSpan single_mutable(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleMutable); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_mutable_spans[data_index]; + } + + template<typename T> GVectorArrayRef<T> vector_mutable(uint param_index, StringRef name = "") + { + return this->vector_mutable(param_index, name).typed<T>(); + } + GVectorArray &vector_mutable(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorMutable); + uint data_index = m_builder->m_signature->data_index(param_index); + return *m_builder->m_vector_arrays[data_index]; + } + + private: + void assert_correct_param(uint param_index, StringRef name, MFParamType param_type) + { + UNUSED_VARS_NDEBUG(param_index, name, param_type); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[param_index] == param_type); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[param_index] == name); + } +#endif + } + + void assert_correct_param(uint param_index, StringRef name, MFParamType::Category category) + { + UNUSED_VARS_NDEBUG(param_index, name, category); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[param_index].category() == category); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[param_index] == name); + } +#endif + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_PARAMS_HH__ */ diff --git a/source/blender/functions/FN_multi_function_signature.hh b/source/blender/functions/FN_multi_function_signature.hh new file mode 100644 index 00000000000..77a8ce14c03 --- /dev/null +++ b/source/blender/functions/FN_multi_function_signature.hh @@ -0,0 +1,166 @@ +/* + * 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 __FN_MULTI_FUNCTION_SIGNATURE_HH__ +#define __FN_MULTI_FUNCTION_SIGNATURE_HH__ + +/** \file + * \ingroup fn + * + * The signature of a multi-function contains the functions name and expected parameters. New + * signatures should be build using the MFSignatureBuilder class. + */ + +#include "FN_multi_function_param_type.hh" + +#include "BLI_vector.hh" + +namespace blender { +namespace fn { + +struct MFSignature { + std::string function_name; + /* Use RawAllocator so that a MultiFunction can have static storage duration. */ + Vector<std::string, 4, RawAllocator> param_names; + Vector<MFParamType, 4, RawAllocator> param_types; + Vector<uint, 4, RawAllocator> param_data_indices; + + uint data_index(uint param_index) const + { + return param_data_indices[param_index]; + } +}; + +class MFSignatureBuilder { + private: + MFSignature &m_data; + uint m_span_count = 0; + uint m_virtual_span_count = 0; + uint m_virtual_array_span_count = 0; + uint m_vector_array_count = 0; + + public: + MFSignatureBuilder(MFSignature &data) : m_data(data) + { + BLI_assert(data.param_names.is_empty()); + BLI_assert(data.param_types.is_empty()); + BLI_assert(data.param_data_indices.is_empty()); + } + + /* Input Param Types */ + + template<typename T> void single_input(StringRef name) + { + this->single_input(name, CPPType::get<T>()); + } + void single_input(StringRef name, const CPPType &type) + { + this->input(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_input(StringRef name) + { + this->vector_input(name, CPPType::get<T>()); + } + void vector_input(StringRef name, const CPPType &base_type) + { + this->input(name, MFDataType::ForVector(base_type)); + } + void input(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Input, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_virtual_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_virtual_array_span_count++); + break; + } + } + + /* Output Param Types */ + + template<typename T> void single_output(StringRef name) + { + this->single_output(name, CPPType::get<T>()); + } + void single_output(StringRef name, const CPPType &type) + { + this->output(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_output(StringRef name) + { + this->vector_output(name, CPPType::get<T>()); + } + void vector_output(StringRef name, const CPPType &base_type) + { + this->output(name, MFDataType::ForVector(base_type)); + } + void output(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Output, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } + + /* Mutable Param Types */ + + template<typename T> void single_mutable(StringRef name) + { + this->single_mutable(name, CPPType::get<T>()); + } + void single_mutable(StringRef name, const CPPType &type) + { + this->mutable_(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_mutable(StringRef name) + { + this->vector_mutable(name, CPPType::get<T>()); + } + void vector_mutable(StringRef name, const CPPType &base_type) + { + this->mutable_(name, MFDataType::ForVector(base_type)); + } + void mutable_(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Mutable, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_SIGNATURE_HH__ */ diff --git a/source/blender/functions/FN_spans.hh b/source/blender/functions/FN_spans.hh new file mode 100644 index 00000000000..b4607527fb5 --- /dev/null +++ b/source/blender/functions/FN_spans.hh @@ -0,0 +1,404 @@ +/* + * 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 __FN_SPANS_HH__ +#define __FN_SPANS_HH__ + +/** \file + * \ingroup fn + * + * This file implements multiple variants of a span for different use cases. There are two + * requirements of the function system that require span implementations other from + * blender::Span<T>. + * 1. The function system works with a run-time type system (see `CPPType`). Therefore, it has to + * deal with types in a generic way. The type of a Span<T> has to be known at compile time. + * 2. Span<T> expects an underlying memory buffer that is as large as the span. However, sometimes + * we can save some memory and processing when we know that all elements are the same. + * + * The first requirement is solved with generic spans, which use the "G" prefix. Those + * store a CPPType instance to keep track of the type that is currently stored. + * + * The second requirement is solved with virtual spans. A virtual span behaves like a normal span, + * but it might not be backed up by an actual array. Elements in a virtual span are always + * immutable. + * + * Different use cases require different combinations of these properties and therefore use + * different data structures. + */ + +#include "BLI_span.hh" + +#include "FN_cpp_type.hh" + +namespace blender { +namespace fn { + +/** + * A generic span. It behaves just like a blender::Span<T>, but the type is only known at run-time. + */ +class GSpan { + private: + const CPPType *m_type; + const void *m_buffer; + uint m_size; + + public: + GSpan(const CPPType &type, const void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_size(size) + { + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + GSpan(const CPPType &type) : GSpan(type, nullptr, 0) + { + } + + template<typename T> + GSpan(Span<T> array) : GSpan(CPPType::get<T>(), (const void *)array.data(), array.size()) + { + } + + const CPPType &type() const + { + return *m_type; + } + + bool is_empty() const + { + return m_size == 0; + } + + uint size() const + { + return m_size; + } + + const void *buffer() const + { + return m_buffer; + } + + const void *operator[](uint index) const + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> Span<T> typed() const + { + BLI_assert(m_type->is<T>()); + return Span<T>((const T *)m_buffer, m_size); + } +}; + +/** + * A generic mutable span. It behaves just like a blender::MutableSpan<T>, but the type is only + * known at run-time. + */ +class GMutableSpan { + private: + const CPPType *m_type; + void *m_buffer; + uint m_size; + + public: + GMutableSpan(const CPPType &type, void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_size(size) + { + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + GMutableSpan(const CPPType &type) : GMutableSpan(type, nullptr, 0) + { + } + + template<typename T> + GMutableSpan(MutableSpan<T> array) + : GMutableSpan(CPPType::get<T>(), (void *)array.begin(), array.size()) + { + } + + operator GSpan() const + { + return GSpan(*m_type, m_buffer, m_size); + } + + const CPPType &type() const + { + return *m_type; + } + + bool is_empty() const + { + return m_size == 0; + } + + uint size() const + { + return m_size; + } + + void *buffer() + { + return m_buffer; + } + + void *operator[](uint index) + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> MutableSpan<T> typed() + { + BLI_assert(m_type->is<T>()); + return MutableSpan<T>((T *)m_buffer, m_size); + } +}; + +enum class VSpanCategory { + Single, + FullArray, + FullPointerArray, +}; + +template<typename T> struct VSpanBase { + protected: + uint m_virtual_size; + VSpanCategory m_category; + union { + struct { + const T *data; + } single; + struct { + const T *data; + } full_array; + struct { + const T *const *data; + } full_pointer_array; + } m_data; + + public: + bool is_single_element() const + { + switch (m_category) { + case VSpanCategory::Single: + return true; + case VSpanCategory::FullArray: + return m_virtual_size == 1; + case VSpanCategory::FullPointerArray: + return m_virtual_size == 1; + } + BLI_assert(false); + return false; + } + + bool is_empty() const + { + return this->m_virtual_size == 0; + } + + uint size() const + { + return this->m_virtual_size; + } +}; + +BLI_STATIC_ASSERT((sizeof(VSpanBase<void>) == sizeof(VSpanBase<AlignedBuffer<64, 64>>)), + "should not depend on the size of the type"); + +/** + * A virtual span. It behaves like a blender::Span<T>, but might not be backed up by an actual + * array. + */ +template<typename T> class VSpan : public VSpanBase<T> { + friend class GVSpan; + + VSpan(const VSpanBase<void> &values) + { + memcpy(this, &values, sizeof(VSpanBase<void>)); + } + + public: + VSpan() + { + this->m_virtual_size = 0; + this->m_category = VSpanCategory::FullArray; + this->m_data.full_array.data = nullptr; + } + + VSpan(Span<T> values) + { + this->m_virtual_size = values.size(); + this->m_category = VSpanCategory::FullArray; + this->m_data.full_array.data = values.begin(); + } + + VSpan(MutableSpan<T> values) : VSpan(Span<T>(values)) + { + } + + VSpan(Span<const T *> values) + { + this->m_virtual_size = values.size(); + this->m_category = VSpanCategory::FullPointerArray; + this->m_data.full_pointer_array.data = values.begin(); + } + + static VSpan FromSingle(const T *value, uint virtual_size) + { + VSpan ref; + ref.m_virtual_size = virtual_size; + ref.m_category = VSpanCategory::Single; + ref.m_data.single.data = value; + return ref; + } + + const T &operator[](uint index) const + { + BLI_assert(index < this->m_virtual_size); + switch (this->m_category) { + case VSpanCategory::Single: + return *this->m_data.single.data; + case VSpanCategory::FullArray: + return this->m_data.full_array.data[index]; + case VSpanCategory::FullPointerArray: + return *this->m_data.full_pointer_array.data[index]; + } + BLI_assert(false); + return *this->m_data.single.data; + } +}; + +/** + * A generic virtual span. It behaves like a blender::Span<T>, but the type is only known at + * run-time and it might not be backed up by an actual array. + */ +class GVSpan : public VSpanBase<void> { + private: + const CPPType *m_type; + + GVSpan() = default; + + public: + GVSpan(const CPPType &type) + { + this->m_type = &type; + this->m_virtual_size = 0; + this->m_category = VSpanCategory::FullArray; + this->m_data.full_array.data = nullptr; + } + + GVSpan(GSpan values) + { + this->m_type = &values.type(); + this->m_virtual_size = values.size(); + this->m_category = VSpanCategory::FullArray; + this->m_data.full_array.data = values.buffer(); + } + + GVSpan(GMutableSpan values) : GVSpan(GSpan(values)) + { + } + + template<typename T> GVSpan(const VSpanBase<T> &values) + { + this->m_type = &CPPType::get<T>(); + memcpy(this, &values, sizeof(VSpanBase<void>)); + } + + template<typename T> GVSpan(Span<T> values) : GVSpan(GSpan(values)) + { + } + + template<typename T> GVSpan(MutableSpan<T> values) : GVSpan(GSpan(values)) + { + } + + static GVSpan FromSingle(const CPPType &type, const void *value, uint virtual_size) + { + GVSpan ref; + ref.m_type = &type; + ref.m_virtual_size = virtual_size; + ref.m_category = VSpanCategory::Single; + ref.m_data.single.data = value; + return ref; + } + + static GVSpan FromFullPointerArray(const CPPType &type, const void *const *values, uint size) + { + GVSpan ref; + ref.m_type = &type; + ref.m_virtual_size = size; + ref.m_category = VSpanCategory::FullPointerArray; + ref.m_data.full_pointer_array.data = values; + return ref; + } + + const CPPType &type() const + { + return *this->m_type; + } + + const void *operator[](uint index) const + { + BLI_assert(index < this->m_virtual_size); + switch (this->m_category) { + case VSpanCategory::Single: + return this->m_data.single.data; + case VSpanCategory::FullArray: + return POINTER_OFFSET(this->m_data.full_array.data, index * m_type->size()); + case VSpanCategory::FullPointerArray: + return this->m_data.full_pointer_array.data[index]; + } + BLI_assert(false); + return this->m_data.single.data; + } + + template<typename T> VSpan<T> typed() const + { + BLI_assert(m_type->is<T>()); + return VSpan<T>(*this); + } + + const void *as_single_element() const + { + BLI_assert(this->is_single_element()); + return (*this)[0]; + } + + void materialize_to_uninitialized(void *dst) const + { + this->materialize_to_uninitialized(IndexRange(m_virtual_size), dst); + } + + void materialize_to_uninitialized(IndexMask mask, void *dst) const + { + BLI_assert(this->size() >= mask.min_array_size()); + + uint element_size = m_type->size(); + for (uint i : mask) { + m_type->copy_to_uninitialized((*this)[i], POINTER_OFFSET(dst, element_size * i)); + } + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_SPANS_HH__ */ diff --git a/source/blender/functions/intern/attributes_ref.cc b/source/blender/functions/intern/attributes_ref.cc new file mode 100644 index 00000000000..dc64f571596 --- /dev/null +++ b/source/blender/functions/intern/attributes_ref.cc @@ -0,0 +1,72 @@ +/* + * 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 "FN_attributes_ref.hh" + +namespace blender { +namespace fn { + +AttributesInfoBuilder::~AttributesInfoBuilder() +{ + for (uint i : m_defaults.index_range()) { + m_types[i]->destruct(m_defaults[i]); + } +} + +void AttributesInfoBuilder::add(StringRef name, const CPPType &type, const void *default_value) +{ + if (m_names.add_as(name)) { + m_types.append(&type); + + if (default_value == nullptr) { + default_value = type.default_value(); + } + void *dst = m_allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(default_value, dst); + m_defaults.append(dst); + } + else { + /* The same name can be added more than once as long as the type is always the same. */ + BLI_assert(m_types[m_names.index_of_as(name)] == &type); + } +} + +AttributesInfo::AttributesInfo(const AttributesInfoBuilder &builder) +{ + for (uint i : builder.m_types.index_range()) { + StringRefNull name = m_allocator.copy_string(builder.m_names[i]); + const CPPType &type = *builder.m_types[i]; + const void *default_value = builder.m_defaults[i]; + + m_index_by_name.add_new(name, i); + m_name_by_index.append(name); + m_type_by_index.append(&type); + + void *dst = m_allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(default_value, dst); + m_defaults.append(dst); + } +} + +AttributesInfo::~AttributesInfo() +{ + for (uint i : m_defaults.index_range()) { + m_type_by_index[i]->destruct(m_defaults[i]); + } +} + +} // namespace fn +} // namespace blender diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc index 6339250caa5..cb4b065e5bc 100644 --- a/source/blender/functions/intern/cpp_types.cc +++ b/source/blender/functions/intern/cpp_types.cc @@ -22,7 +22,7 @@ #include "BLI_float4x4.hh" namespace blender { -namespace FN { +namespace fn { MAKE_CPP_TYPE(bool, bool) @@ -39,5 +39,5 @@ MAKE_CPP_TYPE(Color4b, blender::Color4b) MAKE_CPP_TYPE(string, std::string) -} // namespace FN +} // namespace fn } // namespace blender diff --git a/source/blender/functions/intern/multi_function.cc b/source/blender/functions/intern/multi_function.cc new file mode 100644 index 00000000000..8eb5355511d --- /dev/null +++ b/source/blender/functions/intern/multi_function.cc @@ -0,0 +1,40 @@ +/* + * 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 "FN_multi_function.hh" + +namespace blender { +namespace fn { + +class DummyMultiFunction : public MultiFunction { + public: + DummyMultiFunction() + { + this->get_builder("Dummy"); + } + + void call(IndexMask UNUSED(mask), + MFParams UNUSED(params), + MFContext UNUSED(context)) const override + { + } +}; + +static DummyMultiFunction dummy_multi_function_; +const MultiFunction &dummy_multi_function = dummy_multi_function_; + +} // namespace fn +} // namespace blender diff --git a/source/blender/functions/intern/multi_function_network.cc b/source/blender/functions/intern/multi_function_network.cc new file mode 100644 index 00000000000..93d062f3e5c --- /dev/null +++ b/source/blender/functions/intern/multi_function_network.cc @@ -0,0 +1,278 @@ +/* + * 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 "BLI_dot_export.hh" +#include "FN_multi_function_network.hh" + +namespace blender { +namespace fn { + +MFNetwork::~MFNetwork() +{ + for (MFFunctionNode *node : m_function_nodes) { + node->destruct_sockets(); + node->~MFFunctionNode(); + } + for (MFDummyNode *node : m_dummy_nodes) { + node->destruct_sockets(); + node->~MFDummyNode(); + } +} + +void MFNode::destruct_sockets() +{ + for (MFInputSocket *socket : m_inputs) { + socket->~MFInputSocket(); + } + for (MFOutputSocket *socket : m_outputs) { + socket->~MFOutputSocket(); + } +} + +/** + * Add a new function node to the network. The caller keeps the ownership of the function. The + * function should not be freed before the network. A reference to the new node is returned. The + * node is owned by the network. + */ +MFFunctionNode &MFNetwork::add_function(const MultiFunction &function) +{ + Vector<uint, 16> input_param_indices, output_param_indices; + + for (uint param_index : function.param_indices()) { + switch (function.param_type(param_index).interface_type()) { + case MFParamType::Input: { + input_param_indices.append(param_index); + break; + } + case MFParamType::Output: { + output_param_indices.append(param_index); + break; + } + case MFParamType::Mutable: { + input_param_indices.append(param_index); + output_param_indices.append(param_index); + break; + } + } + } + + MFFunctionNode &node = *m_allocator.construct<MFFunctionNode>(); + m_function_nodes.add_new(&node); + + node.m_network = this; + node.m_is_dummy = false; + node.m_id = m_node_or_null_by_id.append_and_get_index(&node); + node.m_function = &function; + node.m_input_param_indices = m_allocator.construct_array_copy<uint>(input_param_indices); + node.m_output_param_indices = m_allocator.construct_array_copy<uint>(output_param_indices); + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>( + input_param_indices.size()); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>( + output_param_indices.size()); + + for (uint i : input_param_indices.index_range()) { + uint param_index = input_param_indices[i]; + MFParamType param = function.param_type(param_index); + BLI_assert(param.is_input_or_mutable()); + + MFInputSocket &socket = *node.m_inputs[i]; + socket.m_data_type = param.data_type(); + socket.m_node = &node; + socket.m_index = i; + socket.m_is_output = false; + socket.m_name = function.param_name(param_index); + socket.m_origin = nullptr; + socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket); + } + + for (uint i : output_param_indices.index_range()) { + uint param_index = output_param_indices[i]; + MFParamType param = function.param_type(param_index); + BLI_assert(param.is_output_or_mutable()); + + MFOutputSocket &socket = *node.m_outputs[i]; + socket.m_data_type = param.data_type(); + socket.m_node = &node; + socket.m_index = i; + socket.m_is_output = true; + socket.m_name = function.param_name(param_index); + socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket); + } + + return node; +} + +/** + * Add a dummy node with the given input and output sockets. + */ +MFDummyNode &MFNetwork::add_dummy(StringRef name, + Span<MFDataType> input_types, + Span<MFDataType> output_types, + Span<StringRef> input_names, + Span<StringRef> output_names) +{ + assert_same_size(input_types, input_names); + assert_same_size(output_types, output_names); + + MFDummyNode &node = *m_allocator.construct<MFDummyNode>(); + m_dummy_nodes.add_new(&node); + + node.m_network = this; + node.m_is_dummy = true; + node.m_name = m_allocator.copy_string(name); + node.m_id = m_node_or_null_by_id.append_and_get_index(&node); + + node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>( + input_types.size()); + node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>( + output_types.size()); + + node.m_input_names = m_allocator.allocate_array<StringRefNull>(input_types.size()); + node.m_output_names = m_allocator.allocate_array<StringRefNull>(output_types.size()); + + for (uint i : input_types.index_range()) { + MFInputSocket &socket = *node.m_inputs[i]; + socket.m_data_type = input_types[i]; + socket.m_node = &node; + socket.m_index = i; + socket.m_is_output = false; + socket.m_name = m_allocator.copy_string(input_names[i]); + socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket); + node.m_input_names[i] = socket.m_name; + } + + for (uint i : output_types.index_range()) { + MFOutputSocket &socket = *node.m_outputs[i]; + socket.m_data_type = output_types[i]; + socket.m_node = &node; + socket.m_index = i; + socket.m_is_output = true; + socket.m_name = m_allocator.copy_string(output_names[i]); + socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket); + node.m_output_names[i] = socket.m_name; + } + + return node; +} + +/** + * Connect two sockets. This invokes undefined behavior if the sockets belong to different + * networks, the sockets have a different data type, or the `to` socket is connected to something + * else already. + */ +void MFNetwork::add_link(MFOutputSocket &from, MFInputSocket &to) +{ + BLI_assert(to.m_origin == nullptr); + BLI_assert(from.m_node->m_network == to.m_node->m_network); + BLI_assert(from.m_data_type == to.m_data_type); + from.m_targets.append(&to); + to.m_origin = &from; +} + +MFOutputSocket &MFNetwork::add_input(StringRef name, MFDataType data_type) +{ + return this->add_dummy(name, {}, {data_type}, {}, {name}).output(0); +} + +MFInputSocket &MFNetwork::add_output(StringRef name, MFDataType data_type) +{ + return this->add_dummy(name, {data_type}, {}, {name}, {}).input(0); +} + +void MFNetwork::relink(MFOutputSocket &old_output, MFOutputSocket &new_output) +{ + BLI_assert(&old_output != &new_output); + for (MFInputSocket *input : old_output.targets()) { + input->m_origin = &new_output; + } + new_output.m_targets.extend(old_output.m_targets); + old_output.m_targets.clear(); +} + +void MFNetwork::remove(MFNode &node) +{ + for (MFInputSocket *socket : node.m_inputs) { + if (socket->m_origin != nullptr) { + socket->m_origin->m_targets.remove_first_occurrence_and_reorder(socket); + } + m_socket_or_null_by_id[socket->m_id] = nullptr; + } + for (MFOutputSocket *socket : node.m_outputs) { + for (MFInputSocket *other : socket->m_targets) { + other->m_origin = nullptr; + } + m_socket_or_null_by_id[socket->m_id] = nullptr; + } + node.destruct_sockets(); + if (node.is_dummy()) { + MFDummyNode &dummy_node = node.as_dummy(); + dummy_node.~MFDummyNode(); + m_dummy_nodes.remove_contained(&dummy_node); + } + else { + MFFunctionNode &function_node = node.as_function(); + function_node.~MFFunctionNode(); + m_function_nodes.remove_contained(&function_node); + } + m_node_or_null_by_id[node.m_id] = nullptr; +} + +std::string MFNetwork::to_dot() const +{ + dot::DirectedGraph digraph; + digraph.set_rankdir(dot::Attr_rankdir::LeftToRight); + + Map<const MFNode *, dot::NodeWithSocketsRef> dot_nodes; + + Vector<const MFNode *> all_nodes; + all_nodes.extend(m_function_nodes.as_span()); + all_nodes.extend(m_dummy_nodes.as_span()); + + for (const MFNode *node : all_nodes) { + dot::Node &dot_node = digraph.new_node(""); + + Vector<std::string> input_names, output_names; + for (const MFInputSocket *socket : node->m_inputs) { + input_names.append(socket->name() + "(" + socket->data_type().to_string() + ")"); + } + for (const MFOutputSocket *socket : node->m_outputs) { + output_names.append(socket->name() + " (" + socket->data_type().to_string() + ")"); + } + + dot::NodeWithSocketsRef dot_node_ref{dot_node, node->name(), input_names, output_names}; + dot_nodes.add_new(node, dot_node_ref); + } + + for (const MFNode *to_node : all_nodes) { + dot::NodeWithSocketsRef to_dot_node = dot_nodes.lookup(to_node); + + for (const MFInputSocket *to_socket : to_node->m_inputs) { + const MFOutputSocket *from_socket = to_socket->m_origin; + if (from_socket != nullptr) { + const MFNode *from_node = from_socket->m_node; + dot::NodeWithSocketsRef from_dot_node = dot_nodes.lookup(from_node); + digraph.new_edge(from_dot_node.output(from_socket->m_index), + to_dot_node.input(to_socket->m_index)); + } + } + } + + return digraph.to_dot_string(); +} + +} // namespace fn +} // namespace blender diff --git a/source/blender/functions/intern/multi_function_network_evaluation.cc b/source/blender/functions/intern/multi_function_network_evaluation.cc new file mode 100644 index 00000000000..327a3a66561 --- /dev/null +++ b/source/blender/functions/intern/multi_function_network_evaluation.cc @@ -0,0 +1,1063 @@ +/* + * 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. + */ + +/** \file + * \ingroup fn + * + * The `MFNetworkEvaluator` class is a multi-function that consists of potentially many smaller + * multi-functions. When called, it traverses the underlying MFNetwork and executes the required + * function nodes. + * + * There are many possible approaches to evaluate a function network. The approach implemented + * below has the following features: + * - It does not use recursion. Those could become problematic with long node chains. + * - It can handle all existing parameter types (including mutable parameters). + * - Avoids data copies in many cases. + * - Every node is executed at most once. + * - Can compute sub-functions on a single element, when the result is the same for all elements. + * + * Possible improvements: + * - Cache and reuse buffers. + * - Use "deepest depth first" heuristic to decide which order the inputs of a node should be + * computed. This reduces the number of required temporary buffers when they are reused. + */ + +#include "FN_multi_function_network_evaluation.hh" + +#include "BLI_stack.hh" + +namespace blender { +namespace fn { + +struct Value; + +/** + * This keeps track of all the values that flow through the multi-function network. Therefore it + * maintains a mapping between output sockets and their corresponding values. Every `value` + * references some memory, that is owned either by the caller or this storage. + * + * A value can be owned by different sockets over time to avoid unnecessary copies. + */ +class MFNetworkEvaluationStorage { + private: + LinearAllocator<> m_allocator; + IndexMask m_mask; + Array<Value *> m_value_per_output_id; + uint m_min_array_size; + + public: + MFNetworkEvaluationStorage(IndexMask mask, uint max_socket_id); + ~MFNetworkEvaluationStorage(); + + /* Add the values that have been provided by the caller of the multi-function network. */ + void add_single_input_from_caller(const MFOutputSocket &socket, GVSpan virtual_span); + void add_vector_input_from_caller(const MFOutputSocket &socket, GVArraySpan virtual_array_span); + void add_single_output_from_caller(const MFOutputSocket &socket, GMutableSpan span); + void add_vector_output_from_caller(const MFOutputSocket &socket, GVectorArray &vector_array); + + /* Get input buffers for function node evaluations. */ + GVSpan get_single_input__full(const MFInputSocket &socket); + GVSpan get_single_input__single(const MFInputSocket &socket); + GVArraySpan get_vector_input__full(const MFInputSocket &socket); + GVArraySpan get_vector_input__single(const MFInputSocket &socket); + + /* Get output buffers for function node evaluations. */ + GMutableSpan get_single_output__full(const MFOutputSocket &socket); + GMutableSpan get_single_output__single(const MFOutputSocket &socket); + GVectorArray &get_vector_output__full(const MFOutputSocket &socket); + GVectorArray &get_vector_output__single(const MFOutputSocket &socket); + + /* Get mutable buffers for function node evaluations. */ + GMutableSpan get_mutable_single__full(const MFInputSocket &input, const MFOutputSocket &output); + GMutableSpan get_mutable_single__single(const MFInputSocket &input, + const MFOutputSocket &output); + GVectorArray &get_mutable_vector__full(const MFInputSocket &input, const MFOutputSocket &output); + GVectorArray &get_mutable_vector__single(const MFInputSocket &input, + const MFOutputSocket &output); + + /* Mark a node as being done with evaluation. This might free temporary buffers that are no + * longer needed. */ + void finish_node(const MFFunctionNode &node); + void finish_output_socket(const MFOutputSocket &socket); + void finish_input_socket(const MFInputSocket &socket); + + IndexMask mask() const; + bool socket_is_computed(const MFOutputSocket &socket); + bool is_same_value_for_every_index(const MFOutputSocket &socket); + bool socket_has_buffer_for_output(const MFOutputSocket &socket); +}; + +MFNetworkEvaluator::MFNetworkEvaluator(Vector<const MFOutputSocket *> inputs, + Vector<const MFInputSocket *> outputs) + : m_inputs(std::move(inputs)), m_outputs(std::move(outputs)) +{ + BLI_assert(m_outputs.size() > 0); + MFSignatureBuilder signature = this->get_builder("Function Tree"); + + for (auto socket : m_inputs) { + BLI_assert(socket->node().is_dummy()); + + MFDataType type = socket->data_type(); + switch (type.category()) { + case MFDataType::Single: + signature.single_input("Input", type.single_type()); + break; + case MFDataType::Vector: + signature.vector_input("Input", type.vector_base_type()); + break; + } + } + + for (auto socket : m_outputs) { + BLI_assert(socket->node().is_dummy()); + + MFDataType type = socket->data_type(); + switch (type.category()) { + case MFDataType::Single: + signature.single_output("Output", type.single_type()); + break; + case MFDataType::Vector: + signature.vector_output("Output", type.vector_base_type()); + break; + } + } +} + +void MFNetworkEvaluator::call(IndexMask mask, MFParams params, MFContext context) const +{ + if (mask.size() == 0) { + return; + } + + const MFNetwork &network = m_outputs[0]->node().network(); + Storage storage(mask, network.max_socket_id()); + + Vector<const MFInputSocket *> outputs_to_initialize_in_the_end; + + this->copy_inputs_to_storage(params, storage); + this->copy_outputs_to_storage(params, storage, outputs_to_initialize_in_the_end); + this->evaluate_network_to_compute_outputs(context, storage); + this->initialize_remaining_outputs(params, storage, outputs_to_initialize_in_the_end); +} + +BLI_NOINLINE void MFNetworkEvaluator::copy_inputs_to_storage(MFParams params, + Storage &storage) const +{ + for (uint input_index : m_inputs.index_range()) { + uint param_index = input_index + 0; + const MFOutputSocket &socket = *m_inputs[input_index]; + switch (socket.data_type().category()) { + case MFDataType::Single: { + GVSpan input_list = params.readonly_single_input(param_index); + storage.add_single_input_from_caller(socket, input_list); + break; + } + case MFDataType::Vector: { + GVArraySpan input_list_list = params.readonly_vector_input(param_index); + storage.add_vector_input_from_caller(socket, input_list_list); + break; + } + } + } +} + +BLI_NOINLINE void MFNetworkEvaluator::copy_outputs_to_storage( + MFParams params, + Storage &storage, + Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const +{ + for (uint output_index : m_outputs.index_range()) { + uint param_index = output_index + m_inputs.size(); + const MFInputSocket &socket = *m_outputs[output_index]; + const MFOutputSocket &origin = *socket.origin(); + + if (origin.node().is_dummy()) { + BLI_assert(m_inputs.contains(&origin)); + /* Don't overwrite input buffers. */ + outputs_to_initialize_in_the_end.append(&socket); + continue; + } + + if (storage.socket_has_buffer_for_output(origin)) { + /* When two outputs will be initialized to the same values. */ + outputs_to_initialize_in_the_end.append(&socket); + continue; + } + + switch (socket.data_type().category()) { + case MFDataType::Single: { + GMutableSpan span = params.uninitialized_single_output(param_index); + storage.add_single_output_from_caller(origin, span); + break; + } + case MFDataType::Vector: { + GVectorArray &vector_array = params.vector_output(param_index); + storage.add_vector_output_from_caller(origin, vector_array); + break; + } + } + } +} + +BLI_NOINLINE void MFNetworkEvaluator::evaluate_network_to_compute_outputs( + MFContext &global_context, Storage &storage) const +{ + Stack<const MFOutputSocket *, 32> sockets_to_compute; + for (const MFInputSocket *socket : m_outputs) { + sockets_to_compute.push(socket->origin()); + } + + Vector<const MFOutputSocket *, 32> missing_sockets; + + /* This is the main loop that traverses the MFNetwork. */ + while (!sockets_to_compute.is_empty()) { + const MFOutputSocket &socket = *sockets_to_compute.peek(); + const MFNode &node = socket.node(); + + if (storage.socket_is_computed(socket)) { + sockets_to_compute.pop(); + continue; + } + + BLI_assert(node.is_function()); + BLI_assert(node.all_inputs_have_origin()); + const MFFunctionNode &function_node = node.as_function(); + + missing_sockets.clear(); + function_node.foreach_origin_socket([&](const MFOutputSocket &origin) { + if (!storage.socket_is_computed(origin)) { + missing_sockets.append(&origin); + } + }); + + sockets_to_compute.push_multiple(missing_sockets); + + bool all_inputs_are_computed = missing_sockets.size() == 0; + if (all_inputs_are_computed) { + this->evaluate_function(global_context, function_node, storage); + sockets_to_compute.pop(); + } + } +} + +BLI_NOINLINE void MFNetworkEvaluator::evaluate_function(MFContext &global_context, + const MFFunctionNode &function_node, + Storage &storage) const +{ + const MultiFunction &function = function_node.function(); + // std::cout << "Function: " << function.name() << "\n"; + + if (this->can_do_single_value_evaluation(function_node, storage)) { + /* The function output would be the same for all elements. Therefore, it is enough to call the + * function only on a single element. This can avoid many duplicate computations. */ + MFParamsBuilder params{function, 1}; + + for (uint param_index : function.param_indices()) { + MFParamType param_type = function.param_type(param_index); + switch (param_type.category()) { + case MFParamType::SingleInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GVSpan values = storage.get_single_input__single(socket); + params.add_readonly_single_input(values); + break; + } + case MFParamType::VectorInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GVArraySpan values = storage.get_vector_input__single(socket); + params.add_readonly_vector_input(values); + break; + } + case MFParamType::SingleOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GMutableSpan values = storage.get_single_output__single(socket); + params.add_uninitialized_single_output(values); + break; + } + case MFParamType::VectorOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GVectorArray &values = storage.get_vector_output__single(socket); + params.add_vector_output(values); + break; + } + case MFParamType::SingleMutable: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GMutableSpan values = storage.get_mutable_single__single(input, output); + params.add_single_mutable(values); + break; + } + case MFParamType::VectorMutable: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GVectorArray &values = storage.get_mutable_vector__single(input, output); + params.add_vector_mutable(values); + break; + } + } + } + + function.call(IndexRange(1), params, global_context); + } + else { + MFParamsBuilder params{function, storage.mask().min_array_size()}; + + for (uint param_index : function.param_indices()) { + MFParamType param_type = function.param_type(param_index); + switch (param_type.category()) { + case MFParamType::SingleInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GVSpan values = storage.get_single_input__full(socket); + params.add_readonly_single_input(values); + break; + } + case MFParamType::VectorInput: { + const MFInputSocket &socket = function_node.input_for_param(param_index); + GVArraySpan values = storage.get_vector_input__full(socket); + params.add_readonly_vector_input(values); + break; + } + case MFParamType::SingleOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GMutableSpan values = storage.get_single_output__full(socket); + params.add_uninitialized_single_output(values); + break; + } + case MFParamType::VectorOutput: { + const MFOutputSocket &socket = function_node.output_for_param(param_index); + GVectorArray &values = storage.get_vector_output__full(socket); + params.add_vector_output(values); + break; + } + case MFParamType::SingleMutable: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GMutableSpan values = storage.get_mutable_single__full(input, output); + params.add_single_mutable(values); + break; + } + case MFParamType::VectorMutable: { + const MFInputSocket &input = function_node.input_for_param(param_index); + const MFOutputSocket &output = function_node.output_for_param(param_index); + GVectorArray &values = storage.get_mutable_vector__full(input, output); + params.add_vector_mutable(values); + break; + } + } + } + + function.call(storage.mask(), params, global_context); + } + + storage.finish_node(function_node); +} + +bool MFNetworkEvaluator::can_do_single_value_evaluation(const MFFunctionNode &function_node, + Storage &storage) const +{ + for (const MFInputSocket *socket : function_node.inputs()) { + if (!storage.is_same_value_for_every_index(*socket->origin())) { + return false; + } + } + if (storage.mask().min_array_size() >= 1) { + for (const MFOutputSocket *socket : function_node.outputs()) { + if (storage.socket_has_buffer_for_output(*socket)) { + return false; + } + } + } + return true; +} + +BLI_NOINLINE void MFNetworkEvaluator::initialize_remaining_outputs( + MFParams params, Storage &storage, Span<const MFInputSocket *> remaining_outputs) const +{ + for (const MFInputSocket *socket : remaining_outputs) { + uint param_index = m_inputs.size() + m_outputs.first_index_of(socket); + + switch (socket->data_type().category()) { + case MFDataType::Single: { + GVSpan values = storage.get_single_input__full(*socket); + GMutableSpan output_values = params.uninitialized_single_output(param_index); + values.materialize_to_uninitialized(storage.mask(), output_values.buffer()); + break; + } + case MFDataType::Vector: { + GVArraySpan values = storage.get_vector_input__full(*socket); + GVectorArray &output_values = params.vector_output(param_index); + output_values.extend(storage.mask(), values); + break; + } + } + } +} + +/* -------------------------------------------------------------------- */ +/** \name Value Types + * \{ */ + +enum class ValueType { + InputSingle, + InputVector, + OutputSingle, + OutputVector, + OwnSingle, + OwnVector, +}; + +struct Value { + ValueType type; + + Value(ValueType type) : type(type) + { + } +}; + +struct InputSingleValue : public Value { + /** This span has been provided by the code that called the multi-function network. */ + GVSpan virtual_span; + + InputSingleValue(GVSpan virtual_span) : Value(ValueType::InputSingle), virtual_span(virtual_span) + { + } +}; + +struct InputVectorValue : public Value { + /** This span has been provided by the code that called the multi-function network. */ + GVArraySpan virtual_array_span; + + InputVectorValue(GVArraySpan virtual_array_span) + : Value(ValueType::InputVector), virtual_array_span(virtual_array_span) + { + } +}; + +struct OutputValue : public Value { + bool is_computed = false; + + OutputValue(ValueType type) : Value(type) + { + } +}; + +struct OutputSingleValue : public OutputValue { + /** This span has been provided by the code that called the multi-function network. */ + GMutableSpan span; + + OutputSingleValue(GMutableSpan span) : OutputValue(ValueType::OutputSingle), span(span) + { + } +}; + +struct OutputVectorValue : public OutputValue { + /** This vector array has been provided by the code that called the multi-function network. */ + GVectorArray *vector_array; + + OutputVectorValue(GVectorArray &vector_array) + : OutputValue(ValueType::OutputVector), vector_array(&vector_array) + { + } +}; + +struct OwnSingleValue : public Value { + /** This span has been allocated during the evaluation of the multi-function network and contains + * intermediate data. It has to be freed once the network evaluation is finished. */ + GMutableSpan span; + int max_remaining_users; + bool is_single_allocated; + + OwnSingleValue(GMutableSpan span, int max_remaining_users, bool is_single_allocated) + : Value(ValueType::OwnSingle), + span(span), + max_remaining_users(max_remaining_users), + is_single_allocated(is_single_allocated) + { + } +}; + +struct OwnVectorValue : public Value { + /** This vector array has been allocated during the evaluation of the multi-function network and + * contains intermediate data. It has to be freed once the network evaluation is finished. */ + GVectorArray *vector_array; + int max_remaining_users; + + OwnVectorValue(GVectorArray &vector_array, int max_remaining_users) + : Value(ValueType::OwnVector), + vector_array(&vector_array), + max_remaining_users(max_remaining_users) + { + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Storage methods + * \{ */ + +MFNetworkEvaluationStorage::MFNetworkEvaluationStorage(IndexMask mask, uint max_socket_id) + : m_mask(mask), + m_value_per_output_id(max_socket_id + 1, nullptr), + m_min_array_size(mask.min_array_size()) +{ +} + +MFNetworkEvaluationStorage::~MFNetworkEvaluationStorage() +{ + for (Value *any_value : m_value_per_output_id) { + if (any_value == nullptr) { + continue; + } + else if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + GMutableSpan span = value->span; + const CPPType &type = span.type(); + if (value->is_single_allocated) { + type.destruct(span.buffer()); + } + else { + type.destruct_indices(span.buffer(), m_mask); + MEM_freeN(span.buffer()); + } + } + else if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + delete value->vector_array; + } + } +} + +IndexMask MFNetworkEvaluationStorage::mask() const +{ + return m_mask; +} + +bool MFNetworkEvaluationStorage::socket_is_computed(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return false; + } + if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) { + return ((OutputValue *)any_value)->is_computed; + } + return true; +} + +bool MFNetworkEvaluationStorage::is_same_value_for_every_index(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + switch (any_value->type) { + case ValueType::OwnSingle: + return ((OwnSingleValue *)any_value)->span.size() == 1; + case ValueType::OwnVector: + return ((OwnVectorValue *)any_value)->vector_array->size() == 1; + case ValueType::InputSingle: + return ((InputSingleValue *)any_value)->virtual_span.is_single_element(); + case ValueType::InputVector: + return ((InputVectorValue *)any_value)->virtual_array_span.is_single_array(); + case ValueType::OutputSingle: + return ((OutputSingleValue *)any_value)->span.size() == 1; + case ValueType::OutputVector: + return ((OutputVectorValue *)any_value)->vector_array->size() == 1; + } + BLI_assert(false); + return false; +} + +bool MFNetworkEvaluationStorage::socket_has_buffer_for_output(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return false; + } + + BLI_assert(ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)); + return true; +} + +void MFNetworkEvaluationStorage::finish_node(const MFFunctionNode &node) +{ + for (const MFInputSocket *socket : node.inputs()) { + this->finish_input_socket(*socket); + } + for (const MFOutputSocket *socket : node.outputs()) { + this->finish_output_socket(*socket); + } +} + +void MFNetworkEvaluationStorage::finish_output_socket(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + return; + } + + if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) { + ((OutputValue *)any_value)->is_computed = true; + } +} + +void MFNetworkEvaluationStorage::finish_input_socket(const MFInputSocket &socket) +{ + const MFOutputSocket &origin = *socket.origin(); + + Value *any_value = m_value_per_output_id[origin.id()]; + if (any_value == nullptr) { + /* Can happen when a value has been forward to the next node. */ + return; + } + + switch (any_value->type) { + case ValueType::InputSingle: + case ValueType::OutputSingle: + case ValueType::InputVector: + case ValueType::OutputVector: { + break; + } + case ValueType::OwnSingle: { + OwnSingleValue *value = (OwnSingleValue *)any_value; + BLI_assert(value->max_remaining_users >= 1); + value->max_remaining_users--; + if (value->max_remaining_users == 0) { + GMutableSpan span = value->span; + const CPPType &type = span.type(); + if (value->is_single_allocated) { + type.destruct(span.buffer()); + } + else { + type.destruct_indices(span.buffer(), m_mask); + MEM_freeN(span.buffer()); + } + m_value_per_output_id[origin.id()] = nullptr; + } + break; + } + case ValueType::OwnVector: { + OwnVectorValue *value = (OwnVectorValue *)any_value; + BLI_assert(value->max_remaining_users >= 1); + value->max_remaining_users--; + if (value->max_remaining_users == 0) { + delete value->vector_array; + m_value_per_output_id[origin.id()] = nullptr; + } + break; + } + } +} + +void MFNetworkEvaluationStorage::add_single_input_from_caller(const MFOutputSocket &socket, + GVSpan virtual_span) +{ + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(virtual_span.size() >= m_min_array_size); + + auto *value = m_allocator.construct<InputSingleValue>(virtual_span); + m_value_per_output_id[socket.id()] = value; +} + +void MFNetworkEvaluationStorage::add_vector_input_from_caller(const MFOutputSocket &socket, + GVArraySpan virtual_array_span) +{ + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(virtual_array_span.size() >= m_min_array_size); + + auto *value = m_allocator.construct<InputVectorValue>(virtual_array_span); + m_value_per_output_id[socket.id()] = value; +} + +void MFNetworkEvaluationStorage::add_single_output_from_caller(const MFOutputSocket &socket, + GMutableSpan span) +{ + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(span.size() >= m_min_array_size); + + auto *value = m_allocator.construct<OutputSingleValue>(span); + m_value_per_output_id[socket.id()] = value; +} + +void MFNetworkEvaluationStorage::add_vector_output_from_caller(const MFOutputSocket &socket, + GVectorArray &vector_array) +{ + BLI_assert(m_value_per_output_id[socket.id()] == nullptr); + BLI_assert(vector_array.size() >= m_min_array_size); + + auto *value = m_allocator.construct<OutputVectorValue>(vector_array); + m_value_per_output_id[socket.id()] = value; +} + +GMutableSpan MFNetworkEvaluationStorage::get_single_output__full(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().single_type(); + void *buffer = MEM_mallocN_aligned(m_min_array_size * type.size(), type.alignment(), AT); + GMutableSpan span(type, buffer, m_min_array_size); + + auto *value = m_allocator.construct<OwnSingleValue>(span, socket.targets().size(), false); + m_value_per_output_id[socket.id()] = value; + + return span; + } + else { + BLI_assert(any_value->type == ValueType::OutputSingle); + return ((OutputSingleValue *)any_value)->span; + } +} + +GMutableSpan MFNetworkEvaluationStorage::get_single_output__single(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().single_type(); + void *buffer = m_allocator.allocate(type.size(), type.alignment()); + GMutableSpan span(type, buffer, 1); + + auto *value = m_allocator.construct<OwnSingleValue>(span, socket.targets().size(), true); + m_value_per_output_id[socket.id()] = value; + + return value->span; + } + else { + BLI_assert(any_value->type == ValueType::OutputSingle); + GMutableSpan span = ((OutputSingleValue *)any_value)->span; + BLI_assert(span.size() == 1); + return span; + } +} + +GVectorArray &MFNetworkEvaluationStorage::get_vector_output__full(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().vector_base_type(); + GVectorArray *vector_array = new GVectorArray(type, m_min_array_size); + + auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.targets().size()); + m_value_per_output_id[socket.id()] = value; + + return *value->vector_array; + } + else { + BLI_assert(any_value->type == ValueType::OutputVector); + return *((OutputVectorValue *)any_value)->vector_array; + } +} + +GVectorArray &MFNetworkEvaluationStorage::get_vector_output__single(const MFOutputSocket &socket) +{ + Value *any_value = m_value_per_output_id[socket.id()]; + if (any_value == nullptr) { + const CPPType &type = socket.data_type().vector_base_type(); + GVectorArray *vector_array = new GVectorArray(type, 1); + + auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.targets().size()); + m_value_per_output_id[socket.id()] = value; + + return *value->vector_array; + } + else { + BLI_assert(any_value->type == ValueType::OutputVector); + GVectorArray &vector_array = *((OutputVectorValue *)any_value)->vector_array; + BLI_assert(vector_array.size() == 1); + return vector_array; + } +} + +GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__full(const MFInputSocket &input, + const MFOutputSocket &output) +{ + const MFOutputSocket &from = *input.origin(); + const MFOutputSocket &to = output; + const CPPType &type = from.data_type().single_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(type == to.data_type().single_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputSingle); + GMutableSpan span = ((OutputSingleValue *)to_any_value)->span; + GVSpan virtual_span = this->get_single_input__full(input); + virtual_span.materialize_to_uninitialized(m_mask, span.buffer()); + return span; + } + + if (from_any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)from_any_value; + if (value->max_remaining_users == 1 && !value->is_single_allocated) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.targets().size(); + return value->span; + } + } + + GVSpan virtual_span = this->get_single_input__full(input); + void *new_buffer = MEM_mallocN_aligned(m_min_array_size * type.size(), type.alignment(), AT); + GMutableSpan new_array_ref(type, new_buffer, m_min_array_size); + virtual_span.materialize_to_uninitialized(m_mask, new_array_ref.buffer()); + + OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>( + new_array_ref, to.targets().size(), false); + m_value_per_output_id[to.id()] = new_value; + return new_array_ref; +} + +GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__single(const MFInputSocket &input, + const MFOutputSocket &output) +{ + const MFOutputSocket &from = *input.origin(); + const MFOutputSocket &to = output; + const CPPType &type = from.data_type().single_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(type == to.data_type().single_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputSingle); + GMutableSpan span = ((OutputSingleValue *)to_any_value)->span; + BLI_assert(span.size() == 1); + GVSpan virtual_span = this->get_single_input__single(input); + type.copy_to_uninitialized(virtual_span.as_single_element(), span[0]); + return span; + } + + if (from_any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.targets().size(); + BLI_assert(value->span.size() == 1); + return value->span; + } + } + + GVSpan virtual_span = this->get_single_input__single(input); + + void *new_buffer = m_allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(virtual_span.as_single_element(), new_buffer); + GMutableSpan new_array_ref(type, new_buffer, 1); + + OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>( + new_array_ref, to.targets().size(), true); + m_value_per_output_id[to.id()] = new_value; + return new_array_ref; +} + +GVectorArray &MFNetworkEvaluationStorage::get_mutable_vector__full(const MFInputSocket &input, + const MFOutputSocket &output) +{ + const MFOutputSocket &from = *input.origin(); + const MFOutputSocket &to = output; + const CPPType &base_type = from.data_type().vector_base_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(base_type == to.data_type().vector_base_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputVector); + GVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array; + GVArraySpan virtual_array_span = this->get_vector_input__full(input); + vector_array.extend(m_mask, virtual_array_span); + return vector_array; + } + + if (from_any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.targets().size(); + return *value->vector_array; + } + } + + GVArraySpan virtual_array_span = this->get_vector_input__full(input); + + GVectorArray *new_vector_array = new GVectorArray(base_type, m_min_array_size); + new_vector_array->extend(m_mask, virtual_array_span); + + OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array, + to.targets().size()); + m_value_per_output_id[to.id()] = new_value; + + return *new_vector_array; +} + +GVectorArray &MFNetworkEvaluationStorage::get_mutable_vector__single(const MFInputSocket &input, + const MFOutputSocket &output) +{ + const MFOutputSocket &from = *input.origin(); + const MFOutputSocket &to = output; + const CPPType &base_type = from.data_type().vector_base_type(); + + Value *from_any_value = m_value_per_output_id[from.id()]; + Value *to_any_value = m_value_per_output_id[to.id()]; + BLI_assert(from_any_value != nullptr); + BLI_assert(base_type == to.data_type().vector_base_type()); + + if (to_any_value != nullptr) { + BLI_assert(to_any_value->type == ValueType::OutputVector); + GVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array; + BLI_assert(vector_array.size() == 1); + GVArraySpan virtual_array_span = this->get_vector_input__single(input); + vector_array.extend(0, virtual_array_span[0]); + return vector_array; + } + + if (from_any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)from_any_value; + if (value->max_remaining_users == 1) { + m_value_per_output_id[to.id()] = value; + m_value_per_output_id[from.id()] = nullptr; + value->max_remaining_users = to.targets().size(); + return *value->vector_array; + } + } + + GVArraySpan virtual_array_span = this->get_vector_input__single(input); + + GVectorArray *new_vector_array = new GVectorArray(base_type, 1); + new_vector_array->extend(0, virtual_array_span[0]); + + OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array, + to.targets().size()); + m_value_per_output_id[to.id()] = new_value; + return *new_vector_array; +} + +GVSpan MFNetworkEvaluationStorage::get_single_input__full(const MFInputSocket &socket) +{ + const MFOutputSocket &origin = *socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + if (value->is_single_allocated) { + return GVSpan::FromSingle(value->span.type(), value->span.buffer(), m_min_array_size); + } + else { + return value->span; + } + } + else if (any_value->type == ValueType::InputSingle) { + InputSingleValue *value = (InputSingleValue *)any_value; + return value->virtual_span; + } + else if (any_value->type == ValueType::OutputSingle) { + OutputSingleValue *value = (OutputSingleValue *)any_value; + BLI_assert(value->is_computed); + return value->span; + } + + BLI_assert(false); + return GVSpan(CPPType::get<float>()); +} + +GVSpan MFNetworkEvaluationStorage::get_single_input__single(const MFInputSocket &socket) +{ + const MFOutputSocket &origin = *socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnSingle) { + OwnSingleValue *value = (OwnSingleValue *)any_value; + BLI_assert(value->span.size() == 1); + return value->span; + } + else if (any_value->type == ValueType::InputSingle) { + InputSingleValue *value = (InputSingleValue *)any_value; + BLI_assert(value->virtual_span.is_single_element()); + return value->virtual_span; + } + else if (any_value->type == ValueType::OutputSingle) { + OutputSingleValue *value = (OutputSingleValue *)any_value; + BLI_assert(value->is_computed); + BLI_assert(value->span.size() == 1); + return value->span; + } + + BLI_assert(false); + return GVSpan(CPPType::get<float>()); +} + +GVArraySpan MFNetworkEvaluationStorage::get_vector_input__full(const MFInputSocket &socket) +{ + const MFOutputSocket &origin = *socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + if (value->vector_array->size() == 1) { + GSpan span = (*value->vector_array)[0]; + return GVArraySpan(span, m_min_array_size); + } + else { + return *value->vector_array; + } + } + else if (any_value->type == ValueType::InputVector) { + InputVectorValue *value = (InputVectorValue *)any_value; + return value->virtual_array_span; + } + else if (any_value->type == ValueType::OutputVector) { + OutputVectorValue *value = (OutputVectorValue *)any_value; + return *value->vector_array; + } + + BLI_assert(false); + return GVArraySpan(CPPType::get<float>()); +} + +GVArraySpan MFNetworkEvaluationStorage::get_vector_input__single(const MFInputSocket &socket) +{ + const MFOutputSocket &origin = *socket.origin(); + Value *any_value = m_value_per_output_id[origin.id()]; + BLI_assert(any_value != nullptr); + + if (any_value->type == ValueType::OwnVector) { + OwnVectorValue *value = (OwnVectorValue *)any_value; + BLI_assert(value->vector_array->size() == 1); + return *value->vector_array; + } + else if (any_value->type == ValueType::InputVector) { + InputVectorValue *value = (InputVectorValue *)any_value; + BLI_assert(value->virtual_array_span.is_single_array()); + return value->virtual_array_span; + } + else if (any_value->type == ValueType::OutputVector) { + OutputVectorValue *value = (OutputVectorValue *)any_value; + BLI_assert(value->vector_array->size() == 1); + return *value->vector_array; + } + + BLI_assert(false); + return GVArraySpan(CPPType::get<float>()); +} + +/** \} */ + +} // namespace fn +} // namespace blender |