diff options
Diffstat (limited to 'source/blender/functions/FN_field.hh')
-rw-r--r-- | source/blender/functions/FN_field.hh | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh new file mode 100644 index 00000000000..d4375b625ce --- /dev/null +++ b/source/blender/functions/FN_field.hh @@ -0,0 +1,487 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup fn + * + * A #Field represents a function that outputs a value based on an arbitrary number of inputs. The + * inputs for a specific field evaluation are provided by a #FieldContext. + * + * A typical example is a field that computes a displacement vector for every vertex on a mesh + * based on its position. + * + * Fields can be build, composed and evaluated at run-time. They are stored in a directed tree + * graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode + * has an arbitrary number of inputs and at least one output and a #Field references a specific + * output of a #FieldNode. The inputs of a #FieldNode are other fields. + * + * There are two different types of field nodes: + * - #FieldInput: Has no input and exactly one output. It represents an input to the entire field + * when it is evaluated. During evaluation, the value of this input is based on a #FieldContext. + * - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main + * use is to compose multiple existing fields into new fields. + * + * When fields are evaluated, they are converted into a multi-function procedure which allows + * efficient computation. In the future, we might support different field evaluation mechanisms for + * e.g. the following scenarios: + * - Latency of a single evaluation is more important than throughput. + * - Evaluation should happen on other hardware like GPUs. + * + * Whenever possible, multiple fields should be evaluated together to avoid duplicate work when + * they share common sub-fields and a common context. + */ + +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "FN_generic_virtual_array.hh" +#include "FN_multi_function_builder.hh" +#include "FN_multi_function_procedure.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" + +namespace blender::fn { + +class FieldInput; + +/** + * A node in a field-tree. It has at least one output that can be referenced by fields. + */ +class FieldNode { + private: + bool is_input_; + /** + * True when this node is a #FieldInput or (potentially indirectly) depends on one. This could + * always be derived again later by traversing the field-tree, but keeping track of it while the + * field is built is cheaper. + * + * If this is false, the field is constant. Note that even when this is true, the field may be + * constant when all inputs are constant. + */ + bool depends_on_input_; + + public: + FieldNode(bool is_input, bool depends_on_input) + : is_input_(is_input), depends_on_input_(depends_on_input) + { + } + + virtual ~FieldNode() = default; + + virtual const CPPType &output_cpp_type(int output_index) const = 0; + + bool is_input() const + { + return is_input_; + } + + bool is_operation() const + { + return !is_input_; + } + + bool depends_on_input() const + { + return depends_on_input_; + } + + /** + * Invoke callback for every field input. It might be called multiple times for the same input. + * The caller is responsible for deduplication if required. + */ + virtual void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const = 0; + + virtual uint64_t hash() const + { + return get_default_hash(this); + } + + friend bool operator==(const FieldNode &a, const FieldNode &b) + { + return a.is_equal_to(b); + } + + friend bool operator!=(const FieldNode &a, const FieldNode &b) + { + return !(a == b); + } + + virtual bool is_equal_to(const FieldNode &other) const + { + return this == &other; + } +}; + +/** + * Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef. + */ +template<typename NodePtr> class GFieldBase { + protected: + NodePtr node_ = nullptr; + int node_output_index_ = 0; + + GFieldBase(NodePtr node, const int node_output_index) + : node_(std::move(node)), node_output_index_(node_output_index) + { + } + + public: + GFieldBase() = default; + + operator bool() const + { + return node_ != nullptr; + } + + friend bool operator==(const GFieldBase &a, const GFieldBase &b) + { + /* Two nodes can compare equal even when their pointer is not the same. For example, two + * "Position" nodes are the same. */ + return *a.node_ == *b.node_ && a.node_output_index_ == b.node_output_index_; + } + + uint64_t hash() const + { + return get_default_hash_2(*node_, node_output_index_); + } + + const fn::CPPType &cpp_type() const + { + return node_->output_cpp_type(node_output_index_); + } + + const FieldNode &node() const + { + return *node_; + } + + int node_output_index() const + { + return node_output_index_; + } +}; + +/** + * A field whose output type is only known at run-time. + */ +class GField : public GFieldBase<std::shared_ptr<FieldNode>> { + public: + GField() = default; + + GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index) + { + } +}; + +/** + * Same as #GField but is cheaper to copy/move around, because it does not contain a + * #std::shared_ptr. + */ +class GFieldRef : public GFieldBase<const FieldNode *> { + public: + GFieldRef() = default; + + GFieldRef(const GField &field) + : GFieldBase<const FieldNode *>(&field.node(), field.node_output_index()) + { + } + + GFieldRef(const FieldNode &node, const int node_output_index = 0) + : GFieldBase<const FieldNode *>(&node, node_output_index) + { + } +}; + +/** + * A typed version of #GField. It has the same memory layout as #GField. + */ +template<typename T> class Field : public GField { + public: + Field() = default; + + Field(GField field) : GField(std::move(field)) + { + BLI_assert(this->cpp_type().template is<T>()); + } + + Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0) + : Field(GField(std::move(node), node_output_index)) + { + } +}; + +/** + * A #FieldNode that allows composing existing fields into new fields. + */ +class FieldOperation : public FieldNode { + /** + * The multi-function used by this node. It is optionally owned. + * Multi-functions with mutable or vector parameters are not supported currently. + */ + std::unique_ptr<const MultiFunction> owned_function_; + const MultiFunction *function_; + + /** Inputs to the operation. */ + blender::Vector<GField> inputs_; + + public: + FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {}); + FieldOperation(const MultiFunction &function, Vector<GField> inputs = {}); + + Span<GField> inputs() const + { + return inputs_; + } + + const MultiFunction &multi_function() const + { + return *function_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + int output_counter = 0; + for (const int param_index : function_->param_indices()) { + MFParamType param_type = function_->param_type(param_index); + if (param_type.is_output()) { + if (output_counter == output_index) { + return param_type.data_type().single_type(); + } + output_counter++; + } + } + BLI_assert_unreachable(); + return CPPType::get<float>(); + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +class FieldContext; + +/** + * A #FieldNode that represents an input to the entire field-tree. + */ +class FieldInput : public FieldNode { + protected: + const CPPType *type_; + std::string debug_name_; + + public: + FieldInput(const CPPType &type, std::string debug_name = ""); + + /** + * Get the value of this specific input based on the given context. The returned virtual array, + * should live at least as long as the passed in #scope. May return null. + */ + virtual const GVArray *get_varray_for_context(const FieldContext &context, + IndexMask mask, + ResourceScope &scope) const = 0; + + virtual std::string socket_inspection_name() const + { + return debug_name_; + } + + blender::StringRef debug_name() const + { + return debug_name_; + } + + const CPPType &cpp_type() const + { + return *type_; + } + + const CPPType &output_cpp_type(int output_index) const override + { + BLI_assert(output_index == 0); + UNUSED_VARS_NDEBUG(output_index); + return *type_; + } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; +}; + +/** + * Provides inputs for a specific field evaluation. + */ +class FieldContext { + public: + ~FieldContext() = default; + + virtual const GVArray *get_varray_for_input(const FieldInput &field_input, + IndexMask mask, + ResourceScope &scope) const; +}; + +/** + * Utility class that makes it easier to evaluate fields. + */ +class FieldEvaluator : NonMovable, NonCopyable { + private: + struct OutputPointerInfo { + void *dst = nullptr; + /* When a destination virtual array is provided for an input, this is + * unnecessary, otherwise this is used to construct the required virtual array. */ + void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr; + }; + + ResourceScope scope_; + const FieldContext &context_; + const IndexMask mask_; + Vector<GField> fields_to_evaluate_; + Vector<GVMutableArray *> dst_varrays_; + Vector<const GVArray *> evaluated_varrays_; + Vector<OutputPointerInfo> output_pointer_infos_; + bool is_evaluated_ = false; + + public: + /** Takes #mask by pointer because the mask has to live longer than the evaluator. */ + FieldEvaluator(const FieldContext &context, const IndexMask *mask) + : context_(context), mask_(*mask) + { + } + + /** Construct a field evaluator for all indices less than #size. */ + FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size) + { + } + + ~FieldEvaluator() + { + /* While this assert isn't strictly necessary, and could be replaced with a warning, + * it will catch cases where someone forgets to call #evaluate(). */ + BLI_assert(is_evaluated_); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable virtual array that the evaluated result for this field is be written into. + */ + int add_with_destination(GField field, GVMutableArray &dst); + + /** Same as #add_with_destination but typed. */ + template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(dst); + return this->add_with_destination(GField(std::move(field)), varray); + } + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + int add_with_destination(GField field, GMutableSpan dst); + + /** + * \param field: Field to add to the evaluator. + * \param dst: Mutable span that the evaluated result for this field is be written into. + * \note: When the output may only be used as a single value, the version of this function with + * a virtual array result array should be used. + */ + template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst) + { + GVMutableArray &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(dst); + return this->add_with_destination(std::move(field), varray); + } + + int add(GField field, const GVArray **varray_ptr); + + /** + * \param field: Field to add to the evaluator. + * \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be + * assigned to the given position. + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr) + { + const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field)); + dst_varrays_.append(nullptr); + output_pointer_infos_.append( + OutputPointerInfo{varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) { + *(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(varray); + }}); + return field_index; + } + + /** + * \return Index of the field in the evaluator which can be used in the #get_evaluated methods. + */ + int add(GField field); + + /** + * Evaluate all fields on the evaluator. This can only be called once. + */ + void evaluate(); + + const GVArray &get_evaluated(const int field_index) const + { + BLI_assert(is_evaluated_); + return *evaluated_varrays_[field_index]; + } + + template<typename T> const VArray<T> &get_evaluated(const int field_index) + { + const GVArray &varray = this->get_evaluated(field_index); + GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(varray); + return *typed_varray; + } + + /** + * Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used + * to avoid calculations for unnecessary elements later on. The evaluator will own the indices in + * some cases, so it must live at least as long as the returned mask. + */ + IndexMask get_evaluated_as_mask(const int field_index); +}; + +Vector<const GVArray *> evaluate_fields(ResourceScope &scope, + Span<GFieldRef> fields_to_evaluate, + IndexMask mask, + const FieldContext &context, + Span<GVMutableArray *> dst_varrays = {}); + +/* -------------------------------------------------------------------- + * Utility functions for simple field creation and evaluation. + */ + +void evaluate_constant_field(const GField &field, void *r_value); + +template<typename T> T evaluate_constant_field(const Field<T> &field) +{ + T value; + value.~T(); + evaluate_constant_field(field, &value); + return value; +} + +template<typename T> Field<T> make_constant_field(T value) +{ + auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value)); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return Field<T>{GField{std::move(operation), 0}}; +} + +GField make_field_constant_if_possible(GField field); + +} // namespace blender::fn |