Skip to content

Commit 888f309

Browse files
committed
Added back the optimized ValueAccessor size instantiation when a leaf buffer cannot be cached
Signed-off-by: Nick Avramoussis <[email protected]>
1 parent 8a68f14 commit 888f309

File tree

2 files changed

+158
-50
lines changed

2 files changed

+158
-50
lines changed

openvdb/openvdb/tree/ValueAccessor.h

Lines changed: 129 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,23 @@
33

44
/// @file tree/ValueAccessor.h
55
///
6-
/// @brief When traversing a grid in a spatially coherent pattern (e.g.,
7-
/// iterating over neighboring voxels), request a @c ValueAccessor from the
8-
/// grid (with Grid::getAccessor()) and use the accessor's @c getValue() and
9-
/// @c setValue() methods. These will typically be significantly faster than
10-
/// accessing voxels directly in the grid's tree.
6+
/// @brief ValueAccessors are designed to help accelerate accesses into the
7+
/// OpenVDB Tree structures by storing caches to Tree branches. When
8+
/// traversing a grid in a spatially coherent pattern (e.g., iterating over
9+
/// neighboring voxels), the same branches and nodes of the underlying tree
10+
/// can be hit. If you do this using the Tree/RootNode methods directly,
11+
/// traversal will occur at O(log(n)) (or O(n) depending on the hash map
12+
/// implementation) for every access. However, using a ValueAccessor allows
13+
/// for the Accessor to cache previously visited Nodes, providing possible
14+
/// subsequent access speeds of O(1) if the next access is close to a
15+
/// previously cached Node. Accessors are lightweight and can be configured
16+
/// to cache any number of arbitrary Tree levels.
17+
///
18+
/// The ValueAccessor interfaces matches that of compatible OpenVDB Tree
19+
/// nodes. You can request an Accessor from a Grid (with Grid::getAccessor())
20+
/// or construct one directly from a Tree. You can use, for example, the
21+
/// accessor's @c getValue() and @c setValue() methods in place of those on
22+
/// OpenVDB Nodes/Trees.
1123
///
1224
/// @par Example:
1325
/// @code
@@ -200,26 +212,7 @@ class ValueAccessorBase
200212

201213
///////////////////////////////////////////////////////////////////////////////
202214

203-
/// @brief A small class that contains a Mutex which is derived from by the
204-
/// internal Value Accessor Implementation. This allows for the empty base
205-
/// class optimization to be performed in the case where a Mutex/Lock is not
206-
/// in use. From C++20 we can instead switch to [[no_unique_address]].
207-
template <typename MutexT>
208-
struct ValueAccessorLock
209-
{
210-
inline auto lock() const { return std::scoped_lock(m); }
211-
private:
212-
mutable MutexT m;
213-
};
214-
215-
/// @brief Specialization for the case where no Mutex is in use. See above.
216-
template <>
217-
struct ValueAccessorLock<void>
218-
{
219-
inline constexpr auto lock() const { return 0; }
220-
};
221-
222-
///////////////////////////////////////////////////////////////////////////////
215+
/// @cond OPENVDB_DOCS_INTERNAL
223216

224217
namespace value_accessor_internal
225218
{
@@ -269,22 +262,115 @@ struct NodeListBuilder<NodeChainT, RootLevel, openvdb::index_sequence<Is...>>
269262
using ListT = typename NodeListBuilderImpl<NodeChainT, Is..., RootLevel>::ListT;
270263
};
271264

265+
266+
template<typename TreeTypeT, typename NodeT>
267+
struct EnableLeafBuffer
268+
{
269+
using LeafNodeT = typename TreeTypeT::LeafNodeType;
270+
static constexpr bool value =
271+
std::is_same<NodeT, LeafNodeT>::value &&
272+
std::is_same<typename LeafNodeT::Buffer::StorageType,
273+
typename LeafNodeT::ValueType>::value;
274+
};
275+
276+
template<typename TreeTypeT, size_t... Is>
277+
struct EnableLeafBuffer<TreeTypeT, openvdb::index_sequence<Is...>>
278+
{
279+
// Empty integer seq, no nodes being caches
280+
static constexpr bool value = false;
281+
};
282+
283+
template<typename TreeTypeT, size_t First, size_t... Is>
284+
struct EnableLeafBuffer<TreeTypeT, openvdb::index_sequence<First, Is...>>
285+
{
286+
private:
287+
using NodeChainT = typename TreeTypeT::RootNodeType::NodeChainType;
288+
using FirstNodeT = typename NodeChainT::template Get<First>;
289+
public:
290+
static constexpr bool value = EnableLeafBuffer<TreeTypeT, FirstNodeT>::value;
291+
};
292+
272293
} // namespace value_accessor_internal
273294

295+
/// @endcond
296+
297+
///////////////////////////////////////////////////////////////////////////////
298+
299+
/// The following classes exist to perform empty base class optimizations
300+
/// with the final ValueAccessor implementation. Depending on the template
301+
/// types provided to the derived implementation, some member variables may not
302+
/// be necessary (mutex, leaf buffer cache, etc). These classes allow for these
303+
/// variables to be compiled out. Note that from C++20 we can switch to
304+
/// [[no_unique_address]] member annotations instead.
305+
306+
/// @brief A small class that contains a Mutex which is derived from by the
307+
/// internal Value Accessor Implementation. This allows for the empty base
308+
/// class optimization to be performed in the case where a Mutex/Lock is not
309+
/// in use. From C++20 we can instead switch to [[no_unique_address]].
310+
template <typename MutexT>
311+
struct ValueAccessorLock
312+
{
313+
inline auto lock() const { return std::scoped_lock(m); }
314+
private:
315+
mutable MutexT m;
316+
};
317+
318+
/// @brief Specialization for the case where no Mutex is in use. See above.
319+
template <>
320+
struct ValueAccessorLock<void>
321+
{
322+
inline constexpr auto lock() const { return 0; }
323+
};
324+
325+
/// @brief A small class that contains a cached pointer to a LeafNode data
326+
/// buffer which is derived from by the internal Value Accessor
327+
/// Implementation. This allows for the empty base class optimization to be
328+
/// performed in the case where a LeafNode does not store a contiguous
329+
/// index-able buffer. From C++20 we can instead switch to
330+
/// [[no_unique_address]].
331+
template<typename TreeTypeT, typename IntegerSequence, typename Enable = void>
332+
struct ValueAccessorLeafBuffer
333+
{
334+
template <typename NodeT>
335+
static constexpr bool BypassLeafAPI =
336+
std::is_same<NodeT, typename TreeTypeT::LeafNodeType>::value;
337+
inline const typename TreeTypeT::ValueType* buffer() { assert(mBuffer); return mBuffer; }
338+
inline const typename TreeTypeT::ValueType* buffer() const { assert(mBuffer); return mBuffer; }
339+
inline void setBuffer(const typename TreeTypeT::ValueType* b) const { mBuffer = b; }
340+
private:
341+
mutable const typename TreeTypeT::ValueType* mBuffer;
342+
};
343+
344+
/// @brief Specialization for the case where a Leaf Buffer cannot be cached.
345+
// These methods should never be invoked. See above.
346+
template<typename TreeTypeT, typename IntegerSequence>
347+
struct ValueAccessorLeafBuffer<TreeTypeT, IntegerSequence,
348+
typename std::enable_if<
349+
!value_accessor_internal::EnableLeafBuffer<TreeTypeT, IntegerSequence>::value
350+
>::type>
351+
{
352+
template <typename> static constexpr bool BypassLeafAPI = false;
353+
inline constexpr typename TreeTypeT::ValueType* buffer() { assert(false); return nullptr; }
354+
inline constexpr typename TreeTypeT::ValueType* buffer() const { assert(false); return nullptr; }
355+
inline constexpr void setBuffer(const typename TreeTypeT::ValueType*) const { assert(false); }
356+
};
357+
274358
///////////////////////////////////////////////////////////////////////////////
275359

276360
/// @brief The Value Accessor Implementation and API methods. The majoirty of
277-
/// the API matches the API of an OpenVDB Tree.
361+
/// the API matches the API of a compatible OpenVDB Tree Node.
278362
template<typename _TreeType, bool IsSafe, typename MutexT, typename IntegerSequence>
279363
class ValueAccessorImpl final :
280364
public ValueAccessorBase<_TreeType, IsSafe>,
365+
public ValueAccessorLeafBuffer<_TreeType, IntegerSequence>,
281366
public ValueAccessorLock<MutexT>
282367
{
283368
public:
284369
/// @note Not strictly the only Base Type but provided for backwards
285370
/// compatibility.
286371
using BaseT = ValueAccessorBase<_TreeType, IsSafe>;
287372
using LockT = ValueAccessorLock<MutexT>;
373+
using LeafCacheT = ValueAccessorLeafBuffer<_TreeType, IntegerSequence>;
288374

289375
using TreeType = _TreeType;
290376
using ValueType = typename TreeType::ValueType;
@@ -322,13 +408,12 @@ class ValueAccessorImpl final :
322408
/// index-able array of values then this returns true.
323409
template <typename NodeT>
324410
static constexpr bool IsLeafAndBypassLeafAPI =
325-
std::is_same<NodeT, LeafNodeT>::value &&
326-
std::is_same<typename LeafNodeT::Buffer::StorageType, ValueType>::value;
411+
LeafCacheT::template BypassLeafAPI<NodeT>;
327412

328413
/// @brief Helper alias which is true if the lowest cached node level is
329-
/// a LeafNode type and has a compatible value type for optimized
330-
/// access
331-
static constexpr bool BypassLeafAPI = IsLeafAndBypassLeafAPI<NodeTypeAtLevel<0>>;
414+
/// a LeafNode type and has a compatible value type for optimized access.
415+
static constexpr bool BypassLeafAPI =
416+
IsLeafAndBypassLeafAPI<NodeTypeAtLevel<0>>;
332417

333418
/// @brief The number of node levels that this accessor can cache,
334419
/// excluding the RootNode.
@@ -339,10 +424,10 @@ class ValueAccessorImpl final :
339424
/// @brief Constructor from a tree
340425
ValueAccessorImpl(TreeType& tree)
341426
: BaseT(tree)
427+
, LeafCacheT()
342428
, LockT()
343429
, mKeys()
344-
, mNodes()
345-
, mBuffer() {
430+
, mNodes() {
346431
this->clear();
347432
}
348433

@@ -380,8 +465,7 @@ class ValueAccessorImpl final :
380465
if (!this->isHashed<NodeType>(xyz)) return nullptr;
381466

382467
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
383-
assert(mBuffer);
384-
return &(mBuffer[LeafNodeT::coordToOffset(xyz)]);
468+
return &(LeafCacheT::buffer()[LeafNodeT::coordToOffset(xyz)]);
385469
}
386470
else {
387471
auto node = mNodes.template get<Idx>();
@@ -413,9 +497,8 @@ class ValueAccessorImpl final :
413497
assert(node);
414498

415499
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
416-
assert(mBuffer);
417500
const auto offset = LeafNodeT::coordToOffset(xyz);
418-
value = mBuffer[offset];
501+
value = LeafCacheT::buffer()[offset];
419502
return node->isValueOn(offset);
420503
}
421504
else {
@@ -473,9 +556,8 @@ class ValueAccessorImpl final :
473556
assert(node);
474557

475558
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
476-
assert(mBuffer);
477559
const auto offset = LeafNodeT::coordToOffset(xyz);
478-
const_cast<ValueType&>(mBuffer[offset]) = value;
560+
const_cast<ValueType&>(LeafCacheT::buffer()[offset]) = value;
479561
const_cast<NodeType*>(node)->setValueOn(offset);
480562
}
481563
else {
@@ -504,8 +586,7 @@ class ValueAccessorImpl final :
504586
if (!this->isHashed<NodeType>(xyz)) return false;
505587

506588
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
507-
assert(mBuffer);
508-
const_cast<ValueType&>(mBuffer[LeafNodeT::coordToOffset(xyz)]) = value;
589+
const_cast<ValueType&>(LeafCacheT::buffer()[LeafNodeT::coordToOffset(xyz)]) = value;
509590
}
510591
else {
511592
auto node = mNodes.template get<Idx>();
@@ -532,9 +613,8 @@ class ValueAccessorImpl final :
532613
assert(node);
533614

534615
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
535-
assert(mBuffer);
536616
const auto offset = LeafNodeT::coordToOffset(xyz);
537-
const_cast<ValueType&>(mBuffer[offset]) = value;
617+
const_cast<ValueType&>(LeafCacheT::buffer()[offset]) = value;
538618
const_cast<NodeType*>(node)->setValueOff(offset);
539619
}
540620
else {
@@ -558,9 +638,8 @@ class ValueAccessorImpl final :
558638
assert(node);
559639

560640
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
561-
assert(mBuffer);
562641
const auto offset = LeafNodeT::coordToOffset(xyz);
563-
op(const_cast<ValueType&>(mBuffer[offset]));
642+
op(const_cast<ValueType&>(LeafCacheT::buffer()[offset]));
564643
const_cast<NodeType*>(node)->setActiveState(offset, true);
565644
}
566645
else {
@@ -583,10 +662,9 @@ class ValueAccessorImpl final :
583662
assert(node);
584663

585664
if constexpr(IsLeafAndBypassLeafAPI<NodeType>) {
586-
assert(mBuffer);
587665
const auto offset = LeafNodeT::coordToOffset(xyz);
588666
bool state = node->isValueOn(offset);
589-
op(const_cast<ValueType&>(mBuffer[offset]), state);
667+
op(const_cast<ValueType&>(LeafCacheT::buffer()[offset]), state);
590668
const_cast<NodeType*>(node)->setActiveState(offset, state);
591669
}
592670
else {
@@ -803,7 +881,9 @@ class ValueAccessorImpl final :
803881
{
804882
mKeys.fill(Coord::max());
805883
mNodes.foreach([](auto& node) { node = nullptr; });
806-
mBuffer = nullptr;
884+
if constexpr (BypassLeafAPI) {
885+
LeafCacheT::setBuffer(nullptr);
886+
}
807887
if (BaseT::mTree) {
808888
static constexpr int64_t Idx = NodeLevelList::template Index<RootNodeT>;
809889
mNodes.template get<Idx>() = const_cast<RootNodeT*>(&(BaseT::mTree->root()));
@@ -860,7 +940,7 @@ class ValueAccessorImpl final :
860940
mKeys[Idx] = xyz & ~(NodeT::DIM-1);
861941
mNodes.template get<Idx>() = const_cast<NodeT*>(node);
862942
if constexpr(IsLeafAndBypassLeafAPI<NodeT>) {
863-
mBuffer = node->buffer().data();
943+
LeafCacheT::setBuffer(node->buffer().data());
864944
}
865945
}
866946
}
@@ -938,7 +1018,6 @@ class ValueAccessorImpl final :
9381018
private:
9391019
mutable std::array<Coord, NumCacheLevels> mKeys;
9401020
mutable typename NodePtrList::AsTupleList mNodes;
941-
mutable const ValueType* mBuffer;
9421021
}; // ValueAccessorImpl
9431022

9441023
} // namespace tree

openvdb/openvdb/unittest/TestValueAccessor.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,35 @@ TestValueAccessor::multithreadedAccessorTest()
431431
#undef MAX_COORD
432432
}
433433

434+
/// Static assert class sizes
435+
template <typename Type, size_t Expect, size_t Actual = sizeof(Type)>
436+
struct CheckClassSize { static_assert(Expect == Actual); };
437+
438+
// Build a type list of all accessor types and make sure they are all the
439+
// expected size.
440+
template <typename GridT> using GridToAccCheckA =
441+
CheckClassSize<typename GridT::Accessor, 96ul>;
442+
template <typename GridT> using GridToAccCheckB =
443+
CheckClassSize<typename GridT::Accessor, 88ul>;
444+
445+
void StaticAsssertVASizes()
446+
{
447+
// Accessors with a leaf buffer cache have an extra pointer
448+
using AccessorsWithLeafCache =
449+
openvdb::GridTypes
450+
::Remove<openvdb::BoolGrid>
451+
::Remove<openvdb::MaskGrid>
452+
::Transform<GridToAccCheckA>;
453+
454+
// Accessors without a leaf buffer cache
455+
using AccessorsWithoutLeafCache =
456+
openvdb::TypeList<openvdb::BoolGrid, openvdb::MaskGrid>
457+
::Transform<GridToAccCheckB>;
458+
459+
// instantiate these types to force the static check
460+
[[maybe_unused]] AccessorsWithLeafCache::AsTupleList test;
461+
[[maybe_unused]] AccessorsWithoutLeafCache::AsTupleList test2;
462+
}
434463

435464
// cache all node levels
436465
TEST_F(TestValueAccessor, testTree2Accessor) { accessorTest<ValueAccessor<Tree2Type> >(); }

0 commit comments

Comments
 (0)