[Bf-blender-cvs] [fd60f6713a9] master: Functions: support optional outputs in multi-function

Jacques Lucke noreply at git.blender.org
Tue Sep 14 14:53:08 CEST 2021


Commit: fd60f6713a9d9e6f7d706b53bf1311f2f1cd9031
Author: Jacques Lucke
Date:   Tue Sep 14 14:52:44 2021 +0200
Branches: master
https://developer.blender.org/rBfd60f6713a9d9e6f7d706b53bf1311f2f1cd9031

Functions: support optional outputs in multi-function

Sometimes not all outputs of a multi-function are required by the
caller. In those cases it would be a waste of compute resources
to calculate the unused values anyway. Now, the caller of a
multi-function can specify when a specific output is not used.
The called function can check if an output is unused and may
ignore it. Multi-functions can still computed unused outputs as
before if they don't want to check if a specific output is unused.

The multi-function procedure system has been updated to support
ignored outputs in call instructions. An ignored output just has no
variable assigned to it.

The field system has been updated to generate a multi-function
procedure where unused outputs are ignored.

===================================================================

M	source/blender/blenlib/BLI_resource_scope.hh
M	source/blender/functions/FN_multi_function.hh
M	source/blender/functions/FN_multi_function_params.hh
M	source/blender/functions/intern/field.cc
M	source/blender/functions/intern/multi_function_procedure.cc
M	source/blender/functions/intern/multi_function_procedure_executor.cc
M	source/blender/functions/tests/FN_field_test.cc
M	source/blender/functions/tests/FN_multi_function_test.cc
M	source/blender/functions/tests/FN_multi_function_test_common.hh

===================================================================

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
@@ -136,6 +136,17 @@ class ResourceScope : NonCopyable, NonMovable {
     return this->construct<T>(name, std::forward<T>(value));
   }
 
+  /**
+   * 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->para

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list