Add Chromium-only Blender WebEngine parity work
This commit is contained in:
567
blender-5.2.0/source/blender/functions/intern/field.cc
Normal file
567
blender-5.2.0/source/blender/functions/intern/field.cc
Normal file
@@ -0,0 +1,567 @@
|
||||
/* 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>(¤t.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 ¬_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
|
||||
@@ -0,0 +1,639 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "FN_field_evaluation.hh"
|
||||
#include "FN_multi_function.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"
|
||||
#include "FN_multi_function_procedure_optimization.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Field Evaluation
|
||||
* \{ */
|
||||
|
||||
struct FieldTreeInfo {
|
||||
FieldHashDeep deep_hashes;
|
||||
/**
|
||||
* When fields are built, they only have references to the fields that they depend on. This map
|
||||
* allows traversal of fields in the opposite direction. So for every field it stores the other
|
||||
* fields that depend on it directly.
|
||||
*/
|
||||
MultiValueMap<UniqueHash, UniqueHash> field_users;
|
||||
/**
|
||||
* The same field input may exist in the field tree as separate nodes due to the way
|
||||
* the tree is constructed. This set contains every different input only once.
|
||||
*/
|
||||
VectorSet<UniqueHash> deduplicated_input_hashes;
|
||||
Vector<GFieldRef> deduplicated_inputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects some information from the field tree that is required by later steps.
|
||||
*/
|
||||
static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields)
|
||||
{
|
||||
PRF_scope(ProfileCategory::Default);
|
||||
FieldTreeInfo field_tree_info;
|
||||
|
||||
Stack<GFieldRef> fields_to_check;
|
||||
Set<GFieldRef> handled_fields;
|
||||
|
||||
for (GFieldRef field : entry_fields) {
|
||||
if (handled_fields.add(field)) {
|
||||
fields_to_check.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
while (!fields_to_check.is_empty()) {
|
||||
const GFieldRef &field = fields_to_check.pop();
|
||||
const GFieldRef::Variant &field_variant = field.variant();
|
||||
const UniqueHash hash = field_tree_info.deep_hashes.ensure(field);
|
||||
std::visit(
|
||||
[&]<typename T>(const T &v) {
|
||||
if constexpr (std::is_same_v<T, GFieldRef::Input>) {
|
||||
if (field_tree_info.deduplicated_input_hashes.add(hash)) {
|
||||
field_tree_info.deduplicated_inputs.append(field);
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::MultiFn>) {
|
||||
for (const GField &input_field : v.node->inputs()) {
|
||||
const UniqueHash input_hash = field_tree_info.deep_hashes.lookup(input_field);
|
||||
field_tree_info.field_users.add(input_hash, hash);
|
||||
if (handled_fields.add(input_field)) {
|
||||
fields_to_check.push(input_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::Value>) {
|
||||
/* Nothing to do. */
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable_static_t(T);
|
||||
}
|
||||
},
|
||||
field_variant);
|
||||
}
|
||||
return field_tree_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data from the context that is passed as input into the field.
|
||||
*/
|
||||
static Vector<GVArray> get_field_context_inputs(ResourceScope &scope,
|
||||
const IndexMask &mask,
|
||||
const FieldContext &context,
|
||||
const Span<GFieldRef> field_inputs)
|
||||
{
|
||||
Vector<GVArray> field_context_inputs;
|
||||
for (const GFieldRef &input_field : field_inputs) {
|
||||
const FieldInput &field_input = *std::get<GFieldRef::Input>(input_field.variant()).node;
|
||||
GVArray varray = context.get_varray_for_input(field_input, mask, scope);
|
||||
if (!varray) {
|
||||
const CPPType &type = field_input.cpp_type();
|
||||
varray = GVArray::from_single_default(type, mask.min_array_size());
|
||||
}
|
||||
field_context_inputs.append(std::move(varray));
|
||||
}
|
||||
return field_context_inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* \return A set that contains all fields from the field tree that depend on an input that varies
|
||||
* for different indices.
|
||||
*/
|
||||
static Set<UniqueHash> find_varying_fields(const FieldTreeInfo &field_tree_info,
|
||||
const Span<GVArray> field_context_inputs)
|
||||
{
|
||||
Set<UniqueHash> found_fields;
|
||||
Stack<UniqueHash> fields_to_check;
|
||||
|
||||
/* The varying fields are the ones that depend on inputs that are not constant. Therefore we
|
||||
* start the tree search at the non-constant input fields and traverse through all fields that
|
||||
* depend on them. */
|
||||
for (const int input_i : field_tree_info.deduplicated_inputs.index_range()) {
|
||||
const GVArray &varray = field_context_inputs[input_i];
|
||||
if (varray.is_single()) {
|
||||
continue;
|
||||
}
|
||||
const UniqueHash &field = field_tree_info.deduplicated_input_hashes[input_i];
|
||||
for (const UniqueHash &user : field_tree_info.field_users.lookup(field)) {
|
||||
if (found_fields.add(user)) {
|
||||
fields_to_check.push(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!fields_to_check.is_empty()) {
|
||||
const UniqueHash &field = fields_to_check.pop();
|
||||
for (const UniqueHash &user : field_tree_info.field_users.lookup(field)) {
|
||||
if (found_fields.add(user)) {
|
||||
fields_to_check.push(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the #procedure so that it computes the fields.
|
||||
*/
|
||||
static void build_multi_function_procedure_for_fields(mf::Procedure &procedure,
|
||||
ResourceScope &scope,
|
||||
const FieldTreeInfo &field_tree_info,
|
||||
Span<GFieldRef> output_fields)
|
||||
{
|
||||
PRF_scope(ProfileCategory::Default);
|
||||
mf::ProcedureBuilder builder{procedure};
|
||||
/* Every input, intermediate and output field corresponds to a variable in the procedure. */
|
||||
Map<UniqueHash, mf::Variable *> variable_by_field;
|
||||
|
||||
/* Start by adding the field inputs as parameters to the procedure. */
|
||||
for (const GFieldRef &input_field : field_tree_info.deduplicated_inputs) {
|
||||
const UniqueHash input_hash = field_tree_info.deep_hashes.lookup(input_field);
|
||||
const FieldInput &field_input = *std::get<GFieldRef::Input>(input_field.variant()).node;
|
||||
mf::Variable &variable = builder.add_input_parameter(
|
||||
mf::DataType::ForSingle(field_input.cpp_type()), field_input.debug_name());
|
||||
variable_by_field.add_new(input_hash, &variable);
|
||||
}
|
||||
|
||||
/* Utility struct that is used to do proper depth first search traversal of the tree below. */
|
||||
struct FieldWithIndex {
|
||||
GFieldRef field;
|
||||
int current_input_index = 0;
|
||||
};
|
||||
|
||||
for (GFieldRef field : output_fields) {
|
||||
/* We start a new stack for each output field to make sure that a field pushed later to the
|
||||
* stack never depends on a field that was pushed before. */
|
||||
Stack<FieldWithIndex> fields_to_check;
|
||||
fields_to_check.push({field, 0});
|
||||
while (!fields_to_check.is_empty()) {
|
||||
FieldWithIndex &field_with_index = fields_to_check.peek();
|
||||
const GFieldRef &field = field_with_index.field;
|
||||
const UniqueHash field_hash = field_tree_info.deep_hashes.lookup(field);
|
||||
if (variable_by_field.contains(field_hash)) {
|
||||
/* The field has been handled already. */
|
||||
fields_to_check.pop();
|
||||
continue;
|
||||
}
|
||||
const GFieldRef::Variant &field_variant = field.variant();
|
||||
std::visit(
|
||||
[&]<typename T>(const T &v) {
|
||||
if constexpr (std::is_same_v<T, GFieldRef::Input>) {
|
||||
/* Variables for inputs are added above. */
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::MultiFn>) {
|
||||
const FieldOperation &field_multi_fn = *v.node;
|
||||
const Span<GField> fn_inputs = field_multi_fn.inputs();
|
||||
|
||||
if (field_with_index.current_input_index < fn_inputs.size()) {
|
||||
/* Not all inputs are handled yet. Push the next input field to the stack and
|
||||
* increment the input index. */
|
||||
fields_to_check.push({fn_inputs[field_with_index.current_input_index]});
|
||||
field_with_index.current_input_index++;
|
||||
}
|
||||
else {
|
||||
/* All inputs variables are ready, now gather all variables that are used by the
|
||||
* function and call it. */
|
||||
const mf::MultiFunction &multi_function = field_multi_fn.multi_function();
|
||||
Array<mf::Variable *, 8> variables(multi_function.param_amount());
|
||||
|
||||
int param_input_index = 0;
|
||||
int param_output_index = 0;
|
||||
for (const int param_index : multi_function.param_indices()) {
|
||||
const mf::ParamType param_type = multi_function.param_type(param_index);
|
||||
const mf::ParamType::InterfaceType interface_type = param_type.interface_type();
|
||||
if (interface_type == mf::ParamType::Input) {
|
||||
const GField &input_field = fn_inputs[param_input_index];
|
||||
const UniqueHash input_hash = field_tree_info.deep_hashes.lookup(input_field);
|
||||
variables[param_index] = variable_by_field.lookup(input_hash);
|
||||
param_input_index++;
|
||||
}
|
||||
else if (interface_type == mf::ParamType::Output) {
|
||||
const GFieldRef output_field{field_multi_fn, param_output_index};
|
||||
/* NOTE: This abuses the deep hash cache as a set of the fields in the tree. At
|
||||
* the cost of either hashing this output field or building a separate set of
|
||||
* visited GFieldRefs, we wouldn't have to use the cache in this way. */
|
||||
if (!field_tree_info.deep_hashes.contains(output_field)) {
|
||||
/* Ignored outputs don't need a variable. */
|
||||
variables[param_index] = nullptr;
|
||||
}
|
||||
else {
|
||||
/* Create a new variable for used outputs. */
|
||||
mf::Variable &new_variable = procedure.new_variable(param_type.data_type());
|
||||
variables[param_index] = &new_variable;
|
||||
const UniqueHash output_hash = field_tree_info.deep_hashes.lookup(
|
||||
output_field);
|
||||
variable_by_field.add_new(output_hash, &new_variable);
|
||||
}
|
||||
param_output_index++;
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
builder.add_call_with_all_variables(multi_function, variables);
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::Value>) {
|
||||
const mf::MultiFunction &fn =
|
||||
procedure.construct_function<mf::CustomMF_GenericConstant>(
|
||||
*v.type, v.value, false);
|
||||
mf::Variable &new_variable = *builder.add_call<1>(fn)[0];
|
||||
variable_by_field.add_new(field_hash, &new_variable);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable_static_t(T);
|
||||
}
|
||||
},
|
||||
field_variant);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add output parameters to the procedure. */
|
||||
Set<mf::Variable *> output_variables;
|
||||
for (const GFieldRef &field : output_fields) {
|
||||
const UniqueHash field_hash = field_tree_info.deep_hashes.lookup(field);
|
||||
mf::Variable *variable = variable_by_field.lookup(field_hash);
|
||||
if (!output_variables.add(variable)) {
|
||||
/* One variable can be output at most once. To output the same value twice, we have to make
|
||||
* a copy first. */
|
||||
const mf::MultiFunction ©_fn = scope.construct<mf::CustomMF_GenericCopy>(
|
||||
variable->data_type());
|
||||
variable = builder.add_call<1>(copy_fn, {variable})[0];
|
||||
output_variables.add(variable);
|
||||
}
|
||||
builder.add_output_parameter(*variable);
|
||||
}
|
||||
|
||||
for (mf::Variable *variable : procedure.variables()) {
|
||||
if (!output_variables.contains(variable)) {
|
||||
builder.add_destruct(*variable);
|
||||
}
|
||||
}
|
||||
|
||||
mf::ReturnInstruction &return_instr = builder.add_return();
|
||||
|
||||
mf::procedure_optimization::move_destructs_up(procedure, return_instr);
|
||||
|
||||
procedure.prepare_for_execution();
|
||||
|
||||
// std::cout << procedure.to_dot() << "\n";
|
||||
BLI_assert(procedure.validate());
|
||||
}
|
||||
|
||||
Vector<GVArray> evaluate_fields(ResourceScope &scope,
|
||||
Span<GFieldRef> fields_to_evaluate,
|
||||
const IndexMask &mask,
|
||||
const FieldContext &context,
|
||||
Span<GVMutableArray> dst_varrays)
|
||||
{
|
||||
PRF_scope(ProfileCategory::Default);
|
||||
Vector<GVArray> varrays(fields_to_evaluate.size());
|
||||
Array<bool> is_output_written_to_dst(fields_to_evaluate.size(), false);
|
||||
const int array_size = mask.min_array_size();
|
||||
|
||||
if (mask.is_empty()) {
|
||||
for (const int i : fields_to_evaluate.index_range()) {
|
||||
const CPPType &type = fields_to_evaluate[i].cpp_type();
|
||||
varrays[i] = GVArray::from_empty(type);
|
||||
}
|
||||
return varrays;
|
||||
}
|
||||
|
||||
/* Destination arrays are optional. Create a small utility method to access them. */
|
||||
auto get_dst_varray = [&](int index) -> GVMutableArray {
|
||||
if (dst_varrays.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
const GVMutableArray &varray = dst_varrays[index];
|
||||
if (!varray) {
|
||||
return {};
|
||||
}
|
||||
BLI_assert(varray.size() >= array_size);
|
||||
return varray;
|
||||
};
|
||||
|
||||
/* Traverse the field tree and prepare some data that is used in later steps. */
|
||||
FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate);
|
||||
|
||||
/* Get inputs that will be passed into the field when evaluated. */
|
||||
Vector<GVArray> field_context_inputs = get_field_context_inputs(
|
||||
scope, mask, context, field_tree_info.deduplicated_inputs);
|
||||
|
||||
Set<UniqueHash> varying_fields = find_varying_fields(field_tree_info, field_context_inputs);
|
||||
|
||||
/* Process fields that can output a VArray directly, and separate the rest of the fields into
|
||||
* two categories: those that are constant and need to be evaluated only once, and those that
|
||||
* need to be evaluated for every index. */
|
||||
Vector<GFieldRef> varying_fields_to_evaluate;
|
||||
Vector<int> varying_field_indices;
|
||||
Vector<GFieldRef> constant_fields_to_evaluate;
|
||||
Vector<int> constant_field_indices;
|
||||
for (const int out_index : fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = fields_to_evaluate[out_index];
|
||||
const GFieldRef::Variant &field_variant = field.variant();
|
||||
std::visit(
|
||||
[&]<typename T>(const T &v) {
|
||||
if constexpr (std::is_same_v<T, GFieldRef::Input>) {
|
||||
const UniqueHash hash = field_tree_info.deep_hashes.lookup(field);
|
||||
const int input_i = field_tree_info.deduplicated_input_hashes.index_of(hash);
|
||||
const GVArray &varray = field_context_inputs[input_i];
|
||||
varrays[out_index] = varray;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::MultiFn>) {
|
||||
const UniqueHash hash = field_tree_info.deep_hashes.lookup(field);
|
||||
if (varying_fields.contains(hash)) {
|
||||
varying_fields_to_evaluate.append(field);
|
||||
varying_field_indices.append(out_index);
|
||||
}
|
||||
else {
|
||||
constant_fields_to_evaluate.append(field);
|
||||
constant_field_indices.append(out_index);
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GFieldRef::Value>) {
|
||||
varrays[out_index] = GVArray::from_single_ref(*v.type, mask.min_array_size(), v.value);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable_static_t(T);
|
||||
}
|
||||
},
|
||||
field_variant);
|
||||
}
|
||||
|
||||
/* Evaluate varying fields if necessary. */
|
||||
if (!varying_fields_to_evaluate.is_empty()) {
|
||||
/* Build the procedure for those fields. */
|
||||
mf::Procedure procedure;
|
||||
build_multi_function_procedure_for_fields(
|
||||
procedure, scope, field_tree_info, varying_fields_to_evaluate);
|
||||
mf::ProcedureExecutor procedure_executor{procedure};
|
||||
|
||||
mf::ParamsBuilder mf_params{procedure_executor, &mask};
|
||||
mf::ContextBuilder mf_context;
|
||||
|
||||
/* Provide inputs to the procedure executor. */
|
||||
for (const GVArray &varray : field_context_inputs) {
|
||||
mf_params.add_readonly_single_input(varray);
|
||||
}
|
||||
|
||||
for (const int i : varying_fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = varying_fields_to_evaluate[i];
|
||||
const CPPType &type = field.cpp_type();
|
||||
const int out_index = varying_field_indices[i];
|
||||
|
||||
/* Try to get an existing virtual array that the result should be written into. */
|
||||
GVMutableArray dst_varray = get_dst_varray(out_index);
|
||||
void *buffer;
|
||||
if (!dst_varray || !dst_varray.is_span()) {
|
||||
/* Allocate a new buffer for the computed result. */
|
||||
buffer = scope.allocator().allocate_array(type, array_size);
|
||||
|
||||
if (!type.is_trivially_destructible) {
|
||||
/* Destruct values in the end. */
|
||||
scope.add_destruct_call(
|
||||
[buffer, mask, &type]() { type.destruct_indices(buffer, mask); });
|
||||
}
|
||||
|
||||
varrays[out_index] = GVArray::from_span({type, buffer, array_size});
|
||||
}
|
||||
else {
|
||||
/* Write the result into the existing span. */
|
||||
buffer = dst_varray.get_internal_span().data();
|
||||
|
||||
varrays[out_index] = dst_varray;
|
||||
is_output_written_to_dst[out_index] = true;
|
||||
}
|
||||
|
||||
/* Pass output buffer to the procedure executor. */
|
||||
const GMutableSpan span{type, buffer, array_size};
|
||||
mf_params.add_uninitialized_single_output(span);
|
||||
}
|
||||
|
||||
procedure_executor.call_auto(mask, mf_params, mf_context);
|
||||
}
|
||||
|
||||
/* Evaluate constant fields if necessary. */
|
||||
if (!constant_fields_to_evaluate.is_empty()) {
|
||||
/* Build the procedure for those fields. */
|
||||
mf::Procedure procedure;
|
||||
build_multi_function_procedure_for_fields(
|
||||
procedure, scope, field_tree_info, constant_fields_to_evaluate);
|
||||
mf::ProcedureExecutor procedure_executor{procedure};
|
||||
const IndexMask mask(1);
|
||||
mf::ParamsBuilder mf_params{procedure_executor, &mask};
|
||||
mf::ContextBuilder mf_context;
|
||||
|
||||
/* Provide inputs to the procedure executor. */
|
||||
for (const GVArray &varray : field_context_inputs) {
|
||||
mf_params.add_readonly_single_input(varray);
|
||||
}
|
||||
|
||||
for (const int i : constant_fields_to_evaluate.index_range()) {
|
||||
const GFieldRef &field = constant_fields_to_evaluate[i];
|
||||
const CPPType &type = field.cpp_type();
|
||||
/* Allocate memory where the computed value will be stored in. */
|
||||
void *buffer = scope.allocate_owned(type);
|
||||
|
||||
/* Pass output buffer to the procedure executor. */
|
||||
mf_params.add_uninitialized_single_output({type, buffer, 1});
|
||||
|
||||
/* Create virtual array that can be used after the procedure has been executed below. */
|
||||
const int out_index = constant_field_indices[i];
|
||||
varrays[out_index] = GVArray::from_single_ref(type, array_size, buffer);
|
||||
}
|
||||
|
||||
procedure_executor.call(mask, mf_params, mf_context);
|
||||
}
|
||||
|
||||
/* Copy data to supplied destination arrays if necessary. In some cases the evaluation above
|
||||
* has written the computed data in the right place already. */
|
||||
if (!dst_varrays.is_empty()) {
|
||||
for (const int out_index : fields_to_evaluate.index_range()) {
|
||||
GVMutableArray dst_varray = get_dst_varray(out_index);
|
||||
if (!dst_varray) {
|
||||
/* Caller did not provide a destination for this output. */
|
||||
continue;
|
||||
}
|
||||
const GVArray &computed_varray = varrays[out_index];
|
||||
BLI_assert(computed_varray.type() == dst_varray.type());
|
||||
if (is_output_written_to_dst[out_index]) {
|
||||
/* The result has been written into the destination provided by the caller already. */
|
||||
continue;
|
||||
}
|
||||
/* Still have to copy over the data in the destination provided by the caller. */
|
||||
if (dst_varray.is_span()) {
|
||||
computed_varray.type().default_construct_indices(dst_varray.get_internal_span().data(),
|
||||
mask);
|
||||
array_utils::copy(computed_varray,
|
||||
mask,
|
||||
dst_varray.get_internal_span().take_front(mask.min_array_size()));
|
||||
}
|
||||
else {
|
||||
/* Slower materialize into a different structure. */
|
||||
const CPPType &type = computed_varray.type();
|
||||
threading::parallel_for(mask.index_range(), 2048, [&](const IndexRange range) {
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
mask.slice(range).foreach_segment([&](auto segment) {
|
||||
for (const int i : segment) {
|
||||
computed_varray.get_to_uninitialized(i, buffer);
|
||||
dst_varray.set_by_relocate(i, buffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
varrays[out_index] = dst_varray;
|
||||
}
|
||||
}
|
||||
return varrays;
|
||||
}
|
||||
|
||||
void evaluate_constant_field(const GField &field, void *r_value)
|
||||
{
|
||||
if (field.depends_on_input()) {
|
||||
const CPPType &type = field.cpp_type();
|
||||
type.value_initialize(r_value);
|
||||
return;
|
||||
}
|
||||
|
||||
AlignedBuffer<512, 64> local_buffer;
|
||||
ResourceScope scope(local_buffer);
|
||||
FieldContext context;
|
||||
Vector<GVArray> varrays = evaluate_fields(scope, {field}, IndexRange(1), context);
|
||||
varrays[0].get_to_uninitialized(0, r_value);
|
||||
}
|
||||
|
||||
GField make_field_constant_if_possible(GField field)
|
||||
{
|
||||
if (field.depends_on_input()) {
|
||||
return field;
|
||||
}
|
||||
const CPPType &type = field.cpp_type();
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
evaluate_constant_field(field, buffer);
|
||||
GField new_field = GField::from_constant(type, buffer);
|
||||
type.destruct(buffer);
|
||||
return new_field;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #FieldEvaluator
|
||||
* \{ */
|
||||
|
||||
static IndexMask index_mask_from_selection(const IndexMask full_mask,
|
||||
const VArray<bool> &selection,
|
||||
ResourceScope &scope)
|
||||
{
|
||||
return IndexMask::from_bools(full_mask, selection, scope.allocator());
|
||||
}
|
||||
|
||||
int FieldEvaluator::add_with_destination(GField field, GVMutableArray dst)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(dst);
|
||||
output_pointer_infos_.append({});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
|
||||
{
|
||||
return this->add_with_destination(std::move(field), GVMutableArray::from_span(dst));
|
||||
}
|
||||
|
||||
int FieldEvaluator::add(GField field, GVArray *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*/) {
|
||||
*static_cast<GVArray *>(dst) = varray;
|
||||
}});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
int FieldEvaluator::add(GField field)
|
||||
{
|
||||
const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
|
||||
dst_varrays_.append(nullptr);
|
||||
output_pointer_infos_.append({});
|
||||
return field_index;
|
||||
}
|
||||
|
||||
static IndexMask evaluate_selection(const Field<bool> &selection_field,
|
||||
const FieldContext &context,
|
||||
const IndexMask &full_mask,
|
||||
ResourceScope &scope)
|
||||
{
|
||||
VArray<bool> selection =
|
||||
evaluate_fields(scope, {selection_field}, full_mask, context)[0].typed<bool>();
|
||||
return index_mask_from_selection(full_mask, selection, scope);
|
||||
}
|
||||
|
||||
void FieldEvaluator::evaluate()
|
||||
{
|
||||
BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice.");
|
||||
|
||||
selection_mask_ = selection_field_ ?
|
||||
evaluate_selection(*selection_field_, context_, mask_, scope_) :
|
||||
mask_;
|
||||
|
||||
Vector<GFieldRef> fields;
|
||||
fields.reserve(fields_to_evaluate_.size());
|
||||
static constexpr bool true_value = true;
|
||||
for (const int i : fields_to_evaluate_.index_range()) {
|
||||
const GField &field = fields_to_evaluate_[i];
|
||||
if (field == selection_field_) {
|
||||
/* Avoid evaluating the selection field again. */
|
||||
fields.append(GFieldRef::from_constant(CPPType::get<bool>(), &true_value));
|
||||
}
|
||||
else {
|
||||
fields.append(field);
|
||||
}
|
||||
}
|
||||
evaluated_varrays_ = evaluate_fields(scope_, fields, selection_mask_, context_, dst_varrays_);
|
||||
BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size());
|
||||
for (const int i : fields_to_evaluate_.index_range()) {
|
||||
OutputPointerInfo &info = output_pointer_infos_[i];
|
||||
if (info.dst != nullptr) {
|
||||
info.set(info.dst, evaluated_varrays_[i], scope_);
|
||||
}
|
||||
}
|
||||
is_evaluated_ = true;
|
||||
}
|
||||
|
||||
IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
|
||||
{
|
||||
VArray<bool> varray = this->get_evaluated(field_index).typed<bool>();
|
||||
|
||||
if (varray.is_single()) {
|
||||
if (varray.get_internal_single()) {
|
||||
return IndexRange(varray.size());
|
||||
}
|
||||
return IndexRange(0);
|
||||
}
|
||||
return index_mask_from_selection(mask_, varray, scope_);
|
||||
}
|
||||
|
||||
IndexMask FieldEvaluator::get_evaluated_selection_as_mask() const
|
||||
{
|
||||
BLI_assert(is_evaluated_);
|
||||
return selection_mask_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::fn
|
||||
@@ -0,0 +1,71 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
std::string LazyFunction::name() const
|
||||
{
|
||||
return debug_name_;
|
||||
}
|
||||
|
||||
std::string LazyFunction::input_name(int index) const
|
||||
{
|
||||
return inputs_[index].debug_name;
|
||||
}
|
||||
|
||||
std::string LazyFunction::output_name(int index) const
|
||||
{
|
||||
return outputs_[index].debug_name;
|
||||
}
|
||||
|
||||
void *LazyFunction::init_storage(LinearAllocator<> & /*allocator*/) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LazyFunction::destruct_storage(void *storage) const
|
||||
{
|
||||
BLI_assert(storage == nullptr);
|
||||
UNUSED_VARS_NDEBUG(storage);
|
||||
}
|
||||
|
||||
void LazyFunction::possible_output_dependencies(const int /*output_index*/,
|
||||
const FunctionRef<void(Span<int>)> fn) const
|
||||
{
|
||||
/* The output depends on all inputs by default. */
|
||||
Vector<int, 16> indices(inputs_.size());
|
||||
for (const int i : inputs_.index_range()) {
|
||||
indices[i] = i;
|
||||
}
|
||||
fn(indices);
|
||||
}
|
||||
|
||||
bool LazyFunction::always_used_inputs_available(const Params ¶ms) const
|
||||
{
|
||||
if (allow_missing_requested_inputs_) {
|
||||
return true;
|
||||
}
|
||||
for (const int i : inputs_.index_range()) {
|
||||
const Input &fn_input = inputs_[i];
|
||||
if (fn_input.usage == ValueUsage::Used) {
|
||||
if (params.try_get_input_data_ptr(i) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Params::try_enable_multi_threading_impl()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
||||
@@ -0,0 +1,144 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup fn
|
||||
*/
|
||||
|
||||
#include "FN_lazy_function_execute.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name BasicParams.
|
||||
* \{ */
|
||||
|
||||
BasicParams::BasicParams(const LazyFunction &fn,
|
||||
const Span<GMutablePointer> inputs,
|
||||
const Span<GMutablePointer> outputs,
|
||||
MutableSpan<std::optional<ValueUsage>> input_usages,
|
||||
Span<ValueUsage> output_usages,
|
||||
MutableSpan<bool> set_outputs)
|
||||
: Params(fn, true),
|
||||
inputs_(inputs),
|
||||
outputs_(outputs),
|
||||
input_usages_(input_usages),
|
||||
output_usages_(output_usages),
|
||||
set_outputs_(set_outputs)
|
||||
{
|
||||
}
|
||||
|
||||
void *BasicParams::try_get_input_data_ptr_impl(const int index) const
|
||||
{
|
||||
return inputs_[index].get();
|
||||
}
|
||||
|
||||
void *BasicParams::try_get_input_data_ptr_or_request_impl(const int index)
|
||||
{
|
||||
void *value = inputs_[index].get();
|
||||
if (value == nullptr) {
|
||||
input_usages_[index] = ValueUsage::Used;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void *BasicParams::get_output_data_ptr_impl(const int index)
|
||||
{
|
||||
return outputs_[index].get();
|
||||
}
|
||||
|
||||
void BasicParams::output_set_impl(const int index)
|
||||
{
|
||||
set_outputs_[index] = true;
|
||||
}
|
||||
|
||||
bool BasicParams::output_was_set_impl(const int index) const
|
||||
{
|
||||
return set_outputs_[index];
|
||||
}
|
||||
|
||||
ValueUsage BasicParams::get_output_usage_impl(const int index) const
|
||||
{
|
||||
return output_usages_[index];
|
||||
}
|
||||
|
||||
void BasicParams::set_input_unused_impl(const int index)
|
||||
{
|
||||
input_usages_[index] = ValueUsage::Unused;
|
||||
}
|
||||
|
||||
bool BasicParams::try_enable_multi_threading_impl()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name RemappedParams.
|
||||
* \{ */
|
||||
|
||||
RemappedParams::RemappedParams(const LazyFunction &fn,
|
||||
Params &base_params,
|
||||
const Span<int> input_map,
|
||||
const Span<int> output_map,
|
||||
bool &multi_threading_enabled)
|
||||
: Params(fn, multi_threading_enabled),
|
||||
base_params_(base_params),
|
||||
input_map_(input_map),
|
||||
output_map_(output_map),
|
||||
multi_threading_enabled_(multi_threading_enabled)
|
||||
{
|
||||
}
|
||||
|
||||
void *RemappedParams::try_get_input_data_ptr_impl(const int index) const
|
||||
{
|
||||
return base_params_.try_get_input_data_ptr(input_map_[index]);
|
||||
}
|
||||
|
||||
void *RemappedParams::try_get_input_data_ptr_or_request_impl(const int index)
|
||||
{
|
||||
return base_params_.try_get_input_data_ptr_or_request(input_map_[index]);
|
||||
}
|
||||
|
||||
void *RemappedParams::get_output_data_ptr_impl(const int index)
|
||||
{
|
||||
return base_params_.get_output_data_ptr(output_map_[index]);
|
||||
}
|
||||
|
||||
void RemappedParams::output_set_impl(const int index)
|
||||
{
|
||||
base_params_.output_set(output_map_[index]);
|
||||
}
|
||||
|
||||
bool RemappedParams::output_was_set_impl(const int index) const
|
||||
{
|
||||
return base_params_.output_was_set(output_map_[index]);
|
||||
}
|
||||
|
||||
lf::ValueUsage RemappedParams::get_output_usage_impl(const int index) const
|
||||
{
|
||||
return base_params_.get_output_usage(output_map_[index]);
|
||||
}
|
||||
|
||||
void RemappedParams::set_input_unused_impl(const int index)
|
||||
{
|
||||
base_params_.set_input_unused(input_map_[index]);
|
||||
}
|
||||
|
||||
bool RemappedParams::try_enable_multi_threading_impl()
|
||||
{
|
||||
if (multi_threading_enabled_) {
|
||||
return true;
|
||||
}
|
||||
if (base_params_.try_enable_multi_threading()) {
|
||||
multi_threading_enabled_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
||||
@@ -0,0 +1,255 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_dot_export.hh"
|
||||
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
Graph::Graph(const StringRef name)
|
||||
{
|
||||
name_ = allocator_.copy_string(name);
|
||||
graph_input_node_ = allocator_.construct<InterfaceNode>().release();
|
||||
graph_output_node_ = allocator_.construct<InterfaceNode>().release();
|
||||
nodes_.append(graph_input_node_);
|
||||
nodes_.append(graph_output_node_);
|
||||
}
|
||||
|
||||
Graph::~Graph()
|
||||
{
|
||||
for (FunctionNode *node : this->function_nodes()) {
|
||||
for (InputSocket *socket : node->inputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
for (OutputSocket *socket : node->outputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
std::destroy_at(node);
|
||||
}
|
||||
for (const InterfaceNode *node : {graph_input_node_, graph_output_node_}) {
|
||||
for (InputSocket *socket : node->inputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
for (OutputSocket *socket : node->outputs_) {
|
||||
std::destroy_at(socket);
|
||||
}
|
||||
std::destroy_at(node);
|
||||
}
|
||||
}
|
||||
|
||||
FunctionNode &Graph::add_function(const LazyFunction &fn)
|
||||
{
|
||||
const Span<Input> inputs = fn.inputs();
|
||||
const Span<Output> outputs = fn.outputs();
|
||||
|
||||
FunctionNode &node = *allocator_.construct<FunctionNode>().release();
|
||||
node.fn_ = &fn;
|
||||
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(inputs.size());
|
||||
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(outputs.size());
|
||||
|
||||
for (const int i : inputs.index_range()) {
|
||||
InputSocket &socket = *node.inputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = true;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = inputs[i].type;
|
||||
}
|
||||
for (const int i : outputs.index_range()) {
|
||||
OutputSocket &socket = *node.outputs_[i];
|
||||
socket.index_in_node_ = i;
|
||||
socket.is_input_ = false;
|
||||
socket.node_ = &node;
|
||||
socket.type_ = outputs[i].type;
|
||||
}
|
||||
|
||||
nodes_.append(&node);
|
||||
return node;
|
||||
}
|
||||
|
||||
GraphInputSocket &Graph::add_input(const CPPType &type, std::string name)
|
||||
{
|
||||
GraphInputSocket &socket = *allocator_.construct<GraphInputSocket>().release();
|
||||
socket.is_input_ = false;
|
||||
socket.node_ = graph_input_node_;
|
||||
socket.type_ = &type;
|
||||
socket.index_in_node_ = graph_inputs_.append_and_get_index(&socket);
|
||||
graph_input_node_->outputs_ = graph_inputs_;
|
||||
|
||||
graph_input_node_->socket_names_.append(std::move(name));
|
||||
return socket;
|
||||
}
|
||||
|
||||
GraphOutputSocket &Graph::add_output(const CPPType &type, std::string name)
|
||||
{
|
||||
GraphOutputSocket &socket = *allocator_.construct<GraphOutputSocket>().release();
|
||||
socket.is_input_ = true;
|
||||
socket.node_ = graph_output_node_;
|
||||
socket.type_ = &type;
|
||||
socket.index_in_node_ = graph_outputs_.append_and_get_index(&socket);
|
||||
graph_output_node_->inputs_ = graph_outputs_;
|
||||
|
||||
graph_output_node_->socket_names_.append(std::move(name));
|
||||
return socket;
|
||||
}
|
||||
|
||||
void Graph::add_link(OutputSocket &from, InputSocket &to)
|
||||
{
|
||||
BLI_assert(to.origin_ == nullptr);
|
||||
BLI_assert(from.type_ == to.type_);
|
||||
to.origin_ = &from;
|
||||
from.targets_.append(&to);
|
||||
}
|
||||
|
||||
void Graph::clear_origin(InputSocket &socket)
|
||||
{
|
||||
if (socket.origin_ != nullptr) {
|
||||
socket.origin_->targets_.remove_first_occurrence_and_reorder(&socket);
|
||||
socket.origin_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Graph::update_node_indices()
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
nodes_[i]->index_in_graph_ = i;
|
||||
}
|
||||
}
|
||||
|
||||
void Graph::update_socket_indices()
|
||||
{
|
||||
int socket_counter = 0;
|
||||
for (const int i : nodes_.index_range()) {
|
||||
for (InputSocket *socket : nodes_[i]->inputs()) {
|
||||
socket->index_in_graph_ = socket_counter++;
|
||||
}
|
||||
for (OutputSocket *socket : nodes_[i]->outputs()) {
|
||||
socket->index_in_graph_ = socket_counter++;
|
||||
}
|
||||
}
|
||||
socket_num_ = socket_counter;
|
||||
}
|
||||
|
||||
bool Graph::node_indices_are_valid() const
|
||||
{
|
||||
for (const int i : nodes_.index_range()) {
|
||||
if (nodes_[i]->index_in_graph_ != i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Socket::name() const
|
||||
{
|
||||
if (node_->is_function()) {
|
||||
const FunctionNode &fn_node = static_cast<const FunctionNode &>(*node_);
|
||||
const LazyFunction &fn = fn_node.function();
|
||||
if (is_input_) {
|
||||
return fn.input_name(index_in_node_);
|
||||
}
|
||||
return fn.output_name(index_in_node_);
|
||||
}
|
||||
const InterfaceNode &interface_node = *static_cast<const InterfaceNode *>(node_);
|
||||
return interface_node.socket_names_[index_in_node_];
|
||||
}
|
||||
|
||||
std::string Socket::detailed_name() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << node_->name() << ":" << (is_input_ ? "IN" : "OUT") << ":" << index_in_node_ << ":"
|
||||
<< this->name();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Node::name() const
|
||||
{
|
||||
if (this->is_function()) {
|
||||
return fn_->name();
|
||||
}
|
||||
return "Interface";
|
||||
}
|
||||
|
||||
std::string Graph::ToDotOptions::socket_name(const Socket &socket) const
|
||||
{
|
||||
return socket.name();
|
||||
}
|
||||
|
||||
std::optional<std::string> Graph::ToDotOptions::socket_font_color(const Socket & /*socket*/) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Graph::ToDotOptions::add_edge_attributes(const OutputSocket & /*from*/,
|
||||
const InputSocket & /*to*/,
|
||||
dot_export::DirectedEdge & /*dot_edge*/) const
|
||||
{
|
||||
}
|
||||
|
||||
std::string Graph::to_dot(const ToDotOptions &options) const
|
||||
{
|
||||
dot_export::DirectedGraph digraph;
|
||||
digraph.set_rankdir(dot_export::Attr_rankdir::LeftToRight);
|
||||
|
||||
Map<const Node *, dot_export::NodeWithSocketsRef> dot_nodes;
|
||||
|
||||
for (const Node *node : nodes_) {
|
||||
dot_export::Node &dot_node = digraph.new_node("");
|
||||
if (node->is_interface()) {
|
||||
dot_node.set_background_color("lightblue");
|
||||
}
|
||||
else {
|
||||
dot_node.set_background_color("white");
|
||||
}
|
||||
|
||||
dot_export::NodeWithSockets dot_node_with_sockets;
|
||||
dot_node_with_sockets.node_name = node->name();
|
||||
for (const InputSocket *socket : node->inputs()) {
|
||||
dot_export::NodeWithSockets::Input &dot_input = dot_node_with_sockets.add_input(
|
||||
options.socket_name(*socket));
|
||||
dot_input.fontcolor = options.socket_font_color(*socket);
|
||||
}
|
||||
for (const OutputSocket *socket : node->outputs()) {
|
||||
dot_export::NodeWithSockets::Output &dot_output = dot_node_with_sockets.add_output(
|
||||
options.socket_name(*socket));
|
||||
dot_output.fontcolor = options.socket_font_color(*socket);
|
||||
}
|
||||
|
||||
dot_nodes.add_new(node, dot_export::NodeWithSocketsRef(dot_node, dot_node_with_sockets));
|
||||
}
|
||||
|
||||
for (const Node *node : nodes_) {
|
||||
for (const InputSocket *socket : node->inputs()) {
|
||||
const dot_export::NodeWithSocketsRef &to_dot_node = dot_nodes.lookup(&socket->node());
|
||||
const dot_export::NodePort to_dot_port = to_dot_node.input(socket->index());
|
||||
|
||||
if (const OutputSocket *origin = socket->origin()) {
|
||||
dot_export::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&origin->node());
|
||||
dot_export::DirectedEdge &dot_edge = digraph.new_edge(
|
||||
from_dot_node.output(origin->index()), to_dot_port);
|
||||
options.add_edge_attributes(*origin, *socket, dot_edge);
|
||||
}
|
||||
else if (const void *default_value = socket->default_value()) {
|
||||
const CPPType &type = socket->type();
|
||||
std::string value_string;
|
||||
if (type.is_printable()) {
|
||||
value_string = type.to_string(default_value);
|
||||
}
|
||||
else {
|
||||
value_string = type.name();
|
||||
}
|
||||
dot_export::Node &default_value_dot_node = digraph.new_node(value_string);
|
||||
default_value_dot_node.set_shape(dot_export::Attr_shape::Ellipse);
|
||||
default_value_dot_node.attributes.set("color", "#00000055");
|
||||
digraph.new_edge(default_value_dot_node, to_dot_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return digraph.to_dot_string();
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
||||
@@ -0,0 +1,141 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_lazy_function_graph_executor.hh"
|
||||
|
||||
/* The entire executor is included here. Otherwise an additional indirection using forward
|
||||
* declarations of #GenericGraphExecutor would be needed. However, there isn't really a point in
|
||||
* having that because it's tightly coupled to #GraphExecutor anyway. It's only defined in a
|
||||
* separate file for code organization purposes. */
|
||||
#include "lazy_function_graph_executor_generic.hh"
|
||||
|
||||
namespace blender::fn::lazy_function {
|
||||
|
||||
GraphExecutor::GraphExecutor(const Graph &graph,
|
||||
const Logger *logger,
|
||||
const SideEffectProvider *side_effect_provider,
|
||||
const NodeExecuteWrapper *node_execute_wrapper)
|
||||
: GraphExecutor(graph,
|
||||
Vector<const GraphInputSocket *>(graph.graph_inputs()),
|
||||
Vector<const GraphOutputSocket *>(graph.graph_outputs()),
|
||||
logger,
|
||||
side_effect_provider,
|
||||
node_execute_wrapper)
|
||||
{
|
||||
}
|
||||
|
||||
GraphExecutor::GraphExecutor(const Graph &graph,
|
||||
Vector<const GraphInputSocket *> graph_inputs,
|
||||
Vector<const GraphOutputSocket *> graph_outputs,
|
||||
const Logger *logger,
|
||||
const SideEffectProvider *side_effect_provider,
|
||||
const NodeExecuteWrapper *node_execute_wrapper)
|
||||
: graph_(graph),
|
||||
graph_inputs_(std::move(graph_inputs)),
|
||||
graph_outputs_(std::move(graph_outputs)),
|
||||
graph_input_index_by_socket_index_(graph.graph_inputs().size(), -1),
|
||||
graph_output_index_by_socket_index_(graph.graph_outputs().size(), -1),
|
||||
logger_(logger),
|
||||
side_effect_provider_(side_effect_provider),
|
||||
node_execute_wrapper_(node_execute_wrapper)
|
||||
{
|
||||
debug_name_ = graph.name().c_str();
|
||||
|
||||
/* The graph executor can handle partial execution when there are still missing inputs. */
|
||||
allow_missing_requested_inputs_ = true;
|
||||
|
||||
for (const int i : graph_inputs_.index_range()) {
|
||||
const OutputSocket &socket = *graph_inputs_[i];
|
||||
BLI_assert(socket.node().is_interface());
|
||||
inputs_.append({"In", socket.type(), ValueUsage::Maybe});
|
||||
graph_input_index_by_socket_index_[socket.index()] = i;
|
||||
}
|
||||
for (const int i : graph_outputs_.index_range()) {
|
||||
const InputSocket &socket = *graph_outputs_[i];
|
||||
BLI_assert(socket.node().is_interface());
|
||||
outputs_.append({"Out", socket.type()});
|
||||
graph_output_index_by_socket_index_[socket.index()] = i;
|
||||
}
|
||||
|
||||
GenericExecutor::preprocess_graph(*this);
|
||||
}
|
||||
|
||||
void GraphExecutor::execute_impl(Params ¶ms, const Context &context) const
|
||||
{
|
||||
GenericExecutor &executor = *static_cast<GenericExecutor *>(context.storage);
|
||||
executor.execute(params, context);
|
||||
}
|
||||
|
||||
void *GraphExecutor::init_storage(LinearAllocator<> &allocator) const
|
||||
{
|
||||
GenericExecutor &executor = *allocator.construct<GenericExecutor>(*this).release();
|
||||
return &executor;
|
||||
}
|
||||
|
||||
void GraphExecutor::destruct_storage(void *storage) const
|
||||
{
|
||||
std::destroy_at(static_cast<GenericExecutor *>(storage));
|
||||
}
|
||||
|
||||
std::string GraphExecutor::input_name(const int index) const
|
||||
{
|
||||
const lf::OutputSocket &socket = *graph_inputs_[index];
|
||||
return socket.name();
|
||||
}
|
||||
|
||||
std::string GraphExecutor::output_name(const int index) const
|
||||
{
|
||||
const lf::InputSocket &socket = *graph_outputs_[index];
|
||||
return socket.name();
|
||||
}
|
||||
|
||||
GraphExecutorLogger::LoggingEnabledState GraphExecutorLogger::get_logging_enabled_state(
|
||||
const Context & /*context*/) const
|
||||
{
|
||||
return LoggingEnabledState{true};
|
||||
}
|
||||
|
||||
void GraphExecutorLogger::log_socket_value(const Socket &socket,
|
||||
const GPointer value,
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(socket, value, context);
|
||||
}
|
||||
|
||||
void GraphExecutorLogger::log_before_node_execute(const FunctionNode &node,
|
||||
const Params ¶ms,
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(node, params, context);
|
||||
}
|
||||
|
||||
void GraphExecutorLogger::log_after_node_execute(const FunctionNode &node,
|
||||
const Params ¶ms,
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(node, params, context);
|
||||
}
|
||||
|
||||
Vector<const FunctionNode *> GraphExecutorSideEffectProvider::get_nodes_with_side_effects(
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(context);
|
||||
return {};
|
||||
}
|
||||
|
||||
void GraphExecutorLogger::dump_when_outputs_are_missing(const FunctionNode &node,
|
||||
Span<const OutputSocket *> missing_sockets,
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(node, missing_sockets, context);
|
||||
}
|
||||
|
||||
void GraphExecutorLogger::dump_when_input_is_set_twice(const InputSocket &target_socket,
|
||||
const OutputSocket &from_socket,
|
||||
const Context &context) const
|
||||
{
|
||||
UNUSED_VARS(target_socket, from_socket, context);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function
|
||||
File diff suppressed because it is too large
Load Diff
178
blender-5.2.0/source/blender/functions/intern/multi_function.cc
Normal file
178
blender-5.2.0/source/blender/functions/intern/multi_function.cc
Normal file
@@ -0,0 +1,178 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_threads.h"
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
using ExecutionHints = MultiFunction::ExecutionHints;
|
||||
|
||||
void MultiFunction::hash_unique(UniqueHashBytes &hash) const
|
||||
{
|
||||
hash.add(this);
|
||||
}
|
||||
|
||||
bool MultiFunction::equals(const MultiFunction &other) const
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
|
||||
ExecutionHints MultiFunction::execution_hints() const
|
||||
{
|
||||
return this->get_execution_hints();
|
||||
}
|
||||
|
||||
ExecutionHints MultiFunction::get_execution_hints() const
|
||||
{
|
||||
return ExecutionHints{};
|
||||
}
|
||||
|
||||
static bool supports_threading_by_slicing_params(const MultiFunction &fn)
|
||||
{
|
||||
for (const int i : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(i);
|
||||
if (ELEM(param_type.interface_type(),
|
||||
ParamType::InterfaceType::Mutable,
|
||||
ParamType::InterfaceType::Output))
|
||||
{
|
||||
if (param_type.data_type().is_vector()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int64_t compute_grain_size(const ExecutionHints &hints, const IndexMask &mask)
|
||||
{
|
||||
int64_t grain_size = hints.min_grain_size;
|
||||
if (hints.uniform_execution_time) {
|
||||
const int thread_count = BLI_system_thread_count();
|
||||
/* Avoid using a small grain size even if it is not necessary. */
|
||||
const int64_t thread_based_grain_size = mask.size() / thread_count / 4;
|
||||
grain_size = std::max(grain_size, thread_based_grain_size);
|
||||
}
|
||||
if (hints.allocates_array) {
|
||||
const int64_t max_grain_size = 10000;
|
||||
/* Avoid allocating many large intermediate arrays. Better process data in smaller chunks to
|
||||
* keep peak memory usage lower. */
|
||||
grain_size = std::min(grain_size, max_grain_size);
|
||||
}
|
||||
return grain_size;
|
||||
}
|
||||
|
||||
static int64_t compute_alignment(const int64_t grain_size)
|
||||
{
|
||||
if (grain_size <= 512) {
|
||||
/* Don't use a number that's too large, or otherwise the work will be split quite unevenly. */
|
||||
return 8;
|
||||
}
|
||||
/* It's not common that more elements are processed in a loop at once. */
|
||||
return 32;
|
||||
}
|
||||
|
||||
static void add_sliced_parameters(const Signature &signature,
|
||||
Params &full_params,
|
||||
const IndexRange slice_range,
|
||||
ParamsBuilder &r_sliced_params)
|
||||
{
|
||||
for (const int param_index : signature.params.index_range()) {
|
||||
const ParamType ¶m_type = signature.params[param_index].type;
|
||||
switch (param_type.category()) {
|
||||
case ParamCategory::SingleInput: {
|
||||
const GVArray &varray = full_params.readonly_single_input(param_index);
|
||||
r_sliced_params.add_readonly_single_input(varray.slice(slice_range));
|
||||
break;
|
||||
}
|
||||
case ParamCategory::SingleMutable: {
|
||||
const GMutableSpan span = full_params.single_mutable(param_index);
|
||||
const GMutableSpan sliced_span = span.slice(slice_range);
|
||||
r_sliced_params.add_single_mutable(sliced_span);
|
||||
break;
|
||||
}
|
||||
case ParamCategory::SingleOutput: {
|
||||
if (flag_is_set(signature.params[param_index].flag, ParamFlag::SupportsUnusedOutput)) {
|
||||
const GMutableSpan span = full_params.uninitialized_single_output_if_required(
|
||||
param_index);
|
||||
if (span.is_empty()) {
|
||||
r_sliced_params.add_ignored_single_output();
|
||||
}
|
||||
else {
|
||||
const GMutableSpan sliced_span = span.slice(slice_range);
|
||||
r_sliced_params.add_uninitialized_single_output(sliced_span);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const GMutableSpan span = full_params.uninitialized_single_output(param_index);
|
||||
const GMutableSpan sliced_span = span.slice(slice_range);
|
||||
r_sliced_params.add_uninitialized_single_output(sliced_span);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ParamCategory::VectorInput:
|
||||
case ParamCategory::VectorMutable:
|
||||
case ParamCategory::VectorOutput: {
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiFunction::call_auto(const IndexMask &mask, Params params, Context context) const
|
||||
{
|
||||
if (mask.is_empty()) {
|
||||
return;
|
||||
}
|
||||
const ExecutionHints hints = this->execution_hints();
|
||||
const int64_t grain_size = compute_grain_size(hints, mask);
|
||||
|
||||
if (mask.size() <= grain_size) {
|
||||
this->call(mask, params, context);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool supports_threading = supports_threading_by_slicing_params(*this);
|
||||
if (!supports_threading) {
|
||||
this->call(mask, params, context);
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t alignment = compute_alignment(grain_size);
|
||||
threading::parallel_for_aligned(
|
||||
mask.index_range(), grain_size, alignment, [&](const IndexRange sub_range) {
|
||||
const IndexMask sliced_mask = mask.slice(sub_range);
|
||||
if (!hints.allocates_array) {
|
||||
/* There is no benefit to changing indices in this case. */
|
||||
this->call(sliced_mask, params, context);
|
||||
return;
|
||||
}
|
||||
if (sliced_mask[0] < grain_size) {
|
||||
/* The indices are low, no need to offset them. */
|
||||
this->call(sliced_mask, params, context);
|
||||
return;
|
||||
}
|
||||
const int64_t input_slice_start = sliced_mask[0];
|
||||
const int64_t input_slice_size = sliced_mask.last() - input_slice_start + 1;
|
||||
const IndexRange input_slice_range{input_slice_start, input_slice_size};
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const int64_t offset = -input_slice_start;
|
||||
const IndexMask shifted_mask = mask.slice_and_shift(sub_range, offset, memory);
|
||||
|
||||
ParamsBuilder sliced_params{*this, &shifted_mask};
|
||||
add_sliced_parameters(*signature_ref_, params, input_slice_range, sliced_params);
|
||||
this->call(shifted_mask, sliced_params, context);
|
||||
});
|
||||
}
|
||||
|
||||
std::string MultiFunction::debug_name() const
|
||||
{
|
||||
return signature_ref_->function_name;
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
@@ -0,0 +1,133 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type,
|
||||
const void *value,
|
||||
bool make_value_copy)
|
||||
: type_(type), owns_value_(make_value_copy)
|
||||
{
|
||||
if (make_value_copy) {
|
||||
void *copied_value = MEM_new_uninitialized_aligned(type.size, type.alignment, __func__);
|
||||
type.copy_construct(value, copied_value);
|
||||
value = copied_value;
|
||||
}
|
||||
value_ = value;
|
||||
|
||||
SignatureBuilder builder{"Constant", signature_};
|
||||
builder.single_output("Value", type);
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
CustomMF_GenericConstant::~CustomMF_GenericConstant()
|
||||
{
|
||||
if (owns_value_) {
|
||||
signature_.params[0].type.data_type().single_type().destruct(const_cast<void *>(value_));
|
||||
MEM_delete_void(const_cast<void *>(value_));
|
||||
}
|
||||
}
|
||||
|
||||
void CustomMF_GenericConstant::call(const IndexMask &mask,
|
||||
Params params,
|
||||
Context /*context*/) const
|
||||
{
|
||||
GMutableSpan output = params.uninitialized_single_output(0);
|
||||
type_.fill_construct_indices(value_, output.data(), mask);
|
||||
}
|
||||
|
||||
void CustomMF_GenericConstant::hash_unique(UniqueHashBytes &hash) const
|
||||
{
|
||||
hash.add(&HASH_ID);
|
||||
type_.hash_unique(value_, hash);
|
||||
hash.add(&type_);
|
||||
}
|
||||
|
||||
bool CustomMF_GenericConstant::equals(const MultiFunction &other) const
|
||||
{
|
||||
const CustomMF_GenericConstant *_other = dynamic_cast<const CustomMF_GenericConstant *>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (type_ != _other->type_) {
|
||||
return false;
|
||||
}
|
||||
return type_.is_equal(value_, _other->value_);
|
||||
}
|
||||
|
||||
CustomMF_GenericConstantArray::CustomMF_GenericConstantArray(GSpan array) : array_(array)
|
||||
{
|
||||
const CPPType &type = array.type();
|
||||
SignatureBuilder builder{"Constant Vector", signature_};
|
||||
builder.vector_output("Value", type);
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void CustomMF_GenericConstantArray::call(const IndexMask &mask,
|
||||
Params params,
|
||||
Context /*context*/) const
|
||||
{
|
||||
GVectorArray &vectors = params.vector_output(0);
|
||||
mask.foreach_index([&](const int64_t i) { vectors.extend(i, array_); });
|
||||
}
|
||||
|
||||
CustomMF_DefaultOutput::CustomMF_DefaultOutput(Span<DataType> input_types,
|
||||
Span<DataType> output_types)
|
||||
: output_amount_(output_types.size())
|
||||
{
|
||||
SignatureBuilder builder{"Default Output", signature_};
|
||||
for (DataType data_type : input_types) {
|
||||
builder.input("Input", data_type);
|
||||
}
|
||||
for (DataType data_type : output_types) {
|
||||
builder.output("Output", data_type);
|
||||
}
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
void CustomMF_DefaultOutput::call(const IndexMask &mask, Params params, Context /*context*/) const
|
||||
{
|
||||
for (int param_index : this->param_indices()) {
|
||||
ParamType param_type = this->param_type(param_index);
|
||||
if (!param_type.is_output()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param_type.data_type().is_single()) {
|
||||
GMutableSpan span = params.uninitialized_single_output(param_index);
|
||||
const CPPType &type = span.type();
|
||||
type.fill_construct_indices(type.default_value(), span.data(), mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomMF_GenericCopy::CustomMF_GenericCopy(DataType data_type)
|
||||
{
|
||||
SignatureBuilder builder{"Copy", signature_};
|
||||
builder.input("Input", data_type);
|
||||
builder.output("Output", data_type);
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void CustomMF_GenericCopy::call(const IndexMask &mask, Params params, Context /*context*/) const
|
||||
{
|
||||
const DataType data_type = this->param_type(0).data_type();
|
||||
switch (data_type.category()) {
|
||||
case DataType::Single: {
|
||||
const GVArray &inputs = params.readonly_single_input(0, "Input");
|
||||
GMutableSpan outputs = params.uninitialized_single_output(1, "Output");
|
||||
inputs.materialize_to_uninitialized(mask, outputs.data());
|
||||
break;
|
||||
}
|
||||
case DataType::Vector: {
|
||||
const GVVectorArray &inputs = params.readonly_vector_input(0, "Input");
|
||||
GVectorArray &outputs = params.vector_output(1, "Output");
|
||||
outputs.extend(mask, inputs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
@@ -0,0 +1,678 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base_safe.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "FN_init.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_registry.hh"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
/**
|
||||
* An multi-function for the powf operation that more optimally handles simple and
|
||||
* common cases like raising to the power of 2.
|
||||
*/
|
||||
class PowFunction : public MultiFunction {
|
||||
private:
|
||||
static inline const MultiFunction *pow_generic = nullptr;
|
||||
static inline const MultiFunction *pow_2 = nullptr;
|
||||
static inline const MultiFunction *pow_3 = nullptr;
|
||||
|
||||
public:
|
||||
PowFunction()
|
||||
{
|
||||
static Signature signature = []() {
|
||||
pow_2 = ®istry::lookup("float ** 2"_ustr);
|
||||
pow_3 = ®istry::lookup("float ** 3"_ustr);
|
||||
static auto pow_generic_fn = build::SI2_SO<float, float, float>(
|
||||
"pow generic",
|
||||
[](const float a, const float b) { return safe_powf(a, b); },
|
||||
build::exec_presets::Materialized());
|
||||
pow_generic = &pow_generic_fn;
|
||||
|
||||
Signature signature;
|
||||
SignatureBuilder builder("float ** float", signature);
|
||||
builder.single_input<float>("Base");
|
||||
builder.single_input<float>("Exponent");
|
||||
builder.single_output<float>("Result");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context context) const override
|
||||
{
|
||||
/* Use GVArray here to avoid unnecessary conversions to typed virtual arrays. */
|
||||
const GVArray &base = params.readonly_single_input(0, "Base");
|
||||
const GVArray &exponent = params.readonly_single_input(1, "Exponent");
|
||||
MutableSpan<float> result = params.uninitialized_single_output<float>(2, "Result");
|
||||
|
||||
if (exponent.is_single()) {
|
||||
float exponent_single;
|
||||
exponent.get_internal_single(&exponent_single);
|
||||
const int exponent_int = int(exponent_single);
|
||||
/* Handle some exponents without invoking the general powf function. */
|
||||
if (float(exponent_int) == exponent_single) {
|
||||
switch (exponent_int) {
|
||||
case 0: {
|
||||
index_mask::masked_fill(result, 1.0f, mask);
|
||||
return;
|
||||
}
|
||||
case 1: {
|
||||
base.materialize_to_uninitialized(mask, result.data());
|
||||
return;
|
||||
}
|
||||
case 2: {
|
||||
ParamsBuilder sub_params{*pow_2, &mask};
|
||||
sub_params.add_readonly_single_input(base);
|
||||
sub_params.add_uninitialized_single_output(result);
|
||||
pow_2->call(mask, sub_params, context);
|
||||
return;
|
||||
}
|
||||
case 3: {
|
||||
ParamsBuilder sub_params{*pow_3, &mask};
|
||||
sub_params.add_readonly_single_input(base);
|
||||
sub_params.add_uninitialized_single_output(result);
|
||||
pow_3->call(mask, sub_params, context);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pow_generic->call(mask, params, context);
|
||||
}
|
||||
};
|
||||
|
||||
class DivideFunction : public MultiFunction {
|
||||
private:
|
||||
static inline const MultiFunction *multiply = nullptr;
|
||||
static inline const MultiFunction *divide_generic = nullptr;
|
||||
|
||||
public:
|
||||
DivideFunction()
|
||||
{
|
||||
static Signature signature = []() {
|
||||
multiply = ®istry::lookup("float * float"_ustr);
|
||||
static auto divide_generic_fn = build::SI2_SO<float, float, float>(
|
||||
"float / float",
|
||||
[](float a, float b) { return safe_divide(a, b); },
|
||||
build::exec_presets::AllSpanOrSingle());
|
||||
divide_generic = ÷_generic_fn;
|
||||
|
||||
Signature signature;
|
||||
SignatureBuilder builder("float / float", signature);
|
||||
builder.single_input<float>("A");
|
||||
builder.single_input<float>("B");
|
||||
builder.single_output<float>("Result");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
|
||||
{
|
||||
const GVArray &a = params.readonly_single_input(0, "A");
|
||||
const GVArray &b = params.readonly_single_input(1, "B");
|
||||
MutableSpan<float> result = params.uninitialized_single_output<float>(2, "Result");
|
||||
|
||||
if (b.is_single()) {
|
||||
float divisor;
|
||||
b.get_internal_single(&divisor);
|
||||
if (divisor == 0.0f) {
|
||||
/* We define the output to be 0 for division by zero. Same as #safe_divide. */
|
||||
index_mask::masked_fill(result, 0.0f, mask);
|
||||
return;
|
||||
}
|
||||
if (divisor == 1.0f) {
|
||||
/* If the divisor is 1 the result is the dividend. */
|
||||
a.materialize_to_uninitialized(mask, result.data());
|
||||
return;
|
||||
}
|
||||
if (is_inverse_exact(divisor)) {
|
||||
/* Use multiplication by the inverse which is more efficient than division. */
|
||||
const float inverse = 1.0f / divisor;
|
||||
ParamsBuilder sub_params{*multiply, &mask};
|
||||
sub_params.add_readonly_single_input(a);
|
||||
sub_params.add_readonly_single_input_value(inverse);
|
||||
sub_params.add_uninitialized_single_output(result);
|
||||
multiply->call(mask, sub_params, context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (a.is_single()) {
|
||||
float dividend;
|
||||
a.get_internal_single(÷nd);
|
||||
if (dividend == 0.0f) {
|
||||
/* If the dividend is zero the result is always zero regardless of the divisor. */
|
||||
index_mask::masked_fill(result, 0.0f, mask);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* General case. */
|
||||
divide_generic->call(mask, params, context);
|
||||
}
|
||||
|
||||
static bool is_inverse_exact(float x)
|
||||
{
|
||||
BLI_assert(x != 0.0f);
|
||||
x = fabsf(x);
|
||||
int exp;
|
||||
/* Check that x is a power of two. */
|
||||
const float fraction = frexpf(x, &exp);
|
||||
return fraction == 0.5f;
|
||||
}
|
||||
};
|
||||
|
||||
static void register_common_functions_impl()
|
||||
{
|
||||
static constexpr auto exec_fast = build::exec_presets::AllSpanOrSingle();
|
||||
|
||||
registry::add_new_cb([]() {
|
||||
return build::SI1_SO<float, float>(
|
||||
"float ** 2", [](const float a) { return a * a; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([]() {
|
||||
return build::SI1_SO<float, float>(
|
||||
"float ** 3", [](const float a) { return a * a * a; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("exp(float)", [](const float a) { return expf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"sqrt(float)", [](const float a) { return safe_sqrtf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"inverse_sqrt(float)", [](const float a) { return safe_inverse_sqrtf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"abs(float)", [](const float a) { return fabsf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"radians(float)", [](const float a) { return float(DEG2RAD(a)); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"degrees(float)", [](const float a) { return float(RAD2DEG(a)); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"sign(float)", [](const float a) { return compatible_signf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"round(float)", [](const float a) { return floorf(a + 0.5f); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"floor(float)", [](const float a) { return floorf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"ceil(float)", [](const float a) { return ceilf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"frac(float)", [](const float a) { return a - floorf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>(
|
||||
"trunc(float)", [](const float a) { return a >= 0.0f ? floorf(a) : ceilf(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("sin(float)", [](const float a) { return sinf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("cos(float)", [](const float a) { return cosf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("tan(float)", [](const float a) { return tanf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("sinh(float)", [](const float a) { return sinhf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("cosh(float)", [](const float a) { return coshf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("tanh(float)", [](const float a) { return tanhf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("asin(float)", [](const float a) { return safe_asinf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("acos(float)", [](const float a) { return safe_acosf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float, float>("atan(float)", [](const float a) { return atanf(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float + float", [](const float a, const float b) { return a + b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float - float", [](const float a, const float b) { return a - b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float * float", [](const float a, const float b) { return a * b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] { return DivideFunction(); });
|
||||
registry::add_new_cb([] { return PowFunction(); });
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"log(float, float)",
|
||||
[](const float a, const float b) { return safe_logf(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"min(float, float)",
|
||||
[](const float a, const float b) { return std::min(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"max(float, float)",
|
||||
[](const float a, const float b) { return std::max(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float(float < float)",
|
||||
[](const float a, const float b) { return float(a < b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float(float > float)",
|
||||
[](const float a, const float b) { return float(a > b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"float % float", [](const float a, const float b) { return safe_modf(a, b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"floor_mod(float, float)",
|
||||
[](const float a, const float b) { return safe_floored_modf(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"snap(float, float)",
|
||||
[](const float a, const float b) { return floorf(safe_divide(a, b)) * b; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"atan2(float, float)",
|
||||
[](const float a, const float b) { return atan2f(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float, float, float>(
|
||||
"pingpong(float, float)",
|
||||
[](const float a, const float b) { return pingpongf(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float, float, float, float>(
|
||||
"float * float + float",
|
||||
[](const float a, const float b, const float c) { return a * b + c; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float, float, float, float>(
|
||||
"compare(float, float, float)",
|
||||
[](const float a, const float b, const float c) {
|
||||
return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f;
|
||||
},
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float, float, float, float>(
|
||||
"smooth_min(float, float, float)",
|
||||
[](const float a, const float b, const float c) { return smoothminf(a, b, c); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float, float, float, float>(
|
||||
"smooth_max(float, float, float)",
|
||||
[](const float a, const float b, const float c) { return -smoothminf(-a, -b, c); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float, float, float, float>(
|
||||
"wrap(float, float, float)",
|
||||
[](const float a, const float b, const float c) { return wrapf(a, b, c); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"float3 + float3", [](const float3 &a, const float3 &b) { return a + b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"float3 - float3", [](const float3 &a, const float3 &b) { return a - b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"float3 * float3", [](const float3 &a, const float3 &b) { return a * b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"float3 / float3",
|
||||
[](const float3 &a, const float3 &b) { return math::safe_divide(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"cross_product(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::cross_high_precision(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"project(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::project(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"reflect(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::reflect(a, math::normalize(b)); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"snap(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::floor(math::safe_divide(a, b)) * b; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"float3 % float3", [](const float3 &a, const float3 &b) { return math::safe_mod(a, b); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"min(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::min(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>(
|
||||
"max(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::max(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float3>("float3 ** float3", [](float3 a, float3 b) {
|
||||
return float3(safe_powf(a.x, b.x), safe_powf(a.y, b.y), safe_powf(a.z, b.z));
|
||||
});
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float>(
|
||||
"dot_product(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::dot(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float3, float>(
|
||||
"distance(float3, float3)",
|
||||
[](const float3 &a, const float3 &b) { return math::distance(a, b); },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float3, float3, float3, float3>(
|
||||
"float3 * float3 + float3",
|
||||
[](const float3 &a, const float3 &b, const float3 &c) { return a * b + c; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float3, float3, float3, float3>(
|
||||
"wrap(float3, float3, float3)", [](const float3 &a, const float3 &b, const float3 &c) {
|
||||
return float3(wrapf(a.x, b.x, c.x), wrapf(a.y, b.y, c.y), wrapf(a.z, b.z, c.z));
|
||||
});
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float3, float3, float3, float3>(
|
||||
"faceforward(float3, float3, float3)",
|
||||
[](const float3 &a, const float3 &b, const float3 &c) {
|
||||
return math::faceforward(a, b, c);
|
||||
},
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI3_SO<float3, float3, float, float3>(
|
||||
"refract(float3, float3, float)", [](const float3 &a, const float3 &b, float c) {
|
||||
return math::refract(a, math::normalize(b), c);
|
||||
});
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float>(
|
||||
"length(float3)", [](const float3 &a) { return math::length(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<float3, float, float3>(
|
||||
"float3 * float", [](const float3 &a, float b) { return a * b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"normalize(float3)", [](const float3 &a) { return math::normalize(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"round(float3)", [](const float3 &a) { return math::floor(a + 0.5f); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>("floor(float3)",
|
||||
[](const float3 &a) { return math::floor(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>("ceil(float3)",
|
||||
[](const float3 &a) { return math::ceil(a); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"frac(float3)", [](const float3 &a) { return math::fract(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"abs(float3)", [](const float3 &a) { return math::abs(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"sign(float3)", [](const float3 &a) { return math::sign(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"sin(float3)", [](const float3 &a) { return float3(sinf(a.x), sinf(a.y), sinf(a.z)); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"cos(float3)", [](const float3 &a) { return float3(cosf(a.x), cosf(a.y), cosf(a.z)); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI1_SO<float3, float3>(
|
||||
"tan(float3)", [](const float3 &a) { return float3(tanf(a.x), tanf(a.y), tanf(a.z)); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int + int", [](int a, int b) { return a + b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int - int", [](int a, int b) { return a - b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int * int", [](int a, int b) { return a * b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int / int", [](int a, int b) { return math::safe_divide(a, b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"floor(int, int)",
|
||||
[](int a, int b) { return (b != 0) ? divide_floor_i(a, b) : 0; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"divide_ceil(int, int)",
|
||||
[](int a, int b) { return (b != 0) ? -divide_floor_i(a, -b) : 0; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"divide_round(int, int)",
|
||||
[](int a, int b) {
|
||||
/* Derived from `divide_round_i` but fixed to be safe and handle negative inputs. */
|
||||
const int c = math::abs(b);
|
||||
return (a >= 0) ? math::safe_divide((2 * a + c), (2 * c)) * math::sign(b) :
|
||||
-math::safe_divide((2 * -a + c), (2 * c)) * math::sign(b);
|
||||
},
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int ** int", [](int a, int b) { return math::pow(a, b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI3_SO<int, int, int, int>(
|
||||
"int * int + int", [](int a, int b, int c) { return a * b + c; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"mod_periodic(int, int)",
|
||||
[](int a, int b) { return b != 0 ? math::mod_periodic(a, b) : 0; },
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int % int", [](int a, int b) { return b != 0 ? a % b : 0; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI1_SO<int, int>("abs(int)", [](int a) { return math::abs(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI1_SO<int, int>(
|
||||
"sign(int)", [](int a) { return math::sign(a); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"min(int, int)", [](int a, int b) { return math::min(a, b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"max(int, int)", [](int a, int b) { return math::max(a, b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>("gcd(int, int)",
|
||||
[](int a, int b) { return std::gcd(a, b); });
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>("lcm(int, int)",
|
||||
[](int a, int b) { return std::lcm(a, b); });
|
||||
});
|
||||
registry::add_new_cb(
|
||||
[] { return mf::build::SI1_SO<int, int>("-int", [](int a) { return -a; }, exec_fast); });
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"bool && bool", [](bool a, bool b) { return a && b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"bool || bool", [](bool a, bool b) { return a || b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb(
|
||||
[] { return mf::build::SI1_SO<bool, bool>("!bool", [](bool a) { return !a; }, exec_fast); });
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"!(bool && bool)", [](bool a, bool b) { return !(a && b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"!(bool || bool)", [](bool a, bool b) { return !(a || b); }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"bool == bool", [](bool a, bool b) { return a == b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"bool != bool", [](bool a, bool b) { return a != b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"!bool || bool", [](bool a, bool b) { return !a || b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<bool, bool, bool>(
|
||||
"bool && !bool", [](bool a, bool b) { return a && !b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int & int", [](int a, int b) { return a & b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int | int", [](int a, int b) { return a | b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return mf::build::SI2_SO<int, int, int>(
|
||||
"int ^ int", [](int a, int b) { return a ^ b; }, exec_fast);
|
||||
});
|
||||
registry::add_new_cb(
|
||||
[] { return build::SI1_SO<int, int>("~int", [](int a) { return ~a; }, exec_fast); });
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<int, int, int>(
|
||||
"shift(int, int)",
|
||||
[](int a, int b) {
|
||||
const uint32_t value = a;
|
||||
const int shift = math::clamp(b, -32, 32);
|
||||
const uint64_t wide_value = uint64_t(value) << 16;
|
||||
const uint64_t wide_result = shift > 0 ? wide_value << shift : wide_value >> -shift;
|
||||
return uint32_t(wide_result >> 16);
|
||||
},
|
||||
exec_fast);
|
||||
});
|
||||
registry::add_new_cb([] {
|
||||
return build::SI2_SO<int, int, int>(
|
||||
"rotate(int, int)",
|
||||
[](int a, int b) {
|
||||
const uint32_t value = a;
|
||||
const int shift = math::mod_periodic(b, 32);
|
||||
const uint64_t wide_value = uint64_t(value) | (uint64_t(value) << 32);
|
||||
const uint64_t double_result = (wide_value << shift);
|
||||
return uint32_t((double_result | (double_result >> 32)) & ((uint64_t(1) << 33) - 1));
|
||||
},
|
||||
exec_fast);
|
||||
});
|
||||
}
|
||||
|
||||
void register_common_functions()
|
||||
{
|
||||
/* Make sure the functions are only registered once even if called multiple times. */
|
||||
[[maybe_unused]] static bool registered = []() {
|
||||
register_common_functions_impl();
|
||||
return true;
|
||||
}();
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
@@ -0,0 +1,21 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function_params.hh"
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
void ParamsBuilder::add_unused_output_for_unsupporting_function(const CPPType &type)
|
||||
{
|
||||
ResourceScope &scope = this->resource_scope();
|
||||
void *buffer = scope.allocator().allocate_array(type, min_array_size_);
|
||||
const GMutableSpan span{type, buffer, min_array_size_};
|
||||
actual_params_.append_unchecked_as(std::in_place_type<GMutableSpan>, span);
|
||||
if (!type.is_trivially_destructible) {
|
||||
scope.add_destruct_call(
|
||||
[&type, buffer, mask = mask_]() { type.destruct_indices(buffer, mask); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
@@ -0,0 +1,878 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
#include "BLI_dot_export.hh"
|
||||
#include "BLI_stack.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
void InstructionCursor::set_next(Procedure &procedure, Instruction *new_instruction) const
|
||||
{
|
||||
switch (type_) {
|
||||
case Type::None: {
|
||||
break;
|
||||
}
|
||||
case Type::Entry: {
|
||||
procedure.set_entry(*new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Call: {
|
||||
static_cast<CallInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Branch: {
|
||||
BranchInstruction &branch_instruction = *static_cast<BranchInstruction *>(instruction_);
|
||||
if (branch_output_) {
|
||||
branch_instruction.set_branch_true(new_instruction);
|
||||
}
|
||||
else {
|
||||
branch_instruction.set_branch_false(new_instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Destruct: {
|
||||
static_cast<DestructInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Dummy: {
|
||||
static_cast<DummyInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Instruction *InstructionCursor::next(Procedure &procedure) const
|
||||
{
|
||||
switch (type_) {
|
||||
case Type::None:
|
||||
return nullptr;
|
||||
case Type::Entry:
|
||||
return procedure.entry();
|
||||
case Type::Call:
|
||||
return static_cast<CallInstruction *>(instruction_)->next();
|
||||
case Type::Branch: {
|
||||
BranchInstruction &branch_instruction = *static_cast<BranchInstruction *>(instruction_);
|
||||
if (branch_output_) {
|
||||
return branch_instruction.branch_true();
|
||||
}
|
||||
return branch_instruction.branch_false();
|
||||
}
|
||||
case Type::Destruct:
|
||||
return static_cast<DestructInstruction *>(instruction_)->next();
|
||||
case Type::Dummy:
|
||||
return static_cast<DummyInstruction *>(instruction_)->next();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Variable::set_name(std::string name)
|
||||
{
|
||||
name_ = std::move(name);
|
||||
}
|
||||
|
||||
void CallInstruction::set_next(Instruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void CallInstruction::set_param_variable(int param_index, Variable *variable)
|
||||
{
|
||||
if (params_[param_index] != nullptr) {
|
||||
params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
#ifndef NDEBUG
|
||||
const ParamType param_type = fn_->param_type(param_index);
|
||||
BLI_assert(param_type.data_type() == variable->data_type());
|
||||
#endif
|
||||
variable->users_.append(this);
|
||||
}
|
||||
params_[param_index] = variable;
|
||||
}
|
||||
|
||||
void CallInstruction::set_params(Span<Variable *> variables)
|
||||
{
|
||||
BLI_assert(variables.size() == params_.size());
|
||||
for (const int i : variables.index_range()) {
|
||||
this->set_param_variable(i, variables[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void BranchInstruction::set_condition(Variable *variable)
|
||||
{
|
||||
if (condition_ != nullptr) {
|
||||
condition_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
condition_ = variable;
|
||||
}
|
||||
|
||||
void BranchInstruction::set_branch_true(Instruction *instruction)
|
||||
{
|
||||
if (branch_true_ != nullptr) {
|
||||
branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true});
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append({*this, true});
|
||||
}
|
||||
branch_true_ = instruction;
|
||||
}
|
||||
|
||||
void BranchInstruction::set_branch_false(Instruction *instruction)
|
||||
{
|
||||
if (branch_false_ != nullptr) {
|
||||
branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append({*this, false});
|
||||
}
|
||||
branch_false_ = instruction;
|
||||
}
|
||||
|
||||
void DestructInstruction::set_variable(Variable *variable)
|
||||
{
|
||||
if (variable_ != nullptr) {
|
||||
variable_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
variable_ = variable;
|
||||
}
|
||||
|
||||
void DestructInstruction::set_next(Instruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void DummyInstruction::set_next(Instruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
Variable &Procedure::new_variable(DataType data_type, std::string name)
|
||||
{
|
||||
Variable &variable = *allocator_.construct<Variable>().release();
|
||||
variable.name_ = std::move(name);
|
||||
variable.data_type_ = data_type;
|
||||
variable.index_in_graph_ = variables_.size();
|
||||
variables_.append(&variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
CallInstruction &Procedure::new_call_instruction(const MultiFunction &fn)
|
||||
{
|
||||
CallInstruction &instruction = *allocator_.construct<CallInstruction>().release();
|
||||
instruction.type_ = InstructionType::Call;
|
||||
instruction.fn_ = &fn;
|
||||
instruction.params_ = allocator_.allocate_array<Variable *>(fn.param_amount());
|
||||
instruction.params_.fill(nullptr);
|
||||
call_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
BranchInstruction &Procedure::new_branch_instruction()
|
||||
{
|
||||
BranchInstruction &instruction = *allocator_.construct<BranchInstruction>().release();
|
||||
instruction.type_ = InstructionType::Branch;
|
||||
branch_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
DestructInstruction &Procedure::new_destruct_instruction()
|
||||
{
|
||||
DestructInstruction &instruction = *allocator_.construct<DestructInstruction>().release();
|
||||
instruction.type_ = InstructionType::Destruct;
|
||||
destruct_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
DummyInstruction &Procedure::new_dummy_instruction()
|
||||
{
|
||||
DummyInstruction &instruction = *allocator_.construct<DummyInstruction>().release();
|
||||
instruction.type_ = InstructionType::Dummy;
|
||||
dummy_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
ReturnInstruction &Procedure::new_return_instruction()
|
||||
{
|
||||
ReturnInstruction &instruction = *allocator_.construct<ReturnInstruction>().release();
|
||||
instruction.type_ = InstructionType::Return;
|
||||
return_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
void Procedure::add_parameter(ParamType::InterfaceType interface_type, Variable &variable)
|
||||
{
|
||||
params_.append({interface_type, &variable});
|
||||
}
|
||||
|
||||
void Procedure::set_entry(Instruction &entry)
|
||||
{
|
||||
if (entry_ != nullptr) {
|
||||
entry_->prev_.remove_first_occurrence_and_reorder(InstructionCursor::ForEntry());
|
||||
}
|
||||
entry_ = &entry;
|
||||
entry_->prev_.append(InstructionCursor::ForEntry());
|
||||
}
|
||||
|
||||
Procedure::~Procedure()
|
||||
{
|
||||
for (CallInstruction *instruction : call_instructions_) {
|
||||
instruction->~CallInstruction();
|
||||
}
|
||||
for (BranchInstruction *instruction : branch_instructions_) {
|
||||
instruction->~BranchInstruction();
|
||||
}
|
||||
for (DestructInstruction *instruction : destruct_instructions_) {
|
||||
instruction->~DestructInstruction();
|
||||
}
|
||||
for (DummyInstruction *instruction : dummy_instructions_) {
|
||||
instruction->~DummyInstruction();
|
||||
}
|
||||
for (ReturnInstruction *instruction : return_instructions_) {
|
||||
instruction->~ReturnInstruction();
|
||||
}
|
||||
for (Variable *variable : variables_) {
|
||||
variable->~Variable();
|
||||
}
|
||||
}
|
||||
|
||||
bool Procedure::validate() const
|
||||
{
|
||||
if (entry_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_instruction_pointers_set()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_params_provided()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_same_variables_in_one_call()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_parameters()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_initialization()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Procedure::prepare_for_execution()
|
||||
{
|
||||
for (const CallInstruction *instruction : call_instructions_) {
|
||||
instruction->fn().prepare_for_execution();
|
||||
}
|
||||
}
|
||||
|
||||
bool Procedure::validate_all_instruction_pointers_set() const
|
||||
{
|
||||
for (const CallInstruction *instruction : call_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const DestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const BranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->branch_true_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (instruction->branch_false_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const DummyInstruction *instruction : dummy_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Procedure::validate_all_params_provided() const
|
||||
{
|
||||
for (const CallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = instruction->fn();
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
if (param_type.category() == ParamCategory::SingleOutput) {
|
||||
/* Single outputs are optional. */
|
||||
continue;
|
||||
}
|
||||
const Variable *variable = instruction->params_[param_index];
|
||||
if (variable == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const BranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->condition_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const DestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->variable_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Procedure::validate_same_variables_in_one_call() const
|
||||
{
|
||||
for (const CallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
const Variable *variable = instruction->params_[param_index];
|
||||
if (variable == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (const int other_param_index : fn.param_indices()) {
|
||||
if (other_param_index == param_index) {
|
||||
continue;
|
||||
}
|
||||
const Variable *other_variable = instruction->params_[other_param_index];
|
||||
if (other_variable != variable) {
|
||||
continue;
|
||||
}
|
||||
if (ELEM(param_type.interface_type(), ParamType::Mutable, ParamType::Output)) {
|
||||
/* When a variable is used as mutable or output parameter, it can only be used once. */
|
||||
return false;
|
||||
}
|
||||
const ParamType other_param_type = fn.param_type(other_param_index);
|
||||
/* A variable is allowed to be used as input more than once. */
|
||||
if (other_param_type.interface_type() != ParamType::Input) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Procedure::validate_parameters() const
|
||||
{
|
||||
Set<const Variable *> variables;
|
||||
for (const Parameter ¶m : params_) {
|
||||
/* One variable cannot be used as multiple parameters. */
|
||||
if (!variables.add(param.variable)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Procedure::validate_initialization() const
|
||||
{
|
||||
/* TODO: Issue warning when it maybe wrongly initialized. */
|
||||
for (const DestructInstruction *instruction : destruct_instructions_) {
|
||||
const Variable &variable = *instruction->variable_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const BranchInstruction *instruction : branch_instructions_) {
|
||||
const Variable &variable = *instruction->condition_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const CallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
/* If the parameter was an unneeded output, it could be null. */
|
||||
if (!instruction->params_[param_index]) {
|
||||
continue;
|
||||
}
|
||||
const Variable &variable = *instruction->params_[param_index];
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
switch (param_type.interface_type()) {
|
||||
case ParamType::Input:
|
||||
case ParamType::Mutable: {
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ParamType::Output: {
|
||||
if (!state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<const Variable *> variables_that_should_be_initialized_on_return;
|
||||
for (const Parameter ¶m : params_) {
|
||||
if (ELEM(param.type, ParamType::Mutable, ParamType::Output)) {
|
||||
variables_that_should_be_initialized_on_return.add_new(param.variable);
|
||||
}
|
||||
}
|
||||
for (const ReturnInstruction *instruction : return_instructions_) {
|
||||
for (const Variable *variable : variables_) {
|
||||
const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
|
||||
*variable);
|
||||
if (variables_that_should_be_initialized_on_return.contains(variable)) {
|
||||
if (!init_state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!init_state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Procedure::InitState Procedure::find_initialization_state_before_instruction(
|
||||
const Instruction &target_instruction, const Variable &target_variable) const
|
||||
{
|
||||
InitState state;
|
||||
|
||||
auto check_entry_instruction = [&]() {
|
||||
bool caller_initialized_variable = false;
|
||||
for (const Parameter ¶m : params_) {
|
||||
if (param.variable == &target_variable) {
|
||||
if (ELEM(param.type, ParamType::Input, ParamType::Mutable)) {
|
||||
caller_initialized_variable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (caller_initialized_variable) {
|
||||
state.can_be_initialized = true;
|
||||
}
|
||||
else {
|
||||
state.can_be_uninitialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (&target_instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
|
||||
Set<const Instruction *> checked_instructions;
|
||||
Stack<const Instruction *> instructions_to_check;
|
||||
for (const InstructionCursor &cursor : target_instruction.prev_) {
|
||||
if (cursor.instruction() != nullptr) {
|
||||
instructions_to_check.push(cursor.instruction());
|
||||
}
|
||||
}
|
||||
|
||||
while (!instructions_to_check.is_empty()) {
|
||||
const Instruction &instruction = *instructions_to_check.pop();
|
||||
if (!checked_instructions.add(&instruction)) {
|
||||
/* Skip if the instruction has been checked already. */
|
||||
continue;
|
||||
}
|
||||
bool state_modified = false;
|
||||
switch (instruction.type_) {
|
||||
case InstructionType::Call: {
|
||||
const CallInstruction &call_instruction = static_cast<const CallInstruction &>(
|
||||
instruction);
|
||||
const MultiFunction &fn = *call_instruction.fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
if (call_instruction.params_[param_index] == &target_variable) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
if (param_type.interface_type() == ParamType::Output) {
|
||||
state.can_be_initialized = true;
|
||||
state_modified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InstructionType::Destruct: {
|
||||
const DestructInstruction &destruct_instruction = static_cast<const DestructInstruction &>(
|
||||
instruction);
|
||||
if (destruct_instruction.variable_ == &target_variable) {
|
||||
state.can_be_uninitialized = true;
|
||||
state_modified = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InstructionType::Branch:
|
||||
case InstructionType::Dummy:
|
||||
case InstructionType::Return: {
|
||||
/* These instruction types don't change the initialization state of variables. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state_modified) {
|
||||
if (&instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
for (const InstructionCursor &cursor : instruction.prev_) {
|
||||
if (cursor.instruction() != nullptr) {
|
||||
instructions_to_check.push(cursor.instruction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
class ProcedureDotExport {
|
||||
private:
|
||||
const Procedure &procedure_;
|
||||
dot_export::DirectedGraph digraph_;
|
||||
Map<const Instruction *, dot_export::Node *> dot_nodes_by_begin_;
|
||||
Map<const Instruction *, dot_export::Node *> dot_nodes_by_end_;
|
||||
|
||||
public:
|
||||
ProcedureDotExport(const Procedure &procedure) : procedure_(procedure) {}
|
||||
|
||||
std::string generate()
|
||||
{
|
||||
this->create_nodes();
|
||||
this->create_edges();
|
||||
return digraph_.to_dot_string();
|
||||
}
|
||||
|
||||
void create_nodes()
|
||||
{
|
||||
Vector<const Instruction *> all_instructions;
|
||||
auto add_instructions = [&](auto instructions) {
|
||||
all_instructions.extend(instructions.begin(), instructions.end());
|
||||
};
|
||||
add_instructions(procedure_.call_instructions_);
|
||||
add_instructions(procedure_.branch_instructions_);
|
||||
add_instructions(procedure_.destruct_instructions_);
|
||||
add_instructions(procedure_.dummy_instructions_);
|
||||
add_instructions(procedure_.return_instructions_);
|
||||
|
||||
Set<const Instruction *> handled_instructions;
|
||||
|
||||
for (const Instruction *representative : all_instructions) {
|
||||
if (handled_instructions.contains(representative)) {
|
||||
continue;
|
||||
}
|
||||
Vector<const Instruction *> block_instructions = this->get_instructions_in_block(
|
||||
*representative);
|
||||
std::stringstream ss;
|
||||
ss << "<";
|
||||
|
||||
for (const Instruction *current : block_instructions) {
|
||||
handled_instructions.add_new(current);
|
||||
switch (current->type()) {
|
||||
case InstructionType::Call: {
|
||||
this->instruction_to_string(*static_cast<const CallInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Destruct: {
|
||||
this->instruction_to_string(*static_cast<const DestructInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Dummy: {
|
||||
this->instruction_to_string(*static_cast<const DummyInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Return: {
|
||||
this->instruction_to_string(*static_cast<const ReturnInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Branch: {
|
||||
this->instruction_to_string(*static_cast<const BranchInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << R"(<br align="left" />)";
|
||||
}
|
||||
ss << ">";
|
||||
|
||||
dot_export::Node &dot_node = digraph_.new_node(ss.str());
|
||||
dot_node.set_shape(dot_export::Attr_shape::Rectangle);
|
||||
dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
|
||||
dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
|
||||
}
|
||||
}
|
||||
|
||||
void create_edges()
|
||||
{
|
||||
auto create_edge = [&](dot_export::Node &from_node,
|
||||
const Instruction *to_instruction) -> dot_export::DirectedEdge & {
|
||||
if (to_instruction == nullptr) {
|
||||
dot_export::Node &to_node = digraph_.new_node("missing");
|
||||
to_node.set_shape(dot_export::Attr_shape::Diamond);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
}
|
||||
dot_export::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
};
|
||||
|
||||
for (auto item : dot_nodes_by_end_.items()) {
|
||||
const Instruction &from_instruction = *item.key;
|
||||
dot_export::Node &from_node = *item.value;
|
||||
switch (from_instruction.type()) {
|
||||
case InstructionType::Call: {
|
||||
const Instruction *to_instruction =
|
||||
static_cast<const CallInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Destruct: {
|
||||
const Instruction *to_instruction =
|
||||
static_cast<const DestructInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Dummy: {
|
||||
const Instruction *to_instruction =
|
||||
static_cast<const DummyInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Return: {
|
||||
break;
|
||||
}
|
||||
case InstructionType::Branch: {
|
||||
const BranchInstruction &branch_instruction = static_cast<const BranchInstruction &>(
|
||||
from_instruction);
|
||||
const Instruction *to_true_instruction = branch_instruction.branch_true();
|
||||
const Instruction *to_false_instruction = branch_instruction.branch_false();
|
||||
create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
|
||||
create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dot_export::Node &entry_node = this->create_entry_node();
|
||||
create_edge(entry_node, procedure_.entry());
|
||||
}
|
||||
|
||||
bool has_to_be_block_begin(const Instruction &instruction)
|
||||
{
|
||||
if (instruction.prev().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(instruction.prev()[0].type(),
|
||||
InstructionCursor::Type::Branch,
|
||||
InstructionCursor::Type::Entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Instruction &get_first_instruction_in_block(const Instruction &representative)
|
||||
{
|
||||
const Instruction *current = &representative;
|
||||
while (!this->has_to_be_block_begin(*current)) {
|
||||
current = current->prev()[0].instruction();
|
||||
if (current == &representative) {
|
||||
/* There is a loop without entry or exit, just break it up here. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *current;
|
||||
}
|
||||
|
||||
const Instruction *get_next_instruction_in_block(const Instruction &instruction,
|
||||
const Instruction &block_begin)
|
||||
{
|
||||
const Instruction *next = nullptr;
|
||||
switch (instruction.type()) {
|
||||
case InstructionType::Call: {
|
||||
next = static_cast<const CallInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case InstructionType::Destruct: {
|
||||
next = static_cast<const DestructInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case InstructionType::Dummy: {
|
||||
next = static_cast<const DummyInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case InstructionType::Return:
|
||||
case InstructionType::Branch: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (next == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (next == &block_begin) {
|
||||
return nullptr;
|
||||
}
|
||||
if (this->has_to_be_block_begin(*next)) {
|
||||
return nullptr;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
Vector<const Instruction *> get_instructions_in_block(const Instruction &representative)
|
||||
{
|
||||
Vector<const Instruction *> instructions;
|
||||
const Instruction &begin = this->get_first_instruction_in_block(representative);
|
||||
for (const Instruction *current = &begin; current != nullptr;
|
||||
current = this->get_next_instruction_in_block(*current, begin))
|
||||
{
|
||||
instructions.append(current);
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void variable_to_string(const Variable *variable, std::stringstream &ss)
|
||||
{
|
||||
if (variable == nullptr) {
|
||||
ss << "null";
|
||||
}
|
||||
else {
|
||||
ss << "$" << variable->index_in_procedure();
|
||||
if (!variable->name().is_empty()) {
|
||||
ss << "(" << variable->name() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_name_format(StringRef name, std::stringstream &ss)
|
||||
{
|
||||
ss << name;
|
||||
}
|
||||
|
||||
void instruction_to_string(const CallInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
const MultiFunction &fn = instruction.fn();
|
||||
this->instruction_name_format(fn.debug_name() + ": ", ss);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
const Variable *variable = instruction.params()[param_index];
|
||||
ss << R"(<font color="grey30">)";
|
||||
switch (param_type.interface_type()) {
|
||||
case ParamType::Input: {
|
||||
ss << "in";
|
||||
break;
|
||||
}
|
||||
case ParamType::Mutable: {
|
||||
ss << "mut";
|
||||
break;
|
||||
}
|
||||
case ParamType::Output: {
|
||||
ss << "out";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << " </font> ";
|
||||
variable_to_string(variable, ss);
|
||||
if (param_index < fn.param_amount() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const DestructInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Destruct ", ss);
|
||||
variable_to_string(instruction.variable(), ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const DummyInstruction & /*instruction*/, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Dummy ", ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const ReturnInstruction & /*instruction*/, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Return ", ss);
|
||||
|
||||
Vector<ConstParameter> outgoing_parameters;
|
||||
for (const ConstParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, ParamType::Mutable, ParamType::Output)) {
|
||||
outgoing_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : outgoing_parameters.index_range()) {
|
||||
const ConstParameter ¶m = outgoing_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < outgoing_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const BranchInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Branch ", ss);
|
||||
variable_to_string(instruction.condition(), ss);
|
||||
}
|
||||
|
||||
dot_export::Node &create_entry_node()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Entry: ";
|
||||
Vector<ConstParameter> incoming_parameters;
|
||||
for (const ConstParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, ParamType::Input, ParamType::Mutable)) {
|
||||
incoming_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : incoming_parameters.index_range()) {
|
||||
const ConstParameter ¶m = incoming_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < incoming_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
dot_export::Node &node = digraph_.new_node(ss.str());
|
||||
node.set_shape(dot_export::Attr_shape::Ellipse);
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
std::string Procedure::to_dot() const
|
||||
{
|
||||
ProcedureDotExport dot_export{*this};
|
||||
return dot_export.generate();
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
@@ -0,0 +1,119 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
|
||||
namespace blender::fn::multi_function {
|
||||
|
||||
void ProcedureBuilder::add_destruct(Variable &variable)
|
||||
{
|
||||
DestructInstruction &instruction = procedure_->new_destruct_instruction();
|
||||
instruction.set_variable(&variable);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {InstructionCursor{instruction}};
|
||||
}
|
||||
|
||||
void ProcedureBuilder::add_destruct(Span<Variable *> variables)
|
||||
{
|
||||
for (Variable *variable : variables) {
|
||||
this->add_destruct(*variable);
|
||||
}
|
||||
}
|
||||
|
||||
ReturnInstruction &ProcedureBuilder::add_return()
|
||||
{
|
||||
ReturnInstruction &instruction = procedure_->new_return_instruction();
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
CallInstruction &ProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
|
||||
{
|
||||
CallInstruction &instruction = procedure_->new_call_instruction(fn);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {InstructionCursor{instruction}};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
CallInstruction &ProcedureBuilder::add_call_with_all_variables(const MultiFunction &fn,
|
||||
Span<Variable *> param_variables)
|
||||
{
|
||||
CallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
instruction.set_params(param_variables);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
Vector<Variable *> ProcedureBuilder::add_call(const MultiFunction &fn,
|
||||
Span<Variable *> input_and_mutable_variables)
|
||||
{
|
||||
Vector<Variable *> output_variables;
|
||||
CallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const ParamType param_type = fn.param_type(param_index);
|
||||
switch (param_type.interface_type()) {
|
||||
case ParamType::Input:
|
||||
case ParamType::Mutable: {
|
||||
Variable *variable = input_and_mutable_variables.first();
|
||||
instruction.set_param_variable(param_index, variable);
|
||||
input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
|
||||
break;
|
||||
}
|
||||
case ParamType::Output: {
|
||||
Variable &variable = procedure_->new_variable(param_type.data_type(),
|
||||
fn.param_name(param_index));
|
||||
instruction.set_param_variable(param_index, &variable);
|
||||
output_variables.append(&variable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* All passed in variables should have been dropped in the loop above. */
|
||||
BLI_assert(input_and_mutable_variables.is_empty());
|
||||
return output_variables;
|
||||
}
|
||||
|
||||
ProcedureBuilder::Branch ProcedureBuilder::add_branch(Variable &condition)
|
||||
{
|
||||
BranchInstruction &instruction = procedure_->new_branch_instruction();
|
||||
instruction.set_condition(&condition);
|
||||
this->link_to_cursors(&instruction);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
|
||||
Branch branch{*procedure_, *procedure_};
|
||||
branch.branch_true.set_cursor(InstructionCursor{instruction, true});
|
||||
branch.branch_false.set_cursor(InstructionCursor{instruction, false});
|
||||
return branch;
|
||||
}
|
||||
|
||||
ProcedureBuilder::Loop ProcedureBuilder::add_loop()
|
||||
{
|
||||
DummyInstruction &loop_begin = procedure_->new_dummy_instruction();
|
||||
DummyInstruction &loop_end = procedure_->new_dummy_instruction();
|
||||
this->link_to_cursors(&loop_begin);
|
||||
cursors_ = {InstructionCursor{loop_begin}};
|
||||
|
||||
Loop loop;
|
||||
loop.begin = &loop_begin;
|
||||
loop.end = &loop_end;
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void ProcedureBuilder::add_loop_continue(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.begin);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
void ProcedureBuilder::add_loop_break(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.end);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_multi_function_procedure_optimization.hh"
|
||||
|
||||
namespace blender::fn::multi_function::procedure_optimization {
|
||||
|
||||
void move_destructs_up(Procedure &procedure, Instruction &block_end_instr)
|
||||
{
|
||||
/* A mapping from a variable to its destruct instruction. */
|
||||
Map<Variable *, DestructInstruction *> destruct_instructions;
|
||||
Instruction *current_instr = &block_end_instr;
|
||||
while (true) {
|
||||
InstructionType instr_type = current_instr->type();
|
||||
switch (instr_type) {
|
||||
case InstructionType::Destruct: {
|
||||
DestructInstruction &destruct_instr = static_cast<DestructInstruction &>(*current_instr);
|
||||
Variable *variable = destruct_instr.variable();
|
||||
if (variable == nullptr) {
|
||||
continue;
|
||||
}
|
||||
/* Remember this destruct instruction so that it can be moved up later on when the last use
|
||||
* of the variable is found. */
|
||||
destruct_instructions.add(variable, &destruct_instr);
|
||||
break;
|
||||
}
|
||||
case InstructionType::Call: {
|
||||
CallInstruction &call_instr = static_cast<CallInstruction &>(*current_instr);
|
||||
/* For each variable, place the corresponding remembered destruct instruction right after
|
||||
* this call instruction. */
|
||||
for (Variable *variable : call_instr.params()) {
|
||||
if (variable == nullptr) {
|
||||
continue;
|
||||
}
|
||||
DestructInstruction *destruct_instr = destruct_instructions.pop_default(variable,
|
||||
nullptr);
|
||||
if (destruct_instr == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Unlink destruct instruction from previous position. */
|
||||
Instruction *after_destruct_instr = destruct_instr->next();
|
||||
while (!destruct_instr->prev().is_empty()) {
|
||||
/* Do a copy of the cursor here, because `destruct_instr->prev()` changes when
|
||||
* #set_next is called below. */
|
||||
const InstructionCursor cursor = destruct_instr->prev()[0];
|
||||
cursor.set_next(procedure, after_destruct_instr);
|
||||
}
|
||||
|
||||
/* Insert destruct instruction in new position. */
|
||||
Instruction *next_instr = call_instr.next();
|
||||
call_instr.set_next(destruct_instr);
|
||||
destruct_instr->set_next(next_instr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const Span<InstructionCursor> prev_cursors = current_instr->prev();
|
||||
if (prev_cursors.size() != 1) {
|
||||
/* Stop when there is some branching before this instruction. */
|
||||
break;
|
||||
}
|
||||
const InstructionCursor &prev_cursor = prev_cursors[0];
|
||||
current_instr = prev_cursor.instruction();
|
||||
if (current_instr == nullptr) {
|
||||
/* Stop when there is no previous instruction. E.g. when this is the first instruction. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function::procedure_optimization
|
||||
@@ -0,0 +1,55 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_concurrent_map.hh"
|
||||
|
||||
#include "FN_multi_function_registry.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
static CLG_LogRef LOG = {"functions.mf_registry"};
|
||||
|
||||
namespace blender::fn::multi_function::registry {
|
||||
|
||||
using RegistryMap = ConcurrentMap<UString, const MultiFunction *>;
|
||||
|
||||
struct Registry {
|
||||
RegistryMap map;
|
||||
};
|
||||
|
||||
static Registry &get_registry()
|
||||
{
|
||||
static Registry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
void add_new(const MultiFunction &fn)
|
||||
{
|
||||
Registry ®istry = get_registry();
|
||||
RegistryMap::MutableAccessor accessor;
|
||||
const UString id = UString(fn.name());
|
||||
if (registry.map.add(accessor, id)) {
|
||||
accessor->second = &fn;
|
||||
}
|
||||
else {
|
||||
/* A function can only be registered once. */
|
||||
CLOG_ERROR(&LOG, "Multi-function already registered: '%s'", id.c_str());
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
const MultiFunction &lookup(UString id)
|
||||
{
|
||||
Registry ®istry = get_registry();
|
||||
RegistryMap::ConstAccessor accessor;
|
||||
if (registry.map.lookup(accessor, id)) {
|
||||
return *accessor->second;
|
||||
}
|
||||
/* The function is expected to exist when using the #lookup function. */
|
||||
CLOG_ERROR(&LOG, "Multi-function does not exist: '%s'", id.c_str());
|
||||
BLI_assert_unreachable();
|
||||
return *accessor->second;
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function::registry
|
||||
14
blender-5.2.0/source/blender/functions/intern/user_data.cc
Normal file
14
blender-5.2.0/source/blender/functions/intern/user_data.cc
Normal file
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "FN_user_data.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
destruct_ptr<LocalUserData> UserData::get_local(LinearAllocator<> & /*allocator*/)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
||||
Reference in New Issue
Block a user