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/functions/CMakeLists.txt3
-rw-r--r--source/blender/functions/FN_field.hh136
-rw-r--r--source/blender/functions/intern/field.cc115
-rw-r--r--source/blender/functions/tests/FN_field_test.cc73
4 files changed, 327 insertions, 0 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index a44086cfec1..5941034de78 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -28,6 +28,7 @@ set(INC_SYS
set(SRC
intern/cpp_types.cc
+ intern/field.cc
intern/generic_vector_array.cc
intern/generic_virtual_array.cc
intern/generic_virtual_vector_array.cc
@@ -39,6 +40,7 @@ set(SRC
FN_cpp_type.hh
FN_cpp_type_make.hh
+ FN_field.hh
FN_generic_pointer.hh
FN_generic_span.hh
FN_generic_value_map.hh
@@ -66,6 +68,7 @@ blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/FN_cpp_type_test.cc
+ tests/FN_field_test.cc
tests/FN_generic_span_test.cc
tests/FN_generic_vector_array_test.cc
tests/FN_multi_function_procedure_test.cc
diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh
new file mode 100644
index 00000000000..e43a6769ee5
--- /dev/null
+++ b/source/blender/functions/FN_field.hh
@@ -0,0 +1,136 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ *
+ * Field serve as an intermediate representation for a calculation of a group of functions. Having
+ * an intermediate representation is helpful mainly to separate the execution system from the
+ * system that describes the necessary computations. Fields can be executed in different contexts,
+ * and optimization might mean executing the fields differently based on some factors like the
+ * number of elements.
+ *
+ * For now, fields are very tied to the multi-function system, but in the future the #FieldFunction
+ * class could be extended to use different descriptions of its outputs and computation besides
+ * the embedded multi-function.
+ */
+
+#include "BLI_vector.hh"
+
+#include "FN_multi_function_procedure.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+
+namespace blender::fn {
+
+class Field;
+
+/**
+ * An operation acting on data described by fields. Generally corresponds
+ * to a node or a subset of a node in a node graph.
+ */
+class FieldFunction {
+ /**
+ * The function used to calculate the
+ */
+ std::unique_ptr<MultiFunction> function_;
+
+ /**
+ * References to descriptions of the results from the functions this function depends on.
+ */
+ blender::Vector<Field *> inputs_;
+
+ public:
+ FieldFunction(std::unique_ptr<MultiFunction> function, Span<Field *> inputs)
+ : function_(std::move(function)), inputs_(inputs)
+ {
+ }
+
+ Span<Field *> inputs() const
+ {
+ return inputs_;
+ }
+
+ const MultiFunction &multi_function() const
+ {
+ return *function_;
+ }
+};
+
+/**
+ * Descibes the output of a function. Generally corresponds to the combination of an output socket
+ * and link combination in a node graph.
+ */
+class Field {
+ /**
+ * The type of this field's result.
+ */
+ const fn::CPPType *type_;
+
+ /**
+ * The function that calculates this field's values. Many fields can share the same function,
+ * since a function can have many outputs, just like a node graph, where a single output can be
+ * used as multiple inputs. This avoids calling the same function many times, only using one of
+ * its results.
+ */
+ const FieldFunction *function_;
+ /**
+ * Which output of the function this field corresponds to.
+ */
+ int output_index_;
+
+ std::string debug_name_ = "";
+
+ public:
+ Field(const fn::CPPType &type, const FieldFunction &function, const int output_index)
+ : type_(&type), function_(&function), output_index_(output_index)
+ {
+ }
+
+ const fn::CPPType &type() const
+ {
+ BLI_assert(type_ != nullptr);
+ return *type_;
+ }
+
+ const FieldFunction &function() const
+ {
+ BLI_assert(function_ != nullptr);
+ return *function_;
+ }
+
+ int function_output_index() const
+ {
+ return output_index_;
+ }
+
+ blender::StringRef debug_name() const
+ {
+ return debug_name_;
+ }
+};
+
+/**
+ * Evaluate more than one field at a time, as an optimization
+ * in case they share inputs or various intermediate values.
+ */
+void evaluate_fields(blender::Span<Field> fields,
+ blender::IndexMask mask,
+ blender::MutableSpan<GMutableSpan> outputs);
+
+} // namespace blender::fn \ No newline at end of file
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
new file mode 100644
index 00000000000..29f5148c01a
--- /dev/null
+++ b/source/blender/functions/intern/field.cc
@@ -0,0 +1,115 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_map.hh"
+#include "BLI_multi_value_map.hh"
+
+#include "FN_field.hh"
+
+namespace blender::fn {
+
+/* A map to hold the output variables for each function so they can be reused. */
+using OutputMap = MultiValueMap<const FieldFunction *, MFVariable *>;
+
+static MFVariable *get_field_variable(const Field &field, const OutputMap &output_map)
+{
+ const FieldFunction &input_field_function = field.function();
+ const Span<MFVariable *> input_function_outputs = output_map.lookup(&input_field_function);
+ return input_function_outputs[field.function_output_index()];
+}
+
+/**
+ * Traverse the fields recursively. Eventually there will be a field whose function has no
+ * inputs. Start adding multi-function variables there. Those outputs are then used as inputs
+ * for the dependent functions, and the rest of the field tree is built up from there.
+ */
+static void add_field_variables(const Field &field,
+ MFProcedureBuilder &builder,
+ OutputMap &output_map)
+{
+ const FieldFunction &function = field.function();
+ for (const Field *input_field : function.inputs()) {
+ add_field_variables(*input_field, builder, output_map);
+ }
+
+ /* Add the immediate inputs to this field, which were added before in the recursive call.
+ * This will be skipped for functions with no inputs. */
+ Vector<MFVariable *> inputs;
+ for (const Field *input_field : function.inputs()) {
+ MFVariable *input = get_field_variable(*input_field, output_map);
+ builder.add_input_parameter(input->data_type());
+ inputs.append(input);
+ }
+
+ Vector<MFVariable *> outputs = builder.add_call(function.multi_function(), inputs);
+
+ builder.add_destruct(inputs);
+
+ output_map.add_multiple(&function, outputs);
+}
+
+static void build_procedure(const Span<Field> fields, MFProcedure &procedure)
+{
+ MFProcedureBuilder builder{procedure};
+
+ OutputMap output_map;
+
+ for (const Field &field : fields) {
+ add_field_variables(field, builder, output_map);
+ }
+
+ builder.add_return();
+
+ for (const Field &field : fields) {
+ MFVariable *input = get_field_variable(field, output_map);
+ builder.add_output_parameter(*input);
+ }
+
+ std::cout << procedure.to_dot();
+
+ BLI_assert(procedure.validate());
+}
+
+static void evaluate_procedure(MFProcedure &procedure,
+ const IndexMask mask,
+ const MutableSpan<GMutableSpan> outputs)
+{
+ MFProcedureExecutor executor{"Evaluate Field", procedure};
+ MFParamsBuilder params{executor, mask.min_array_size()};
+ MFContextBuilder context;
+
+ /* Add the output arrays. */
+ for (const int i : outputs.index_range()) {
+ params.add_uninitialized_single_output(outputs[i]);
+ }
+
+ executor.call(mask, params, context);
+}
+
+/**
+ * Evaluate more than one prodecure at a time
+ */
+void evaluate_fields(const Span<Field> fields,
+ const IndexMask mask,
+ const MutableSpan<GMutableSpan> outputs)
+{
+ MFProcedure procedure;
+ build_procedure(fields, procedure);
+
+ evaluate_procedure(procedure, mask, outputs);
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc
new file mode 100644
index 00000000000..da275f7ab92
--- /dev/null
+++ b/source/blender/functions/tests/FN_field_test.cc
@@ -0,0 +1,73 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_cpp_type.hh"
+#include "FN_field.hh"
+#include "FN_multi_function_builder.hh"
+
+namespace blender::fn::tests {
+
+TEST(field, ConstantInput)
+{
+ FieldFunction function = FieldFunction(std::make_unique<CustomMF_Constant<int>>(10), {});
+ Field constant_field = Field(CPPType::get<int>(), function, 0);
+
+ Array<int> result(4);
+ GMutableSpan result_generic(result.as_mutable_span());
+ evaluate_fields({&constant_field, 1}, IndexMask(IndexRange(4)), {&result_generic, 1});
+
+ EXPECT_EQ(result[0], 10);
+ EXPECT_EQ(result[1], 10);
+ EXPECT_EQ(result[2], 10);
+ EXPECT_EQ(result[3], 10);
+}
+
+class IndexFunction : public MultiFunction {
+ public:
+ IndexFunction()
+ {
+ static MFSignature signature = create_signature();
+ this->set_signature(&signature);
+ }
+
+ static MFSignature create_signature()
+ {
+ MFSignatureBuilder signature("Index");
+ signature.single_output<int>("Index");
+ return signature.build();
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ MutableSpan<int> result = params.uninitialized_single_output<int>(0, "Index");
+ for (int64_t i : mask) {
+ result[i] = i;
+ }
+ }
+};
+
+TEST(field, VArrayInput)
+{
+
+ FieldFunction function = FieldFunction(std::make_unique<IndexFunction>(), {});
+ Field index_field = Field(CPPType::get<int>(), function, 0);
+
+ Array<int> result_1(4);
+ GMutableSpan result_generic_1(result_1.as_mutable_span());
+ evaluate_fields({&index_field, 1}, IndexMask(IndexRange(4)), {&result_generic_1, 1});
+ EXPECT_EQ(result_1[0], 0);
+ EXPECT_EQ(result_1[1], 1);
+ EXPECT_EQ(result_1[2], 2);
+ EXPECT_EQ(result_1[3], 3);
+
+ Array<int> result_2(4);
+ GMutableSpan result_generic_2(result_2.as_mutable_span());
+ evaluate_fields({&index_field, 1}, {20, 30, 40, 50}, {&result_generic_2, 1});
+ EXPECT_EQ(result_2[0], 20);
+ EXPECT_EQ(result_2[1], 30);
+ EXPECT_EQ(result_2[2], 40);
+ EXPECT_EQ(result_2[3], 50);
+}
+
+} // namespace blender::fn::tests