OpenVDB  12.0.0
LeafManager.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: Apache-2.0
3 
4 /// @file LeafManager.h
5 ///
6 /// @brief A LeafManager manages a linear array of pointers to a given tree's
7 /// leaf nodes, as well as optional auxiliary buffers (one or more per leaf)
8 /// that can be swapped with the leaf nodes' voxel data buffers.
9 /// @details The leaf array is useful for multithreaded computations over
10 /// leaf voxels in a tree with static topology but varying voxel values.
11 /// The auxiliary buffers are convenient for temporal integration.
12 /// Efficient methods are provided for multithreaded swapping and synching
13 /// (i.e., copying the contents) of these buffers.
14 
15 #ifndef OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED
16 #define OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED
17 
18 #include <openvdb/Types.h>
19 #include <openvdb/util/Assert.h>
20 #include "RootNode.h" // for NodeChain
21 #include <tbb/blocked_range.h>
22 #include <tbb/parallel_for.h>
23 #include <tbb/parallel_reduce.h>
24 #include <deque>
25 #include <functional>
26 #include <type_traits>
27 
28 
29 namespace openvdb {
31 namespace OPENVDB_VERSION_NAME {
32 namespace tree {
33 
34 namespace leafmgr {
35 
36 //@{
37 /// Useful traits for Tree types
38 template<typename TreeT> struct TreeTraits {
39  static const bool IsConstTree = false;
40  using LeafIterType = typename TreeT::LeafIter;
41 };
42 template<typename TreeT> struct TreeTraits<const TreeT> {
43  static const bool IsConstTree = true;
44  using LeafIterType = typename TreeT::LeafCIter;
45 };
46 //@}
47 
48 } // namespace leafmgr
49 
50 
51 /// This helper class implements LeafManager methods that need to be
52 /// specialized for const vs. non-const trees.
53 template<typename ManagerT>
55 {
56  using RangeT = typename ManagerT::RangeType;
57  using LeafT = typename ManagerT::LeafType;
58  using BufT = typename ManagerT::BufferType;
59 
60  static inline void doSwapLeafBuffer(const RangeT& r, size_t auxBufferIdx,
61  LeafT** leafs, BufT* bufs, size_t bufsPerLeaf)
62  {
63  for (size_t n = r.begin(), m = r.end(), N = bufsPerLeaf; n != m; ++n) {
64  leafs[n]->swap(bufs[n * N + auxBufferIdx]);
65  }
66  }
67 };
68 
69 
70 ////////////////////////////////////////
71 
72 
73 /// @brief This class manages a linear array of pointers to a given tree's
74 /// leaf nodes, as well as optional auxiliary buffers (one or more per leaf)
75 /// that can be swapped with the leaf nodes' voxel data buffers.
76 /// @details The leaf array is useful for multithreaded computations over
77 /// leaf voxels in a tree with static topology but varying voxel values.
78 /// The auxiliary buffers are convenient for temporal integration.
79 /// Efficient methods are provided for multithreaded swapping and sync'ing
80 /// (i.e., copying the contents) of these buffers.
81 ///
82 /// @note Buffer index 0 denotes a leaf node's internal voxel data buffer.
83 /// Any auxiliary buffers are indexed starting from one.
84 template<typename TreeT>
86 {
87 public:
88  using TreeType = TreeT;
89  using ValueType = typename TreeT::ValueType;
90  using RootNodeType = typename TreeT::RootNodeType;
91  using NonConstLeafType = typename TreeType::LeafNodeType;
95  using NonConstBufferType = typename LeafType::Buffer;
97  using RangeType = tbb::blocked_range<size_t>; // leaf index range
98  static const Index DEPTH = 2; // root + leaf nodes
99 
100  static const bool IsConstTree = leafmgr::TreeTraits<TreeT>::IsConstTree;
101 
102  class LeafRange
103  {
104  public:
105  class Iterator
106  {
107  public:
108  Iterator(const LeafRange& range, size_t pos): mRange(range), mPos(pos)
109  {
110  OPENVDB_ASSERT(this->isValid());
111  }
112  Iterator(const Iterator&) = default;
113  Iterator& operator=(const Iterator&) = default;
114  /// Advance to the next leaf node.
115  Iterator& operator++() { ++mPos; return *this; }
116  /// Return a reference to the leaf node to which this iterator is pointing.
117  LeafType& operator*() const { return mRange.mLeafManager.leaf(mPos); }
118  /// Return a pointer to the leaf node to which this iterator is pointing.
119  LeafType* operator->() const { return &(this->operator*()); }
120  /// @brief Return the nth buffer for the leaf node to which this iterator is pointing,
121  /// where n = @a bufferIdx and n = 0 corresponds to the leaf node's own buffer.
122  BufferType& buffer(size_t bufferIdx)
123  {
124  return mRange.mLeafManager.getBuffer(mPos, bufferIdx);
125  }
126  /// Return the index into the leaf array of the current leaf node.
127  size_t pos() const { return mPos; }
128  /// Return @c true if the position of this iterator is in a valid range.
129  bool isValid() const { return mPos>=mRange.mBegin && mPos<=mRange.mEnd; }
130  /// Return @c true if this iterator is not yet exhausted.
131  bool test() const { return mPos < mRange.mEnd; }
132  /// Return @c true if this iterator is not yet exhausted.
133  operator bool() const { return this->test(); }
134  /// Return @c true if this iterator is exhausted.
135  bool empty() const { return !this->test(); }
136  bool operator!=(const Iterator& other) const
137  {
138  return (mPos != other.mPos) || (&mRange != &other.mRange);
139  }
140  bool operator==(const Iterator& other) const { return !(*this != other); }
141  const LeafRange& leafRange() const { return mRange; }
142 
143  private:
144  const LeafRange& mRange;
145  size_t mPos;
146  };// end Iterator
147 
148  LeafRange(size_t begin, size_t end, const LeafManager& leafManager, size_t grainSize=1)
149  : mEnd(end)
150  , mBegin(begin)
151  , mGrainSize(grainSize)
152  , mLeafManager(leafManager)
153  {
154  }
155 
156  Iterator begin() const {return Iterator(*this, mBegin);}
157 
158  Iterator end() const {return Iterator(*this, mEnd);}
159 
160  size_t size() const { return mEnd - mBegin; }
161 
162  size_t grainsize() const { return mGrainSize; }
163 
164  const LeafManager& leafManager() const { return mLeafManager; }
165 
166  bool empty() const {return !(mBegin < mEnd);}
167 
168  bool is_divisible() const {return mGrainSize < this->size();}
169 
171  : mEnd(r.mEnd)
172  , mBegin(doSplit(r))
173  , mGrainSize(r.mGrainSize)
174  , mLeafManager(r.mLeafManager)
175  {
176  }
177 
178  private:
179  size_t mEnd, mBegin, mGrainSize;
180  const LeafManager& mLeafManager;
181 
182  static size_t doSplit(LeafRange& r)
183  {
185  size_t middle = r.mBegin + (r.mEnd - r.mBegin) / 2u;
186  r.mEnd = middle;
187  return middle;
188  }
189  };// end of LeafRange
190 
191  /// @brief Constructor from a tree reference and an auxiliary buffer count
192  /// @note The default is no auxiliary buffers
193  LeafManager(TreeType& tree, size_t auxBuffersPerLeaf=0, bool serial=false)
194  : mTree(&tree)
195  , mLeafCount(0)
196  , mAuxBufferCount(0)
197  , mAuxBuffersPerLeaf(auxBuffersPerLeaf)
198  {
199  this->rebuild(serial);
200  }
201 
202  /// @brief Construct directly from an existing array of leafnodes.
203  /// @warning The leafnodes are implicitly assumed to exist in the
204  /// input @a tree.
205  LeafManager(TreeType& tree, LeafType** begin, LeafType** end,
206  size_t auxBuffersPerLeaf=0, bool serial=false)
207  : mTree(&tree)
208  , mLeafCount(end-begin)
209  , mAuxBufferCount(0)
210  , mAuxBuffersPerLeaf(auxBuffersPerLeaf)
211  , mLeafPtrs(new LeafType*[mLeafCount])
212  , mLeafs(mLeafPtrs.get())
213  {
214  size_t n = mLeafCount;
215  LeafType **target = mLeafs, **source = begin;
216  while (n--) *target++ = *source++;
217  if (auxBuffersPerLeaf) this->initAuxBuffers(serial);
218  }
219 
220  /// Shallow copy constructor called by tbb::parallel_for() threads
221  ///
222  /// @note This should never get called directly
223  LeafManager(const LeafManager& other)
224  : mTree(other.mTree)
225  , mLeafCount(other.mLeafCount)
226  , mAuxBufferCount(other.mAuxBufferCount)
227  , mAuxBuffersPerLeaf(other.mAuxBuffersPerLeaf)
228  , mLeafs(other.mLeafs)
229  , mAuxBuffers(other.mAuxBuffers)
230  , mTask(other.mTask)
231  {
232  }
233 
234  /// @brief (Re)initialize by resizing (if necessary) and repopulating the leaf array
235  /// and by deleting existing auxiliary buffers and allocating new ones.
236  /// @details Call this method if the tree's topology, and therefore the number
237  /// of leaf nodes, changes. New auxiliary buffers are initialized with copies
238  /// of corresponding leaf node buffers.
239  void rebuild(bool serial=false)
240  {
241  this->initLeafArray(serial);
242  this->initAuxBuffers(serial);
243  }
244  //@{
245  /// Repopulate the leaf array and delete and reallocate auxiliary buffers.
246  void rebuild(size_t auxBuffersPerLeaf, bool serial=false)
247  {
248  mAuxBuffersPerLeaf = auxBuffersPerLeaf;
249  this->rebuild(serial);
250  }
251  void rebuild(TreeType& tree, bool serial=false)
252  {
253  mTree = &tree;
254  this->rebuild(serial);
255  }
256  void rebuild(TreeType& tree, size_t auxBuffersPerLeaf, bool serial=false)
257  {
258  mTree = &tree;
259  mAuxBuffersPerLeaf = auxBuffersPerLeaf;
260  this->rebuild(serial);
261  }
262  //@}
263  /// @brief Change the number of auxiliary buffers.
264  /// @details If auxBuffersPerLeaf is 0, all existing auxiliary buffers are deleted.
265  /// New auxiliary buffers are initialized with copies of corresponding leaf node buffers.
266  /// This method does not rebuild the leaf array.
267  void rebuildAuxBuffers(size_t auxBuffersPerLeaf, bool serial=false)
268  {
269  mAuxBuffersPerLeaf = auxBuffersPerLeaf;
270  this->initAuxBuffers(serial);
271  }
272  /// @brief Remove the auxiliary buffers, but don't rebuild the leaf array.
273  void removeAuxBuffers() { this->rebuildAuxBuffers(0); }
274 
275  /// @brief Remove the auxiliary buffers and rebuild the leaf array.
276  void rebuildLeafArray(bool serial = false)
277  {
278  this->removeAuxBuffers();
279  this->initLeafArray(serial);
280  }
281 
282  /// @brief Return the total number of allocated auxiliary buffers.
283  size_t auxBufferCount() const { return mAuxBufferCount; }
284  /// @brief Return the number of auxiliary buffers per leaf node.
285  size_t auxBuffersPerLeaf() const { return mAuxBuffersPerLeaf; }
286 
287  /// @brief Return the number of leaf nodes.
288  size_t leafCount() const { return mLeafCount; }
289 
290  /// @brief Return the number of active voxels in the leaf nodes.
291  /// @note Multi-threaded for better performance than Tree::activeLeafVoxelCount
293  {
294  return tbb::parallel_reduce(this->leafRange(), Index64(0),
295  [] (const LeafRange& range, Index64 sum) -> Index64 {
296  for (const auto& leaf: range) { sum += leaf.onVoxelCount(); }
297  return sum;
298  },
299  [] (Index64 n, Index64 m) -> Index64 { return n + m; });
300  }
301 
302  /// Return a const reference to tree associated with this manager.
303  const TreeType& tree() const { return *mTree; }
304 
305  /// Return a reference to the tree associated with this manager.
306  TreeType& tree() { return *mTree; }
307 
308  /// Return a const reference to root node associated with this manager.
309  const RootNodeType& root() const { return mTree->root(); }
310 
311  /// Return a reference to the root node associated with this manager.
312  RootNodeType& root() { return mTree->root(); }
313 
314  /// Return @c true if the tree associated with this manager is immutable.
315  bool isConstTree() const { return this->IsConstTree; }
316 
317  /// @brief Return a pointer to the leaf node at index @a leafIdx in the array.
318  /// @note For performance reasons no range check is performed (other than an assertion)!
319  LeafType& leaf(size_t leafIdx) const { OPENVDB_ASSERT(leafIdx<mLeafCount); return *mLeafs[leafIdx]; }
320 
321  /// @brief Return the leaf or auxiliary buffer for the leaf node at index @a leafIdx.
322  /// If @a bufferIdx is zero, return the leaf buffer, otherwise return the nth
323  /// auxiliary buffer, where n = @a bufferIdx - 1.
324  ///
325  /// @note For performance reasons no range checks are performed on the inputs
326  /// (other than assertions)! Since auxiliary buffers, unlike leaf buffers,
327  /// might not exist, be especially careful when specifying the @a bufferIdx.
328  /// @note For const trees, this method always returns a reference to a const buffer.
329  /// It is safe to @c const_cast and modify any auxiliary buffer (@a bufferIdx > 0),
330  /// but it is not safe to modify the leaf buffer (@a bufferIdx = 0).
331  BufferType& getBuffer(size_t leafIdx, size_t bufferIdx) const
332  {
333  OPENVDB_ASSERT(leafIdx < mLeafCount);
334  OPENVDB_ASSERT(bufferIdx == 0 || bufferIdx - 1 < mAuxBuffersPerLeaf);
335  return bufferIdx == 0 ? mLeafs[leafIdx]->buffer()
336  : mAuxBuffers[leafIdx * mAuxBuffersPerLeaf + bufferIdx - 1];
337  }
338 
339  /// @brief Return a @c tbb::blocked_range of leaf array indices.
340  ///
341  /// @note Consider using leafRange() instead, which provides access methods
342  /// to leaf nodes and buffers.
343  RangeType getRange(size_t grainsize = 1) const { return RangeType(0, mLeafCount, grainsize); }
344 
345  /// Return a TBB-compatible LeafRange.
346  LeafRange leafRange(size_t grainsize = 1) const
347  {
348  return LeafRange(0, mLeafCount, *this, grainsize);
349  }
350 
351  /// @brief Swap each leaf node's buffer with the nth corresponding auxiliary buffer,
352  /// where n = @a bufferIdx.
353  /// @return @c true if the swap was successful
354  /// @param bufferIdx index of the buffer that will be swapped with
355  /// the corresponding leaf node buffer
356  /// @param serial if false, swap buffers in parallel using multiple threads.
357  /// @note Recall that the indexing of auxiliary buffers is 1-based, since
358  /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes
359  /// the first auxiliary buffer.
360  bool swapLeafBuffer(size_t bufferIdx, bool serial = false)
361  {
362  namespace ph = std::placeholders;
363  if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf || this->isConstTree()) return false;
364  mTask = std::bind(&LeafManager::doSwapLeafBuffer, ph::_1, ph::_2, bufferIdx - 1);
365  this->cook(serial ? 0 : 512);
366  return true;//success
367  }
368  /// @brief Swap any two buffers for each leaf node.
369  /// @note Recall that the indexing of auxiliary buffers is 1-based, since
370  /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes
371  /// the first auxiliary buffer.
372  bool swapBuffer(size_t bufferIdx1, size_t bufferIdx2, bool serial = false)
373  {
374  namespace ph = std::placeholders;
375  const size_t b1 = std::min(bufferIdx1, bufferIdx2);
376  const size_t b2 = std::max(bufferIdx1, bufferIdx2);
377  if (b1 == b2 || b2 > mAuxBuffersPerLeaf) return false;
378  if (b1 == 0) {
379  if (this->isConstTree()) return false;
380  mTask = std::bind(&LeafManager::doSwapLeafBuffer, ph::_1, ph::_2, b2-1);
381  } else {
382  mTask = std::bind(&LeafManager::doSwapAuxBuffer, ph::_1, ph::_2, b1-1, b2-1);
383  }
384  this->cook(serial ? 0 : 512);
385  return true;//success
386  }
387 
388  /// @brief Sync up the specified auxiliary buffer with the corresponding leaf node buffer.
389  /// @return @c true if the sync was successful
390  /// @param bufferIdx index of the buffer that will contain a
391  /// copy of the corresponding leaf node buffer
392  /// @param serial if false, sync buffers in parallel using multiple threads.
393  /// @note Recall that the indexing of auxiliary buffers is 1-based, since
394  /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes
395  /// the first auxiliary buffer.
396  bool syncAuxBuffer(size_t bufferIdx, bool serial = false)
397  {
398  namespace ph = std::placeholders;
399  if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf) return false;
400  mTask = std::bind(&LeafManager::doSyncAuxBuffer, ph::_1, ph::_2, bufferIdx - 1);
401  this->cook(serial ? 0 : 64);
402  return true;//success
403  }
404 
405  /// @brief Sync up all auxiliary buffers with their corresponding leaf node buffers.
406  /// @return true if the sync was successful
407  /// @param serial if false, sync buffers in parallel using multiple threads.
408  bool syncAllBuffers(bool serial = false)
409  {
410  namespace ph = std::placeholders;
411  switch (mAuxBuffersPerLeaf) {
412  case 0: return false;//nothing to do
413  case 1: mTask = std::bind(&LeafManager::doSyncAllBuffers1, ph::_1, ph::_2); break;
414  case 2: mTask = std::bind(&LeafManager::doSyncAllBuffers2, ph::_1, ph::_2); break;
415  default: mTask = std::bind(&LeafManager::doSyncAllBuffersN, ph::_1, ph::_2); break;
416  }
417  this->cook(serial ? 0 : 64);
418  return true;//success
419  }
420 
421  /// @brief Threaded method that applies a user-supplied functor
422  /// to each leaf node in the LeafManager.
423  ///
424  /// @details The user-supplied functor needs to define the methods
425  /// required for tbb::parallel_for.
426  ///
427  /// @param op user-supplied functor, see examples for interface details.
428  /// @param threaded optional toggle to disable threading, on by default.
429  /// @param grainSize optional parameter to specify the grainsize
430  /// for threading, one by default.
431  ///
432  /// @warning The functor object is deep-copied to create TBB tasks.
433  /// This allows the function to use non-thread-safe members
434  /// like a ValueAccessor.
435  ///
436  /// @par Example:
437  /// @code
438  /// // Functor to offset a tree's voxel values with values from another tree.
439  /// template<typename TreeType>
440  /// struct OffsetOp
441  /// {
442  /// using Accessor = tree::ValueAccessor<const TreeType>;
443  ///
444  /// OffsetOp(const TreeType& tree): mRhsTreeAcc(tree) {}
445  ///
446  /// template <typename LeafNodeType>
447  /// void operator()(LeafNodeType &lhsLeaf, size_t) const
448  /// {
449  /// const LeafNodeType *rhsLeaf = mRhsTreeAcc.probeConstLeaf(lhsLeaf.origin());
450  /// if (rhsLeaf) {
451  /// typename LeafNodeType::ValueOnIter iter = lhsLeaf.beginValueOn();
452  /// for (; iter; ++iter) {
453  /// iter.setValue(iter.getValue() + rhsLeaf->getValue(iter.pos()));
454  /// }
455  /// }
456  /// }
457  /// Accessor mRhsTreeAcc;
458  /// };
459  ///
460  /// // usage:
461  /// tree::LeafManager<FloatTree> leafNodes(lhsTree);
462  /// leafNodes.foreach(OffsetOp<FloatTree>(rhsTree));
463  ///
464  /// // A functor that performs a min operation between different auxiliary buffers.
465  /// template<typename LeafManagerType>
466  /// struct MinOp
467  /// {
468  /// using BufferType = typename LeafManagerType::BufferType;
469  ///
470  /// MinOp(LeafManagerType& leafNodes): mLeafs(leafNodes) {}
471  ///
472  /// template <typename LeafNodeType>
473  /// void operator()(LeafNodeType &leaf, size_t leafIndex) const
474  /// {
475  /// // get the first buffer
476  /// BufferType& buffer = mLeafs.getBuffer(leafIndex, 1);
477  ///
478  /// // min ...
479  /// }
480  /// LeafManagerType& mLeafs;
481  /// };
482  /// @endcode
483  template<typename LeafOp>
484  void foreach(const LeafOp& op, bool threaded = true, size_t grainSize=1)
485  {
486  LeafTransformer<LeafOp> transform(op);
487  transform.run(this->leafRange(grainSize), threaded);
488  }
489 
490  /// @brief Threaded method that applies a user-supplied functor
491  /// to each leaf node in the LeafManager. Unlike foreach
492  /// (defined above) this method performs a reduction on
493  /// all the leaf nodes.
494  ///
495  /// @details The user-supplied functor needs to define the methods
496  /// required for tbb::parallel_reduce.
497  ///
498  /// @param op user-supplied functor, see examples for interface details.
499  /// @param threaded optional toggle to disable threading, on by default.
500  /// @param grainSize optional parameter to specify the grainsize
501  /// for threading, one by default.
502  ///
503  /// @warning The functor object is deep-copied to create TBB tasks.
504  /// This allows the function to use non-thread-safe members
505  /// like a ValueAccessor.
506  ///
507  /// @par Example:
508  /// @code
509  /// // Functor to count the number of negative (active) leaf values
510  /// struct CountOp
511  /// {
512  /// CountOp() : mCounter(0) {}
513  /// CountOp(const CountOp &other) : mCounter(other.mCounter) {}
514  /// CountOp(const CountOp &other, tbb::split) : mCounter(0) {}
515  /// template <typename LeafNodeType>
516  /// void operator()(LeafNodeType &leaf, size_t)
517  /// {
518  /// typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn();
519  /// for (; iter; ++iter) if (*iter < 0.0f) ++mCounter;
520  /// }
521  /// void join(const CountOp &other) {mCounter += other.mCounter;}
522  /// size_t mCounter;
523  /// };
524  ///
525  /// // usage:
526  /// tree::LeafManager<FloatTree> leafNodes(tree);
527  /// MinValueOp min;
528  /// leafNodes.reduce(min);
529  /// std::cerr << "Number of negative active voxels = " << min.mCounter << std::endl;
530  ///
531  /// @endcode
532  template<typename LeafOp>
533  void reduce(LeafOp& op, bool threaded = true, size_t grainSize=1)
534  {
535  LeafReducer<LeafOp> transform(op);
536  transform.run(this->leafRange(grainSize), threaded);
537  }
538 
539  /// @brief Generate a linear array of prefix sums of offsets into the
540  /// active voxels in the leafs. So @a offsets[n]+m is the offset to the
541  /// mth active voxel in the nth leaf node (useful for
542  /// user-managed value buffers, e.g. in tools/LevelSetAdvect.h).
543  /// @return The total number of active values in the leaf nodes
544  /// @param offsets array of prefix sums of offsets to active voxels
545  /// @param size on input, the size of @a offsets; on output, its new size
546  /// @param grainSize optional grain size for threading
547  /// @details If @a offsets is @c nullptr or @a size is smaller than the
548  /// total number of active voxels (the return value) then @a offsets
549  /// is reallocated and @a size equals the total number of active voxels.
550  size_t getPrefixSum(size_t*& offsets, size_t& size, size_t grainSize=1) const
551  {
552  if (offsets == nullptr || size < mLeafCount) {
553  delete [] offsets;
554  offsets = new size_t[mLeafCount];
555  size = mLeafCount;
556  }
557  size_t prefix = 0;
558  if ( grainSize > 0 ) {
559  PrefixSum tmp(this->leafRange( grainSize ), offsets, prefix);
560  } else {// serial
561  for (size_t i=0; i<mLeafCount; ++i) {
562  offsets[i] = prefix;
563  prefix += mLeafs[i]->onVoxelCount();
564  }
565  }
566  return prefix;
567  }
568 
569  ////////////////////////////////////////////////////////////////////////////////////
570  // All methods below are for internal use only and should never be called directly
571 
572  /// Used internally by tbb::parallel_for() - never call it directly!
573  void operator()(const RangeType& r) const
574  {
575  if (mTask) mTask(const_cast<LeafManager*>(this), r);
576  else OPENVDB_THROW(ValueError, "task is undefined");
577  }
578 
579 private:
580 
581  void initLeafArray(bool serial = false)
582  {
583  // Build an array of all nodes that have leaf nodes as their immediate children
584 
585  using NodeChainT = typename NodeChain<RootNodeType, RootNodeType::LEVEL>::Type;
586  using NonConstLeafParentT = typename NodeChainT::template Get</*Level=*/1>;
587  using LeafParentT = typename CopyConstness<TreeType, NonConstLeafParentT>::Type;
588 
589  std::deque<LeafParentT*> leafParents;
590  if constexpr(std::is_same<NonConstLeafParentT, RootNodeType>::value) {
591  leafParents.emplace_back(&mTree->root());
592  }
593  else {
594  mTree->getNodes(leafParents);
595  }
596 
597  // Compute the leaf counts for each node
598 
599  std::vector<Index64> leafCounts;
600  if (serial) {
601  leafCounts.reserve(leafParents.size());
602  for (LeafParentT* leafParent : leafParents) {
603  leafCounts.push_back(leafParent->childCount());
604  }
605  } else {
606  leafCounts.resize(leafParents.size());
607  tbb::parallel_for(
608  // with typical node sizes and SSE enabled, there are only a handful
609  // of instructions executed per-operation with a default grainsize
610  // of 1, so increase to 64 to reduce parallel scheduling overhead
611  tbb::blocked_range<size_t>(0, leafParents.size(), /*grainsize=*/64),
612  [&](tbb::blocked_range<size_t>& range)
613  {
614  for (size_t i = range.begin(); i < range.end(); i++) {
615  leafCounts[i] = leafParents[i]->childCount();
616  }
617  }
618  );
619  }
620 
621  // Turn leaf counts into a cumulative histogram and obtain total leaf count
622 
623  for (size_t i = 1; i < leafCounts.size(); i++) {
624  leafCounts[i] += leafCounts[i-1];
625  }
626 
627  const size_t leafCount = leafCounts.empty() ? 0 : leafCounts.back();
628 
629  // Allocate (or deallocate) the leaf pointer array
630 
631  if (leafCount != mLeafCount) {
632  if (leafCount > 0) {
633  mLeafPtrs.reset(new LeafType*[leafCount]);
634  mLeafs = mLeafPtrs.get();
635  } else {
636  mLeafPtrs.reset();
637  mLeafs = nullptr;
638  }
639  mLeafCount = leafCount;
640  }
641 
642  if (mLeafCount == 0) return;
643 
644  // Populate the leaf node pointers
645 
646  if (serial) {
647  LeafType** leafPtr = mLeafs;
648  for (LeafParentT* leafParent : leafParents) {
649  for (auto iter = leafParent->beginChildOn(); iter; ++iter) {
650  *leafPtr++ = &iter.getValue();
651  }
652  }
653  } else {
654  tbb::parallel_for(
655  tbb::blocked_range<size_t>(0, leafParents.size()),
656  [&](tbb::blocked_range<size_t>& range)
657  {
658  size_t i = range.begin();
659  LeafType** leafPtr = mLeafs;
660  if (i > 0) leafPtr += leafCounts[i-1];
661  for ( ; i < range.end(); i++) {
662  for (auto iter = leafParents[i]->beginChildOn(); iter; ++iter) {
663  *leafPtr++ = &iter.getValue();
664  }
665  }
666  }
667  );
668  }
669  }
670 
671  void initAuxBuffers(bool serial)
672  {
673  const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf;
674  if (auxBufferCount != mAuxBufferCount) {
675  if (auxBufferCount > 0) {
676  mAuxBufferPtrs.reset(new NonConstBufferType[auxBufferCount]);
677  mAuxBuffers = mAuxBufferPtrs.get();
678  } else {
679  mAuxBufferPtrs.reset();
680  mAuxBuffers = nullptr;
681  }
682  mAuxBufferCount = auxBufferCount;
683  }
684  this->syncAllBuffers(serial);
685  }
686 
687  void cook(size_t grainsize)
688  {
689  if (grainsize>0) {
690  tbb::parallel_for(this->getRange(grainsize), *this);
691  } else {
692  (*this)(this->getRange());
693  }
694  }
695 
696  void doSwapLeafBuffer(const RangeType& r, size_t auxBufferIdx)
697  {
699  r, auxBufferIdx, mLeafs, mAuxBuffers, mAuxBuffersPerLeaf);
700  }
701 
702  void doSwapAuxBuffer(const RangeType& r, size_t auxBufferIdx1, size_t auxBufferIdx2)
703  {
704  for (size_t N = mAuxBuffersPerLeaf, n = N*r.begin(), m = N*r.end(); n != m; n+=N) {
705  mAuxBuffers[n + auxBufferIdx1].swap(mAuxBuffers[n + auxBufferIdx2]);
706  }
707  }
708 
709  void doSyncAuxBuffer(const RangeType& r, size_t auxBufferIdx)
710  {
711  for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) {
712  mAuxBuffers[n*N + auxBufferIdx] = mLeafs[n]->buffer();
713  }
714  }
715 
716  void doSyncAllBuffers1(const RangeType& r)
717  {
718  for (size_t n = r.begin(), m = r.end(); n != m; ++n) {
719  mAuxBuffers[n] = mLeafs[n]->buffer();
720  }
721  }
722 
723  void doSyncAllBuffers2(const RangeType& r)
724  {
725  for (size_t n = r.begin(), m = r.end(); n != m; ++n) {
726  const BufferType& leafBuffer = mLeafs[n]->buffer();
727  mAuxBuffers[2*n ] = leafBuffer;
728  mAuxBuffers[2*n+1] = leafBuffer;
729  }
730  }
731 
732  void doSyncAllBuffersN(const RangeType& r)
733  {
734  for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) {
735  const BufferType& leafBuffer = mLeafs[n]->buffer();
736  for (size_t i=n*N, j=i+N; i!=j; ++i) mAuxBuffers[i] = leafBuffer;
737  }
738  }
739 
740  /// @brief Private member class that applies a user-defined
741  /// functor to perform parallel_for on all the leaf nodes.
742  template<typename LeafOp>
743  struct LeafTransformer
744  {
745  LeafTransformer(const LeafOp &leafOp) : mLeafOp(leafOp)
746  {
747  }
748  void run(const LeafRange &range, bool threaded) const
749  {
750  threaded ? tbb::parallel_for(range, *this) : (*this)(range);
751  }
752  void operator()(const LeafRange &range) const
753  {
754  for (typename LeafRange::Iterator it = range.begin(); it; ++it) mLeafOp(*it, it.pos());
755  }
756  const LeafOp mLeafOp;
757  };// LeafTransformer
758 
759  /// @brief Private member class that applies a user-defined
760  /// functor to perform parallel_reduce on all the leaf nodes.
761  template<typename LeafOp>
762  struct LeafReducer
763  {
764  LeafReducer(LeafOp &leafOp) : mLeafOp(&leafOp)
765  {
766  }
767  LeafReducer(const LeafReducer &other, tbb::split)
768  : mLeafOpPtr(std::make_unique<LeafOp>(*(other.mLeafOp), tbb::split()))
769  , mLeafOp(mLeafOpPtr.get())
770  {
771  }
772  void run(const LeafRange& range, bool threaded)
773  {
774  threaded ? tbb::parallel_reduce(range, *this) : (*this)(range);
775  }
776  void operator()(const LeafRange& range)
777  {
778  LeafOp &op = *mLeafOp;//local registry
779  for (typename LeafRange::Iterator it = range.begin(); it; ++it) op(*it, it.pos());
780  }
781  void join(const LeafReducer& other) { mLeafOp->join(*(other.mLeafOp)); }
782  std::unique_ptr<LeafOp> mLeafOpPtr;
783  LeafOp *mLeafOp = nullptr;
784  };// LeafReducer
785 
786  // Helper class to compute a prefix sum of offsets to active voxels
787  struct PrefixSum
788  {
789  PrefixSum(const LeafRange& r, size_t* offsets, size_t& prefix)
790  : mOffsets(offsets)
791  {
792  tbb::parallel_for( r, *this);
793  for (size_t i=0, leafCount = r.size(); i<leafCount; ++i) {
794  size_t tmp = offsets[i];
795  offsets[i] = prefix;
796  prefix += tmp;
797  }
798  }
799  inline void operator()(const LeafRange& r) const {
800  for (typename LeafRange::Iterator i = r.begin(); i; ++i) {
801  mOffsets[i.pos()] = i->onVoxelCount();
802  }
803  }
804  size_t* mOffsets;
805  };// PrefixSum
806 
807  using FuncType = typename std::function<void (LeafManager*, const RangeType&)>;
808 
809  TreeType* mTree;
810  size_t mLeafCount, mAuxBufferCount, mAuxBuffersPerLeaf;
811  std::unique_ptr<LeafType*[]> mLeafPtrs;
812  LeafType** mLeafs = nullptr;//array of LeafNode pointers
813  std::unique_ptr<NonConstBufferType[]> mAuxBufferPtrs;
814  NonConstBufferType* mAuxBuffers = nullptr;//array of auxiliary buffers
815  FuncType mTask = nullptr;
816 };//end of LeafManager class
817 
818 
819 // Partial specializations of LeafManager methods for const trees
820 template<typename TreeT>
821 struct LeafManagerImpl<LeafManager<const TreeT> >
822 {
824  using RangeT = typename ManagerT::RangeType;
825  using LeafT = typename ManagerT::LeafType;
826  using BufT = typename ManagerT::BufferType;
827 
828  static inline void doSwapLeafBuffer(const RangeT&, size_t /*auxBufferIdx*/,
829  LeafT**, BufT*, size_t /*bufsPerLeaf*/)
830  {
831  // Buffers can't be swapped into const trees.
832  }
833 };
834 
835 } // namespace tree
836 } // namespace OPENVDB_VERSION_NAME
837 } // namespace openvdb
838 
839 #endif // OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED
bool isValid() const
Return true if the position of this iterator is in a valid range.
Definition: LeafManager.h:129
LeafType & operator*() const
Return a reference to the leaf node to which this iterator is pointing.
Definition: LeafManager.h:117
typename CopyConstness< TreeType, NonConstLeafType >::Type LeafType
Definition: LeafManager.h:92
bool swapLeafBuffer(size_t bufferIdx, bool serial=false)
Swap each leaf node&#39;s buffer with the nth corresponding auxiliary buffer, where n = bufferIdx...
Definition: LeafManager.h:360
static void doSwapLeafBuffer(const RangeT &, size_t, LeafT **, BufT *, size_t)
Definition: LeafManager.h:828
typename TreeT::LeafCIter LeafIterType
Definition: LeafManager.h:44
Iterator & operator++()
Advance to the next leaf node.
Definition: LeafManager.h:115
bool isConstTree() const
Return true if the tree associated with this manager is immutable.
Definition: LeafManager.h:315
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:74
TreeType & tree()
Return a reference to the tree associated with this manager.
Definition: LeafManager.h:306
LeafManager(const LeafManager &other)
Definition: LeafManager.h:223
size_t auxBufferCount() const
Return the total number of allocated auxiliary buffers.
Definition: LeafManager.h:283
Iterator begin() const
Definition: LeafManager.h:156
__hostdev__ bool isValid(GridType gridType, GridClass gridClass)
return true if the combination of GridType and GridClass is valid.
Definition: NanoVDB.h:613
uint64_t Index64
Definition: Types.h:53
size_t grainsize() const
Definition: LeafManager.h:162
BufferType & getBuffer(size_t leafIdx, size_t bufferIdx) const
Return the leaf or auxiliary buffer for the leaf node at index leafIdx. If bufferIdx is zero...
Definition: LeafManager.h:331
typename ManagerT::RangeType RangeT
Definition: LeafManager.h:824
const LeafManager & leafManager() const
Definition: LeafManager.h:164
LeafManager(TreeType &tree, LeafType **begin, LeafType **end, size_t auxBuffersPerLeaf=0, bool serial=false)
Construct directly from an existing array of leafnodes.
Definition: LeafManager.h:205
typename std::remove_const< ToType >::type Type
Definition: Types.h:439
typename ManagerT::BufferType BufT
Definition: LeafManager.h:826
bool test() const
Return true if this iterator is not yet exhausted.
Definition: LeafManager.h:131
bool syncAllBuffers(bool serial=false)
Sync up all auxiliary buffers with their corresponding leaf node buffers.
Definition: LeafManager.h:408
void rebuildLeafArray(bool serial=false)
Remove the auxiliary buffers and rebuild the leaf array.
Definition: LeafManager.h:276
typename TreeType::LeafNodeType NonConstLeafType
Definition: LeafManager.h:91
typename TreeT::LeafIter LeafIterType
Definition: LeafManager.h:40
void removeAuxBuffers()
Remove the auxiliary buffers, but don&#39;t rebuild the leaf array.
Definition: LeafManager.h:273
Useful traits for Tree types.
Definition: LeafManager.h:38
Index32 Index
Definition: Types.h:54
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition: Composite.h:110
OutGridT XformOp & op
Definition: ValueTransformer.h:139
size_t size() const
Definition: LeafManager.h:160
RootNodeType & root()
Return a reference to the root node associated with this manager.
Definition: LeafManager.h:312
Mat3< typename promote< T0, T1 >::type > operator*(const Mat3< T0 > &m0, const Mat3< T1 > &m1)
Multiply m0 by m1 and return the resulting matrix.
Definition: Mat3.h:597
typename leafmgr::TreeTraits< TreeType >::LeafIterType LeafIterType
Definition: LeafManager.h:94
Definition: Exceptions.h:65
typename ManagerT::LeafType LeafT
Definition: LeafManager.h:57
bool is_divisible() const
Definition: LeafManager.h:168
Index64 activeLeafVoxelCount() const
Return the number of active voxels in the leaf nodes.
Definition: LeafManager.h:292
bool operator==(const Iterator &other) const
Definition: LeafManager.h:140
void split(ContainerT &out, const std::string &in, const char delim)
Definition: Name.h:43
size_t leafCount() const
Return the number of leaf nodes.
Definition: LeafManager.h:288
Definition: LeafManager.h:54
LeafType * operator->() const
Return a pointer to the leaf node to which this iterator is pointing.
Definition: LeafManager.h:119
#define OPENVDB_ASSERT(X)
Definition: Assert.h:41
Iterator(const LeafRange &range, size_t pos)
Definition: LeafManager.h:108
LeafRange leafRange(size_t grainsize=1) const
Return a TBB-compatible LeafRange.
Definition: LeafManager.h:346
LeafType & leaf(size_t leafIdx) const
Return a pointer to the leaf node at index leafIdx in the array.
Definition: LeafManager.h:319
bool operator!=(const Iterator &other) const
Definition: LeafManager.h:136
const LeafRange & leafRange() const
Definition: LeafManager.h:141
tbb::blocked_range< size_t > RangeType
Definition: LeafManager.h:97
OutGridT XformOp bool threaded
Definition: ValueTransformer.h:140
Definition: Exceptions.h:13
LeafManager(TreeType &tree, size_t auxBuffersPerLeaf=0, bool serial=false)
Constructor from a tree reference and an auxiliary buffer count.
Definition: LeafManager.h:193
typename ManagerT::RangeType RangeT
Definition: LeafManager.h:56
LeafType LeafNodeType
Definition: LeafManager.h:93
BufferType & buffer(size_t bufferIdx)
Return the nth buffer for the leaf node to which this iterator is pointing, where n = bufferIdx and n...
Definition: LeafManager.h:122
void rebuild(TreeType &tree, bool serial=false)
Repopulate the leaf array and delete and reallocate auxiliary buffers.
Definition: LeafManager.h:251
bool syncAuxBuffer(size_t bufferIdx, bool serial=false)
Sync up the specified auxiliary buffer with the corresponding leaf node buffer.
Definition: LeafManager.h:396
OutGridT const XformOp bool bool
Definition: ValueTransformer.h:609
OPENVDB_AX_API void run(const char *ax, openvdb::GridBase &grid, const AttributeBindings &bindings={})
Run a full AX pipeline (parse, compile and execute) on a single OpenVDB Grid.
static void doSwapLeafBuffer(const RangeT &r, size_t auxBufferIdx, LeafT **leafs, BufT *bufs, size_t bufsPerLeaf)
Definition: LeafManager.h:60
typename CopyConstness< TreeType, NonConstBufferType >::Type BufferType
Definition: LeafManager.h:96
void rebuild(TreeType &tree, size_t auxBuffersPerLeaf, bool serial=false)
Repopulate the leaf array and delete and reallocate auxiliary buffers.
Definition: LeafManager.h:256
size_t getPrefixSum(size_t *&offsets, size_t &size, size_t grainSize=1) const
Generate a linear array of prefix sums of offsets into the active voxels in the leafs. So offsets[n]+m is the offset to the mth active voxel in the nth leaf node (useful for user-managed value buffers, e.g. in tools/LevelSetAdvect.h).
Definition: LeafManager.h:550
void rebuild(size_t auxBuffersPerLeaf, bool serial=false)
Repopulate the leaf array and delete and reallocate auxiliary buffers.
Definition: LeafManager.h:246
bool empty() const
Definition: LeafManager.h:166
typename SubtreeT::template Append< HeadT > Type
Definition: RootNode.h:1039
LeafRange(size_t begin, size_t end, const LeafManager &leafManager, size_t grainSize=1)
Definition: LeafManager.h:148
void rebuildAuxBuffers(size_t auxBuffersPerLeaf, bool serial=false)
Change the number of auxiliary buffers.
Definition: LeafManager.h:267
The root node of an OpenVDB tree.
typename ManagerT::BufferType BufT
Definition: LeafManager.h:58
typename TreeType::ValueType ValueType
Definition: LeafManager.h:89
This class manages a linear array of pointers to a given tree&#39;s leaf nodes, as well as optional auxil...
Definition: LeafManager.h:85
Iterator end() const
Definition: LeafManager.h:158
void operator()(const RangeType &r) const
Used internally by tbb::parallel_for() - never call it directly!
Definition: LeafManager.h:573
void reduce(LeafOp &op, bool threaded=true, size_t grainSize=1)
Threaded method that applies a user-supplied functor to each leaf node in the LeafManager. Unlike foreach (defined above) this method performs a reduction on all the leaf nodes.
Definition: LeafManager.h:533
bool empty() const
Return true if this iterator is exhausted.
Definition: LeafManager.h:135
const std::enable_if<!VecTraits< T >::IsVec, T >::type & min(const T &a, const T &b)
Definition: Composite.h:106
bool swapBuffer(size_t bufferIdx1, size_t bufferIdx2, bool serial=false)
Swap any two buffers for each leaf node.
Definition: LeafManager.h:372
typename ManagerT::LeafType LeafT
Definition: LeafManager.h:825
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h.in:121
void rebuild(bool serial=false)
(Re)initialize by resizing (if necessary) and repopulating the leaf array and by deleting existing au...
Definition: LeafManager.h:239
typename TreeType::RootNodeType RootNodeType
Definition: LeafManager.h:90
const TreeType & tree() const
Return a const reference to tree associated with this manager.
Definition: LeafManager.h:303
size_t auxBuffersPerLeaf() const
Return the number of auxiliary buffers per leaf node.
Definition: LeafManager.h:285
size_t pos() const
Return the index into the leaf array of the current leaf node.
Definition: LeafManager.h:127
RangeType getRange(size_t grainsize=1) const
Return a tbb::blocked_range of leaf array indices.
Definition: LeafManager.h:343
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h.in:218
LeafRange(LeafRange &r, tbb::split)
Definition: LeafManager.h:170
const RootNodeType & root() const
Return a const reference to root node associated with this manager.
Definition: LeafManager.h:309