Compare commits
136 Commits
temp-compo
...
temp-geome
Author | SHA1 | Date | |
---|---|---|---|
73e52ab55f | |||
3fec225931 | |||
f0443b8859 | |||
b73f692919 | |||
4aeb9dc996 | |||
3b12594054 | |||
0597e93e5e | |||
5f003515a1 | |||
26f8647fea | |||
12f296d800 | |||
75ec632b61 | |||
d6519d4305 | |||
54cf7eaf92 | |||
d0b1e739b1 | |||
b04e2e8877 | |||
84660c44f0 | |||
c398cad059 | |||
35bf6b9790 | |||
e5a59e876e | |||
d464816c37 | |||
007651129a | |||
1968c9b702 | |||
eb54741226 | |||
ce86a518b9 | |||
09a5ea059f | |||
b3cc26bf35 | |||
5f29552be7 | |||
6afa55b7e4 | |||
37d717fe14 | |||
f193cf66d4 | |||
68efa935da | |||
1a0fed5d8e | |||
c3206bd2a0 | |||
26e7fef920 | |||
92e1c8744b | |||
cfe8832dd3 | |||
6a5b048658 | |||
ad0dbde653 | |||
f25c1b4950 | |||
469f752b80 | |||
55f2867db3 | |||
80429002d7 | |||
0910b76be3 | |||
1ccfd6842b | |||
0a0360c8cd | |||
be0201259a | |||
ef2a48329d | |||
81a0c384da | |||
70eaba3cb1 | |||
7e39e78259 | |||
c2122c39ae | |||
fb26ee8a7e | |||
001072721f | |||
7ebc3140bb | |||
acc2e8afa9 | |||
04bb1bda32 | |||
eed93aaa07 | |||
fc0bb6cdee | |||
10f2ad1556 | |||
6d77b87b13 | |||
2101b46802 | |||
34f6765630 | |||
c58d1acba8 | |||
0ee79f304e | |||
e642de3d6f | |||
eca5a8b695 | |||
79c79f3c70 | |||
a9970d3cb9 | |||
b812f289f5 | |||
215ce0fb57 | |||
5154598845 | |||
1ee80d792c | |||
95284d2f1e | |||
7281f3eb56 | |||
607ef8f6c5 | |||
1ce640cc0b | |||
7bed18fdb1 | |||
c827b50d40 | |||
3c7e3c8e44 | |||
98e38ce4f3 | |||
132cf268c0 | |||
fd7edc9b05 | |||
ecf7c90840 | |||
d78a530af1 | |||
3ebe61db9f | |||
86c2f139c6 | |||
3596c348eb | |||
41a81474e4 | |||
aa2822d137 | |||
55b333d3e3 | |||
00cfad8578 | |||
1891c956e5 | |||
249c050757 | |||
a0081046b6 | |||
635f73b7f1 | |||
74fcd50e2f | |||
b04a2a7be7 | |||
083671e8ac | |||
78ea401e19 | |||
f3ca987bce | |||
2245add9f8 | |||
31004d7fac | |||
3d3f66ed41 | |||
c9c0195da5 | |||
70c0403858 | |||
8d4de82c7f | |||
22c51c2d51 | |||
158bd7c6a0 | |||
4a28d0b583 | |||
0501e6e693 | |||
8450ac09c1 | |||
1af00015e8 | |||
b44c3a3125 | |||
d729f1ca37 | |||
0fc9f00c14 | |||
6c9b339af7 | |||
b7a976af01 | |||
a689037917 | |||
313403c1f1 | |||
60409b8823 | |||
773dc2ec94 | |||
2a98c5d06b | |||
6d1b4ce3c6 | |||
0b2d961b70 | |||
d553b70470 | |||
6954f2cdd7 | |||
8cc832110a | |||
7b8c54b5a1 | |||
e850d175b5 | |||
326f79d59b | |||
ec4954ece2 | |||
b30e782c82 | |||
e34fe5d28e | |||
8581a062f1 | |||
b43971e5e9 | |||
855382170e |
@@ -28,14 +28,19 @@ 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
|
||||
intern/multi_function.cc
|
||||
intern/multi_function_builder.cc
|
||||
intern/multi_function_procedure.cc
|
||||
intern/multi_function_procedure_builder.cc
|
||||
intern/multi_function_procedure_executor.cc
|
||||
|
||||
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
|
||||
@@ -48,6 +53,9 @@ set(SRC
|
||||
FN_multi_function_data_type.hh
|
||||
FN_multi_function_param_type.hh
|
||||
FN_multi_function_params.hh
|
||||
FN_multi_function_procedure.hh
|
||||
FN_multi_function_procedure_builder.hh
|
||||
FN_multi_function_procedure_executor.hh
|
||||
FN_multi_function_signature.hh
|
||||
)
|
||||
|
||||
@@ -60,8 +68,10 @@ 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
|
||||
tests/FN_multi_function_test.cc
|
||||
)
|
||||
set(TEST_LIB
|
||||
|
173
source/blender/functions/FN_field.hh
Normal file
173
source/blender/functions/FN_field.hh
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 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_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "FN_generic_virtual_array.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 FieldInput;
|
||||
class FieldFunction;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
std::shared_ptr<FieldFunction> function_;
|
||||
/**
|
||||
* Which output of the function this field corresponds to.
|
||||
*/
|
||||
int output_index_;
|
||||
|
||||
std::shared_ptr<FieldInput> input_;
|
||||
|
||||
public:
|
||||
Field(const fn::CPPType &type, std::shared_ptr<FieldFunction> function, const int output_index)
|
||||
: type_(&type), function_(function), output_index_(output_index)
|
||||
{
|
||||
}
|
||||
|
||||
Field(const fn::CPPType &type, std::shared_ptr<FieldInput> input) : type_(&type), input_(input)
|
||||
{
|
||||
}
|
||||
|
||||
const fn::CPPType &type() const
|
||||
{
|
||||
BLI_assert(type_ != nullptr);
|
||||
return *type_;
|
||||
}
|
||||
|
||||
bool is_input() const
|
||||
{
|
||||
return input_ != nullptr;
|
||||
}
|
||||
const FieldInput &input() const
|
||||
{
|
||||
BLI_assert(function_ == nullptr);
|
||||
BLI_assert(input_ != nullptr);
|
||||
return *input_;
|
||||
}
|
||||
|
||||
bool is_function() const
|
||||
{
|
||||
return function_ != nullptr;
|
||||
}
|
||||
const FieldFunction &function() const
|
||||
{
|
||||
BLI_assert(function_ != nullptr);
|
||||
BLI_assert(input_ == nullptr);
|
||||
return *function_;
|
||||
}
|
||||
|
||||
int function_output_index() const
|
||||
{
|
||||
BLI_assert(function_ != nullptr);
|
||||
BLI_assert(input_ == nullptr);
|
||||
return output_index_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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, Vector<Field> &&inputs)
|
||||
: function_(std::move(function)), inputs_(std::move(inputs))
|
||||
{
|
||||
}
|
||||
|
||||
Span<Field> inputs() const
|
||||
{
|
||||
return inputs_;
|
||||
}
|
||||
|
||||
const MultiFunction &multi_function() const
|
||||
{
|
||||
return *function_;
|
||||
}
|
||||
};
|
||||
|
||||
class FieldInput {
|
||||
protected:
|
||||
StringRef name_;
|
||||
|
||||
public:
|
||||
FieldInput(StringRef name = "") : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
virtual GVArrayPtr get_varray_generic_context(IndexMask mask) const = 0;
|
||||
|
||||
blender::StringRef name() const
|
||||
{
|
||||
return 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::Span<GMutableSpan> outputs);
|
||||
|
||||
} // namespace blender::fn
|
413
source/blender/functions/FN_multi_function_procedure.hh
Normal file
413
source/blender/functions/FN_multi_function_procedure.hh
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
class MFVariable;
|
||||
class MFInstruction;
|
||||
class MFCallInstruction;
|
||||
class MFBranchInstruction;
|
||||
class MFDestructInstruction;
|
||||
class MFDummyInstruction;
|
||||
class MFReturnInstruction;
|
||||
class MFProcedure;
|
||||
|
||||
enum class MFInstructionType {
|
||||
Call,
|
||||
Branch,
|
||||
Destruct,
|
||||
Dummy,
|
||||
Return,
|
||||
};
|
||||
|
||||
class MFVariable : NonCopyable, NonMovable {
|
||||
private:
|
||||
MFDataType data_type_;
|
||||
Vector<MFInstruction *> users_;
|
||||
std::string name_;
|
||||
int id_;
|
||||
|
||||
friend MFProcedure;
|
||||
friend MFCallInstruction;
|
||||
friend MFBranchInstruction;
|
||||
friend MFDestructInstruction;
|
||||
|
||||
public:
|
||||
MFDataType data_type() const;
|
||||
Span<MFInstruction *> users();
|
||||
|
||||
StringRefNull name() const;
|
||||
void set_name(std::string name);
|
||||
|
||||
int id() const;
|
||||
};
|
||||
|
||||
class MFInstruction : NonCopyable, NonMovable {
|
||||
protected:
|
||||
MFInstructionType type_;
|
||||
Vector<MFInstruction *> prev_;
|
||||
|
||||
friend MFProcedure;
|
||||
friend MFCallInstruction;
|
||||
friend MFBranchInstruction;
|
||||
friend MFDestructInstruction;
|
||||
friend MFDummyInstruction;
|
||||
friend MFReturnInstruction;
|
||||
|
||||
public:
|
||||
MFInstructionType type() const;
|
||||
Span<MFInstruction *> prev();
|
||||
Span<const MFInstruction *> prev() const;
|
||||
};
|
||||
|
||||
class MFCallInstruction : public MFInstruction {
|
||||
private:
|
||||
const MultiFunction *fn_ = nullptr;
|
||||
MFInstruction *next_ = nullptr;
|
||||
MutableSpan<MFVariable *> params_;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
const MultiFunction &fn() const;
|
||||
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
|
||||
void set_param_variable(int param_index, MFVariable *variable);
|
||||
void set_params(Span<MFVariable *> variables);
|
||||
Span<MFVariable *> params();
|
||||
Span<const MFVariable *> params() const;
|
||||
};
|
||||
|
||||
class MFBranchInstruction : public MFInstruction {
|
||||
private:
|
||||
MFVariable *condition_ = nullptr;
|
||||
MFInstruction *branch_true_ = nullptr;
|
||||
MFInstruction *branch_false_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFVariable *condition();
|
||||
const MFVariable *condition() const;
|
||||
void set_condition(MFVariable *variable);
|
||||
|
||||
MFInstruction *branch_true();
|
||||
const MFInstruction *branch_true() const;
|
||||
void set_branch_true(MFInstruction *instruction);
|
||||
|
||||
MFInstruction *branch_false();
|
||||
const MFInstruction *branch_false() const;
|
||||
void set_branch_false(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
class MFDestructInstruction : public MFInstruction {
|
||||
private:
|
||||
MFVariable *variable_ = nullptr;
|
||||
MFInstruction *next_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFVariable *variable();
|
||||
const MFVariable *variable() const;
|
||||
void set_variable(MFVariable *variable);
|
||||
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
class MFDummyInstruction : public MFInstruction {
|
||||
private:
|
||||
MFInstruction *next_ = nullptr;
|
||||
|
||||
friend MFProcedure;
|
||||
|
||||
public:
|
||||
MFInstruction *next();
|
||||
const MFInstruction *next() const;
|
||||
void set_next(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
class MFReturnInstruction : public MFInstruction {
|
||||
};
|
||||
|
||||
/**
|
||||
* Inputs and outputs of the entire procedure network.
|
||||
*/
|
||||
struct MFParameter {
|
||||
MFParamType::InterfaceType type;
|
||||
MFVariable *variable;
|
||||
};
|
||||
|
||||
struct ConstMFParameter {
|
||||
MFParamType::InterfaceType type;
|
||||
const MFVariable *variable;
|
||||
};
|
||||
|
||||
class MFProcedure : NonCopyable, NonMovable {
|
||||
private:
|
||||
LinearAllocator<> allocator_;
|
||||
Vector<MFCallInstruction *> call_instructions_;
|
||||
Vector<MFBranchInstruction *> branch_instructions_;
|
||||
Vector<MFDestructInstruction *> destruct_instructions_;
|
||||
Vector<MFDummyInstruction *> dummy_instructions_;
|
||||
Vector<MFReturnInstruction *> return_instructions_;
|
||||
Vector<MFVariable *> variables_;
|
||||
Vector<MFParameter> params_;
|
||||
MFInstruction *entry_ = nullptr;
|
||||
|
||||
friend class MFProcedureDotExport;
|
||||
|
||||
public:
|
||||
MFProcedure() = default;
|
||||
~MFProcedure();
|
||||
|
||||
MFVariable &new_variable(MFDataType data_type, std::string name = "");
|
||||
MFCallInstruction &new_call_instruction(const MultiFunction &fn);
|
||||
MFBranchInstruction &new_branch_instruction();
|
||||
MFDestructInstruction &new_destruct_instruction();
|
||||
MFDummyInstruction &new_dummy_instruction();
|
||||
MFReturnInstruction &new_return_instruction();
|
||||
|
||||
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
|
||||
|
||||
Span<ConstMFParameter> params() const;
|
||||
|
||||
MFInstruction *entry();
|
||||
const MFInstruction *entry() const;
|
||||
void set_entry(MFInstruction &entry);
|
||||
|
||||
Span<MFVariable *> variables();
|
||||
Span<const MFVariable *> variables() const;
|
||||
|
||||
void assert_valid() const;
|
||||
|
||||
std::string to_dot() const;
|
||||
|
||||
bool validate() const;
|
||||
|
||||
private:
|
||||
bool validate_all_instruction_pointers_set() const;
|
||||
bool validate_all_params_provided() const;
|
||||
bool validate_same_variables_in_one_call() const;
|
||||
bool validate_parameters() const;
|
||||
bool validate_initialization() const;
|
||||
|
||||
struct InitState {
|
||||
bool can_be_initialized = false;
|
||||
bool can_be_uninitialized = false;
|
||||
};
|
||||
|
||||
InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
|
||||
const MFVariable &variable) const;
|
||||
};
|
||||
|
||||
namespace multi_function_procedure_types {
|
||||
using MFVariable = fn::MFVariable;
|
||||
using MFInstruction = fn::MFInstruction;
|
||||
using MFCallInstruction = fn::MFCallInstruction;
|
||||
using MFBranchInstruction = fn::MFBranchInstruction;
|
||||
using MFDestructInstruction = fn::MFDestructInstruction;
|
||||
using MFProcedure = fn::MFProcedure;
|
||||
} // namespace multi_function_procedure_types
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFVariable inline methods.
|
||||
*/
|
||||
|
||||
inline MFDataType MFVariable::data_type() const
|
||||
{
|
||||
return data_type_;
|
||||
}
|
||||
|
||||
inline Span<MFInstruction *> MFVariable::users()
|
||||
{
|
||||
return users_;
|
||||
}
|
||||
|
||||
inline StringRefNull MFVariable::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline int MFVariable::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionType MFInstruction::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline Span<MFInstruction *> MFInstruction::prev()
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
|
||||
inline Span<const MFInstruction *> MFInstruction::prev() const
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFCallInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline const MultiFunction &MFCallInstruction::fn() const
|
||||
{
|
||||
return *fn_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFCallInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFCallInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline Span<MFVariable *> MFCallInstruction::params()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
inline Span<const MFVariable *> MFCallInstruction::params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFBranchInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFVariable *MFBranchInstruction::condition()
|
||||
{
|
||||
return condition_;
|
||||
}
|
||||
|
||||
inline const MFVariable *MFBranchInstruction::condition() const
|
||||
{
|
||||
return condition_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFBranchInstruction::branch_true()
|
||||
{
|
||||
return branch_true_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFBranchInstruction::branch_true() const
|
||||
{
|
||||
return branch_true_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFBranchInstruction::branch_false()
|
||||
{
|
||||
return branch_false_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFBranchInstruction::branch_false() const
|
||||
{
|
||||
return branch_false_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFDestructInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFVariable *MFDestructInstruction::variable()
|
||||
{
|
||||
return variable_;
|
||||
}
|
||||
|
||||
inline const MFVariable *MFDestructInstruction::variable() const
|
||||
{
|
||||
return variable_;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFDestructInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFDestructInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFDummyInstruction inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstruction *MFDummyInstruction::next()
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFDummyInstruction::next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFProcedure inline methods.
|
||||
*/
|
||||
|
||||
inline Span<ConstMFParameter> MFProcedure::params() const
|
||||
{
|
||||
static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
|
||||
return params_.as_span().cast<ConstMFParameter>();
|
||||
}
|
||||
|
||||
inline MFInstruction *MFProcedure::entry()
|
||||
{
|
||||
return entry_;
|
||||
}
|
||||
|
||||
inline const MFInstruction *MFProcedure::entry() const
|
||||
{
|
||||
return entry_;
|
||||
}
|
||||
|
||||
inline Span<MFVariable *> MFProcedure::variables()
|
||||
{
|
||||
return variables_;
|
||||
}
|
||||
|
||||
inline Span<const MFVariable *> MFProcedure::variables() const
|
||||
{
|
||||
return variables_;
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
251
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal file
251
source/blender/functions/FN_multi_function_procedure_builder.hh
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
class MFInstructionCursor {
|
||||
private:
|
||||
MFInstruction *instruction_ = nullptr;
|
||||
/* Only used when it is a branch instruction. */
|
||||
bool branch_output_ = false;
|
||||
/* Only used when instruction is null. */
|
||||
bool is_entry_ = false;
|
||||
|
||||
public:
|
||||
MFInstructionCursor() = default;
|
||||
|
||||
MFInstructionCursor(MFCallInstruction &instruction);
|
||||
MFInstructionCursor(MFDestructInstruction &instruction);
|
||||
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
|
||||
MFInstructionCursor(MFDummyInstruction &instruction);
|
||||
|
||||
static MFInstructionCursor Entry();
|
||||
|
||||
void insert(MFProcedure &procedure, MFInstruction *new_instruction);
|
||||
};
|
||||
|
||||
class MFProcedureBuilder {
|
||||
private:
|
||||
MFProcedure *procedure_ = nullptr;
|
||||
Vector<MFInstructionCursor> cursors_;
|
||||
|
||||
public:
|
||||
struct Branch;
|
||||
struct Loop;
|
||||
|
||||
MFProcedureBuilder(MFProcedure &procedure,
|
||||
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
|
||||
|
||||
MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
|
||||
|
||||
MFProcedureBuilder(Branch &branch);
|
||||
|
||||
void set_cursor(const MFInstructionCursor &cursor);
|
||||
void set_cursor(Span<MFInstructionCursor> cursors);
|
||||
void set_cursor(Span<MFProcedureBuilder *> builders);
|
||||
void set_cursor_after_branch(Branch &branch);
|
||||
void set_cursor_after_loop(Loop &loop);
|
||||
|
||||
void add_destruct(MFVariable &variable);
|
||||
void add_destruct(Span<MFVariable *> variables);
|
||||
|
||||
MFReturnInstruction &add_return();
|
||||
|
||||
Branch add_branch(MFVariable &condition);
|
||||
|
||||
Loop add_loop();
|
||||
void add_loop_continue(Loop &loop);
|
||||
void add_loop_break(Loop &loop);
|
||||
|
||||
MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
|
||||
MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
|
||||
Span<MFVariable *> param_variables);
|
||||
|
||||
Vector<MFVariable *> add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables = {});
|
||||
|
||||
template<int OutputN>
|
||||
std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables = {});
|
||||
|
||||
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
|
||||
MFVariable &add_parameter(MFParamType param_type, std::string name = "");
|
||||
|
||||
MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
|
||||
template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
|
||||
template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
|
||||
|
||||
void add_output_parameter(MFVariable &variable);
|
||||
|
||||
private:
|
||||
void link_to_cursors(MFInstruction *instruction);
|
||||
};
|
||||
|
||||
struct MFProcedureBuilder::Branch {
|
||||
MFProcedureBuilder branch_true;
|
||||
MFProcedureBuilder branch_false;
|
||||
};
|
||||
|
||||
struct MFProcedureBuilder::Loop {
|
||||
MFInstruction *begin = nullptr;
|
||||
MFDummyInstruction *end = nullptr;
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstructionCursor inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
|
||||
bool branch_output)
|
||||
: instruction_(&instruction), branch_output_(branch_output)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor MFInstructionCursor::Entry()
|
||||
{
|
||||
MFInstructionCursor cursor;
|
||||
cursor.is_entry_ = true;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFProcedureBuilder inline methods.
|
||||
*/
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
|
||||
: MFProcedureBuilder(*branch.branch_true.procedure_)
|
||||
{
|
||||
this->set_cursor_after_branch(branch);
|
||||
}
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
|
||||
MFInstructionCursor initial_cursor)
|
||||
: procedure_(&procedure), cursors_({initial_cursor})
|
||||
{
|
||||
}
|
||||
|
||||
inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
|
||||
: MFProcedureBuilder(*builders[0]->procedure_)
|
||||
{
|
||||
this->set_cursor(builders);
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
|
||||
{
|
||||
cursors_ = {cursor};
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
|
||||
{
|
||||
cursors_ = cursors;
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
|
||||
{
|
||||
this->set_cursor({&branch.branch_false, &branch.branch_true});
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
|
||||
{
|
||||
this->set_cursor(MFInstructionCursor{*loop.end});
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
|
||||
{
|
||||
cursors_.clear();
|
||||
for (MFProcedureBuilder *builder : builders) {
|
||||
cursors_.extend(builder->cursors_);
|
||||
}
|
||||
}
|
||||
|
||||
template<int OutputN>
|
||||
inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
|
||||
const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
|
||||
{
|
||||
Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
|
||||
BLI_assert(output_variables.size() == OutputN);
|
||||
|
||||
std::array<MFVariable *, OutputN> output_array;
|
||||
initialized_copy_n(output_variables.data(), OutputN, output_array.data());
|
||||
return output_array;
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
|
||||
MFVariable &variable)
|
||||
{
|
||||
procedure_->add_parameter(interface_type, variable);
|
||||
}
|
||||
|
||||
inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
|
||||
{
|
||||
MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
|
||||
this->add_parameter(param_type.interface_type(), variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
|
||||
{
|
||||
return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
|
||||
{
|
||||
this->add_parameter(MFParamType::Output, variable);
|
||||
}
|
||||
|
||||
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
|
||||
{
|
||||
for (MFInstructionCursor &cursor : cursors_) {
|
||||
cursor.insert(*procedure_, instruction);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "FN_multi_function_procedure.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
class MFProcedureExecutor : public MultiFunction {
|
||||
private:
|
||||
MFSignature signature_;
|
||||
const MFProcedure &procedure_;
|
||||
|
||||
public:
|
||||
MFProcedureExecutor(std::string name, const MFProcedure &procedure);
|
||||
|
||||
void call(IndexMask mask, MFParams params, MFContext context) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::fn
|
346
source/blender/functions/intern/field.cc
Normal file
346
source/blender/functions/intern/field.cc
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
/**
|
||||
* TODO: There might be a more obvious way to implement this, or we might end up with
|
||||
* a separate map for functions and inputs anyway, so we could just remove it.
|
||||
*/
|
||||
struct InputOrFunction {
|
||||
const void *ptr;
|
||||
|
||||
public:
|
||||
InputOrFunction(const blender::fn::FieldFunction &function) : ptr(&function)
|
||||
{
|
||||
}
|
||||
InputOrFunction(const blender::fn::FieldInput &input) : ptr(&input)
|
||||
{
|
||||
}
|
||||
InputOrFunction(const blender::fn::Field &field) /* Maybe this is too clever. */
|
||||
{
|
||||
if (field.is_function()) {
|
||||
ptr = &field.function();
|
||||
}
|
||||
else {
|
||||
ptr = &field.input();
|
||||
}
|
||||
}
|
||||
friend bool operator==(const InputOrFunction &a, const InputOrFunction &b)
|
||||
{
|
||||
return a.ptr == b.ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct blender::DefaultHash<InputOrFunction> {
|
||||
uint64_t operator()(const InputOrFunction &value) const
|
||||
{
|
||||
return DefaultHash<const void *>{}(value.ptr);
|
||||
}
|
||||
};
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/**
|
||||
* TODO: This exists because it seemed helpful for the procedure creation to be able to store
|
||||
* mutable data for each input or function output. That still may be helpful in the future, but
|
||||
* currently it isn't useful.
|
||||
*/
|
||||
struct FieldVariable {
|
||||
MFVariable *mf_variable;
|
||||
FieldVariable(MFVariable &variable) : mf_variable(&variable)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A map to hold the output variables for each function output or input so they can be reused.
|
||||
*/
|
||||
using VariableMap = Map<InputOrFunction, Vector<FieldVariable>>;
|
||||
|
||||
/**
|
||||
* A map of the computed inputs for all of a field system's inputs, to avoid creating duplicates.
|
||||
* Usually virtual arrays are just references, but sometimes they can be heavier as well.
|
||||
*/
|
||||
using ComputedInputMap = Map<const MFVariable *, GVArrayPtr>;
|
||||
|
||||
static FieldVariable &get_field_variable(const Field &field, VariableMap &unique_variables)
|
||||
{
|
||||
if (field.is_input()) {
|
||||
const FieldInput &input = field.input();
|
||||
return unique_variables.lookup(input).first();
|
||||
}
|
||||
const FieldFunction &function = field.function();
|
||||
MutableSpan<FieldVariable> function_outputs = unique_variables.lookup(function);
|
||||
return function_outputs[field.function_output_index()];
|
||||
}
|
||||
|
||||
static const FieldVariable &get_field_variable(const Field &field,
|
||||
const VariableMap &unique_variables)
|
||||
{
|
||||
if (field.is_input()) {
|
||||
const FieldInput &input = field.input();
|
||||
return unique_variables.lookup(input).first();
|
||||
}
|
||||
const FieldFunction &function = field.function();
|
||||
Span<FieldVariable> function_outputs = unique_variables.lookup(function);
|
||||
return function_outputs[field.function_output_index()];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Merge duplicate input nodes, not just fields pointing to the same FieldInput.
|
||||
*/
|
||||
static void add_variables_for_input(const Field &field,
|
||||
Stack<const Field *> &fields_to_visit,
|
||||
MFProcedureBuilder &builder,
|
||||
VariableMap &unique_variables)
|
||||
{
|
||||
fields_to_visit.pop();
|
||||
const FieldInput &input = field.input();
|
||||
MFVariable &variable = builder.add_input_parameter(MFDataType::ForSingle(field.type()),
|
||||
input.name());
|
||||
unique_variables.add(input, {variable});
|
||||
}
|
||||
|
||||
static void add_variables_for_function(const Field &field,
|
||||
Stack<const Field *> &fields_to_visit,
|
||||
MFProcedureBuilder &builder,
|
||||
VariableMap &unique_variables)
|
||||
{
|
||||
const FieldFunction &function = field.function();
|
||||
for (const Field &input_field : function.inputs()) {
|
||||
if (!unique_variables.contains(input_field)) {
|
||||
/* The field for this input hasn't been handled yet. Handle it now, so that we know all
|
||||
* of this field's function inputs already have variables. TODO: Verify that this is the
|
||||
* best way to do a depth first traversal. These extra lookups don't seem ideal. */
|
||||
fields_to_visit.push(&input_field);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fields_to_visit.pop();
|
||||
|
||||
Vector<MFVariable *> inputs;
|
||||
Set<FieldVariable *> unique_inputs;
|
||||
for (const Field &input_field : function.inputs()) {
|
||||
FieldVariable &input = get_field_variable(input_field, unique_variables);
|
||||
unique_inputs.add(&input);
|
||||
inputs.append(input.mf_variable);
|
||||
}
|
||||
|
||||
Vector<MFVariable *> outputs = builder.add_call(function.multi_function(), inputs);
|
||||
Vector<FieldVariable> &unique_outputs = unique_variables.lookup_or_add(function, {});
|
||||
for (MFVariable *output : outputs) {
|
||||
unique_outputs.append(*output);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_unique_variables(const Span<Field> fields,
|
||||
MFProcedureBuilder &builder,
|
||||
VariableMap &unique_variables)
|
||||
{
|
||||
Stack<const Field *> fields_to_visit;
|
||||
for (const Field &field : fields) {
|
||||
fields_to_visit.push(&field);
|
||||
}
|
||||
|
||||
while (!fields_to_visit.is_empty()) {
|
||||
const Field &field = *fields_to_visit.peek();
|
||||
if (unique_variables.contains(field)) {
|
||||
fields_to_visit.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.is_input()) {
|
||||
add_variables_for_input(field, fields_to_visit, builder, unique_variables);
|
||||
}
|
||||
else {
|
||||
add_variables_for_function(field, fields_to_visit, builder, unique_variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add destruct calls to the procedure so that internal variables and inputs are destructed before
|
||||
* the procedure finishes. Currently this just adds all of the destructs at the end. That is not
|
||||
* optimal, but properly ordering destructs should be combined with reordering function calls to
|
||||
* use variables more optimally.
|
||||
*/
|
||||
static void add_destructs(const Span<Field> fields,
|
||||
MFProcedureBuilder &builder,
|
||||
VariableMap &unique_variables)
|
||||
{
|
||||
Set<MFVariable *> destructed_variables;
|
||||
Set<FieldVariable *> outputs;
|
||||
for (const Field &field : fields) {
|
||||
/* Currently input fields are handled separately in the evaluator. */
|
||||
BLI_assert(!field.is_input());
|
||||
outputs.add(&get_field_variable(field, unique_variables));
|
||||
}
|
||||
|
||||
Stack<const Field *> fields_to_visit;
|
||||
for (const Field &field : fields) {
|
||||
fields_to_visit.push(&field);
|
||||
}
|
||||
|
||||
while (!fields_to_visit.is_empty()) {
|
||||
const Field &field = *fields_to_visit.pop();
|
||||
if (field.is_function()) {
|
||||
const FieldFunction &function = field.function();
|
||||
for (const Field &input_field : function.inputs()) {
|
||||
fields_to_visit.push(&input_field);
|
||||
}
|
||||
}
|
||||
|
||||
FieldVariable &variable = get_field_variable(field, unique_variables);
|
||||
|
||||
/* Don't destruct the same variable twice. */
|
||||
if (destructed_variables.contains(variable.mf_variable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't destruct the variable if it is used as an output parameter. */
|
||||
if (outputs.contains(&variable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.add_destruct(*variable.mf_variable);
|
||||
destructed_variables.add_new(variable.mf_variable);
|
||||
}
|
||||
}
|
||||
|
||||
static void build_procedure(const Span<Field> fields,
|
||||
MFProcedure &procedure,
|
||||
VariableMap &unique_variables)
|
||||
{
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
add_unique_variables(fields, builder, unique_variables);
|
||||
|
||||
add_destructs(fields, builder, unique_variables);
|
||||
|
||||
builder.add_return();
|
||||
|
||||
for (const Field &field : fields) {
|
||||
MFVariable &input = *get_field_variable(field, unique_variables).mf_variable;
|
||||
builder.add_output_parameter(input);
|
||||
}
|
||||
|
||||
std::cout << procedure.to_dot();
|
||||
|
||||
BLI_assert(procedure.validate());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Maybe this doesn't add inputs in the same order as the the unique
|
||||
* variable traversal. Add a test for that and fix it if it doesn't work.
|
||||
*/
|
||||
static void gather_inputs(const Span<Field> fields,
|
||||
const VariableMap &unique_variables,
|
||||
const IndexMask mask,
|
||||
MFParamsBuilder ¶ms,
|
||||
Vector<GVArrayPtr> &r_inputs)
|
||||
{
|
||||
Set<const MFVariable *> computed_inputs;
|
||||
Stack<const Field *> fields_to_visit;
|
||||
for (const Field &field : fields) {
|
||||
fields_to_visit.push(&field);
|
||||
}
|
||||
|
||||
while (!fields_to_visit.is_empty()) {
|
||||
const Field &field = *fields_to_visit.pop();
|
||||
if (field.is_input()) {
|
||||
const FieldInput &input = field.input();
|
||||
const FieldVariable &variable = get_field_variable(field, unique_variables);
|
||||
if (!computed_inputs.contains(variable.mf_variable)) {
|
||||
GVArrayPtr data = input.get_varray_generic_context(mask);
|
||||
computed_inputs.add_new(variable.mf_variable);
|
||||
params.add_readonly_single_input(*data, input.name());
|
||||
r_inputs.append(std::move(data));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const FieldFunction &function = field.function();
|
||||
for (const Field &input_field : function.inputs()) {
|
||||
fields_to_visit.push(&input_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_outputs(MFParamsBuilder ¶ms, Span<GMutableSpan> outputs)
|
||||
{
|
||||
for (const int i : outputs.index_range()) {
|
||||
params.add_uninitialized_single_output(outputs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void evaluate_non_input_fields(const Span<Field> fields,
|
||||
const IndexMask mask,
|
||||
const Span<GMutableSpan> outputs)
|
||||
{
|
||||
MFProcedure procedure;
|
||||
VariableMap unique_variables;
|
||||
build_procedure(fields, procedure, unique_variables);
|
||||
|
||||
MFProcedureExecutor executor{"Evaluate Field", procedure};
|
||||
MFParamsBuilder params{executor, mask.min_array_size()};
|
||||
MFContextBuilder context;
|
||||
|
||||
Vector<GVArrayPtr> inputs;
|
||||
gather_inputs(fields, unique_variables, mask, params, inputs);
|
||||
|
||||
add_outputs(params, outputs);
|
||||
|
||||
executor.call(mask, params, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate more than one prodecure at a time, since often intermediate results will be shared
|
||||
* between multiple final results, and the procedure evaluator can optimize for this case.
|
||||
*/
|
||||
void evaluate_fields(const Span<Field> fields,
|
||||
const IndexMask mask,
|
||||
const Span<GMutableSpan> outputs)
|
||||
{
|
||||
BLI_assert(fields.size() == outputs.size());
|
||||
|
||||
/* Process fields that just connect to inputs separately, since otherwise we need a special
|
||||
* case to avoid sharing the same variable for an input and output parameters elsewhere.
|
||||
* TODO: It would be nice if there were a more elegant way to handle this, rather than a
|
||||
* separate step here, but I haven't thought of anything yet. */
|
||||
Vector<Field> non_input_fields{fields};
|
||||
Vector<GMutableSpan> non_input_outputs{outputs};
|
||||
for (int i = fields.size() - 1; i >= 0; i--) {
|
||||
if (non_input_fields[i].is_input()) {
|
||||
non_input_fields[i].input().get_varray_generic_context(mask)->materialize(mask,
|
||||
outputs[i].data());
|
||||
|
||||
non_input_fields.remove_and_reorder(i);
|
||||
non_input_outputs.remove_and_reorder(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!non_input_fields.is_empty()) {
|
||||
evaluate_non_input_fields(non_input_fields, mask, non_input_outputs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
808
source/blender/functions/intern/multi_function_procedure.cc
Normal file
808
source/blender/functions/intern/multi_function_procedure.cc
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* 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 "FN_multi_function_procedure.hh"
|
||||
|
||||
#include "BLI_dot_export.hh"
|
||||
#include "BLI_stack.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFVariable::set_name(std::string name)
|
||||
{
|
||||
name_ = std::move(name);
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
|
||||
{
|
||||
if (params_[param_index] != nullptr) {
|
||||
params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
|
||||
variable->users_.append(this);
|
||||
}
|
||||
params_[param_index] = variable;
|
||||
}
|
||||
|
||||
void MFCallInstruction::set_params(Span<MFVariable *> variables)
|
||||
{
|
||||
BLI_assert(variables.size() == params_.size());
|
||||
for (const int i : variables.index_range()) {
|
||||
this->set_param_variable(i, variables[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_condition(MFVariable *variable)
|
||||
{
|
||||
if (condition_ != nullptr) {
|
||||
condition_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
condition_ = variable;
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_true_ != nullptr) {
|
||||
branch_true_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
branch_true_ = instruction;
|
||||
}
|
||||
|
||||
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_false_ != nullptr) {
|
||||
branch_false_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
branch_false_ = instruction;
|
||||
}
|
||||
|
||||
void MFDestructInstruction::set_variable(MFVariable *variable)
|
||||
{
|
||||
if (variable_ != nullptr) {
|
||||
variable_->users_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (variable != nullptr) {
|
||||
variable->users_.append(this);
|
||||
}
|
||||
variable_ = variable;
|
||||
}
|
||||
|
||||
void MFDestructInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
void MFDummyInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
|
||||
MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
|
||||
{
|
||||
MFVariable &variable = *allocator_.construct<MFVariable>().release();
|
||||
variable.name_ = std::move(name);
|
||||
variable.data_type_ = data_type;
|
||||
variable.id_ = variables_.size();
|
||||
variables_.append(&variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
|
||||
{
|
||||
MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Call;
|
||||
instruction.fn_ = &fn;
|
||||
instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
|
||||
instruction.params_.fill(nullptr);
|
||||
call_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFBranchInstruction &MFProcedure::new_branch_instruction()
|
||||
{
|
||||
MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Branch;
|
||||
branch_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFDestructInstruction &MFProcedure::new_destruct_instruction()
|
||||
{
|
||||
MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Destruct;
|
||||
destruct_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFDummyInstruction &MFProcedure::new_dummy_instruction()
|
||||
{
|
||||
MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Dummy;
|
||||
dummy_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFReturnInstruction &MFProcedure::new_return_instruction()
|
||||
{
|
||||
MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
|
||||
instruction.type_ = MFInstructionType::Return;
|
||||
return_instructions_.append(&instruction);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
|
||||
{
|
||||
params_.append({interface_type, &variable});
|
||||
}
|
||||
|
||||
void MFProcedure::set_entry(MFInstruction &entry)
|
||||
{
|
||||
entry_ = &entry;
|
||||
}
|
||||
|
||||
void MFProcedure::assert_valid() const
|
||||
{
|
||||
/**
|
||||
* - Non parameter variables are destructed.
|
||||
* - At every instruction, every variable is either initialized or uninitialized.
|
||||
* - Input and mutable parameters of call instructions are initialized.
|
||||
* - Condition of branch instruction is initialized.
|
||||
* - Output parameters of call instructions are not initialized.
|
||||
* - Input parameters are never destructed.
|
||||
* - Mutable and output parameteres are initialized on every exit.
|
||||
* - No aliasing issues in call instructions (can happen when variable is used more than once).
|
||||
*/
|
||||
}
|
||||
|
||||
MFProcedure::~MFProcedure()
|
||||
{
|
||||
for (MFCallInstruction *instruction : call_instructions_) {
|
||||
instruction->~MFCallInstruction();
|
||||
}
|
||||
for (MFBranchInstruction *instruction : branch_instructions_) {
|
||||
instruction->~MFBranchInstruction();
|
||||
}
|
||||
for (MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
instruction->~MFDestructInstruction();
|
||||
}
|
||||
for (MFDummyInstruction *instruction : dummy_instructions_) {
|
||||
instruction->~MFDummyInstruction();
|
||||
}
|
||||
for (MFReturnInstruction *instruction : return_instructions_) {
|
||||
instruction->~MFReturnInstruction();
|
||||
}
|
||||
for (MFVariable *variable : variables_) {
|
||||
variable->~MFVariable();
|
||||
}
|
||||
}
|
||||
|
||||
bool MFProcedure::validate() const
|
||||
{
|
||||
if (entry_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_instruction_pointers_set()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_all_params_provided()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_same_variables_in_one_call()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_parameters()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->validate_initialization()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_all_instruction_pointers_set() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->branch_true_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (instruction->branch_false_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDummyInstruction *instruction : dummy_instructions_) {
|
||||
if (instruction->next_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_all_params_provided() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
for (const MFVariable *variable : instruction->params_) {
|
||||
if (variable == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
if (instruction->condition_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
if (instruction->variable_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_same_variables_in_one_call() const
|
||||
{
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable *variable = instruction->params_[param_index];
|
||||
for (const int other_param_index : fn.param_indices()) {
|
||||
if (other_param_index == param_index) {
|
||||
continue;
|
||||
}
|
||||
const MFVariable *other_variable = instruction->params_[other_param_index];
|
||||
if (other_variable != variable) {
|
||||
continue;
|
||||
}
|
||||
if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
|
||||
/* When a variable is used as mutable or output parameter, it can only be used once. */
|
||||
return false;
|
||||
}
|
||||
const MFParamType other_param_type = fn.param_type(other_param_index);
|
||||
/* A variable is allowed to be used as input more than once. */
|
||||
if (other_param_type.interface_type() != MFParamType::Input) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_parameters() const
|
||||
{
|
||||
Set<const MFVariable *> variables;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
/* One variable cannot be used as multiple parameters. */
|
||||
if (!variables.add(param.variable)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MFProcedure::validate_initialization() const
|
||||
{
|
||||
/* TODO: Issue warning when it maybe wrongly initialized. */
|
||||
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
||||
const MFVariable &variable = *instruction->variable_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
||||
const MFVariable &variable = *instruction->condition_;
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const MFCallInstruction *instruction : call_instructions_) {
|
||||
const MultiFunction &fn = *instruction->fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable &variable = *instruction->params_[param_index];
|
||||
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
||||
variable);
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input:
|
||||
case MFParamType::Mutable: {
|
||||
if (!state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
if (!state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<const MFVariable *> variables_that_should_be_initialized_on_return;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
||||
variables_that_should_be_initialized_on_return.add_new(param.variable);
|
||||
}
|
||||
}
|
||||
for (const MFReturnInstruction *instruction : return_instructions_) {
|
||||
for (const MFVariable *variable : variables_) {
|
||||
const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
|
||||
*variable);
|
||||
if (variables_that_should_be_initialized_on_return.contains(variable)) {
|
||||
if (!init_state.can_be_initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!init_state.can_be_uninitialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
|
||||
const MFInstruction &target_instruction, const MFVariable &target_variable) const
|
||||
{
|
||||
InitState state;
|
||||
|
||||
auto check_entry_instruction = [&]() {
|
||||
bool caller_initialized_variable = false;
|
||||
for (const MFParameter ¶m : params_) {
|
||||
if (param.variable == &target_variable) {
|
||||
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
||||
caller_initialized_variable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (caller_initialized_variable) {
|
||||
state.can_be_initialized = true;
|
||||
}
|
||||
else {
|
||||
state.can_be_uninitialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (&target_instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
|
||||
Set<const MFInstruction *> checked_instructions;
|
||||
Stack<const MFInstruction *> instructions_to_check;
|
||||
instructions_to_check.push_multiple(target_instruction.prev_);
|
||||
|
||||
while (!instructions_to_check.is_empty()) {
|
||||
const MFInstruction &instruction = *instructions_to_check.pop();
|
||||
if (!checked_instructions.add(&instruction)) {
|
||||
/* Skip if the instruction has been checked already. */
|
||||
continue;
|
||||
}
|
||||
bool state_modified = false;
|
||||
switch (instruction.type_) {
|
||||
case MFInstructionType::Call: {
|
||||
const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
|
||||
instruction);
|
||||
const MultiFunction &fn = *call_instruction.fn_;
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
if (call_instruction.params_[param_index] == &target_variable) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
if (param_type.interface_type() == MFParamType::Output) {
|
||||
state.can_be_initialized = true;
|
||||
state_modified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
const MFDestructInstruction &destruct_instruction =
|
||||
static_cast<const MFDestructInstruction &>(instruction);
|
||||
if (destruct_instruction.variable_ == &target_variable) {
|
||||
state.can_be_uninitialized = true;
|
||||
state_modified = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch:
|
||||
case MFInstructionType::Dummy:
|
||||
case MFInstructionType::Return: {
|
||||
/* These instruction types don't change the initialization state of variables. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state_modified) {
|
||||
if (&instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
instructions_to_check.push_multiple(instruction.prev_);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
class MFProcedureDotExport {
|
||||
private:
|
||||
const MFProcedure &procedure_;
|
||||
dot::DirectedGraph digraph_;
|
||||
Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
|
||||
Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
|
||||
|
||||
public:
|
||||
MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
|
||||
{
|
||||
}
|
||||
|
||||
std::string generate()
|
||||
{
|
||||
this->create_nodes();
|
||||
this->create_edges();
|
||||
return digraph_.to_dot_string();
|
||||
}
|
||||
|
||||
void create_nodes()
|
||||
{
|
||||
Vector<const MFInstruction *> all_instructions;
|
||||
auto add_instructions = [&](auto instructions) {
|
||||
all_instructions.extend(instructions.begin(), instructions.end());
|
||||
};
|
||||
add_instructions(procedure_.call_instructions_);
|
||||
add_instructions(procedure_.branch_instructions_);
|
||||
add_instructions(procedure_.destruct_instructions_);
|
||||
add_instructions(procedure_.dummy_instructions_);
|
||||
add_instructions(procedure_.return_instructions_);
|
||||
|
||||
Set<const MFInstruction *> handled_instructions;
|
||||
|
||||
for (const MFInstruction *representative : all_instructions) {
|
||||
if (handled_instructions.contains(representative)) {
|
||||
continue;
|
||||
}
|
||||
Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
|
||||
*representative);
|
||||
std::stringstream ss;
|
||||
ss << "<";
|
||||
|
||||
for (const MFInstruction *current : block_instructions) {
|
||||
handled_instructions.add_new(current);
|
||||
switch (current->type()) {
|
||||
case MFInstructionType::Call: {
|
||||
this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << R"(<br align="left" />)";
|
||||
}
|
||||
ss << ">";
|
||||
|
||||
dot::Node &dot_node = digraph_.new_node(ss.str());
|
||||
dot_node.set_shape(dot::Attr_shape::Rectangle);
|
||||
dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
|
||||
dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
|
||||
}
|
||||
}
|
||||
|
||||
void create_edges()
|
||||
{
|
||||
auto create_edge = [&](dot::Node &from_node,
|
||||
const MFInstruction *to_instruction) -> dot::DirectedEdge & {
|
||||
if (to_instruction == nullptr) {
|
||||
dot::Node &to_node = digraph_.new_node("missing");
|
||||
to_node.set_shape(dot::Attr_shape::Diamond);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
}
|
||||
dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
|
||||
return digraph_.new_edge(from_node, to_node);
|
||||
};
|
||||
|
||||
for (auto item : dot_nodes_by_end_.items()) {
|
||||
const MFInstruction &from_instruction = *item.key;
|
||||
dot::Node &from_node = *item.value;
|
||||
switch (from_instruction.type()) {
|
||||
case MFInstructionType::Call: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFCallInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFDestructInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
const MFInstruction *to_instruction =
|
||||
static_cast<const MFDummyInstruction &>(from_instruction).next();
|
||||
create_edge(from_node, to_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
|
||||
from_instruction);
|
||||
const MFInstruction *to_true_instruction = branch_instruction.branch_true();
|
||||
const MFInstruction *to_false_instruction = branch_instruction.branch_false();
|
||||
create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
|
||||
create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dot::Node &entry_node = this->create_entry_node();
|
||||
create_edge(entry_node, procedure_.entry());
|
||||
}
|
||||
|
||||
bool has_to_be_block_begin(const MFInstruction &instruction)
|
||||
{
|
||||
if (procedure_.entry() == &instruction) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
|
||||
{
|
||||
const MFInstruction *current = &representative;
|
||||
while (!this->has_to_be_block_begin(*current)) {
|
||||
current = current->prev()[0];
|
||||
if (current == &representative) {
|
||||
/* There is a loop without entry or exit, just break it up here. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *current;
|
||||
}
|
||||
|
||||
const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
|
||||
const MFInstruction &block_begin)
|
||||
{
|
||||
const MFInstruction *next = nullptr;
|
||||
switch (instruction.type()) {
|
||||
case MFInstructionType::Call: {
|
||||
next = static_cast<const MFCallInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
next = static_cast<const MFDestructInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
next = static_cast<const MFDummyInstruction &>(instruction).next();
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return:
|
||||
case MFInstructionType::Branch: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (next == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (next == &block_begin) {
|
||||
return nullptr;
|
||||
}
|
||||
if (this->has_to_be_block_begin(*next)) {
|
||||
return nullptr;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
|
||||
{
|
||||
Vector<const MFInstruction *> instructions;
|
||||
const MFInstruction &begin = this->get_first_instruction_in_block(representative);
|
||||
for (const MFInstruction *current = &begin; current != nullptr;
|
||||
current = this->get_next_instruction_in_block(*current, begin)) {
|
||||
instructions.append(current);
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void variable_to_string(const MFVariable *variable, std::stringstream &ss)
|
||||
{
|
||||
if (variable == nullptr) {
|
||||
ss << "null";
|
||||
}
|
||||
else {
|
||||
ss << "$" << variable->id();
|
||||
if (!variable->name().is_empty()) {
|
||||
ss << "(" << variable->name() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_name_format(StringRef name, std::stringstream &ss)
|
||||
{
|
||||
ss << name;
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
const MultiFunction &fn = instruction.fn();
|
||||
this->instruction_name_format(fn.name() + ": ", ss);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
const MFVariable *variable = instruction.params()[param_index];
|
||||
ss << R"(<font color="grey30">)";
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input: {
|
||||
ss << "in";
|
||||
break;
|
||||
}
|
||||
case MFParamType::Mutable: {
|
||||
ss << "mut";
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
ss << "out";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << " </font> ";
|
||||
variable_to_string(variable, ss);
|
||||
if (param_index < fn.param_amount() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Destruct ", ss);
|
||||
variable_to_string(instruction.variable(), ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Dummy ", ss);
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Return ", ss);
|
||||
|
||||
Vector<ConstMFParameter> outgoing_parameters;
|
||||
for (const ConstMFParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
||||
outgoing_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : outgoing_parameters.index_range()) {
|
||||
const ConstMFParameter ¶m = outgoing_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < outgoing_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
|
||||
{
|
||||
instruction_name_format("Branch ", ss);
|
||||
variable_to_string(instruction.condition(), ss);
|
||||
}
|
||||
|
||||
dot::Node &create_entry_node()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Entry: ";
|
||||
Vector<ConstMFParameter> incoming_parameters;
|
||||
for (const ConstMFParameter ¶m : procedure_.params()) {
|
||||
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
||||
incoming_parameters.append(param);
|
||||
}
|
||||
}
|
||||
for (const int param_index : incoming_parameters.index_range()) {
|
||||
const ConstMFParameter ¶m = incoming_parameters[param_index];
|
||||
variable_to_string(param.variable, ss);
|
||||
if (param_index < incoming_parameters.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
dot::Node &node = digraph_.new_node(ss.str());
|
||||
node.set_shape(dot::Attr_shape::Ellipse);
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
std::string MFProcedure::to_dot() const
|
||||
{
|
||||
MFProcedureDotExport dot_export{*this};
|
||||
return dot_export.generate();
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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 "FN_multi_function_procedure_builder.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
|
||||
{
|
||||
if (instruction_ == nullptr) {
|
||||
if (is_entry_) {
|
||||
procedure.set_entry(*new_instruction);
|
||||
}
|
||||
else {
|
||||
/* The cursors points at nothing, nothing to do. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (instruction_->type()) {
|
||||
case MFInstructionType::Call: {
|
||||
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
|
||||
instruction_);
|
||||
if (branch_output_) {
|
||||
branch_instruction.set_branch_true(new_instruction);
|
||||
}
|
||||
else {
|
||||
branch_instruction.set_branch_false(new_instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
/* It shouldn't be possible to build a cursor that points to a return instruction. */
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_destruct(MFVariable &variable)
|
||||
{
|
||||
MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
|
||||
instruction.set_variable(&variable);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {MFInstructionCursor{instruction}};
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
|
||||
{
|
||||
for (MFVariable *variable : variables) {
|
||||
this->add_destruct(*variable);
|
||||
}
|
||||
}
|
||||
|
||||
MFReturnInstruction &MFProcedureBuilder::add_return()
|
||||
{
|
||||
MFReturnInstruction &instruction = procedure_->new_return_instruction();
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
|
||||
{
|
||||
MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
|
||||
this->link_to_cursors(&instruction);
|
||||
cursors_ = {MFInstructionCursor{instruction}};
|
||||
return instruction;
|
||||
}
|
||||
|
||||
MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
|
||||
const MultiFunction &fn, Span<MFVariable *> param_variables)
|
||||
{
|
||||
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
instruction.set_params(param_variables);
|
||||
return instruction;
|
||||
}
|
||||
|
||||
Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
|
||||
Span<MFVariable *> input_and_mutable_variables)
|
||||
{
|
||||
Vector<MFVariable *> output_variables;
|
||||
MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
|
||||
for (const int param_index : fn.param_indices()) {
|
||||
const MFParamType param_type = fn.param_type(param_index);
|
||||
switch (param_type.interface_type()) {
|
||||
case MFParamType::Input:
|
||||
case MFParamType::Mutable: {
|
||||
MFVariable *variable = input_and_mutable_variables.first();
|
||||
instruction.set_param_variable(param_index, variable);
|
||||
input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
|
||||
break;
|
||||
}
|
||||
case MFParamType::Output: {
|
||||
MFVariable &variable = procedure_->new_variable(param_type.data_type(),
|
||||
fn.param_name(param_index));
|
||||
instruction.set_param_variable(param_index, &variable);
|
||||
output_variables.append(&variable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* All passed in variables should have been dropped in the loop above. */
|
||||
BLI_assert(input_and_mutable_variables.is_empty());
|
||||
return output_variables;
|
||||
}
|
||||
|
||||
MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
|
||||
{
|
||||
MFBranchInstruction &instruction = procedure_->new_branch_instruction();
|
||||
instruction.set_condition(&condition);
|
||||
this->link_to_cursors(&instruction);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
|
||||
Branch branch{*procedure_, *procedure_};
|
||||
branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
|
||||
branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
|
||||
return branch;
|
||||
}
|
||||
|
||||
MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
|
||||
{
|
||||
MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
|
||||
MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
|
||||
this->link_to_cursors(&loop_begin);
|
||||
cursors_ = {MFInstructionCursor{loop_begin}};
|
||||
|
||||
Loop loop;
|
||||
loop.begin = &loop_begin;
|
||||
loop.end = &loop_end;
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_loop_continue(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.begin);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_loop_break(Loop &loop)
|
||||
{
|
||||
this->link_to_cursors(loop.end);
|
||||
/* Clear cursors because this builder ends here. */
|
||||
cursors_.clear();
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
1188
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
1188
source/blender/functions/intern/multi_function_procedure_executor.cc
Normal file
File diff suppressed because it is too large
Load Diff
225
source/blender/functions/tests/FN_field_test.cc
Normal file
225
source/blender/functions/tests/FN_field_test.cc
Normal file
@@ -0,0 +1,225 @@
|
||||
/* 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, ConstantFunction)
|
||||
{
|
||||
/* TODO: Figure out how to not use another "FieldFunction(" inside of std::make_shared. */
|
||||
Field constant_field{CPPType::get<int>(),
|
||||
std::make_shared<FieldFunction>(
|
||||
FieldFunction(std::make_unique<CustomMF_Constant<int>>(10), {})),
|
||||
0};
|
||||
|
||||
Array<int> result(4);
|
||||
GMutableSpan result_generic(result.as_mutable_span());
|
||||
evaluate_fields({constant_field}, IndexMask(IndexRange(4)), {result_generic});
|
||||
|
||||
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("Index")
|
||||
{
|
||||
}
|
||||
|
||||
GVArrayPtr get_varray_generic_context(IndexMask mask) const final
|
||||
{
|
||||
auto index_func = [](int i) { return i; };
|
||||
return std::make_unique<
|
||||
GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
|
||||
mask.min_array_size(), mask.min_array_size(), index_func);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(field, VArrayInput)
|
||||
{
|
||||
Field index_field{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
|
||||
Array<int> result_1(4);
|
||||
GMutableSpan result_generic_1(result_1.as_mutable_span());
|
||||
evaluate_fields({index_field}, IndexMask(IndexRange(4)), {result_generic_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);
|
||||
|
||||
/* Evaluate a second time, just to test that the first didn't break anything. */
|
||||
Array<int> result_2(10);
|
||||
GMutableSpan result_generic_2(result_2.as_mutable_span());
|
||||
evaluate_fields({index_field}, {2, 4, 6, 8}, {result_generic_2});
|
||||
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(field, VArrayInputMultipleOutputs)
|
||||
{
|
||||
std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>();
|
||||
Field field_1{CPPType::get<int>(), index_input};
|
||||
Field field_2{CPPType::get<int>(), index_input};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
GMutableSpan result_generic_1(result_1.as_mutable_span());
|
||||
GMutableSpan result_generic_2(result_2.as_mutable_span());
|
||||
|
||||
evaluate_fields({field_1, field_2}, {2, 4, 6, 8}, {result_generic_1, result_generic_2});
|
||||
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(field, InputAndFunction)
|
||||
{
|
||||
Field index_field{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
|
||||
"add", [](int a, int b) { return a + b; });
|
||||
Field output_field{CPPType::get<int>(),
|
||||
std::make_shared<FieldFunction>(
|
||||
FieldFunction(std::move(add_fn), {index_field, index_field})),
|
||||
0};
|
||||
|
||||
Array<int> result(10);
|
||||
GMutableSpan result_generic(result.as_mutable_span());
|
||||
evaluate_fields({output_field}, {2, 4, 6, 8}, {result_generic});
|
||||
EXPECT_EQ(result[2], 4);
|
||||
EXPECT_EQ(result[4], 8);
|
||||
EXPECT_EQ(result[6], 12);
|
||||
EXPECT_EQ(result[8], 16);
|
||||
}
|
||||
|
||||
TEST(field, TwoFunctions)
|
||||
{
|
||||
Field index_field{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
|
||||
"add", [](int a, int b) { return a + b; });
|
||||
Field add_field{CPPType::get<int>(),
|
||||
std::make_shared<FieldFunction>(
|
||||
FieldFunction(std::move(add_fn), {index_field, index_field})),
|
||||
0};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
|
||||
"add_10", [](int a) { return a + 10; });
|
||||
Field result_field{
|
||||
CPPType::get<int>(),
|
||||
std::make_shared<FieldFunction>(FieldFunction(std::move(add_10_fn), {add_field})),
|
||||
0};
|
||||
|
||||
Array<int> result(10);
|
||||
GMutableSpan result_generic(result.as_mutable_span());
|
||||
evaluate_fields({result_field}, {2, 4, 6, 8}, {result_generic});
|
||||
EXPECT_EQ(result[2], 14);
|
||||
EXPECT_EQ(result[4], 18);
|
||||
EXPECT_EQ(result[6], 22);
|
||||
EXPECT_EQ(result[8], 26);
|
||||
}
|
||||
|
||||
class TwoOutputFunction : public MultiFunction {
|
||||
private:
|
||||
MFSignature signature_;
|
||||
|
||||
public:
|
||||
TwoOutputFunction(StringRef name)
|
||||
{
|
||||
MFSignatureBuilder signature{name};
|
||||
signature.single_input<int>("In1");
|
||||
signature.single_input<int>("In2");
|
||||
signature.single_output<int>("Add");
|
||||
signature.single_output<int>("Add10");
|
||||
signature_ = signature.build();
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(IndexMask mask, MFParams params, MFContext UNUSED(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(field, FunctionTwoOutputs)
|
||||
{
|
||||
/* Also use two separate input fields, why not. */
|
||||
Field index_field_1{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
Field index_field_2{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::shared_ptr<FieldFunction> fn = std::make_shared<FieldFunction>(FieldFunction(
|
||||
std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2}));
|
||||
|
||||
Field result_field_1{CPPType::get<int>(), fn, 0};
|
||||
Field result_field_2{CPPType::get<int>(), fn, 1};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
GMutableSpan result_generic_1(result_1.as_mutable_span());
|
||||
GMutableSpan result_generic_2(result_2.as_mutable_span());
|
||||
evaluate_fields(
|
||||
{result_field_1, result_field_2}, {2, 4, 6, 8}, {result_generic_1, result_generic_2});
|
||||
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(field, TwoFunctionsTwoOutputs)
|
||||
{
|
||||
Field index_field{CPPType::get<int>(), std::make_shared<IndexFieldInput>()};
|
||||
|
||||
std::shared_ptr<FieldFunction> fn = std::make_shared<FieldFunction>(FieldFunction(
|
||||
std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field}));
|
||||
|
||||
Field result_field_1{CPPType::get<int>(), fn, 0};
|
||||
Field intermediate_field{CPPType::get<int>(), fn, 1};
|
||||
|
||||
std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
|
||||
"add_10", [](int a) { return a + 10; });
|
||||
Field result_field_2{
|
||||
CPPType::get<int>(),
|
||||
std::make_shared<FieldFunction>(FieldFunction(std::move(add_10_fn), {intermediate_field})),
|
||||
0};
|
||||
|
||||
Array<int> result_1(10);
|
||||
Array<int> result_2(10);
|
||||
GMutableSpan result_generic_1(result_1.as_mutable_span());
|
||||
GMutableSpan result_generic_2(result_2.as_mutable_span());
|
||||
evaluate_fields(
|
||||
{result_field_1, result_field_2}, {2, 4, 6, 8}, {result_generic_1, result_generic_2});
|
||||
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], 24);
|
||||
EXPECT_EQ(result_2[4], 28);
|
||||
EXPECT_EQ(result_2[6], 32);
|
||||
EXPECT_EQ(result_2[8], 36);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::tests
|
@@ -0,0 +1,344 @@
|
||||
/* Apache License, Version 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"
|
||||
|
||||
namespace blender::fn::tests {
|
||||
|
||||
TEST(multi_function_procedure, SimpleTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int var2, int *var4) {
|
||||
* int var3 = var1 + var2;
|
||||
* var4 = var2 + var3;
|
||||
* var4 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
|
||||
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var1 = &builder.add_single_input_parameter<int>();
|
||||
MFVariable *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());
|
||||
|
||||
MFProcedureExecutor executor{"My Procedure", procedure};
|
||||
|
||||
MFParamsBuilder params{executor, 3};
|
||||
MFContextBuilder 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(IndexRange(3), params, context);
|
||||
|
||||
EXPECT_EQ(output_array[0], 17);
|
||||
EXPECT_EQ(output_array[1], 18);
|
||||
EXPECT_EQ(output_array[2], 19);
|
||||
}
|
||||
|
||||
TEST(multi_function_procedure, BranchTest)
|
||||
{
|
||||
/**
|
||||
* procedure(int &var1, bool var2) {
|
||||
* if (var2) {
|
||||
* var1 += 100;
|
||||
* }
|
||||
* else {
|
||||
* var1 += 10;
|
||||
* }
|
||||
* var1 += 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
|
||||
CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
|
||||
MFVariable *var2 = &builder.add_single_input_parameter<bool>();
|
||||
|
||||
MFProcedureBuilder::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());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Condition Test", procedure};
|
||||
MFParamsBuilder params(procedure_fn, 5);
|
||||
|
||||
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());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({1, 2, 3, 4}, 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(multi_function_procedure, EvaluateOne)
|
||||
{
|
||||
/**
|
||||
* procedure(int var1, int *var2) {
|
||||
* var2 = var1 + 10;
|
||||
* }
|
||||
*/
|
||||
|
||||
int tot_evaluations = 0;
|
||||
CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
|
||||
tot_evaluations++;
|
||||
return a + 10;
|
||||
}};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *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);
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
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());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, 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(multi_function_procedure, 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};
|
||||
CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
|
||||
[](int a, int b) { return a >= b; }};
|
||||
CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
|
||||
CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
|
||||
CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *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");
|
||||
|
||||
MFProcedureBuilder::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");
|
||||
MFProcedureBuilder::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());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
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());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, 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(multi_function_procedure, 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};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
|
||||
MFVariable *var_v2 = &builder.add_parameter(MFParamType::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());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Vectors", procedure};
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
|
||||
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);
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 1, 3, 4}, 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(multi_function_procedure, 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;
|
||||
* }
|
||||
*/
|
||||
|
||||
CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }};
|
||||
|
||||
MFProcedure procedure;
|
||||
MFProcedureBuilder builder{procedure};
|
||||
|
||||
MFVariable *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());
|
||||
|
||||
MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure};
|
||||
|
||||
Array<int> inputs = {4, 1, 6, 2, 3};
|
||||
Array<int> results(5, -1);
|
||||
|
||||
MFParamsBuilder params{procedure_fn, 5};
|
||||
params.add_readonly_single_input(inputs.as_span());
|
||||
params.add_uninitialized_single_output(results.as_mutable_span());
|
||||
|
||||
MFContextBuilder context;
|
||||
procedure_fn.call({0, 2, 3, 4}, 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);
|
||||
}
|
||||
|
||||
} // namespace blender::fn::tests
|
Reference in New Issue
Block a user