/* 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; using FieldOperationPtr = ImplicitSharingPtr; using FieldInputsPtr = ImplicitSharingPtr; template 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 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 static constexpr bool type_supported_v = std::is_trivially_destructible_v && std::is_trivially_copyable_v && sizeof(T) <= inline_size && alignof(T) <= inline_alignment; static bool cpp_type_supported(const CPPType &type); const CPPType *type = nullptr; AlignedBuffer 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 static constexpr bool is_constant_value_v = is_same_any_v; using Variant = std::variant; 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 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 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 happens to be identical to #GField on * a bit-level. So this is just a cast. */ template const Field &typed() const; template Field &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 class Field { public: using base_type = T; using generic_type = GField; private: /** * #Field 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 static Field from_input(Args &&...args); template 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; 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 GFieldRef(const Field &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 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> 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 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 inputs_; /** Optionally owned multi-function. */ std::shared_ptr owned_fn_; const mf::MultiFunction *fn_; /** Cached field inputs. */ FieldInputsPtr field_inputs_; public: /** Prefer `from*` constructor functions instead. */ FieldOperation(std::shared_ptr fn, Vector inputs); FieldOperation(const mf::MultiFunction &fn, Vector inputs); static FieldOperationPtr from(std::shared_ptr fn, Vector inputs); static FieldOperationPtr from(const mf::MultiFunction &fn, Vector 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 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 constexpr bool is_field_v = false; template constexpr bool is_field_v> = true; Field invert_boolean_field(const Field &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 &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 inline Field Field::from_non_owning_ref(const Field &field) { return GField::from_non_owning_ref(field).template typed(); } template inline GField GField::from_input(Args &&...args) { FieldInputPtr input{MEM_new(__func__, std::forward(args)...)}; return GField(Input{std::move(input)}); } template template inline Field Field::from_input(Args &&...args) { return GField::from_input(std::forward(args)...).template typed(); } template inline Field::Field(T value) : field_([&]() { const CPPType &type = CPPType::get(); if constexpr (GField::TrivialInlineConstant::type_supported_v) { GField::TrivialInlineConstant constant; constant.type = &type; new (constant.value.ptr()) T(std::move(value)); return GField(constant); } else { T *new_value = MEM_new(__func__, std::move(new_value)); return GField(GField::OwnedConstant{&type, new_value}); } }()) { } template inline bool Field::depends_on_input() const { return field_.depends_on_input(); } inline const CPPType &GField::cpp_type() const { return std::visit( [](const T &v) -> const CPPType & { if constexpr (std::is_same_v) { return v.node->cpp_type(); } else if constexpr (std::is_same_v) { return v.node->output_cpp_type(v.output_i); } else if constexpr (std::is_same_v) { return v.field_ref->cpp_type(); } else if constexpr (is_same_any_v) { 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(&this->variant_)) { return field_ref->field_ref->deref_field_ref(); } return *this; } template inline bool operator==(const Field &a, const Field &b) { return static_cast(a) == static_cast(b); } template inline uint64_t Field::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 inline Field::operator const GField &() const { return field_; } template inline const Field &GField::typed() const { static_assert(sizeof(GField) == sizeof(Field)); BLI_assert(this->cpp_type().is()); return reinterpret_cast &>(*this); } template inline Field &GField::typed() { static_assert(sizeof(GField) == sizeof(Field)); BLI_assert(this->cpp_type().is()); return reinterpret_cast &>(*this); } inline const GField::Variant &GField::variant() const { return variant_; } template inline Field::Field() : field_(CPPType::get()) {} template inline Field::Field(FieldInputPtr node) : field_(GField(std::move(node))) { } template inline Field::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 inline const InputT *GField::get_input_if() const { const GField &deref_field = this->deref_field_ref(); if (const auto *input = std::get_if(&deref_field.variant())) { return dynamic_cast(input->node.get()); } return nullptr; } template template inline const InputT *Field::get_input_if() const { return field_.get_input_if(); } inline Span 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 inline GFieldRef::GFieldRef(const Field &field) : GFieldRef(static_cast(field)) { } inline const GFieldRef::Variant &GFieldRef::variant() const { return variant_; } inline const CPPType &GFieldRef::cpp_type() const { return std::visit( [](const T &v) -> const CPPType & { if constexpr (std::is_same_v) { return *v.type; } else if constexpr (std::is_same_v) { return v.node->cpp_type(); } else if constexpr (std::is_same_v) { 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