OpenVDB 14.0.0
Loading...
Searching...
No Matches
IO.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 IO.h
6
7 \author Ken Museth
8
9 \date May 1, 2020
10
11 \brief Implements I/O for NanoVDB grids. Features optional BLOSC and ZIP
12 file compression, support for multiple grids per file as well as
13 multiple grid types.
14
15 \note This file does NOT depend on OpenVDB, but optionally on ZIP and BLOSC
16
17 \details NanoVDB files take on one of the two following formats:
18 1) multiple segments each with multiple grids (segments have easy to access metadata about its grids)
19 2) starting with verion 32.6.0 nanovdb files also support a raw buffer with one or more grids (just a
20 dump of a raw grid buffer, so no new metadata in headers as when using segments mentioned above).
21
22 Example of case 1:
23 | <------------------------------------ segment 1 with N grids --------------------------------------> | <--- segment 2 ...
24 FileHeader, FileMetaData0, gridName0...FileMetaDataN, gridNameN, compressed Grid0, ... compressed GridN FileHeader ...
25 Example of case 2:
26 | <-- grid buffer ---> |
27 Grid0, Grid1, ... GridN
28
29 Note that FileHeader and FileMetaData (both defined in NanoVDB.h) have fixed sizes of respectively 16B and 176B.
30 However, GridNameX and GridX have variable sizes!
31*/
32
33#ifndef NANOVDB_IO_H_HAS_BEEN_INCLUDED
34#define NANOVDB_IO_H_HAS_BEEN_INCLUDED
35
36#include <nanovdb/NanoVDB.h>
37#include <nanovdb/GridHandle.h>
38#include <nanovdb/tools/GridChecksum.h>// for updateGridCount
39
40#include <fstream> // for std::ifstream
41#include <iostream> // for std::cerr/cout
42#include <string> // for std::string
43#include <sstream> // for std::stringstream
44#include <cstring> // for std::strcmp
45#include <type_traits> // for std::is_unsigned
46#include <memory> // for std::unique_ptr
47#include <vector> // for std::vector
48#ifdef NANOVDB_USE_ZIP
49#include <zlib.h> // for ZIP compression
50#endif
51#ifdef NANOVDB_USE_BLOSC
52#include <nanovdb_blosc.h> // for BLOSC compression
53#endif
54
55// Due to a bug in older versions of gcc, including fstream might
56// define "major" and "minor" which are used as member data below.
57// See https://bugzilla.redhat.com/show_bug.cgi?id=130601
58#if defined(major) || defined(minor)
59#undef major
60#undef minor
61#endif
62
63namespace nanovdb {// ==========================================================
64
65namespace io {// ===============================================================
66
67// --------------------------> writeGrid(s) <------------------------------------
68
69/// @brief Write a single grid to file (over-writing existing content of the file)
70///
71/// @note The single grid is written into a single segment, i.e. header with metadata about its type and size.
72template<typename BufferT>
73void writeGrid(const std::string& fileName, const GridHandle<BufferT>& handle, io::Codec codec = io::Codec::NONE, int verbose = 0);
74
75/// @brief Write multiple grids to file (over-writing existing content of the file)
76///
77/// @note The multiple grids are written into the same segment, i.e. header with metadata about all grids
78template<typename BufferT = HostBuffer, template<typename...> class VecT = std::vector>
79void writeGrids(const std::string& fileName, const VecT<GridHandle<BufferT>>& handles, Codec codec = Codec::NONE, int verbose = 0);
80
81// --------------------------> readGrid(s) <------------------------------------
82
83/// @brief Read and return one or all grids from a file into a single GridHandle
84/// @tparam BufferT Type of buffer used memory allocation
85/// @param fileName string name of file to be read from
86/// @param n zero-based signed index of the grid to be read.
87/// The default value of 0 means read only first grid.
88/// A negative value of n means read all grids in the file.
89/// @param verbose specify verbosity level. Default value of zero means quiet.
90/// @param buffer optional buffer used for memory allocation
91/// @return return a single GridHandle with one or all grids found in the file
92/// @throw will throw a std::runtime_error if the file does not contain a grid with index n
93template<typename BufferT = HostBuffer>
94GridHandle<BufferT> readGrid(const std::string& fileName, int n = 0, int verbose = 0, const BufferT& buffer = BufferT());
95
96/// @brief Read and return the first grid with a specific name from a file
97/// @tparam BufferT Type of buffer used memory allocation
98/// @param fileName string name of file to be read from
99/// @param gridName string name of the grid to be read
100/// @param verbose specify verbosity level. Default value of zero means quiet.
101/// @param buffer optional buffer used for memory allocation
102/// @return return a single GridHandle containing the grid with the specific name
103/// @throw will throw a std::runtime_error if the file does not contain a grid with the specific name
104template<typename BufferT = HostBuffer>
105GridHandle<BufferT> readGrid(const std::string& fileName, const std::string& gridName, int verbose = 0, const BufferT& buffer = BufferT());
106
107/// @brief Read all the grids in the file and return them as a vector of multiple GridHandles, each containing
108/// all grids encoded in the same segment of the file (i.e. they where written together). This method also
109/// works if the file contains a raw grid buffer in which case a single GridHandle is returned.
110/// @tparam BufferT Type of buffer used memory allocation
111/// @param fileName string name of file to be read from
112/// @param verbose specify verbosity level. Default value of zero means quiet.
113/// @param buffer optional buffer used for memory allocation
114/// @return Return a vector of GridHandles each containing all grids encoded
115/// in the same segment of the file (i.e. they where written together).
116template<typename BufferT = HostBuffer, template<typename...> class VecT = std::vector>
117VecT<GridHandle<BufferT>> readGrids(const std::string& fileName, int verbose = 0, const BufferT& buffer = BufferT());
118
119// -----------------------------------------------------------------------
120
121/// We fix a specific size for counting bytes in files so that they
122/// are saved the same regardless of machine precision. (Note there are
123/// still little/bigendian issues, however)
124using fileSize_t = uint64_t;
125
126/// @brief Internal functions for compressed read/write of a NanoVDB GridHandle into a stream
127///
128/// @warning These functions should never be called directly by client code
129/// @cond
130namespace Internal {
131static constexpr fileSize_t MAX_SIZE = 1UL << 30; // size is 1 GB
132
133/// @brief Add a grid size read from a stream to a running byte count
134/// @throw std::runtime_error if the sum wraps around
135template<typename SizeT>
136inline void addGridSize(SizeT& total, SizeT gridSize)
137{
138 static_assert(std::is_unsigned<SizeT>::value,
139 "addGridSize relies on unsigned wrap-around to detect overflow");
140 const SizeT sum = total + gridSize;// unsigned wrap-around is well defined
141 if (sum < total) throw std::runtime_error("Grid sizes in stream overflow a byte count");
142 total = sum;
143}
144
145template<typename BufferT>
146static fileSize_t write(std::ostream& os, const GridHandle<BufferT>& handle, Codec codec, uint32_t n);
147
148template<typename BufferT>
149static void read(std::istream& is, BufferT& buffer, Codec codec);
150
151static inline void read(std::istream& is, char* data, fileSize_t size, Codec codec);
152} // namespace Internal
153/// @endcond
154
155/// @brief Standard hash function to use on strings; std::hash may vary by
156/// platform/implementation and is know to produce frequent collisions.
157uint64_t stringHash(const char* cstr);
158
159/// @brief Return a uint64_t hash key of a std::string
160inline uint64_t stringHash(const std::string& str){return stringHash(str.c_str());}
161
162/// @brief Return a uint64_t with its bytes reversed so we can check for endianness
163inline uint64_t reverseEndianness(uint64_t val)
164{
165 return (((val) >> 56) & 0x00000000000000FF) | (((val) >> 40) & 0x000000000000FF00) |
166 (((val) >> 24) & 0x0000000000FF0000) | (((val) >> 8) & 0x00000000FF000000) |
167 (((val) << 8) & 0x000000FF00000000) | (((val) << 24) & 0x0000FF0000000000) |
168 (((val) << 40) & 0x00FF000000000000) | (((val) << 56) & 0xFF00000000000000);
169}
170
171/// @brief This class defines the meta data stored for each grid in a segment
172///
173/// @details A segment consists of a FileHeader followed by a list of FileGridMetaData
174/// each followed by grid names and then finally the grids themselves.
175///
176/// @note This class should not be confused with nanovdb::GridMetaData defined in NanoVDB.h
177/// Also, io::FileMetaData is defined in NanoVDB.h.
179{
180 static_assert(sizeof(FileMetaData) == 176, "Unexpected sizeof(FileMetaData)");
181 std::string gridName;
182 void read(std::istream& is);
183 void write(std::ostream& os) const;
185 FileGridMetaData(uint64_t size, Codec c, const GridData &gridData);
186 uint64_t memUsage() const { return sizeof(FileMetaData) + nameSize; }
187}; // FileGridMetaData
188
189/// @brief This class defines all the data stored in segment of a file
190///
191/// @details A segment consists of a FileHeader followed by a list of FileGridMetaData
192/// each followed by grid names and then finally the grids themselves.
194{
195 // Check assumptions made during read and write of FileHeader and FileMetaData
196 static_assert(sizeof(FileHeader) == 16u, "Unexpected sizeof(FileHeader)");
197 FileHeader header;// defined in NanoVDB.h
198 std::vector<FileGridMetaData> meta;// defined in NanoVDB.h
200#ifdef NANOVDB_USE_NEW_MAGIC_NUMBERS
202#else
204#endif
205 , meta()
206 {
207 }
208 template<typename BufferT>
209 void add(const GridHandle<BufferT>& h);
210 bool read(std::istream& is);
211 void write(std::ostream& os) const;
212 uint64_t memUsage() const;
213}; // Segment
214
215/// @brief Return true if the file contains a grid with the specified name
216bool hasGrid(const std::string& fileName, const std::string& gridName);
217
218/// @brief Return true if the stream contains a grid with the specified name
219bool hasGrid(std::istream& is, const std::string& gridName);
220
221/// @brief Reads and returns a vector of meta data for all the grids found in the specified file
222std::vector<FileGridMetaData> readGridMetaData(const std::string& fileName);
223
224/// @brief Reads and returns a vector of meta data for all the grids found in the specified stream
225std::vector<FileGridMetaData> readGridMetaData(std::istream& is);
226
227// --------------------------> Implementations for Internal <------------------------------------
228
229/// @cond
230
231template<typename BufferT>
232fileSize_t Internal::write(std::ostream& os, const GridHandle<BufferT>& handle, Codec codec, unsigned int n)
233{
234 const char* data = reinterpret_cast<const char*>(handle.gridData(n));
235 fileSize_t total = 0, residual = handle.gridSize(n);
236
237 switch (codec) {
238 case Codec::ZIP: {
239#ifdef NANOVDB_USE_ZIP
240 uLongf size = compressBound(static_cast<uLongf>(residual)); // Get an upper bound on the size of the compressed data.
241 std::unique_ptr<Bytef[]> tmp(new Bytef[size]);
242 const int status = compress(tmp.get(), &size, reinterpret_cast<const Bytef*>(data), static_cast<uLongf>(residual));
243 if (status != Z_OK) throw std::runtime_error("Internal write error in ZIP");
244 if (size > residual) std::cerr << "\nWarning: Unexpected ZIP compression from " << residual << " to " << size << " bytes\n";
245 const fileSize_t outBytes = size;
246 os.write(reinterpret_cast<const char*>(&outBytes), sizeof(fileSize_t));
247 os.write(reinterpret_cast<const char*>(tmp.get()), outBytes);
248 total += sizeof(fileSize_t) + outBytes;
249#else
250 throw std::runtime_error("ZIP compression codec was disabled during build");
251#endif
252 break;
253 }
254 case Codec::BLOSC: {
255#ifdef NANOVDB_USE_BLOSC
256 do {
257 fileSize_t chunk = residual < MAX_SIZE ? residual : MAX_SIZE, size = chunk + NANOVDB_BLOSC_MAX_OVERHEAD;
258 std::unique_ptr<char[]> tmp(new char[size]);
259 const int count = nanovdb_blosc_compress_ctx(9, 1, sizeof(float), chunk, data, tmp.get(), size, NANOVDB_BLOSC_LZ4_COMPNAME, 1 << 18, 1);
260 if (count <= 0) throw std::runtime_error("Internal write error in BLOSC");
261 const fileSize_t outBytes = count;
262 os.write(reinterpret_cast<const char*>(&outBytes), sizeof(fileSize_t));
263 os.write(reinterpret_cast<const char*>(tmp.get()), outBytes);
264 total += sizeof(fileSize_t) + outBytes;
265 data += chunk;
266 residual -= chunk;
267 } while (residual > 0);
268#else
269 throw std::runtime_error("BLOSC compression codec was disabled during build");
270#endif
271 break;
272 }
273 default:
274 os.write(data, residual);
275 total += residual;
276 }
277 if (!os) throw std::runtime_error("Failed to write Tree to file");
278 return total;
279} // Internal::write
280
281template<typename BufferT>
282void Internal::read(std::istream& is, BufferT& buffer, Codec codec)
283{
284 Internal::read(is, reinterpret_cast<char*>(buffer.data()), buffer.size(), codec);
285} // Internal::read
286
287/// @brief read compressed grid from stream
288/// @param is input stream to read from
289/// @param data data buffer to write into. Must be of size @c residual or larger.
290/// @param residual expected byte size of uncompressed data.
291/// @param codec mode of compression
292void Internal::read(std::istream& is, char* data, fileSize_t residual, Codec codec)
293{
294 // read tree using optional compression
295 switch (codec) {
296 case Codec::ZIP: {
297#ifdef NANOVDB_USE_ZIP
298 fileSize_t size;
299 is.read(reinterpret_cast<char*>(&size), sizeof(fileSize_t));
300 std::unique_ptr<Bytef[]> tmp(new Bytef[size]);// temp buffer for compressed data
301 is.read(reinterpret_cast<char*>(tmp.get()), size);
302 uLongf numBytes = static_cast<uLongf>(residual);
303 int status = uncompress(reinterpret_cast<Bytef*>(data), &numBytes, tmp.get(), static_cast<uLongf>(size));
304 if (status != Z_OK) throw std::runtime_error("Internal read error in ZIP");
305 if (fileSize_t(numBytes) != residual) throw std::runtime_error("UNZIP failed on byte size");
306#else
307 throw std::runtime_error("ZIP compression codec was disabled during build");
308#endif
309 break;
310 }
311 case Codec::BLOSC: {
312#ifdef NANOVDB_USE_BLOSC
313 do {
314 fileSize_t size;
315 is.read(reinterpret_cast<char*>(&size), sizeof(fileSize_t));
316 std::unique_ptr<char[]> tmp(new char[size]);// temp buffer for compressed data
317 is.read(reinterpret_cast<char*>(tmp.get()), size);
318 const fileSize_t chunk = residual < MAX_SIZE ? residual : MAX_SIZE;
319 const int count = nanovdb_blosc_decompress_ctx(tmp.get(), data, size_t(chunk), 1); //fails with more threads :(
320 if (count < 1) throw std::runtime_error("Internal read error in BLOSC");
321 if (count != int(chunk)) throw std::runtime_error("BLOSC failed on byte size");
322 data += size_t(chunk);
323 residual -= chunk;
324 } while (residual > 0);
325#else
326 throw std::runtime_error("BLOSC compression codec was disabled during build");
327#endif
328 break;
329 }
330 default:
331 is.read(data, residual);// read uncompressed data
332 }
333 if (!is) throw std::runtime_error("Failed to read Tree from file");
334} // Internal::read
335/// @endcond
336
337// --------------------------> Implementations for FileGridMetaData <------------------------------------
338
339inline FileGridMetaData::FileGridMetaData(uint64_t size, Codec c, const GridData &gridData)
340 : FileMetaData{size, // gridSize
341 size, // fileSize (will typically be redefined)
342 0u, // nameKey
343 0u, // voxelCount
344 gridData.mGridType, // gridType
345 gridData.mGridClass, // gridClass
346 gridData.mWorldBBox, // worldBBox
347 gridData.indexBBox(), // indexBBox
348 gridData.mVoxelSize, // voxelSize
349 0, // nameSize
350 {0, 0, 0, 1}, // nodeCount[4]
351 {0, 0, 0}, // tileCount[3]
352 c, // codec
353 uint16_t(gridData.mBlindMetadataCount), // number of blind meta data
354 Version()}// version
355 , gridName(gridData.gridName())
356{
357 NANOVDB_ASSERT(gridData.mBlindMetadataCount <= uint32_t(1 << 16));// due to uint32_t -> uin16_t conversion
358 auto &treeData = *reinterpret_cast<const TreeData*>(gridData.treePtr());
359 nameKey = stringHash(gridName);
360 voxelCount = treeData.mVoxelCount;
361 nameSize = static_cast<uint32_t>(gridName.size() + 1); // include '\0'
362 for (int i = 0; i < 3; ++i) {
363 FileMetaData::nodeCount[i] = treeData.mNodeCount[i];
364 FileMetaData::tileCount[i] = treeData.mTileCount[i];
365 }
366}// FileGridMetaData::FileGridMetaData
367
368inline void FileGridMetaData::write(std::ostream& os) const
369{
370 os.write(reinterpret_cast<const char*>(this), sizeof(FileMetaData));
371 os.write(gridName.c_str(), nameSize);
372 if (!os) throw std::runtime_error("Failed writing FileGridMetaData");
373}// FileGridMetaData::write
374
375inline void FileGridMetaData::read(std::istream& is)
376{
377 is.read(reinterpret_cast<char*>(this), sizeof(FileMetaData));
378 std::unique_ptr<char[]> tmp(new char[nameSize]);
379 is.read(reinterpret_cast<char*>(tmp.get()), nameSize);
380 gridName.assign(tmp.get());
381 if (!is) throw std::runtime_error("Failed reading FileGridMetaData");
382}// FileGridMetaData::read
383
384// --------------------------> Implementations for Segment <------------------------------------
385
386inline uint64_t Segment::memUsage() const
387{
388 uint64_t sum = sizeof(FileHeader);
389 for (auto& m : meta) sum += m.memUsage();// includes FileMetaData + grid name
390 return sum;
391}// Segment::memUsage
392
393template<typename BufferT>
395{
396 for (uint32_t i = 0; i < h.gridCount(); ++i) {
397 const GridData *gridData = h.gridData(i);
398 if (!gridData) throw std::runtime_error("Segment::add: GridHandle does not contain grid #" + std::to_string(i));
399 meta.emplace_back(h.gridSize(i), header.codec, *gridData);
400 }
401 header.gridCount += h.gridCount();
402}// Segment::add
403
404inline void Segment::write(std::ostream& os) const
405{
406 if (header.gridCount == 0) {
407 throw std::runtime_error("Segment contains no grids");
408 } else if (!os.write(reinterpret_cast<const char*>(&header), sizeof(FileHeader))) {
409 throw std::runtime_error("Failed to write FileHeader of Segment");
410 }
411 for (auto& m : meta) m.write(os);
412}// Segment::write
413
414inline bool Segment::read(std::istream& is)
415{
416 is.read(reinterpret_cast<char*>(&header), sizeof(FileHeader));
417 if (is.eof()) {// The EOF flag is only set once a read tries to read past the end of the file
418 is.clear(std::ios_base::eofbit);// clear eof flag so we can rewind and read again
419 return false;
420 }
421 const MagicType magic = toMagic(header.magic);
422 if (magic != MagicType::NanoVDB && magic != MagicType::NanoFile) {
423 // first check for byte-swapped header magic.
426 throw std::runtime_error("This nvdb file has reversed endianness");
427 } else {
428 if (magic == MagicType::OpenVDB) {
429 throw std::runtime_error("Expected a NanoVDB file, but read an OpenVDB file!");
430 } else if (magic == MagicType::NanoGrid) {
431 throw std::runtime_error("Expected a NanoVDB file, but read a raw NanoVDB grid!");
432 } else {
433 throw std::runtime_error("Expected a NanoVDB file, but read a file of unknown type!");
434 }
435 }
436 } else if ( !header.version.isCompatible()) {
437 std::stringstream ss;
438 Version v;
439 is.read(reinterpret_cast<char*>(&v), sizeof(Version));// read GridData::mVersion located at byte 16=sizeof(FileHeader) is stream
441 ss << "This file looks like it contains a raw grid buffer and not a standard file with meta data";
442 } else if ( header.version.getMajor() < NANOVDB_MAJOR_VERSION_NUMBER) {
443 char str[30];
444 ss << "The file contains an older version of NanoVDB: " << std::string(toStr(str, header.version)) << "!\n\t"
445 << "Recommendation: Re-generate this NanoVDB file with this version: " << NANOVDB_MAJOR_VERSION_NUMBER << ".X of NanoVDB";
446 } else {
447 ss << "This tool was compiled against an older version of NanoVDB: " << NANOVDB_MAJOR_VERSION_NUMBER << ".X!\n\t"
448 << "Recommendation: Re-compile this tool against the newer version: " << header.version.getMajor() << ".X of NanoVDB";
449 }
450 throw std::runtime_error("An unrecoverable error in nanovdb::Segment::read:\n\tIncompatible file format: " + ss.str());
451 }
452 meta.resize(header.gridCount);
453 for (auto& m : meta) {
454 m.read(is);
455 m.version = header.version;
456 }
457 return true;
458}// Segment::read
459
460// --------------------------> writeGrid <------------------------------------
461
462template<typename BufferT>
463void writeGrid(std::ostream& os, const GridHandle<BufferT>& handle, Codec codec)
464{
465 Segment seg(codec);
466 seg.add(handle);
467 const auto start = os.tellp();
468 seg.write(os); // write header without the correct fileSize (so it's allocated)
469 for (uint32_t i = 0; i < handle.gridCount(); ++i) {
470 seg.meta[i].fileSize = Internal::write(os, handle, codec, i);
471 }
472 os.seekp(start);
473 seg.write(os);// re-write header with the correct fileSize
474 os.seekp(0, std::ios_base::end);// skip to end
475}// writeGrid
476
477template<typename BufferT>
478void writeGrid(const std::string& fileName, const GridHandle<BufferT>& handle, Codec codec, int verbose)
479{
480 std::ofstream os(fileName, std::ios::out | std::ios::binary | std::ios::trunc);
481 if (!os.is_open()) {
482 throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for output");
483 }
484 writeGrid<BufferT>(os, handle, codec);
485 if (verbose) {
486 std::cout << "Wrote nanovdb::Grid to file named \"" << fileName << "\"" << std::endl;
487 }
488}// writeGrid
489
490// --------------------------> writeGrids <------------------------------------
491
492template<typename BufferT = HostBuffer, template<typename...> class VecT = std::vector>
493void writeGrids(std::ostream& os, const VecT<GridHandle<BufferT>>& handles, Codec codec = Codec::NONE)
494{
495 for (auto& h : handles) writeGrid(os, h, codec);
496}// writeGrids
497
498template<typename BufferT, template<typename...> class VecT>
499void writeGrids(const std::string& fileName, const VecT<GridHandle<BufferT>>& handles, Codec codec, int verbose)
500{
501 std::ofstream os(fileName, std::ios::out | std::ios::binary | std::ios::trunc);
502 if (!os.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for output");
503 writeGrids<BufferT, VecT>(os, handles, codec);
504 if (verbose) std::cout << "Wrote " << handles.size() << " nanovdb::Grid(s) to file named \"" << fileName << "\"" << std::endl;
505}// writeGrids
506
507// --------------------------> readGrid <------------------------------------
508
509template<typename BufferT>
510GridHandle<BufferT> readGrid(std::istream& is, int n, const BufferT& pool)
511{
512 GridHandle<BufferT> handle;
513 if (n<0) {// read all grids into the same buffer
514 try {//first try to read a raw grid buffer
515 handle.read(is, pool);
516 } catch(const std::logic_error&) {
517 Segment seg;
518 uint64_t bufferSize = 0u;
519 uint32_t gridCount = 0u, gridIndex = 0u;
520 const auto start = is.tellg();
521 while (seg.read(is)) {
522 std::streamoff skipSize = 0;
523 for (auto& m : seg.meta) {
524 ++gridCount;
525 Internal::addGridSize(bufferSize, m.gridSize);
526 skipSize += m.fileSize;
527 }// loop over grids in segment
528 is.seekg(skipSize, std::ios_base::cur); // skip forward from the current position
529 }// loop over segments
530 auto buffer = BufferT::create(bufferSize, &pool);
531 char *ptr = (char*)buffer.data();
532 is.seekg(start);// rewind
533 while (seg.read(is)) {
534 for (auto& m : seg.meta) {
535 Internal::read(is, ptr, m.gridSize, seg.header.codec);
536 tools::updateGridCount((GridData*)ptr, gridIndex++, gridCount);
537 ptr += m.gridSize;
538 }// loop over grids in segment
539 }// loop over segments
540 return GridHandle<BufferT>(std::move(buffer));
541 }
542 } else {// read a specific grid
543 try {//first try to read a raw grid buffer
544 handle.read(is, uint32_t(n), pool);
545 tools::updateGridCount((GridData*)handle.data(), 0u, 1u);
546 } catch(const std::logic_error&) {
547 Segment seg;
548 int counter = -1;
549 while (seg.read(is)) {
550 std::streamoff seek = 0;
551 for (auto& m : seg.meta) {
552 if (++counter == n) {
553 auto buffer = BufferT::create(m.gridSize, &pool);
554 Internal::read(is, buffer, seg.header.codec);
555 tools::updateGridCount((GridData*)buffer.data(), 0u, 1u);
556 return GridHandle<BufferT>(std::move(buffer));
557 } else {
558 seek += m.fileSize;
559 }
560 }// loop over grids in segment
561 is.seekg(seek, std::ios_base::cur); // skip forward from the current position
562 }// loop over segments
563 if (n != counter) throw std::runtime_error("stream does not contain a #" + std::to_string(n) + " grid");
564 }
565 }
566 return handle;
567}// readGrid
568
569/// @brief Read the n'th grid
570template<typename BufferT>
571GridHandle<BufferT> readGrid(const std::string& fileName, int n, int verbose, const BufferT& buffer)
572{
573 std::ifstream is(fileName, std::ios::in | std::ios::binary);
574 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
575 auto handle = readGrid<BufferT>(is, n, buffer);
576 if (verbose) {
577 if (n<0) {
578 std::cout << "Read all NanoGrids from the file named \"" << fileName << "\"" << std::endl;
579 } else {
580 std::cout << "Read NanoGrid # " << n << " from the file named \"" << fileName << "\"" << std::endl;
581 }
582 }
583 return handle; // is converted to r-value and return value is move constructed.
584}// readGrid
585
586/// @brief Read a specific grid from an input stream given the name of the grid
587/// @tparam BufferT Buffer type used for allocation
588/// @param is input stream from which to read the grid
589/// @param gridName string name of the (first) grid to be returned
590/// @param pool optional memory pool from which to allocate the grid buffer
591/// @return Return the first grid in the input stream with a specific name
592/// @throw std::runtime_error with no grid exists with the specified name
593template<typename BufferT>
594GridHandle<BufferT> readGrid(std::istream& is, const std::string& gridName, const BufferT& pool)
595{
596 try {
597 GridHandle<BufferT> handle;
598 handle.read(is, gridName, pool);
599 return handle;
600 } catch(const std::logic_error&) {
601 const auto key = stringHash(gridName);
602 Segment seg;
603 while (seg.read(is)) {// loop over all segments in stream
604 std::streamoff seek = 0;
605 for (auto& m : seg.meta) {// loop over all grids in segment
606 if ((m.nameKey == 0u || m.nameKey == key) && m.gridName == gridName) { // check for hash key collision
607 auto buffer = BufferT::create(m.gridSize, &pool);
608 is.seekg(seek, std::ios_base::cur); // rewind
609 Internal::read(is, buffer, seg.header.codec);
610 tools::updateGridCount((GridData*)buffer.data(), 0u, 1u);
611 return GridHandle<BufferT>(std::move(buffer));
612 } else {
613 seek += m.fileSize;
614 }
615 }
616 is.seekg(seek, std::ios_base::cur); // skip forward from the current position
617 }
618 }
619 throw std::runtime_error("Grid name '" + gridName + "' not found in file");
620}// readGrid
621
622/// @brief Read the first grid with a specific name
623template<typename BufferT>
624GridHandle<BufferT> readGrid(const std::string& fileName, const std::string& gridName, int verbose, const BufferT& buffer)
625{
626 std::ifstream is(fileName, std::ios::in | std::ios::binary);
627 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
628 auto handle = readGrid<BufferT>(is, gridName, buffer);
629 if (verbose) {
630 if (handle) {
631 std::cout << "Read NanoGrid named \"" << gridName << "\" from the file named \"" << fileName << "\"" << std::endl;
632 } else {
633 std::cout << "File named \"" << fileName << "\" does not contain a grid named \"" + gridName + "\"" << std::endl;
634 }
635 }
636 return handle; // is converted to r-value and return value is move constructed.
637}// readGrid
638
639// --------------------------> readGrids <------------------------------------
640
641template<typename BufferT = HostBuffer, template<typename...> class VecT = std::vector>
642VecT<GridHandle<BufferT>> readGrids(std::istream& is, const BufferT& pool = BufferT())
643{
644 VecT<GridHandle<BufferT>> handles;
645 try {//first try to read a raw grid buffer
646 GridHandle<BufferT> handle;
647 handle.read(is, pool);// will throw if stream does not contain a raw grid buffer
648 handles.push_back(std::move(handle)); // force move copy assignment
649 } catch(const std::logic_error&) {
650 Segment seg;
651 while (seg.read(is)) {
652 uint64_t bufferSize = 0;
653 for (auto& m : seg.meta) Internal::addGridSize(bufferSize, m.gridSize);
654 auto buffer = BufferT::create(bufferSize, &pool);
655 uint64_t bufferOffset = 0;
656 for (uint16_t i = 0; i < seg.header.gridCount; ++i) {
657 auto *data = util::PtrAdd<GridData>(buffer.data(), bufferOffset);
658 Internal::read(is, (char*)data, seg.meta[i].gridSize, seg.header.codec);
659 tools::updateGridCount(data, uint32_t(i), uint32_t(seg.header.gridCount));
660 bufferOffset += seg.meta[i].gridSize;
661 }// loop over grids in segment
662 handles.emplace_back(std::move(buffer)); // force move copy assignment
663 }// loop over segments
664 }
665 return handles; // is converted to r-value and return value is move constructed.
666}// readGrids
667
668/// @brief Read all the grids
669template<typename BufferT, template<typename...> class VecT>
670VecT<GridHandle<BufferT>> readGrids(const std::string& fileName, int verbose, const BufferT& buffer)
671{
672 std::ifstream is(fileName, std::ios::in | std::ios::binary);
673 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
674 auto handles = readGrids<BufferT, VecT>(is, buffer);
675 if (verbose) std::cout << "Read " << handles.size() << " NanoGrid(s) from the file named \"" << fileName << "\"" << std::endl;
676 return handles; // is converted to r-value and return value is move constructed.
677}// readGrids
678
679// --------------------------> readGridMetaData <------------------------------------
680
681inline std::vector<FileGridMetaData> readGridMetaData(const std::string& fileName)
682{
683 std::ifstream is(fileName, std::ios::in | std::ios::binary);
684 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
685 return readGridMetaData(is); // is converted to r-value and return value is move constructed.
686}// readGridMetaData
687
688inline std::vector<FileGridMetaData> readGridMetaData(std::istream& is)
689{
690 Segment seg;
691 std::vector<FileGridMetaData> meta;
692 try {
693 GridHandle<> handle;// if stream contains a raw grid buffer we unfortunately have to load everything
694 handle.read(is);
695 seg.add(handle);
696 meta = std::move(seg.meta);
697 } catch(const std::logic_error&) {
698 while (seg.read(is)) {
699 std::streamoff skip = 0;
700 for (auto& m : seg.meta) {
701 meta.push_back(m);
702 skip += m.fileSize;
703 }// loop over grid meta data in segment
704 is.seekg(skip, std::ios_base::cur);
705 }// loop over segments
706 }
707 return meta; // is converted to r-value and return value is move constructed.
708}// readGridMetaData
709
710// --------------------------> hasGrid <------------------------------------
711
712inline bool hasGrid(const std::string& fileName, const std::string& gridName)
713{
714 std::ifstream is(fileName, std::ios::in | std::ios::binary);
715 if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
716 return hasGrid(is, gridName);
717}// hasGrid
718
719inline bool hasGrid(std::istream& is, const std::string& gridName)
720{
721 const auto key = stringHash(gridName);
722 Segment seg;
723 while (seg.read(is)) {
724 std::streamoff seek = 0;
725 for (auto& m : seg.meta) {
726 if (m.nameKey == key && m.gridName == gridName) return true; // check for hash key collision
727 seek += m.fileSize;
728 }// loop over grid meta data in segment
729 is.seekg(seek, std::ios_base::cur);
730 }// loop over segments
731 return false;
732}// hasGrid
733
734// --------------------------> stringHash <------------------------------------
735
736inline uint64_t stringHash(const char* c_str)
737{
738 uint64_t hash = 0;// zero is returned when cstr = nullptr or "\0"
739 if (c_str) {
740 for (auto* str = reinterpret_cast<const unsigned char*>(c_str); *str; ++str) {
741 uint64_t overflow = hash >> (64 - 8);
742 hash *= 67; // Next-ish prime after 26 + 26 + 10
743 hash += *str + overflow;
744 }
745 }
746 return hash;
747}// stringHash
748
749} // namespace io ======================================================================
750
751} // namespace nanovdb ===================================================================
752
753// the following stream specializations should not be namespaced!
754
755template<typename T>
756inline std::ostream&
757operator<<(std::ostream& os, const nanovdb::math::BBox<nanovdb::math::Vec3<T>>& b)
758{
759 os << "(" << b[0][0] << "," << b[0][1] << "," << b[0][2] << ") -> "
760 << "(" << b[1][0] << "," << b[1][1] << "," << b[1][2] << ")";
761 return os;
762}
763
764inline std::ostream&
765operator<<(std::ostream& os, const nanovdb::CoordBBox& b)
766{
767 os << "(" << b[0][0] << "," << b[0][1] << "," << b[0][2] << ") -> "
768 << "(" << b[1][0] << "," << b[1][1] << "," << b[1][2] << ")";
769 return os;
770}
771
772inline std::ostream&
773operator<<(std::ostream& os, const nanovdb::Coord& ijk)
774{
775 os << "(" << ijk[0] << "," << ijk[1] << "," << ijk[2] << ")";
776 return os;
777}
778
779template<typename T>
780inline std::ostream&
781operator<<(std::ostream& os, const nanovdb::math::Vec3<T>& v)
782{
783 os << "(" << v[0] << "," << v[1] << "," << v[2] << ")";
784 return os;
785}
786
787template<typename T>
788inline std::ostream&
789operator<<(std::ostream& os, const nanovdb::math::Vec4<T>& v)
790{
791 os << "(" << v[0] << "," << v[1] << "," << v[2] << "," << v[3] << ")";
792 return os;
793}
794
795#endif // NANOVDB_IO_H_HAS_BEEN_INCLUDED
Computes a pair of uint32_t checksums, of a Grid, by means of 32 bit Cyclic Redundancy Check (CRC32)
Defines GridHandle, which manages a memory buffer containing one or more NanoVDB grids: host-resident...
Implements a light-weight self-contained VDB data-structure in a single file! In other words,...
#define NANOVDB_MAGIC_FILE
Definition NanoVDB.h:141
#define NANOVDB_MAJOR_VERSION_NUMBER
Definition NanoVDB.h:146
#define NANOVDB_MAGIC_NUMB
Definition NanoVDB.h:139
This class serves to manage a buffer containing one or more NanoVDB Grids.
Definition GridHandle.h:164
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
uint64_t gridSize(uint32_t n=0) const
Return the grid size of the n'th grid in this GridHandle.
Definition GridHandle.h:374
uint32_t gridCount() const
Return the total number of grids contained in this buffer.
Definition GridHandle.h:369
void * data()
Returns a pointer to the host data; not available for a single-space device buffer,...
Definition GridHandle.h:307
void read(std::istream &is, const BufferT &pool=BufferT())
Read an entire raw grid buffer from an input stream.
Definition GridHandle.h:586
This is a buffer that contains a shared or private pool to either externally or internally managed ho...
Definition HostBuffer.h:180
Bit-compacted representation of all three version numbers.
Definition NanoVDB.h:727
uint32_t getMajor() const
Definition NanoVDB.h:754
Signed (i, j, k) 32-bit integer coordinate class, similar to openvdb::math::Coord.
Definition Math.h:425
A simple vector class with three components, similar to openvdb::math::Vec3.
Definition Math.h:1865
A simple vector class with four components, similar to openvdb::math::Vec4.
Definition Math.h:2034
__hostdev__ uint32_t hash(uint32_t x)
Definition common.h:16
std::ostream & operator<<(std::ostream &os, const nanovdb::math::BBox< nanovdb::math::Vec3< T > > &b)
Definition IO.h:757
Definition NanoVDB.h:5973
VecT< GridHandle< BufferT > > readGrids(const std::string &fileName, int verbose=0, const BufferT &buffer=BufferT())
Read all the grids in the file and return them as a vector of multiple GridHandles,...
Definition IO.h:670
void writeGrids(const std::string &fileName, const VecT< GridHandle< BufferT > > &handles, Codec codec=Codec::NONE, int verbose=0)
Write multiple grids to file (over-writing existing content of the file)
Definition IO.h:499
uint64_t fileSize_t
Definition IO.h:124
std::vector< FileGridMetaData > readGridMetaData(const std::string &fileName)
Reads and returns a vector of meta data for all the grids found in the specified file.
Definition IO.h:681
uint64_t stringHash(const char *cstr)
Internal functions for compressed read/write of a NanoVDB GridHandle into a stream.
Definition IO.h:736
uint64_t reverseEndianness(uint64_t val)
Return a uint64_t with its bytes reversed so we can check for endianness.
Definition IO.h:163
void writeGrid(const std::string &fileName, const GridHandle< BufferT > &handle, io::Codec codec=io::Codec::NONE, int verbose=0)
Write a single grid to file (over-writing existing content of the file)
Definition IO.h:478
GridHandle< BufferT > readGrid(const std::string &fileName, int n=0, int verbose=0, const BufferT &buffer=BufferT())
Read and return one or all grids from a file into a single GridHandle.
Definition IO.h:571
Codec
Define compression codecs.
Definition NanoVDB.h:5981
@ ZIP
Definition NanoVDB.h:5982
@ BLOSC
Definition NanoVDB.h:5983
@ NONE
Definition NanoVDB.h:5981
bool hasGrid(const std::string &fileName, const std::string &gridName)
Return true if the file contains a grid with the specified name.
Definition IO.h:712
__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
MagicType toMagic(uint64_t magic)
maps 64 bits of magic number to enum
Definition NanoVDB.h:370
MagicType
Enums used to identify magic numbers recognized by NanoVDB.
Definition NanoVDB.h:361
@ OpenVDB
Definition NanoVDB.h:362
@ NanoGrid
Definition NanoVDB.h:364
@ NanoVDB
Definition NanoVDB.h:363
@ NanoFile
Definition NanoVDB.h:365
char * toStr(char *dst, GridType gridType)
Maps a GridType to a c-string.
Definition NanoVDB.h:257
math::BBox< Coord > CoordBBox
Definition Math.h:2838
#define NANOVDB_ASSERT(x)
Definition Util.h:53
Struct with all the member data of the Grid (useful during serialization of an openvdb grid)
Definition NanoVDB.h:1952
void write(std::ostream &os) const
Definition IO.h:368
uint64_t memUsage() const
Definition IO.h:186
std::string gridName
Definition IO.h:181
FileGridMetaData()
Definition IO.h:184
void read(std::istream &is)
Definition IO.h:375
Data encoded at the head of each segment of a file or stream.
Definition NanoVDB.h:6009
uint16_t gridCount
Definition NanoVDB.h:6012
Codec codec
Definition NanoVDB.h:6013
Definition NanoVDB.h:6035
CoordBBox indexBBox
Definition NanoVDB.h:6040
uint32_t nameSize
Definition NanoVDB.h:6042
This class defines all the data stored in segment of a file.
Definition IO.h:194
bool read(std::istream &is)
Definition IO.h:414
void add(const GridHandle< BufferT > &h)
Definition IO.h:394
void write(std::ostream &os) const
Definition IO.h:404
std::vector< FileGridMetaData > meta
Definition IO.h:198
uint64_t memUsage() const
Definition IO.h:386
Segment(Codec c=Codec::NONE)
Definition IO.h:199
FileHeader header
Definition IO.h:197
Definition Math.h:2412