A lot of files were missing copyright field in the header and
the Blender Foundation contributed to them in a sense of bug
fixing and general maintenance.
This change makes it explicit that those files are at least
partially copyrighted by the Blender Foundation.
Note that this does not make it so the Blender Foundation is
the only holder of the copyright in those files, and developers
who do not have a signed contract with the foundation still
hold the copyright as well.
Another aspect of this change is using SPDX format for the
header. We already used it for the license specification,
and now we state it for the copyright as well, following the
FAQ:
https://reuse.software/faq/
167 lines
6.5 KiB
C++
167 lines
6.5 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
|
*
|
|
* 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
|
|
* negligible 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 function parameters 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.
|
|
*/
|
|
|
|
namespace blender {
|
|
|
|
/**
|
|
* Calls the given function with devirtualized parameters if possible. Note that using many
|
|
* non-trivial devirtualizers results in exponential code growth.
|
|
*
|
|
* \return True if the function has been called.
|
|
*
|
|
* Every devirtualizer is expected to have a `devirtualize(auto fn) -> bool` method.
|
|
* This method is expected to do one of two things:
|
|
* - Call `fn` with the devirtualized argument and return what `fn` returns.
|
|
* - Don't call `fn` (because the devirtualization failed) and return false.
|
|
*
|
|
* Examples for devirtualizers: #BasicDevirtualizer, #VArrayDevirtualizer.
|
|
*/
|
|
template<typename Fn, typename... Devirtualizers>
|
|
inline bool call_with_devirtualized_parameters(const std::tuple<Devirtualizers...> &devis,
|
|
const Fn &fn)
|
|
{
|
|
/* In theory the code below could be generalized to avoid code duplication. However, the maximum
|
|
* number of parameters is expected to be relatively low. Explicitly implementing the different
|
|
* cases makes it more obvious to see what is going on and also makes inlining everything easier
|
|
* for the compiler. */
|
|
constexpr size_t DeviNum = sizeof...(Devirtualizers);
|
|
if constexpr (DeviNum == 0) {
|
|
fn();
|
|
return true;
|
|
}
|
|
if constexpr (DeviNum == 1) {
|
|
return std::get<0>(devis).devirtualize([&](auto param0) {
|
|
fn(param0);
|
|
return true;
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 2) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
fn(param0, param1);
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 3) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
return std::get<2>(devis).devirtualize([&](auto &¶m2) {
|
|
fn(param0, param1, param2);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 4) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
return std::get<2>(devis).devirtualize([&](auto &¶m2) {
|
|
return std::get<3>(devis).devirtualize([&](auto &¶m3) {
|
|
fn(param0, param1, param2, param3);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 5) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
return std::get<2>(devis).devirtualize([&](auto &¶m2) {
|
|
return std::get<3>(devis).devirtualize([&](auto &¶m3) {
|
|
return std::get<4>(devis).devirtualize([&](auto &¶m4) {
|
|
fn(param0, param1, param2, param3, param4);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 6) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
return std::get<2>(devis).devirtualize([&](auto &¶m2) {
|
|
return std::get<3>(devis).devirtualize([&](auto &¶m3) {
|
|
return std::get<4>(devis).devirtualize([&](auto &¶m4) {
|
|
return std::get<5>(devis).devirtualize([&](auto &¶m5) {
|
|
fn(param0, param1, param2, param3, param4, param5);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
if constexpr (DeviNum == 7) {
|
|
return std::get<0>(devis).devirtualize([&](auto &¶m0) {
|
|
return std::get<1>(devis).devirtualize([&](auto &¶m1) {
|
|
return std::get<2>(devis).devirtualize([&](auto &¶m2) {
|
|
return std::get<3>(devis).devirtualize([&](auto &¶m3) {
|
|
return std::get<4>(devis).devirtualize([&](auto &¶m4) {
|
|
return std::get<5>(devis).devirtualize([&](auto &¶m5) {
|
|
return std::get<6>(devis).devirtualize([&](auto &¶m6) {
|
|
fn(param0, param1, param2, param3, param4, param5, param6);
|
|
return true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A devirtualizer to be used with #call_with_devirtualized_parameters.
|
|
*
|
|
* This one is very simple, it does not perform any actual devirtualization. It can be used to pass
|
|
* parameters to the function that shouldn't be devirtualized.
|
|
*/
|
|
template<typename T> struct BasicDevirtualizer {
|
|
const T value;
|
|
|
|
template<typename Fn> bool devirtualize(const Fn &fn) const
|
|
{
|
|
return fn(this->value);
|
|
}
|
|
};
|
|
|
|
} // namespace blender
|