Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenlib/BLI_resource_scope.hh11
-rw-r--r--source/blender/functions/FN_multi_function.hh9
-rw-r--r--source/blender/functions/FN_multi_function_params.hh68
-rw-r--r--source/blender/functions/intern/field.cc46
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc16
-rw-r--r--source/blender/functions/intern/multi_function_procedure_executor.cc31
-rw-r--r--source/blender/functions/tests/FN_field_test.cc16
-rw-r--r--source/blender/functions/tests/FN_multi_function_test.cc27
-rw-r--r--source/blender/functions/tests/FN_multi_function_test_common.hh29
9 files changed, 226 insertions, 27 deletions
diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh
index 6a98c2dcc1c..c3c0af4af50 100644
--- a/source/blender/blenlib/BLI_resource_scope.hh
+++ b/source/blender/blenlib/BLI_resource_scope.hh
@@ -137,6 +137,17 @@ class ResourceScope : NonCopyable, NonMovable {
}
/**
+ * The passed in function will be called when the scope is destructed.
+ */
+ template<typename Func> void add_destruct_call(Func func, const char *name)
+ {
+ void *buffer = m_allocator.allocate(sizeof(func), alignof(func));
+ new (buffer) Func(std::move(func));
+ this->add(
+ buffer, [](void *data) { (*(Func *)data)(); }, name);
+ }
+
+ /**
* Returns a reference to a linear allocator that is owned by the ResourcesCollector. Memory
* allocated through this allocator will be freed when the collector is destructed.
*/
diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh
index f6c4addfb52..98788025558 100644
--- a/source/blender/functions/FN_multi_function.hh
+++ b/source/blender/functions/FN_multi_function.hh
@@ -121,8 +121,13 @@ class MultiFunction {
}
};
-inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size)
- : MFParamsBuilder(fn.signature(), min_array_size)
+inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, int64_t mask_size)
+ : MFParamsBuilder(fn.signature(), IndexMask(mask_size))
+{
+}
+
+inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, const IndexMask *mask)
+ : MFParamsBuilder(fn.signature(), *mask)
{
}
diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh
index 5af86c7c284..cae105e7c19 100644
--- a/source/blender/functions/FN_multi_function_params.hh
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -38,6 +38,7 @@ class MFParamsBuilder {
private:
ResourceScope scope_;
const MFSignature *signature_;
+ IndexMask mask_;
int64_t min_array_size_;
Vector<const GVArray *> virtual_arrays_;
Vector<GMutableSpan> mutable_spans_;
@@ -46,13 +47,18 @@ class MFParamsBuilder {
friend class MFParams;
- public:
- MFParamsBuilder(const MFSignature &signature, int64_t min_array_size)
- : signature_(&signature), min_array_size_(min_array_size)
+ MFParamsBuilder(const MFSignature &signature, const IndexMask mask)
+ : signature_(&signature), mask_(mask), min_array_size_(mask.min_array_size())
{
}
- MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size);
+ public:
+ MFParamsBuilder(const class MultiFunction &fn, int64_t size);
+ /**
+ * The indices referenced by the #mask has to live longer than the params builder. This is
+ * because the it might have to destruct elements for all masked indices in the end.
+ */
+ MFParamsBuilder(const class MultiFunction &fn, const IndexMask *mask);
template<typename T> void add_readonly_single_input_value(T value, StringRef expected_name = "")
{
@@ -112,6 +118,17 @@ class MFParamsBuilder {
BLI_assert(ref.size() >= min_array_size_);
mutable_spans_.append(ref);
}
+ void add_ignored_single_output(StringRef expected_name = "")
+ {
+ 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);
+ const CPPType &type = param_type.data_type().single_type();
+ /* An empty span indicates that this is ignored. */
+ const GMutableSpan dummy_span{type};
+ mutable_spans_.append(dummy_span);
+ }
void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "")
{
@@ -176,6 +193,19 @@ class MFParamsBuilder {
#endif
}
+ void assert_current_param_name(StringRef expected_name)
+ {
+ UNUSED_VARS_NDEBUG(expected_name);
+#ifdef DEBUG
+ if (expected_name.is_empty()) {
+ return;
+ }
+ const int param_index = this->current_param_index();
+ StringRef actual_name = signature_->param_names[param_index];
+ BLI_assert(actual_name == expected_name);
+#endif
+ }
+
int current_param_index() const
{
return virtual_arrays_.size() + mutable_spans_.size() + virtual_vector_arrays_.size() +
@@ -204,6 +234,19 @@ class MFParams {
return *builder_->virtual_arrays_[data_index];
}
+ /**
+ * \return True when the caller provided a buffer for this output parameter. This allows the
+ * called multi-function to skip some computation. It is still valid to call
+ * #uninitialized_single_output when this returns false. In this case a new temporary buffer is
+ * allocated.
+ */
+ bool single_output_is_required(int param_index, StringRef name = "")
+ {
+ this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
+ int data_index = builder_->signature_->data_index(param_index);
+ return !builder_->mutable_spans_[data_index].is_empty();
+ }
+
template<typename T>
MutableSpan<T> uninitialized_single_output(int param_index, StringRef name = "")
{
@@ -213,7 +256,22 @@ class MFParams {
{
this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
int data_index = builder_->signature_->data_index(param_index);
- return builder_->mutable_spans_[data_index];
+ GMutableSpan span = builder_->mutable_spans_[data_index];
+ if (span.is_empty()) {
+ /* 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. */
+ const CPPType &type = span.type();
+ void *buffer = builder_->scope_.linear_allocator().allocate(
+ builder_->min_array_size_ * type.size(), type.alignment());
+ if (!type.is_trivially_destructible()) {
+ /* Make sure the temporary elements will be destructed in the end. */
+ builder_->scope_.add_destruct_call(
+ [&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); },
+ __func__);
+ }
+ span = GMutableSpan{type, buffer, builder_->min_array_size_};
+ }
+ return span;
}
template<typename T>
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
index 6133658d8e3..574a9e6284f 100644
--- a/source/blender/functions/intern/field.cc
+++ b/source/blender/functions/intern/field.cc
@@ -189,17 +189,43 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
field_with_index.current_input_index++;
}
else {
- /* All inputs variables are ready, now add the function call. */
- Vector<MFVariable *> input_variables;
- for (const GField &field : operation_inputs) {
- input_variables.append(variable_by_field.lookup(field));
- }
+ /* All inputs variables are ready, now gather all variables that are used by the function
+ * and call it. */
const MultiFunction &multi_function = operation.multi_function();
- Vector<MFVariable *> output_variables = builder.add_call(multi_function, input_variables);
- /* Add newly created variables to the map. */
- for (const int i : output_variables.index_range()) {
- variable_by_field.add_new({operation, i}, output_variables[i]);
+ Vector<MFVariable *> variables(multi_function.param_amount());
+
+ int param_input_index = 0;
+ int param_output_index = 0;
+ for (const int param_index : multi_function.param_indices()) {
+ const MFParamType param_type = multi_function.param_type(param_index);
+ const MFParamType::InterfaceType interface_type = param_type.interface_type();
+ if (interface_type == MFParamType::Input) {
+ const GField &input_field = operation_inputs[param_input_index];
+ variables[param_index] = variable_by_field.lookup(input_field);
+ param_input_index++;
+ }
+ else if (interface_type == MFParamType::Output) {
+ const GFieldRef output_field{operation, param_output_index};
+ const bool output_is_ignored =
+ field_tree_info.field_users.lookup(output_field).is_empty() &&
+ !output_fields.contains(output_field);
+ if (output_is_ignored) {
+ /* Ignored outputs don't need a variable. */
+ variables[param_index] = nullptr;
+ }
+ else {
+ /* Create a new variable for used outputs. */
+ MFVariable &new_variable = procedure.new_variable(param_type.data_type());
+ variables[param_index] = &new_variable;
+ variable_by_field.add_new(output_field, &new_variable);
+ }
+ param_output_index++;
+ }
+ else {
+ BLI_assert_unreachable();
+ }
}
+ builder.add_call_with_all_variables(multi_function, variables);
}
}
}
@@ -334,7 +360,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
build_multi_function_procedure_for_fields(
procedure, scope, field_tree_info, varying_fields_to_evaluate);
MFProcedureExecutor procedure_executor{"Procedure", procedure};
- MFParamsBuilder mf_params{procedure_executor, array_size};
+ MFParamsBuilder mf_params{procedure_executor, &mask};
MFContextBuilder mf_context;
/* Provide inputs to the procedure executor. */
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
index 2aa760a494f..fa95e8de71e 100644
--- a/source/blender/functions/intern/multi_function_procedure.cc
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -325,7 +325,14 @@ bool MFProcedure::validate_all_instruction_pointers_set() const
bool MFProcedure::validate_all_params_provided() const
{
for (const MFCallInstruction *instruction : call_instructions_) {
- for (const MFVariable *variable : instruction->params_) {
+ 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) {
+ /* Single outputs are optional. */
+ continue;
+ }
+ const MFVariable *variable = instruction->params_[param_index];
if (variable == nullptr) {
return false;
}
@@ -351,6 +358,9 @@ bool MFProcedure::validate_same_variables_in_one_call() const
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
const MFVariable *variable = instruction->params_[param_index];
+ if (variable == nullptr) {
+ continue;
+ }
for (const int other_param_index : fn.param_indices()) {
if (other_param_index == param_index) {
continue;
@@ -681,7 +691,9 @@ class MFProcedureDotExport {
if (instruction.prev().size() != 1) {
return true;
}
- if (instruction.prev()[0].type() == MFInstructionCursor::Type::Branch) {
+ if (ELEM(instruction.prev()[0].type(),
+ MFInstructionCursor::Type::Branch,
+ MFInstructionCursor::Type::Entry)) {
return true;
}
return false;
diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc
index 38b26415779..b97282accdd 100644
--- a/source/blender/functions/intern/multi_function_procedure_executor.cc
+++ b/source/blender/functions/intern/multi_function_procedure_executor.cc
@@ -978,7 +978,7 @@ static bool evaluate_as_one(const MultiFunction &fn,
return false;
}
for (VariableState *state : param_variable_states) {
- if (!state->is_one()) {
+ if (state != nullptr && !state->is_one()) {
return false;
}
}
@@ -997,8 +997,13 @@ static void execute_call_instruction(const MFCallInstruction &instruction,
for (const int param_index : fn.param_indices()) {
const MFVariable *variable = instruction.params()[param_index];
- VariableState &variable_state = variable_states.get_variable_state(*variable);
- param_variable_states[param_index] = &variable_state;
+ if (variable == nullptr) {
+ param_variable_states[param_index] = nullptr;
+ }
+ else {
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ param_variable_states[param_index] = &variable_state;
+ }
}
/* If all inputs to the function are constant, it's enough to call the function only once instead
@@ -1008,19 +1013,29 @@ static void execute_call_instruction(const MFCallInstruction &instruction,
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
- VariableState &variable_state = *param_variable_states[param_index];
- variable_states.add_as_param__one(variable_state, params, param_type, mask);
+ VariableState *variable_state = param_variable_states[param_index];
+ if (variable_state == nullptr) {
+ params.add_ignored_single_output();
+ }
+ else {
+ variable_states.add_as_param__one(*variable_state, params, param_type, mask);
+ }
}
fn.call(IndexRange(1), params, context);
}
else {
- MFParamsBuilder params(fn, mask.min_array_size());
+ MFParamsBuilder params(fn, &mask);
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
- VariableState &variable_state = *param_variable_states[param_index];
- variable_states.add_as_param(variable_state, params, param_type, mask);
+ VariableState *variable_state = param_variable_states[param_index];
+ if (variable_state == nullptr) {
+ params.add_ignored_single_output();
+ }
+ else {
+ variable_states.add_as_param(*variable_state, params, param_type, mask);
+ }
}
fn.call(mask, params, context);
diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc
index 212b79e75d3..1c2d5c8eaad 100644
--- a/source/blender/functions/tests/FN_field_test.cc
+++ b/source/blender/functions/tests/FN_field_test.cc
@@ -5,6 +5,7 @@
#include "FN_cpp_type.hh"
#include "FN_field.hh"
#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_test_common.hh"
namespace blender::fn::tests {
@@ -275,4 +276,19 @@ TEST(field, SameFieldTwice)
EXPECT_EQ(varray2->get(1), 10);
}
+TEST(field, IgnoredOutput)
+{
+ static OptionalOutputsFunction fn;
+ Field<int> field{std::make_shared<FieldOperation>(fn), 0};
+
+ FieldContext field_context;
+ FieldEvaluator field_evaluator{field_context, 10};
+ const VArray<int> *results = nullptr;
+ field_evaluator.add(field, &results);
+ field_evaluator.evaluate();
+
+ EXPECT_EQ(results->get(0), 5);
+ EXPECT_EQ(results->get(3), 5);
+}
+
} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc
index 204dbfab6aa..d99993b35ac 100644
--- a/source/blender/functions/tests/FN_multi_function_test.cc
+++ b/source/blender/functions/tests/FN_multi_function_test.cc
@@ -328,5 +328,32 @@ TEST(multi_function, CustomMF_Convert)
EXPECT_EQ(outputs[2], 9);
}
+TEST(multi_function, IgnoredOutputs)
+{
+ OptionalOutputsFunction fn;
+ {
+ MFParamsBuilder params(fn, 10);
+ params.add_ignored_single_output("Out 1");
+ params.add_ignored_single_output("Out 2");
+ MFContextBuilder context;
+ fn.call(IndexRange(10), params, context);
+ }
+ {
+ Array<int> results_1(10);
+ Array<std::string> results_2(10, NoInitialization());
+
+ MFParamsBuilder params(fn, 10);
+ params.add_uninitialized_single_output(results_1.as_mutable_span(), "Out 1");
+ params.add_uninitialized_single_output(results_2.as_mutable_span(), "Out 2");
+ MFContextBuilder context;
+ fn.call(IndexRange(10), params, context);
+
+ EXPECT_EQ(results_1[0], 5);
+ EXPECT_EQ(results_1[3], 5);
+ EXPECT_EQ(results_1[9], 5);
+ EXPECT_EQ(results_2[0], "hello, this is a long string");
+ }
+}
+
} // namespace
} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_test_common.hh b/source/blender/functions/tests/FN_multi_function_test_common.hh
index 51c8fac8a96..2a64cc302fe 100644
--- a/source/blender/functions/tests/FN_multi_function_test_common.hh
+++ b/source/blender/functions/tests/FN_multi_function_test_common.hh
@@ -171,4 +171,33 @@ class SumVectorFunction : public MultiFunction {
}
};
+class OptionalOutputsFunction : public MultiFunction {
+ public:
+ OptionalOutputsFunction()
+ {
+ static MFSignature signature = create_signature();
+ this->set_signature(&signature);
+ }
+
+ static MFSignature create_signature()
+ {
+ MFSignatureBuilder signature{"Optional Outputs"};
+ signature.single_output<int>("Out 1");
+ signature.single_output<std::string>("Out 2");
+ return signature.build();
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ if (params.single_output_is_required(0, "Out 1")) {
+ MutableSpan<int> values = params.uninitialized_single_output<int>(0, "Out 1");
+ values.fill_indices(mask, 5);
+ }
+ MutableSpan<std::string> values = params.uninitialized_single_output<std::string>(1, "Out 2");
+ for (const int i : mask) {
+ new (&values[i]) std::string("hello, this is a long string");
+ }
+ }
+};
+
} // namespace blender::fn::tests