Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View 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

View File

@@ -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 &params, 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 &params, 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 &params, 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

View File

@@ -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

View File

@@ -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

View File

@@ -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