Add Chromium-only Blender WebEngine parity work
This commit is contained in:
308
blender-5.2.0/source/blender/functions/tests/FN_field_test.cc
Normal file
308
blender-5.2.0/source/blender/functions/tests/FN_field_test.cc
Normal file
@@ -0,0 +1,308 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
#include "BLI_cpp_type.hh"
|
||||
|
||||
#include "FN_field_evaluation.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_test_common.hh"
|
||||
|
||||
namespace blender::fn::tests {
|
||||
|
||||
class FieldTest : public bke::BlenderGTestBase {};
|
||||
|
||||
TEST_F(FieldTest, ConstantFunction)
|
||||
{
|
||||
GField constant_field{FieldOperation::from(std::make_unique<mf::CustomMF_Constant<int>>(10), {}),
|
||||
0};
|
||||
|
||||
Array<int> result(4);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, 4};
|
||||
evaluator.add_with_destination(constant_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[0], 10);
|
||||
EXPECT_EQ(result[1], 10);
|
||||
EXPECT_EQ(result[2], 10);
|
||||
EXPECT_EQ(result[3], 10);
|
||||
}
|
||||
|
||||
class IndexFieldInput final : public FieldInput {
|
||||
public:
|
||||
IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") {}
|
||||
|
||||
GVArray get_varray_for_context(const FieldContext & /*context*/,
|
||||
const IndexMask &mask,
|
||||
ResourceScope & /*scope*/) const final
|
||||
{
|
||||
auto index_func = [](int i) { return i; };
|
||||
return VArray<int>::from_func(mask.min_array_size(), index_func);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FieldTest, VArrayInput)
|
||||
{
|
||||
GField index_field = GField::from_input<IndexFieldInput>();
|
||||
|
||||
Array<int> result_1(4);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, 4};
|
||||
evaluator.add_with_destination(index_field, result_1.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[0], 0);
|
||||
EXPECT_EQ(result_1[1], 1);
|
||||
EXPECT_EQ(result_1[2], 2);
|
||||
EXPECT_EQ(result_1[3], 3);
|
||||
|
||||
/* Evaluate a second time, just to test that the first didn't break anything. */
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int64_t>(indices, memory);
|
||||
|
||||
FieldEvaluator evaluator_2{context, &mask};
|
||||
evaluator_2.add_with_destination(index_field, result_2.as_mutable_span());
|
||||
evaluator_2.evaluate();
|
||||
EXPECT_EQ(result_2[2], 2);
|
||||
EXPECT_EQ(result_2[4], 4);
|
||||
EXPECT_EQ(result_2[6], 6);
|
||||
EXPECT_EQ(result_2[8], 8);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, VArrayInputMultipleOutputs)
|
||||
{
|
||||
FieldInputPtr index_input{MEM_new<IndexFieldInput>(__func__)};
|
||||
GField field_1{index_input};
|
||||
GField field_2{index_input};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int64_t>(indices, memory);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(field_1, result_1.as_mutable_span());
|
||||
evaluator.add_with_destination(field_2, result_2.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[2], 2);
|
||||
EXPECT_EQ(result_1[4], 4);
|
||||
EXPECT_EQ(result_1[6], 6);
|
||||
EXPECT_EQ(result_1[8], 8);
|
||||
EXPECT_EQ(result_2[2], 2);
|
||||
EXPECT_EQ(result_2[4], 4);
|
||||
EXPECT_EQ(result_2[6], 6);
|
||||
EXPECT_EQ(result_2[8], 8);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, InputAndFunction)
|
||||
{
|
||||
GField index_field = GField::from_input<IndexFieldInput>();
|
||||
|
||||
auto add_fn = mf::build::SI2_SO<int, int, int>("add", [](int a, int b) { return a + b; });
|
||||
GField output_field{FieldOperation::from(add_fn, {index_field, index_field}), 0};
|
||||
|
||||
Array<int> result(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int64_t>(indices, memory);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(output_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[2], 4);
|
||||
EXPECT_EQ(result[4], 8);
|
||||
EXPECT_EQ(result[6], 12);
|
||||
EXPECT_EQ(result[8], 16);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, TwoFunctions)
|
||||
{
|
||||
GField index_field = GField::from_input<IndexFieldInput>();
|
||||
|
||||
auto add_fn = mf::build::SI2_SO<int, int, int>("add", [](int a, int b) { return a + b; });
|
||||
GField add_field{FieldOperation::from(add_fn, {index_field, index_field}), 0};
|
||||
|
||||
auto add_10_fn = mf::build::SI1_SO<int, int>("add_10", [](int a) { return a + 10; });
|
||||
GField result_field{FieldOperation::from(add_10_fn, {add_field}), 0};
|
||||
|
||||
Array<int> result(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int64_t>(indices, memory);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(result_field, result.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result[2], 14);
|
||||
EXPECT_EQ(result[4], 18);
|
||||
EXPECT_EQ(result[6], 22);
|
||||
EXPECT_EQ(result[8], 26);
|
||||
}
|
||||
|
||||
class TwoOutputFunction : public mf::MultiFunction {
|
||||
private:
|
||||
mf::Signature signature_;
|
||||
|
||||
public:
|
||||
TwoOutputFunction()
|
||||
{
|
||||
mf::SignatureBuilder builder{"Two Outputs", signature_};
|
||||
builder.single_input<int>("In1");
|
||||
builder.single_input<int>("In2");
|
||||
builder.single_output<int>("Add");
|
||||
builder.single_output<int>("Add10");
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1");
|
||||
const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2");
|
||||
MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add");
|
||||
MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
add[i] = in1[i] + in2[i];
|
||||
add_10[i] = add[i] + 10;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FieldTest, FunctionTwoOutputs)
|
||||
{
|
||||
/* Also use two separate input fields, why not. */
|
||||
GField index_field_1 = GField::from_input<IndexFieldInput>();
|
||||
GField index_field_2 = GField::from_input<IndexFieldInput>();
|
||||
|
||||
FieldOperationPtr fn = FieldOperation::from(std::make_unique<TwoOutputFunction>(),
|
||||
{index_field_1, index_field_2});
|
||||
|
||||
GField result_field_1{fn, 0};
|
||||
GField result_field_2{fn, 1};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
|
||||
const Array<int64_t> indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int64_t>(indices, memory);
|
||||
|
||||
FieldContext context;
|
||||
FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add_with_destination(result_field_1, result_1.as_mutable_span());
|
||||
evaluator.add_with_destination(result_field_2, result_2.as_mutable_span());
|
||||
evaluator.evaluate();
|
||||
EXPECT_EQ(result_1[2], 4);
|
||||
EXPECT_EQ(result_1[4], 8);
|
||||
EXPECT_EQ(result_1[6], 12);
|
||||
EXPECT_EQ(result_1[8], 16);
|
||||
EXPECT_EQ(result_2[2], 14);
|
||||
EXPECT_EQ(result_2[4], 18);
|
||||
EXPECT_EQ(result_2[6], 22);
|
||||
EXPECT_EQ(result_2[8], 26);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, TwoFunctionsTwoOutputs)
|
||||
{
|
||||
GField index_field = GField::from_input<IndexFieldInput>();
|
||||
|
||||
FieldOperationPtr fn = FieldOperation::from(std::make_unique<TwoOutputFunction>(),
|
||||
{index_field, index_field});
|
||||
|
||||
Array<int64_t> mask_indices = {2, 4, 6, 8};
|
||||
IndexMaskMemory memory;
|
||||
IndexMask mask = IndexMask::from_indices<int64_t>(mask_indices, memory);
|
||||
|
||||
Field<int> result_field_1{fn, 0};
|
||||
Field<int> intermediate_field{fn, 1};
|
||||
|
||||
auto add_10_fn = mf::build::SI1_SO<int, int>("add_10", [](int a) { return a + 10; });
|
||||
Field<int> result_field_2{FieldOperation::from(add_10_fn, {intermediate_field}), 0};
|
||||
|
||||
FieldContext field_context;
|
||||
FieldEvaluator field_evaluator{field_context, &mask};
|
||||
VArray<int> result_1;
|
||||
VArray<int> result_2;
|
||||
field_evaluator.add(result_field_1, &result_1);
|
||||
field_evaluator.add(result_field_2, &result_2);
|
||||
field_evaluator.evaluate();
|
||||
|
||||
EXPECT_EQ(result_1.get(2), 4);
|
||||
EXPECT_EQ(result_1.get(4), 8);
|
||||
EXPECT_EQ(result_1.get(6), 12);
|
||||
EXPECT_EQ(result_1.get(8), 16);
|
||||
EXPECT_EQ(result_2.get(2), 24);
|
||||
EXPECT_EQ(result_2.get(4), 28);
|
||||
EXPECT_EQ(result_2.get(6), 32);
|
||||
EXPECT_EQ(result_2.get(8), 36);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, SameFieldTwice)
|
||||
{
|
||||
GField constant_field{FieldOperation::from(std::make_unique<mf::CustomMF_Constant<int>>(10), {}),
|
||||
0};
|
||||
|
||||
FieldContext field_context;
|
||||
IndexMask mask{IndexRange(2)};
|
||||
ResourceScope scope;
|
||||
Vector<GVArray> results = evaluate_fields(
|
||||
scope, {constant_field, constant_field}, mask, field_context);
|
||||
|
||||
VArray<int> varray1 = results[0].typed<int>();
|
||||
VArray<int> varray2 = results[1].typed<int>();
|
||||
|
||||
EXPECT_EQ(varray1.get(0), 10);
|
||||
EXPECT_EQ(varray1.get(1), 10);
|
||||
EXPECT_EQ(varray2.get(0), 10);
|
||||
EXPECT_EQ(varray2.get(1), 10);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, IgnoredOutput)
|
||||
{
|
||||
static mf::tests::OptionalOutputsFunction fn;
|
||||
Field<int> field{FieldOperation::from(fn, {}), 0};
|
||||
|
||||
FieldContext field_context;
|
||||
FieldEvaluator field_evaluator{field_context, 10};
|
||||
VArray<int> results;
|
||||
field_evaluator.add(field, &results);
|
||||
field_evaluator.evaluate();
|
||||
|
||||
EXPECT_EQ(results.get(0), 5);
|
||||
EXPECT_EQ(results.get(3), 5);
|
||||
}
|
||||
|
||||
TEST_F(FieldTest, EvaluateWithVArrayPtr)
|
||||
{
|
||||
VArray<int> dst_a;
|
||||
VArraySpan<int> dst_b;
|
||||
|
||||
FieldContext field_context;
|
||||
FieldEvaluator field_evaluator{field_context, 2};
|
||||
field_evaluator.add(Field<int>(10), &dst_a);
|
||||
field_evaluator.add(Field<int>(20), &dst_b);
|
||||
field_evaluator.evaluate();
|
||||
|
||||
EXPECT_EQ(dst_a.size(), 2);
|
||||
EXPECT_EQ(dst_b.size(), 2);
|
||||
EXPECT_EQ(dst_a[0], 10);
|
||||
EXPECT_EQ(dst_a[1], 10);
|
||||
EXPECT_EQ(dst_b[0], 20);
|
||||
EXPECT_EQ(dst_b[1], 20);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::tests
|
||||
@@ -0,0 +1,185 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_lazy_function_execute.hh"
|
||||
#include "FN_lazy_function_graph.hh"
|
||||
#include "FN_lazy_function_graph_executor.hh"
|
||||
|
||||
#include "BLI_task.h"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
namespace blender::fn::lazy_function::tests {
|
||||
|
||||
class LazyFunctionTest : public bke::BlenderGTestBase {};
|
||||
|
||||
class AddLazyFunction : public LazyFunction {
|
||||
public:
|
||||
AddLazyFunction()
|
||||
{
|
||||
debug_name_ = "Add";
|
||||
inputs_.append({"A", CPPType::get<int>()});
|
||||
inputs_.append({"B", CPPType::get<int>()});
|
||||
outputs_.append({"Result", CPPType::get<int>()});
|
||||
}
|
||||
|
||||
void execute_impl(Params ¶ms, const Context & /*context*/) const override
|
||||
{
|
||||
const int a = params.get_input<int>(0);
|
||||
const int b = params.get_input<int>(1);
|
||||
params.set_output(0, a + b);
|
||||
}
|
||||
};
|
||||
|
||||
class StoreValueFunction : public LazyFunction {
|
||||
private:
|
||||
int *dst1_;
|
||||
int *dst2_;
|
||||
|
||||
public:
|
||||
StoreValueFunction(int *dst1, int *dst2) : dst1_(dst1), dst2_(dst2)
|
||||
{
|
||||
debug_name_ = "Store Value";
|
||||
inputs_.append({"A", CPPType::get<int>()});
|
||||
inputs_.append({"B", CPPType::get<int>(), ValueUsage::Maybe});
|
||||
}
|
||||
|
||||
void execute_impl(Params ¶ms, const Context & /*context*/) const override
|
||||
{
|
||||
*dst1_ = params.get_input<int>(0);
|
||||
if (int *value = params.try_get_input_data_ptr_or_request<int>(1)) {
|
||||
*dst2_ = *value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleSideEffectProvider : public GraphExecutor::SideEffectProvider {
|
||||
private:
|
||||
Vector<const FunctionNode *> side_effect_nodes_;
|
||||
|
||||
public:
|
||||
SimpleSideEffectProvider(Span<const FunctionNode *> side_effect_nodes)
|
||||
: side_effect_nodes_(side_effect_nodes)
|
||||
{
|
||||
}
|
||||
|
||||
Vector<const FunctionNode *> get_nodes_with_side_effects(
|
||||
const Context & /*context*/) const override
|
||||
{
|
||||
return side_effect_nodes_;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LazyFunctionTest, SimpleAdd)
|
||||
{
|
||||
const AddLazyFunction add_fn;
|
||||
int result = 0;
|
||||
execute_lazy_function_eagerly(
|
||||
add_fn, nullptr, nullptr, std::make_tuple(30, 5), std::make_tuple(&result));
|
||||
EXPECT_EQ(result, 35);
|
||||
}
|
||||
|
||||
TEST_F(LazyFunctionTest, SideEffects)
|
||||
{
|
||||
BLI_task_scheduler_init();
|
||||
int dst1 = 0;
|
||||
int dst2 = 0;
|
||||
|
||||
const AddLazyFunction add_fn;
|
||||
const StoreValueFunction store_fn{&dst1, &dst2};
|
||||
|
||||
Graph graph;
|
||||
FunctionNode &add_node_1 = graph.add_function(add_fn);
|
||||
FunctionNode &add_node_2 = graph.add_function(add_fn);
|
||||
FunctionNode &store_node = graph.add_function(store_fn);
|
||||
GraphInputSocket &graph_input = graph.add_input(CPPType::get<int>());
|
||||
|
||||
graph.add_link(graph_input, add_node_1.input(0));
|
||||
graph.add_link(graph_input, add_node_2.input(0));
|
||||
graph.add_link(add_node_1.output(0), store_node.input(0));
|
||||
graph.add_link(add_node_2.output(0), store_node.input(1));
|
||||
|
||||
const int value_10 = 10;
|
||||
const int value_100 = 100;
|
||||
add_node_1.input(1).set_default_value(&value_10);
|
||||
add_node_2.input(1).set_default_value(&value_100);
|
||||
|
||||
graph.update_node_indices();
|
||||
|
||||
SimpleSideEffectProvider side_effect_provider{{&store_node}};
|
||||
|
||||
GraphExecutor executor_fn{graph, {&graph_input}, {}, nullptr, &side_effect_provider, nullptr};
|
||||
execute_lazy_function_eagerly(
|
||||
executor_fn, nullptr, nullptr, std::make_tuple(5), std::make_tuple());
|
||||
|
||||
EXPECT_EQ(dst1, 15);
|
||||
EXPECT_EQ(dst2, 105);
|
||||
}
|
||||
|
||||
class PartialEvaluationTestFunction : public LazyFunction {
|
||||
public:
|
||||
PartialEvaluationTestFunction()
|
||||
{
|
||||
debug_name_ = "Partial Evaluation";
|
||||
allow_missing_requested_inputs_ = true;
|
||||
|
||||
inputs_.append_as("A", CPPType::get<int>(), ValueUsage::Used);
|
||||
inputs_.append_as("B", CPPType::get<int>(), ValueUsage::Used);
|
||||
|
||||
outputs_.append_as("A*2", CPPType::get<int>());
|
||||
outputs_.append_as("B*5", CPPType::get<int>());
|
||||
}
|
||||
|
||||
void execute_impl(Params ¶ms, const Context & /*context*/) const override
|
||||
{
|
||||
if (!params.output_was_set(0)) {
|
||||
if (int *a = params.try_get_input_data_ptr<int>(0)) {
|
||||
params.set_output(0, *a * 2);
|
||||
}
|
||||
}
|
||||
if (!params.output_was_set(1)) {
|
||||
if (int *b = params.try_get_input_data_ptr<int>(1)) {
|
||||
params.set_output(1, *b * 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void possible_output_dependencies(const int output_index,
|
||||
FunctionRef<void(Span<int>)> fn) const override
|
||||
{
|
||||
/* Each output only depends on the input with the same index. */
|
||||
const int input_index = output_index;
|
||||
fn({input_index});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LazyFunctionTest, GraphWithCycle)
|
||||
{
|
||||
const PartialEvaluationTestFunction fn;
|
||||
|
||||
Graph graph;
|
||||
FunctionNode &fn_node = graph.add_function(fn);
|
||||
|
||||
GraphInputSocket &input_socket = graph.add_input(CPPType::get<int>());
|
||||
GraphOutputSocket &output_socket = graph.add_output(CPPType::get<int>());
|
||||
|
||||
graph.add_link(input_socket, fn_node.input(0));
|
||||
/* NOTE: This creates a cycle in the graph. However, it should still be possible to evaluate it,
|
||||
* because there is no actual data dependency in the cycle. */
|
||||
graph.add_link(fn_node.output(0), fn_node.input(1));
|
||||
graph.add_link(fn_node.output(1), output_socket);
|
||||
|
||||
graph.update_node_indices();
|
||||
|
||||
GraphExecutor executor_fn{graph, {&input_socket}, {&output_socket}, nullptr, nullptr, nullptr};
|
||||
int result = 0;
|
||||
execute_lazy_function_eagerly(
|
||||
executor_fn, nullptr, nullptr, std::make_tuple(10), std::make_tuple(&result));
|
||||
|
||||
EXPECT_EQ(result, 10 * 2 * 5);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::lazy_function::tests
|
||||
@@ -0,0 +1,430 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_procedure_builder.hh"
|
||||
#include "FN_multi_function_procedure_executor.hh"
|
||||
#include "FN_multi_function_test_common.hh"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
namespace blender::fn::multi_function::tests {
|
||||
|
||||
class MultiFunctionProcedureTest : public bke::BlenderGTestBase {};
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, ConstantOutput)
|
||||
{
|
||||
/**
|
||||
* procedure(int *var2) {
|
||||
* var1 = 5;
|
||||
* var2 = var1 + var1;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_Constant<int> constant_fn{5};
|
||||
auto add_fn = build::SI2_SO<int, int, int>("Add", [](int a, int b) { return a + b; });
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
auto [var1] = builder.add_call<1>(constant_fn);
|
||||
auto [var2] = builder.add_call<1>(add_fn, {var1, var1});
|
||||
builder.add_destruct(*var1);
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var2);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor executor{procedure};
|
||||
|
||||
const IndexMask mask(2);
|
||||
ParamsBuilder params{executor, &mask};
|
||||
ContextBuilder context;
|
||||
|
||||
Array<int> output_array(2);
|
||||
params.add_uninitialized_single_output(output_array.as_mutable_span());
|
||||
|
||||
executor.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(output_array[0], 10);
|
||||
EXPECT_EQ(output_array[1], 10);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, SimpleTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int var2, int *var4) {
|
||||
* int var3 = var1 + var2;
|
||||
* var4 = var2 + var3;
|
||||
* var4 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
auto add_fn = mf::build::SI2_SO<int, int, int>("add", [](int a, int b) { return a + b; });
|
||||
auto add_10_fn = mf::build::SM<int>("add_10", [](int &a) { a += 10; });
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var1 = &builder.add_single_input_parameter<int>();
|
||||
Variable *var2 = &builder.add_single_input_parameter<int>();
|
||||
auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
|
||||
auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
|
||||
builder.add_call(add_10_fn, {var4});
|
||||
builder.add_destruct({var1, var2, var3});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var4);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor executor{procedure};
|
||||
|
||||
const IndexMask mask(3);
|
||||
ParamsBuilder params{executor, &mask};
|
||||
ContextBuilder context;
|
||||
|
||||
Array<int> input_array = {1, 2, 3};
|
||||
params.add_readonly_single_input(input_array.as_span());
|
||||
params.add_readonly_single_input_value(3);
|
||||
|
||||
Array<int> output_array(3);
|
||||
params.add_uninitialized_single_output(output_array.as_mutable_span());
|
||||
|
||||
executor.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(output_array[0], 17);
|
||||
EXPECT_EQ(output_array[1], 18);
|
||||
EXPECT_EQ(output_array[2], 19);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, BranchTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int &var1, bool var2) {
|
||||
* if (var2) {
|
||||
* var1 += 100;
|
||||
* }
|
||||
* else {
|
||||
* var1 += 10;
|
||||
* }
|
||||
* var1 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
auto add_10_fn = build::SM<int>("add_10", [](int &a) { a += 10; });
|
||||
auto add_100_fn = build::SM<int>("add_100", [](int &a) { a += 100; });
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var1 = &builder.add_single_mutable_parameter<int>();
|
||||
Variable *var2 = &builder.add_single_input_parameter<bool>();
|
||||
|
||||
ProcedureBuilder::Branch branch = builder.add_branch(*var2);
|
||||
branch.branch_false.add_call(add_10_fn, {var1});
|
||||
branch.branch_true.add_call(add_100_fn, {var1});
|
||||
builder.set_cursor_after_branch(branch);
|
||||
builder.add_call(add_10_fn, {var1});
|
||||
builder.add_destruct({var2});
|
||||
builder.add_return();
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
const IndexMask mask(IndexRange(1, 4));
|
||||
ParamsBuilder params(procedure_fn, &mask);
|
||||
|
||||
Array<int> values_a = {1, 5, 3, 6, 2};
|
||||
Array<bool> values_cond = {true, false, true, true, false};
|
||||
|
||||
params.add_single_mutable(values_a.as_mutable_span());
|
||||
params.add_readonly_single_input(values_cond.as_span());
|
||||
|
||||
ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(values_a[0], 1);
|
||||
EXPECT_EQ(values_a[1], 25);
|
||||
EXPECT_EQ(values_a[2], 113);
|
||||
EXPECT_EQ(values_a[3], 116);
|
||||
EXPECT_EQ(values_a[4], 22);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, EvaluateOne)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int *var2) {
|
||||
* var2 = var1 + 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
int tot_evaluations = 0;
|
||||
const auto add_10_fn = mf::build::SI1_SO<int, int>("add_10", [&](int a) {
|
||||
tot_evaluations++;
|
||||
return a + 10;
|
||||
});
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var1 = &builder.add_single_input_parameter<int>();
|
||||
auto [var2] = builder.add_call<1>(add_10_fn, {var1});
|
||||
builder.add_destruct(*var1);
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var2);
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 1, 3, 4}, memory);
|
||||
ParamsBuilder params{procedure_fn, &mask};
|
||||
|
||||
Array<int> values_out = {1, 2, 3, 4, 5};
|
||||
params.add_readonly_single_input_value(1);
|
||||
params.add_uninitialized_single_output(values_out.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(values_out[0], 11);
|
||||
EXPECT_EQ(values_out[1], 11);
|
||||
EXPECT_EQ(values_out[2], 3);
|
||||
EXPECT_EQ(values_out[3], 11);
|
||||
EXPECT_EQ(values_out[4], 11);
|
||||
/* We expect only one evaluation, because the input is constant. */
|
||||
EXPECT_EQ(tot_evaluations, 1);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, SimpleLoop)
|
||||
{
|
||||
/**
|
||||
* procedure(int count, int *out) {
|
||||
* out = 1;
|
||||
* int index = 0'
|
||||
* loop {
|
||||
* if (index >= count) {
|
||||
* break;
|
||||
* }
|
||||
* out *= 2;
|
||||
* index += 1;
|
||||
* }
|
||||
* out += 1000;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_Constant<int> const_1_fn{1};
|
||||
CustomMF_Constant<int> const_0_fn{0};
|
||||
auto greater_or_equal_fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"greater or equal", [](int a, int b) { return a >= b; });
|
||||
auto double_fn = build::SM<int>("double", [](int &a) { a *= 2; });
|
||||
auto add_1000_fn = build::SM<int>("add 1000", [](int &a) { a += 1000; });
|
||||
auto add_1_fn = build::SM<int>("add 1", [](int &a) { a += 1; });
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var_count = &builder.add_single_input_parameter<int>("count");
|
||||
auto [var_out] = builder.add_call<1>(const_1_fn);
|
||||
var_out->set_name("out");
|
||||
auto [var_index] = builder.add_call<1>(const_0_fn);
|
||||
var_index->set_name("index");
|
||||
|
||||
ProcedureBuilder::Loop loop = builder.add_loop();
|
||||
auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
|
||||
var_condition->set_name("condition");
|
||||
ProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
|
||||
branch.branch_true.add_destruct(*var_condition);
|
||||
branch.branch_true.add_loop_break(loop);
|
||||
branch.branch_false.add_destruct(*var_condition);
|
||||
builder.set_cursor_after_branch(branch);
|
||||
builder.add_call(double_fn, {var_out});
|
||||
builder.add_call(add_1_fn, {var_index});
|
||||
builder.add_loop_continue(loop);
|
||||
builder.set_cursor_after_loop(loop);
|
||||
builder.add_call(add_1000_fn, {var_out});
|
||||
builder.add_destruct({var_count, var_index});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_out);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 1, 3, 4}, memory);
|
||||
ParamsBuilder params{procedure_fn, &mask};
|
||||
|
||||
Array<int> counts = {4, 3, 7, 6, 4};
|
||||
Array<int> results(5, -1);
|
||||
|
||||
params.add_readonly_single_input(counts.as_span());
|
||||
params.add_uninitialized_single_output(results.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(results[0], 1016);
|
||||
EXPECT_EQ(results[1], 1008);
|
||||
EXPECT_EQ(results[2], -1);
|
||||
EXPECT_EQ(results[3], 1064);
|
||||
EXPECT_EQ(results[4], 1016);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, Vectors)
|
||||
{
|
||||
/**
|
||||
* procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
|
||||
* v1.extend(v2);
|
||||
* int constant = 5;
|
||||
* v2.append(constant);
|
||||
* v2.extend(v1);
|
||||
* int len = sum(v2);
|
||||
* v3 = range(len);
|
||||
* }
|
||||
*/
|
||||
|
||||
CreateRangeFunction create_range_fn;
|
||||
ConcatVectorsFunction extend_fn;
|
||||
GenericAppendFunction append_fn{CPPType::get<int>()};
|
||||
SumVectorFunction sum_elements_fn;
|
||||
CustomMF_Constant<int> constant_5_fn{5};
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var_v1 = &builder.add_input_parameter(DataType::ForVector<int>());
|
||||
Variable *var_v2 = &builder.add_parameter(ParamType::ForMutableVector(CPPType::get<int>()));
|
||||
builder.add_call(extend_fn, {var_v1, var_v2});
|
||||
auto [var_constant] = builder.add_call<1>(constant_5_fn);
|
||||
builder.add_call(append_fn, {var_v2, var_constant});
|
||||
builder.add_destruct(*var_constant);
|
||||
builder.add_call(extend_fn, {var_v2, var_v1});
|
||||
auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
|
||||
auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
|
||||
builder.add_destruct({var_v1, var_len});
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_v3);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 1, 3, 4}, memory);
|
||||
ParamsBuilder params{procedure_fn, &mask};
|
||||
|
||||
Array<int> v1 = {5, 2, 3};
|
||||
GVectorArray v2{CPPType::get<int>(), 5};
|
||||
GVectorArray v3{CPPType::get<int>(), 5};
|
||||
|
||||
int value_10 = 10;
|
||||
v2.append(0, &value_10);
|
||||
v2.append(4, &value_10);
|
||||
|
||||
params.add_readonly_vector_input(v1.as_span());
|
||||
params.add_vector_mutable(v2);
|
||||
params.add_vector_output(v3);
|
||||
|
||||
ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(v2[0].size(), 6);
|
||||
EXPECT_EQ(v2[1].size(), 4);
|
||||
EXPECT_EQ(v2[2].size(), 0);
|
||||
EXPECT_EQ(v2[3].size(), 4);
|
||||
EXPECT_EQ(v2[4].size(), 6);
|
||||
|
||||
EXPECT_EQ(v3[0].size(), 35);
|
||||
EXPECT_EQ(v3[1].size(), 15);
|
||||
EXPECT_EQ(v3[2].size(), 0);
|
||||
EXPECT_EQ(v3[3].size(), 15);
|
||||
EXPECT_EQ(v3[4].size(), 35);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, BufferReuse)
|
||||
{
|
||||
/**
|
||||
* procedure(int a, int *out) {
|
||||
* int b = a + 10;
|
||||
* int c = c + 10;
|
||||
* int d = d + 10;
|
||||
* int e = d + 10;
|
||||
* out = e + 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
auto add_10_fn = build::SI1_SO<int, int>("add 10", [](int a) { return a + 10; });
|
||||
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
Variable *var_a = &builder.add_single_input_parameter<int>();
|
||||
auto [var_b] = builder.add_call<1>(add_10_fn, {var_a});
|
||||
builder.add_destruct(*var_a);
|
||||
auto [var_c] = builder.add_call<1>(add_10_fn, {var_b});
|
||||
builder.add_destruct(*var_b);
|
||||
auto [var_d] = builder.add_call<1>(add_10_fn, {var_c});
|
||||
builder.add_destruct(*var_c);
|
||||
auto [var_e] = builder.add_call<1>(add_10_fn, {var_d});
|
||||
builder.add_destruct(*var_d);
|
||||
auto [var_out] = builder.add_call<1>(add_10_fn, {var_e});
|
||||
builder.add_destruct(*var_e);
|
||||
builder.add_return();
|
||||
builder.add_output_parameter(*var_out);
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
|
||||
Array<int> inputs = {4, 1, 6, 2, 3};
|
||||
Array<int> results(5, -1);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 2, 3, 4}, memory);
|
||||
ParamsBuilder params{procedure_fn, &mask};
|
||||
|
||||
params.add_readonly_single_input(inputs.as_span());
|
||||
params.add_uninitialized_single_output(results.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(results[0], 54);
|
||||
EXPECT_EQ(results[1], -1);
|
||||
EXPECT_EQ(results[2], 56);
|
||||
EXPECT_EQ(results[3], 52);
|
||||
EXPECT_EQ(results[4], 53);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionProcedureTest, OutputBufferReplaced)
|
||||
{
|
||||
Procedure procedure;
|
||||
ProcedureBuilder builder{procedure};
|
||||
|
||||
const int output_value = 42;
|
||||
CustomMF_GenericConstant constant_fn(CPPType::get<int>(), &output_value, false);
|
||||
Variable &var_o = procedure.new_variable(DataType::ForSingle<int>());
|
||||
builder.add_output_parameter(var_o);
|
||||
builder.add_call_with_all_variables(constant_fn, {&var_o});
|
||||
builder.add_destruct(var_o);
|
||||
builder.add_call_with_all_variables(constant_fn, {&var_o});
|
||||
builder.add_return();
|
||||
|
||||
EXPECT_TRUE(procedure.validate());
|
||||
|
||||
ProcedureExecutor procedure_fn{procedure};
|
||||
|
||||
Array<int> output(3, 0);
|
||||
IndexMask mask(output.size());
|
||||
mf::ParamsBuilder params(procedure_fn, &mask);
|
||||
params.add_uninitialized_single_output(output.as_mutable_span());
|
||||
|
||||
mf::ContextBuilder context;
|
||||
procedure_fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(output[0], output_value);
|
||||
EXPECT_EQ(output[1], output_value);
|
||||
EXPECT_EQ(output[2], output_value);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::multi_function::tests
|
||||
@@ -0,0 +1,284 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
#include "FN_multi_function_builder.hh"
|
||||
#include "FN_multi_function_test_common.hh"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
namespace blender::fn::multi_function::tests {
|
||||
|
||||
namespace {
|
||||
class MultiFunctionTest : public bke::BlenderGTestBase {};
|
||||
|
||||
class AddFunction : public MultiFunction {
|
||||
public:
|
||||
AddFunction()
|
||||
{
|
||||
static Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder("Add", signature);
|
||||
builder.single_input<int>("A");
|
||||
builder.single_input<int>("B");
|
||||
builder.single_output<int>("Result");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
const VArray<int> &a = params.readonly_single_input<int>(0, "A");
|
||||
const VArray<int> &b = params.readonly_single_input<int>(1, "B");
|
||||
MutableSpan<int> result = params.uninitialized_single_output<int>(2, "Result");
|
||||
|
||||
mask.foreach_index([&](const int64_t i) { result[i] = a[i] + b[i]; });
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MultiFunctionTest, AddFunction)
|
||||
{
|
||||
AddFunction fn;
|
||||
|
||||
Array<int> input1 = {4, 5, 6};
|
||||
Array<int> input2 = {10, 20, 30};
|
||||
Array<int> output(3, -1);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 2}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_readonly_single_input(input1.as_span());
|
||||
params.add_readonly_single_input(input2.as_span());
|
||||
params.add_uninitialized_single_output(output.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(output[0], 14);
|
||||
EXPECT_EQ(output[1], -1);
|
||||
EXPECT_EQ(output[2], 36);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, AddPrefixFunction)
|
||||
{
|
||||
AddPrefixFunction fn;
|
||||
|
||||
Array<std::string> strings = {
|
||||
"Hello",
|
||||
"World",
|
||||
"This is a test",
|
||||
"Another much longer string to trigger an allocation",
|
||||
};
|
||||
|
||||
std::string prefix = "AB";
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 2, 3}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_readonly_single_input(&prefix);
|
||||
params.add_single_mutable(strings.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(strings[0], "ABHello");
|
||||
EXPECT_EQ(strings[1], "World");
|
||||
EXPECT_EQ(strings[2], "ABThis is a test");
|
||||
EXPECT_EQ(strings[3], "ABAnother much longer string to trigger an allocation");
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, CreateRangeFunction)
|
||||
{
|
||||
CreateRangeFunction fn;
|
||||
|
||||
GVectorArray ranges(CPPType::get<int>(), 5);
|
||||
GVectorArray_TypedMutableRef<int> ranges_ref{ranges};
|
||||
Array<int> sizes = {3, 0, 6, 1, 4};
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 1, 2, 3}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_readonly_single_input(sizes.as_span());
|
||||
params.add_vector_output(ranges);
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(ranges[0].size(), 3);
|
||||
EXPECT_EQ(ranges[1].size(), 0);
|
||||
EXPECT_EQ(ranges[2].size(), 6);
|
||||
EXPECT_EQ(ranges[3].size(), 1);
|
||||
EXPECT_EQ(ranges[4].size(), 0);
|
||||
|
||||
EXPECT_EQ(ranges_ref[0][0], 0);
|
||||
EXPECT_EQ(ranges_ref[0][1], 1);
|
||||
EXPECT_EQ(ranges_ref[0][2], 2);
|
||||
EXPECT_EQ(ranges_ref[2][0], 0);
|
||||
EXPECT_EQ(ranges_ref[2][1], 1);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, GenericAppendFunction)
|
||||
{
|
||||
GenericAppendFunction fn(CPPType::get<int32_t>());
|
||||
|
||||
GVectorArray vectors(CPPType::get<int32_t>(), 4);
|
||||
GVectorArray_TypedMutableRef<int> vectors_ref{vectors};
|
||||
vectors_ref.append(0, 1);
|
||||
vectors_ref.append(0, 2);
|
||||
vectors_ref.append(2, 6);
|
||||
Array<int> values = {5, 7, 3, 1};
|
||||
|
||||
const IndexMask mask(IndexRange(vectors.size()));
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_vector_mutable(vectors);
|
||||
params.add_readonly_single_input(values.as_span());
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(vectors[0].size(), 3);
|
||||
EXPECT_EQ(vectors[1].size(), 1);
|
||||
EXPECT_EQ(vectors[2].size(), 2);
|
||||
EXPECT_EQ(vectors[3].size(), 1);
|
||||
|
||||
EXPECT_EQ(vectors_ref[0][0], 1);
|
||||
EXPECT_EQ(vectors_ref[0][1], 2);
|
||||
EXPECT_EQ(vectors_ref[0][2], 5);
|
||||
EXPECT_EQ(vectors_ref[1][0], 7);
|
||||
EXPECT_EQ(vectors_ref[2][0], 6);
|
||||
EXPECT_EQ(vectors_ref[2][1], 3);
|
||||
EXPECT_EQ(vectors_ref[3][0], 1);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, CustomMF_Constant)
|
||||
{
|
||||
CustomMF_Constant<int> fn{42};
|
||||
|
||||
Array<int> outputs(4, 0);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 2, 3}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(outputs[0], 42);
|
||||
EXPECT_EQ(outputs[1], 0);
|
||||
EXPECT_EQ(outputs[2], 42);
|
||||
EXPECT_EQ(outputs[3], 42);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, CustomMF_GenericConstant)
|
||||
{
|
||||
int value = 42;
|
||||
CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value, false};
|
||||
|
||||
Array<int> outputs(4, 0);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({0, 1, 2}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(outputs[0], 42);
|
||||
EXPECT_EQ(outputs[1], 42);
|
||||
EXPECT_EQ(outputs[2], 42);
|
||||
EXPECT_EQ(outputs[3], 0);
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, CustomMF_GenericConstantArray)
|
||||
{
|
||||
std::array<int, 4> values = {3, 4, 5, 6};
|
||||
CustomMF_GenericConstantArray fn{GSpan(Span(values))};
|
||||
|
||||
GVectorArray vector_array{CPPType::get<int32_t>(), 4};
|
||||
GVectorArray_TypedMutableRef<int> vector_array_ref{vector_array};
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({1, 2, 3}, memory);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_vector_output(vector_array);
|
||||
|
||||
ContextBuilder context;
|
||||
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(vector_array[0].size(), 0);
|
||||
EXPECT_EQ(vector_array[1].size(), 4);
|
||||
EXPECT_EQ(vector_array[2].size(), 4);
|
||||
EXPECT_EQ(vector_array[3].size(), 4);
|
||||
for (int i = 1; i < 4; i++) {
|
||||
EXPECT_EQ(vector_array_ref[i][0], 3);
|
||||
EXPECT_EQ(vector_array_ref[i][1], 4);
|
||||
EXPECT_EQ(vector_array_ref[i][2], 5);
|
||||
EXPECT_EQ(vector_array_ref[i][3], 6);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, IgnoredOutputs)
|
||||
{
|
||||
OptionalOutputsFunction fn;
|
||||
{
|
||||
const IndexMask mask(10);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_ignored_single_output("Out 1");
|
||||
params.add_ignored_single_output("Out 2");
|
||||
ContextBuilder context;
|
||||
fn.call(mask, params, context);
|
||||
}
|
||||
{
|
||||
Array<int> results_1(10);
|
||||
Array<std::string> results_2(10, NoInitialization());
|
||||
const IndexMask mask(10);
|
||||
|
||||
ParamsBuilder params(fn, &mask);
|
||||
params.add_uninitialized_single_output(results_1.as_mutable_span(), "Out 1");
|
||||
params.add_uninitialized_single_output(results_2.as_mutable_span(), "Out 2");
|
||||
ContextBuilder context;
|
||||
fn.call(mask, 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");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MultiFunctionTest, build_move_only)
|
||||
{
|
||||
auto adder = std::make_unique<int>(10);
|
||||
const auto fn = mf::build::SI1_SO<int, int>(
|
||||
"add", [adder = std::move(adder)](const int a) { return a + *adder; });
|
||||
|
||||
const IndexMask mask(2);
|
||||
ParamsBuilder params(fn, &mask);
|
||||
|
||||
Array<int> inputs = {3, 5};
|
||||
Array<int> outputs(2);
|
||||
params.add_readonly_single_input(inputs.as_span());
|
||||
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
||||
|
||||
ContextBuilder context;
|
||||
fn.call(mask, params, context);
|
||||
|
||||
EXPECT_EQ(outputs[0], 13);
|
||||
EXPECT_EQ(outputs[1], 15);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace blender::fn::multi_function::tests
|
||||
@@ -0,0 +1,187 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
namespace blender::fn::multi_function::tests {
|
||||
|
||||
class AddPrefixFunction : public MultiFunction {
|
||||
public:
|
||||
AddPrefixFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Add Prefix", signature};
|
||||
builder.single_input<std::string>("Prefix");
|
||||
builder.single_mutable<std::string>("Strings");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
const VArray<std::string> &prefixes = params.readonly_single_input<std::string>(0, "Prefix");
|
||||
MutableSpan<std::string> strings = params.single_mutable<std::string>(1, "Strings");
|
||||
|
||||
mask.foreach_index([&](const int64_t i) { strings[i] = prefixes[i] + strings[i]; });
|
||||
}
|
||||
};
|
||||
|
||||
class CreateRangeFunction : public MultiFunction {
|
||||
public:
|
||||
CreateRangeFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Create Range", signature};
|
||||
builder.single_input<int>("Size");
|
||||
builder.vector_output<int>("Range");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
const VArray<int> &sizes = params.readonly_single_input<int>(0, "Size");
|
||||
GVectorArray &ranges = params.vector_output(1, "Range");
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
int size = sizes[i];
|
||||
for (int j : IndexRange(size)) {
|
||||
ranges.append(i, &j);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class GenericAppendFunction : public MultiFunction {
|
||||
private:
|
||||
Signature signature_;
|
||||
|
||||
public:
|
||||
GenericAppendFunction(const CPPType &type)
|
||||
{
|
||||
SignatureBuilder builder{"Append", signature_};
|
||||
builder.vector_mutable("Vector", type);
|
||||
builder.single_input("Value", type);
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
GVectorArray &vectors = params.vector_mutable(0, "Vector");
|
||||
const GVArray &values = params.readonly_single_input(1, "Value");
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(values.type(), buffer);
|
||||
values.get(i, buffer);
|
||||
vectors.append(i, buffer);
|
||||
values.type().destruct(buffer);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ConcatVectorsFunction : public MultiFunction {
|
||||
public:
|
||||
ConcatVectorsFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Concat Vectors", signature};
|
||||
builder.vector_mutable<int>("A");
|
||||
builder.vector_input<int>("B");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
GVectorArray &a = params.vector_mutable(0);
|
||||
const GVVectorArray &b = params.readonly_vector_input(1);
|
||||
a.extend(mask, b);
|
||||
}
|
||||
};
|
||||
|
||||
class AppendFunction : public MultiFunction {
|
||||
public:
|
||||
AppendFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Append", signature};
|
||||
builder.vector_mutable<int>("Vector");
|
||||
builder.single_input<int>("Value");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
GVectorArray_TypedMutableRef<int> vectors = params.vector_mutable<int>(0);
|
||||
const VArray<int> &values = params.readonly_single_input<int>(1);
|
||||
|
||||
mask.foreach_index([&](const int64_t i) { vectors.append(i, values[i]); });
|
||||
}
|
||||
};
|
||||
|
||||
class SumVectorFunction : public MultiFunction {
|
||||
public:
|
||||
SumVectorFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Sum Vectors", signature};
|
||||
builder.vector_input<int>("Vector");
|
||||
builder.single_output<int>("Sum");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
const VVectorArray<int> &vectors = params.readonly_vector_input<int>(0);
|
||||
MutableSpan<int> sums = params.uninitialized_single_output<int>(1);
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
int sum = 0;
|
||||
for (int j : IndexRange(vectors.get_vector_size(i))) {
|
||||
sum += vectors.get_vector_element(i, j);
|
||||
}
|
||||
sums[i] = sum;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class OptionalOutputsFunction : public MultiFunction {
|
||||
public:
|
||||
OptionalOutputsFunction()
|
||||
{
|
||||
static const Signature signature = []() {
|
||||
Signature signature;
|
||||
SignatureBuilder builder{"Optional Outputs", signature};
|
||||
builder.single_output<int>("Out 1");
|
||||
builder.single_output<std::string>("Out 2");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, Params params, Context /*context*/) const override
|
||||
{
|
||||
if (params.single_output_is_required(0, "Out 1")) {
|
||||
MutableSpan<int> values = params.uninitialized_single_output<int>(0, "Out 1");
|
||||
index_mask::masked_fill(values, 5, mask);
|
||||
}
|
||||
MutableSpan<std::string> values = params.uninitialized_single_output<std::string>(1, "Out 2");
|
||||
mask.foreach_index(
|
||||
[&](const int i) { new (&values[i]) std::string("hello, this is a long string"); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::fn::multi_function::tests
|
||||
Reference in New Issue
Block a user