/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup fn * * This file contains several utilities to create multi-functions with less redundant code. */ #include #include "FN_multi_function.hh" namespace blender::fn { /** * Generates a multi-function with the following parameters: * 1. single input (SI) of type In1 * 2. single output (SO) of type Out1 * * This example creates a function that adds 10 to the incoming values: * `CustomMF_SI_SO fn("add 10", [](int value) { return value + 10; });` */ template class CustomMF_SI_SO : public MultiFunction { private: using FunctionT = std::function &, MutableSpan)>; FunctionT function_; MFSignature signature_; public: CustomMF_SI_SO(const char *name, FunctionT function) : function_(std::move(function)) { MFSignatureBuilder signature{name}; signature.single_input("In1"); signature.single_output("Out1"); signature_ = signature.build(); this->set_signature(&signature_); } template CustomMF_SI_SO(const char *name, ElementFuncT element_fn) : CustomMF_SI_SO(name, CustomMF_SI_SO::create_function(element_fn)) { } template static FunctionT create_function(ElementFuncT element_fn) { return [=](IndexMask mask, const VArray &in1, MutableSpan out1) { if (in1.is_single()) { /* Only evaluate the function once when the input is a single value. */ const In1 in1_single = in1.get_internal_single(); const Out1 out1_single = element_fn(in1_single); out1.fill_indices(mask, out1_single); return; } if (in1.is_span()) { const Span in1_span = in1.get_internal_span(); mask.to_best_mask_type( [&](auto mask) { execute_SI_SO(element_fn, mask, in1_span, out1.data()); }); return; } /* The input is an unknown virtual array type. To avoid virtual function call overhead for * every element, elements are retrieved and processed in chunks. */ static constexpr int64_t MaxChunkSize = 32; TypedBuffer in1_buffer_owner; MutableSpan in1_buffer{in1_buffer_owner.ptr(), MaxChunkSize}; const int64_t mask_size = mask.size(); for (int64_t chunk_start = 0; chunk_start < mask_size; chunk_start += MaxChunkSize) { const int64_t chunk_size = std::min(mask_size - chunk_start, MaxChunkSize); const IndexMask sliced_mask = mask.slice(chunk_start, chunk_size); /* Load input from the virtual array. */ MutableSpan in1_chunk = in1_buffer.take_front(chunk_size); in1.materialize_compressed_to_uninitialized(sliced_mask, in1_chunk); if (sliced_mask.is_range()) { execute_SI_SO( element_fn, IndexRange(chunk_size), in1_chunk, out1.data() + sliced_mask[0]); } else { execute_SI_SO_compressed(element_fn, sliced_mask, in1_chunk, out1.data()); } destruct_n(in1_chunk.data(), chunk_size); } }; } template BLI_NOINLINE static void execute_SI_SO(const ElementFuncT &element_fn, MaskT mask, const In1Array &in1, Out1 *__restrict r_out) { for (const int64_t i : mask) { new (r_out + i) Out1(element_fn(in1[i])); } } /** Expects the input array to be "compressed", i.e. there are no gaps between the elements. */ template BLI_NOINLINE static void execute_SI_SO_compressed(const ElementFuncT &element_fn, MaskT mask, const In1Array &in1, Out1 *__restrict r_out) { for (const int64_t i : IndexRange(mask.size())) { new (r_out + mask[i]) Out1(element_fn(in1[i])); } } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { const VArray &in1 = params.readonly_single_input(0); MutableSpan out1 = params.uninitialized_single_output(1); function_(mask, in1, out1); } }; /** * Generates a multi-function with the following parameters: * 1. single input (SI) of type In1 * 2. single input (SI) of type In2 * 3. single output (SO) of type Out1 */ template class CustomMF_SI_SI_SO : public MultiFunction { private: using FunctionT = std::function &, const VArray &, MutableSpan)>; FunctionT function_; MFSignature signature_; public: CustomMF_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function)) { MFSignatureBuilder signature{name}; signature.single_input("In1"); signature.single_input("In2"); signature.single_output("Out1"); signature_ = signature.build(); this->set_signature(&signature_); } template CustomMF_SI_SI_SO(const char *name, ElementFuncT element_fn) : CustomMF_SI_SI_SO(name, CustomMF_SI_SI_SO::create_function(element_fn)) { } template static FunctionT create_function(ElementFuncT element_fn) { return [=](IndexMask mask, const VArray &in1, const VArray &in2, MutableSpan out1) { /* Devirtualization results in a 2-3x speedup for some simple functions. */ devirtualize_varray2(in1, in2, [&](const auto &in1, const auto &in2) { mask.to_best_mask_type( [&](const auto &mask) { execute_SI_SI_SO(element_fn, mask, in1, in2, out1.data()); }); }); }; } template BLI_NOINLINE static void execute_SI_SI_SO(const ElementFuncT &element_fn, MaskT mask, const In1Array &in1, const In2Array &in2, Out1 *__restrict r_out) { for (const int64_t i : mask) { new (r_out + i) Out1(element_fn(in1[i], in2[i])); } } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { const VArray &in1 = params.readonly_single_input(0); const VArray &in2 = params.readonly_single_input(1); MutableSpan out1 = params.uninitialized_single_output(2); function_(mask, in1, in2, out1); } }; /** * Generates a multi-function with the following parameters: * 1. single input (SI) of type In1 * 2. single input (SI) of type In2 * 3. single input (SI) of type In3 * 4. single output (SO) of type Out1 */ template class CustomMF_SI_SI_SI_SO : public MultiFunction { private: using FunctionT = std::function &, const VArray &, const VArray &, MutableSpan)>; FunctionT function_; MFSignature signature_; public: CustomMF_SI_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function)) { MFSignatureBuilder signature{name}; signature.single_input("In1"); signature.single_input("In2"); signature.single_input("In3"); signature.single_output("Out1"); signature_ = signature.build(); this->set_signature(&signature_); } template CustomMF_SI_SI_SI_SO(const char *name, ElementFuncT element_fn) : CustomMF_SI_SI_SI_SO(name, CustomMF_SI_SI_SI_SO::create_function(element_fn)) { } template static FunctionT create_function(ElementFuncT element_fn) { return [=](IndexMask mask, const VArray &in1, const VArray &in2, const VArray &in3, MutableSpan out1) { /* Virtual arrays are not devirtualized yet, to avoid generating lots of code without further * consideration. */ execute_SI_SI_SI_SO(element_fn, mask, in1, in2, in3, out1.data()); }; } template BLI_NOINLINE static void execute_SI_SI_SI_SO(const ElementFuncT &element_fn, MaskT mask, const In1Array &in1, const In2Array &in2, const In3Array &in3, Out1 *__restrict r_out) { for (const int64_t i : mask) { new (r_out + i) Out1(element_fn(in1[i], in2[i], in3[i])); } } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { const VArray &in1 = params.readonly_single_input(0); const VArray &in2 = params.readonly_single_input(1); const VArray &in3 = params.readonly_single_input(2); MutableSpan out1 = params.uninitialized_single_output(3); function_(mask, in1, in2, in3, out1); } }; /** * Generates a multi-function with the following parameters: * 1. single input (SI) of type In1 * 2. single input (SI) of type In2 * 3. single input (SI) of type In3 * 4. single input (SI) of type In4 * 5. single output (SO) of type Out1 */ template class CustomMF_SI_SI_SI_SI_SO : public MultiFunction { private: using FunctionT = std::function &, const VArray &, const VArray &, const VArray &, MutableSpan)>; FunctionT function_; MFSignature signature_; public: CustomMF_SI_SI_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function)) { MFSignatureBuilder signature{name}; signature.single_input("In1"); signature.single_input("In2"); signature.single_input("In3"); signature.single_input("In4"); signature.single_output("Out1"); signature_ = signature.build(); this->set_signature(&signature_); } template CustomMF_SI_SI_SI_SI_SO(const char *name, ElementFuncT element_fn) : CustomMF_SI_SI_SI_SI_SO(name, CustomMF_SI_SI_SI_SI_SO::create_function(element_fn)) { } template static FunctionT create_function(ElementFuncT element_fn) { return [=](IndexMask mask, const VArray &in1, const VArray &in2, const VArray &in3, const VArray &in4, MutableSpan out1) { /* Virtual arrays are not devirtualized yet, to avoid generating lots of code without further * consideration. */ execute_SI_SI_SI_SI_SO(element_fn, mask, in1, in2, in3, in4, out1.data()); }; } template BLI_NOINLINE static void execute_SI_SI_SI_SI_SO(const ElementFuncT &element_fn, MaskT mask, const In1Array &in1, const In2Array &in2, const In3Array &in3, const In4Array &in4, Out1 *__restrict r_out) { for (const int64_t i : mask) { new (r_out + i) Out1(element_fn(in1[i], in2[i], in3[i], in4[i])); } } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { const VArray &in1 = params.readonly_single_input(0); const VArray &in2 = params.readonly_single_input(1); const VArray &in3 = params.readonly_single_input(2); const VArray &in4 = params.readonly_single_input(3); MutableSpan out1 = params.uninitialized_single_output(4); function_(mask, in1, in2, in3, in4, out1); } }; /** * Generates a multi-function with the following parameters: * 1. single mutable (SM) of type Mut1 */ template class CustomMF_SM : public MultiFunction { private: using FunctionT = std::function)>; FunctionT function_; MFSignature signature_; public: CustomMF_SM(const char *name, FunctionT function) : function_(std::move(function)) { MFSignatureBuilder signature{name}; signature.single_mutable("Mut1"); signature_ = signature.build(); this->set_signature(&signature_); } template CustomMF_SM(const char *name, ElementFuncT element_fn) : CustomMF_SM(name, CustomMF_SM::create_function(element_fn)) { } template static FunctionT create_function(ElementFuncT element_fn) { return [=](IndexMask mask, MutableSpan mut1) { mask.to_best_mask_type([&](const auto &mask) { for (const int64_t i : mask) { element_fn(mut1[i]); } }); }; } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { MutableSpan mut1 = params.single_mutable(0); function_(mask, mut1); } }; /** * A multi-function that outputs the same value every time. The value is not owned by an instance * of this function. If #make_value_copy is false, the caller is responsible for destructing and * freeing the value. */ class CustomMF_GenericConstant : public MultiFunction { private: const CPPType &type_; const void *value_; MFSignature signature_; bool owns_value_; template friend class CustomMF_Constant; public: CustomMF_GenericConstant(const CPPType &type, const void *value, bool make_value_copy); ~CustomMF_GenericConstant(); void call(IndexMask mask, MFParams params, MFContext context) const override; uint64_t hash() const override; bool equals(const MultiFunction &other) const override; }; /** * A multi-function that outputs the same array every time. The array is not owned by in instance * of this function. The caller is responsible for destructing and freeing the values. */ class CustomMF_GenericConstantArray : public MultiFunction { private: GSpan array_; MFSignature signature_; public: CustomMF_GenericConstantArray(GSpan array); void call(IndexMask mask, MFParams params, MFContext context) const override; }; /** * Generates a multi-function that outputs a constant value. */ template class CustomMF_Constant : public MultiFunction { private: T value_; MFSignature signature_; public: template CustomMF_Constant(U &&value) : value_(std::forward(value)) { MFSignatureBuilder signature{"Constant"}; signature.single_output("Value"); signature_ = signature.build(); this->set_signature(&signature_); } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { MutableSpan output = params.uninitialized_single_output(0); mask.to_best_mask_type([&](const auto &mask) { for (const int64_t i : mask) { new (&output[i]) T(value_); } }); } uint64_t hash() const override { return get_default_hash(value_); } bool equals(const MultiFunction &other) const override { const CustomMF_Constant *other1 = dynamic_cast(&other); if (other1 != nullptr) { return value_ == other1->value_; } const CustomMF_GenericConstant *other2 = dynamic_cast( &other); if (other2 != nullptr) { const CPPType &type = CPPType::get(); if (type == other2->type_) { return type.is_equal_or_false(static_cast(&value_), other2->value_); } } return false; } }; class CustomMF_DefaultOutput : public MultiFunction { private: int output_amount_; MFSignature signature_; public: CustomMF_DefaultOutput(Span input_types, Span output_types); void call(IndexMask mask, MFParams params, MFContext context) const override; }; class CustomMF_GenericCopy : public MultiFunction { private: MFSignature signature_; public: CustomMF_GenericCopy(MFDataType data_type); void call(IndexMask mask, MFParams params, MFContext context) const override; }; } // namespace blender::fn