Files
2026-08-12 04:47:48 -04:00

696 lines
21 KiB
C++

/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#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 built, composed and evaluated at run-time. They are stored in a directed tree
* graph data structure. A field may generally depend on other 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_cache_mutex.hh"
#include "BLI_implicit_sharing_ptr.hh"
#include "FN_multi_function.hh"
namespace blender::fn {
class GField;
class FieldInput;
class FieldOperation;
class FieldInputs;
class FieldContext;
using FieldInputPtr = ImplicitSharingPtr<FieldInput>;
using FieldOperationPtr = ImplicitSharingPtr<FieldOperation>;
using FieldInputsPtr = ImplicitSharingPtr<FieldInputs>;
template<typename T> class Field;
/**
* A field with a type that is only known at runtime which can be accessed through the #cpp_type
* method. If the type is known at compile time, it is recommended to use #Field<T> instead.
*
* It is designed to support various internal storage representations to avoid unnecessary
* allocations or reference counting in many common cases.
*/
class GField {
public:
struct Input {
FieldInputPtr node;
};
struct MultiFn {
FieldOperationPtr node;
int output_i = 0;
};
/**
* Allows referencing another field without owning it. This helps with fields that are highly
* reused like the position field because it avoids reference counting..
*/
struct FieldRef {
const GField *field_ref = nullptr;
};
struct ConstantRef {
const CPPType *type = nullptr;
/** This value is not owned. Typically it has static lifetime. */
const void *value = nullptr;
};
/**
* Allows storing constants inside of #GField without any additional memory allocation.
*/
struct TrivialInlineConstant {
static constexpr int64_t inline_size = 16;
static constexpr int64_t inline_alignment = 8;
template<typename T>
static constexpr bool type_supported_v = std::is_trivially_destructible_v<T> &&
std::is_trivially_copyable_v<T> &&
sizeof(T) <= inline_size &&
alignof(T) <= inline_alignment;
static bool cpp_type_supported(const CPPType &type);
const CPPType *type = nullptr;
AlignedBuffer<inline_size, inline_alignment> value;
};
/** Used for storing constants that can't be inlined. */
struct OwnedConstant {
const CPPType *type = nullptr;
/* This value is owned by the #GField. */
void *value = nullptr;
};
template<typename T>
static constexpr bool is_constant_value_v =
is_same_any_v<T, ConstantRef, TrivialInlineConstant, OwnedConstant>;
using Variant =
std::variant<Input, MultiFn, FieldRef, ConstantRef, TrivialInlineConstant, OwnedConstant>;
private:
Variant variant_;
public:
/**
* #GField is expected to always have a valid #CPPType. Therefore, it can't be default
* constructed.
*/
GField() = delete;
/** Construct a field that just outputs the default value of the given type. */
explicit GField(const CPPType &type) noexcept;
/** Construct a field owning a field input. */
explicit GField(FieldInputPtr node) noexcept;
/** Construct a field that owns a field operation and outputs one of its outputs. */
explicit GField(FieldOperationPtr node, int output_i = 0) noexcept;
/** Construct directly from a #Variant, mostly for internal use. */
explicit GField(Variant variant) noexcept;
/**
* Wraps the given field in a new field. This is used to avoid reference counting for some field
* fields which have static lifetime.
*/
static GField from_non_owning_ref(const GField &field);
/** Construct a field that just outputs the given constant value. */
static GField from_constant(const CPPType &type, const void *value);
/** Construct a field that just outputs the given constant value without owning it. */
static GField from_non_owning_constant(const CPPType &type, const void *value);
/** Build a new #FieldInput with the given arguments. */
template<typename InputT, typename... Args> static GField from_input(Args &&...args);
/**
* #GField requires manual memory management due to inlined values and to support move semantics
* without making #GField nullable.
*/
GField(const GField &other);
GField(GField &&other) noexcept;
GField &operator=(const GField &other);
GField &operator=(GField &&other) noexcept;
~GField();
/** The value type the field outputs for each element, e.g. float. */
const CPPType &cpp_type() const;
/** Root #FieldInput nodes that this field depends on. */
const FieldInputsPtr &field_inputs() const;
/**
* This "normalizes" the field. Specifically, if this field is just a non-owning reference to
* some other field, the referenced field is returned.
*/
const GField &deref_field_ref() const;
/** Get the underlying #Variant. */
const Variant &variant() const;
/** Returns true when the field depends on some input. */
bool depends_on_input() const;
/** Utility to access a specific input type if this field is just an input. */
template<typename InputT> const InputT *get_input_if() const;
/**
* This only implements shallow comparison. A more deep comparison could reveal that two fields
* are semantically the same even if this comparison is false. Deep comparison is much more
* expensive though.
*/
friend bool operator==(const GField &a, const GField &b);
uint64_t hash() const;
/**
* Get a typed reference to this field. Note that #Field<T> happens to be identical to #GField on
* a bit-level. So this is just a cast.
*/
template<typename T> const Field<T> &typed() const;
template<typename T> Field<T> &typed();
/**
* Attempts to take ownership of a FieldOperation stored in this field, leaving the field input.
* It's expected to be deleted shortly after. This is necessary to avoid deep recursion when
* destructing a field tree.
*/
FieldOperationPtr try_extract_operation();
};
/** A version of #GField that should be used when the field type is known at compile time. */
template<typename T> class Field {
public:
using base_type = T;
using generic_type = GField;
private:
/**
* #Field<T> just stores a #GField. This makes converting between the two types easy.
*/
GField field_;
friend GField;
public:
/**
* Unlike #GField, default construction is allowed here, because the type is known without extra
* arguments.
*/
Field();
/** Same as corresponding #GField constructors. */
explicit Field(FieldInputPtr node);
explicit Field(FieldOperationPtr node, int output_i = 0);
/** Construct a field that just outputs the given value. */
explicit Field(T value);
/** This is implicitly cast to #GField which is always valid. */
operator const GField &() const;
/** These are the same as the corresponding #GField methods. */
bool depends_on_input() const;
template<typename InputT, typename... Args> static Field from_input(Args &&...args);
template<typename InputT> const InputT *get_input_if() const;
uint64_t hash() const;
static Field from_non_owning_ref(const Field &field);
};
/**
* A version of #GField that only references data from other fields but does not own any data
* itself. This allows it to be smaller and trivially copyable making it more efficient in some
* contexts. This is mainly used during field evaluation.
*/
class GFieldRef {
public:
struct Value {
const CPPType *type = nullptr;
const void *value = nullptr;
};
struct Input {
const FieldInput *node = nullptr;
};
struct MultiFn {
const FieldOperation *node = nullptr;
int output_i = 0;
};
using Variant = std::variant<Value, Input, MultiFn>;
private:
Variant variant_;
public:
/**
* Create a reference to the given fields. The caller is responsible for making sure that the
* referenced data stays valid.
*/
GFieldRef(const GField &field);
template<typename T> GFieldRef(const Field<T> &field);
explicit GFieldRef(const FieldInput &field_input);
explicit GFieldRef(const FieldOperation &field_multi_fn, int output_i = 0);
explicit GFieldRef(Variant variant);
/** Get access to the underlying #Variant. */
const Variant &variant() const;
/** These are the same as the corresponding #GField methods. */
const CPPType &cpp_type() const;
const FieldInputsPtr &field_inputs() const;
uint64_t hash() const;
static GFieldRef from_constant(const CPPType &type, const void *value);
};
/**
* A field is always evaluated in some context. This context determines the value of the field
* inputs.
*/
class FieldContext {
public:
virtual ~FieldContext() = default;
virtual GVArray get_varray_for_input(const FieldInput &field_input,
const IndexMask &mask,
ResourceScope &scope) const;
};
/**
* "Deep" hashing for fields that considers the operation and inputs semantically, rather than
* just the shallow data (i.e. memory address) of the field data, like the default "hash()"
* implementation. Because common field reuse would give this potentially exponential cost, this
* struct caches the hashes of intermediate fields.
*/
struct FieldHashDeep {
Map<GFieldRef, UniqueHash> cache;
UniqueHash ensure(const GFieldRef &field);
UniqueHash lookup(const GFieldRef &field) const
{
return this->cache.lookup(field);
}
bool contains(const GFieldRef &field) const
{
return this->cache.contains(field);
}
};
/**
* Cache of field inputs. This is used quite often and is therefore computed eagerly for
* intermediate operations. Otherwise one would have to parse the field tree every time the set of
* inputs is required. Since many fields share the same set of inputs, this is often shared.
*/
class FieldInputs : public ImplicitSharingMixin {
public:
/** Deduplicated set of field inputs. */
VectorSet<std::reference_wrapper<const FieldInput>> inputs;
void delete_self() override;
};
/**
* This is an abstract class which concrete field inputs have to derive from. When a field is
* evaluated, this can provide values based on the provided context.
*
* Since there is no better way yet, #FieldInput is also often used to process the output of
* intermediate fields, in which case this is not technically an "input".
*/
class FieldInput : public ImplicitSharingMixin {
protected:
const CPPType *type_;
std::string debug_name_;
/**
* Field inputs are initialized lazily because it can't be done in the constructor because the
* derived class constructor has not run yet.
*/
mutable CacheMutex field_inputs_mutex_;
mutable FieldInputsPtr field_inputs_;
public:
FieldInput(const CPPType &type, std::string debug_name = "");
~FieldInput() override;
StringRefNull debug_name() const;
virtual std::string socket_inspection_name() const;
const CPPType &cpp_type() const;
const FieldInputsPtr &field_inputs() const;
uint64_t hash() const;
virtual void hash_unique(UniqueHashBytes &hash, FieldHashDeep &deep_hash_cache) const;
/**
* If this #FieldInput depends on other fields, this function should be overridden.
*/
virtual void foreach_recursive_field(FunctionRef<void(const GField &)> fn) const;
/**
* Output a virtual array for the given index mask in the given context.
*/
virtual GVArray get_varray_for_context(const FieldContext &context,
const IndexMask &mask,
ResourceScope &scope) const = 0;
void delete_self() override;
};
/**
* This is an intermediate node in a field tree which executes a #MultiFunction on each value. The
* #MultiFunction can either be owned or just referenced.
*
* It also stores a #GField for every input of the multi-function. Other fields may reference
* individual outputs.
*/
class FieldOperation : public ImplicitSharingMixin {
private:
/** One #GField for every input of the multi-function. */
Vector<GField> inputs_;
/** Optionally owned multi-function. */
std::shared_ptr<const mf::MultiFunction> owned_fn_;
const mf::MultiFunction *fn_;
/** Cached field inputs. */
FieldInputsPtr field_inputs_;
public:
/** Prefer `from*` constructor functions instead. */
FieldOperation(std::shared_ptr<const mf::MultiFunction> fn, Vector<GField> inputs);
FieldOperation(const mf::MultiFunction &fn, Vector<GField> inputs);
static FieldOperationPtr from(std::shared_ptr<const mf::MultiFunction> fn,
Vector<GField> inputs);
static FieldOperationPtr from(const mf::MultiFunction &fn, Vector<GField> inputs);
/** Get the type of a specific output. */
const CPPType &output_cpp_type(int output_i) const;
const mf::MultiFunction &multi_function() const;
const FieldInputsPtr &field_inputs() const;
Span<GField> inputs() const;
void delete_self() override;
private:
void delete_input_fields();
};
bool operator==(const GField &a, const GField &b);
bool operator==(const GFieldRef &a, const GFieldRef &b);
/** Type trait to detect field types. */
template<typename T> constexpr bool is_field_v = false;
template<typename T> constexpr bool is_field_v<Field<T>> = true;
Field<bool> invert_boolean_field(const Field<bool> &field);
class IndexFieldInput final : public FieldInput {
public:
IndexFieldInput();
static GVArray get_index_varray(const IndexMask &mask);
GVArray get_varray_for_context(const FieldContext &context,
const IndexMask &mask,
ResourceScope &scope) const final;
void hash_unique(UniqueHashBytes &hash, FieldHashDeep &deep_hash_cache) const override;
/** Cached index field to avoid allocating a new one every time. */
static const Field<int> &get_field();
};
/* -------------------------------------------------------------------- */
/** \name Inline Methods
* \{ */
inline GField::GField(const CPPType &type) noexcept
: variant_(ConstantRef{&type, type.default_value()})
{
}
inline GField::GField(FieldInputPtr node) noexcept : variant_(Input{std::move(node)}) {}
inline GField::GField(Variant variant) noexcept : variant_(std::move(variant)) {}
inline GField::GField(FieldOperationPtr node, const int output_i) noexcept
: variant_(MultiFn{std::move(node), output_i})
{
}
inline GField GField::from_non_owning_ref(const GField &field)
{
return GField(FieldRef{&field});
}
inline bool GField::TrivialInlineConstant::cpp_type_supported(const CPPType &type)
{
return type.is_trivial && type.size <= TrivialInlineConstant::inline_size &&
type.alignment <= TrivialInlineConstant::inline_alignment;
}
inline GField GField::from_non_owning_constant(const CPPType &type, const void *value)
{
return GField(ConstantRef{&type, value});
}
template<typename T> inline Field<T> Field<T>::from_non_owning_ref(const Field &field)
{
return GField::from_non_owning_ref(field).template typed<T>();
}
template<typename InputT, typename... Args> inline GField GField::from_input(Args &&...args)
{
FieldInputPtr input{MEM_new<InputT>(__func__, std::forward<Args>(args)...)};
return GField(Input{std::move(input)});
}
template<typename T>
template<typename InputT, typename... Args>
inline Field<T> Field<T>::from_input(Args &&...args)
{
return GField::from_input<InputT>(std::forward<Args>(args)...).template typed<T>();
}
template<typename T>
inline Field<T>::Field(T value)
: field_([&]() {
const CPPType &type = CPPType::get<T>();
if constexpr (GField::TrivialInlineConstant::type_supported_v<T>) {
GField::TrivialInlineConstant constant;
constant.type = &type;
new (constant.value.ptr()) T(std::move(value));
return GField(constant);
}
else {
T *new_value = MEM_new<T>(__func__, std::move(new_value));
return GField(GField::OwnedConstant{&type, new_value});
}
}())
{
}
template<typename T> inline bool Field<T>::depends_on_input() const
{
return field_.depends_on_input();
}
inline const CPPType &GField::cpp_type() const
{
return std::visit(
[]<typename T>(const T &v) -> const CPPType & {
if constexpr (std::is_same_v<T, Input>) {
return v.node->cpp_type();
}
else if constexpr (std::is_same_v<T, MultiFn>) {
return v.node->output_cpp_type(v.output_i);
}
else if constexpr (std::is_same_v<T, FieldRef>) {
return v.field_ref->cpp_type();
}
else if constexpr (is_same_any_v<T, ConstantRef, TrivialInlineConstant, OwnedConstant>) {
return *v.type;
}
else {
BLI_assert_unreachable_static_t(T);
}
},
this->variant_);
}
inline const GField &GField::deref_field_ref() const
{
if (const auto *field_ref = std::get_if<FieldRef>(&this->variant_)) {
return field_ref->field_ref->deref_field_ref();
}
return *this;
}
template<typename T> inline bool operator==(const Field<T> &a, const Field<T> &b)
{
return static_cast<const GField &>(a) == static_cast<const GField &>(b);
}
template<typename T> inline uint64_t Field<T>::hash() const
{
return field_.hash();
}
inline const CPPType &FieldInput::cpp_type() const
{
return *this->type_;
}
inline const FieldInputsPtr &FieldOperation::field_inputs() const
{
return field_inputs_;
}
inline StringRefNull FieldInput::debug_name() const
{
return debug_name_;
}
inline std::string FieldInput::socket_inspection_name() const
{
return debug_name_;
}
template<typename T> inline Field<T>::operator const GField &() const
{
return field_;
}
template<typename T> inline const Field<T> &GField::typed() const
{
static_assert(sizeof(GField) == sizeof(Field<T>));
BLI_assert(this->cpp_type().is<T>());
return reinterpret_cast<const Field<T> &>(*this);
}
template<typename T> inline Field<T> &GField::typed()
{
static_assert(sizeof(GField) == sizeof(Field<T>));
BLI_assert(this->cpp_type().is<T>());
return reinterpret_cast<Field<T> &>(*this);
}
inline const GField::Variant &GField::variant() const
{
return variant_;
}
template<typename T> inline Field<T>::Field() : field_(CPPType::get<T>()) {}
template<typename T> inline Field<T>::Field(FieldInputPtr node) : field_(GField(std::move(node)))
{
}
template<typename T>
inline Field<T>::Field(FieldOperationPtr node, const int output_i)
: field_(GField(std::move(node), output_i))
{
}
inline bool GField::depends_on_input() const
{
const FieldInputsPtr &inputs = this->field_inputs();
if (!inputs) {
return false;
}
return !inputs->inputs.is_empty();
}
template<typename InputT> inline const InputT *GField::get_input_if() const
{
const GField &deref_field = this->deref_field_ref();
if (const auto *input = std::get_if<Input>(&deref_field.variant())) {
return dynamic_cast<const InputT *>(input->node.get());
}
return nullptr;
}
template<typename T> template<typename InputT> inline const InputT *Field<T>::get_input_if() const
{
return field_.get_input_if<InputT>();
}
inline Span<GField> FieldOperation::inputs() const
{
return inputs_;
}
inline GFieldRef::GFieldRef(const FieldInput &field_input) : variant_(Input{&field_input}) {}
inline GFieldRef::GFieldRef(const FieldOperation &field_multi_fn, int output_i)
: variant_(MultiFn{&field_multi_fn, output_i})
{
}
inline GFieldRef::GFieldRef(Variant variant) : variant_(std::move(variant)) {}
inline GFieldRef GFieldRef::from_constant(const CPPType &type, const void *value)
{
return GFieldRef(Value{&type, value});
}
template<typename T>
inline GFieldRef::GFieldRef(const Field<T> &field) : GFieldRef(static_cast<const GField &>(field))
{
}
inline const GFieldRef::Variant &GFieldRef::variant() const
{
return variant_;
}
inline const CPPType &GFieldRef::cpp_type() const
{
return std::visit(
[]<typename T>(const T &v) -> const CPPType & {
if constexpr (std::is_same_v<T, Value>) {
return *v.type;
}
else if constexpr (std::is_same_v<T, Input>) {
return v.node->cpp_type();
}
else if constexpr (std::is_same_v<T, MultiFn>) {
return v.node->output_cpp_type(v.output_i);
}
else {
BLI_assert_unreachable_static_t(T);
}
},
variant_);
}
inline bool operator==(const FieldInput &a, const FieldInput &b)
{
return &a == &b;
}
inline const mf::MultiFunction &FieldOperation::multi_function() const
{
return *this->fn_;
}
/** \} */
} // namespace blender::fn