Files
workinf_Blender_Wasm/blender-5.2.0/source/blender/functions/intern/field.cc
2026-08-12 04:47:48 -04:00

568 lines
17 KiB
C++

/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_set.hh"
#include "BLI_stack.hh"
#include "FN_field.hh"
#include "FN_multi_function_registry.hh"
#include <xxhash.h>
namespace blender::fn {
FieldInput::FieldInput(const CPPType &type, std::string debug_name)
: type_(&type), debug_name_(std::move(debug_name))
{
}
GField GField::from_constant(const CPPType &type, const void *value)
{
if (TrivialInlineConstant::cpp_type_supported(type)) {
TrivialInlineConstant constant;
constant.type = &type;
type.copy_construct(value, constant.value.ptr());
return GField(constant);
}
void *new_value = MEM_new_uninitialized_aligned(type.size, type.alignment, __func__);
type.copy_construct(value, new_value);
return GField(OwnedConstant{&type, new_value});
}
bool operator==(const GField &a, const GField &b)
{
const GField &a_ref = a.deref_field_ref();
const GField &b_ref = b.deref_field_ref();
return std::visit(
[&]<typename T>(const T &v_a) -> bool {
if constexpr (std::is_same_v<T, GField::Input>) {
if (const auto *v_b = std::get_if<GField::Input>(&b_ref.variant_)) {
return v_a.node == v_b->node;
}
return false;
}
else if constexpr (std::is_same_v<T, GField::MultiFn>) {
if (const auto *v_b = std::get_if<GField::MultiFn>(&b_ref.variant_)) {
return v_a.node == v_b->node && v_a.output_i == v_b->output_i;
}
return false;
}
else if constexpr (std::is_same_v<T, GField::FieldRef>) {
/* Should not exist due to #deref_field_ref above. */
BLI_assert_unreachable();
return false;
}
else if constexpr (GField::is_constant_value_v<T>) {
const CPPType &type_a = *v_a.type;
const void *constant_a = v_a.value;
return std::visit(
[&]<typename U>(const U &v_b) -> bool {
if constexpr (GField::is_constant_value_v<U>) {
const CPPType &type_b = *v_b.type;
if (type_a != type_b) {
return false;
}
const void *constant_b = v_b.value;
return type_a.is_equal_or_false(constant_a, constant_b);
}
else {
return false;
}
},
b_ref.variant_);
}
else {
BLI_assert_unreachable_static_t(T);
}
},
a_ref.variant_);
}
uint64_t GField::hash() const
{
const GField &ref = this->deref_field_ref();
return std::visit(
[&]<typename T>(const T &v) -> uint64_t {
if constexpr (std::is_same_v<T, Input>) {
return get_default_hash(v.node);
}
else if constexpr (std::is_same_v<T, MultiFn>) {
return get_default_hash(v.node, v.output_i);
}
else if constexpr (std::is_same_v<T, FieldRef>) {
/* Should not exist due to #deref_field_ref above. */
BLI_assert_unreachable();
return 0;
}
else if constexpr (is_constant_value_v<T>) {
return v.type->hash_or_fallback(v.value, uint64_t(v.type));
}
else {
BLI_assert_unreachable_static_t(T);
}
},
ref.variant_);
}
UniqueHash FieldHashDeep::ensure(const GFieldRef &field)
{
if (const UniqueHash *cached = cache.lookup_ptr(field)) {
return *cached;
}
/* With a post-order DFS traversal, push each node twice. On the first pop (not yet in
* `visited`), push a field's children. On the second pop (already in `visited`), all children
* will be in `cache`, so compute and store the hash. Checking the cache for a hash avoids
* duplicate work when the same sub-field is reached via multiple paths (e.g. diamond-shaped
* graphs). */
Set<GFieldRef, 8> visited;
Stack<GFieldRef, 16> stack;
stack.push(field);
while (!stack.is_empty()) {
GFieldRef current = stack.pop();
if (cache.contains(current)) {
continue;
}
if (visited.contains(current)) {
UniqueHashBytes hash_context;
std::visit(
[&]<typename T>(const T &v) {
if constexpr (std::is_same_v<T, GFieldRef::Value>) {
v.type->hash_unique(v.value, hash_context);
hash_context.add(v.type);
}
else if constexpr (std::is_same_v<T, GFieldRef::Input>) {
v.node->hash_unique(hash_context, *this);
}
else if constexpr (std::is_same_v<T, GFieldRef::MultiFn>) {
v.node->multi_function().hash_unique(hash_context);
hash_context.add(v.output_i);
for (const GField &input_field : v.node->inputs()) {
hash_context.add(cache.lookup(input_field));
}
}
else {
BLI_assert_unreachable_static_t(T);
}
},
current.variant());
const Span bytes = hash_context.data.as_span();
UniqueHash hash;
const XXH128_hash_t xxhash = XXH3_128bits(bytes.data(), bytes.size());
static_assert(sizeof(UniqueHash) == sizeof(xxhash));
memcpy(static_cast<void *>(&hash), &xxhash, sizeof(xxhash));
cache.add_new(current, hash);
continue;
}
visited.add(current);
stack.push(current);
if (const auto *multi_fn = std::get_if<GFieldRef::MultiFn>(&current.variant())) {
for (const GField &input : multi_fn->node->inputs()) {
stack.push(input);
}
}
}
return cache.lookup(field);
}
const FieldInputsPtr &FieldInput::field_inputs() const
{
field_inputs_mutex_.ensure([&]() {
FieldInputs *inputs = MEM_new<FieldInputs>(__func__);
inputs->inputs.add(*this);
field_inputs_ = FieldInputsPtr(inputs);
});
return field_inputs_;
}
uint64_t FieldInput::hash() const
{
UniqueHashBytes hash_context;
FieldHashDeep deep_hash_cache;
this->hash_unique(hash_context, deep_hash_cache);
return get_default_hash(hash_context.data);
}
FieldInput::~FieldInput() = default;
void FieldInput::foreach_recursive_field(FunctionRef<void(const GField &)> /*fn*/) const {}
void FieldInput::hash_unique(UniqueHashBytes &hash, FieldHashDeep & /*deep_hash_cache*/) const
{
hash.add(this);
}
FieldOperationPtr GField::try_extract_operation()
{
MultiFn *multi_fn = std::get_if<MultiFn>(&variant_);
if (!multi_fn || !multi_fn->node) {
return nullptr;
}
return std::move(multi_fn->node);
}
void FieldInput::delete_self()
{
MEM_delete(this);
}
void FieldOperation::delete_self()
{
this->delete_input_fields();
MEM_delete(this);
}
void FieldOperation::delete_input_fields()
{
BLI_assert(this->is_expired());
/* Some input fields are freed iteratively instead of recursively to avoid a potentially very
* deep call stack. */
Vector<FieldOperationPtr, 16> remaining;
for (GField &input : inputs_) {
if (FieldOperationPtr input_op = input.try_extract_operation()) {
remaining.append(std::move(input_op));
}
}
while (!remaining.is_empty()) {
FieldOperationPtr op = remaining.pop_last();
if (!op->is_mutable()) {
continue;
}
FieldOperation &op_ref = const_cast<FieldOperation &>(*op);
for (GField &input : op_ref.inputs_) {
if (FieldOperationPtr input_op = input.try_extract_operation()) {
remaining.append(std::move(input_op));
}
}
}
}
void FieldInputs::delete_self()
{
MEM_delete(this);
}
FieldOperationPtr FieldOperation::from(std::shared_ptr<const mf::MultiFunction> fn,
Vector<GField> inputs)
{
return FieldOperationPtr(MEM_new<FieldOperation>(__func__, std::move(fn), std::move(inputs)));
}
FieldOperationPtr FieldOperation::from(const mf::MultiFunction &fn, Vector<GField> inputs)
{
return FieldOperationPtr(MEM_new<FieldOperation>(__func__, fn, std::move(inputs)));
}
/**
* Combine the field inputs from multiple fields. If possible, nothing new is allocated.
*/
static FieldInputsPtr combine_field_inputs(const Span<GField> &fields)
{
/* Try to find an existing #FieldInputsPtr that covers all given fields. */
bool candidate_valid = true;
const FieldInputsPtr *candidate = nullptr;
for (const GField &field : fields) {
const FieldInputsPtr &field_inputs_ptr = field.field_inputs();
if (!field_inputs_ptr) {
continue;
}
if (!candidate) {
candidate = &field_inputs_ptr;
continue;
}
if (field_inputs_ptr == *candidate) {
continue;
}
const FieldInputsPtr *smaller_candidate = candidate;
const FieldInputsPtr *larger_candidate = &field_inputs_ptr;
if ((*smaller_candidate)->inputs.size() > (*larger_candidate)->inputs.size()) {
std::swap(smaller_candidate, larger_candidate);
}
/* Check if the smaller candidate is fully contained in the larger one. */
for (const FieldInput &field_input : (*smaller_candidate)->inputs) {
if (!(*larger_candidate)->inputs.contains(field_input)) {
candidate_valid = false;
break;
}
}
if (!candidate_valid) {
break;
}
candidate = larger_candidate;
}
if (candidate_valid) {
if (candidate) {
return *candidate;
}
return {};
}
/* None of the existing #FieldInputs can be reused, create a new #FieldInputs and add all the
* inputs to it. */
FieldInputs *new_field_inputs = MEM_new<FieldInputs>(__func__);
for (const GField &field : fields) {
const FieldInputsPtr &field_inputs_ptr = field.field_inputs();
if (!field_inputs_ptr) {
continue;
}
for (const FieldInput &field_input : field_inputs_ptr->inputs) {
new_field_inputs->inputs.add(field_input);
}
}
return FieldInputsPtr(new_field_inputs);
}
GField::GField(const GField &other) : variant_(other.variant_)
{
std::visit(
[&]<typename T>(T &v) {
if constexpr (std::is_same_v<T, OwnedConstant>) {
void *new_value = MEM_new_uninitialized_aligned(
v.type->size, v.type->alignment, __func__);
v.type->copy_construct(v.value, new_value);
v.value = new_value;
}
},
variant_);
}
GField::GField(GField &&other) noexcept : variant_(std::move(other.variant_))
{
const CPPType &type = this->cpp_type();
other.variant_ = ConstantRef{&type, type.default_value()};
}
GField &GField::operator=(const GField &other)
{
if (this == &other) {
return *this;
}
this->~GField();
new (this) GField(other);
return *this;
}
GField &GField::operator=(GField &&other) noexcept
{
if (this == &other) {
return *this;
}
this->~GField();
new (this) GField(std::move(other));
return *this;
}
GField::~GField()
{
std::visit(
[&]<typename T>(T &v) {
if constexpr (std::is_same_v<T, OwnedConstant>) {
v.type->destruct(v.value);
MEM_delete_void(v.value);
}
},
variant_);
}
GFieldRef::GFieldRef(const GField &field)
: variant_(std::visit(
[]<typename T>(const T &v) -> Variant {
if constexpr (std::is_same_v<T, GField::Input>) {
return Input{v.node.get()};
}
else if constexpr (std::is_same_v<T, GField::MultiFn>) {
return MultiFn{v.node.get(), v.output_i};
}
else if constexpr (std::is_same_v<T, GField::FieldRef>) {
/* Should not exist due to #deref_field_ref. */
BLI_assert_unreachable();
return Value{};
}
else if constexpr (GField::is_constant_value_v<T>) {
return Value{v.type, v.value};
}
else {
BLI_assert_unreachable_static_t(T);
}
},
field.deref_field_ref().variant()))
{
}
const FieldInputsPtr &GFieldRef::field_inputs() const
{
static const ImplicitSharingPtr<FieldInputs> empty_inputs;
return std::visit(
[&]<typename T>(const T &v) -> const FieldInputsPtr & {
if constexpr (std::is_same_v<T, Input>) {
return v.node->field_inputs();
}
else if constexpr (std::is_same_v<T, MultiFn>) {
return v.node->field_inputs();
}
else if constexpr (std::is_same_v<T, Value>) {
return empty_inputs;
}
else {
BLI_assert_unreachable_static_t(T);
}
},
variant_);
}
bool operator==(const GFieldRef &a, const GFieldRef &b)
{
return std::visit(
[&]<typename T>(const T &v_a) -> bool {
if constexpr (std::is_same_v<T, GFieldRef::Value>) {
if (const auto *v_b = std::get_if<GFieldRef::Value>(&b.variant())) {
if (v_a.type != v_b->type) {
return false;
}
if (v_a.value == v_b->value) {
/* This may return true even if the values don't compare equal, e.g. due to NaN
* values. */
return true;
}
return v_a.type->is_equal_or_false(v_a.value, v_b->value);
}
return false;
}
else if constexpr (std::is_same_v<T, GFieldRef::Input>) {
if (const auto *v_b = std::get_if<GFieldRef::Input>(&b.variant())) {
return v_a.node == v_b->node;
}
return false;
}
else if constexpr (std::is_same_v<T, GFieldRef::MultiFn>) {
if (const auto *v_b = std::get_if<GFieldRef::MultiFn>(&b.variant())) {
return v_a.node == v_b->node && v_a.output_i == v_b->output_i;
}
return false;
}
else {
BLI_assert_unreachable_static_t(T);
}
},
a.variant());
}
uint64_t GFieldRef::hash() const
{
return std::visit(
[&]<typename T>(const T &v) -> uint64_t {
if constexpr (std::is_same_v<T, Value>) {
return v.type->hash_or_fallback(v.value, uint64_t(v.type));
}
else if constexpr (std::is_same_v<T, Input>) {
return get_default_hash(v.node);
}
else if constexpr (std::is_same_v<T, MultiFn>) {
return get_default_hash(v.node, v.output_i);
}
else {
BLI_assert_unreachable_static_t(T);
}
},
variant_);
}
FieldOperation::FieldOperation(std::shared_ptr<const mf::MultiFunction> fn, Vector<GField> inputs)
: FieldOperation(*fn, std::move(inputs))
{
owned_fn_ = std::move(fn);
}
FieldOperation::FieldOperation(const mf::MultiFunction &fn, Vector<GField> inputs)
: inputs_(inputs), fn_(&fn)
{
field_inputs_ = combine_field_inputs(inputs_);
}
const CPPType &FieldOperation::output_cpp_type(const int output_i) const
{
int count = 0;
for (const int param_index : fn_->param_indices()) {
const mf::ParamType param_type = fn_->param_type(param_index);
if (param_type.is_output()) {
if (count == output_i) {
return param_type.data_type().single_type();
}
count++;
}
}
BLI_assert_unreachable();
return CPPType::get<float>();
}
const FieldInputsPtr &GField::field_inputs() const
{
static const ImplicitSharingPtr<FieldInputs> empty_inputs;
return std::visit(
[]<typename T>(const T &v) -> const FieldInputsPtr & {
if constexpr (is_same_any_v<T, Input, MultiFn>) {
return v.node->field_inputs();
}
else if constexpr (std::is_same_v<T, FieldRef>) {
return v.field_ref->field_inputs();
}
else if constexpr (is_same_any_v<T, ConstantRef, TrivialInlineConstant, OwnedConstant>) {
return empty_inputs;
}
else {
BLI_assert_unreachable_static_t(T);
}
},
this->variant_);
}
GVArray FieldContext::get_varray_for_input(const FieldInput &field_input,
const IndexMask &mask,
ResourceScope &scope) const
{
/* By default ask the field input to create the varray. Another field context might overwrite
* the context here. */
return field_input.get_varray_for_context(*this, mask, scope);
}
IndexFieldInput::IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") {}
GVArray IndexFieldInput::get_index_varray(const IndexMask &mask)
{
auto index_func = [](int i) { return i; };
return VArray<int>::from_func(mask.min_array_size(), index_func);
}
GVArray IndexFieldInput::get_varray_for_context(const fn::FieldContext & /*context*/,
const IndexMask &mask,
ResourceScope & /*scope*/) const
{
/* TODO: Investigate a similar method to IndexRange::as_span() */
return get_index_varray(mask);
}
void IndexFieldInput::hash_unique(UniqueHashBytes &hash,
fn::FieldHashDeep & /*deep_hash_cache*/) const
{
static constexpr int8_t id = 0;
hash.add(&id);
}
const Field<int> &IndexFieldInput::get_field()
{
static const Field<int> field = Field<int>::from_input<IndexFieldInput>();
static const Field<int> field_ref = Field<int>::from_non_owning_ref(field);
return field_ref;
}
Field<bool> invert_boolean_field(const Field<bool> &field)
{
const mf::MultiFunction &not_fn = fn::multi_function::registry::lookup("!bool"_ustr);
auto not_op = FieldOperation::from(not_fn, {field});
return GField(not_op, 0).typed<bool>();
}
} // namespace blender::fn