Geometry Nodes: refactor array devirtualization

Goals:
* Better high level control over where devirtualization occurs. There is always
  a trade-off between performance and compile-time/binary-size.
* Simplify using array devirtualization.
* Better performance for cases where devirtualization wasn't used before.

Many geometry nodes accept fields as inputs. Internally, that means that the
execution functions have to accept so called "virtual arrays" as inputs. Those
 can be e.g. actual arrays, just single values, or lazily computed arrays.
Due to these different possible virtual arrays implementations, access to
individual elements is slower than it would be if everything was just a normal
array (access does through a virtual function call). For more complex execution
functions, this overhead does not matter, but for small functions (like a simple
addition) it very much does. The virtual function call also prevents the compiler
from doing some optimizations (e.g. loop unrolling and inserting simd instructions).

The solution is to "devirtualize" the virtual arrays for small functions where the
overhead is measurable. Essentially, the function is generated many times with
different array types as input. Then there is a run-time dispatch that calls the
best implementation. We have been doing devirtualization in e.g. math nodes
for a long time already. This patch just generalizes the concept and makes it
easier to control. It also makes it easier to investigate the different trade-offs
when it comes to devirtualization.

Nodes that we've optimized using devirtualization before didn't get a speedup.
However, a couple of nodes are using devirtualization now, that didn't before.
Those got a 2-4x speedup in common cases.
* Map Range
* Random Value
* Switch
* Combine XYZ

Differential Revision: https://developer.blender.org/D14628
This commit is contained in:
Jacques Lucke 2022-04-26 17:12:34 +02:00
parent 9a53599180
commit ae94e36cfb
28 changed files with 1574 additions and 1180 deletions

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_devirtualize_parameters.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"

View File

@ -24,7 +24,8 @@ static void add_implicit_conversion(DataTypeConversions &conversions)
conversion_name.c_str(),
/* Use lambda instead of passing #ConversionF directly, because otherwise the compiler won't
* inline the function. */
[](const From &a) { return ConversionF(a); }};
[](const From &a) { return ConversionF(a); },
fn::CustomMF_presets::AllSpanOrSingle()};
static auto convert_single_to_initialized = [](const void *src, void *dst) {
*(To *)dst = ConversionF(*(const From *)src);
};

View File

@ -0,0 +1,309 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* In geometry nodes, many functions accept fields as inputs. For the implementation that means
* that the inputs are virtual arrays. Usually those are backed by actual arrays or single values
* but sometimes virtual arrays are used to compute values on demand or convert between data
* formats.
*
* Using virtual arrays has the downside that individual elements are accessed through a virtual
* method call, which has some overhead compared to normal array access. Whether this overhead is
* negilible depends on the context. For very small functions (e.g. a single addition), the
* overhead can make the function many times slower. Furthermore, it prevents the compiler from
* doing some optimizations (e.g. loop unrolling and inserting SIMD instructions).
*
* The solution is to "devirtualize" the virtual arrays in cases when the overhead cannot be
* ignored. That means that the function is instantiated multiple times at compile time for the
* different cases. For example, there can be an optimized function that adds a span and a single
* value, and another function that adds a span and another span. At run-time there is a dynamic
* dispatch that executes the best function given the specific virtual arrays.
*
* The problem with this devirtualization is that it can result in exponentially increasing compile
* times and binary sizes, depending on the number of parameters that are devirtualized separately.
* So there is always a trade-off between run-time performance and compile-time/binary-size.
*
* This file provides a utility to devirtualize array parameters to a function using a high level
* API. This makes it easy to experiment with different extremes of the mentioned trade-off and
* allows finding a good compromise for each function.
*/
#include "BLI_parameter_pack_utils.hh"
#include "BLI_virtual_array.hh"
namespace blender::devirtualize_parameters {
/**
* Bit flag that specifies how an individual parameter is or can be devirtualized.
*/
enum class DeviMode {
/* This is used as zero-value to compare to, to avoid casting to int. */
None = 0,
/* Don't use devirtualization for that parameter, just pass it along. */
Keep = (1 << 0),
/* Devirtualize #Varray as #Span. */
Span = (1 << 1),
/* Devirtualize #VArray as #SingleAsSpan. */
Single = (1 << 2),
/* Devirtualize #IndexMask as #IndexRange. */
Range = (1 << 3),
};
ENUM_OPERATORS(DeviMode, DeviMode::Range);
/** Utility to encode multiple #DeviMode in a type. */
template<DeviMode... Mode> using DeviModeSequence = ValueSequence<DeviMode, Mode...>;
/**
* Main class that performs the devirtualization.
*/
template<typename Fn, typename... SourceTypes> class Devirtualizer {
private:
/** Utility to get the tag of the I-th source type. */
template<size_t I>
using type_at_index = typename TypeSequence<SourceTypes...>::template at_index<I>;
static constexpr size_t SourceTypesNum = sizeof...(SourceTypes);
/** Function to devirtualize. */
Fn fn_;
/**
* Source values that will be devirtualized. Note that these are stored as pointers to avoid
* unnecessary copies. The caller is responsible for keeping the memory alive.
*/
std::tuple<const SourceTypes *...> sources_;
/** Keeps track of whether #fn_ has been called already to avoid calling it twice. */
bool executed_ = false;
public:
Devirtualizer(Fn fn, const SourceTypes *...sources) : fn_(std::move(fn)), sources_{sources...}
{
}
/**
* Return true when the function passed to the constructor has been called already.
*/
bool executed() const
{
return executed_;
}
/**
* At compile time, generates multiple variants of the function, each optimized for a different
* combination of devirtualized parameters. For every parameter, a bit flag is passed that
* determines how it will be devirtualized. At run-time, if possible, one of the generated
* functions is picked and executed.
*
* To check whether the function was called successfully, call #executed() afterwards.
*
* \note This generates an exponential amount of code in the final binary, depending on how many
* to-be-virtualized parameters there are.
*/
template<DeviMode... AllowedModes>
void try_execute_devirtualized(DeviModeSequence<AllowedModes...> /* allowed_modes */)
{
BLI_assert(!executed_);
static_assert(sizeof...(AllowedModes) == SourceTypesNum);
return this->try_execute_devirtualized_impl(DeviModeSequence<>(),
DeviModeSequence<AllowedModes...>());
}
/**
* Execute the function and pass in the original parameters without doing any devirtualization.
*/
void execute_without_devirtualization()
{
BLI_assert(!executed_);
this->try_execute_devirtualized_impl_call(
make_value_sequence<DeviMode, DeviMode::Keep, SourceTypesNum>(),
std::make_index_sequence<SourceTypesNum>());
}
private:
/**
* A recursive method that generates all the combinations of devirtualized parameters that the
* caller requested. A recursive function is necessary to achieve generating an exponential
* number of function calls (which has to be used with care, but is expected here).
*
* At every recursive step, the #DeviMode of one parameter is determined. This is achieved by
* extending #DeviModeSequence<Mode...> by one element in each step. The recursion ends once all
* parameters are handled.
*/
template<DeviMode... Mode, DeviMode... AllowedModes>
void try_execute_devirtualized_impl(
/* Initially empty, but then extended by one element in each recursive step. */
DeviModeSequence<Mode...> /* modes */,
/* Bit flag for every parameter. */
DeviModeSequence<AllowedModes...> /* allowed_modes */)
{
static_assert(SourceTypesNum == sizeof...(AllowedModes));
if constexpr (SourceTypesNum == sizeof...(Mode)) {
/* End of recursion, now call the function with the determined #DeviModes. */
this->try_execute_devirtualized_impl_call(DeviModeSequence<Mode...>(),
std::make_index_sequence<SourceTypesNum>());
}
else {
/* Index of the parameter that is checked in the current recursive step. */
constexpr size_t I = sizeof...(Mode);
/* Non-devirtualized parameter type. */
using SourceType = type_at_index<I>;
/* A bit flag indicating what devirtualizations are allowed in this step. */
constexpr DeviMode allowed_modes = DeviModeSequence<AllowedModes...>::template at_index<I>();
/* Handle #VArray types. */
if constexpr (is_VArray_v<SourceType>) {
/* The actual virtual array, used for dynamic dispatch at run-time. */
const SourceType &varray = *std::get<I>(sources_);
/* Check if the virtual array is a single value. */
if constexpr ((allowed_modes & DeviMode::Single) != DeviMode::None) {
if (varray.is_single()) {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Single>(),
DeviModeSequence<AllowedModes...>());
}
}
/* Check if the virtual array is a span. */
if constexpr ((allowed_modes & DeviMode::Span) != DeviMode::None) {
if (varray.is_span()) {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Span>(),
DeviModeSequence<AllowedModes...>());
}
}
/* Check if it is ok if the virtual array is not devirtualized. */
if constexpr ((allowed_modes & DeviMode::Keep) != DeviMode::None) {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Keep>(),
DeviModeSequence<AllowedModes...>());
}
}
/* Handle #IndexMask. */
else if constexpr (std::is_same_v<IndexMask, SourceType>) {
/* Check if the mask is actually a contiguous range. */
if constexpr ((allowed_modes & DeviMode::Range) != DeviMode::None) {
/* The actual mask used for dynamic dispatch at run-time. */
const IndexMask &mask = *std::get<I>(sources_);
if (mask.is_range()) {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Range>(),
DeviModeSequence<AllowedModes...>());
}
}
/* Check if mask is also allowed to stay a span. */
if constexpr ((allowed_modes & DeviMode::Span) != DeviMode::None) {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Span>(),
DeviModeSequence<AllowedModes...>());
}
}
/* Handle unknown types. */
else {
this->try_execute_devirtualized_impl(DeviModeSequence<Mode..., DeviMode::Keep>(),
DeviModeSequence<AllowedModes...>());
}
}
}
/**
* Actually call the function with devirtualized parameters.
*/
template<DeviMode... Mode, size_t... I>
void try_execute_devirtualized_impl_call(DeviModeSequence<Mode...> /* modes */,
std::index_sequence<I...> /* indices */)
{
fn_(this->get_devirtualized_parameter<I, Mode>()...);
executed_ = true;
}
/**
* Return the I-th parameter devirtualized using the passed in #DeviMode. This has different
* return types based on the template parameters.
*
* \note It is expected that the caller already knows that the parameter can be devirtualized
* with the given mode.
*/
template<size_t I, DeviMode Mode> decltype(auto) get_devirtualized_parameter()
{
using SourceType = type_at_index<I>;
static_assert(Mode != DeviMode::None);
if constexpr (Mode == DeviMode::Keep) {
/* Don't change the original parameter at all. */
return *std::get<I>(sources_);
}
if constexpr (is_VArray_v<SourceType>) {
const SourceType &varray = *std::get<I>(sources_);
if constexpr (Mode == DeviMode::Single) {
/* Devirtualize virtual array as single value. */
return SingleAsSpan(varray);
}
else if constexpr (Mode == DeviMode::Span) {
/* Devirtualize virtual array as span. */
return varray.get_internal_span();
}
}
else if constexpr (std::is_same_v<IndexMask, SourceType>) {
const IndexMask &mask = *std::get<I>(sources_);
if constexpr (ELEM(Mode, DeviMode::Span)) {
/* Don't devirtualize mask, it's still a span. */
return mask;
}
else if constexpr (Mode == DeviMode::Range) {
/* Devirtualize the mask as range. */
return mask.as_range();
}
}
}
};
} // namespace blender::devirtualize_parameters
namespace blender {
/**
* Generate multiple versions of the given function optimized for different virtual arrays.
* One has to be careful with nesting multiple devirtualizations, because that results in an
* exponential number of function instantiations (increasing compile time and binary size).
*
* Generally, this function should only be used when the virtual method call overhead to get an
* element from a virtual array is significant.
*/
template<typename T, typename Func>
inline void devirtualize_varray(const VArray<T> &varray, const Func &func, bool enable = true)
{
using namespace devirtualize_parameters;
if (enable) {
Devirtualizer<decltype(func), VArray<T>> devirtualizer(func, &varray);
constexpr DeviMode devi_mode = DeviMode::Single | DeviMode::Span;
devirtualizer.try_execute_devirtualized(DeviModeSequence<devi_mode>());
if (devirtualizer.executed()) {
return;
}
}
func(varray);
}
/**
* Same as `devirtualize_varray`, but devirtualizes two virtual arrays at the same time.
* This is better than nesting two calls to `devirtualize_varray`, because it instantiates fewer
* cases.
*/
template<typename T1, typename T2, typename Func>
inline void devirtualize_varray2(const VArray<T1> &varray1,
const VArray<T2> &varray2,
const Func &func,
bool enable = true)
{
using namespace devirtualize_parameters;
if (enable) {
Devirtualizer<decltype(func), VArray<T1>, VArray<T2>> devirtualizer(func, &varray1, &varray2);
constexpr DeviMode devi_mode = DeviMode::Single | DeviMode::Span;
devirtualizer.try_execute_devirtualized(DeviModeSequence<devi_mode, devi_mode>());
if (devirtualizer.executed()) {
return;
}
}
func(varray1, varray2);
}
} // namespace blender

View File

@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* C++ has a feature called "parameter packs" which allow building variadic templates.
* This file has some utilities to work with such parameter packs.
*/
#include <tuple>
#include <type_traits>
#include "BLI_utildefines.h"
namespace blender {
/**
* A type that encodes a specific value.
*/
template<typename T, T Element> struct TypeForValue {
static constexpr T value = Element;
};
/**
* A type that encodes a list of values of the same type.
* This is similar to #std::integer_sequence, but a bit more general. It's main purpose it to also
* support enums instead of just ints.
*/
template<typename T, T... Elements> struct ValueSequence {
/**
* Get the number of elements in the sequence.
*/
static constexpr size_t size() noexcept
{
return sizeof...(Elements);
}
/**
* Get the element at a specific index.
*/
template<size_t I> static constexpr T at_index()
{
static_assert(I < sizeof...(Elements));
return std::tuple_element_t<I, std::tuple<TypeForValue<T, Elements>...>>::value;
}
/**
* Return true if the element is in the sequence.
*/
template<T Element> static constexpr bool contains()
{
return ((Element == Elements) || ...);
}
};
/**
* A type that encodes a list of types.
* #std::tuple can also encode a list of types, but has a much more complex implementation.
*/
template<typename... T> struct TypeSequence {
/**
* Get the number of types in the sequence.
*/
static constexpr size_t size() noexcept
{
return sizeof...(T);
}
/**
* Get the type at a specific index.
*/
template<size_t I> using at_index = std::tuple_element_t<I, std::tuple<T...>>;
};
namespace detail {
template<typename T, T Value, size_t... I>
inline ValueSequence<T, ((I == 0) ? Value : Value)...> make_value_sequence_impl(
std::index_sequence<I...> /* indices */)
{
return {};
}
template<typename T, T Value1, T Value2, size_t... Value1Indices, size_t... I>
inline ValueSequence<T,
(ValueSequence<size_t, Value1Indices...>::template contains<I>() ? Value1 :
Value2)...>
make_two_value_sequence_impl(ValueSequence<size_t, Value1Indices...> /* value1_indices */,
std::index_sequence<I...> /* indices */)
{
return {};
};
} // namespace detail
/**
* Utility to create a #ValueSequence that has the same value at every index.
*/
template<typename T, T Value, size_t Size>
using make_value_sequence = decltype(detail::make_value_sequence_impl<T, Value>(
std::make_index_sequence<Size>()));
/**
* Utility to create a #ValueSequence that contains two different values. The indices of where the
* first value should be used are passed in.
*/
template<typename T, T Value1, T Value2, size_t Size, size_t... Value1Indices>
using make_two_value_sequence = decltype(detail::make_two_value_sequence_impl<T, Value1, Value2>(
ValueSequence<size_t, Value1Indices...>(), std::make_index_sequence<Size>()));
namespace parameter_pack_utils_static_tests {
enum class MyEnum { A, B };
static_assert(std::is_same_v<make_value_sequence<MyEnum, MyEnum::A, 3>,
ValueSequence<MyEnum, MyEnum::A, MyEnum::A, MyEnum::A>>);
static_assert(
std::is_same_v<make_two_value_sequence<MyEnum, MyEnum::A, MyEnum::B, 5, 1, 2>,
ValueSequence<MyEnum, MyEnum::B, MyEnum::A, MyEnum::A, MyEnum::B, MyEnum::B>>);
} // namespace parameter_pack_utils_static_tests
} // namespace blender

View File

@ -1089,6 +1089,12 @@ template<typename T> class VMutableArray : public VArrayCommon<T> {
}
};
template<typename T> static constexpr bool is_VArray_v = false;
template<typename T> static constexpr bool is_VArray_v<VArray<T>> = true;
template<typename T> static constexpr bool is_VMutableArray_v = false;
template<typename T> static constexpr bool is_VMutableArray_v<VMutableArray<T>> = true;
/**
* In many cases a virtual array is a span internally. In those cases, access to individual could
* be much more efficient than calling a virtual method. When the underlying virtual array is not a
@ -1207,69 +1213,4 @@ template<typename T> class SingleAsSpan {
}
};
/**
* Generate multiple versions of the given function optimized for different virtual arrays.
* One has to be careful with nesting multiple devirtualizations, because that results in an
* exponential number of function instantiations (increasing compile time and binary size).
*
* Generally, this function should only be used when the virtual method call overhead to get an
* element from a virtual array is significant.
*/
template<typename T, typename Func>
inline void devirtualize_varray(const VArray<T> &varray, const Func &func, bool enable = true)
{
/* Support disabling the devirtualization to simplify benchmarking. */
if (enable) {
if (varray.is_single()) {
func(SingleAsSpan<T>(varray));
return;
}
if (varray.is_span()) {
func(varray.get_internal_span());
return;
}
}
func(varray);
}
/**
* Same as `devirtualize_varray`, but devirtualizes two virtual arrays at the same time.
* This is better than nesting two calls to `devirtualize_varray`, because it instantiates fewer
* cases.
*/
template<typename T1, typename T2, typename Func>
inline void devirtualize_varray2(const VArray<T1> &varray1,
const VArray<T2> &varray2,
const Func &func,
bool enable = true)
{
/* Support disabling the devirtualization to simplify benchmarking. */
if (enable) {
const bool is_span1 = varray1.is_span();
const bool is_span2 = varray2.is_span();
const bool is_single1 = varray1.is_single();
const bool is_single2 = varray2.is_single();
if (is_span1 && is_span2) {
func(varray1.get_internal_span(), varray2.get_internal_span());
return;
}
if (is_span1 && is_single2) {
func(varray1.get_internal_span(), SingleAsSpan(varray2));
return;
}
if (is_single1 && is_span2) {
func(SingleAsSpan(varray1), varray2.get_internal_span());
return;
}
if (is_single1 && is_single2) {
func(SingleAsSpan(varray1), SingleAsSpan(varray2));
return;
}
}
/* This fallback is used even when one of the inputs could be optimized. It's probably not worth
* it to optimize just one of the inputs, because then the compiler still has to call into
* unknown code, which inhibits many compiler optimizations. */
func(varray1, varray2);
}
} // namespace blender

View File

@ -180,6 +180,7 @@ set(SRC
BLI_cpp_type.hh
BLI_cpp_type_make.hh
BLI_delaunay_2d.h
BLI_devirtualize_parameters.hh
BLI_dial_2d.h
BLI_disjoint_set.hh
BLI_dlrbTree.h
@ -274,6 +275,7 @@ set(SRC
BLI_noise.h
BLI_noise.hh
BLI_path_util.h
BLI_parameter_pack_utils.hh
BLI_polyfill_2d.h
BLI_polyfill_2d_beautify.h
BLI_probing_strategies.hh

View File

@ -10,10 +10,410 @@
#include <functional>
#include "BLI_devirtualize_parameters.hh"
#include "FN_multi_function.hh"
namespace blender::fn {
namespace devi = devirtualize_parameters;
/**
* These presets determine what code is generated for a #CustomMF. Different presets make different
* trade-offs between run-time performance and compile-time/binary size.
*/
namespace CustomMF_presets {
/** Method to execute a function in case devirtualization was not possible. */
enum class FallbackMode {
/** Access all elements in virtual arrays through virtual function calls. */
Simple,
/** Process elements in chunks to reduce virtual function call overhead. */
Materialized,
};
/**
* The "naive" method for executing a #CustomMF. Every element is processed separately and input
* values are retrieved from the virtual arrays one by one. This generates the least amount of
* code, but is also the slowest method.
*/
struct Simple {
static constexpr bool use_devirtualization = false;
static constexpr FallbackMode fallback_mode = FallbackMode::Simple;
};
/**
* This is an improvement over the #Simple method. It still generates a relatively small amount of
* code, because the function is only instantiated once. It's generally faster than #Simple,
* because inputs are retrieved from the virtual arrays in chunks, reducing virtual method call
* overhead.
*/
struct Materialized {
static constexpr bool use_devirtualization = false;
static constexpr FallbackMode fallback_mode = FallbackMode::Materialized;
};
/**
* The most efficient preset, but also potentially generates a lot of code (exponential in the
* number of inputs of the function). It generates separate optimized loops for all combinations of
* inputs. This should be used for small functions of which all inputs are likely to be single
* values or spans, and the number of inputs is relatively small.
*/
struct AllSpanOrSingle {
static constexpr bool use_devirtualization = true;
static constexpr FallbackMode fallback_mode = FallbackMode::Materialized;
template<typename Fn, typename... ParamTypes>
void try_devirtualize(devi::Devirtualizer<Fn, ParamTypes...> &devirtualizer)
{
using devi::DeviMode;
devirtualizer.try_execute_devirtualized(
make_value_sequence<DeviMode,
DeviMode::Span | DeviMode::Single | DeviMode::Range,
sizeof...(ParamTypes)>());
}
};
/**
* A slighly weaker variant of #AllSpanOrSingle. It generates less code, because it assumes that
* some of the inputs are most likely single values. It should be used for small functions which
* have too many inputs to make #AllSingleOrSpan a reasonable choice.
*/
template<size_t... Indices> struct SomeSpanOrSingle {
static constexpr bool use_devirtualization = true;
static constexpr FallbackMode fallback_mode = FallbackMode::Materialized;
template<typename Fn, typename... ParamTypes>
void try_devirtualize(devi::Devirtualizer<Fn, ParamTypes...> &devirtualizer)
{
using devi::DeviMode;
devirtualizer.try_execute_devirtualized(
make_two_value_sequence<DeviMode,
DeviMode::Span | DeviMode::Single | DeviMode::Range,
DeviMode::Single,
sizeof...(ParamTypes),
0,
(Indices + 1)...>());
}
};
} // namespace CustomMF_presets
namespace detail {
/**
* Executes #element_fn for all indices in the mask. The passed in #args contain the input as well
* as output parameters. Usually types in #args are devirtualized (e.g. a `Span<int>` is passed in
* instead of a `VArray<int>`).
*/
template<typename MaskT, typename... Args, typename... ParamTags, size_t... I, typename ElementFn>
void execute_array(TypeSequence<ParamTags...> /* param_tags */,
std::index_sequence<I...> /* indices */,
ElementFn element_fn,
MaskT mask,
/* Use restrict to tell the compiler that pointer inputs do not alias each
* other. This is important for some compiler optimizations. */
Args &&__restrict... args)
{
for (const int64_t i : mask) {
element_fn([&]() -> decltype(auto) {
using ParamTag = typename TypeSequence<ParamTags...>::template at_index<I>;
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
/* For inputs, pass the value (or a reference to it) to the function. */
return args[i];
}
else if constexpr (ParamTag::category == MFParamCategory::SingleOutput) {
/* For outputs, pass a pointer to the function. This is done instead of passing a
* reference, because the pointer points to uninitialized memory. */
return &args[i];
}
}()...);
}
}
} // namespace detail
namespace materialize_detail {
enum class ArgMode {
Unknown,
Single,
Span,
Materialized,
};
template<typename ParamTag> struct ArgInfo {
ArgMode mode = ArgMode::Unknown;
Span<typename ParamTag::base_type> internal_span;
};
/**
* Similar to #execute_array but accepts two mask inputs, one for inputs and one for outputs.
*/
template<typename... ParamTags, typename ElementFn, typename... Chunks>
void execute_materialized_impl(TypeSequence<ParamTags...> /* param_tags */,
const ElementFn element_fn,
const IndexRange in_mask,
const IndexMask out_mask,
Chunks &&__restrict... chunks)
{
BLI_assert(in_mask.size() == out_mask.size());
for (const int64_t i : IndexRange(in_mask.size())) {
const int64_t in_i = in_mask[i];
const int64_t out_i = out_mask[i];
element_fn([&]() -> decltype(auto) {
using ParamTag = ParamTags;
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
return chunks[in_i];
}
else if constexpr (ParamTag::category == MFParamCategory::SingleOutput) {
/* For outputs, a pointer is passed, because the memory is uninitialized. */
return &chunks[out_i];
}
}()...);
}
}
/**
* Executes #element_fn for all indices in #mask. However, instead of processing every element
* separately, processing happens in chunks. This allows retrieving from input virtual arrays in
* chunks, which reduces virtual function call overhead.
*/
template<typename... ParamTags, size_t... I, typename ElementFn, typename... Args>
void execute_materialized(TypeSequence<ParamTags...> /* param_tags */,
std::index_sequence<I...> /* indices */,
const ElementFn element_fn,
const IndexMask mask,
Args &&...args)
{
/* In theory, all elements could be processed in one chunk. However, that has the disadvantage
* that large temporary arrays are needed. Using small chunks allows using small arrays, which
* are reused multiple times, which improves cache efficiency. The chunk size also shouldn't be
* too small, because then overhead of the outer loop over chunks becomes significant again. */
static constexpr int64_t MaxChunkSize = 32;
const int64_t mask_size = mask.size();
const int64_t buffer_size = std::min(mask_size, MaxChunkSize);
/* Local buffers that are used to temporarily store values retrieved from virtual arrays. */
std::tuple<TypedBuffer<typename ParamTags::base_type, MaxChunkSize>...> buffers_owner;
/* A span for each parameter which is either empty or points to memory in #buffers_owner. */
std::tuple<MutableSpan<typename ParamTags::base_type>...> buffers;
/* Information about every parameter. */
std::tuple<ArgInfo<ParamTags>...> args_info;
(
/* Setup information for all parameters. */
[&] {
using ParamTag = ParamTags;
using T = typename ParamTag::base_type;
ArgInfo<ParamTags> &arg_info = std::get<I>(args_info);
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
VArray<T> &varray = *args;
if (varray.is_single()) {
/* If an input #VArray is a single value, we have to fill the buffer with that value
* only once. The same unchanged buffer can then be reused in every chunk. */
MutableSpan<T> in_chunk{std::get<I>(buffers_owner).ptr(), buffer_size};
const T in_single = varray.get_internal_single();
uninitialized_fill_n(in_chunk.data(), in_chunk.size(), in_single);
std::get<I>(buffers) = in_chunk;
arg_info.mode = ArgMode::Single;
}
else if (varray.is_span()) {
/* Remember the span so that it doesn't have to be retrieved in every iteration. */
arg_info.internal_span = varray.get_internal_span();
}
}
}(),
...);
/* Outer loop over all chunks. */
for (int64_t chunk_start = 0; chunk_start < mask_size; chunk_start += MaxChunkSize) {
const IndexMask sliced_mask = mask.slice(chunk_start, MaxChunkSize);
const int64_t chunk_size = sliced_mask.size();
const bool sliced_mask_is_range = sliced_mask.is_range();
execute_materialized_impl(
TypeSequence<ParamTags...>(),
element_fn,
/* Inputs are "compressed" into contiguous arrays without gaps. */
IndexRange(chunk_size),
/* Outputs are written directly into the correct place in the output arrays. */
sliced_mask,
/* Prepare every parameter for this chunk. */
[&] {
using ParamTag = ParamTags;
using T = typename ParamTag::base_type;
ArgInfo<ParamTags> &arg_info = std::get<I>(args_info);
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
if (arg_info.mode == ArgMode::Single) {
/* The single value has been filled into a buffer already reused for every chunk. */
return Span<T>(std::get<I>(buffers));
}
else {
const VArray<T> &varray = *args;
if (sliced_mask_is_range) {
if (!arg_info.internal_span.is_empty()) {
/* In this case we can just use an existing span instead of "compressing" it into
* a new temporary buffer. */
const IndexRange sliced_mask_range = sliced_mask.as_range();
arg_info.mode = ArgMode::Span;
return arg_info.internal_span.slice(sliced_mask_range);
}
}
/* As a fallback, do a virtual function call to retrieve all elements in the current
* chunk. The elements are stored in a temporary buffer reused for every chunk. */
MutableSpan<T> in_chunk{std::get<I>(buffers_owner).ptr(), chunk_size};
varray.materialize_compressed_to_uninitialized(sliced_mask, in_chunk);
/* Remember that this parameter has been materialized, so that the values are
* destructed properly when the chunk is done. */
arg_info.mode = ArgMode::Materialized;
return Span<T>(in_chunk);
}
}
else if constexpr (ParamTag::category == MFParamCategory::SingleOutput) {
/* For outputs, just pass a pointer. This is important so that `__restrict` works. */
return args->data();
}
}()...);
(
/* Destruct values that have been materialized before. */
[&] {
using ParamTag = ParamTags;
using T = typename ParamTag::base_type;
ArgInfo<ParamTags> &arg_info = std::get<I>(args_info);
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
if (arg_info.mode == ArgMode::Materialized) {
T *in_chunk = std::get<I>(buffers_owner).ptr();
destruct_n(in_chunk, chunk_size);
}
}
}(),
...);
}
(
/* Destruct buffers for single value inputs. */
[&] {
using ParamTag = ParamTags;
using T = typename ParamTag::base_type;
ArgInfo<ParamTags> &arg_info = std::get<I>(args_info);
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
if (arg_info.mode == ArgMode::Single) {
MutableSpan<T> in_chunk = std::get<I>(buffers);
destruct_n(in_chunk.data(), in_chunk.size());
}
}
}(),
...);
}
} // namespace materialize_detail
template<typename... ParamTags> class CustomMF : public MultiFunction {
private:
std::function<void(IndexMask mask, MFParams params)> fn_;
MFSignature signature_;
using TagsSequence = TypeSequence<ParamTags...>;
public:
template<typename ElementFn, typename ExecPreset = CustomMF_presets::Materialized>
CustomMF(const char *name,
ElementFn element_fn,
ExecPreset exec_preset = CustomMF_presets::Materialized())
{
MFSignatureBuilder signature{name};
add_signature_parameters(signature, std::make_index_sequence<TagsSequence::size()>());
signature_ = signature.build();
this->set_signature(&signature_);
fn_ = [element_fn, exec_preset](IndexMask mask, MFParams params) {
execute(
element_fn, exec_preset, mask, params, std::make_index_sequence<TagsSequence::size()>());
};
}
template<typename ElementFn, typename ExecPreset, size_t... I>
static void execute(ElementFn element_fn,
ExecPreset exec_preset,
IndexMask mask,
MFParams params,
std::index_sequence<I...> /* indices */)
{
std::tuple<typename ParamTags::array_type...> retrieved_params;
(
/* Get all parameters from #params and store them in #retrieved_params. */
[&]() {
using ParamTag = typename TagsSequence::template at_index<I>;
using T = typename ParamTag::base_type;
if constexpr (ParamTag::category == MFParamCategory::SingleInput) {
std::get<I>(retrieved_params) = params.readonly_single_input<T>(I);
}
if constexpr (ParamTag::category == MFParamCategory::SingleOutput) {
std::get<I>(retrieved_params) = params.uninitialized_single_output<T>(I);
}
}(),
...);
auto array_executor = [&](auto &&...args) {
detail::execute_array(TagsSequence(),
std::make_index_sequence<TagsSequence::size()>(),
element_fn,
std::forward<decltype(args)>(args)...);
};
/* First try devirtualized execution, since this is the most efficient. */
bool executed_devirtualized = false;
if constexpr (ExecPreset::use_devirtualization) {
devi::Devirtualizer<decltype(array_executor), IndexMask, typename ParamTags::array_type...>
devirtualizer{
array_executor, &mask, [&] { return &std::get<I>(retrieved_params); }()...};
exec_preset.try_devirtualize(devirtualizer);
executed_devirtualized = devirtualizer.executed();
}
/* If devirtualized execution was disabled or not possible, use a fallback method which is
* slower but always works. */
if (!executed_devirtualized) {
if constexpr (ExecPreset::fallback_mode == CustomMF_presets::FallbackMode::Materialized) {
materialize_detail::execute_materialized(
TypeSequence<ParamTags...>(), std::index_sequence<I...>(), element_fn, mask, [&] {
return &std::get<I>(retrieved_params);
}()...);
}
else {
detail::execute_array(TagsSequence(),
std::make_index_sequence<TagsSequence::size()>(),
element_fn,
mask,
std::get<I>(retrieved_params)...);
}
}
}
template<size_t... I>
static void add_signature_parameters(MFSignatureBuilder &signature,
std::index_sequence<I...> /* indices */)
{
(
/* Loop over all parameter types and add an entry for each in the signature. */
[&] {
using ParamTag = typename TagsSequence::template at_index<I>;
signature.add(ParamTag(), "");
}(),
...);
}
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
{
fn_(mask, params);
}
};
/**
* Generates a multi-function with the following parameters:
* 1. single input (SI) of type In1
@ -22,102 +422,20 @@ namespace blender::fn {
* This example creates a function that adds 10 to the incoming values:
* `CustomMF_SI_SO<int, int> fn("add 10", [](int value) { return value + 10; });`
*/
template<typename In1, typename Out1> class CustomMF_SI_SO : public MultiFunction {
private:
using FunctionT = std::function<void(IndexMask, const VArray<In1> &, MutableSpan<Out1>)>;
FunctionT function_;
MFSignature signature_;
template<typename In1, typename Out1>
class CustomMF_SI_SO : public CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleOutput, Out1>> {
public:
CustomMF_SI_SO(const char *name, FunctionT function) : function_(std::move(function))
template<typename ElementFn, typename ExecPreset = CustomMF_presets::Materialized>
CustomMF_SI_SO(const char *name,
ElementFn element_fn,
ExecPreset exec_preset = CustomMF_presets::Materialized())
: CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleOutput, Out1>>(
name,
[element_fn](const In1 &in1, Out1 *out1) { new (out1) Out1(element_fn(in1)); },
exec_preset)
{
MFSignatureBuilder signature{name};
signature.single_input<In1>("In1");
signature.single_output<Out1>("Out1");
signature_ = signature.build();
this->set_signature(&signature_);
}
template<typename ElementFuncT>
CustomMF_SI_SO(const char *name, ElementFuncT element_fn)
: CustomMF_SI_SO(name, CustomMF_SI_SO::create_function(element_fn))
{
}
template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
{
return [=](IndexMask mask, const VArray<In1> &in1, MutableSpan<Out1> 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> 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, MaxChunkSize> in1_buffer_owner;
MutableSpan<In1> 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> 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<typename ElementFuncT, typename MaskT, typename In1Array>
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<typename ElementFuncT, typename MaskT, typename In1Array>
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> &in1 = params.readonly_single_input<In1>(0);
MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(1);
function_(mask, in1, out1);
}
};
@ -128,62 +446,23 @@ template<typename In1, typename Out1> class CustomMF_SI_SO : public MultiFunctio
* 3. single output (SO) of type Out1
*/
template<typename In1, typename In2, typename Out1>
class CustomMF_SI_SI_SO : public MultiFunction {
private:
using FunctionT =
std::function<void(IndexMask, const VArray<In1> &, const VArray<In2> &, MutableSpan<Out1>)>;
FunctionT function_;
MFSignature signature_;
class CustomMF_SI_SI_SO : public CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleOutput, Out1>> {
public:
CustomMF_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function))
template<typename ElementFn, typename ExecPreset = CustomMF_presets::Materialized>
CustomMF_SI_SI_SO(const char *name,
ElementFn element_fn,
ExecPreset exec_preset = CustomMF_presets::Materialized())
: CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleOutput, Out1>>(
name,
[element_fn](const In1 &in1, const In2 &in2, Out1 *out1) {
new (out1) Out1(element_fn(in1, in2));
},
exec_preset)
{
MFSignatureBuilder signature{name};
signature.single_input<In1>("In1");
signature.single_input<In2>("In2");
signature.single_output<Out1>("Out1");
signature_ = signature.build();
this->set_signature(&signature_);
}
template<typename ElementFuncT>
CustomMF_SI_SI_SO(const char *name, ElementFuncT element_fn)
: CustomMF_SI_SI_SO(name, CustomMF_SI_SI_SO::create_function(element_fn))
{
}
template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
{
return [=](IndexMask mask,
const VArray<In1> &in1,
const VArray<In2> &in2,
MutableSpan<Out1> 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<typename ElementFuncT, typename MaskT, typename In1Array, typename In2Array>
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> &in1 = params.readonly_single_input<In1>(0);
const VArray<In2> &in2 = params.readonly_single_input<In2>(1);
MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(2);
function_(mask, in1, in2, out1);
}
};
@ -195,71 +474,25 @@ class CustomMF_SI_SI_SO : public MultiFunction {
* 4. single output (SO) of type Out1
*/
template<typename In1, typename In2, typename In3, typename Out1>
class CustomMF_SI_SI_SI_SO : public MultiFunction {
private:
using FunctionT = std::function<void(IndexMask,
const VArray<In1> &,
const VArray<In2> &,
const VArray<In3> &,
MutableSpan<Out1>)>;
FunctionT function_;
MFSignature signature_;
class CustomMF_SI_SI_SI_SO : public CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleInput, In3>,
MFParamTag<MFParamCategory::SingleOutput, Out1>> {
public:
CustomMF_SI_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function))
template<typename ElementFn, typename ExecPreset = CustomMF_presets::Materialized>
CustomMF_SI_SI_SI_SO(const char *name,
ElementFn element_fn,
ExecPreset exec_preset = CustomMF_presets::Materialized())
: CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleInput, In3>,
MFParamTag<MFParamCategory::SingleOutput, Out1>>(
name,
[element_fn](const In1 &in1, const In2 &in2, const In3 &in3, Out1 *out1) {
new (out1) Out1(element_fn(in1, in2, in3));
},
exec_preset)
{
MFSignatureBuilder signature{name};
signature.single_input<In1>("In1");
signature.single_input<In2>("In2");
signature.single_input<In3>("In3");
signature.single_output<Out1>("Out1");
signature_ = signature.build();
this->set_signature(&signature_);
}
template<typename ElementFuncT>
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<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
{
return [=](IndexMask mask,
const VArray<In1> &in1,
const VArray<In2> &in2,
const VArray<In3> &in3,
MutableSpan<Out1> 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<typename ElementFuncT,
typename MaskT,
typename In1Array,
typename In2Array,
typename In3Array>
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> &in1 = params.readonly_single_input<In1>(0);
const VArray<In2> &in2 = params.readonly_single_input<In2>(1);
const VArray<In3> &in3 = params.readonly_single_input<In3>(2);
MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(3);
function_(mask, in1, in2, in3, out1);
}
};
@ -272,77 +505,28 @@ class CustomMF_SI_SI_SI_SO : public MultiFunction {
* 5. single output (SO) of type Out1
*/
template<typename In1, typename In2, typename In3, typename In4, typename Out1>
class CustomMF_SI_SI_SI_SI_SO : public MultiFunction {
private:
using FunctionT = std::function<void(IndexMask,
const VArray<In1> &,
const VArray<In2> &,
const VArray<In3> &,
const VArray<In4> &,
MutableSpan<Out1>)>;
FunctionT function_;
MFSignature signature_;
class CustomMF_SI_SI_SI_SI_SO : public CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleInput, In3>,
MFParamTag<MFParamCategory::SingleInput, In4>,
MFParamTag<MFParamCategory::SingleOutput, Out1>> {
public:
CustomMF_SI_SI_SI_SI_SO(const char *name, FunctionT function) : function_(std::move(function))
template<typename ElementFn, typename ExecPreset = CustomMF_presets::Materialized>
CustomMF_SI_SI_SI_SI_SO(const char *name,
ElementFn element_fn,
ExecPreset exec_preset = CustomMF_presets::Materialized())
: CustomMF<MFParamTag<MFParamCategory::SingleInput, In1>,
MFParamTag<MFParamCategory::SingleInput, In2>,
MFParamTag<MFParamCategory::SingleInput, In3>,
MFParamTag<MFParamCategory::SingleInput, In4>,
MFParamTag<MFParamCategory::SingleOutput, Out1>>(
name,
[element_fn](
const In1 &in1, const In2 &in2, const In3 &in3, const In4 &in4, Out1 *out1) {
new (out1) Out1(element_fn(in1, in2, in3, in4));
},
exec_preset)
{
MFSignatureBuilder signature{name};
signature.single_input<In1>("In1");
signature.single_input<In2>("In2");
signature.single_input<In3>("In3");
signature.single_input<In4>("In4");
signature.single_output<Out1>("Out1");
signature_ = signature.build();
this->set_signature(&signature_);
}
template<typename ElementFuncT>
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<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
{
return [=](IndexMask mask,
const VArray<In1> &in1,
const VArray<In2> &in2,
const VArray<In3> &in3,
const VArray<In4> &in4,
MutableSpan<Out1> 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<typename ElementFuncT,
typename MaskT,
typename In1Array,
typename In2Array,
typename In3Array,
typename In4Array>
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> &in1 = params.readonly_single_input<In1>(0);
const VArray<In2> &in2 = params.readonly_single_input<In2>(1);
const VArray<In3> &in3 = params.readonly_single_input<In3>(2);
const VArray<In4> &in4 = params.readonly_single_input<In4>(3);
MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(4);
function_(mask, in1, in2, in3, in4, out1);
}
};

View File

@ -22,6 +22,23 @@
namespace blender::fn {
enum class MFParamCategory {
SingleInput,
VectorInput,
SingleOutput,
VectorOutput,
SingleMutable,
VectorMutable,
};
template<MFParamCategory Category, typename T> struct MFParamTag {
static constexpr MFParamCategory category = Category;
using base_type = T;
/* TODO: Doesn't support all categories yet, this can be generalized when necessary. */
using array_type =
std::conditional_t<Category == MFParamCategory::SingleInput, VArray<T>, MutableSpan<T>>;
};
class MFParamType {
public:
enum InterfaceType {
@ -30,15 +47,6 @@ class MFParamType {
Mutable,
};
enum Category {
SingleInput,
VectorInput,
SingleOutput,
VectorOutput,
SingleMutable,
VectorMutable,
};
private:
InterfaceType interface_type_;
MFDataType data_type_;
@ -89,34 +97,34 @@ class MFParamType {
return interface_type_;
}
Category category() const
MFParamCategory category() const
{
switch (data_type_.category()) {
case MFDataType::Single: {
switch (interface_type_) {
case Input:
return SingleInput;
return MFParamCategory::SingleInput;
case Output:
return SingleOutput;
return MFParamCategory::SingleOutput;
case Mutable:
return SingleMutable;
return MFParamCategory::SingleMutable;
}
break;
}
case MFDataType::Vector: {
switch (interface_type_) {
case Input:
return VectorInput;
return MFParamCategory::VectorInput;
case Output:
return VectorOutput;
return MFParamCategory::VectorOutput;
case Mutable:
return VectorMutable;
return MFParamCategory::VectorMutable;
}
break;
}
}
BLI_assert(false);
return SingleInput;
BLI_assert_unreachable();
return MFParamCategory::SingleInput;
}
bool is_input_or_mutable() const

View File

@ -111,7 +111,7 @@ class MFParamsBuilder {
this->assert_current_param_name(expected_name);
const int param_index = this->current_param_index();
const MFParamType &param_type = signature_->param_types[param_index];
BLI_assert(param_type.category() == MFParamType::SingleOutput);
BLI_assert(param_type.category() == MFParamCategory::SingleOutput);
const CPPType &type = param_type.data_type().single_type();
/* An empty span indicates that this is ignored. */
const GMutableSpan dummy_span{type};
@ -144,8 +144,8 @@ class MFParamsBuilder {
GMutableSpan computed_array(int param_index)
{
BLI_assert(ELEM(signature_->param_types[param_index].category(),
MFParamType::SingleOutput,
MFParamType::SingleMutable));
MFParamCategory::SingleOutput,
MFParamCategory::SingleMutable));
int data_index = signature_->data_index(param_index);
return mutable_spans_[data_index];
}
@ -153,8 +153,8 @@ class MFParamsBuilder {
GVectorArray &computed_vector_array(int param_index)
{
BLI_assert(ELEM(signature_->param_types[param_index].category(),
MFParamType::VectorOutput,
MFParamType::VectorMutable));
MFParamCategory::VectorOutput,
MFParamCategory::VectorMutable));
int data_index = signature_->data_index(param_index);
return *vector_arrays_[data_index];
}
@ -217,7 +217,7 @@ class MFParams {
}
const GVArray &readonly_single_input(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::SingleInput);
this->assert_correct_param(param_index, name, MFParamCategory::SingleInput);
int data_index = builder_->signature_->data_index(param_index);
return builder_->virtual_arrays_[data_index];
}
@ -230,7 +230,7 @@ class MFParams {
*/
bool single_output_is_required(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
this->assert_correct_param(param_index, name, MFParamCategory::SingleOutput);
int data_index = builder_->signature_->data_index(param_index);
return !builder_->mutable_spans_[data_index].is_empty();
}
@ -242,7 +242,7 @@ class MFParams {
}
GMutableSpan uninitialized_single_output(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
this->assert_correct_param(param_index, name, MFParamCategory::SingleOutput);
int data_index = builder_->signature_->data_index(param_index);
GMutableSpan span = builder_->mutable_spans_[data_index];
if (!span.is_empty()) {
@ -264,7 +264,7 @@ class MFParams {
}
GMutableSpan uninitialized_single_output_if_required(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
this->assert_correct_param(param_index, name, MFParamCategory::SingleOutput);
int data_index = builder_->signature_->data_index(param_index);
return builder_->mutable_spans_[data_index];
}
@ -277,7 +277,7 @@ class MFParams {
}
const GVVectorArray &readonly_vector_input(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::VectorInput);
this->assert_correct_param(param_index, name, MFParamCategory::VectorInput);
int data_index = builder_->signature_->data_index(param_index);
return *builder_->virtual_vector_arrays_[data_index];
}
@ -289,7 +289,7 @@ class MFParams {
}
GVectorArray &vector_output(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::VectorOutput);
this->assert_correct_param(param_index, name, MFParamCategory::VectorOutput);
int data_index = builder_->signature_->data_index(param_index);
return *builder_->vector_arrays_[data_index];
}
@ -300,7 +300,7 @@ class MFParams {
}
GMutableSpan single_mutable(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::SingleMutable);
this->assert_correct_param(param_index, name, MFParamCategory::SingleMutable);
int data_index = builder_->signature_->data_index(param_index);
return builder_->mutable_spans_[data_index];
}
@ -312,7 +312,7 @@ class MFParams {
}
GVectorArray &vector_mutable(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, MFParamType::VectorMutable);
this->assert_correct_param(param_index, name, MFParamCategory::VectorMutable);
int data_index = builder_->signature_->data_index(param_index);
return *builder_->vector_arrays_[data_index];
}
@ -329,7 +329,7 @@ class MFParams {
#endif
}
void assert_correct_param(int param_index, StringRef name, MFParamType::Category category)
void assert_correct_param(int param_index, StringRef name, MFParamCategory category)
{
UNUSED_VARS_NDEBUG(param_index, name, category);
#ifdef DEBUG

View File

@ -168,6 +168,32 @@ class MFSignatureBuilder {
}
}
template<MFParamCategory Category, typename T>
void add(MFParamTag<Category, T> /* tag */, const char *name)
{
switch (Category) {
case MFParamCategory::SingleInput:
this->single_input<T>(name);
return;
case MFParamCategory::VectorInput:
this->vector_input<T>(name);
return;
case MFParamCategory::SingleOutput:
this->single_output<T>(name);
return;
case MFParamCategory::VectorOutput:
this->vector_output<T>(name);
return;
case MFParamCategory::SingleMutable:
this->single_mutable<T>(name);
return;
case MFParamCategory::VectorMutable:
this->vector_mutable<T>(name);
return;
}
BLI_assert_unreachable();
}
/* Context */
/** This indicates that the function accesses the context. This disables optimizations that

View File

@ -96,18 +96,18 @@ void MultiFunction::call_auto(IndexMask mask, MFParams params, MFContext context
for (const int param_index : this->param_indices()) {
const MFParamType param_type = this->param_type(param_index);
switch (param_type.category()) {
case MFParamType::SingleInput: {
case MFParamCategory::SingleInput: {
const GVArray &varray = params.readonly_single_input(param_index);
offset_params.add_readonly_single_input(varray.slice(input_slice_range));
break;
}
case MFParamType::SingleMutable: {
case MFParamCategory::SingleMutable: {
const GMutableSpan span = params.single_mutable(param_index);
const GMutableSpan sliced_span = span.slice(input_slice_range);
offset_params.add_single_mutable(sliced_span);
break;
}
case MFParamType::SingleOutput: {
case MFParamCategory::SingleOutput: {
const GMutableSpan span = params.uninitialized_single_output_if_required(param_index);
if (span.is_empty()) {
offset_params.add_ignored_single_output();
@ -118,9 +118,9 @@ void MultiFunction::call_auto(IndexMask mask, MFParams params, MFContext context
}
break;
}
case MFParamType::VectorInput:
case MFParamType::VectorMutable:
case MFParamType::VectorOutput: {
case MFParamCategory::VectorInput:
case MFParamCategory::VectorMutable:
case MFParamCategory::VectorOutput: {
BLI_assert_unreachable();
break;
}

View File

@ -314,7 +314,7 @@ bool MFProcedure::validate_all_params_provided() const
const MultiFunction &fn = instruction->fn();
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
if (param_type.category() == MFParamType::SingleOutput) {
if (param_type.category() == MFParamCategory::SingleOutput) {
/* Single outputs are optional. */
continue;
}

View File

@ -854,32 +854,32 @@ class VariableStates {
};
switch (param_type.category()) {
case MFParamType::SingleInput: {
case MFParamCategory::SingleInput: {
const GVArray &data = params.readonly_single_input(param_index);
add_state(value_allocator_.obtain_GVArray(data), true);
break;
}
case MFParamType::VectorInput: {
case MFParamCategory::VectorInput: {
const GVVectorArray &data = params.readonly_vector_input(param_index);
add_state(value_allocator_.obtain_GVVectorArray(data), true);
break;
}
case MFParamType::SingleOutput: {
case MFParamCategory::SingleOutput: {
GMutableSpan data = params.uninitialized_single_output(param_index);
add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data());
break;
}
case MFParamType::VectorOutput: {
case MFParamCategory::VectorOutput: {
GVectorArray &data = params.vector_output(param_index);
add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data);
break;
}
case MFParamType::SingleMutable: {
case MFParamCategory::SingleMutable: {
GMutableSpan data = params.single_mutable(param_index);
add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data());
break;
}
case MFParamType::VectorMutable: {
case MFParamCategory::VectorMutable: {
GVectorArray &data = params.vector_mutable(param_index);
add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data);
break;

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_devirtualize_parameters.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"

View File

@ -9,6 +9,8 @@
#include "BLI_math_vector.hh"
#include "BLI_string_ref.hh"
#include "FN_multi_function_builder.hh"
namespace blender::nodes {
struct FloatMathOperationInfo {
@ -49,55 +51,58 @@ inline bool try_dispatch_float_math_fl_to_fl(const int operation, Callback &&cal
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just an utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_MATH_EXPONENT:
return dispatch([](float a) { return expf(a); });
return dispatch(exec_preset_slow, [](float a) { return expf(a); });
case NODE_MATH_SQRT:
return dispatch([](float a) { return safe_sqrtf(a); });
return dispatch(exec_preset_fast, [](float a) { return safe_sqrtf(a); });
case NODE_MATH_INV_SQRT:
return dispatch([](float a) { return safe_inverse_sqrtf(a); });
return dispatch(exec_preset_fast, [](float a) { return safe_inverse_sqrtf(a); });
case NODE_MATH_ABSOLUTE:
return dispatch([](float a) { return fabs(a); });
return dispatch(exec_preset_fast, [](float a) { return fabs(a); });
case NODE_MATH_RADIANS:
return dispatch([](float a) { return (float)DEG2RAD(a); });
return dispatch(exec_preset_fast, [](float a) { return (float)DEG2RAD(a); });
case NODE_MATH_DEGREES:
return dispatch([](float a) { return (float)RAD2DEG(a); });
return dispatch(exec_preset_fast, [](float a) { return (float)RAD2DEG(a); });
case NODE_MATH_SIGN:
return dispatch([](float a) { return compatible_signf(a); });
return dispatch(exec_preset_fast, [](float a) { return compatible_signf(a); });
case NODE_MATH_ROUND:
return dispatch([](float a) { return floorf(a + 0.5f); });
return dispatch(exec_preset_fast, [](float a) { return floorf(a + 0.5f); });
case NODE_MATH_FLOOR:
return dispatch([](float a) { return floorf(a); });
return dispatch(exec_preset_fast, [](float a) { return floorf(a); });
case NODE_MATH_CEIL:
return dispatch([](float a) { return ceilf(a); });
return dispatch(exec_preset_fast, [](float a) { return ceilf(a); });
case NODE_MATH_FRACTION:
return dispatch([](float a) { return a - floorf(a); });
return dispatch(exec_preset_fast, [](float a) { return a - floorf(a); });
case NODE_MATH_TRUNC:
return dispatch([](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); });
return dispatch(exec_preset_fast, [](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); });
case NODE_MATH_SINE:
return dispatch([](float a) { return sinf(a); });
return dispatch(exec_preset_slow, [](float a) { return sinf(a); });
case NODE_MATH_COSINE:
return dispatch([](float a) { return cosf(a); });
return dispatch(exec_preset_slow, [](float a) { return cosf(a); });
case NODE_MATH_TANGENT:
return dispatch([](float a) { return tanf(a); });
return dispatch(exec_preset_slow, [](float a) { return tanf(a); });
case NODE_MATH_SINH:
return dispatch([](float a) { return sinhf(a); });
return dispatch(exec_preset_slow, [](float a) { return sinhf(a); });
case NODE_MATH_COSH:
return dispatch([](float a) { return coshf(a); });
return dispatch(exec_preset_slow, [](float a) { return coshf(a); });
case NODE_MATH_TANH:
return dispatch([](float a) { return tanhf(a); });
return dispatch(exec_preset_slow, [](float a) { return tanhf(a); });
case NODE_MATH_ARCSINE:
return dispatch([](float a) { return safe_asinf(a); });
return dispatch(exec_preset_slow, [](float a) { return safe_asinf(a); });
case NODE_MATH_ARCCOSINE:
return dispatch([](float a) { return safe_acosf(a); });
return dispatch(exec_preset_slow, [](float a) { return safe_acosf(a); });
case NODE_MATH_ARCTANGENT:
return dispatch([](float a) { return atanf(a); });
return dispatch(exec_preset_slow, [](float a) { return atanf(a); });
}
return false;
}
@ -113,41 +118,45 @@ inline bool try_dispatch_float_math_fl_fl_to_fl(const int operation, Callback &&
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just an utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_MATH_ADD:
return dispatch([](float a, float b) { return a + b; });
return dispatch(exec_preset_fast, [](float a, float b) { return a + b; });
case NODE_MATH_SUBTRACT:
return dispatch([](float a, float b) { return a - b; });
return dispatch(exec_preset_fast, [](float a, float b) { return a - b; });
case NODE_MATH_MULTIPLY:
return dispatch([](float a, float b) { return a * b; });
return dispatch(exec_preset_fast, [](float a, float b) { return a * b; });
case NODE_MATH_DIVIDE:
return dispatch([](float a, float b) { return safe_divide(a, b); });
return dispatch(exec_preset_fast, [](float a, float b) { return safe_divide(a, b); });
case NODE_MATH_POWER:
return dispatch([](float a, float b) { return safe_powf(a, b); });
return dispatch(exec_preset_slow, [](float a, float b) { return safe_powf(a, b); });
case NODE_MATH_LOGARITHM:
return dispatch([](float a, float b) { return safe_logf(a, b); });
return dispatch(exec_preset_slow, [](float a, float b) { return safe_logf(a, b); });
case NODE_MATH_MINIMUM:
return dispatch([](float a, float b) { return std::min(a, b); });
return dispatch(exec_preset_fast, [](float a, float b) { return std::min(a, b); });
case NODE_MATH_MAXIMUM:
return dispatch([](float a, float b) { return std::max(a, b); });
return dispatch(exec_preset_fast, [](float a, float b) { return std::max(a, b); });
case NODE_MATH_LESS_THAN:
return dispatch([](float a, float b) { return (float)(a < b); });
return dispatch(exec_preset_fast, [](float a, float b) { return (float)(a < b); });
case NODE_MATH_GREATER_THAN:
return dispatch([](float a, float b) { return (float)(a > b); });
return dispatch(exec_preset_fast, [](float a, float b) { return (float)(a > b); });
case NODE_MATH_MODULO:
return dispatch([](float a, float b) { return safe_modf(a, b); });
return dispatch(exec_preset_fast, [](float a, float b) { return safe_modf(a, b); });
case NODE_MATH_SNAP:
return dispatch([](float a, float b) { return floorf(safe_divide(a, b)) * b; });
return dispatch(exec_preset_fast,
[](float a, float b) { return floorf(safe_divide(a, b)) * b; });
case NODE_MATH_ARCTAN2:
return dispatch([](float a, float b) { return atan2f(a, b); });
return dispatch(exec_preset_slow, [](float a, float b) { return atan2f(a, b); });
case NODE_MATH_PINGPONG:
return dispatch([](float a, float b) { return pingpongf(a, b); });
return dispatch(exec_preset_fast, [](float a, float b) { return pingpongf(a, b); });
}
return false;
}
@ -164,57 +173,29 @@ inline bool try_dispatch_float_math_fl_fl_fl_to_fl(const int operation, Callback
}
/* This is just an utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_MATH_MULTIPLY_ADD:
return dispatch([](float a, float b, float c) { return a * b + c; });
return dispatch(fn::CustomMF_presets::AllSpanOrSingle(),
[](float a, float b, float c) { return a * b + c; });
case NODE_MATH_COMPARE:
return dispatch([](float a, float b, float c) -> float {
return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f;
});
return dispatch(fn::CustomMF_presets::SomeSpanOrSingle<0, 1>(),
[](float a, float b, float c) -> float {
return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f;
});
case NODE_MATH_SMOOTH_MIN:
return dispatch([](float a, float b, float c) { return smoothminf(a, b, c); });
return dispatch(fn::CustomMF_presets::SomeSpanOrSingle<0, 1>(),
[](float a, float b, float c) { return smoothminf(a, b, c); });
case NODE_MATH_SMOOTH_MAX:
return dispatch([](float a, float b, float c) { return -smoothminf(-a, -b, c); });
return dispatch(fn::CustomMF_presets::SomeSpanOrSingle<0, 1>(),
[](float a, float b, float c) { return -smoothminf(-a, -b, c); });
case NODE_MATH_WRAP:
return dispatch([](float a, float b, float c) { return wrapf(a, b, c); });
}
return false;
}
/**
* This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature.
*/
template<typename Callback>
inline bool try_dispatch_float_math_fl_fl_to_bool(const NodeCompareOperation operation,
Callback &&callback)
{
const FloatMathOperationInfo *info = get_float_compare_operation_info(operation);
if (info == nullptr) {
return false;
}
/* This is just an utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
return true;
};
switch (operation) {
case NODE_COMPARE_LESS_THAN:
return dispatch([](float a, float b) { return a < b; });
case NODE_COMPARE_LESS_EQUAL:
return dispatch([](float a, float b) { return a <= b; });
case NODE_COMPARE_GREATER_THAN:
return dispatch([](float a, float b) { return a > b; });
case NODE_COMPARE_GREATER_EQUAL:
return dispatch([](float a, float b) { return a >= b; });
default:
return false;
return dispatch(fn::CustomMF_presets::SomeSpanOrSingle<0>(),
[](float a, float b, float c) { return wrapf(a, b, c); });
}
return false;
}
@ -233,35 +214,41 @@ inline bool try_dispatch_float_math_fl3_fl3_to_fl3(const NodeVectorMathOperation
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_ADD:
return dispatch([](float3 a, float3 b) { return a + b; });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return a + b; });
case NODE_VECTOR_MATH_SUBTRACT:
return dispatch([](float3 a, float3 b) { return a - b; });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return a - b; });
case NODE_VECTOR_MATH_MULTIPLY:
return dispatch([](float3 a, float3 b) { return a * b; });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return a * b; });
case NODE_VECTOR_MATH_DIVIDE:
return dispatch([](float3 a, float3 b) { return safe_divide(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return safe_divide(a, b); });
case NODE_VECTOR_MATH_CROSS_PRODUCT:
return dispatch([](float3 a, float3 b) { return cross_high_precision(a, b); });
return dispatch(exec_preset_fast,
[](float3 a, float3 b) { return cross_high_precision(a, b); });
case NODE_VECTOR_MATH_PROJECT:
return dispatch([](float3 a, float3 b) { return project(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return project(a, b); });
case NODE_VECTOR_MATH_REFLECT:
return dispatch([](float3 a, float3 b) { return reflect(a, normalize(b)); });
return dispatch(exec_preset_fast,
[](float3 a, float3 b) { return reflect(a, normalize(b)); });
case NODE_VECTOR_MATH_SNAP:
return dispatch([](float3 a, float3 b) { return floor(safe_divide(a, b)) * b; });
return dispatch(exec_preset_fast,
[](float3 a, float3 b) { return floor(safe_divide(a, b)) * b; });
case NODE_VECTOR_MATH_MODULO:
return dispatch([](float3 a, float3 b) { return mod(a, b); });
return dispatch(exec_preset_slow, [](float3 a, float3 b) { return mod(a, b); });
case NODE_VECTOR_MATH_MINIMUM:
return dispatch([](float3 a, float3 b) { return min(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return min(a, b); });
case NODE_VECTOR_MATH_MAXIMUM:
return dispatch([](float3 a, float3 b) { return max(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return max(a, b); });
default:
return false;
}
@ -282,17 +269,19 @@ inline bool try_dispatch_float_math_fl3_fl3_to_fl(const NodeVectorMathOperation
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_DOT_PRODUCT:
return dispatch([](float3 a, float3 b) { return dot(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return dot(a, b); });
case NODE_VECTOR_MATH_DISTANCE:
return dispatch([](float3 a, float3 b) { return distance(a, b); });
return dispatch(exec_preset_fast, [](float3 a, float3 b) { return distance(a, b); });
default:
return false;
}
@ -313,21 +302,25 @@ inline bool try_dispatch_float_math_fl3_fl3_fl3_to_fl3(const NodeVectorMathOpera
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_MULTIPLY_ADD:
return dispatch([](float3 a, float3 b, float3 c) { return a * b + c; });
return dispatch(exec_preset_fast, [](float3 a, float3 b, float3 c) { return a * b + c; });
case NODE_VECTOR_MATH_WRAP:
return dispatch([](float3 a, float3 b, float3 c) {
return dispatch(exec_preset_slow, [](float3 a, float3 b, 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));
});
case NODE_VECTOR_MATH_FACEFORWARD:
return dispatch([](float3 a, float3 b, float3 c) { return faceforward(a, b, c); });
return dispatch(exec_preset_fast,
[](float3 a, float3 b, float3 c) { return faceforward(a, b, c); });
default:
return false;
}
@ -348,15 +341,18 @@ inline bool try_dispatch_float_math_fl3_fl3_fl_to_fl3(const NodeVectorMathOperat
return false;
}
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_REFRACT:
return dispatch([](float3 a, float3 b, float c) { return refract(a, normalize(b), c); });
return dispatch(exec_preset_slow,
[](float3 a, float3 b, float c) { return refract(a, normalize(b), c); });
default:
return false;
}
@ -377,15 +373,17 @@ inline bool try_dispatch_float_math_fl3_to_fl(const NodeVectorMathOperation oper
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_LENGTH:
return dispatch([](float3 in) { return length(in); });
return dispatch(exec_preset_fast, [](float3 in) { return length(in); });
default:
return false;
}
@ -404,15 +402,17 @@ inline bool try_dispatch_float_math_fl3_fl_to_fl3(const NodeVectorMathOperation
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_SCALE:
return dispatch([](float3 a, float b) { return a * b; });
return dispatch(exec_preset_fast, [](float3 a, float b) { return a * b; });
default:
return false;
}
@ -433,29 +433,36 @@ inline bool try_dispatch_float_math_fl3_to_fl3(const NodeVectorMathOperation ope
return false;
}
static auto exec_preset_fast = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_slow = fn::CustomMF_presets::Materialized();
/* This is just a utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
auto dispatch = [&](auto exec_preset, auto math_function) -> bool {
callback(exec_preset, math_function, *info);
return true;
};
switch (operation) {
case NODE_VECTOR_MATH_NORMALIZE:
return dispatch([](float3 in) { return normalize(in); }); /* Should be safe. */
return dispatch(exec_preset_fast,
[](float3 in) { return normalize(in); }); /* Should be safe. */
case NODE_VECTOR_MATH_FLOOR:
return dispatch([](float3 in) { return floor(in); });
return dispatch(exec_preset_fast, [](float3 in) { return floor(in); });
case NODE_VECTOR_MATH_CEIL:
return dispatch([](float3 in) { return ceil(in); });
return dispatch(exec_preset_fast, [](float3 in) { return ceil(in); });
case NODE_VECTOR_MATH_FRACTION:
return dispatch([](float3 in) { return fract(in); });
return dispatch(exec_preset_fast, [](float3 in) { return fract(in); });
case NODE_VECTOR_MATH_ABSOLUTE:
return dispatch([](float3 in) { return abs(in); });
return dispatch(exec_preset_fast, [](float3 in) { return abs(in); });
case NODE_VECTOR_MATH_SINE:
return dispatch([](float3 in) { return float3(sinf(in.x), sinf(in.y), sinf(in.z)); });
return dispatch(exec_preset_slow,
[](float3 in) { return float3(sinf(in.x), sinf(in.y), sinf(in.z)); });
case NODE_VECTOR_MATH_COSINE:
return dispatch([](float3 in) { return float3(cosf(in.x), cosf(in.y), cosf(in.z)); });
return dispatch(exec_preset_slow,
[](float3 in) { return float3(cosf(in.x), cosf(in.y), cosf(in.z)); });
case NODE_VECTOR_MATH_TANGENT:
return dispatch([](float3 in) { return float3(tanf(in.x), tanf(in.y), tanf(in.z)); });
return dispatch(exec_preset_slow,
[](float3 in) { return float3(tanf(in.x), tanf(in.y), tanf(in.z)); });
default:
return false;
}

View File

@ -70,23 +70,24 @@ static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static const fn::MultiFunction *get_multi_function(bNode &bnode)
{
static fn::CustomMF_SI_SI_SO<bool, bool, bool> and_fn{"And",
[](bool a, bool b) { return a && b; }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> or_fn{"Or",
[](bool a, bool b) { return a || b; }};
static fn::CustomMF_SI_SO<bool, bool> not_fn{"Not", [](bool a) { return !a; }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nand_fn{"Not And",
[](bool a, bool b) { return !(a && b); }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nor_fn{"Nor",
[](bool a, bool b) { return !(a || b); }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> xnor_fn{"Equal",
[](bool a, bool b) { return a == b; }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> xor_fn{"Not Equal",
[](bool a, bool b) { return a != b; }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> imply_fn{"Imply",
[](bool a, bool b) { return !a || b; }};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nimply_fn{"Subtract",
[](bool a, bool b) { return a && !b; }};
static auto exec_preset = fn::CustomMF_presets::AllSpanOrSingle();
static fn::CustomMF_SI_SI_SO<bool, bool, bool> and_fn{
"And", [](bool a, bool b) { return a && b; }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> or_fn{
"Or", [](bool a, bool b) { return a || b; }, exec_preset};
static fn::CustomMF_SI_SO<bool, bool> not_fn{"Not", [](bool a) { return !a; }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nand_fn{
"Not And", [](bool a, bool b) { return !(a && b); }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nor_fn{
"Nor", [](bool a, bool b) { return !(a || b); }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> xnor_fn{
"Equal", [](bool a, bool b) { return a == b; }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> xor_fn{
"Not Equal", [](bool a, bool b) { return a != b; }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> imply_fn{
"Imply", [](bool a, bool b) { return !a || b; }, exec_preset};
static fn::CustomMF_SI_SI_SO<bool, bool, bool> nimply_fn{
"Subtract", [](bool a, bool b) { return a && !b; }, exec_preset};
switch (bnode.custom1) {
case NODE_BOOLEAN_MATH_AND:

View File

@ -171,71 +171,77 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
{
const NodeFunctionCompare *data = (NodeFunctionCompare *)node.storage;
static auto exec_preset_all = fn::CustomMF_presets::AllSpanOrSingle();
static auto exec_preset_first_two = fn::CustomMF_presets::SomeSpanOrSingle<0, 1>();
switch (data->data_type) {
case SOCK_FLOAT:
switch (data->operation) {
case NODE_COMPARE_LESS_THAN: {
static fn::CustomMF_SI_SI_SO<float, float, bool> fn{
"Less Than", [](float a, float b) { return a < b; }};
"Less Than", [](float a, float b) { return a < b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_LESS_EQUAL: {
static fn::CustomMF_SI_SI_SO<float, float, bool> fn{
"Less Equal", [](float a, float b) { return a <= b; }};
"Less Equal", [](float a, float b) { return a <= b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_GREATER_THAN: {
static fn::CustomMF_SI_SI_SO<float, float, bool> fn{
"Greater Than", [](float a, float b) { return a > b; }};
"Greater Than", [](float a, float b) { return a > b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_GREATER_EQUAL: {
static fn::CustomMF_SI_SI_SO<float, float, bool> fn{
"Greater Equal", [](float a, float b) { return a >= b; }};
"Greater Equal", [](float a, float b) { return a >= b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_EQUAL: {
static fn::CustomMF_SI_SI_SI_SO<float, float, float, bool> fn{
"Equal", [](float a, float b, float epsilon) { return std::abs(a - b) <= epsilon; }};
"Equal",
[](float a, float b, float epsilon) { return std::abs(a - b) <= epsilon; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_NOT_EQUAL:
static fn::CustomMF_SI_SI_SI_SO<float, float, float, bool> fn{
"Not Equal",
[](float a, float b, float epsilon) { return std::abs(a - b) > epsilon; }};
[](float a, float b, float epsilon) { return std::abs(a - b) > epsilon; },
exec_preset_first_two};
return &fn;
}
break;
case SOCK_INT:
switch (data->operation) {
case NODE_COMPARE_LESS_THAN: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Less Than",
[](int a, int b) { return a < b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Less Than", [](int a, int b) { return a < b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_LESS_EQUAL: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Less Equal",
[](int a, int b) { return a <= b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Less Equal", [](int a, int b) { return a <= b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_GREATER_THAN: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Greater Than",
[](int a, int b) { return a > b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Greater Than", [](int a, int b) { return a > b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_GREATER_EQUAL: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Greater Equal",
[](int a, int b) { return a >= b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Greater Equal", [](int a, int b) { return a >= b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_EQUAL: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Equal",
[](int a, int b) { return a == b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Equal", [](int a, int b) { return a == b; }, exec_preset_all};
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{"Not Equal",
[](int a, int b) { return a != b; }};
static fn::CustomMF_SI_SI_SO<int, int, bool> fn{
"Not Equal", [](int a, int b) { return a != b; }, exec_preset_all};
return &fn;
}
}
@ -247,31 +253,36 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Than - Average",
[](float3 a, float3 b) { return component_average(a) < component_average(b); }};
[](float3 a, float3 b) { return component_average(a) < component_average(b); },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Less Than - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) < comp; }};
[](float3 a, float3 b, float comp) { return math::dot(a, b) < comp; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Less Than - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) < angle; }};
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) < angle; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Than - Element-wise",
[](float3 a, float3 b) { return a.x < b.x && a.y < b.y && a.z < b.z; }};
[](float3 a, float3 b) { return a.x < b.x && a.y < b.y && a.z < b.z; },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Than - Length",
[](float3 a, float3 b) { return math::length(a) < math::length(b); }};
[](float3 a, float3 b) { return math::length(a) < math::length(b); },
exec_preset_all};
return &fn;
}
}
@ -281,31 +292,36 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Equal - Average",
[](float3 a, float3 b) { return component_average(a) <= component_average(b); }};
[](float3 a, float3 b) { return component_average(a) <= component_average(b); },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Less Equal - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) <= comp; }};
[](float3 a, float3 b, float comp) { return math::dot(a, b) <= comp; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Less Equal - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) <= angle; }};
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) <= angle; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Equal - Element-wise",
[](float3 a, float3 b) { return a.x <= b.x && a.y <= b.y && a.z <= b.z; }};
[](float3 a, float3 b) { return a.x <= b.x && a.y <= b.y && a.z <= b.z; },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Less Equal - Length",
[](float3 a, float3 b) { return math::length(a) <= math::length(b); }};
[](float3 a, float3 b) { return math::length(a) <= math::length(b); },
exec_preset_all};
return &fn;
}
}
@ -315,31 +331,36 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Than - Average",
[](float3 a, float3 b) { return component_average(a) > component_average(b); }};
[](float3 a, float3 b) { return component_average(a) > component_average(b); },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Greater Than - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) > comp; }};
[](float3 a, float3 b, float comp) { return math::dot(a, b) > comp; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Greater Than - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) > angle; }};
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) > angle; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Than - Element-wise",
[](float3 a, float3 b) { return a.x > b.x && a.y > b.y && a.z > b.z; }};
[](float3 a, float3 b) { return a.x > b.x && a.y > b.y && a.z > b.z; },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Than - Length",
[](float3 a, float3 b) { return math::length(a) > math::length(b); }};
[](float3 a, float3 b) { return math::length(a) > math::length(b); },
exec_preset_all};
return &fn;
}
}
@ -349,31 +370,36 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Equal - Average",
[](float3 a, float3 b) { return component_average(a) >= component_average(b); }};
[](float3 a, float3 b) { return component_average(a) >= component_average(b); },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Greater Equal - Dot Product",
[](float3 a, float3 b, float comp) { return math::dot(a, b) >= comp; }};
[](float3 a, float3 b, float comp) { return math::dot(a, b) >= comp; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Greater Equal - Direction",
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) >= angle; }};
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) >= angle; },
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Equal - Element-wise",
[](float3 a, float3 b) { return a.x >= b.x && a.y >= b.y && a.z >= b.z; }};
[](float3 a, float3 b) { return a.x >= b.x && a.y >= b.y && a.z >= b.z; },
exec_preset_all};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SO<float3, float3, bool> fn{
"Greater Equal - Length",
[](float3 a, float3 b) { return math::length(a) >= math::length(b); }};
[](float3 a, float3 b) { return math::length(a) >= math::length(b); },
exec_preset_all};
return &fn;
}
}
@ -382,38 +408,48 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Equal - Average", [](float3 a, float3 b, float epsilon) {
"Equal - Average",
[](float3 a, float3 b, float epsilon) {
return abs(component_average(a) - component_average(b)) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SI_SO<float3, float3, float, float, bool> fn{
"Equal - Dot Product", [](float3 a, float3 b, float comp, float epsilon) {
"Equal - Dot Product",
[](float3 a, float3 b, float comp, float epsilon) {
return abs(math::dot(a, b) - comp) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SI_SO<float3, float3, float, float, bool> fn{
"Equal - Direction", [](float3 a, float3 b, float angle, float epsilon) {
"Equal - Direction",
[](float3 a, float3 b, float angle, float epsilon) {
return abs(angle_v3v3(a, b) - angle) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Equal - Element-wise", [](float3 a, float3 b, float epsilon) {
"Equal - Element-wise",
[](float3 a, float3 b, float epsilon) {
return abs(a.x - b.x) <= epsilon && abs(a.y - b.y) <= epsilon &&
abs(a.z - b.z) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Equal - Length", [](float3 a, float3 b, float epsilon) {
"Equal - Length",
[](float3 a, float3 b, float epsilon) {
return abs(math::length(a) - math::length(b)) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
}
@ -422,38 +458,48 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
switch (data->mode) {
case NODE_COMPARE_MODE_AVERAGE: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Not Equal - Average", [](float3 a, float3 b, float epsilon) {
"Not Equal - Average",
[](float3 a, float3 b, float epsilon) {
return abs(component_average(a) - component_average(b)) > epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DOT_PRODUCT: {
static fn::CustomMF_SI_SI_SI_SI_SO<float3, float3, float, float, bool> fn{
"Not Equal - Dot Product", [](float3 a, float3 b, float comp, float epsilon) {
"Not Equal - Dot Product",
[](float3 a, float3 b, float comp, float epsilon) {
return abs(math::dot(a, b) - comp) >= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_DIRECTION: {
static fn::CustomMF_SI_SI_SI_SI_SO<float3, float3, float, float, bool> fn{
"Not Equal - Direction", [](float3 a, float3 b, float angle, float epsilon) {
"Not Equal - Direction",
[](float3 a, float3 b, float angle, float epsilon) {
return abs(angle_v3v3(a, b) - angle) > epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_ELEMENT: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Not Equal - Element-wise", [](float3 a, float3 b, float epsilon) {
"Not Equal - Element-wise",
[](float3 a, float3 b, float epsilon) {
return abs(a.x - b.x) > epsilon && abs(a.y - b.y) > epsilon &&
abs(a.z - b.z) > epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_MODE_LENGTH: {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, bool> fn{
"Not Equal - Length", [](float3 a, float3 b, float epsilon) {
"Not Equal - Length",
[](float3 a, float3 b, float epsilon) {
return abs(math::length(a) - math::length(b)) > epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
}
@ -464,32 +510,40 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
switch (data->operation) {
case NODE_COMPARE_EQUAL: {
static fn::CustomMF_SI_SI_SI_SO<ColorGeometry4f, ColorGeometry4f, float, bool> fn{
"Equal", [](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
"Equal",
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
return abs(a.r - b.r) <= epsilon && abs(a.g - b.g) <= epsilon &&
abs(a.b - b.b) <= epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_NOT_EQUAL: {
static fn::CustomMF_SI_SI_SI_SO<ColorGeometry4f, ColorGeometry4f, float, bool> fn{
"Not Equal", [](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
"Not Equal",
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
return abs(a.r - b.r) > epsilon && abs(a.g - b.g) > epsilon &&
abs(a.b - b.b) > epsilon;
}};
},
exec_preset_first_two};
return &fn;
}
case NODE_COMPARE_COLOR_BRIGHTER: {
static fn::CustomMF_SI_SI_SO<ColorGeometry4f, ColorGeometry4f, bool> fn{
"Brighter", [](ColorGeometry4f a, ColorGeometry4f b) {
"Brighter",
[](ColorGeometry4f a, ColorGeometry4f b) {
return rgb_to_grayscale(a) > rgb_to_grayscale(b);
}};
},
exec_preset_all};
return &fn;
}
case NODE_COMPARE_COLOR_DARKER: {
static fn::CustomMF_SI_SI_SO<ColorGeometry4f, ColorGeometry4f, bool> fn{
"Darker", [](ColorGeometry4f a, ColorGeometry4f b) {
"Darker",
[](ColorGeometry4f a, ColorGeometry4f b) {
return rgb_to_grayscale(a) < rgb_to_grayscale(b);
}};
},
exec_preset_all};
return &fn;
}
}

View File

@ -2,6 +2,7 @@
#include <cmath>
#include "BLI_noise.hh"
#include "BLI_string.h"
#include "RNA_enum_types.h"
@ -40,11 +41,15 @@ static void node_float_to_int_label(const bNodeTree *UNUSED(ntree),
static const fn::MultiFunction *get_multi_function(bNode &bnode)
{
static fn::CustomMF_SI_SO<float, int> round_fn{"Round", [](float a) { return (int)round(a); }};
static fn::CustomMF_SI_SO<float, int> floor_fn{"Floor", [](float a) { return (int)floor(a); }};
static fn::CustomMF_SI_SO<float, int> ceil_fn{"Ceiling", [](float a) { return (int)ceil(a); }};
static fn::CustomMF_SI_SO<float, int> trunc_fn{"Truncate",
[](float a) { return (int)trunc(a); }};
static auto exec_preset = fn::CustomMF_presets::AllSpanOrSingle();
static fn::CustomMF_SI_SO<float, int> round_fn{
"Round", [](float a) { return (int)round(a); }, exec_preset};
static fn::CustomMF_SI_SO<float, int> floor_fn{
"Floor", [](float a) { return (int)floor(a); }, exec_preset};
static fn::CustomMF_SI_SO<float, int> ceil_fn{
"Ceiling", [](float a) { return (int)ceil(a); }, exec_preset};
static fn::CustomMF_SI_SO<float, int> trunc_fn{
"Truncate", [](float a) { return (int)trunc(a); }, exec_preset};
switch (static_cast<FloatToIntRoundingMode>(bnode.custom1)) {
case FN_NODE_FLOAT_TO_INT_ROUND:

View File

@ -134,162 +134,6 @@ static void fn_node_random_value_gather_link_search(GatherLinkSearchOpParams &pa
}
}
class RandomVectorFunction : public fn::MultiFunction {
public:
RandomVectorFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Random Value"};
signature.single_input<float3>("Min");
signature.single_input<float3>("Max");
signature.single_input<int>("ID");
signature.single_input<int>("Seed");
signature.single_output<float3>("Value");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &min_values = params.readonly_single_input<float3>(0, "Min");
const VArray<float3> &max_values = params.readonly_single_input<float3>(1, "Max");
const VArray<int> &ids = params.readonly_single_input<int>(2, "ID");
const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed");
MutableSpan<float3> values = params.uninitialized_single_output<float3>(4, "Value");
for (int64_t i : mask) {
const float3 min_value = min_values[i];
const float3 max_value = max_values[i];
const int seed = seeds[i];
const int id = ids[i];
const float x = noise::hash_to_float(seed, id, 0);
const float y = noise::hash_to_float(seed, id, 1);
const float z = noise::hash_to_float(seed, id, 2);
values[i] = float3(x, y, z) * (max_value - min_value) + min_value;
}
}
};
class RandomFloatFunction : public fn::MultiFunction {
public:
RandomFloatFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Random Value"};
signature.single_input<float>("Min");
signature.single_input<float>("Max");
signature.single_input<int>("ID");
signature.single_input<int>("Seed");
signature.single_output<float>("Value");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &min_values = params.readonly_single_input<float>(0, "Min");
const VArray<float> &max_values = params.readonly_single_input<float>(1, "Max");
const VArray<int> &ids = params.readonly_single_input<int>(2, "ID");
const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed");
MutableSpan<float> values = params.uninitialized_single_output<float>(4, "Value");
for (int64_t i : mask) {
const float min_value = min_values[i];
const float max_value = max_values[i];
const int seed = seeds[i];
const int id = ids[i];
const float value = noise::hash_to_float(seed, id);
values[i] = value * (max_value - min_value) + min_value;
}
}
};
class RandomIntFunction : public fn::MultiFunction {
public:
RandomIntFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Random Value"};
signature.single_input<int>("Min");
signature.single_input<int>("Max");
signature.single_input<int>("ID");
signature.single_input<int>("Seed");
signature.single_output<int>("Value");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<int> &min_values = params.readonly_single_input<int>(0, "Min");
const VArray<int> &max_values = params.readonly_single_input<int>(1, "Max");
const VArray<int> &ids = params.readonly_single_input<int>(2, "ID");
const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed");
MutableSpan<int> values = params.uninitialized_single_output<int>(4, "Value");
/* Add one to the maximum and use floor to produce an even
* distribution for the first and last values (See T93591). */
for (int64_t i : mask) {
const float min_value = min_values[i];
const float max_value = max_values[i] + 1.0f;
const int seed = seeds[i];
const int id = ids[i];
const float value = noise::hash_to_float(id, seed);
values[i] = floor(value * (max_value - min_value) + min_value);
}
}
};
class RandomBoolFunction : public fn::MultiFunction {
public:
RandomBoolFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Random Value"};
signature.single_input<float>("Probability");
signature.single_input<int>("ID");
signature.single_input<int>("Seed");
signature.single_output<bool>("Value");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &probabilities = params.readonly_single_input<float>(0, "Probability");
const VArray<int> &ids = params.readonly_single_input<int>(1, "ID");
const VArray<int> &seeds = params.readonly_single_input<int>(2, "Seed");
MutableSpan<bool> values = params.uninitialized_single_output<bool>(3, "Value");
for (int64_t i : mask) {
const int seed = seeds[i];
const int id = ids[i];
const float probability = probabilities[i];
values[i] = noise::hash_to_float(id, seed) <= probability;
}
}
};
static void fn_node_random_value_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const NodeRandomValue &storage = node_storage(builder.node());
@ -297,22 +141,64 @@ static void fn_node_random_value_build_multi_function(NodeMultiFunctionBuilder &
switch (data_type) {
case CD_PROP_FLOAT3: {
static RandomVectorFunction fn;
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float3>>
fn{"Random Vector",
[](float3 min_value, float3 max_value, int id, int seed, float3 *r_value) {
const float x = noise::hash_to_float(seed, id, 0);
const float y = noise::hash_to_float(seed, id, 1);
const float z = noise::hash_to_float(seed, id, 2);
*r_value = float3(x, y, z) * (max_value - min_value) + min_value;
},
fn::CustomMF_presets::SomeSpanOrSingle<2>()};
builder.set_matching_fn(fn);
break;
}
case CD_PROP_FLOAT: {
static RandomFloatFunction fn;
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float>>
fn{"Random Float",
[](float min_value, float max_value, int id, int seed, float *r_value) {
const float value = noise::hash_to_float(seed, id);
*r_value = value * (max_value - min_value) + min_value;
},
fn::CustomMF_presets::SomeSpanOrSingle<2>()};
builder.set_matching_fn(fn);
break;
}
case CD_PROP_INT32: {
static RandomIntFunction fn;
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, int>>
fn{"Random Int",
[](int min_value, int max_value, int id, int seed, int *r_value) {
const float value = noise::hash_to_float(id, seed);
/* Add one to the maximum and use floor to produce an even
* distribution for the first and last values (See T93591). */
*r_value = floor(value * (max_value + 1 - min_value) + min_value);
},
fn::CustomMF_presets::SomeSpanOrSingle<2>()};
builder.set_matching_fn(fn);
break;
}
case CD_PROP_BOOL: {
static RandomBoolFunction fn;
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, int>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, bool>>
fn{"Random Bool",
[](float probability, int id, int seed, bool *r_value) {
*r_value = noise::hash_to_float(id, seed) <= probability;
},
fn::CustomMF_presets::SomeSpanOrSingle<1>()};
builder.set_matching_fn(fn);
break;
}

View File

@ -551,8 +551,10 @@ static Field<int> get_curve_count_field(GeoNodeExecParams params,
const GeometryNodeCurveResampleMode mode)
{
if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) {
static fn::CustomMF_SI_SO<int, int> max_one_fn("Clamp Above One",
[](int value) { return std::max(1, value); });
static fn::CustomMF_SI_SO<int, int> max_one_fn(
"Clamp Above One",
[](int value) { return std::max(1, value); },
fn::CustomMF_presets::AllSpanOrSingle());
auto clamp_op = std::make_shared<FieldOperation>(
FieldOperation(max_one_fn, {Field<int>(params.extract_input<Field<int>>("Count"))}));
@ -561,12 +563,14 @@ static Field<int> get_curve_count_field(GeoNodeExecParams params,
if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) {
static fn::CustomMF_SI_SI_SO<float, float, int> get_count_fn(
"Length Input to Count", [](const float curve_length, const float sample_length) {
"Length Input to Count",
[](const float curve_length, const float sample_length) {
/* Find the number of sampled segments by dividing the total length by
* the sample length. Then there is one more sampled point than segment. */
const int count = int(curve_length / sample_length) + 1;
return std::max(1, count);
});
},
fn::CustomMF_presets::AllSpanOrSingle());
auto get_count_op = std::make_shared<FieldOperation>(
FieldOperation(get_count_fn,
@ -588,9 +592,11 @@ static Field<int> get_curve_count_field(GeoNodeExecParams params,
static Field<bool> get_selection_field(GeoNodeExecParams params)
{
static fn::CustomMF_SI_SI_SO<bool, int, bool> get_selection_fn(
"Create Curve Selection", [](const bool orig_selection, const int evaluated_points_num) {
"Create Curve Selection",
[](const bool orig_selection, const int evaluated_points_num) {
return orig_selection && evaluated_points_num > 1;
});
},
fn::CustomMF_presets::AllSpanOrSingle());
auto selection_op = std::make_shared<FieldOperation>(
FieldOperation(get_selection_fn,

View File

@ -203,9 +203,11 @@ static Field<float> get_length_input_field(const GeoNodeExecParams &params,
/* Just make sure the length is in bounds of the curve. */
Field<float> length_field = params.get_input<Field<float>>("Length");
auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>(
__func__, [curve_total_length](float length) {
__func__,
[curve_total_length](float length) {
return std::clamp(length, 0.0f, curve_total_length);
});
},
fn::CustomMF_presets::AllSpanOrSingle());
auto clamp_op = std::make_shared<FieldOperation>(
FieldOperation(std::move(clamp_fn), {std::move(length_field)}));
@ -215,10 +217,12 @@ static Field<float> get_length_input_field(const GeoNodeExecParams &params,
/* Convert the factor to a length and clamp it to the bounds of the curve. */
Field<float> factor_field = params.get_input<Field<float>>("Factor");
auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>(
__func__, [curve_total_length](float factor) {
__func__,
[curve_total_length](float factor) {
const float length = factor * curve_total_length;
return std::clamp(length, 0.0f, curve_total_length);
});
},
fn::CustomMF_presets::AllSpanOrSingle());
auto process_op = std::make_shared<FieldOperation>(
FieldOperation(std::move(clamp_fn), {std::move(factor_field)}));

View File

@ -1274,7 +1274,9 @@ static void node_geo_exec(GeoNodeExecParams params)
/* Create a combined field from the offset and the scale so the field evaluator
* can take care of the multiplication and to simplify each extrude function. */
static fn::CustomMF_SI_SI_SO<float3, float, float3> multiply_fn{
"Scale", [](const float3 &offset, const float scale) { return offset * scale; }};
"Scale",
[](const float3 &offset, const float scale) { return offset * scale; },
fn::CustomMF_presets::AllSpanOrSingle()};
std::shared_ptr<FieldOperation> multiply_op = std::make_shared<FieldOperation>(
FieldOperation(multiply_fn, {std::move(offset_field), std::move(scale_field)}));
const Field<float3> final_offset{std::move(multiply_op)};

View File

@ -124,7 +124,9 @@ static void node_geo_exec(GeoNodeExecParams params)
/* Use another multi-function operation to make sure the input radius is greater than zero.
* TODO: Use mutable multi-function once that is supported. */
static fn::CustomMF_SI_SO<float, float> max_zero_fn(
__func__, [](float value) { return std::max(0.0f, value); });
__func__,
[](float value) { return std::max(0.0f, value); },
fn::CustomMF_presets::AllSpanOrSingle());
auto max_zero_op = std::make_shared<FieldOperation>(
FieldOperation(max_zero_fn, {std::move(radius)}));
Field<float> positive_radius(std::move(max_zero_op), 0);

View File

@ -139,35 +139,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams &params)
}
}
template<typename T> class SwitchFieldsFunction : public fn::MultiFunction {
public:
SwitchFieldsFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Switch"};
signature.single_input<bool>("Switch");
signature.single_input<T>("False");
signature.single_input<T>("True");
signature.single_output<T>("Output");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<bool> &switches = params.readonly_single_input<bool>(0, "Switch");
const VArray<T> &falses = params.readonly_single_input<T>(1, "False");
const VArray<T> &trues = params.readonly_single_input<T>(2, "True");
MutableSpan<T> values = params.uninitialized_single_output_if_required<T>(3, "Output");
for (int64_t i : mask) {
new (&values[i]) T(switches[i] ? trues[i] : falses[i]);
}
}
};
template<typename T> void switch_fields(GeoNodeExecParams &params, const StringRef suffix)
{
if (params.lazy_require_input("Switch")) {
@ -190,7 +161,11 @@ template<typename T> void switch_fields(GeoNodeExecParams &params, const StringR
Field<T> falses_field = params.extract_input<Field<T>>(name_false);
Field<T> trues_field = params.extract_input<Field<T>>(name_true);
auto switch_fn = std::make_unique<SwitchFieldsFunction<T>>();
static fn::CustomMF_SI_SI_SI_SO<bool, T, T, T> switch_fn{
"Switch", [](bool condition, const T &false_value, const T &true_value) {
return condition ? true_value : false_value;
}};
auto switch_op = std::make_shared<FieldOperation>(FieldOperation(
std::move(switch_fn),
{std::move(switches_field), std::move(falses_field), std::move(trues_field)}));

View File

@ -223,327 +223,106 @@ static float3 clamp_range(const float3 value, const float3 min, const float3 max
clamp_range(value.z, min.z, max.z));
}
static void map_range_vector_signature(fn::MFSignatureBuilder *signature, bool use_steps)
template<bool Clamp> static auto build_float_linear()
{
signature->single_input<float3>("Vector");
signature->single_input<float3>("From Min");
signature->single_input<float3>("From Max");
signature->single_input<float3>("To Min");
signature->single_input<float3>("To Max");
if (use_steps) {
signature->single_input<float3>("Steps");
}
signature->single_output<float3>("Vector");
return fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float>>{
Clamp ? "Map Range (clamped)" : "Map Range (unclamped)",
[](float value, float from_min, float from_max, float to_min, float to_max, float *r_value) {
const float factor = safe_divide(value - from_min, from_max - from_min);
float result = to_min + factor * (to_max - to_min);
if constexpr (Clamp) {
result = clamp_range(result, to_min, to_max);
}
*r_value = result;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
}
class MapRangeVectorFunction : public fn::MultiFunction {
private:
bool clamp_;
public:
MapRangeVectorFunction(bool clamp) : clamp_(clamp)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Vector Map Range"};
map_range_vector_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &values = params.readonly_single_input<float3>(0, "Vector");
const VArray<float3> &from_min = params.readonly_single_input<float3>(1, "From Min");
const VArray<float3> &from_max = params.readonly_single_input<float3>(2, "From Max");
const VArray<float3> &to_min = params.readonly_single_input<float3>(3, "To Min");
const VArray<float3> &to_max = params.readonly_single_input<float3>(4, "To Max");
MutableSpan<float3> results = params.uninitialized_single_output<float3>(5, "Vector");
for (int64_t i : mask) {
float3 factor = math::safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
results[i] = factor * (to_max[i] - to_min[i]) + to_min[i];
}
if (clamp_) {
for (int64_t i : mask) {
results[i] = clamp_range(results[i], to_min[i], to_max[i]);
}
}
}
};
class MapRangeSteppedVectorFunction : public fn::MultiFunction {
private:
bool clamp_;
public:
MapRangeSteppedVectorFunction(bool clamp) : clamp_(clamp)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Vector Map Range Stepped"};
map_range_vector_signature(&signature, true);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &values = params.readonly_single_input<float3>(0, "Vector");
const VArray<float3> &from_min = params.readonly_single_input<float3>(1, "From Min");
const VArray<float3> &from_max = params.readonly_single_input<float3>(2, "From Max");
const VArray<float3> &to_min = params.readonly_single_input<float3>(3, "To Min");
const VArray<float3> &to_max = params.readonly_single_input<float3>(4, "To Max");
const VArray<float3> &steps = params.readonly_single_input<float3>(5, "Steps");
MutableSpan<float3> results = params.uninitialized_single_output<float3>(6, "Vector");
for (int64_t i : mask) {
float3 factor = math::safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
factor = math::safe_divide(math::floor(factor * (steps[i] + 1.0f)), steps[i]);
results[i] = factor * (to_max[i] - to_min[i]) + to_min[i];
}
if (clamp_) {
for (int64_t i : mask) {
results[i] = clamp_range(results[i], to_min[i], to_max[i]);
}
}
}
};
class MapRangeSmoothstepVectorFunction : public fn::MultiFunction {
public:
MapRangeSmoothstepVectorFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Vector Map Range Smoothstep"};
map_range_vector_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &values = params.readonly_single_input<float3>(0, "Vector");
const VArray<float3> &from_min = params.readonly_single_input<float3>(1, "From Min");
const VArray<float3> &from_max = params.readonly_single_input<float3>(2, "From Max");
const VArray<float3> &to_min = params.readonly_single_input<float3>(3, "To Min");
const VArray<float3> &to_max = params.readonly_single_input<float3>(4, "To Max");
MutableSpan<float3> results = params.uninitialized_single_output<float3>(5, "Vector");
for (int64_t i : mask) {
float3 factor = math::safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
clamp_v3(factor, 0.0f, 1.0f);
factor = (float3(3.0f) - 2.0f * factor) * (factor * factor);
results[i] = factor * (to_max[i] - to_min[i]) + to_min[i];
}
}
};
class MapRangeSmootherstepVectorFunction : public fn::MultiFunction {
public:
MapRangeSmootherstepVectorFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Vector Map Range Smoothstep"};
map_range_vector_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &values = params.readonly_single_input<float3>(0, "Vector");
const VArray<float3> &from_min = params.readonly_single_input<float3>(1, "From Min");
const VArray<float3> &from_max = params.readonly_single_input<float3>(2, "From Max");
const VArray<float3> &to_min = params.readonly_single_input<float3>(3, "To Min");
const VArray<float3> &to_max = params.readonly_single_input<float3>(4, "To Max");
MutableSpan<float3> results = params.uninitialized_single_output<float3>(5, "Vector");
for (int64_t i : mask) {
float3 factor = math::safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
clamp_v3(factor, 0.0f, 1.0f);
factor = factor * factor * factor * (factor * (factor * 6.0f - 15.0f) + 10.0f);
results[i] = factor * (to_max[i] - to_min[i]) + to_min[i];
}
}
};
static void map_range_signature(fn::MFSignatureBuilder *signature, bool use_steps)
template<bool Clamp> static auto build_float_stepped()
{
signature->single_input<float>("Value");
signature->single_input<float>("From Min");
signature->single_input<float>("From Max");
signature->single_input<float>("To Min");
signature->single_input<float>("To Max");
if (use_steps) {
signature->single_input<float>("Steps");
}
signature->single_output<float>("Result");
return fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float>>{
Clamp ? "Map Range Stepped (clamped)" : "Map Range Stepped (unclamped)",
[](float value,
float from_min,
float from_max,
float to_min,
float to_max,
float steps,
float *r_value) {
float factor = safe_divide(value - from_min, from_max - from_min);
factor = safe_divide(floorf(factor * (steps + 1.0f)), steps);
float result = to_min + factor * (to_max - to_min);
if constexpr (Clamp) {
result = clamp_range(result, to_min, to_max);
}
*r_value = result;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
}
class MapRangeFunction : public fn::MultiFunction {
private:
bool clamp_;
template<bool Clamp> static auto build_vector_linear()
{
return fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float3>>{
Clamp ? "Vector Map Range (clamped)" : "Vector Map Range (unclamped)",
[](const float3 &value,
const float3 &from_min,
const float3 &from_max,
const float3 &to_min,
const float3 &to_max,
float3 *r_value) {
float3 factor = math::safe_divide(value - from_min, from_max - from_min);
float3 result = factor * (to_max - to_min) + to_min;
if constexpr (Clamp) {
result = clamp_range(result, to_min, to_max);
}
*r_value = result;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
}
public:
MapRangeFunction(bool clamp) : clamp_(clamp)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Map Range"};
map_range_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &values = params.readonly_single_input<float>(0, "Value");
const VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min");
const VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max");
const VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min");
const VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max");
MutableSpan<float> results = params.uninitialized_single_output<float>(5, "Result");
for (int64_t i : mask) {
float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
results[i] = to_min[i] + factor * (to_max[i] - to_min[i]);
}
if (clamp_) {
for (int64_t i : mask) {
results[i] = clamp_range(results[i], to_min[i], to_max[i]);
}
}
}
};
class MapRangeSteppedFunction : public fn::MultiFunction {
private:
bool clamp_;
public:
MapRangeSteppedFunction(bool clamp) : clamp_(clamp)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Map Range Stepped"};
map_range_signature(&signature, true);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &values = params.readonly_single_input<float>(0, "Value");
const VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min");
const VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max");
const VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min");
const VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max");
const VArray<float> &steps = params.readonly_single_input<float>(5, "Steps");
MutableSpan<float> results = params.uninitialized_single_output<float>(6, "Result");
for (int64_t i : mask) {
float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
factor = safe_divide(floorf(factor * (steps[i] + 1.0f)), steps[i]);
results[i] = to_min[i] + factor * (to_max[i] - to_min[i]);
}
if (clamp_) {
for (int64_t i : mask) {
results[i] = clamp_range(results[i], to_min[i], to_max[i]);
}
}
}
};
class MapRangeSmoothstepFunction : public fn::MultiFunction {
public:
MapRangeSmoothstepFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Map Range Smoothstep"};
map_range_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &values = params.readonly_single_input<float>(0, "Value");
const VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min");
const VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max");
const VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min");
const VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max");
MutableSpan<float> results = params.uninitialized_single_output<float>(5, "Result");
for (int64_t i : mask) {
float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
factor = std::clamp(factor, 0.0f, 1.0f);
factor = (3.0f - 2.0f * factor) * (factor * factor);
results[i] = to_min[i] + factor * (to_max[i] - to_min[i]);
}
}
};
class MapRangeSmootherstepFunction : public fn::MultiFunction {
public:
MapRangeSmootherstepFunction()
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Map Range Smoothstep"};
map_range_signature(&signature, false);
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float> &values = params.readonly_single_input<float>(0, "Value");
const VArray<float> &from_min = params.readonly_single_input<float>(1, "From Min");
const VArray<float> &from_max = params.readonly_single_input<float>(2, "From Max");
const VArray<float> &to_min = params.readonly_single_input<float>(3, "To Min");
const VArray<float> &to_max = params.readonly_single_input<float>(4, "To Max");
MutableSpan<float> results = params.uninitialized_single_output<float>(5, "Result");
for (int64_t i : mask) {
float factor = safe_divide(values[i] - from_min[i], from_max[i] - from_min[i]);
factor = std::clamp(factor, 0.0f, 1.0f);
factor = factor * factor * factor * (factor * (factor * 6.0f - 15.0f) + 10.0f);
results[i] = to_min[i] + factor * (to_max[i] - to_min[i]);
}
}
};
template<bool Clamp> static auto build_vector_stepped()
{
return fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float3>>{
Clamp ? "Vector Map Range Stepped (clamped)" : "Vector Map Range Stepped (unclamped)",
[](const float3 &value,
const float3 &from_min,
const float3 &from_max,
const float3 &to_min,
const float3 &to_max,
const float3 &steps,
float3 *r_value) {
float3 factor = math::safe_divide(value - from_min, from_max - from_min);
factor = math::safe_divide(math::floor(factor * (steps + 1.0f)), steps);
float3 result = factor * (to_max - to_min) + to_min;
if constexpr (Clamp) {
result = clamp_range(result, to_min, to_max);
}
*r_value = result;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
}
static void sh_node_map_range_build_multi_function(NodeMultiFunctionBuilder &builder)
{
@ -556,34 +335,70 @@ static void sh_node_map_range_build_multi_function(NodeMultiFunctionBuilder &bui
switch (interpolation_type) {
case NODE_MAP_RANGE_LINEAR: {
if (clamp) {
static MapRangeVectorFunction fn_with_clamp{true};
builder.set_matching_fn(fn_with_clamp);
static auto fn = build_vector_linear<true>();
builder.set_matching_fn(fn);
}
else {
static MapRangeVectorFunction fn_without_clamp{false};
builder.set_matching_fn(fn_without_clamp);
static auto fn = build_vector_linear<false>();
builder.set_matching_fn(fn);
}
break;
}
case NODE_MAP_RANGE_STEPPED: {
if (clamp) {
static MapRangeSteppedVectorFunction fn_stepped_with_clamp{true};
builder.set_matching_fn(fn_stepped_with_clamp);
static auto fn = build_vector_stepped<true>();
builder.set_matching_fn(fn);
}
else {
static MapRangeSteppedVectorFunction fn_stepped_without_clamp{false};
builder.set_matching_fn(fn_stepped_without_clamp);
static auto fn = build_vector_stepped<false>();
builder.set_matching_fn(fn);
}
break;
}
case NODE_MAP_RANGE_SMOOTHSTEP: {
static MapRangeSmoothstepVectorFunction smoothstep;
builder.set_matching_fn(smoothstep);
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float3>>
fn{"Vector Map Range Smoothstep",
[](const float3 &value,
const float3 &from_min,
const float3 &from_max,
const float3 &to_min,
const float3 &to_max,
float3 *r_value) {
float3 factor = math::safe_divide(value - from_min, from_max - from_min);
clamp_v3(factor, 0.0f, 1.0f);
factor = (float3(3.0f) - 2.0f * factor) * (factor * factor);
*r_value = factor * (to_max - to_min) + to_min;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
builder.set_matching_fn(fn);
break;
}
case NODE_MAP_RANGE_SMOOTHERSTEP: {
static MapRangeSmootherstepVectorFunction smootherstep;
builder.set_matching_fn(smootherstep);
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float3>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float3>>
fn{"Vector Map Range Smootherstep",
[](const float3 &value,
const float3 &from_min,
const float3 &from_max,
const float3 &to_min,
const float3 &to_max,
float3 *r_value) {
float3 factor = math::safe_divide(value - from_min, from_max - from_min);
clamp_v3(factor, 0.0f, 1.0f);
factor = factor * factor * factor * (factor * (factor * 6.0f - 15.0f) + 10.0f);
*r_value = factor * (to_max - to_min) + to_min;
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
builder.set_matching_fn(fn);
break;
}
default:
@ -594,34 +409,70 @@ static void sh_node_map_range_build_multi_function(NodeMultiFunctionBuilder &bui
switch (interpolation_type) {
case NODE_MAP_RANGE_LINEAR: {
if (clamp) {
static MapRangeFunction fn_with_clamp{true};
builder.set_matching_fn(fn_with_clamp);
static auto fn = build_float_linear<true>();
builder.set_matching_fn(fn);
}
else {
static MapRangeFunction fn_without_clamp{false};
builder.set_matching_fn(fn_without_clamp);
static auto fn = build_float_linear<false>();
builder.set_matching_fn(fn);
}
break;
}
case NODE_MAP_RANGE_STEPPED: {
if (clamp) {
static MapRangeSteppedFunction fn_stepped_with_clamp{true};
builder.set_matching_fn(fn_stepped_with_clamp);
static auto fn = build_float_stepped<true>();
builder.set_matching_fn(fn);
}
else {
static MapRangeSteppedFunction fn_stepped_without_clamp{false};
builder.set_matching_fn(fn_stepped_without_clamp);
static auto fn = build_float_stepped<false>();
builder.set_matching_fn(fn);
}
break;
}
case NODE_MAP_RANGE_SMOOTHSTEP: {
static MapRangeSmoothstepFunction smoothstep;
builder.set_matching_fn(smoothstep);
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float>>
fn{"Map Range Smoothstep",
[](float value,
float from_min,
float from_max,
float to_min,
float to_max,
float *r_value) {
float factor = safe_divide(value - from_min, from_max - from_min);
factor = std::clamp(factor, 0.0f, 1.0f);
factor = (3.0f - 2.0f * factor) * (factor * factor);
*r_value = to_min + factor * (to_max - to_min);
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
builder.set_matching_fn(fn);
break;
}
case NODE_MAP_RANGE_SMOOTHERSTEP: {
static MapRangeSmootherstepFunction smootherstep;
builder.set_matching_fn(smootherstep);
static fn::CustomMF<fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleInput, float>,
fn::MFParamTag<fn::MFParamCategory::SingleOutput, float>>
fn{"Map Range Smoothstep",
[](float value,
float from_min,
float from_max,
float to_min,
float to_max,
float *r_value) {
float factor = safe_divide(value - from_min, from_max - from_min);
factor = std::clamp(factor, 0.0f, 1.0f);
factor = factor * factor * factor * (factor * (factor * 6.0f - 15.0f) + 10.0f);
*r_value = to_min + factor * (to_max - to_min);
},
fn::CustomMF_presets::SomeSpanOrSingle<0>()};
builder.set_matching_fn(fn);
break;
}
default:

View File

@ -106,28 +106,30 @@ static const fn::MultiFunction *get_base_multi_function(bNode &node)
const int mode = node.custom1;
const fn::MultiFunction *base_fn = nullptr;
try_dispatch_float_math_fl_to_fl(mode, [&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float, float> fn{info.title_case_name.c_str(), function};
base_fn = &fn;
});
try_dispatch_float_math_fl_to_fl(
mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float, float> fn{
info.title_case_name.c_str(), function, devi_fn};
base_fn = &fn;
});
if (base_fn != nullptr) {
return base_fn;
}
try_dispatch_float_math_fl_fl_to_fl(mode,
[&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float, float, float> fn{
info.title_case_name.c_str(), function};
base_fn = &fn;
});
try_dispatch_float_math_fl_fl_to_fl(
mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float, float, float> fn{
info.title_case_name.c_str(), function, devi_fn};
base_fn = &fn;
});
if (base_fn != nullptr) {
return base_fn;
}
try_dispatch_float_math_fl_fl_fl_to_fl(
mode, [&](auto function, const FloatMathOperationInfo &info) {
mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{
info.title_case_name.c_str(), function};
info.title_case_name.c_str(), function, devi_fn};
base_fn = &fn;
});
if (base_fn != nullptr) {

View File

@ -106,7 +106,9 @@ static int gpu_shader_combxyz(GPUMaterial *mat,
static void sh_node_combxyz_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static fn::CustomMF_SI_SI_SI_SO<float, float, float, float3> fn{
"Combine Vector", [](float x, float y, float z) { return float3(x, y, z); }};
"Combine Vector",
[](float x, float y, float z) { return float3(x, y, z); },
fn::CustomMF_presets::AllSpanOrSingle()};
builder.set_matching_fn(fn);
}

View File

@ -231,20 +231,20 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
const fn::MultiFunction *multi_fn = nullptr;
try_dispatch_float_math_fl3_fl3_to_fl3(operation,
[&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{
info.title_case_name.c_str(), function};
multi_fn = &fn;
});
try_dispatch_float_math_fl3_fl3_to_fl3(
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float3, float3> fn{
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
return multi_fn;
}
try_dispatch_float_math_fl3_fl3_fl3_to_fl3(
operation, [&](auto function, const FloatMathOperationInfo &info) {
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float3, float3> fn{
info.title_case_name.c_str(), function};
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
@ -252,38 +252,39 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
}
try_dispatch_float_math_fl3_fl3_fl_to_fl3(
operation, [&](auto function, const FloatMathOperationInfo &info) {
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> fn{
info.title_case_name.c_str(), function};
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
return multi_fn;
}
try_dispatch_float_math_fl3_fl3_to_fl(operation,
[&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float3, float> fn{
info.title_case_name.c_str(), function};
multi_fn = &fn;
});
try_dispatch_float_math_fl3_fl3_to_fl(
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float3, float> fn{
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
return multi_fn;
}
try_dispatch_float_math_fl3_fl_to_fl3(operation,
[&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float, float3> fn{
info.title_case_name.c_str(), function};
multi_fn = &fn;
});
try_dispatch_float_math_fl3_fl_to_fl3(
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SI_SO<float3, float, float3> fn{
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
return multi_fn;
}
try_dispatch_float_math_fl3_to_fl3(
operation, [&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float3, float3> fn{info.title_case_name.c_str(), function};
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float3, float3> fn{
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {
@ -291,8 +292,9 @@ static const fn::MultiFunction *get_multi_function(bNode &node)
}
try_dispatch_float_math_fl3_to_fl(
operation, [&](auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float3, float> fn{info.title_case_name.c_str(), function};
operation, [&](auto exec_preset, auto function, const FloatMathOperationInfo &info) {
static fn::CustomMF_SI_SO<float3, float> fn{
info.title_case_name.c_str(), function, exec_preset};
multi_fn = &fn;
});
if (multi_fn != nullptr) {