io/nanovdb.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file nanovdb.h | ||
| 3 | /// @brief NanoVDB grids: reading .nvdb/.vdb files and building grids. | ||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "ernie/geometry/bounds.h" | ||
| 7 | |||
| 8 | #include <glm/glm.hpp> | ||
| 9 | |||
| 10 | #include <cstdint> | ||
| 11 | #include <filesystem> | ||
| 12 | #include <span> | ||
| 13 | #include <string> | ||
| 14 | #include <string_view> | ||
| 15 | #include <vector> | ||
| 16 | |||
| 17 | namespace ernie { | ||
| 18 | |||
| 19 | /// @brief A NanoVDB grid's value type, numbered as | ||
| 20 | /// `PNANOVDB_GRID_TYPE_*`. | ||
| 21 | /// Volumes sample scalars and float vectors; other types load only. | ||
| 22 | enum class NanoVdbType : std::uint32_t { | ||
| 23 | Unknown = 0, | ||
| 24 | Float = 1, | ||
| 25 | Double = 2, | ||
| 26 | Vec3f = 6, | ||
| 27 | Half = 9, | ||
| 28 | Fp4 = 13, | ||
| 29 | Fp8 = 14, | ||
| 30 | Fp16 = 15, | ||
| 31 | FpN = 16, | ||
| 32 | Vec4f = 17, | ||
| 33 | }; | ||
| 34 | |||
| 35 | /// @brief One sparse grid in NanoVDB layout, with its header's facts. | ||
| 36 | /// The GPU walks the buffer in place (PNanoVDB). The grid's map | ||
| 37 | /// takes index space to the grid frame (OpenVDB's "world"), which a | ||
| 38 | /// Volume prim's transform places. | ||
| 39 | /// | ||
| 40 | /// References: | ||
| 41 | /// - K. Museth, "NanoVDB: A GPU-Friendly and Portable VDB Data | ||
| 42 | /// Structure For Real-Time Rendering And Simulation", ACM SIGGRAPH | ||
| 43 | /// 2021 Talks. https://doi.org/10.1145/3450623.3464653 | ||
| 44 | /// - K. Museth, "VDB: High-Resolution Sparse Volumes with Dynamic | ||
| 45 | /// Topology", ACM Transactions on Graphics 32(3), 27, 2013. | ||
| 46 | /// https://doi.org/10.1145/2487228.2487235 | ||
| 47 | /// - NanoVDB's file layout, nanovdb/io/IO.h and FileMetaData in | ||
| 48 | /// nanovdb/NanoVDB.h, OpenVDB 13.0.0. | ||
| 49 | 50 | struct NanoVdbGrid { | |
| 50 | /// The grid's name. | ||
| 51 | std::string name; | ||
| 52 | /// The value type. | ||
| 53 | NanoVdbType type = NanoVdbType::Unknown; | ||
| 54 | /// Index coordinates to the grid frame, from the grid's map. | ||
| 55 | glm::dmat4 indexToGrid{1.0}; | ||
| 56 | /// Lower inclusive index bound of active values; min > max when | ||
| 57 | /// there are none. | ||
| 58 | glm::ivec3 indexMin{0}; | ||
| 59 | /// Upper inclusive index bound of active values. | ||
| 60 | glm::ivec3 indexMax{-1}; | ||
| 61 | /// A voxel's size in the grid frame. | ||
| 62 | glm::dvec3 voxelSize{1.0}; | ||
| 63 | /// Whether the grid has value statistics (HAS_MIN_MAX; Warp's | ||
| 64 | /// grids do not). | ||
| 65 | bool hasStatistics = false; | ||
| 66 | /// Smallest active value (vector length for vector grids); 0 | ||
| 67 | /// without statistics. | ||
| 68 | double minValue = 0.0; | ||
| 69 | /// Largest active value (vector length for vector grids); 0 | ||
| 70 | /// without statistics. | ||
| 71 | double maxValue = 0.0; | ||
| 72 | /// The grid from its GridData header on, padded to 8 bytes; | ||
| 73 | /// empty when only the head was read (`NanoVdbGridHead`). | ||
| 74 | std::vector<std::uint8_t> buffer; | ||
| 75 | /// The grid's size in bytes, per its header. | ||
| 76 | std::uint64_t size = 0; | ||
| 77 | |||
| 78 | /// @return Whether the grid has no active values. | ||
| 79 | bool Empty() const; | ||
| 80 | /// @return Whether volumes sample one value per voxel. | ||
| 81 | bool Scalar() const; | ||
| 82 | /// @return Whether volumes sample a 3-vector per voxel. | ||
| 83 | bool Vector() const; | ||
| 84 | /// @return Active bounds in the grid frame, grown by a voxel on each side for trilinear | ||
| 85 | /// sampling; the grid's origin when it has no active voxels. | ||
| 86 | Box GridBounds() const; | ||
| 87 | }; | ||
| 88 | |||
| 89 | /// @brief Reads every grid of a NanoVDB file, in file order. | ||
| 90 | /// Accepts segmented files or a raw grid buffer. | ||
| 91 | /// @param path The .nvdb file. | ||
| 92 | /// @return The grids. | ||
| 93 | /// @throws std::runtime_error If unreadable, not NanoVDB, not major | ||
| 94 | /// version 32, or zip/blosc compressed (save uncompressed: | ||
| 95 | /// `nanovdb_convert` without `-z`/`-b`). | ||
| 96 | std::vector<NanoVdbGrid> ReadNanoVdb(const std::filesystem::path& path); | ||
| 97 | |||
| 98 | /// @brief Reads the headers of a NanoVDB file's grids (names, types, bounds and transforms) | ||
| 99 | /// without their voxels, as `NanoVdbGridHead` does for each grid. | ||
| 100 | /// @throws std::runtime_error As `ReadNanoVdb`. | ||
| 101 | std::vector<NanoVdbGrid> ReadNanoVdbHeads(const std::filesystem::path& path); | ||
| 102 | |||
| 103 | /// @brief Splits a raw in-memory NanoVDB buffer into its grids. | ||
| 104 | /// @param bytes Grids back to back, as Warp's `Volume.array()`. | ||
| 105 | /// @return The grids. | ||
| 106 | /// @throws std::runtime_error If it does not start with a version 32 | ||
| 107 | /// NanoVDB grid. | ||
| 108 | std::vector<NanoVdbGrid> ParseNanoVdbGrids(std::span<const std::uint8_t> bytes); | ||
| 109 | /// @brief Reads the headers of a raw buffer's grids, without copying their voxels. | ||
| 110 | /// @throws std::runtime_error As `ParseNanoVdbGrids`. | ||
| 111 | std::vector<NanoVdbGrid> ParseNanoVdbHeads(std::span<const std::uint8_t> bytes); | ||
| 112 | |||
| 113 | /// Bytes at a grid's start holding its header, tree and root, root | ||
| 114 | /// statistics included. | ||
| 115 | inline constexpr std::size_t kNanoVdbHeadBytes = 1024; | ||
| 116 | |||
| 117 | /// @brief Reads a grid's header without its buffer, for grids on a | ||
| 118 | /// device of which only the head reaches the host. | ||
| 119 | /// @param head The first `kNanoVdbHeadBytes`, or fewer for small | ||
| 120 | /// grids. | ||
| 121 | /// @return The grid; its `size` locates the next grid. | ||
| 122 | /// @throws std::runtime_error If `head` is not a version 32 NanoVDB | ||
| 123 | /// grid or ends before its root. | ||
| 124 | NanoVdbGrid NanoVdbGridHead(std::span<const std::uint8_t> head); | ||
| 125 | |||
| 126 | /// @brief Finds the grid a UsdVol OpenVDBAsset names. | ||
| 127 | /// @param grids The file's grids. | ||
| 128 | /// @param name The grid name; empty matches any. | ||
| 129 | /// @param index Which match, 0 the first. | ||
| 130 | /// @return The grid, or null if there is none. | ||
| 131 | const NanoVdbGrid* FindNanoVdbGrid(std::span<const NanoVdbGrid> grids, std::string_view name, | ||
| 132 | int index = 0); | ||
| 133 | |||
| 134 | /// @brief Builds an in-memory NanoVDB float grid (a fog volume) from | ||
| 135 | /// dense values, active where not 0, with bounds and min/max. | ||
| 136 | /// @param values C-order (x, y, z) values, z fastest. | ||
| 137 | /// @param shape The voxel counts along x, y and z. | ||
| 138 | /// @param voxelSize The voxel size in the grid frame; positive. | ||
| 139 | /// @param origin Where index (0, 0, 0) lies in the grid frame. | ||
| 140 | /// @param name The grid's name. | ||
| 141 | /// @return The grid buffer, for a NanoVDBField's array. | ||
| 142 | /// @throws std::invalid_argument If `values` does not match `shape` | ||
| 143 | /// or `voxelSize` is not positive. | ||
| 144 | /// | ||
| 145 | /// References: | ||
| 146 | /// - K. Museth, "NanoVDB: A GPU-Friendly and Portable VDB Data | ||
| 147 | /// Structure For Real-Time Rendering And Simulation", ACM SIGGRAPH | ||
| 148 | /// 2021 Talks. https://doi.org/10.1145/3450623.3464653 | ||
| 149 | /// - PNanoVDB.h, OpenVDB 13.0.0: the layout and its setters. | ||
| 150 | std::vector<std::uint8_t> DenseNanoVdbGrid(std::span<const float> values, const glm::ivec3& shape, | ||
| 151 | double voxelSize, const glm::dvec3& origin, | ||
| 152 | std::string_view name = ""); | ||
| 153 | |||
| 154 | /// @brief Where a `DenseNanoVdbTopology`'s values go. | ||
| 155 | /// Leaf (a, b, c) starts at firstLeaf + ((a leaves.y + b) leaves.z + | ||
| 156 | /// c) leafBytes; voxel (i, j, k) is at valuesOffset + 4 ((i & 7) 64 | ||
| 157 | /// + (j & 7) 8 + (k & 7)) within it. | ||
| 158 | struct DenseNanoVdbLayout { | ||
| 159 | /// Byte offset of the first leaf in the grid. | ||
| 160 | std::uint64_t firstLeaf = 0; | ||
| 161 | /// Bytes per leaf. | ||
| 162 | std::uint32_t leafBytes = 0; | ||
| 163 | /// Byte offset of a leaf's values. | ||
| 164 | std::uint32_t valuesOffset = 0; | ||
| 165 | /// Leaves (8^3 blocks) along x, y and z. | ||
| 166 | glm::ivec3 leaves{0}; | ||
| 167 | }; | ||
| 168 | |||
| 169 | /// @brief As `DenseNanoVdbGrid` with every voxel active and 0, for | ||
| 170 | /// values written in place later (a DenseField on the GPU). | ||
| 171 | /// No min and max. | ||
| 172 | /// @param shape The voxel counts along x, y and z. | ||
| 173 | /// @param voxelSize The voxel size in the grid frame; positive. | ||
| 174 | /// @param origin Where index (0, 0, 0) lies in the grid frame. | ||
| 175 | /// @param name The grid's name. | ||
| 176 | /// @param layout Receives where the values go. | ||
| 177 | /// @return The grid buffer. | ||
| 178 | /// @throws std::invalid_argument For a negative shape or a voxel | ||
| 179 | /// size that is not positive. | ||
| 180 | std::vector<std::uint8_t> DenseNanoVdbTopology(const glm::ivec3& shape, double voxelSize, | ||
| 181 | const glm::dvec3& origin, std::string_view name, | ||
| 182 | DenseNanoVdbLayout& layout); | ||
| 183 | |||
| 184 | } // namespace ernie | ||
| 185 |