Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
mini_tree_builder.h
Go to the documentation of this file.
1#ifndef BVH_V2_MINI_TREE_BUILDER_H
2#define BVH_V2_MINI_TREE_BUILDER_H
3
7#include "bvh/v2/executor.h"
8
9#include <stack>
10#include <tuple>
11#include <algorithm>
12#include <optional>
13#include <numeric>
14#include <cassert>
15
16namespace bvh::v2 {
17
18/// Multi-threaded top-down builder that partitions primitives using a grid. Multiple instances
19/// of a single-threaded builder are run in parallel on that partition, generating many small
20/// trees. Finally, a top-level tree is built on these smaller trees to form the final BVH.
21/// This builder is inspired by
22/// "Rapid Bounding Volume Hierarchy Generation using Mini Trees", by P. Ganestam et al.
23template <typename Node, typename MortonCode = uint32_t>
25 using Scalar = typename Node::Scalar;
28
29public:
30 struct Config : TopDownSahBuilder<Node>::Config {
31 /// Flag that turns on/off mini-tree pruning.
32 bool enable_pruning = true;
33
34 /// Threshold on the area of a mini-tree node above which it is pruned, expressed in
35 /// fraction of the area of bounding box around the entire set of primitives.
36 Scalar pruning_area_ratio = static_cast<Scalar>(0.01);
37
38 /// Minimum number of primitives per parallel task.
39 size_t parallel_threshold = 1024;
40
41 /// Log of the dimension of the grid used to split the workload horizontally.
42 size_t log2_grid_dim = 4;
43 };
44
45 /// Starts building a BVH with the given primitive data. The build algorithm is multi-threaded,
46 /// and runs on the given thread pool.
48 ThreadPool& thread_pool,
49 std::span<const BBox> bboxes,
50 std::span<const Vec> centers,
51 const Config& config = {})
52 {
53 MiniTreeBuilder builder(thread_pool, bboxes, centers, config);
54 auto mini_trees = builder.build_mini_trees();
55 if (config.enable_pruning)
56 mini_trees = builder.prune_mini_trees(std::move(mini_trees));
57 return builder.build_top_bvh(mini_trees);
58 }
59
60private:
61 friend struct BuildTask;
62
63 struct Bin {
64 std::vector<size_t> ids;
65
66 BVH_ALWAYS_INLINE void add(size_t id) { ids.push_back(id); }
67
68 BVH_ALWAYS_INLINE void merge(Bin&& other) {
69 if (ids.empty())
70 ids = std::move(other.ids);
71 else {
72 ids.insert(ids.end(), other.ids.begin(), other.ids.end());
73 other.ids.clear();
74 }
75 }
76 };
77
78 struct LocalBins {
79 std::vector<Bin> bins;
80
81 BVH_ALWAYS_INLINE Bin& operator [] (size_t i) { return bins[i]; }
82 BVH_ALWAYS_INLINE const Bin& operator [] (size_t i) const { return bins[i]; }
83
84 BVH_ALWAYS_INLINE void merge_small_bins(size_t threshold) {
85 for (size_t i = 0; i < bins.size();) {
86 size_t j = i + 1;
87 for (; j < bins.size() && bins[j].ids.size() + bins[i].ids.size() <= threshold; ++j)
88 bins[i].merge(std::move(bins[j]));
89 i = j;
90 }
91 }
92
94 bins.resize(std::remove_if(bins.begin(), bins.end(),
95 [] (const Bin& bin) { return bin.ids.empty(); }) - bins.begin());
96 }
97
99 bins.resize(std::max(bins.size(), other.bins.size()));
100 for (size_t i = 0, n = std::min(bins.size(), other.bins.size()); i < n; ++i)
101 bins[i].merge(std::move(other[i]));
102 }
103 };
104
105 struct BuildTask {
108 std::vector<size_t> prim_ids;
109
110 std::vector<BBox> bboxes;
111 std::vector<Vec> centers;
112
115 Bvh<Node>& bvh,
116 std::vector<size_t>&& prim_ids)
118 , bvh(bvh)
119 , prim_ids(std::move(prim_ids))
120 {}
121
123 // Make sure that rebuilds produce the same BVH
124 std::sort(prim_ids.begin(), prim_ids.end());
125
126 // Extract bounding boxes and centers for this set of primitives
127 bboxes.resize(prim_ids.size());
128 centers.resize(prim_ids.size());
129 for (size_t i = 0; i < prim_ids.size(); ++i) {
130 bboxes[i] = builder->bboxes_[prim_ids[i]];
132 }
133
135
136 // Permute primitive indices so that they index the proper set of primitives
137 for (size_t i = 0; i < bvh.prim_ids.size(); ++i)
138 bvh.prim_ids[i] = prim_ids[bvh.prim_ids[i]];
139 }
140 };
141
143 std::span<const BBox> bboxes_;
144 std::span<const Vec> centers_;
146
148 ThreadPool& thread_pool,
149 std::span<const BBox> bboxes,
150 std::span<const Vec> centers,
151 const Config& config)
152 : executor_(thread_pool)
153 , bboxes_(bboxes)
154 , centers_(centers)
155 , config_(config)
156 {
157 assert(bboxes.size() == centers.size());
158 }
159
160 std::vector<Bvh<Node>> build_mini_trees() {
161 // Compute the bounding box of all centers
162 auto center_bbox = executor_.reduce(0, bboxes_.size(), BBox::make_empty(),
163 [this] (BBox& bbox, size_t begin, size_t end) {
164 for (size_t i = begin; i < end; ++i)
165 bbox.extend(centers_[i]);
166 },
167 [] (BBox& bbox, const BBox& other) { bbox.extend(other); });
168
169 assert(config_.log2_grid_dim <= std::numeric_limits<MortonCode>::digits / Node::dimension);
170 auto bin_count = size_t{1} << (config_.log2_grid_dim * Node::dimension);
171 auto grid_dim = size_t{1} << config_.log2_grid_dim;
172 auto grid_scale = Vec(static_cast<Scalar>(grid_dim)) * safe_inverse(center_bbox.get_diagonal());
173 auto grid_offset = -center_bbox.min * grid_scale;
174
175 // Place primitives in bins
176 auto final_bins = executor_.reduce(0, bboxes_.size(), LocalBins {},
177 [&] (LocalBins& local_bins, size_t begin, size_t end) {
178 local_bins.bins.resize(bin_count);
179 for (size_t i = begin; i < end; ++i) {
180 auto p = robust_max(fast_mul_add(centers_[i], grid_scale, grid_offset), Vec(0));
181 auto x = std::min(grid_dim - 1, static_cast<size_t>(p[0]));
182 auto y = std::min(grid_dim - 1, static_cast<size_t>(p[1]));
183 auto z = std::min(grid_dim - 1, static_cast<size_t>(p[2]));
184 local_bins[morton_encode(x, y, z) & (bin_count - 1)].add(i);
185 }
186 },
187 [&] (LocalBins& result, LocalBins&& other) { result.merge(std::move(other)); });
188
189 // Note: Merging small bins will deteriorate the quality of the top BVH if there is no
190 // pruning, since it will then produce larger mini-trees. For this reason, it is only enabled
191 // when mini-tree pruning is enabled.
193 final_bins.merge_small_bins(config_.parallel_threshold);
194 final_bins.remove_empty_bins();
195
196 // Iterate over bins to collect groups of primitives and build BVHs over them in parallel
197 std::vector<Bvh<Node>> mini_trees(final_bins.bins.size());
198 for (size_t i = 0; i < final_bins.bins.size(); ++i) {
199 auto task = new BuildTask(this, mini_trees[i], std::move(final_bins[i].ids));
200 executor_.thread_pool.push([task] (size_t) { task->run(); delete task; });
201 }
203
204 return mini_trees;
205 }
206
207 std::vector<Bvh<Node>> prune_mini_trees(std::vector<Bvh<Node>>&& mini_trees) {
208 // Compute the area threshold based on the area of the entire set of primitives
209 auto avg_area = static_cast<Scalar>(0.);
210 for (auto& mini_tree : mini_trees)
211 avg_area += mini_tree.get_root().get_bbox().get_half_area();
212 avg_area /= static_cast<Scalar>(mini_trees.size());
213 auto threshold = avg_area * config_.pruning_area_ratio;
214
215 // Cull nodes whose area is above the threshold
216 std::stack<size_t> stack;
217 std::vector<std::pair<size_t, size_t>> pruned_roots;
218 for (size_t i = 0; i < mini_trees.size(); ++i) {
219 stack.push(0);
220 auto& mini_tree = mini_trees[i];
221 while (!stack.empty()) {
222 auto node_id = stack.top();
223 auto& node = mini_tree.nodes[node_id];
224 stack.pop();
225 if (node.get_bbox().get_half_area() < threshold || node.is_leaf()) {
226 pruned_roots.emplace_back(i, node_id);
227 } else {
228 stack.push(node.index.first_id());
229 stack.push(node.index.first_id() + 1);
230 }
231 }
232 }
233
234 // Extract the BVHs rooted at the previously computed indices
235 std::vector<Bvh<Node>> pruned_trees(pruned_roots.size());
236 executor_.for_each(0, pruned_roots.size(),
237 [&] (size_t begin, size_t end) {
238 for (size_t i = begin; i < end; ++i) {
239 if (pruned_roots[i].second == 0)
240 pruned_trees[i] = std::move(mini_trees[pruned_roots[i].first]);
241 else
242 pruned_trees[i] = mini_trees[pruned_roots[i].first]
243 .extract_bvh(pruned_roots[i].second);
244 }
245 });
246 return pruned_trees;
247 }
248
249 Bvh<Node> build_top_bvh(std::vector<Bvh<Node>>& mini_trees) {
250 // Build a BVH using the mini trees as leaves
251 std::vector<Vec> centers(mini_trees.size());
252 std::vector<BBox> bboxes(mini_trees.size());
253 for (size_t i = 0; i < mini_trees.size(); ++i) {
254 bboxes[i] = mini_trees[i].get_root().get_bbox();
255 centers[i] = bboxes[i].get_center();
256 }
257
258 typename SweepSahBuilder<Node>::Config config = config_;
259 config.max_leaf_size = config.min_leaf_size = 1; // Needs to have only one mini-tree in each leaf
260 auto bvh = SweepSahBuilder<Node>::build(bboxes, centers, config);
261
262 // Compute the offsets to apply to primitive and node indices
263 std::vector<size_t> node_offsets(mini_trees.size());
264 std::vector<size_t> prim_offsets(mini_trees.size());
265 size_t node_count = bvh.nodes.size();
266 size_t prim_count = 0;
267 for (size_t i = 0; i < mini_trees.size(); ++i) {
268 node_offsets[i] = node_count - 1; // Skip root node
269 prim_offsets[i] = prim_count;
270 node_count += mini_trees[i].nodes.size() - 1; // idem
271 prim_count += mini_trees[i].prim_ids.size();
272 }
273
274 // Helper function to copy and fix the child/primitive index of a node
275 auto copy_node = [&] (size_t i, Node& dst_node, const Node& src_node) {
276 dst_node = src_node;
277 dst_node.index.set_first_id(dst_node.index.first_id() +
278 (src_node.is_leaf() ? prim_offsets[i] : node_offsets[i]));
279 };
280
281 // Make the leaves of the top BVH point to the right internal nodes
282 for (auto& node : bvh.nodes) {
283 if (!node.is_leaf())
284 continue;
285 assert(node.index.prim_count() == 1);
286 size_t tree_id = bvh.prim_ids[node.index.first_id()];
287 copy_node(tree_id, node, mini_trees[tree_id].get_root());
288 }
289
290 bvh.nodes.resize(node_count);
291 bvh.prim_ids.resize(prim_count);
292 executor_.for_each(0, mini_trees.size(),
293 [&] (size_t begin, size_t end) {
294 for (size_t i = begin; i < end; ++i) {
295 auto& mini_tree = mini_trees[i];
296
297 // Copy the nodes of the mini tree with the offsets applied, without copying
298 // the root node (since it is already copied to the top-level part of the BVH).
299 for (size_t j = 1; j < mini_tree.nodes.size(); ++j)
300 copy_node(i, bvh.nodes[node_offsets[i] + j], mini_tree.nodes[j]);
301
302 std::copy(
303 mini_tree.prim_ids.begin(),
304 mini_tree.prim_ids.end(),
305 bvh.prim_ids.begin() + prim_offsets[i]);
306 }
307 });
308
309 return bvh;
310 }
311};
312
313} // namespace bvh::v2
314
315#endif
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Multi-threaded top-down builder that partitions primitives using a grid.
static BVH_ALWAYS_INLINE Bvh< Node > build(ThreadPool &thread_pool, std::span< const BBox > bboxes, std::span< const Vec > centers, const Config &config={})
Starts building a BVH with the given primitive data.
std::span< const BBox > bboxes_
std::span< const Vec > centers_
std::vector< Bvh< Node > > build_mini_trees()
std::vector< Bvh< Node > > prune_mini_trees(std::vector< Bvh< Node > > &&mini_trees)
BVH_ALWAYS_INLINE MiniTreeBuilder(ThreadPool &thread_pool, std::span< const BBox > bboxes, std::span< const Vec > centers, const Config &config)
bvh::v2::Vec< Scalar, Node::dimension > Vec
Bvh< Node > build_top_bvh(std::vector< Bvh< Node > > &mini_trees)
typename Node::Scalar Scalar
Single-threaded top-down builder that partitions primitives based on the Surface Area Heuristic (SAH)...
void push(Task &&fun)
Definition thread_pool.h:48
Base class for all SAH-based, top-down builders.
const Int_t n
Definition legend1.C:16
Definition bbox.h:9
BVH_ALWAYS_INLINE T safe_inverse(T x)
Computes the inverse of the given value, always returning a finite value.
Definition utils.h:59
Definition bbox.h:9
#define BVH_ALWAYS_INLINE
Definition platform.h:19
static BVH_ALWAYS_INLINE constexpr BBox make_empty()
Definition bbox.h:40
BVH_ALWAYS_INLINE void merge(Bin &&other)
BVH_ALWAYS_INLINE void add(size_t id)
BuildTask(MiniTreeBuilder *builder, Bvh< Node > &bvh, std::vector< size_t > &&prim_ids)
size_t log2_grid_dim
Log of the dimension of the grid used to split the workload horizontally.
Scalar pruning_area_ratio
Threshold on the area of a mini-tree node above which it is pruned, expressed in fraction of the area...
bool enable_pruning
Flag that turns on/off mini-tree pruning.
size_t parallel_threshold
Minimum number of primitives per parallel task.
BVH_ALWAYS_INLINE void merge_small_bins(size_t threshold)
BVH_ALWAYS_INLINE Bin & operator[](size_t i)
BVH_ALWAYS_INLINE void remove_empty_bins()
BVH_ALWAYS_INLINE void merge(LocalBins &&other)
Binary BVH node, containing its bounds and an index into its children or the primitives it contains.
Definition node.h:23
static constexpr size_t dimension
Definition node.h:27
Executor that executes in parallel using the given thread pool.
Definition executor.h:42
T reduce(size_t begin, size_t end, const T &init, const Reduce &reduce, const Join &join)
Definition executor.h:64
void for_each(size_t begin, size_t end, const Loop &loop)
Definition executor.h:51
ThreadPool & thread_pool
Definition executor.h:43
size_t min_leaf_size
Nodes containing less than this amount of primitives will not be split.
size_t max_leaf_size
Nodes that cannot be split based on the SAH and have a number of primitives larger than this will be ...