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.
This commit is contained in:
Jacques Lucke 2023-04-28 16:53:03 +02:00
parent d6c61ccadc
commit 7d38cef6d1
4 changed files with 179 additions and 64 deletions

View File

@ -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<int64_t InlineBufferCapacity = 64, typename Allocator = GuardedAllocator>
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<InlineBufferCapacity, Allocator> 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;
}

View File

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

View File

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

View File

@ -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<const bNodeSocket *, int> referenced_by_field_socket;
MultiValueMap<const bNodeSocket *, int> 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<const bNodeSocket *, int> 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<const aal::RelationsInNode *> relations_by_node,
const Span<AttributeReferenceKey> attribute_reference_keys,
const Span<AttributeReferenceInfo> attribute_reference_infos,
MultiValueMap<const bNodeSocket *, int> &r_referenced_by_field_socket,
MultiValueMap<const bNodeSocket *, int> &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<int> referenced_keys;
Vector<int> 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<int>(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<int>(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<const aal::RelationsInNode *> relations_by_node,
const VectorSet<AttributeReferenceKey> &attribute_reference_keys,
const MultiValueMap<const bNodeSocket *, int> &referenced_by_field_socket,
const MultiValueMap<const bNodeSocket *, int> &propagated_to_geometry_socket,
MultiValueMap<const bNodeSocket *, int> &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<const bNodeSocket *, int> 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<int> 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<int> 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<int> 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<int> 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<AttributeReferenceKey> attribute_reference_keys,
const Span<AttributeReferenceInfo> attribute_reference_infos,
const MultiValueMap<const bNodeSocket *, int> &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<int> 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<lf::OutputSocket *> attribute_set_sockets;
Vector<lf::OutputSocket *> 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);