Functions: improve handling of unused multi-function outputs

Previously, `ParamsBuilder` lazily allocated an array for an
output when it was unused, but the called multi-function
wanted to access it. Now, whether the multi-function supports
an output to be unused is part of the signature. This way, the
allocation can happen earlier when the parameters are build.
The benefit is that this makes all methods of `MFParams`
thread-safe again, removing the need for a mutex.
This commit is contained in:
Jacques Lucke 2023-01-14 15:35:44 +01:00
parent aea26830dc
commit 8625495b1c
21 changed files with 103 additions and 90 deletions

View File

@ -32,9 +32,6 @@ class ParamsBuilder {
Vector<std::variant<GVArray, GMutableSpan, const GVVectorArray *, GVectorArray *>>
actual_params_;
std::mutex mutex_;
Vector<std::pair<int, GMutableSpan>> dummy_output_spans_;
friend class MFParams;
ParamsBuilder(const Signature &signature, const IndexMask mask)
@ -127,9 +124,15 @@ class ParamsBuilder {
const ParamType &param_type = signature_->params[param_index].type;
BLI_assert(param_type.category() == ParamCategory::SingleOutput);
const CPPType &type = param_type.data_type().single_type();
/* An empty span indicates that this is ignored. */
const GMutableSpan dummy_span{type};
actual_params_.append_unchecked_as(std::in_place_type<GMutableSpan>, dummy_span);
if (bool(signature_->params[param_index].flag & ParamFlag::SupportsUnusedOutput)) {
/* An empty span indicates that this is ignored. */
const GMutableSpan dummy_span{type};
actual_params_.append_unchecked_as(std::in_place_type<GMutableSpan>, dummy_span);
}
else {
this->add_unused_output_for_unsupporting_function(type);
}
}
void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "")
@ -210,6 +213,8 @@ class ParamsBuilder {
{
return actual_params_.size();
}
void add_unused_output_for_unsupporting_function(const CPPType &type);
};
class MFParams {
@ -252,13 +257,11 @@ class MFParams {
GMutableSpan uninitialized_single_output(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, ParamCategory::SingleOutput);
BLI_assert(
!bool(builder_->signature_->params[param_index].flag & ParamFlag::SupportsUnusedOutput));
GMutableSpan span = std::get<GMutableSpan>(builder_->actual_params_[param_index]);
if (!span.is_empty()) {
return span;
}
/* The output is ignored by the caller, but the multi-function does not handle this case. So
* create a temporary buffer that the multi-function can write to. */
return this->ensure_dummy_single_output(param_index);
BLI_assert(span.size() >= builder_->min_array_size_);
return span;
}
/**
@ -273,6 +276,8 @@ class MFParams {
GMutableSpan uninitialized_single_output_if_required(int param_index, StringRef name = "")
{
this->assert_correct_param(param_index, name, ParamCategory::SingleOutput);
BLI_assert(
bool(builder_->signature_->params[param_index].flag & ParamFlag::SupportsUnusedOutput));
return std::get<GMutableSpan>(builder_->actual_params_[param_index]);
}
@ -342,8 +347,6 @@ class MFParams {
}
#endif
}
GMutableSpan ensure_dummy_single_output(int param_index);
};
} // namespace blender::fn::multi_function

View File

@ -15,10 +15,22 @@
namespace blender::fn::multi_function {
enum class ParamFlag {
None = 0,
/**
* If set, the multi-function parameter can be accessed using
* #MFParams::uninitialized_single_output_if_required which can result in better performance
* because the output does not have to be computed when it is not needed.
*/
SupportsUnusedOutput = 1 << 0,
};
ENUM_OPERATORS(ParamFlag, ParamFlag::SupportsUnusedOutput);
struct Signature {
struct ParamInfo {
ParamType type;
const char *name;
ParamFlag flag = ParamFlag::None;
};
/**
@ -68,25 +80,27 @@ class SignatureBuilder {
/* Output Parameter Types */
template<typename T> void single_output(const char *name)
template<typename T> void single_output(const char *name, const ParamFlag flag = ParamFlag::None)
{
this->single_output(name, CPPType::get<T>());
this->single_output(name, CPPType::get<T>(), flag);
}
void single_output(const char *name, const CPPType &type)
void single_output(const char *name, const CPPType &type, const ParamFlag flag = ParamFlag::None)
{
this->output(name, DataType::ForSingle(type));
this->output(name, DataType::ForSingle(type), flag);
}
template<typename T> void vector_output(const char *name)
template<typename T> void vector_output(const char *name, const ParamFlag flag = ParamFlag::None)
{
this->vector_output(name, CPPType::get<T>());
this->vector_output(name, CPPType::get<T>(), flag);
}
void vector_output(const char *name, const CPPType &base_type)
void vector_output(const char *name,
const CPPType &base_type,
const ParamFlag flag = ParamFlag::None)
{
this->output(name, DataType::ForVector(base_type));
this->output(name, DataType::ForVector(base_type), flag);
}
void output(const char *name, DataType data_type)
void output(const char *name, DataType data_type, const ParamFlag flag = ParamFlag::None)
{
signature_.params.append({ParamType(ParamType::Output, data_type), name});
signature_.params.append({ParamType(ParamType::Output, data_type), name, flag});
}
/* Mutable Parameter Types */

View File

@ -108,11 +108,18 @@ void MultiFunction::call_auto(IndexMask mask, MFParams params, Context context)
break;
}
case ParamCategory::SingleOutput: {
const GMutableSpan span = params.uninitialized_single_output_if_required(param_index);
if (span.is_empty()) {
offset_params.add_ignored_single_output();
if (bool(signature_ref_->params[param_index].flag & ParamFlag::SupportsUnusedOutput)) {
const GMutableSpan span = params.uninitialized_single_output_if_required(param_index);
if (span.is_empty()) {
offset_params.add_ignored_single_output();
}
else {
const GMutableSpan sliced_span = span.slice(input_slice_range);
offset_params.add_uninitialized_single_output(sliced_span);
}
}
else {
const GMutableSpan span = params.uninitialized_single_output(param_index);
const GMutableSpan sliced_span = span.slice(input_slice_range);
offset_params.add_uninitialized_single_output(sliced_span);
}

View File

@ -4,27 +4,16 @@
namespace blender::fn::multi_function {
GMutableSpan MFParams::ensure_dummy_single_output(int param_index)
void ParamsBuilder::add_unused_output_for_unsupporting_function(const CPPType &type)
{
/* Lock because we are actually modifying #builder_ and it may be used by multiple threads. */
std::lock_guard lock{builder_->mutex_};
for (const std::pair<int, GMutableSpan> &items : builder_->dummy_output_spans_) {
if (items.first == param_index) {
return items.second;
}
}
const CPPType &type = std::get_if<GMutableSpan>(&builder_->actual_params_[param_index])->type();
void *buffer = builder_->scope_.linear_allocator().allocate(
builder_->min_array_size_ * type.size(), type.alignment());
void *buffer = scope_.linear_allocator().allocate(type.size() * min_array_size_,
type.alignment());
const GMutableSpan span{type, buffer, min_array_size_};
actual_params_.append_unchecked_as(std::in_place_type<GMutableSpan>, span);
if (!type.is_trivially_destructible()) {
builder_->scope_.add_destruct_call(
[&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); });
scope_.add_destruct_call(
[&type, buffer, mask = mask_]() { type.destruct_indices(buffer, mask); });
}
const GMutableSpan span{type, buffer, builder_->min_array_size_};
builder_->dummy_output_spans_.append({param_index, span});
return span;
}
} // namespace blender::fn::multi_function

View File

@ -45,10 +45,10 @@ class SeparateRGBAFunction : public mf::MultiFunction {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate Color", signature};
builder.single_input<ColorGeometry4f>("Color");
builder.single_output<float>("Red");
builder.single_output<float>("Green");
builder.single_output<float>("Blue");
builder.single_output<float>("Alpha");
builder.single_output<float>("Red", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Green", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Blue", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
@ -107,7 +107,7 @@ class SeparateHSVAFunction : public mf::MultiFunction {
builder.single_output<float>("Hue");
builder.single_output<float>("Saturation");
builder.single_output<float>("Value");
builder.single_output<float>("Alpha");
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);
@ -145,7 +145,7 @@ class SeparateHSLAFunction : public mf::MultiFunction {
builder.single_output<float>("Hue");
builder.single_output<float>("Saturation");
builder.single_output<float>("Lightness");
builder.single_output<float>("Alpha");
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -261,10 +261,10 @@ class SampleCurveFunction : public mf::MultiFunction {
mf::SignatureBuilder builder{"Sample Curve", signature_};
builder.single_input<int>("Curve Index");
builder.single_input<float>("Length");
builder.single_output<float3>("Position");
builder.single_output<float3>("Tangent");
builder.single_output<float3>("Normal");
builder.single_output("Value", src_field_.cpp_type());
builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Tangent", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Normal", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output("Value", src_field_.cpp_type(), mf::ParamFlag::SupportsUnusedOutput);
this->set_signature(&signature_);
this->evaluate_source();

View File

@ -69,7 +69,7 @@ class ImageFieldsFunction : public mf::MultiFunction {
mf::SignatureBuilder builder{"ImageFunction", signature};
builder.single_input<float3>("Vector");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Alpha");
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -142,7 +142,7 @@ class ProximityFunction : public mf::MultiFunction {
mf::Signature signature;
mf::SignatureBuilder builder{"Geometry Proximity", signature};
builder.single_input<float3>("Source Position");
builder.single_output<float3>("Position");
builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Distance");
return signature;
}();

View File

@ -230,12 +230,13 @@ class RaycastFunction : public mf::MultiFunction {
builder.single_input<float3>("Source Position");
builder.single_input<float3>("Ray Direction");
builder.single_input<float>("Ray Length");
builder.single_output<bool>("Is Hit");
builder.single_output<bool>("Is Hit", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Hit Position");
builder.single_output<float3>("Hit Normal");
builder.single_output<float>("Distance");
builder.single_output<float3>("Hit Normal", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
if (target_data_) {
builder.single_output("Attribute", target_data_->type());
builder.single_output(
"Attribute", target_data_->type(), mf::ParamFlag::SupportsUnusedOutput);
}
this->set_signature(&signature_);
}
@ -245,9 +246,8 @@ class RaycastFunction : public mf::MultiFunction {
/* Hit positions are always necessary for retrieving the attribute from the target if that
* output is required, so always retrieve a span from the evaluator in that case (it's
* expected that the evaluator is more likely to have a spare buffer that could be used). */
MutableSpan<float3> hit_positions =
(target_data_) ? params.uninitialized_single_output<float3>(4, "Hit Position") :
params.uninitialized_single_output_if_required<float3>(4, "Hit Position");
MutableSpan<float3> hit_positions = params.uninitialized_single_output<float3>(4,
"Hit Position");
Array<int> hit_indices;
if (target_data_) {

View File

@ -143,7 +143,7 @@ class SampleNearestSurfaceFunction : public mf::MultiFunction {
mf::SignatureBuilder builder{"Sample Nearest Surface", signature_};
builder.single_input<float3>("Position");
builder.single_output("Value", src_field_.cpp_type());
builder.single_output("Value", src_field_.cpp_type(), mf::ParamFlag::SupportsUnusedOutput);
this->set_signature(&signature_);
}

View File

@ -200,9 +200,9 @@ class ReverseUVSampleFunction : public mf::MultiFunction {
mf::Signature signature;
mf::SignatureBuilder builder{"Sample UV Surface", signature};
builder.single_input<float2>("Sample UV");
builder.single_output<bool>("Is Valid");
builder.single_output<int>("Triangle Index");
builder.single_output<float3>("Barycentric Weights");
builder.single_output<bool>("Is Valid", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<int>("Triangle Index", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float3>("Barycentric Weights", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -35,9 +35,9 @@ class MF_SeparateXYZ : public mf::MultiFunction {
mf::Signature signature;
mf::SignatureBuilder builder{"Separate XYZ", signature};
builder.single_input<float3>("XYZ");
builder.single_output<float>("X");
builder.single_output<float>("Y");
builder.single_output<float>("Z");
builder.single_output<float>("X", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Y", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Z", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -135,8 +135,8 @@ class BrickFunction : public mf::MultiFunction {
builder.single_input<float>("Bias");
builder.single_input<float>("Brick Width");
builder.single_input<float>("Row Height");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Fac");
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Fac", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -55,7 +55,7 @@ class NodeTexChecker : public mf::MultiFunction {
builder.single_input<ColorGeometry4f>("Color1");
builder.single_input<ColorGeometry4f>("Color2");
builder.single_input<float>("Scale");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Fac");
return signature;
}();

View File

@ -58,7 +58,7 @@ class GradientFunction : public mf::MultiFunction {
mf::Signature signature;
mf::SignatureBuilder builder{"GradientFunction", signature};
builder.single_input<float3>("Vector");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Fac");
return signature;
}();

View File

@ -62,7 +62,7 @@ class MagicFunction : public mf::MultiFunction {
builder.single_input<float>("Scale");
builder.single_input<float>("Distortion");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Fac");
builder.single_output<float>("Fac", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}();
this->set_signature(&signature);

View File

@ -190,7 +190,7 @@ class MusgraveFunction : public mf::MultiFunction {
builder.single_input<float>("Gain");
}
builder.single_output<float>("Fac");
builder.single_output<float>("Fac", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}

View File

@ -115,8 +115,8 @@ class NoiseFunction : public mf::MultiFunction {
builder.single_input<float>("Roughness");
builder.single_input<float>("Distortion");
builder.single_output<float>("Fac");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Fac", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}

View File

@ -224,14 +224,14 @@ class VoronoiMinowskiFunction : public mf::MultiFunction {
}
builder.single_input<float>("Exponent");
builder.single_input<float>("Randomness");
builder.single_output<float>("Distance");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
if (dimensions != 1) {
builder.single_output<float3>("Position");
builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
}
if (ELEM(dimensions, 1, 4)) {
builder.single_output<float>("W");
builder.single_output<float>("W", mf::ParamFlag::SupportsUnusedOutput);
}
return signature;
@ -661,14 +661,14 @@ class VoronoiMetricFunction : public mf::MultiFunction {
builder.single_input<float>("Smoothness");
}
builder.single_input<float>("Randomness");
builder.single_output<float>("Distance");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Distance", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
if (dimensions != 1) {
builder.single_output<float3>("Position");
builder.single_output<float3>("Position", mf::ParamFlag::SupportsUnusedOutput);
}
if (ELEM(dimensions, 1, 4)) {
builder.single_output<float>("W");
builder.single_output<float>("W", mf::ParamFlag::SupportsUnusedOutput);
}
return signature;

View File

@ -104,7 +104,7 @@ class WaveFunction : public mf::MultiFunction {
builder.single_input<float>("Detail Scale");
builder.single_input<float>("Detail Roughness");
builder.single_input<float>("Phase Offset");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<float>("Fac");
return signature;
}();

View File

@ -92,8 +92,8 @@ class WhiteNoiseFunction : public mf::MultiFunction {
builder.single_input<float>("W");
}
builder.single_output<float>("Value");
builder.single_output<ColorGeometry4f>("Color");
builder.single_output<float>("Value", mf::ParamFlag::SupportsUnusedOutput);
builder.single_output<ColorGeometry4f>("Color", mf::ParamFlag::SupportsUnusedOutput);
return signature;
}