From 7d38cef6d1eb124a6a89c046849f84a343002358 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 28 Apr 2023 16:53:03 +0200 Subject: [PATCH] Geometry Nodes: use new BitGroupVector to find attribute propagation sets A `BitGroupVector` is a compact data structure that allows storing multiple bits per element, for example 5 bits per vertex. The implementation is mostly just a wrapper around `BitVector`. There is some additional logic to make sure that the bit span of every element is bounded (according to the `is_bounded_span` function). This makes it more efficient to operate on groups as a whole (e.g. `or` one group into another). In some sense, this data structure can also be interpreted as a 2D bit array. Functions like `append` can be added when they become necessary. The new data structure is used to replace some `MultiValueMap` in geometry nodes. This simplifies the code. --- .../blender/blenlib/BLI_bit_group_vector.hh | 93 +++++++++++++ source/blender/blenlib/CMakeLists.txt | 2 + .../tests/BLI_bit_group_vector_test.cc | 25 ++++ .../intern/geometry_nodes_lazy_function.cc | 123 +++++++++--------- 4 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 source/blender/blenlib/BLI_bit_group_vector.hh create mode 100644 source/blender/blenlib/tests/BLI_bit_group_vector_test.cc diff --git a/source/blender/blenlib/BLI_bit_group_vector.hh b/source/blender/blenlib/BLI_bit_group_vector.hh new file mode 100644 index 00000000000..3573a65324d --- /dev/null +++ b/source/blender/blenlib/BLI_bit_group_vector.hh @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_bit_vector.hh" + +namespace blender::bits { + +/** + * A #BitGroupVector is a compact data structure that allows storing an arbitrary but fixed number + * of bits per element. For example, it could be used to compactly store 5 bits per vertex in a + * mesh. The data structure stores the bits in a way so that the #BitSpan for every element is + * bounded according to #is_bounded_span. The makes sure that operations on entire groups can be + * implemented efficiently. For example, one can easy `or` one group into another. + */ +template +class BitGroupVector { + private: + /** + * Number of bits per group. + */ + int64_t group_size_ = 0; + /** + * Actually stored number of bits per group so that individual groups are bounded according to + * #is_bounded_span. + */ + int64_t aligned_group_size_ = 0; + BitVector data_; + + static int64_t align_group_size(const int64_t group_size) + { + if (group_size < 64) { + /* Align to next power of two so that a single group never spans across two ints. */ + return int64_t(power_of_2_max_u(uint32_t(group_size))); + } + /* Align to multiple of BitsPerInt. */ + return (group_size + BitsPerInt - 1) & ~(BitsPerInt - 1); + } + + public: + BitGroupVector() = default; + + BitGroupVector(const int64_t size_in_groups, + const int64_t group_size, + const bool value = false, + Allocator allocator = {}) + : group_size_(group_size), + aligned_group_size_(align_group_size(group_size)), + data_(size_in_groups * aligned_group_size_, value, allocator) + { + BLI_assert(group_size >= 0); + BLI_assert(size_in_groups >= 0); + } + + /** Get all the bits at an index. */ + BoundedBitSpan operator[](const int64_t i) const + { + const int64_t offset = aligned_group_size_ * i; + return {data_.data() + (offset >> BitToIntIndexShift), + IndexRange(offset & BitIndexMask, group_size_)}; + } + + /** Get all the bits at an index. */ + MutableBoundedBitSpan operator[](const int64_t i) + { + const int64_t offset = aligned_group_size_ * i; + return {data_.data() + (offset >> BitToIntIndexShift), + IndexRange(offset & BitIndexMask, group_size_)}; + } + + /** Number of groups. */ + int64_t size() const + { + return aligned_group_size_ == 0 ? 0 : data_.size() / aligned_group_size_; + } + + /** Number of bits per group. */ + int64_t group_size() const + { + return group_size_; + } + + IndexRange index_range() const + { + return IndexRange{this->size()}; + } +}; + +} // namespace blender::bits + +namespace blender { +using bits::BitGroupVector; +} diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index aeaa235627f..d9014c614df 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -182,6 +182,7 @@ set(SRC BLI_assert.h BLI_astar.h BLI_atomic_disjoint_set.hh + BLI_bit_group_vector.hh BLI_bit_ref.hh BLI_bit_span.hh BLI_bit_span_ops.hh @@ -477,6 +478,7 @@ if(WITH_GTESTS) tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc + tests/BLI_bit_group_vector_test.cc tests/BLI_bit_ref_test.cc tests/BLI_bit_span_test.cc tests/BLI_bit_vector_test.cc diff --git a/source/blender/blenlib/tests/BLI_bit_group_vector_test.cc b/source/blender/blenlib/tests/BLI_bit_group_vector_test.cc new file mode 100644 index 00000000000..984f172e377 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_bit_group_vector_test.cc @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "BLI_bit_group_vector.hh" +#include "BLI_strict_flags.h" + +#include "testing/testing.h" + +namespace blender::bits::tests { + +TEST(bit_group_vector, DefaultConstruct) +{ + BitGroupVector<> groups; + EXPECT_EQ(groups.size(), 0); +} + +TEST(bit_group_vector, Construct) +{ + BitGroupVector<> groups(12, 5); + + EXPECT_EQ(groups.size(), 12); + EXPECT_EQ(groups[0].size(), 5); + EXPECT_EQ(groups[4].size(), 5); +} + +} // namespace blender::bits::tests diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 7b0561e182b..1761dbda733 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -18,6 +18,8 @@ #include "NOD_multi_function.hh" #include "NOD_node_declaration.hh" +#include "BLI_bit_group_vector.hh" +#include "BLI_bit_span_ops.hh" #include "BLI_cpp_types.hh" #include "BLI_dot_export.hh" #include "BLI_hash.h" @@ -2443,15 +2445,21 @@ struct GeometryNodesLazyFunctionGraphBuilder { this->build_attribute_references( relations_by_node, attribute_reference_keys, attribute_reference_infos); - MultiValueMap referenced_by_field_socket; - MultiValueMap propagated_to_geometry_socket; + const int sockets_num = btree_.all_sockets().size(); + const int attribute_references_num = attribute_reference_keys.size(); + + /* The code below uses #BitGroupVector to store a set of attribute references per socket. Each + * socket has a bit span where each bit corresponds to one attribute reference. */ + BitGroupVector<> referenced_by_field_socket(sockets_num, attribute_references_num, false); + BitGroupVector<> propagated_to_geometry_socket(sockets_num, attribute_references_num, false); this->gather_referenced_and_potentially_propagated_data(relations_by_node, attribute_reference_keys, attribute_reference_infos, referenced_by_field_socket, propagated_to_geometry_socket); - MultiValueMap required_propagated_to_geometry_socket; + BitGroupVector<> required_propagated_to_geometry_socket( + sockets_num, attribute_references_num, false); this->gather_required_propagated_data(relations_by_node, attribute_reference_keys, referenced_by_field_socket, @@ -2552,10 +2560,10 @@ struct GeometryNodesLazyFunctionGraphBuilder { const Span relations_by_node, const Span attribute_reference_keys, const Span attribute_reference_infos, - MultiValueMap &r_referenced_by_field_socket, - MultiValueMap &r_propagated_to_geometry_socket) + BitGroupVector<> &r_referenced_by_field_socket, + BitGroupVector<> &r_propagated_to_geometry_socket) { - /* Initialize maps. */ + /* Insert initial referenced/propagated attributes. */ for (const int key_index : attribute_reference_keys.index_range()) { const AttributeReferenceKey &key = attribute_reference_keys[key_index]; const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; @@ -2563,7 +2571,7 @@ struct GeometryNodesLazyFunctionGraphBuilder { case AttributeReferenceKeyType::InputField: { for (const bNode *bnode : btree_.group_input_nodes()) { const bNodeSocket &bsocket = bnode->output_socket(key.index); - r_referenced_by_field_socket.add(&bsocket, key_index); + r_referenced_by_field_socket[bsocket.index_in_tree()][key_index].set(); } break; } @@ -2571,34 +2579,28 @@ struct GeometryNodesLazyFunctionGraphBuilder { break; } case AttributeReferenceKeyType::Socket: { - r_referenced_by_field_socket.add(key.bsocket, key_index); + r_referenced_by_field_socket[key.bsocket->index_in_tree()][key_index].set(); break; } } for (const bNodeSocket *geometry_bsocket : info.initial_geometry_sockets) { - r_propagated_to_geometry_socket.add(geometry_bsocket, key_index); + r_propagated_to_geometry_socket[geometry_bsocket->index_in_tree()][key_index].set(); } } /* Propagate attribute usages from left to right. */ for (const bNode *bnode : btree_.toposort_left_to_right()) { for (const bNodeSocket *bsocket : bnode->input_sockets()) { if (bsocket->is_available()) { - Vector referenced_keys; - Vector propagated_keys; + const int dst_index = bsocket->index_in_tree(); + MutableBoundedBitSpan referenced_dst = r_referenced_by_field_socket[dst_index]; + MutableBoundedBitSpan propagated_dst = r_propagated_to_geometry_socket[dst_index]; for (const bNodeLink *blink : bsocket->directly_linked_links()) { if (blink->is_used()) { - referenced_keys.extend_non_duplicates( - r_referenced_by_field_socket.lookup(blink->fromsock)); - propagated_keys.extend_non_duplicates( - r_propagated_to_geometry_socket.lookup(blink->fromsock)); + const int src_index = blink->fromsock->index_in_tree(); + referenced_dst |= r_referenced_by_field_socket[src_index]; + propagated_dst |= r_propagated_to_geometry_socket[src_index]; } } - if (!referenced_keys.is_empty()) { - r_referenced_by_field_socket.add_multiple(bsocket, referenced_keys); - } - if (!propagated_keys.is_empty()) { - r_propagated_to_geometry_socket.add_multiple(bsocket, propagated_keys); - } } } const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; @@ -2608,8 +2610,8 @@ struct GeometryNodesLazyFunctionGraphBuilder { if (!input_bsocket.is_available() || !output_bsocket.is_available()) { continue; } - r_referenced_by_field_socket.add_multiple( - &output_bsocket, Vector(r_referenced_by_field_socket.lookup(&input_bsocket))); + r_referenced_by_field_socket[output_bsocket.index_in_tree()] |= + r_referenced_by_field_socket[input_bsocket.index_in_tree()]; } for (const aal::PropagateRelation &relation : relations.propagate_relations) { const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_geometry_input); @@ -2617,8 +2619,8 @@ struct GeometryNodesLazyFunctionGraphBuilder { if (!input_bsocket.is_available() || !output_bsocket.is_available()) { continue; } - r_propagated_to_geometry_socket.add_multiple( - &output_bsocket, Vector(r_propagated_to_geometry_socket.lookup(&input_bsocket))); + r_propagated_to_geometry_socket[output_bsocket.index_in_tree()] |= + r_propagated_to_geometry_socket[input_bsocket.index_in_tree()]; } } } @@ -2629,12 +2631,14 @@ struct GeometryNodesLazyFunctionGraphBuilder { void gather_required_propagated_data( const Span relations_by_node, const VectorSet &attribute_reference_keys, - const MultiValueMap &referenced_by_field_socket, - const MultiValueMap &propagated_to_geometry_socket, - MultiValueMap &r_required_propagated_to_geometry_socket) + const BitGroupVector<> &referenced_by_field_socket, + const BitGroupVector<> &propagated_to_geometry_socket, + BitGroupVector<> &r_required_propagated_to_geometry_socket) { const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; - MultiValueMap required_by_geometry_socket; + const int sockets_num = btree_.all_sockets().size(); + const int attribute_references_num = referenced_by_field_socket.group_size(); + BitGroupVector<> required_by_geometry_socket(sockets_num, attribute_references_num, false); /* Initialize required attributes at group output. */ if (const bNode *group_output_bnode = btree_.group_output_node()) { @@ -2643,74 +2647,66 @@ struct GeometryNodesLazyFunctionGraphBuilder { key.type = AttributeReferenceKeyType::OutputGeometry; key.index = relation.to_geometry_output; const int key_index = attribute_reference_keys.index_of(key); - required_by_geometry_socket.add( - &group_output_bnode->input_socket(relation.to_geometry_output), key_index); + required_by_geometry_socket[group_output_bnode->input_socket(relation.to_geometry_output) + .index_in_tree()][key_index] + .set(); } for (const aal::AvailableRelation &relation : tree_relations.available_relations) { const bNodeSocket &geometry_bsocket = group_output_bnode->input_socket( relation.geometry_output); const bNodeSocket &field_bsocket = group_output_bnode->input_socket(relation.field_output); - required_by_geometry_socket.add_multiple( - &geometry_bsocket, referenced_by_field_socket.lookup(&field_bsocket)); + required_by_geometry_socket[geometry_bsocket.index_in_tree()] |= + referenced_by_field_socket[field_bsocket.index_in_tree()]; } } /* Propagate attribute usages from right to left. */ + BitVector<> required_attributes(attribute_references_num); for (const bNode *bnode : btree_.toposort_right_to_left()) { const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; for (const bNodeSocket *bsocket : bnode->output_sockets()) { if (!bsocket->is_available()) { continue; } - Vector required_attributes; + required_attributes.fill(false); for (const bNodeLink *blink : bsocket->directly_linked_links()) { if (blink->is_used()) { const bNodeSocket &to_socket = *blink->tosock; - required_attributes.extend_non_duplicates( - required_by_geometry_socket.lookup(&to_socket)); + required_attributes |= required_by_geometry_socket[to_socket.index_in_tree()]; } } - const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); - for (const int key_index : required_attributes) { - if (available_attributes.contains(key_index)) { - required_by_geometry_socket.add(bsocket, key_index); - - const AttributeReferenceKey &key = attribute_reference_keys[key_index]; - if (key.type != AttributeReferenceKeyType::Socket || - &key.bsocket->owner_node() != bnode) { - r_required_propagated_to_geometry_socket.add(bsocket, key_index); - } + required_attributes &= propagated_to_geometry_socket[bsocket->index_in_tree()]; + required_by_geometry_socket[bsocket->index_in_tree()] |= required_attributes; + bits::foreach_1_index(required_attributes, [&](const int key_index) { + const AttributeReferenceKey &key = attribute_reference_keys[key_index]; + if (key.type != AttributeReferenceKeyType::Socket || + &key.bsocket->owner_node() != bnode) { + r_required_propagated_to_geometry_socket[bsocket->index_in_tree()][key_index].set(); } - } + }); } for (const bNodeSocket *bsocket : bnode->input_sockets()) { if (!bsocket->is_available()) { continue; } - Vector required_attributes; + required_attributes.fill(false); for (const aal::PropagateRelation &relation : relations.propagate_relations) { if (relation.from_geometry_input == bsocket->index()) { const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output); - required_attributes.extend_non_duplicates( - required_by_geometry_socket.lookup(&output_bsocket)); + required_attributes |= required_by_geometry_socket[output_bsocket.index_in_tree()]; } } for (const aal::EvalRelation &relation : relations.eval_relations) { if (relation.geometry_input == bsocket->index()) { const bNodeSocket &field_bsocket = bnode->input_socket(relation.field_input); if (field_bsocket.is_available()) { - required_attributes.extend_non_duplicates( - referenced_by_field_socket.lookup(&field_bsocket)); + required_attributes |= referenced_by_field_socket[field_bsocket.index_in_tree()]; } } } - const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); - for (const int key_index : required_attributes) { - if (available_attributes.contains(key_index)) { - required_by_geometry_socket.add(bsocket, key_index); - } - } + required_attributes &= propagated_to_geometry_socket[bsocket->index_in_tree()]; + required_by_geometry_socket[bsocket->index_in_tree()] |= required_attributes; } } } @@ -2722,20 +2718,19 @@ struct GeometryNodesLazyFunctionGraphBuilder { void build_attribute_sets_to_propagate( const Span attribute_reference_keys, const Span attribute_reference_infos, - const MultiValueMap &required_propagated_to_geometry_socket) + const BitGroupVector<> &required_propagated_to_geometry_socket) { JoinAttibuteSetsCache join_attribute_sets_cache; for (const auto [geometry_output_bsocket, lf_attribute_set_input] : attribute_set_propagation_map_.items()) { - const Span required = required_propagated_to_geometry_socket.lookup( - geometry_output_bsocket); + const BoundedBitSpan required = + required_propagated_to_geometry_socket[geometry_output_bsocket->index_in_tree()]; Vector attribute_set_sockets; Vector used_sockets; - for (const int i : required.index_range()) { - const int key_index = required[i]; + bits::foreach_1_index(required, [&](const int key_index) { const AttributeReferenceKey &key = attribute_reference_keys[key_index]; const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; lf::OutputSocket *lf_socket_usage = nullptr; @@ -2760,7 +2755,7 @@ struct GeometryNodesLazyFunctionGraphBuilder { attribute_set_sockets.append(info.lf_attribute_set_socket); used_sockets.append(lf_socket_usage); } - } + }); if (lf::OutputSocket *joined_attribute_set = this->join_attribute_sets( attribute_set_sockets, used_sockets, join_attribute_sets_cache)) { lf_graph_->add_link(*joined_attribute_set, *lf_attribute_set_input);