OpenVDB 14.0.0
Loading...
Searching...
No Matches
GridHandle.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5 \file nanovdb/GridHandle.h
6
7 \author Ken Museth
8
9 \date January 8, 2020
10
11 \brief Defines GridHandle, which manages a memory buffer containing one
12 or more NanoVDB grids: host-resident, or a single-space buffer
13 such as cuda::Buffer.
14*/
15
16#ifndef NANOVDB_GRID_HANDLE_H_HAS_BEEN_INCLUDED
17#define NANOVDB_GRID_HANDLE_H_HAS_BEEN_INCLUDED
18
19#include <fstream> // for std::ifstream
20#include <iostream> // for std::cerr/cout
21#include <cstring> // for std::memcpy
22#include <stdexcept> // for std::runtime_error
23#include <string> // for std::to_string
24#include <vector>
25#include <initializer_list>
26#include <cstddef> // for std::byte
27#include <type_traits> // for std::void_t
28#include <utility> // for std::move
29
30#include <nanovdb/NanoVDB.h>// for toGridType
31#include <nanovdb/HostBuffer.h>
32#include <nanovdb/tools/GridChecksum.h>// for updateGridCount
33
34namespace nanovdb {
35
36// --------------------------> GridHandle <------------------------------------
37
39
40template<typename BufferT> class GridHandle;
41
42namespace detail {
43
44struct LayoutCopy;
45
46/// @brief Allocates @c bytes of host-readable storage for a GridHandle:
47/// through @c BufferT::create for buffers that provide it, and through
48/// @c pool's resource for a host-accessible single-space buffer (e.g. a
49/// pinned-resource cuda::Buffer). The braced third argument selects the
50/// uninitialized-storage constructor without naming its tag type, so
51/// this header stays CUDA-free.
52template<typename BufferT>
53inline BufferT createHostStorage(uint64_t bytes, const BufferT& pool)
54{
56 return BufferT(pool.stream(), pool.resource(), bytes, {});// stream-ordered resource: allocate on the pool's retained stream
57 } else if constexpr (BufferHasHostSingle<BufferT>::value) {
58 return BufferT(pool.resource(), bytes, {});
59 } else {
60 return BufferT::create(bytes, &pool);
61 }
62}
63
64/// @brief Pointer-taking form for callers whose pool is optional: a null pool
65/// default-constructs the buffer type as the prototype, which for a
66/// single-space buffer means a default-constructed resource.
67template<typename BufferT>
68inline BufferT createHostStorage(uint64_t bytes, const BufferT* pool)
69{
71 return BufferT::create(bytes, pool);
73 return pool ? createHostStorage(bytes, *pool) : createHostStorage(bytes, BufferT());
74 } else {
75 if (!pool) throw std::runtime_error("GridHandle: a host-accessible buffer over a non-default-constructible "
76 "resource requires a pool buffer to take the resource from");
77 return createHostStorage(bytes, *pool);
78 }
79}
80
81/// @brief Checks that @a meta describes grids laid out back to back from
82/// offset zero inside @a bytes of storage, each starting at a multiple
83/// of NANOVDB_DATA_ALIGNMENT and naming a grid type below GridType::End.
84/// This is all the validation a handle adopting caller-supplied
85/// metadata performs: no header is read. No grids at all is accepted
86/// only over an empty buffer (an empty handle).
87inline void validateGridLayout(const std::vector<GridHandleMetaData>& meta, uint64_t bytes)
88{
89 if (meta.empty()) {
90 if (bytes == 0) return;
91 throw std::runtime_error("GridHandle::adopt: the metadata describes no grids but the buffer is not empty");
92 }
93 uint64_t offset = 0;
94 for (size_t i = 0; i < meta.size(); ++i) {
95 auto where = [&] { return " (grid " + std::to_string(i) + " of " + std::to_string(meta.size()) + ")"; };
96 if (meta[i].offset != offset)
97 throw std::runtime_error("GridHandle::adopt: grids must be contiguous from offset zero" + where());
98 if (offset % NANOVDB_DATA_ALIGNMENT != 0)
99 throw std::runtime_error("GridHandle::adopt: grid offset is not a multiple of NANOVDB_DATA_ALIGNMENT" + where());
100 if (meta[i].size < sizeof(GridData))
101 throw std::runtime_error("GridHandle::adopt: grid size is smaller than a grid header" + where());
102 if (meta[i].size > bytes - offset)
103 throw std::runtime_error("GridHandle::adopt: grid exceeds the buffer" + where());
104 if (!(meta[i].gridType < GridType::End))
105 throw std::runtime_error("GridHandle::adopt: invalid grid type" + where());
106 offset += meta[i].size;
107 }
108}
109
110/// @brief Validates the grid chain in @a bytes of host-readable memory headed
111/// by @a head and fills @a meta with one entry per grid: every header
112/// must be valid, carry its expected index and the chain's total count,
113/// and fit inside the buffer, so a truncated buffer or a forged header
114/// is rejected before its metadata is trusted. This is the host
115/// counterpart of the device-side chain parse. Like it, this does not
116/// require grid offsets to be aligned, unlike GridHandle::adopt.
117inline void parseHostGridChain(const GridData* head, uint64_t bytes, std::vector<GridHandleMetaData>& meta)
118{
119 if (bytes < sizeof(GridData))
120 throw std::runtime_error("GridHandle: grid chain exceeds the host buffer (truncated or corrupt grid data)");
121 if (!head->isValid()) throw std::runtime_error("GridHandle was constructed with an invalid host buffer");
122 const uint32_t count = head->mGridCount;
123 if (count == 0) throw std::runtime_error("GridHandle: host buffer contains no grids");
124 if (uint64_t(count) > bytes / sizeof(GridData))// every grid is at least one full header
125 throw std::runtime_error("GridHandle: grid chain exceeds the host buffer (truncated or corrupt grid data)");
126 meta.resize(count);
127 uint64_t offset = 0;
128 for (uint32_t i = 0; i < count; ++i) {
129 auto where = [&] { return " (grid " + std::to_string(i) + " of " + std::to_string(count) + ")"; };
130 if (offset + sizeof(GridData) > bytes)
131 throw std::runtime_error("GridHandle: grid chain exceeds the host buffer (truncated or corrupt grid data)" + where());
132 // Read through a copy: a forged size in the preceding header can place this one at an
133 // unaligned offset, where dereferencing a GridData pointer would be undefined behavior.
134 alignas(GridData) unsigned char raw[sizeof(GridData)];
135 std::memcpy(raw, util::PtrAdd<const void>(head, offset), sizeof(GridData));
136 const GridData* data = reinterpret_cast<const GridData*>(raw);
137 if (!data->isValid())
138 throw std::runtime_error("GridHandle was constructed with an invalid host buffer" + where());
139 if (data->mGridIndex != i || data->mGridCount != count)
140 throw std::runtime_error("GridHandle: inconsistent grid index/count in the host buffer's grid chain" + where());
141 if (data->mGridSize < sizeof(GridData) || data->mGridSize > bytes - offset)
142 throw std::runtime_error("GridHandle: grid size field exceeds the host buffer (truncated or corrupt grid data)" + where());
143 meta[i] = GridHandleMetaData{offset, data->mGridSize, data->mGridType};
144 offset += data->mGridSize;
145 }
146}
147
148/// @brief Detects a BufferTraits specialization that still declares the
149/// hasDeviceDual member removed in NanoVDB 14.0, so GridHandle can reject
150/// it instead of parsing the device-only grids as an empty host buffer.
151template<typename BufferT, typename = void>
152struct BufferDeclaresDeviceDual { static constexpr bool value = false; };
153template<typename BufferT>
154struct BufferDeclaresDeviceDual<BufferT, std::void_t<decltype(BufferTraits<BufferT>::hasDeviceDual)>>
155{ static constexpr bool value = true; };
156
157}// namespace detail
158
159/// @brief This class serves to manage a buffer containing one or more NanoVDB Grids.
160///
161/// @note It is important to note that this class does NOT depend on OpenVDB.
162template<typename BufferT = HostBuffer>
163class GridHandle
164{
167 "GridHandle requires byte-addressed single-space storage, e.g. cuda::Buffer<std::byte, R>");
169 "BufferTraits::hasDeviceDual was removed in NanoVDB 14.0 together with the dual-space buffers: "
170 "hold device grids in a single-space buffer such as cuda::Buffer<std::byte> and set "
171 "BufferTraits::hasDeviceSingle for a custom one");
172
173 std::vector<GridHandleMetaData> mMetaData;
174 BufferT mBuffer;
175
176 template <typename T>
177 static T* no_const(const T* ptr) { return const_cast<T*>(ptr); }
178
179 /// @brief Shared lookup behind grid() and deviceGrid(): the @a n'th grid
180 /// within @a base, or nullptr when @a base is null, @a n is out of
181 /// range, or the value type does not match the grid.
182 template<typename ValueT>
183 const NanoGrid<ValueT>* gridAt(const void* base, uint32_t n) const
184 {
185 if (base == nullptr || n >= mMetaData.size() || mMetaData[n].gridType != toGridType<ValueT>()) return nullptr;
186 return util::PtrAdd<NanoGrid<ValueT>>(base, mMetaData[n].offset);
187 }
188
189 GridHandle(BufferT&& buffer, std::vector<GridHandleMetaData> meta)
190 : mMetaData(std::move(meta))
191 , mBuffer(std::move(buffer)) {}
192
193 /// @brief Parses the grid metadata of a single-space device mBuffer; defined in nanovdb/cuda/GridHandle.cuh
194 void parseSingleBuffer();
195
196 friend struct detail::LayoutCopy;
197
198public:
199 using BufferType = BufferT;
200
201 /// @brief Constructs a handle over @a buffer from metadata the caller
202 /// already knows to describe its contents, because the bytes were
203 /// copied from another handle (see metaData()) or laid out by the
204 /// caller. No grid header is read, so this wraps device bytes
205 /// without a parse kernel and works for buffers that are not
206 /// default-constructible. Only the layout arithmetic is checked:
207 /// the grids must be contiguous from offset zero, start aligned to
208 /// NANOVDB_DATA_ALIGNMENT and fit the buffer,
209 /// and an empty layout is accepted only over an empty buffer.
210 /// Each grid type is trusted as given, so a wrong type makes grid()
211 /// reinterpret the grid's values; only GridType::End is rejected.
212 /// @throw std::runtime_error when the layout does not fit @a buffer
213 static GridHandle adopt(BufferT&& buffer, std::vector<GridHandleMetaData> meta)
214 {
216 return GridHandle(std::move(buffer), std::move(meta));
217 }
218
219 /// @brief The layout of the grids in this handle's buffer: offset, size
220 /// and grid type of each. Valid input to adopt() over a buffer
221 /// holding a byte-for-byte copy of the grids.
222 const std::vector<GridHandleMetaData>& metaData() const { return mMetaData; }
223
224 /// @brief Move constructor from a host or single-space device buffer
225 /// @param buffer buffer containing one or more NanoGrids that will be moved into this GridHandle
226 /// @throw Will throw and error with the buffer does not contain a valid NanoGrid!
227 /// @note The device variant is implemented in nanovdb/cuda/GridHandle.cuh since it requires CUDA.
228 /// This is deliberately not a template: clang >= 17 mangles the dependent type of a SFINAE
229 /// template parameter while g++ does not, which breaks a clang build linked against nvcc with a g++ host compiler.
231 : mBuffer(std::move(buffer))
232 {
234 this->parseSingleBuffer();
235 } else {
236 if (auto *data = reinterpret_cast<const GridData*>(mBuffer.data())) {
237 detail::parseHostGridChain(data, mBuffer.size(), mMetaData);
238 }
239 }
240 }
241
242 /// @brief Reject anything other than a BufferT rvalue, e.g. a size or a different buffer
243 /// type that would otherwise implicitly convert to BufferT. Never instantiated,
244 /// so it emits no symbol.
245 template<typename U> GridHandle(U&&) = delete;
246
247 /// @brief Constructs an empty GridHandle
248 GridHandle() = default;
249
250 /// @brief Disallow copy-construction
251 GridHandle(const GridHandle&) = delete;
252
253 /// @brief Move copy-constructor
254 GridHandle(GridHandle&& other) noexcept
255 : mMetaData(std::move(other.mMetaData))
256 , mBuffer(std::move(other.mBuffer)) {}
257
258 /// @brief clear this GridHandle to an empty handle
259 void reset() {
260 if constexpr (BufferHasDestroy<BufferT>::value) mBuffer.destroy();
261 else mBuffer.clear();
262 mMetaData.clear();
263 }
264
265 /// @brief Disallow copy assignment operation
266 GridHandle& operator=(const GridHandle&) = delete;
267
268 /// @brief Move copy assignment operation
269 GridHandle& operator=(GridHandle&& other) noexcept {
270 mBuffer = std::move(other.mBuffer);
271 mMetaData = std::move(other.mMetaData);
272 return *this;
273 }
274
275 /// @brief Performs a deep copy of the GridHandle, possibly templated on a different buffer type
276 /// @tparam OtherBufferT Buffer type of the deep copy. A single-space device
277 /// handle only copies to its own buffer type (device-to-device,
278 /// ordered on the source's retained stream), so call it as
279 /// copy<BufferT>() -- the default OtherBufferT does not compile.
280 /// @param buffer optional buffer used for allocation; a compile error for
281 /// single-space handles, which allocate through the source buffer's
282 /// resource (use the no-argument overload)
283 /// @return A new handle of the specified buffer type that contains a deep copy of the current handle
284 template <typename OtherBufferT = HostBuffer>
285 GridHandle<OtherBufferT> copy(const OtherBufferT& buffer) const;
286
287 /// @brief Deep copy without a pool argument. The single-space path
288 /// allocates through the source buffer's resource and never
289 /// constructs a pool buffer, so it works for buffers that are not
290 /// default-constructible, e.g. over a ResourceRef. The host path
291 /// default-constructs the pool argument, so it requires a
292 /// default-constructible OtherBufferT.
293 template <typename OtherBufferT = HostBuffer>
295
296 /// @brief Return a reference to the buffer
297 BufferT& buffer() { return mBuffer; }
298
299 /// @brief Return a const reference to the buffer
300 const BufferT& buffer() const { return mBuffer; }
301
302 //@{
303 /// @brief Returns a pointer to the host data; not available for a
304 /// single-space device buffer, which has no host-readable bytes.
305 /// @warning Note that the return pointer can be NULL if the GridHandle was not initialized
306 template<typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
307 void* data() { return mBuffer.data(); }
308 template<typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
309 const void* data() const { return mBuffer.data(); }
310 //@}
311
312 //@{
313 /// @brief For a single-space buffer the device data is the buffer itself.
314 template<typename U = BufferT>
315 typename util::enable_if<BufferHasDeviceSingle<U>::value, const void*>::type
316 deviceData() const { return mBuffer.data(); }
317 template<typename U = BufferT>
319 deviceData() { return mBuffer.data(); }
320 //@}
321
322 //@{
323 /// @brief Returns the size in bytes of the raw memory buffer managed by this GridHandle.
324 uint64_t bufferSize() const { return mBuffer.size(); }
325 //@}
326
327 //@{
328 /// @brief Return true if this handle is empty, i.e. has no allocated memory
329 bool empty() const { return mBuffer.size() == 0; }
330 bool isEmpty() const { return mBuffer.size() == 0; }
331 //@}
332
333 /// @brief Return true if this handle is not empty, i.e. contains at least one grid
334 operator bool() const { return !this->empty(); }
335
336 /// @brief Returns a const host pointer to the @a n'th NanoVDB grid encoded in this GridHandle.
337 /// @tparam ValueT Value type of the grid point to be returned
338 /// @param n Index of the (host) grid pointer to be returned
339 /// @warning Note that the return pointer can be NULL if the GridHandle no host grid, @a n is invalid
340 /// or if the template parameter does not match the specified grid!
341 template<typename ValueT, typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
342 const NanoGrid<ValueT>* grid(uint32_t n = 0) const;
343
344 /// @brief Returns a host pointer to the @a n'th NanoVDB grid encoded in this GridHandle.
345 /// @tparam ValueT Value type of the grid point to be returned
346 /// @param n Index of the (host) grid pointer to be returned
347 /// @warning Note that the return pointer can be NULL if the GridHandle no host grid, @a n is invalid
348 /// or if the template parameter does not match the specified grid!
349 template<typename ValueT, typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
350 NanoGrid<ValueT>* grid(uint32_t n = 0) {return const_cast<NanoGrid<ValueT>*>(static_cast<const GridHandle*>(this)->template grid<ValueT>(n));}
351
352 //@{
353 /// @brief Return a pointer to the @a n'th grid of a single-space device buffer
354 /// @warning Note that the return pointer can be NULL if the GridHandle is empty, @a n is invalid,
355 /// or if the template parameter does not match the specified grid.
356 template<typename ValueT, typename U = BufferT>
358 deviceGrid(uint32_t n=0) const { return this->template gridAt<ValueT>(mBuffer.data(), n); }
359 template<typename ValueT, typename U = BufferT>
361 deviceGrid(uint32_t n=0){return const_cast<NanoGrid<ValueT>*>(static_cast<const GridHandle*>(this)->template deviceGrid<ValueT>(n));}
362 //@}
363
364 /// @brief Check if the buffer is this handle has any padding, i.e. if the buffer is larger than the combined size of all its grids
365 /// @return true is the combined size of all grid is smaller than the buffer size
366 bool isPadded() const {return mMetaData.empty() ? false : mMetaData.back().offset + mMetaData.back().size != mBuffer.size();}
367
368 /// @brief Return the total number of grids contained in this buffer
369 uint32_t gridCount() const {return static_cast<uint32_t>(mMetaData.size());}
370
371 /// @brief Return the grid size of the @a n'th grid in this GridHandle
372 /// @param n index of the grid (assumed to be less than gridCount())
373 /// @return Return the byte size of the specified grid
374 uint64_t gridSize(uint32_t n = 0) const {return mMetaData[n].size; }
375
376 /// @brief compute the total sum of memory footprints of all the grids in this buffer
377 /// @return the number of bytes occupied by all grids associated with this buffer
378 uint64_t totalGridSize() const {
379 uint64_t sum = 0;
380 for (auto &m : mMetaData) sum += m.size;
381 NANOVDB_ASSERT(sum <= mBuffer.size());
382 return sum;
383 }
384
385 /// @brief compute the size of unused storage in this buffer
386 /// @return the number of unused bytes in this buffer.
387 uint64_t freeSize() const {return mBuffer.size() - this->totalGridSize();}
388
389 /// @brief Test if this buffer has any unused storage left, i.e. memory not occupied by grids
390 /// @return true if there is no extra storage left in this buffer, i.e. empty or fully occupied with grids
391 bool isFull() const { return this->totalGridSize() == mBuffer.size(); }
392
393 /// @brief Return the GridType of the @a n'th grid in this GridHandle
394 /// @param n index of the grid (assumed to be less than gridCount())
395 /// @return Return the GridType of the specified grid
396 GridType gridType(uint32_t n = 0) const {return mMetaData[n].gridType; }
397
398 /// @brief Access to the GridData of the n'th grid in the current handle
399 /// @param n zero-based ID of the grid
400 /// @return Const pointer to the n'th GridData in the current handle
401 template<typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
402 const GridData* gridData(uint32_t n = 0) const;
403
404 /// @brief Returns a const point to the @a n'th grid meta data
405 /// @param n zero-based ID of the grid
406 /// @warning Note that the return pointer can be NULL if the GridHandle was not initialized
407 template<typename U = BufferT, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type = 0>
408 const GridMetaData* gridMetaData(uint32_t n = 0) const;
409
410 /// @brief Write a specific grid in this buffer to an output stream
411 /// @param os output stream that the buffer will be written to
412 /// @param n zero-based index of the grid to be written to stream
413 void write(std::ostream& os, uint32_t n) const {
414 static_assert(!(BufferIsDeviceOnly<BufferT>::value),
415 "GridHandle::write requires host-accessible grids: cuda::copyTo a host-readable handle first");
416 if (const GridData* data = this->gridData(n)) {
417 os.write((const char*)data, data->mGridSize);
418 } else {
419 throw std::runtime_error("GridHandle does not contain a #" + std::to_string(n) + " grid");
420 }
421 }
422
423 /// @brief Write the entire grid buffer to an output stream
424 /// @param os output stream that the buffer will be written to
425 void write(std::ostream& os) const {
426 static_assert(!(BufferIsDeviceOnly<BufferT>::value),
427 "GridHandle::write requires host-accessible grids: cuda::copyTo a host-readable handle first");
428
429 for (uint32_t n=0; n<this->gridCount(); ++n) this->write(os, n);
430 }
431
432 /// @brief Write this entire grid buffer to a file
433 /// @param fileName string name of the output file
434 void write(const std::string &fileName) const {
435 std::ofstream os(fileName, std::ios::out | std::ios::binary | std::ios::trunc);
436 if (!os.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for output");
437 this->write(os);
438 }
439
440 /// @brief Write a specific grid to file
441 /// @param fileName string name of the output file
442 /// @param n zero-based index of the grid to be written to file
443 void write(const std::string &fileName, uint32_t n) const {
444 std::ofstream os(fileName, std::ios::out | std::ios::binary | std::ios::trunc);
445 if (!os.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for output");
446 this->write(os, n);
447 }
448
449 /// @brief Read an entire raw grid buffer from an input stream
450 /// @param is input stream containing a raw grid buffer
451 /// @param pool optional pool from which to allocate the new grid buffer
452 /// @throw Will throw a std::logic_error if the stream does not contain a valid raw grid
453 void read(std::istream& is, const BufferT& pool = BufferT());
454
455 /// @brief Read a specific grid from an input stream containing a raw grid buffer
456 /// @param is input stream containing a raw grid buffer
457 /// @param n zero-based index of the grid to be read
458 /// @param pool optional pool from which to allocate the new grid buffer
459 /// @throw Will throw a std::logic_error if the stream does not contain a valid raw grid
460 void read(std::istream& is, uint32_t n, const BufferT& pool = BufferT());
461
462 /// @brief Read a specific grid from an input stream containing a raw grid buffer
463 /// @param is input stream containing a raw grid buffer
464 /// @param gridName string name of the grid to be read
465 /// @param pool optional pool from which to allocate the new grid buffer
466 /// @throw Will throw a std::logic_error if the stream does not contain a valid raw grid with the specified name
467 void read(std::istream& is, const std::string &gridName, const BufferT& pool = BufferT());
468
469 /// @brief Read a raw grid buffer from a file
470 /// @param fileName string name of the input file containing a raw grid buffer
471 /// @param pool optional pool from which to allocate the new grid buffer
472 void read(const std::string &fileName, const BufferT& pool = BufferT()) {
473 std::ifstream is(fileName, std::ios::in | std::ios::binary);
474 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
475 this->read(is, pool);
476 }
477
478 /// @brief Read a specific grid from a file containing a raw grid buffer
479 /// @param fileName string name of the input file containing a raw grid buffer
480 /// @param n zero-based index of the grid to be read
481 /// @param pool optional pool from which to allocate the new grid buffer
482 /// @throw Will throw a std::ios_base::failure if the file does not exist and a
483 /// std::logic_error if the files does not contain a valid raw grid
484 void read(const std::string &fileName, uint32_t n, const BufferT& pool = BufferT()) {
485 std::ifstream is(fileName, std::ios::in | std::ios::binary);
486 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
487 this->read(is, n, pool);
488 }
489
490 /// @brief Read a specific grid from a file containing a raw grid buffer
491 /// @param fileName string name of the input file containing a raw grid buffer
492 /// @param gridName string name of the grid to be read
493 /// @param pool optional pool from which to allocate the new grid buffer
494 /// @throw Will throw a std::ios_base::failure if the file does not exist and a
495 /// std::logic_error if the files does not contain a valid raw grid withe the specified name
496 void read(const std::string &fileName, const std::string &gridName, const BufferT& pool = BufferT()) {
497 std::ifstream is(fileName, std::ios::in | std::ios::binary);
498 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
499 this->read(is, gridName, pool);
500 }
501}; // GridHandle
502
503namespace detail {
504
505/// @brief Wraps a byte-for-byte copy of a handle's buffer in a handle with
506/// that handle's layout. The layout already passed a header parse or
507/// GridHandle::adopt when the source was made, so it is not checked
508/// again. This also carries grids at unaligned offsets, which the
509/// constructors accept and GridHandle::adopt rejects. Used by cuda::copyTo.
511{
512 template<typename DstBufferT, typename SrcBufferT>
513 static GridHandle<DstBufferT> make(DstBufferT&& buffer, const GridHandle<SrcBufferT>& src)
514 {
515 if (buffer.size() != src.bufferSize())
516 throw std::runtime_error("GridHandle: a copied buffer must match the size of its source");
517 return GridHandle<DstBufferT>(std::move(buffer), src.metaData());
518 }
519};
520
521}// namespace detail
522
523// --------------------------> Implementation of private methods in GridHandle <------------------------------------
524
525template<typename BufferT>
526template<typename U, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type>
527inline const GridData* GridHandle<BufferT>::gridData(uint32_t n) const
528{
529 const void *data = this->data();
530 if (data == nullptr || n >= mMetaData.size()) return nullptr;
531 return util::PtrAdd<GridData>(data, mMetaData[n].offset);
532}// const GridData* GridHandle<BufferT>::gridData(uint32_t n) const
533
534template<typename BufferT>
535template<typename U, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type>
536inline const GridMetaData* GridHandle<BufferT>::gridMetaData(uint32_t n) const
537{
538 const auto *data = this->data();
539 if (data == nullptr || n >= mMetaData.size()) return nullptr;
540 return util::PtrAdd<GridMetaData>(data, mMetaData[n].offset);
541}// const GridMetaData* GridHandle<BufferT>::gridMetaData(uint32_t n) const
542
543template<typename BufferT>
544template <typename OtherBufferT>
545inline GridHandle<OtherBufferT> GridHandle<BufferT>::copy(const OtherBufferT& other) const
546{
548 "GridHandle::copy(pool) cannot honor a pool argument for a single-space device buffer, "
549 "whose copy allocates through the source buffer's resource: use the no-argument copy() "
550 "for a same-space deep copy, or cuda::copyTo (cuda/HandleStorage.h) to cross address spaces");
551 if (mBuffer.size() == 0) return GridHandle<OtherBufferT>();// return an empty handle
552 auto buffer = detail::createHostStorage<OtherBufferT>(mBuffer.size(), other);
553 std::memcpy(buffer.data(), mBuffer.data(), mBuffer.size());// deep copy of buffer
554 return GridHandle<OtherBufferT>(std::move(buffer));
555}// GridHandle<OtherBufferT> GridHandle<BufferT>::copy(const OtherBufferT& other) const
556
557template<typename BufferT>
558template <typename OtherBufferT>
559inline GridHandle<OtherBufferT> GridHandle<BufferT>::copy() const
560{
563 "GridHandle::copy is same-space only: a single-space device handle copies to its own "
564 "buffer type; use cuda::copyTo (cuda/HandleStorage.h) to move grids across address spaces");
565 // Device-to-device deep copy; for a stream-ordered resource it is
566 // ordered on the source's retained stream, so synchronize that stream
567 // before reading the result. Metadata is host-resident, so the copy
568 // reuses it directly with no device re-parse.
569 return GridHandle(mBuffer.copy(), mMetaData);
570 } else {
572 "GridHandle::copy() without arguments default-constructs the target pool buffer: "
573 "pass a prototype to copy(other) for a buffer over a non-default-constructible resource");
574 return this->copy(OtherBufferT());
575 }
576}// GridHandle<OtherBufferT> GridHandle<BufferT>::copy() const
577
578template<typename BufferT>
579template<typename ValueT, typename U, typename util::disable_if<BufferIsDeviceOnly<U>::value, int>::type>
580inline const NanoGrid<ValueT>* GridHandle<BufferT>::grid(uint32_t n) const
581{
582 return this->template gridAt<ValueT>(mBuffer.data(), n);
583}// const NanoGrid<ValueT>* GridHandle<BufferT>::grid(uint32_t n) const
584
585template<typename BufferT>
586void GridHandle<BufferT>::read(std::istream& is, const BufferT& pool)
587{
588 static_assert(!(BufferIsDeviceOnly<BufferT>::value),
589 "GridHandle::read requires a host-accessible buffer: read into a host-readable handle, then cuda::copyTo");
590 const std::streampos start = is.tellg();// remember where the raw buffer begins
592 is.read((char*)&data, sizeof(GridData));
593 if (data.isValid()) {
594 uint64_t size = data.mGridSize, sum = 0u;
595 while(data.mGridIndex + 1u < data.mGridCount) {// loop over remaining raw grids in stream
596 is.seekg(data.mGridSize - sizeof(GridData), std::ios::cur);// skip grid
597 is.read((char*)&data, sizeof(GridData));
598 sum += data.mGridSize;
599 }
600 auto buffer = detail::createHostStorage(size + sum, pool);
601 is.seekg(start);// rewind to the start of the raw buffer
602 is.read((char*)(buffer.data()), buffer.size());
603 *this = GridHandle(std::move(buffer));
604 } else {
605 is.seekg(-sizeof(GridData), std::ios::cur);// rewind
606 throw std::logic_error("This stream does not contain a valid raw grid buffer");
607 }
608}// void GridHandle<BufferT>::read(std::istream& is, const BufferT& pool)
609
610template<typename BufferT>
611void GridHandle<BufferT>::read(std::istream& is, uint32_t n, const BufferT& pool)
612{
613 static_assert(!(BufferIsDeviceOnly<BufferT>::value),
614 "GridHandle::read requires a host-accessible buffer: read into a host-readable handle, then cuda::copyTo");
616 is.read((char*)&data, sizeof(GridData));
617 if (data.isValid()) {
618 if (n>=data.mGridCount) throw std::runtime_error("stream does not contain a #" + std::to_string(n) + " grid");
619 while(data.mGridIndex != n) {
620 is.seekg(data.mGridSize - sizeof(GridData), std::ios::cur);// skip grid
621 is.read((char*)&data, sizeof(GridData));
622 }
623 auto buffer = detail::createHostStorage(data.mGridSize, pool);
624 is.seekg(-sizeof(GridData), std::ios::cur);// rewind
625 is.read((char*)(buffer.data()), data.mGridSize);
626 tools::updateGridCount((GridData*)buffer.data(), 0u, 1u);
627 *this = GridHandle(std::move(buffer));
628 } else {
629 is.seekg(-sizeof(GridData), std::ios::cur);// rewind sizeof(GridData) bytes to undo initial read
630 throw std::logic_error("This file does not contain a valid raw buffer");
631 }
632}// void GridHandle<BufferT>::read(std::istream& is, uint32_t n, const BufferT& pool)
633
634template<typename BufferT>
635void GridHandle<BufferT>::read(std::istream& is, const std::string &gridName, const BufferT& pool)
636{
637 static_assert(!(BufferIsDeviceOnly<BufferT>::value),
638 "GridHandle::read requires a host-accessible buffer: read into a host-readable handle, then cuda::copyTo");
639 static const std::streamsize byteSize = sizeof(GridData);
641 is.read((char*)&data, byteSize);
642 is.seekg(-byteSize, std::ios::cur);// rewind
643 if (data.isValid()) {
644 uint32_t n = 0;
645 while(data.mGridName != gridName && n++ < data.mGridCount) {
646 is.seekg(data.mGridSize, std::ios::cur);// skip grid
647 is.read((char*)&data, byteSize);// read sizeof(GridData) bytes
648 is.seekg(-byteSize, std::ios::cur);// rewind
649 }
650 if (n>data.mGridCount) throw std::runtime_error("No raw grid named \""+gridName+"\"");
651 auto buffer = detail::createHostStorage(data.mGridSize, pool);
652 is.read((char*)(buffer.data()), data.mGridSize);
653 tools::updateGridCount((GridData*)buffer.data(), 0u, 1u);
654 *this = GridHandle(std::move(buffer));
655 } else {
656 throw std::logic_error("This file does not contain a valid raw buffer");
657 }
658}// void GridHandle<BufferT>::read(std::istream& is, const std::string &gridName n, const BufferT& pool)
659
660// --------------------------> free-standing functions <------------------------------------
661
662/// @brief Split all grids in a single GridHandle into a vector of multiple GridHandles each with a single grid
663/// @tparam BufferT Type of the input and output grid buffers: HostBuffer, or
664/// a host-accessible single-space buffer such as
665/// cuda::Buffer<std::byte, cuda::PinnedResource>. Device handles use
666/// cuda::splitGridHandles.
667/// @param handle GridHandle with grids that will be split into individual GridHandles
668/// @param other optional pool used for allocation of output GridHandle
669/// @return Vector of GridHandles each containing a single grid
670/// @throw std::runtime_error if a grid header's size disagrees with the handle's metadata
671/// @note Grids are copied on the host without synchronizing any stream. For a
672/// buffer over a stream-ordered resource, synchronize the source's and
673/// the pool's streams first.
674template<typename BufferT, template <class, class...> class VectorT = std::vector>
675inline VectorT<GridHandle<BufferT>>
676splitGrids(const GridHandle<BufferT> &handle, const BufferT* other = nullptr)
677{
679 "splitGrids copies grids on the host: use cuda::splitGridHandles for a device handle");
680 using HandleT = GridHandle<BufferT>;
681 VectorT<HandleT> handles;
682 if (handle.data() == nullptr) return handles;
683 handles.reserve(handle.gridCount());// push_back, since BufferT may not be default-constructible
684 for (uint32_t n = 0; n < handle.gridCount(); ++n) {
685 const uint64_t size = handle.gridSize(n);
686 const GridData *src = handle.gridData(n);
687 NANOVDB_ASSERT(src->isValid());
688 if (src->mGridSize != size)
689 throw std::runtime_error("splitGrids: grid header size disagrees with the handle's metadata (grid " + std::to_string(n) + ")");
690 auto buffer = detail::createHostStorage<BufferT>(size, other);
691 GridData *dst = reinterpret_cast<GridData*>(buffer.data());
692 std::memcpy(dst, src, size);
693 tools::updateGridCount(dst, 0u, 1u);
694 handles.push_back(HandleT(std::move(buffer)));
695 }
696 return handles;
697}// splitGrids
698
699/// @brief Combines (or merges) multiple GridHandles into a single GridHandle containing all grids
700/// @tparam BufferT Type of the input and output grid buffers: HostBuffer, or
701/// a host-accessible single-space buffer such as
702/// cuda::Buffer<std::byte, cuda::PinnedResource>. Device handles use
703/// cuda::mergeGridHandles.
704/// @param handles Vector of GridHandles to be combined
705/// @param pool optional pool used for allocation of output GridHandle
706/// @return single GridHandle containing all input grids
707/// @throw std::runtime_error if a grid header's size disagrees with its handle's metadata
708/// @note Grids are copied on the host without synchronizing any stream. For a
709/// buffer over a stream-ordered resource, synchronize the sources' and
710/// the pool's streams first.
711template<typename BufferT, template <class, class...> class VectorT>
712inline GridHandle<BufferT>
713mergeGrids(const VectorT<GridHandle<BufferT>> &handles, const BufferT* pool = nullptr)
714{
716 "mergeGrids copies grids on the host: use cuda::mergeGridHandles for device handles");
717 uint64_t size = 0u;
718 uint32_t counter = 0u, gridCount = 0u;
719 for (auto &h : handles) {
720 gridCount += h.gridCount();
721 for (uint32_t n=0; n<h.gridCount(); ++n) {
722 if (h.gridData(n)->mGridSize != h.gridSize(n))
723 throw std::runtime_error("mergeGrids: grid header size disagrees with its handle's metadata (grid " + std::to_string(n) + ")");
724 size += h.gridSize(n);
725 }
726 }
727 auto buffer = detail::createHostStorage<BufferT>(size, pool);
728 void *dst = buffer.data();
729 for (auto &h : handles) {
730 for (uint32_t n=0; n<h.gridCount(); ++n) {
731 std::memcpy(dst, h.gridData(n), h.gridSize(n));
732 GridData *data = reinterpret_cast<GridData*>(dst);
733 NANOVDB_ASSERT(data->isValid());
734 tools::updateGridCount(data, counter++, gridCount);
735 dst = util::PtrAdd(dst, h.gridSize(n));
736 }
737 }
738 return GridHandle<BufferT>(std::move(buffer));
739}// mergeGrids
740
741} // namespace nanovdb
742
743// cuda/HandleStorage.h and tools/cuda/GridChecksum.cuh, which cuda/GridHandle.cuh
744// depends on, include this header before their own definitions; while either is
745// being parsed it defers cuda/GridHandle.cuh to its own end.
746#if defined(__CUDACC__) && !defined(NANOVDB_CUDA_HANDLESTORAGE_DEFERS_GRIDHANDLE_CUH) \
747 && !defined(NANOVDB_TOOLS_CUDA_GRIDCHECKSUM_DEFERS_GRIDHANDLE_CUH)
748#include <nanovdb/cuda/GridHandle.cuh>
749#endif// defined(__CUDACC__)
750
751#endif // NANOVDB_GRID_HANDLE_H_HAS_BEEN_INCLUDED
Computes a pair of uint32_t checksums, of a Grid, by means of 32 bit Cyclic Redundancy Check (CRC32)
HostBuffer - a buffer that contains a shared or private bump pool to either externally or internally ...
Implements a light-weight self-contained VDB data-structure in a single file! In other words,...
#define NANOVDB_DATA_ALIGNMENT
Definition NanoVDB.h:133
This class serves to manage a buffer containing one or more NanoVDB Grids.
Definition GridHandle.h:164
uint64_t freeSize() const
compute the size of unused storage in this buffer
Definition GridHandle.h:387
util::enable_if< BufferHasDeviceSingle< U >::value, constNanoGrid< ValueT > * >::type deviceGrid(uint32_t n=0) const
Return a pointer to the n'th grid of a single-space device buffer.
Definition GridHandle.h:358
GridHandle & operator=(const GridHandle &)=delete
Disallow copy assignment operation.
GridHandle(BufferT &&buffer)
Move constructor from a host or single-space device buffer.
Definition GridHandle.h:230
uint64_t totalGridSize() const
compute the total sum of memory footprints of all the grids in this buffer
Definition GridHandle.h:378
BufferT & buffer()
Return a reference to the buffer.
Definition GridHandle.h:297
void write(std::ostream &os) const
Write the entire grid buffer to an output stream.
Definition GridHandle.h:425
GridHandle(const GridHandle &)=delete
Disallow copy-construction.
GridHandle & operator=(GridHandle &&other) noexcept
Move copy assignment operation.
Definition GridHandle.h:269
GridHandle(U &&)=delete
Reject anything other than a BufferT rvalue, e.g. a size or a different buffer type that would otherw...
uint64_t bufferSize() const
Returns the size in bytes of the raw memory buffer managed by this GridHandle.
Definition GridHandle.h:324
void read(const std::string &fileName, const std::string &gridName, const BufferT &pool=BufferT())
Read a specific grid from a file containing a raw grid buffer.
Definition GridHandle.h:496
bool empty() const
Return true if this handle is empty, i.e. has no allocated memory.
Definition GridHandle.h:329
util::enable_if< BufferHasDeviceSingle< U >::value, void * >::type deviceData()
Definition GridHandle.h:319
const void * data() const
Definition GridHandle.h:309
const GridData * gridData(uint32_t n=0) const
Access to the GridData of the n'th grid in the current handle.
Definition GridHandle.h:527
const std::vector< GridHandleMetaData > & metaData() const
The layout of the grids in this handle's buffer: offset, size and grid type of each....
Definition GridHandle.h:222
util::enable_if< BufferHasDeviceSingle< U >::value, constvoid * >::type deviceData() const
For a single-space buffer the device data is the buffer itself.
Definition GridHandle.h:316
const NanoGrid< ValueT > * grid(uint32_t n=0) const
Returns a const host pointer to the n'th NanoVDB grid encoded in this GridHandle.
Definition GridHandle.h:580
const BufferT & buffer() const
Return a const reference to the buffer.
Definition GridHandle.h:300
GridHandle(GridHandle &&other) noexcept
Move copy-constructor.
Definition GridHandle.h:254
uint64_t gridSize(uint32_t n=0) const
Return the grid size of the n'th grid in this GridHandle.
Definition GridHandle.h:374
GridType gridType(uint32_t n=0) const
Return the GridType of the n'th grid in this GridHandle.
Definition GridHandle.h:396
void read(const std::string &fileName, uint32_t n, const BufferT &pool=BufferT())
Read a specific grid from a file containing a raw grid buffer.
Definition GridHandle.h:484
void write(const std::string &fileName) const
Write this entire grid buffer to a file.
Definition GridHandle.h:434
bool isPadded() const
Check if the buffer is this handle has any padding, i.e. if the buffer is larger than the combined si...
Definition GridHandle.h:366
static GridHandle adopt(BufferT &&buffer, std::vector< GridHandleMetaData > meta)
Constructs a handle over buffer from metadata the caller already knows to describe its contents,...
Definition GridHandle.h:213
NanoGrid< ValueT > * grid(uint32_t n=0)
Returns a host pointer to the n'th NanoVDB grid encoded in this GridHandle.
Definition GridHandle.h:350
const GridMetaData * gridMetaData(uint32_t n=0) const
Returns a const point to the n'th grid meta data.
Definition GridHandle.h:536
util::enable_if< BufferHasDeviceSingle< U >::value, NanoGrid< ValueT > * >::type deviceGrid(uint32_t n=0)
Definition GridHandle.h:361
BufferT BufferType
Definition GridHandle.h:199
GridHandle< OtherBufferT > copy() const
Deep copy without a pool argument. The single-space path allocates through the source buffer's resour...
Definition GridHandle.h:559
uint32_t gridCount() const
Return the total number of grids contained in this buffer.
Definition GridHandle.h:369
bool isEmpty() const
Definition GridHandle.h:330
void reset()
clear this GridHandle to an empty handle
Definition GridHandle.h:259
bool isFull() const
Test if this buffer has any unused storage left, i.e. memory not occupied by grids.
Definition GridHandle.h:391
void * data()
Returns a pointer to the host data; not available for a single-space device buffer,...
Definition GridHandle.h:307
void read(const std::string &fileName, const BufferT &pool=BufferT())
Read a raw grid buffer from a file.
Definition GridHandle.h:472
void write(const std::string &fileName, uint32_t n) const
Write a specific grid to file.
Definition GridHandle.h:443
void read(std::istream &is, const BufferT &pool=BufferT())
Read an entire raw grid buffer from an input stream.
Definition GridHandle.h:586
void write(std::ostream &os, uint32_t n) const
Write a specific grid in this buffer to an output stream.
Definition GridHandle.h:413
GridHandle< OtherBufferT > copy(const OtherBufferT &buffer) const
Performs a deep copy of the GridHandle, possibly templated on a different buffer type.
Definition GridHandle.h:545
GridHandle()=default
Constructs an empty GridHandle.
This is a convenient class that allows for access to grid meta-data that are independent of the value...
Definition NanoVDB.h:5630
Definition VoxToNanoVDB.h:15
void validateGridLayout(const std::vector< GridHandleMetaData > &meta, uint64_t bytes)
Checks that meta describes grids laid out back to back from offset zero inside bytes of storage,...
Definition GridHandle.h:87
BufferT createHostStorage(uint64_t bytes, const BufferT &pool)
Allocates bytes of host-readable storage for a GridHandle: through BufferT::create for buffers that p...
Definition GridHandle.h:53
void parseHostGridChain(const GridData *head, uint64_t bytes, std::vector< GridHandleMetaData > &meta)
Validates the grid chain in bytes of host-readable memory headed by head and fills meta with one entr...
Definition GridHandle.h:117
__hostdev__ void updateGridCount(GridData *data, uint32_t gridIndex, uint32_t gridCount)
Updates the ground index and count, as well as the head checksum if needed.
Definition GridChecksum.h:387
static DstT * PtrAdd(void *p, int64_t offset)
Adds a byte offset to a non-const pointer to produce another non-const pointer.
Definition Util.h:555
Defines a simple memory pool used to call cub functions that use dynamic temporary storage.
Definition GridHandle.h:34
GridHandle< BufferT > mergeGrids(const VectorT< GridHandle< BufferT > > &handles, const BufferT *pool=nullptr)
Combines (or merges) multiple GridHandles into a single GridHandle containing all grids.
Definition GridHandle.h:713
GridType toGridType()
Maps from a templated build type to a GridType enum.
Definition NanoVDB.h:848
Grid< NanoTree< BuildT > > NanoGrid
Definition NanoVDB.h:4699
VectorT< GridHandle< BufferT > > splitGrids(const GridHandle< BufferT > &handle, const BufferT *other=nullptr)
Split all grids in a single GridHandle into a vector of multiple GridHandles each with a single grid.
Definition GridHandle.h:676
GridType
List of types that are currently supported by NanoVDB.
Definition NanoVDB.h:223
@ End
Definition NanoVDB.h:250
Definition Coord.h:590
#define NANOVDB_ASSERT(x)
Definition Util.h:53
static constexpr bool value
Definition HostBuffer.h:146
static constexpr bool value
Definition HostBuffer.h:164
static constexpr bool value
Definition HostBuffer.h:111
static constexpr bool value
Definition HostBuffer.h:120
static constexpr bool value
Definition HostBuffer.h:137
static constexpr bool value
Definition HostBuffer.h:155
static constexpr bool value
Definition HostBuffer.h:131
Struct with all the member data of the Grid (useful during serialization of an openvdb grid)
Definition NanoVDB.h:1952
GridType mGridType
Definition NanoVDB.h:1966
bool isValid() const
return true if the magic number and the version are both valid
Definition NanoVDB.h:2009
uint64_t mGridSize
Definition NanoVDB.h:1960
uint32_t mGridCount
Definition NanoVDB.h:1959
uint32_t mGridIndex
Definition NanoVDB.h:1958
Definition GridHandle.h:38
uint64_t offset
Definition GridHandle.h:38
GridType gridType
Definition GridHandle.h:38
uint64_t size
Definition GridHandle.h:38
Detects a BufferTraits specialization that still declares the hasDeviceDual member removed in NanoVDB...
Definition GridHandle.h:152
static constexpr bool value
Definition GridHandle.h:152
Wraps a byte-for-byte copy of a handle's buffer in a handle with that handle's layout....
Definition GridHandle.h:511
static GridHandle< DstBufferT > make(DstBufferT &&buffer, const GridHandle< SrcBufferT > &src)
Definition GridHandle.h:513
C++11 implementation of std::enable_if.
Definition Util.h:384
static constexpr bool value
Definition Util.h:359