/* 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) { const int64_t mask_size = mask.size(); const bool in1_is_single = in1.is_single(); const bool in1_is_span = in1.is_span(); static constexpr int64_t MaxChunkSize = 32; /* Properly handle initialization. */ TypedBuffer in1_buffer_owner; MutableSpan in1_buffer{in1_buffer_owner.ptr(), MaxChunkSize}; if (in1_is_single) { const In1 in1_single = in1.get_internal_single(); in1_buffer.fill(in1_single); } Span in1_span; if (in1_is_span) { in1_span = in1.get_internal_span(); } 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); if (sliced_mask.is_range()) { const IndexRange sliced_mask_range = sliced_mask.as_range(); Span in1_chunk; if (in1_is_single) { in1_chunk = in1_buffer; } else if (in1_is_span) { in1_chunk = in1_span.slice(sliced_mask_range); } else { in1.materialize_compressed_to_uninitialized(sliced_mask, in1_buffer.take_front(chunk_size)); in1_chunk = in1_buffer; } execute_SI_SO(element_fn, IndexRange(chunk_size), in1_chunk, out1.data() + chunk_start); } else { // const Span sliced_mask_indices = sliced_mask.indices(); /* TODO */ BLI_assert_unreachable(); } } return; }; } 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])); } } 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); } }; /** * Generates a multi-function that converts between two types. */ template class CustomMF_Convert : public MultiFunction { public: CustomMF_Convert() { static MFSignature signature = create_signature(); this->set_signature(&signature); } static MFSignature create_signature() { static std::string name = CPPType::get().name() + " to " + CPPType::get().name(); MFSignatureBuilder signature{name.c_str()}; signature.single_input("Input"); signature.single_output("Output"); return signature.build(); } void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override { const VArray &inputs = params.readonly_single_input(0); MutableSpan outputs = params.uninitialized_single_output(1); mask.to_best_mask_type([&](const auto &mask) { for (int64_t i : mask) { new (static_cast(&outputs[i])) To(inputs[i]); } }); } }; /** * 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