diff options
-rw-r--r-- | source/blender/blenkernel/BKE_geometry_set.hh | 9 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/attribute_access.cc | 16 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_draw.cc | 83 | ||||
-rw-r--r-- | source/blender/functions/FN_field.hh | 59 | ||||
-rw-r--r-- | source/blender/functions/FN_multi_function_builder.hh | 7 | ||||
-rw-r--r-- | source/blender/functions/intern/field.cc | 72 | ||||
-rw-r--r-- | source/blender/functions/intern/multi_function_builder.cc | 21 | ||||
-rw-r--r-- | source/blender/functions/tests/FN_multi_function_test.cc | 2 | ||||
-rw-r--r-- | source/blender/modifiers/intern/MOD_nodes_evaluator.cc | 10 |
9 files changed, 237 insertions, 42 deletions
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index c3d594d7dcd..3da35cb4fe1 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -657,10 +657,17 @@ class AttributeFieldInput : public fn::FieldInput { { } + StringRefNull attribute_name() const + { + return name_; + } + const GVArray *get_varray_for_context(const fn::FieldContext &context, IndexMask mask, ResourceScope &scope) const override; + std::string socket_inspection_name() const override; + uint64_t hash() const override; bool is_equal_to(const fn::FieldNode &other) const override; }; @@ -683,6 +690,8 @@ class AnonymousAttributeFieldInput : public fn::FieldInput { IndexMask mask, ResourceScope &scope) const override; + std::string socket_inspection_name() const override; + uint64_t hash() const override; bool is_equal_to(const fn::FieldNode &other) const override; }; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 08bd5dc5981..2a5bb99a18b 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -32,6 +32,8 @@ #include "BLI_float2.hh" #include "BLI_span.hh" +#include "BLT_translation.h" + #include "CLG_log.h" #include "NOD_type_conversions.hh" @@ -1318,6 +1320,13 @@ const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContex return nullptr; } +std::string AttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Attribute: ") << name_; + return ss.str(); +} + uint64_t AttributeFieldInput::hash() const { return get_default_hash_2(name_, type_); @@ -1346,6 +1355,13 @@ const GVArray *AnonymousAttributeFieldInput::get_varray_for_context( return nullptr; } +std::string AnonymousAttributeFieldInput::socket_inspection_name() const +{ + std::stringstream ss; + ss << TIP_("Anonymous Attribute: ") << debug_name_; + return ss.str(); +} + uint64_t AnonymousAttributeFieldInput::hash() const { return get_default_hash_2(anonymous_id_.get(), type_); diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 5b4e3b3b6f5..aa241071425 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -41,10 +41,12 @@ #include "BLI_span.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" +#include "BLI_vector_set.hh" #include "BLT_translation.h" #include "BKE_context.h" +#include "BKE_geometry_set.hh" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -78,6 +80,8 @@ #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field_cpp_type.hh" + #include "node_intern.h" /* own include */ #ifdef WITH_COMPOSITOR @@ -88,7 +92,11 @@ using blender::Map; using blender::Set; using blender::Span; using blender::Vector; +using blender::VectorSet; using blender::fn::CPPType; +using blender::fn::FieldCPPType; +using blender::fn::FieldInput; +using blender::fn::GField; using blender::fn::GPointer; namespace geo_log = blender::nodes::geometry_nodes_eval_log; @@ -850,31 +858,70 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal }; const GPointer value = value_log.value(); - if (value.is_type<int>()) { - ss << *value.get<int>() << TIP_(" (Integer)"); - } - else if (value.is_type<float>()) { - ss << *value.get<float>() << TIP_(" (Float)"); - } - else if (value.is_type<blender::float3>()) { - ss << *value.get<blender::float3>() << TIP_(" (Vector)"); - } - else if (value.is_type<bool>()) { - ss << (*value.get<bool>() ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); - } - else if (value.is_type<std::string>()) { - ss << *value.get<std::string>() << TIP_(" (String)"); + const CPPType &type = *value.type(); + if (const FieldCPPType *field_type = dynamic_cast<const FieldCPPType *>(&type)) { + const CPPType &base_type = field_type->field_type(); + BUFFER_FOR_CPP_TYPE_VALUE(base_type, buffer); + const GField &field = field_type->get_gfield(value.get()); + if (field.node().depends_on_input()) { + if (base_type.is<int>()) { + ss << TIP_("Integer Field"); + } + else if (base_type.is<float>()) { + ss << TIP_("Float Field"); + } + else if (base_type.is<blender::float3>()) { + ss << TIP_("Vector Field"); + } + else if (base_type.is<bool>()) { + ss << TIP_("Boolean Field"); + } + else if (base_type.is<std::string>()) { + ss << TIP_("String Field"); + } + ss << TIP_(" based on:\n"); + + /* Use vector set to deduplicate inputs. */ + VectorSet<std::reference_wrapper<const FieldInput>> field_inputs; + field.node().foreach_field_input( + [&](const FieldInput &field_input) { field_inputs.add(field_input); }); + for (const FieldInput &field_input : field_inputs) { + ss << "\u2022 " << field_input.socket_inspection_name(); + if (field_input != field_inputs.as_span().last().get()) { + ss << ".\n"; + } + } + } + else { + blender::fn::evaluate_constant_field(field, buffer); + if (base_type.is<int>()) { + ss << *(int *)buffer << TIP_(" (Integer)"); + } + else if (base_type.is<float>()) { + ss << *(float *)buffer << TIP_(" (Float)"); + } + else if (base_type.is<blender::float3>()) { + ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); + } + else if (base_type.is<bool>()) { + ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); + } + else if (base_type.is<std::string>()) { + ss << *(std::string *)buffer << TIP_(" (String)"); + } + base_type.destruct(buffer); + } } - else if (value.is_type<Object *>()) { + else if (type.is<Object *>()) { id_to_inspection_string((ID *)*value.get<Object *>(), ID_OB); } - else if (value.is_type<Material *>()) { + else if (type.is<Material *>()) { id_to_inspection_string((ID *)*value.get<Material *>(), ID_MA); } - else if (value.is_type<Tex *>()) { + else if (type.is<Tex *>()) { id_to_inspection_string((ID *)*value.get<Tex *>(), ID_TE); } - else if (value.is_type<Collection *>()) { + else if (type.is<Collection *>()) { id_to_inspection_string((ID *)*value.get<Collection *>(), ID_GR); } } diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh index 79a6faf499b..976d260d91b 100644 --- a/source/blender/functions/FN_field.hh +++ b/source/blender/functions/FN_field.hh @@ -46,6 +46,7 @@ * they share common sub-fields and a common context. */ +#include "BLI_function_ref.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" @@ -57,15 +58,27 @@ namespace blender::fn { +class FieldInput; + /** * A node in a field-tree. It has at least one output that can be referenced by fields. */ class FieldNode { private: bool is_input_; + /** + * True when this node is a #FieldInput or (potentially indirectly) depends on one. This could + * always be derived again later by traversing the field-tree, but keeping track of it while the + * field is built is cheaper. + * + * If this is false, the field is constant. Note that even when this is true, the field may be + * constant when all inputs are constant. + */ + bool depends_on_input_; public: - FieldNode(bool is_input) : is_input_(is_input) + FieldNode(bool is_input, bool depends_on_input) + : is_input_(is_input), depends_on_input_(depends_on_input) { } @@ -83,6 +96,17 @@ class FieldNode { return !is_input_; } + bool depends_on_input() const + { + return depends_on_input_; + } + + /** + * Invoke callback for every field input. It might be called multiple times for the same input. + * The caller is responsible for deduplication if required. + */ + virtual void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const = 0; + virtual uint64_t hash() const { return get_default_hash(this); @@ -93,6 +117,11 @@ class FieldNode { return a.is_equal_to(b); } + friend bool operator!=(const FieldNode &a, const FieldNode &b) + { + return !(a == b); + } + virtual bool is_equal_to(const FieldNode &other) const { return this == &other; @@ -211,16 +240,8 @@ class FieldOperation : public FieldNode { blender::Vector<GField> inputs_; public: - FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {}) - : FieldNode(false), owned_function_(std::move(function)), inputs_(std::move(inputs)) - { - function_ = owned_function_.get(); - } - - FieldOperation(const MultiFunction &function, Vector<GField> inputs = {}) - : FieldNode(false), function_(&function), inputs_(std::move(inputs)) - { - } + FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {}); + FieldOperation(const MultiFunction &function, Vector<GField> inputs = {}); Span<GField> inputs() const { @@ -247,6 +268,8 @@ class FieldOperation : public FieldNode { BLI_assert_unreachable(); return CPPType::get<float>(); } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; }; class FieldContext; @@ -260,10 +283,7 @@ class FieldInput : public FieldNode { std::string debug_name_; public: - FieldInput(const CPPType &type, std::string debug_name = "") - : FieldNode(true), type_(&type), debug_name_(std::move(debug_name)) - { - } + FieldInput(const CPPType &type, std::string debug_name = ""); /** * Get the value of this specific input based on the given context. The returned virtual array, @@ -273,6 +293,11 @@ class FieldInput : public FieldNode { IndexMask mask, ResourceScope &scope) const = 0; + virtual std::string socket_inspection_name() const + { + return debug_name_; + } + blender::StringRef debug_name() const { return debug_name_; @@ -289,6 +314,8 @@ class FieldInput : public FieldNode { UNUSED_VARS_NDEBUG(output_index); return *type_; } + + void foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const override; }; /** @@ -453,4 +480,6 @@ template<typename T> Field<T> make_constant_field(T value) return Field<T>{GField{std::move(operation), 0}}; } +GField make_field_constant_if_possible(GField field); + } // namespace blender::fn diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh index d13615ced07..0ce05cbca30 100644 --- a/source/blender/functions/FN_multi_function_builder.hh +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -326,18 +326,21 @@ template<typename From, typename To> class CustomMF_Convert : public MultiFuncti /** * A multi-function that outputs the same value every time. The value is not owned by an instance - * of this function. The caller is responsible for destructing and freeing the value. + * of this function. If #make_value_copy is false, the caller is responsible for destructing and + * freeing the value. */ class CustomMF_GenericConstant : public MultiFunction { private: const CPPType &type_; const void *value_; MFSignature signature_; + bool owns_value_; template<typename T> friend class CustomMF_Constant; public: - CustomMF_GenericConstant(const CPPType &type, const void *value); + CustomMF_GenericConstant(const CPPType &type, const void *value, bool make_value_copy); + ~CustomMF_GenericConstant(); void call(IndexMask mask, MFParams params, MFContext context) const override; uint64_t hash() const override; bool equals(const MultiFunction &other) const override; diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc index fa7dea97b7f..6133658d8e3 100644 --- a/source/blender/functions/intern/field.cc +++ b/source/blender/functions/intern/field.cc @@ -462,6 +462,29 @@ void evaluate_constant_field(const GField &field, void *r_value) varrays[0]->get_to_uninitialized(0, r_value); } +/** + * If the field depends on some input, the same field is returned. + * Otherwise the field is evaluated and a new field is created that just computes this constant. + * + * Making the field constant has two benefits: + * - The field-tree becomes a single node, which is more efficient when the field is evaluated many + * times. + * - Memory of the input fields may be freed. + */ +GField make_field_constant_if_possible(GField field) +{ + if (field.node().depends_on_input()) { + return field; + } + const CPPType &type = field.cpp_type(); + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + evaluate_constant_field(field, buffer); + auto constant_fn = std::make_unique<CustomMF_GenericConstant>(type, buffer, true); + type.destruct(buffer); + auto operation = std::make_shared<FieldOperation>(std::move(constant_fn)); + return GField{operation, 0}; +} + const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, IndexMask mask, ResourceScope &scope) const @@ -472,6 +495,55 @@ const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, } /* -------------------------------------------------------------------- + * FieldOperation. + */ + +FieldOperation::FieldOperation(std::unique_ptr<const MultiFunction> function, + Vector<GField> inputs) + : FieldOperation(*function, std::move(inputs)) +{ + owned_function_ = std::move(function); +} + +static bool any_field_depends_on_input(Span<GField> fields) +{ + for (const GField &field : fields) { + if (field.node().depends_on_input()) { + return true; + } + } + return false; +} + +FieldOperation::FieldOperation(const MultiFunction &function, Vector<GField> inputs) + : FieldNode(false, any_field_depends_on_input(inputs)), + function_(&function), + inputs_(std::move(inputs)) +{ +} + +void FieldOperation::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + for (const GField &field : inputs_) { + field.node().foreach_field_input(foreach_fn); + } +} + +/* -------------------------------------------------------------------- + * FieldInput. + */ + +FieldInput::FieldInput(const CPPType &type, std::string debug_name) + : FieldNode(true, true), type_(&type), debug_name_(std::move(debug_name)) +{ +} + +void FieldInput::foreach_field_input(FunctionRef<void(const FieldInput &)> foreach_fn) const +{ + foreach_fn(*this); +} + +/* -------------------------------------------------------------------- * FieldEvaluator. */ diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc index 180d1f17a54..f891f162820 100644 --- a/source/blender/functions/intern/multi_function_builder.cc +++ b/source/blender/functions/intern/multi_function_builder.cc @@ -20,9 +20,18 @@ namespace blender::fn { -CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const void *value) - : type_(type), value_(value) +CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, + const void *value, + bool make_value_copy) + : type_(type), owns_value_(make_value_copy) { + if (make_value_copy) { + void *copied_value = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_construct(value, copied_value); + value = copied_value; + } + value_ = value; + MFSignatureBuilder signature{"Constant " + type.name()}; std::stringstream ss; type.print_or_default(value, ss, type.name()); @@ -31,6 +40,14 @@ CustomMF_GenericConstant::CustomMF_GenericConstant(const CPPType &type, const vo this->set_signature(&signature_); } +CustomMF_GenericConstant::~CustomMF_GenericConstant() +{ + if (owns_value_) { + signature_.param_types[0].data_type().single_type().destruct((void *)value_); + MEM_freeN((void *)value_); + } +} + void CustomMF_GenericConstant::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc index 91c72a51dd6..204dbfab6aa 100644 --- a/source/blender/functions/tests/FN_multi_function_test.cc +++ b/source/blender/functions/tests/FN_multi_function_test.cc @@ -263,7 +263,7 @@ TEST(multi_function, CustomMF_Constant) TEST(multi_function, CustomMF_GenericConstant) { int value = 42; - CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value}; + CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value, false}; EXPECT_EQ(fn.param_name(0), "42"); Array<int> outputs(4, 0); diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index 4f21924619b..f67f7f967c9 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -892,8 +892,10 @@ class GeometryNodesEvaluator { OutputState &output_state = node_state.outputs[i]; const DOutputSocket socket{node.context(), &socket_ref}; const CPPType *cpp_type = get_socket_cpp_type(socket_ref); - GField &field = *allocator.construct<GField>(operation, output_index).release(); - this->forward_output(socket, {cpp_type, &field}); + GField new_field{operation, output_index}; + new_field = fn::make_field_constant_if_possible(std::move(new_field)); + GField &field_to_forward = *allocator.construct<GField>(std::move(new_field)).release(); + this->forward_output(socket, {cpp_type, &field_to_forward}); output_state.has_been_computed = true; output_index++; } @@ -1411,8 +1413,8 @@ class GeometryNodesEvaluator { { if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) { const CPPType &base_type = field_cpp_type->field_type(); - auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>(base_type, - base_type.default_value()); + auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>( + base_type, base_type.default_value(), false); auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn)); new (r_value) GField(std::move(operation), 0); return; |