1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 #ifndef OPENVDB_TREE_LEAFBUFFER_HAS_BEEN_INCLUDED
5 #define OPENVDB_TREE_LEAFBUFFER_HAS_BEEN_INCLUDED
6 
7 #include <openvdb/Types.h>
8 #include <openvdb/io/Compression.h> // for io::readCompressedValues(), etc
9 #include <openvdb/util/NodeMasks.h>
10 #include <tbb/spin_mutex.h>
11 #include <algorithm> // for std::swap
12 #include <atomic>
13 #include <cstddef> // for offsetof()
14 #include <iostream>
15 #include <memory>
16 #include <type_traits>
17 
18 
19 class TestLeaf;
20 
21 namespace openvdb {
22 OPENVDB_USE_VERSION_NAMESPACE
23 namespace OPENVDB_VERSION_NAME {
24 namespace tree {
25 
26 
27 /// @brief Array of fixed size 2<SUP>3<I>Log2Dim</I></SUP> that stores
28 /// the voxel values of a LeafNode
29 template<typename T, Index Log2Dim>
30 class LeafBuffer
31 {
32 public:
33     using ValueType = T;
34     using StorageType = ValueType;
35     using NodeMaskType = util::NodeMask<Log2Dim>;
36     static const Index SIZE = 1 << 3 * Log2Dim;
37 
38     struct FileInfo
39     {
FileInfoFileInfo40         FileInfo(): bufpos(0) , maskpos(0) {}
41         std::streamoff bufpos;
42         std::streamoff maskpos;
43         io::MappedFile::Ptr mapping;
44         SharedPtr<io::StreamMetadata> meta;
45     };
46 
47     /// Default constructor
LeafBuffer()48     inline LeafBuffer(): mData(new ValueType[SIZE]) { mOutOfCore = 0; }
49     /// Construct a buffer populated with the specified value.
50     explicit inline LeafBuffer(const ValueType&);
51     /// Copy constructor
52     inline LeafBuffer(const LeafBuffer&);
53     /// Construct a buffer but don't allocate memory for the full array of values.
LeafBuffer(PartialCreate,const ValueType &)54     LeafBuffer(PartialCreate, const ValueType&): mData(nullptr) { mOutOfCore = 0; }
55     /// Destructor
56     inline ~LeafBuffer();
57 
58     /// Return @c true if this buffer's values have not yet been read from disk.
isOutOfCore()59     bool isOutOfCore() const { return bool(mOutOfCore); }
60     /// Return @c true if memory for this buffer has not yet been allocated.
empty()61     bool empty() const { return !mData || this->isOutOfCore(); }
62     /// Allocate memory for this buffer if it has not already been allocated.
allocate()63     bool allocate() { if (mData == nullptr) mData = new ValueType[SIZE]; return true; }
64 
65     /// Populate this buffer with a constant value.
66     inline void fill(const ValueType&);
67 
68     /// Return a const reference to the i'th element of this buffer.
getValue(Index i)69     const ValueType& getValue(Index i) const { return this->at(i); }
70     /// Return a const reference to the i'th element of this buffer.
71     const ValueType& operator[](Index i) const { return this->at(i); }
72     /// Set the i'th value of this buffer to the specified value.
73     inline void setValue(Index i, const ValueType&);
74 
75     /// Copy the other buffer's values into this buffer.
76     inline LeafBuffer& operator=(const LeafBuffer&);
77 
78     /// @brief Return @c true if the contents of the other buffer
79     /// exactly equal the contents of this buffer.
80     inline bool operator==(const LeafBuffer&) const;
81     /// @brief Return @c true if the contents of the other buffer
82     /// are not exactly equal to the contents of this buffer.
83     inline bool operator!=(const LeafBuffer& other) const { return !(other == *this); }
84 
85     /// Exchange this buffer's values with the other buffer's values.
86     inline void swap(LeafBuffer&);
87 
88     /// Return the memory footprint of this buffer in bytes.
89     inline Index memUsage() const;
90     /// Return the number of values contained in this buffer.
size()91     static Index size() { return SIZE; }
92 
93     /// @brief Return a const pointer to the array of voxel values.
94     /// @details This method guarantees that the buffer is allocated and loaded.
95     /// @warning This method should only be used by experts seeking low-level optimizations.
96     const ValueType* data() const;
97     /// @brief Return a pointer to the array of voxel values.
98     /// @details This method guarantees that the buffer is allocated and loaded.
99     /// @warning This method should only be used by experts seeking low-level optimizations.
100     ValueType* data();
101 
102 private:
103     /// If this buffer is empty, return zero, otherwise return the value at index @ i.
104     inline const ValueType& at(Index i) const;
105 
106     /// @brief Return a non-const reference to the value at index @a i.
107     /// @details This method is private since it makes assumptions about the
108     /// buffer's memory layout.  LeafBuffers associated with custom leaf node types
109     /// (e.g., a bool buffer implemented as a bitmask) might not be able to
110     /// return non-const references to their values.
111     ValueType& operator[](Index i) { return const_cast<ValueType&>(this->at(i)); }
112 
113     bool deallocate();
114 
setOutOfCore(bool b)115     inline void setOutOfCore(bool b) { mOutOfCore = b; }
116     // To facilitate inlining in the common case in which the buffer is in-core,
117     // the loading logic is split into a separate function, doLoad().
loadValues()118     inline void loadValues() const { if (this->isOutOfCore()) this->doLoad(); }
119     inline void doLoad() const;
120     inline bool detachFromFile();
121 
122     using FlagsType = std::atomic<Index32>;
123 
124     union {
125         ValueType* mData;
126         FileInfo*  mFileInfo;
127     };
128     FlagsType mOutOfCore; // interpreted as bool; extra bits reserved for future use
129     tbb::spin_mutex mMutex; // 1 byte
130     //int8_t mReserved[3]; // padding for alignment
131 
132     static const ValueType sZero;
133 
134     friend class ::TestLeaf;
135     // Allow the parent LeafNode to access this buffer's data pointer.
136     template<typename, Index> friend class LeafNode;
137 }; // class LeafBuffer
138 
139 
140 ////////////////////////////////////////
141 
142 
143 template<typename T, Index Log2Dim>
144 const T LeafBuffer<T, Log2Dim>::sZero = zeroVal<T>();
145 
146 
147 template<typename T, Index Log2Dim>
148 inline
LeafBuffer(const ValueType & val)149 LeafBuffer<T, Log2Dim>::LeafBuffer(const ValueType& val)
150     : mData(new ValueType[SIZE])
151 {
152     mOutOfCore = 0;
153     this->fill(val);
154 }
155 
156 
157 template<typename T, Index Log2Dim>
158 inline
~LeafBuffer()159 LeafBuffer<T, Log2Dim>::~LeafBuffer()
160 {
161     if (this->isOutOfCore()) {
162         this->detachFromFile();
163     } else {
164         this->deallocate();
165     }
166 }
167 
168 
169 template<typename T, Index Log2Dim>
170 inline
LeafBuffer(const LeafBuffer & other)171 LeafBuffer<T, Log2Dim>::LeafBuffer(const LeafBuffer& other)
172     : mData(nullptr)
173     , mOutOfCore(other.mOutOfCore.load())
174 {
175     if (other.isOutOfCore()) {
176         mFileInfo = new FileInfo(*other.mFileInfo);
177     } else if (other.mData != nullptr) {
178         this->allocate();
179         ValueType* target = mData;
180         const ValueType* source = other.mData;
181         Index n = SIZE;
182         while (n--) *target++ = *source++;
183     }
184 }
185 
186 
187 template<typename T, Index Log2Dim>
188 inline void
setValue(Index i,const ValueType & val)189 LeafBuffer<T, Log2Dim>::setValue(Index i, const ValueType& val)
190 {
191     assert(i < SIZE);
192     this->loadValues();
193     if (mData) mData[i] = val;
194 }
195 
196 
197 template<typename T, Index Log2Dim>
198 inline LeafBuffer<T, Log2Dim>&
199 LeafBuffer<T, Log2Dim>::operator=(const LeafBuffer& other)
200 {
201     if (&other != this) {
202         if (this->isOutOfCore()) {
203             this->detachFromFile();
204         } else {
205             if (other.isOutOfCore()) this->deallocate();
206         }
207         if (other.isOutOfCore()) {
208             mOutOfCore.store(other.mOutOfCore.load(std::memory_order_acquire),
209                              std::memory_order_release);
210             mFileInfo = new FileInfo(*other.mFileInfo);
211         } else if (other.mData != nullptr) {
212             this->allocate();
213             ValueType* target = mData;
214             const ValueType* source = other.mData;
215             Index n = SIZE;
216             while (n--) *target++ = *source++;
217         }
218     }
219     return *this;
220 }
221 
222 
223 template<typename T, Index Log2Dim>
224 inline void
fill(const ValueType & val)225 LeafBuffer<T, Log2Dim>::fill(const ValueType& val)
226 {
227     this->detachFromFile();
228     if (mData != nullptr) {
229         ValueType* target = mData;
230         Index n = SIZE;
231         while (n--) *target++ = val;
232     }
233 }
234 
235 
236 template<typename T, Index Log2Dim>
237 inline bool
238 LeafBuffer<T, Log2Dim>::operator==(const LeafBuffer& other) const
239 {
240     this->loadValues();
241     other.loadValues();
242     const ValueType *target = mData, *source = other.mData;
243     if (!target && !source) return true;
244     if (!target || !source) return false;
245     Index n = SIZE;
246     while (n && math::isExactlyEqual(*target++, *source++)) --n;
247     return n == 0;
248 }
249 
250 
251 template<typename T, Index Log2Dim>
252 inline void
swap(LeafBuffer & other)253 LeafBuffer<T, Log2Dim>::swap(LeafBuffer& other)
254 {
255     std::swap(mData, other.mData);
256 
257     // Two atomics can't be swapped because it would require hardware support:
258     // https://en.wikipedia.org/wiki/Double_compare-and-swap
259     // Note that there's a window in which other.mOutOfCore could be written
260     // between our load from it and our store to it.
261     auto tmp = other.mOutOfCore.load(std::memory_order_acquire);
262     tmp = mOutOfCore.exchange(std::move(tmp));
263     other.mOutOfCore.store(std::move(tmp), std::memory_order_release);
264 }
265 
266 
267 template<typename T, Index Log2Dim>
268 inline Index
memUsage()269 LeafBuffer<T, Log2Dim>::memUsage() const
270 {
271     size_t n = sizeof(*this);
272     if (this->isOutOfCore()) n += sizeof(FileInfo);
273     else if (mData) n += SIZE * sizeof(ValueType);
274     return static_cast<Index>(n);
275 }
276 
277 
278 template<typename T, Index Log2Dim>
279 inline const typename LeafBuffer<T, Log2Dim>::ValueType*
data()280 LeafBuffer<T, Log2Dim>::data() const
281 {
282     this->loadValues();
283     if (mData == nullptr) {
284         LeafBuffer* self = const_cast<LeafBuffer*>(this);
285         // This lock will be contended at most once.
286         tbb::spin_mutex::scoped_lock lock(self->mMutex);
287         if (mData == nullptr) self->mData = new ValueType[SIZE];
288     }
289     return mData;
290 }
291 
292 template<typename T, Index Log2Dim>
293 inline typename LeafBuffer<T, Log2Dim>::ValueType*
data()294 LeafBuffer<T, Log2Dim>::data()
295 {
296     this->loadValues();
297     if (mData == nullptr) {
298         // This lock will be contended at most once.
299         tbb::spin_mutex::scoped_lock lock(mMutex);
300         if (mData == nullptr) mData = new ValueType[SIZE];
301     }
302     return mData;
303 }
304 
305 
306 template<typename T, Index Log2Dim>
307 inline const typename LeafBuffer<T, Log2Dim>::ValueType&
at(Index i)308 LeafBuffer<T, Log2Dim>::at(Index i) const
309 {
310     assert(i < SIZE);
311     this->loadValues();
312     // We can't use the ternary operator here, otherwise Visual C++ returns
313     // a reference to a temporary.
314     if (mData) return mData[i]; else return sZero;
315 }
316 
317 
318 template<typename T, Index Log2Dim>
319 inline bool
deallocate()320 LeafBuffer<T, Log2Dim>::deallocate()
321 {
322     if (mData != nullptr && !this->isOutOfCore()) {
323         delete[] mData;
324         mData = nullptr;
325         return true;
326     }
327     return false;
328 }
329 
330 
331 template<typename T, Index Log2Dim>
332 inline void
doLoad()333 LeafBuffer<T, Log2Dim>::doLoad() const
334 {
335     if (!this->isOutOfCore()) return;
336 
337     LeafBuffer<T, Log2Dim>* self = const_cast<LeafBuffer<T, Log2Dim>*>(this);
338 
339     // This lock will be contended at most once, after which this buffer
340     // will no longer be out-of-core.
341     tbb::spin_mutex::scoped_lock lock(self->mMutex);
342     if (!this->isOutOfCore()) return;
343 
344     std::unique_ptr<FileInfo> info(self->mFileInfo);
345     assert(info.get() != nullptr);
346     assert(info->mapping.get() != nullptr);
347     assert(info->meta.get() != nullptr);
348 
349     /// @todo For now, we have to clear the mData pointer in order for allocate() to take effect.
350     self->mData = nullptr;
351     self->allocate();
352 
353     SharedPtr<std::streambuf> buf = info->mapping->createBuffer();
354     std::istream is(buf.get());
355 
356     io::setStreamMetadataPtr(is, info->meta, /*transfer=*/true);
357 
358     NodeMaskType mask;
359     is.seekg(info->maskpos);
360     mask.load(is);
361 
362     is.seekg(info->bufpos);
363     io::readCompressedValues(is, self->mData, SIZE, mask, io::getHalfFloat(is));
364 
365     self->setOutOfCore(false);
366 }
367 
368 
369 template<typename T, Index Log2Dim>
370 inline bool
detachFromFile()371 LeafBuffer<T, Log2Dim>::detachFromFile()
372 {
373     if (this->isOutOfCore()) {
374         delete mFileInfo;
375         mFileInfo = nullptr;
376         this->setOutOfCore(false);
377         return true;
378     }
379     return false;
380 }
381 
382 
383 ////////////////////////////////////////
384 
385 
386 // Partial specialization for bool ValueType
387 template<Index Log2Dim>
388 class LeafBuffer<bool, Log2Dim>
389 {
390 public:
391     using NodeMaskType = util::NodeMask<Log2Dim>;
392     using WordType = typename NodeMaskType::Word;
393     using ValueType = bool;
394     using StorageType = WordType;
395 
396     static const Index WORD_COUNT = NodeMaskType::WORD_COUNT;
397     static const Index SIZE = 1 << 3 * Log2Dim;
398 
399     // These static declarations must be on separate lines to avoid VC9 compiler errors.
400     static const bool sOn;
401     static const bool sOff;
402 
LeafBuffer()403     LeafBuffer() {}
LeafBuffer(bool on)404     LeafBuffer(bool on): mData(on) {}
LeafBuffer(const NodeMaskType & other)405     LeafBuffer(const NodeMaskType& other): mData(other) {}
LeafBuffer(const LeafBuffer & other)406     LeafBuffer(const LeafBuffer& other): mData(other.mData) {}
~LeafBuffer()407     ~LeafBuffer() {}
fill(bool val)408     void fill(bool val) { mData.set(val); }
409     LeafBuffer& operator=(const LeafBuffer& b) { if (&b != this) { mData=b.mData; } return *this; }
410 
getValue(Index i)411     const bool& getValue(Index i) const
412     {
413         assert(i < SIZE);
414         // We can't use the ternary operator here, otherwise Visual C++ returns
415         // a reference to a temporary.
416         if (mData.isOn(i)) return sOn; else return sOff;
417     }
418     const bool& operator[](Index i) const { return this->getValue(i); }
419 
420     bool operator==(const LeafBuffer& other) const { return mData == other.mData; }
421     bool operator!=(const LeafBuffer& other) const { return mData != other.mData; }
422 
setValue(Index i,bool val)423     void setValue(Index i, bool val) { assert(i < SIZE); mData.set(i, val); }
424 
swap(LeafBuffer & other)425     void swap(LeafBuffer& other) { if (&other != this) std::swap(mData, other.mData); }
426 
memUsage()427     Index memUsage() const { return sizeof(*this); }
size()428     static Index size() { return SIZE; }
429 
430     /// @brief Return a pointer to the C-style array of words encoding the bits.
431     /// @warning This method should only be used by experts seeking low-level optimizations.
data()432     WordType* data() { return &(mData.template getWord<WordType>(0)); }
433     /// @brief Return a const pointer to the C-style array of words encoding the bits.
434     /// @warning This method should only be used by experts seeking low-level optimizations.
data()435     const WordType* data() const { return const_cast<LeafBuffer*>(this)->data(); }
436 
437 private:
438     // Allow the parent LeafNode to access this buffer's data.
439     template<typename, Index> friend class LeafNode;
440 
441     NodeMaskType mData;
442 }; // class LeafBuffer
443 
444 
445 /// @internal For consistency with other nodes and with iterators, methods like
446 /// LeafNode::getValue() return a reference to a value.  Since it's not possible
447 /// to return a reference to a bit in a node mask, we return a reference to one
448 /// of the following static values instead.
449 template<Index Log2Dim> const bool LeafBuffer<bool, Log2Dim>::sOn = true;
450 template<Index Log2Dim> const bool LeafBuffer<bool, Log2Dim>::sOff = false;
451 
452 } // namespace tree
453 } // namespace OPENVDB_VERSION_NAME
454 } // namespace openvdb
455 
456 #endif // OPENVDB_TREE_LEAFBUFFER_HAS_BEEN_INCLUDED
457