Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
binned_sah_builder.h
Go to the documentation of this file.
1#ifndef BVH_V2_BINNED_SAH_BUILDER_H
2#define BVH_V2_BINNED_SAH_BUILDER_H
3
5
6#include <stack>
7#include <tuple>
8#include <algorithm>
9#include <optional>
10#include <numeric>
11#include <cassert>
12
13namespace bvh::v2 {
14
15/// Single-threaded top-down builder that partitions primitives based on a binned approximation of
16/// the Surface Area Heuristic (SAH). This builder is inspired by
17/// "On Fast Construction of SAH-based Bounding Volume Hierarchies", by I. Wald.
18template <typename Node, size_t BinCount = 8>
19class BinnedSahBuilder : public TopDownSahBuilder<Node> {
21 using typename TopDownSahBuilder<Node>::Vec;
22 using typename TopDownSahBuilder<Node>::BBox;
23
28
29public:
31
33 std::span<const BBox> bboxes,
34 std::span<const Vec> centers,
35 const Config& config = {})
36 {
37 return BinnedSahBuilder(bboxes, centers, config).build();
38 }
39
40protected:
41 struct Split {
42 size_t bin_id;
44 size_t axis;
45 };
46
47 struct Bin {
49 size_t prim_count = 0;
50
51 Bin() = default;
52
54 return sah.get_leaf_cost(0, prim_count, bbox);
55 }
56
57 BVH_ALWAYS_INLINE void add(const BBox& bbox, size_t prim_count = 1) {
58 this->bbox.extend(bbox);
59 this->prim_count += prim_count;
60 }
61
62 BVH_ALWAYS_INLINE void add(const Bin& bin) { add(bin.bbox, bin.prim_count); }
63 };
64
65 using Bins = std::array<Bin, BinCount>;
66 using PerAxisBins = std::array<Bins, Node::dimension>;
67
68 std::vector<size_t> prim_ids_;
69
71 std::span<const BBox> bboxes,
72 std::span<const Vec> centers,
73 const Config& config)
74 : TopDownSahBuilder<Node>(bboxes, centers, config)
75 , prim_ids_(bboxes.size())
76 {
77 std::iota(prim_ids_.begin(), prim_ids_.end(), 0);
78 }
79
80 std::vector<size_t>& get_prim_ids() override { return prim_ids_; }
81
83 PerAxisBins& per_axis_bins,
84 const BBox& bbox,
85 size_t begin,
86 size_t end)
87 {
88 auto bin_scale = Vec(BinCount) / bbox.get_diagonal();
89 auto bin_offset = -bbox.min * bin_scale;
90
91 for (size_t i = begin; i < end; ++i) {
92 auto pos = fast_mul_add(centers_[prim_ids_[i]], bin_scale, bin_offset);
93 static_for<0, Node::dimension>([&] (size_t axis) {
94 size_t index = std::min(BinCount - 1,
95 static_cast<size_t>(robust_max(pos[axis], static_cast<Scalar>(0.))));
96 per_axis_bins[axis][index].add(bboxes_[prim_ids_[i]]);
97 });
98 }
99 }
100
101 void find_best_split(size_t axis, const Bins& bins, Split& best_split) {
102 Bin right_accum;
103 std::array<Scalar, BinCount> right_costs;
104 for (size_t i = BinCount - 1; i > 0; --i) {
105 right_accum.add(bins[i]);
106 right_costs[i] = right_accum.get_cost(config_.sah);
107 }
108
109 Bin left_accum;
110 for (size_t i = 0; i < BinCount - 1; ++i) {
111 left_accum.add(bins[i]);
112 auto cost = left_accum.get_cost(config_.sah) + right_costs[i + 1];
113 if (cost < best_split.cost)
114 best_split = Split { i + 1, cost, axis };
115 }
116 }
117
118 size_t fallback_split(size_t axis, size_t begin, size_t end) {
119 size_t mid = (begin + end + 1) / 2;
120 std::partial_sort(
121 prim_ids_.begin() + begin,
122 prim_ids_.begin() + mid,
123 prim_ids_.begin() + end,
124 [&] (size_t i, size_t j) { return centers_[i][axis] < centers_[j][axis]; });
125 return mid;
126 }
127
128 std::optional<size_t> try_split(const BBox& bbox, size_t begin, size_t end) override {
129 PerAxisBins per_axis_bins;
130 fill_bins(per_axis_bins, bbox, begin, end);
131
132 auto largest_axis = bbox.get_diagonal().get_largest_axis();
133 auto best_split = Split { BinCount / 2, std::numeric_limits<Scalar>::max(), largest_axis };
134 for (size_t axis = 0; axis < Node::dimension; ++axis)
135 find_best_split(axis, per_axis_bins[axis], best_split);
136
137 // Make sure that the split is good before proceeding with it
138 auto leaf_cost = config_.sah.get_non_split_cost(begin, end, bbox);
139 if (best_split.cost >= leaf_cost) {
140 if (end - begin <= config_.max_leaf_size)
141 return std::nullopt;
142 return fallback_split(largest_axis, begin, end);
143 }
144
145 auto split_pos = fast_mul_add(
146 bbox.get_diagonal()[best_split.axis] / static_cast<Scalar>(BinCount),
147 static_cast<Scalar>(best_split.bin_id),
148 bbox.min[best_split.axis]);
149
150 size_t index = std::partition(prim_ids_.begin() + begin, prim_ids_.begin() + end,
151 [&] (size_t i) { return centers_[i][best_split.axis] < split_pos; }) - prim_ids_.begin();
152 if (index == begin || index == end)
153 return fallback_split(largest_axis, begin, end);
154
155 return std::make_optional(index);
156 }
157};
158
159} // namespace bvh::v2
160
161#endif
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
Single-threaded top-down builder that partitions primitives based on a binned approximation of the Su...
size_t fallback_split(size_t axis, size_t begin, size_t end)
std::array< Bin, BinCount > Bins
BVH_ALWAYS_INLINE void fill_bins(PerAxisBins &per_axis_bins, const BBox &bbox, size_t begin, size_t end)
std::array< Bins, Node::dimension > PerAxisBins
std::optional< size_t > try_split(const BBox &bbox, size_t begin, size_t end) override
void find_best_split(size_t axis, const Bins &bins, Split &best_split)
static BVH_ALWAYS_INLINE Bvh< Node > build(std::span< const BBox > bboxes, std::span< const Vec > centers, const Config &config={})
std::vector< size_t > & get_prim_ids() override
std::vector< size_t > prim_ids_
BVH_ALWAYS_INLINE BinnedSahBuilder(std::span< const BBox > bboxes, std::span< const Vec > centers, const Config &config)
BVH_ALWAYS_INLINE T get_leaf_cost(size_t begin, size_t end, const BBox< T, N > &bbox) const
BVH_ALWAYS_INLINE T get_non_split_cost(size_t begin, size_t end, const BBox< T, N > &bbox) const
Base class for all SAH-based, top-down builders.
bvh::v2::Vec< Scalar, Node::dimension > Vec
std::span< const Vec > centers_
std::span< const BBox > bboxes_
Definition bbox.h:9
BVH_ALWAYS_INLINE T robust_max(T a, T b)
Definition utils.h:43
BVH_ALWAYS_INLINE T fast_mul_add(T a, T b, T c)
Fast multiply-add operation.
Definition utils.h:74
#define BVH_ALWAYS_INLINE
Definition platform.h:19
Vec< T, N > min
Definition bbox.h:13
BVH_ALWAYS_INLINE Vec< T, N > get_diagonal() const
Definition bbox.h:29
BVH_ALWAYS_INLINE BBox & extend(const Vec< T, N > &point)
Definition bbox.h:19
static BVH_ALWAYS_INLINE constexpr BBox make_empty()
Definition bbox.h:40
BVH_ALWAYS_INLINE void add(const BBox &bbox, size_t prim_count=1)
BVH_ALWAYS_INLINE Scalar get_cost(const SplitHeuristic< Scalar > &sah) const
BVH_ALWAYS_INLINE void add(const Bin &bin)
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
SplitHeuristic< Scalar > sah
SAH heuristic parameters that control how primitives are partitioned.
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 ...