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