This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/functions/FN_field.hh
Jacques Lucke fd477e738d Geometry Nodes: remove reference to anonymous attributes in tooltips
This changes socket inspection for fields according to T91881.

Differential Revision: https://developer.blender.org/D13006
2021-10-26 15:32:01 +02:00

556 lines
17 KiB
C++

/*
* 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);
virtual ~FieldNode() = default;
virtual const CPPType &output_cpp_type(int output_index) const = 0;
bool is_input() const;
bool is_operation() const;
bool depends_on_input() const;
/**
* 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;
virtual bool is_equal_to(const FieldNode &other) const;
};
/**
* 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::shared_ptr<const MultiFunction> owned_function_;
const MultiFunction *function_;
/** Inputs to the operation. */
blender::Vector<GField> inputs_;
public:
FieldOperation(std::shared_ptr<const MultiFunction> function, Vector<GField> inputs = {});
FieldOperation(const MultiFunction &function, Vector<GField> inputs = {});
Span<GField> inputs() const;
const MultiFunction &multi_function() const;
const CPPType &output_cpp_type(int output_index) const override;
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 {
public:
/* The order is also used for sorting in socket inspection. */
enum class Category {
NamedAttribute = 0,
Generated = 1,
AnonymousAttribute = 2,
Unknown,
};
protected:
const CPPType *type_;
std::string debug_name_;
Category category_ = Category::Unknown;
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;
blender::StringRef debug_name() const;
const CPPType &cpp_type() const;
Category category() const;
const CPPType &output_cpp_type(int output_index) const override;
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 = {});
/* -------------------------------------------------------------------- */
/** \name 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);
class IndexFieldInput final : public FieldInput {
public:
IndexFieldInput();
static GVArray *get_index_varray(IndexMask mask, ResourceScope &scope);
const GVArray *get_varray_for_context(const FieldContext &context,
IndexMask mask,
ResourceScope &scope) const final;
uint64_t hash() const override;
bool is_equal_to(const fn::FieldNode &other) const override;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name #FieldNode Inline Methods
* \{ */
inline FieldNode::FieldNode(bool is_input, bool depends_on_input)
: is_input_(is_input), depends_on_input_(depends_on_input)
{
}
inline bool FieldNode::is_input() const
{
return is_input_;
}
inline bool FieldNode::is_operation() const
{
return !is_input_;
}
inline bool FieldNode::depends_on_input() const
{
return depends_on_input_;
}
inline uint64_t FieldNode::hash() const
{
return get_default_hash(this);
}
inline bool FieldNode::is_equal_to(const FieldNode &other) const
{
return this == &other;
}
inline bool operator==(const FieldNode &a, const FieldNode &b)
{
return a.is_equal_to(b);
}
inline bool operator!=(const FieldNode &a, const FieldNode &b)
{
return !(a == b);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #FieldOperation Inline Methods
* \{ */
inline Span<GField> FieldOperation::inputs() const
{
return inputs_;
}
inline const MultiFunction &FieldOperation::multi_function() const
{
return *function_;
}
inline const CPPType &FieldOperation::output_cpp_type(int output_index) const
{
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>();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #FieldInput Inline Methods
* \{ */
inline std::string FieldInput::socket_inspection_name() const
{
return debug_name_;
}
inline StringRef FieldInput::debug_name() const
{
return debug_name_;
}
inline const CPPType &FieldInput::cpp_type() const
{
return *type_;
}
inline FieldInput::Category FieldInput::category() const
{
return category_;
}
inline const CPPType &FieldInput::output_cpp_type(int output_index) const
{
BLI_assert(output_index == 0);
UNUSED_VARS_NDEBUG(output_index);
return *type_;
}
/** \} */
} // namespace blender::fn