diff --git a/ci/install_windows.ps1 b/ci/install_windows.ps1
index 6054cb96bf..a4d44da353 100644
--- a/ci/install_windows.ps1
+++ b/ci/install_windows.ps1
@@ -21,20 +21,52 @@ $vcpkgPackages = @(
"nanobind"
)
+$maxAttempts = 3
+
+# curl's schannel backend reports an unreachable CRL/OCSP responder as a
+# certificate verification failure (error 60), which vcpkg then treats as
+# permanent. Downgrade a missing revocation answer to a warning; the rest of
+# certificate validation still applies.
+$env:VCPKG_SSL_REVOKE_BEST_EFFORT = "1"
+
# Update vcpkg
vcpkg update
-# Allow the vcpkg command to fail once so we can retry with the latest
-try {
- vcpkg install $vcpkgPackages
-} catch {
- Write-Host "vcpkg install failed, retrying with latest ports..."
- # Retry the installation with updated ports
- Push-Location $env:VCPKG_INSTALLATION_ROOT
- git pull
- Pop-Location
- vcpkg update
+$installed = $false
+
+for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
vcpkg install $vcpkgPackages
+
+ # A failing native command does not raise a terminating error, so the exit
+ # code has to be inspected explicitly rather than relying on try/catch.
+ if ($LASTEXITCODE -eq 0) {
+ $installed = $true
+ break
+ }
+
+ if ($attempt -eq $maxAttempts) {
+ break
+ }
+
+ # vcpkg fetches port sources directly from upstream hosts and won't retry
+ # downloads it classifies as permanent failures, so a single flaky TLS
+ # handshake aborts the whole install.
+ Write-Host "vcpkg install failed (attempt $attempt of $maxAttempts), retrying..."
+ Start-Sleep -Seconds 15
+
+ # Refresh the ports before the last attempt in case the failure is caused
+ # by a stale port rather than the network.
+ if ($attempt -eq ($maxAttempts - 1)) {
+ Write-Host "Retrying with latest ports..."
+ Push-Location $env:VCPKG_INSTALLATION_ROOT
+ git pull
+ Pop-Location
+ vcpkg update
+ }
+}
+
+if (-not $installed) {
+ throw "vcpkg install failed after $maxAttempts attempts"
}
Write-Host "vcpkg install completed successfully"
diff --git a/doc/changes.txt b/doc/changes.txt
index 3c05074c81..033231bbaa 100644
--- a/doc/changes.txt
+++ b/doc/changes.txt
@@ -2034,7 +2034,7 @@ Bug fixes:
New features:
- Added @vdblink{tools::FindActiveValues,FindActiveValues}, which counts
the active values in a tree that intersect a given bounding box.
-- Added @vdblink{io::DelayedLoadMetadata,DelayedLoadMetadata}, which stores
+- Added @c io::DelayedLoadMetadata, which stores
mask offsets and compression sizes on write to accelerate delayed load
reading.
@@ -2933,7 +2933,7 @@ New features:
- Added a toggle to the @vdblink::tools::clip() clip@endlink tool
to invert the clipping mask.
- Custom leaf node implementations may now optimize their file layout
- by inheriting from @vdblink::io::MultiPass io::MultiPass@endlink.
+ by inheriting from @c io::MultiPass.
Voxel data for grids with such leaf nodes will be written and read in
multiple passes, allowing blocks of related data to be stored contiguously.
[Contributed by Double Negative]
diff --git a/openvdb/openvdb/Grid.h b/openvdb/openvdb/Grid.h
index a71afad140..d32f064c8d 100644
--- a/openvdb/openvdb/Grid.h
+++ b/openvdb/openvdb/Grid.h
@@ -9,6 +9,7 @@
#include "Types.h"
#include "io/io.h"
#include "math/Transform.h"
+#include "tree/LeafManager.h"
#include "tree/Tree.h"
#include "util/Assert.h"
#include "util/logging.h"
@@ -1632,6 +1633,28 @@ inline void
Grid::readTopology(std::istream& is)
{
tree().readTopology(is, saveFloatAsHalf());
+ // When called from the legacy (non-codec) TopologyOnly path, the stream
+ // metadata carries a flag requesting that leaf buffers be allocated and
+ // filled with the background value (PartialCreate leaves them
+ // unallocated after readTopology).
+ if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(is)) {
+ if (meta->allocateLeafBuffers()) {
+ meta->setAllocateLeafBuffers(false);
+ if constexpr (!std::is_void_v) {
+ const auto background = tree().root().background();
+ tree::LeafManager leafManager(tree());
+ leafManager.foreach([&background](auto& leaf, size_t) {
+ using LeafType = std::decay_t;
+ if constexpr (!std::is_same_v) {
+ if (leaf.buffer().empty()) {
+ leaf.buffer().allocate();
+ leaf.buffer().fill(background);
+ }
+ }
+ });
+ }
+ }
+ }
}
diff --git a/openvdb/openvdb/codecs/BoolCodec.h b/openvdb/openvdb/codecs/BoolCodec.h
index e8537fa662..82c7ae5a38 100644
--- a/openvdb/openvdb/codecs/BoolCodec.h
+++ b/openvdb/openvdb/codecs/BoolCodec.h
@@ -110,7 +110,7 @@ struct BoolCodec final: public TopologyCodec
static inline std::string name() { return GridT::gridType(); }
- void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
+ void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
{
GridT& grid = static_cast(*data.grid);
diff --git a/openvdb/openvdb/codecs/PointDataCodec.h b/openvdb/openvdb/codecs/PointDataCodec.h
index f4bc713bd7..7cb6219cb7 100644
--- a/openvdb/openvdb/codecs/PointDataCodec.h
+++ b/openvdb/openvdb/codecs/PointDataCodec.h
@@ -111,13 +111,14 @@ template
inline void readPointDataVoxelData(const std::vector& leaves,
std::istream& is, bool saveFloatAsHalf,
const typename LeafT::ValueType& background,
- [[maybe_unused]] const std::unordered_map& voxelBufferSizes)
+ [[maybe_unused]] const std::unordered_map& voxelBufferSizes,
+ const typename LeafT::ValueType* storageBackground = nullptr)
{
using BaseLeaf = typename LeafT::BaseLeaf;
for (auto* leaf : leaves) {
OPENVDB_ASSERT(voxelBufferSizes.find(leaf->origin()) != voxelBufferSizes.end());
BaseLeaf& baseLeaf = static_cast(*leaf);
- readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background);
+ readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr, storageBackground);
}
}
@@ -310,7 +311,7 @@ struct PointDataCodec final: public TopologyCodec
static inline std::string name() { return GridT::gridType(); }
- void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
+ void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
{
OPENVDB_ASSERT(dynamic_cast(data.grid.get()));
@@ -334,7 +335,11 @@ struct PointDataCodec final: public TopologyCodec
uint16_t numPasses = 1;
is.read(reinterpret_cast(&numPasses), sizeof(uint16_t));
- const Index attributes = (numPasses - 4) / 2;
+ // The pass layout is: voxel sizes (1) + descriptors (1) + attribute
+ // sizes (N) + voxel data (1) + attribute data (N) = 2N + 4 passes.
+ // A leafless grid stores numPasses == 0, and malformed files may store
+ // numPasses < 4; guard against unsigned underflow in either case.
+ const Index attributes = numPasses >= 4 ? Index(numPasses - 4) / 2 : 0;
using LeafT = typename GridT::TreeType::LeafNodeType;
std::vector leaves;
@@ -351,7 +356,17 @@ struct PointDataCodec final: public TopologyCodec
// An empty pointAttributeNames means no filtering (read all attributes).
std::set skipIndices;
if (!pointAttributeNames.empty() && !leaves.empty()) {
- const auto& nameMap = leaves[0]->attributeSet().descriptor().map();
+ // Attribute filtering requires homogeneous descriptors across all
+ // leaves because skip decisions are made per-index across all leaves.
+ const auto* firstDesc = &leaves[0]->attributeSet().descriptor();
+ for (size_t i = 1; i < leaves.size(); ++i) {
+ if (&leaves[i]->attributeSet().descriptor() != firstDesc) {
+ OPENVDB_THROW(IoError,
+ "Attribute filtering is not supported for PointDataGrids "
+ "with heterogeneous descriptors");
+ }
+ }
+ const auto& nameMap = firstDesc->map();
const std::set wantedNames(
pointAttributeNames.begin(),
pointAttributeNames.end());
@@ -373,8 +388,10 @@ struct PointDataCodec final: public TopologyCodec
}
// Pass N+2: read voxel data
+ using ValueT = typename GridT::TreeType::ValueType;
+ auto& topoData = static_cast&>(data);
internal::readPointDataVoxelData(leaves, is, saveFloatAsHalf,
- tree.background(), voxelBufferSizes);
+ tree.background(), voxelBufferSizes, &topoData.storageBackground);
// Passes N+3..2N+2: read attribute data buffers
for (Index i = 0; i < attributes; ++i) {
@@ -419,7 +436,9 @@ struct PointDataCodec final: public TopologyCodec
static_cast(internal::countPointDataPasses(leaves));
os.write(reinterpret_cast(&numPasses), sizeof(uint16_t));
- const Index attributes = (numPasses - 4) / 2;
+ // See readBuffers(): a leafless grid yields numPasses == 0, so guard
+ // against unsigned underflow rather than computing (numPasses - 4) / 2.
+ const Index attributes = numPasses >= 4 ? Index(numPasses - 4) / 2 : 0;
// Pass 0: write voxel data sizes + descriptor tracking
bool matching = true;
diff --git a/openvdb/openvdb/codecs/PointIndexCodec.h b/openvdb/openvdb/codecs/PointIndexCodec.h
index 851ee140fd..d1694d5b96 100644
--- a/openvdb/openvdb/codecs/PointIndexCodec.h
+++ b/openvdb/openvdb/codecs/PointIndexCodec.h
@@ -26,10 +26,11 @@ struct ReadPointIndexBuffersOp
using ValueT = typename TreeT::ValueType;
ReadPointIndexBuffersOp(std::istream& _is, bool _saveFloatAsHalf,
- const ValueT& _background)
+ const ValueT& _background, const ValueT* _storageBackground = nullptr)
: is(_is)
, saveFloatAsHalf(_saveFloatAsHalf)
- , background(_background) { }
+ , background(_background)
+ , storageBackground(_storageBackground) { }
template
void operator()(NodeT&, size_t) { }
@@ -40,7 +41,7 @@ struct ReadPointIndexBuffersOp
// Read the value mask and voxel data via base class
BaseLeaf& baseLeaf = static_cast(leaf);
- readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr);
+ readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr, storageBackground);
// Read the number of indices.
Index64 numIndices = Index64(0);
@@ -63,6 +64,7 @@ struct ReadPointIndexBuffersOp
std::istream& is;
const bool saveFloatAsHalf;
const ValueT& background;
+ const ValueT* storageBackground = nullptr;
}; // struct ReadPointIndexBuffersOp
template
@@ -114,7 +116,7 @@ struct PointIndexCodec final: public TopologyCodec
static inline std::string name() { return GridT::gridType(); }
- void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics& diagnostics) final
+ void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics& diagnostics) final
{
OPENVDB_ASSERT(dynamic_cast(data.grid.get()));
@@ -135,7 +137,9 @@ struct PointIndexCodec final: public TopologyCodec
diagnostics.addWarning(grid.getName(), "bounding box clipping is not supported for PointIndexGrids");
}
- internal::ReadPointIndexBuffersOp readBuffersOp(is, saveFloatAsHalf, tree.background());
+ using ValueT = typename GridT::TreeType::ValueType;
+ auto& topoData = static_cast&>(data);
+ internal::ReadPointIndexBuffersOp readBuffersOp(is, saveFloatAsHalf, tree.background(), &topoData.storageBackground);
tools::visitNodesDepthFirst(grid.tree(), readBuffersOp, /*idx=*/0, /*topDown=*/false);
}
diff --git a/openvdb/openvdb/codecs/ScalarCodec.h b/openvdb/openvdb/codecs/ScalarCodec.h
index 114c575bcf..60b1eb6425 100644
--- a/openvdb/openvdb/codecs/ScalarCodec.h
+++ b/openvdb/openvdb/codecs/ScalarCodec.h
@@ -23,21 +23,26 @@ template
struct WriteBuffersOp
{
using LeafT = typename TreeT::LeafNodeType;
+ using ValueT = typename TreeT::ValueType;
- WriteBuffersOp(std::ostream& _os, bool _saveFloatAsHalf)
+ WriteBuffersOp(std::ostream& _os, bool _saveFloatAsHalf, const ValueT& _background)
: os(_os)
- , saveFloatAsHalf(_saveFloatAsHalf) { }
+ , saveFloatAsHalf(_saveFloatAsHalf)
+ , background(_background) { }
template
void operator()(const NodeT&, size_t) { }
void operator()(const LeafT& leaf, size_t)
{
- writeScalarLeafBuffers(leaf, os, saveFloatAsHalf);
+ // Pass the background explicitly so leaf compression does not depend on
+ // the stream's background pointer (which the codec path no longer sets).
+ writeScalarLeafBuffers(leaf, os, saveFloatAsHalf, &background);
}
std::ostream& os;
const bool saveFloatAsHalf;
+ const ValueT& background;
}; // struct WriteBuffersOp
@@ -48,13 +53,15 @@ struct ReadBuffersOp
using LeafT = typename TreeT::LeafNodeType;
using ValueT = typename TreeT::ValueType;
using StorageLeafT = typename StorageTreeT::LeafNodeType;
+ using StorageValueT = typename StorageTreeT::ValueType;
ReadBuffersOp(std::istream& _is, bool _saveFloatAsHalf, const ValueT& _background,
- const CoordBBox* _clipBBox)
+ const CoordBBox* _clipBBox, const StorageValueT* _storageBackground = nullptr)
: is(_is)
, saveFloatAsHalf(_saveFloatAsHalf)
, background(_background)
- , clipBBox(_clipBBox) { }
+ , clipBBox(_clipBBox)
+ , storageBackground(_storageBackground) { }
void operator()(RootT& root, size_t)
{
@@ -73,20 +80,22 @@ struct ReadBuffersOp
void operator()(LeafT& leaf, size_t)
{
- readScalarLeafBuffers(leaf, is, saveFloatAsHalf, background, /*skip=*/false, clipBBox);
+ readScalarLeafBuffers(leaf, is, saveFloatAsHalf, background, /*skip=*/false, clipBBox, storageBackground);
}
std::istream& is;
const bool saveFloatAsHalf;
const ValueT& background;
const CoordBBox* clipBBox = nullptr;
+ const StorageValueT* storageBackground = nullptr;
}; // struct ReadBuffersOp
// Free-standing function for both standard and conversion codec cases
// Uses StorageGridT = GridT by default, but allows different storage type for conversions
template
-void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions& options)
+void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions& options,
+ const typename StorageGridT::TreeType::ValueType* storageBackground)
{
if (grid.hasMultiPassIO()) {
OPENVDB_THROW(IoError, "Multi-pass IO is not supported in ScalarCodec");
@@ -109,7 +118,7 @@ void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions
// Works for both standard (TreeT == StorageTreeT) and conversion cases
ReadBuffersOp readBuffersOp(is, saveFloatAsHalf, tree.background(),
- clipIndexBBox.get());
+ clipIndexBBox.get(), storageBackground);
tools::visitNodesDepthFirst(grid.tree(), readBuffersOp, /*idx=*/0, /*topDown=*/false);
}
@@ -123,7 +132,7 @@ void scalarCodecWriteBuffers(const GridT& grid, std::ostream& os)
OPENVDB_THROW(IoError, "Multi-pass IO is not supported in ScalarCodec");
}
- WriteBuffersOp writeBuffersOp(os, grid.saveFloatAsHalf());
+ WriteBuffersOp writeBuffersOp(os, grid.saveFloatAsHalf(), grid.tree().background());
tools::visitNodesDepthFirst(grid.tree(), writeBuffersOp);
}
@@ -150,18 +159,25 @@ struct ScalarCodec final: public TopologyCodec
}
}
- void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
+ void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
{
+ using StorageValueT = typename StorageGridT::TreeType::ValueType;
GridT& grid = static_cast(*data.grid);
- internal::scalarCodecReadBuffers(grid, is, options);
+ auto& topoData = static_cast&>(data);
+ internal::scalarCodecReadBuffers(grid, is, options, &topoData.storageBackground);
}
void writeBuffers(std::ostream& os, const GridBase& gridBase, const io::WriteOptions&) final
{
- if constexpr (Mode == io::CodecMode::ReadOnly) return;
-
- const GridT& grid = static_cast(gridBase);
- internal::scalarCodecWriteBuffers(grid, os);
+ // Note: the write body must live inside the negated if constexpr branch
+ // so it is not instantiated for read-only codecs. A bare
+ // `if constexpr (Mode == ReadOnly) return;` would still instantiate the
+ // code that follows, which fails to compile for the scalar-to-mask/bool
+ // convert codecs (their leaf buffers expose WordType*, not ValueType*).
+ if constexpr (Mode != io::CodecMode::ReadOnly) {
+ const GridT& grid = static_cast(gridBase);
+ internal::scalarCodecWriteBuffers(grid, os);
+ }
}
}; // struct ScalarCodec
diff --git a/openvdb/openvdb/codecs/TopologyCodec.h b/openvdb/openvdb/codecs/TopologyCodec.h
index e602f176ce..eee4ead6e5 100644
--- a/openvdb/openvdb/codecs/TopologyCodec.h
+++ b/openvdb/openvdb/codecs/TopologyCodec.h
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include
#include
@@ -15,6 +16,16 @@ namespace openvdb {
OPENVDB_USE_VERSION_NAMESPACE
namespace OPENVDB_VERSION_NAME {
namespace codecs {
+
+/// Per-read-operation state shared between readTopology() and readBuffers().
+/// Stores the storage-typed background so readBuffers() can pass it directly
+/// to readCompressedValues(), bypassing the stream background ptr entirely.
+template
+struct TopologyCodecData : public io::CodecData
+{
+ StorageValueT storageBackground{};
+}; // struct TopologyCodecData
+
namespace internal {
template
@@ -40,7 +51,6 @@ struct WriteTopologyOp
ValueT truncatedVal = io::truncateRealToHalf(*background);
os.write(reinterpret_cast(&truncatedVal), sizeof(ValueT));
}
- io::setGridBackgroundValuePtr(os, background);
const Index numTiles = root.tileCount(), numChildren = root.childCount();
os.write(reinterpret_cast(&numTiles), sizeof(Index));
@@ -83,7 +93,7 @@ struct WriteTopologyOp
values[i] = (node.isChildMaskOff(i) ? node.getValueUnsafe(i) : zero);
}
// Compress (optionally) and write out the contents of the array.
- io::writeCompressedValues(os, values, NodeT::NUM_VALUES, valueMask, childMask, saveFloatAsHalf);
+ io::writeCompressedValues(os, values, NodeT::NUM_VALUES, valueMask, childMask, saveFloatAsHalf, background);
}
}
@@ -129,16 +139,8 @@ struct ReadTopologyOp
// Read a RootNode that was stored in the current format.
- if constexpr (std::is_same_v) {
- is.read(reinterpret_cast(&background), sizeof(ValueT));
- } else {
- StorageValueT _background;
- is.read(reinterpret_cast(&_background), sizeof(StorageValueT));
- background = static_cast(_background);
- }
-
- root.setBackground(background, false);
- io::setGridBackgroundValuePtr(is, &root.background());
+ is.read(reinterpret_cast(&storageBackground), sizeof(StorageValueT));
+ background = static_cast(storageBackground);
Index numTiles = 0, numChildren = 0;
is.read(reinterpret_cast(&numTiles), sizeof(Index));
@@ -190,12 +192,16 @@ struct ReadTopologyOp
// into a contiguous array.
std::unique_ptr valuePtr(new StorageValueT[numValues]);
StorageValueT* values = valuePtr.get();
- io::readCompressedValues(is, values, numValues, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, values, numValues, valueMask, saveFloatAsHalf, &storageBackground);
// Copy values from the array into this node's table.
if (oldVersion) {
+ // The node's member child mask is still empty at this point
+ // (PartialCreate; setChildUnsafe runs below), so iterate the
+ // local childMask's off-bits to match the legacy ordering and
+ // avoid over-reading the countOff-sized values array.
Index n = 0;
- for (auto iter = node.beginValueAll(); iter; ++iter) {
+ for (auto iter = childMask.beginOff(); iter; ++iter) {
node.setValueOnlyUnsafe(iter.pos(), static_cast(values[n++]));
}
OPENVDB_ASSERT(n == numValues);
@@ -207,11 +213,12 @@ struct ReadTopologyOp
}
// Read in all child nodes and insert them into the table at their proper locations.
+ // Register the child before recursing so that node's destructor frees it on a throw.
for (auto iter = childMask.beginOn(); iter; ++iter) {
Coord origin = node.offsetToGlobalCoord(iter.pos());
auto* child = new ChildT(PartialCreate(), origin, background);
- (*this)(*child);
node.setChildUnsafe(iter.pos(), child);
+ (*this)(*child);
}
}
@@ -225,6 +232,7 @@ struct ReadTopologyOp
std::istream& is;
bool saveFloatAsHalf;
ValueT background;
+ StorageValueT storageBackground;
io::ReadDiagnostics& diagnostics;
std::string gridName;
}; // struct ReadTopologyOp
@@ -273,9 +281,12 @@ void setTilesToBackground(TreeT& tree)
nodeManager.foreachTopDown(op);
}
-// Free-standing function for read case (supports type conversion via StorageGridT)
+// Free-standing function for read case (supports type conversion via StorageGridT).
+// codecData receives the storage-typed background value so readBuffers() callers
+// can pass it explicitly to readCompressedValues(), avoiding the stream background ptr.
template
-void topologyCodecReadTopology(GridBase& gridBase, std::istream& is, const io::ReadOptions& options, io::ReadDiagnostics& diagnostics)
+void topologyCodecReadTopology(GridBase& gridBase, std::istream& is, const io::ReadOptions& options,
+ io::ReadDiagnostics& diagnostics, TopologyCodecData& codecData)
{
io::checkFormatVersion(is);
@@ -285,8 +296,33 @@ void topologyCodecReadTopology(GridBase& gridBase, std::istream& is, const io::R
internal::ReadTopologyOp readTopologyOp(is, grid.saveFloatAsHalf(), diagnostics, grid.getName());
readTopologyOp(grid.tree().root());
+ // Restore the (value-typed) background on the root. ReadTopologyOp only reads
+ // the on-disk background into a local; without this the grid would retain the
+ // default background from GridT::create(). Pass updateChildNodes=false so the
+ // already-populated child nodes are left untouched.
+ grid.tree().root().setBackground(readTopologyOp.background, /*updateChildNodes=*/false);
+
+ // Copy storageBackground out of the stack-local ReadTopologyOp into codecData
+ // so it stays alive until readBuffers() completes. readBuffers() passes it
+ // explicitly to readCompressedValues(), so the stream background ptr is never
+ // used and setGridBackgroundValuePtr() is not needed in the codec path.
+ codecData.storageBackground = readTopologyOp.storageBackground;
+
if (options.readMode == io::ReadMode::TopologyOnly) {
internal::setTilesToBackground(grid.tree());
+ // allocate leaf buffers in parallel and fill with the background value;
+ // ReadTopologyOp uses PartialCreate which leaves buffers unallocated.
+ const auto background = grid.tree().root().background();
+ tree::LeafManager leafManager(grid.tree());
+ leafManager.foreach([&background](auto& leaf, size_t) {
+ using LeafType = std::decay_t;
+ if constexpr (!std::is_same_v) {
+ if (leaf.buffer().empty()) {
+ leaf.buffer().allocate();
+ leaf.buffer().fill(background);
+ }
+ }
+ });
return;
}
}
@@ -307,13 +343,14 @@ void topologyCodecWriteTopology(const GridBase& gridBase, std::ostream& os)
template
struct TopologyCodec : public io::Codec
{
+ using StorageValueT = typename StorageGridT::TreeType::ValueType;
using Ptr = std::unique_ptr>;
~TopologyCodec() noexcept = default;
io::CodecData::Ptr createData() override
{
- auto data = std::make_unique();
+ auto data = std::make_unique>();
data->grid = GridT::create();
return data;
}
@@ -321,15 +358,36 @@ struct TopologyCodec : public io::Codec
void readTopology(std::istream& is, io::CodecData& data, const io::ReadOptions& options,
io::ReadDiagnostics& diagnostics) final
{
- internal::topologyCodecReadTopology(*data.grid, is, options, diagnostics);
+ // Warn when a conversion readMode was requested but this codec is a
+ // non-conversion instance (GridT == StorageGridT), meaning no conversion
+ // codec was registered for this grid type and we are falling back to the
+ // original type.
+ if constexpr (std::is_same_v) {
+ if (options.readMode == io::ReadMode::Half ||
+ options.readMode == io::ReadMode::Bool ||
+ options.readMode == io::ReadMode::Mask)
+ {
+ const std::string modeStr =
+ options.readMode == io::ReadMode::Half ? "Half" :
+ options.readMode == io::ReadMode::Bool ? "Bool" : "Mask";
+ diagnostics.addWarning(data.grid->getName(),
+ "ReadMode::" + modeStr + " conversion is not supported for grid type '"
+ + GridT::gridType() + "'; reading as original type");
+ }
+ }
+ auto& topoData = static_cast&>(data);
+ internal::topologyCodecReadTopology(*data.grid, is, options, diagnostics, topoData);
}
void writeTopology(std::ostream& os, const GridBase& gridBase, const io::WriteOptions&) final
{
- // disable implementation when read only
- if constexpr (Mode == io::CodecMode::ReadOnly) return;
-
- internal::topologyCodecWriteTopology(gridBase, os);
+ // Disable implementation when read only. The body must live inside the
+ // negated if constexpr branch so it is not instantiated for read-only
+ // codecs; a bare `if constexpr (...) return;` still instantiates what
+ // follows.
+ if constexpr (Mode != io::CodecMode::ReadOnly) {
+ internal::topologyCodecWriteTopology(gridBase, os);
+ }
}
}; // struct TopologyCodec
diff --git a/openvdb/openvdb/codecs/ValueMaskCodec.h b/openvdb/openvdb/codecs/ValueMaskCodec.h
index 27f6289cb9..3d2b8d7bd0 100644
--- a/openvdb/openvdb/codecs/ValueMaskCodec.h
+++ b/openvdb/openvdb/codecs/ValueMaskCodec.h
@@ -102,7 +102,7 @@ struct ValueMaskCodec final: public TopologyCodec
static inline std::string name() { return GridT::gridType(); }
- void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
+ void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
{
GridT& grid = static_cast(*data.grid);
diff --git a/openvdb/openvdb/codecs/impl/ScalarLeafCodec.h b/openvdb/openvdb/codecs/impl/ScalarLeafCodec.h
index ad9e088604..2288f8a5a2 100644
--- a/openvdb/openvdb/codecs/impl/ScalarLeafCodec.h
+++ b/openvdb/openvdb/codecs/impl/ScalarLeafCodec.h
@@ -13,7 +13,8 @@ namespace codecs {
namespace internal {
template
-void writeScalarLeafBuffers(const LeafT& leaf, std::ostream& os, bool saveFloatAsHalf)
+void writeScalarLeafBuffers(const LeafT& leaf, std::ostream& os, bool saveFloatAsHalf,
+ const typename LeafT::ValueType* background = nullptr)
{
using NodeMaskT = typename LeafT::NodeMaskType;
@@ -23,14 +24,15 @@ void writeScalarLeafBuffers(const LeafT& leaf, std::ostream& os, bool saveFloatA
leaf.buffer().data(); // load values
io::writeCompressedValues(os, leaf.buffer().data(), LeafT::SIZE,
- leaf.getValueMask(), /*childMask=*/NodeMaskT(), saveFloatAsHalf);
+ leaf.getValueMask(), /*childMask=*/NodeMaskT(), saveFloatAsHalf, background);
}
template
void readScalarLeafBuffers(LeafT& leaf, std::istream& is, bool saveFloatAsHalf,
const typename LeafT::ValueType& background, bool skip = false,
- const math::CoordBBox* clipBBox = nullptr)
+ const math::CoordBBox* clipBBox = nullptr,
+ const typename StorageLeafT::ValueType* storageBackground = nullptr)
{
using ValueT = typename LeafT::ValueType;
using NodeMaskT = typename LeafT::NodeMaskType;
@@ -65,10 +67,10 @@ void readScalarLeafBuffers(LeafT& leaf, std::istream& is, bool saveFloatAsHalf,
if (skip) {
if (seekable) {
- io::readCompressedValues(is, nullptr, SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, nullptr, SIZE, valueMask, saveFloatAsHalf, storageBackground);
} else {
StorageBufferT storageTemp;
- io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
}
// Clear the value mask so that the skipped leaf has no active
// voxels. Without this, the leaf retains its on-disk active
@@ -81,18 +83,18 @@ void readScalarLeafBuffers(LeafT& leaf, std::istream& is, bool saveFloatAsHalf,
// ValueMask leaf: value == active state, already captured in the value mask above.
// Seek/consume past the storage buffer without populating any separate leaf buffer.
if (seekable) {
- io::readCompressedValues(is, nullptr, SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, nullptr, SIZE, valueMask, saveFloatAsHalf, storageBackground);
} else {
StorageBufferT storageTemp;
- io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
}
} else if constexpr (std::is_same_v) {
// Bool leaf: must read storage values regardless of seekability, then convert.
if constexpr (std::is_same_v) {
- io::readCompressedValues(is, leaf.buffer().data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, leaf.buffer().data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
} else {
StorageBufferT storageTemp;
- io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
for (Index i = 0; i < SIZE; ++i) {
leaf.buffer().setValue(i, static_cast(storageTemp.getValue(i)));
}
@@ -100,10 +102,10 @@ void readScalarLeafBuffers(LeafT& leaf, std::istream& is, bool saveFloatAsHalf,
} else {
leaf.buffer().allocate();
if constexpr (std::is_same_v) {
- io::readCompressedValues(is, leaf.buffer().data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, leaf.buffer().data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
} else {
StorageBufferT storageTemp;
- io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
+ io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf, storageBackground);
for (Index i = 0; i < SIZE; ++i) {
leaf.buffer().setValue(i, static_cast(storageTemp.getValue(i)));
}
diff --git a/openvdb/openvdb/io/Archive.cc b/openvdb/openvdb/io/Archive.cc
index c2cf6869a8..d1d2451943 100644
--- a/openvdb/openvdb/io/Archive.cc
+++ b/openvdb/openvdb/io/Archive.cc
@@ -182,6 +182,7 @@ struct StreamMetadata::Impl
bool mDelayedLoadMeta = false;
uint64_t mLeaf = 0;
uint32_t mTest = 0; // for testing only
+ bool mAllocateLeafBuffers = false;
}; // struct StreamMetadata
@@ -243,6 +244,7 @@ bool StreamMetadata::writeGridStats() const { return mImpl->mWriteGr
bool StreamMetadata::seekable() const { return mImpl->mSeekable; }
bool StreamMetadata::delayedLoadMeta() const { return mImpl->mDelayedLoadMeta; }
bool StreamMetadata::countingPasses() const { return mImpl->mCountingPasses; }
+bool StreamMetadata::allocateLeafBuffers() const { return mImpl->mAllocateLeafBuffers; }
uint32_t StreamMetadata::pass() const { return mImpl->mPass; }
MetaMap& StreamMetadata::gridMetadata() { return mImpl->mGridMetadata; }
const MetaMap& StreamMetadata::gridMetadata() const { return mImpl->mGridMetadata; }
@@ -260,6 +262,7 @@ void StreamMetadata::setHalfFloat(bool b) { mImpl->mHalfFloat = b;
void StreamMetadata::setWriteGridStats(bool b) { mImpl->mWriteGridStats = b; }
void StreamMetadata::setSeekable(bool b) { mImpl->mSeekable = b; }
void StreamMetadata::setCountingPasses(bool b) { mImpl->mCountingPasses = b; }
+void StreamMetadata::setAllocateLeafBuffers(bool b) { mImpl->mAllocateLeafBuffers = b; }
void StreamMetadata::setPass(uint32_t i) { mImpl->mPass = i; }
void StreamMetadata::__setTest(uint32_t t) { mImpl->mTest = t; }
@@ -900,14 +903,15 @@ Archive::findCodec(const std::string& gridType, const io::ReadOptions& options)
// if readMode is Half, then search for a codec that converts
// from the storage grid type to the grid type
if (options.readMode == ReadMode::Half) {
- return io::CodecRegistry::get(gridType + "_to_half");
+ if (auto* codec = io::CodecRegistry::get(gridType + "_to_half")) return codec;
} else if (options.readMode == ReadMode::Bool) {
- return io::CodecRegistry::get(gridType + "_to_bool");
+ if (auto* codec = io::CodecRegistry::get(gridType + "_to_bool")) return codec;
} else if (options.readMode == ReadMode::Mask) {
- return io::CodecRegistry::get(gridType + "_to_mask");
+ if (auto* codec = io::CodecRegistry::get(gridType + "_to_mask")) return codec;
}
- // Determine the I/O codec to use to read this grid
+ // Determine the I/O codec to use to read this grid (also the fallback when
+ // no conversion codec is registered for a Half/Bool/Mask readMode).
return io::CodecRegistry::get(gridType);
}
@@ -1010,16 +1014,40 @@ Archive::readGrid(const GridDescriptor& gd, std::istream& is, const io::ReadOpti
io::setGridClass(is, gridClass);
grid->readTransform(is);
- if (readOptions.readMode != io::ReadMode::TopologyOnly && !gd.isInstance()) {
+ const bool readTopology = readOptions.readMode != io::ReadMode::MetadataOnly && !gd.isInstance();
+ const bool readBuffers = readTopology && readOptions.readMode != io::ReadMode::TopologyOnly;
+ if (readTopology) {
// read topology
if (codec) {
codec->readTopology(is, *codecData, readOptions, diagnostics);
} else {
- grid->readTopology(is);
+ io::StreamMetadata::Ptr allocateLeafBuffersMeta;
+ if (readOptions.readMode == io::ReadMode::TopologyOnly) {
+ // Signal Grid::readTopology to allocate leaf buffers and
+ // fill them with the background value.
+ allocateLeafBuffersMeta = io::getStreamMetadataPtr(is);
+ if (allocateLeafBuffersMeta) {
+ allocateLeafBuffersMeta->setAllocateLeafBuffers(true);
+ }
+ }
+ try {
+ grid->readTopology(is);
+ } catch (...) {
+ // Grid::readTopology() clears the flag on success, but if
+ // it throws the flag must not leak into subsequent grid reads.
+ if (allocateLeafBuffersMeta) {
+ allocateLeafBuffersMeta->setAllocateLeafBuffers(false);
+ }
+ throw;
+ }
}
+ }
+ if (readBuffers) {
// read buffers
if (codec) {
- codec->readBuffers(is, *codecData, readOptions, diagnostics);
+ OPENVDB_ASSERT(gd.getEndPos() >= gd.getGridPos());
+ const Index64 size = static_cast(gd.getEndPos() - gd.getGridPos());
+ codec->readBuffers(is, size, *codecData, readOptions, diagnostics);
} else {
const auto& worldBBox = readOptions.clipBBox;
const bool clip = worldBBox.isSorted();
diff --git a/openvdb/openvdb/io/Archive.h b/openvdb/openvdb/io/Archive.h
index e0414f166a..5d15b9cdc2 100644
--- a/openvdb/openvdb/io/Archive.h
+++ b/openvdb/openvdb/io/Archive.h
@@ -162,12 +162,14 @@ class OPENVDB_API Archive
/// Write the given grid descriptor and grid to an output stream
/// and update the GridDescriptor offsets.
/// @param seekable if true, the output stream supports seek operations
+ /// @param writeOptions options controlling how grid data is written
void writeGrid(GridDescriptor&, GridBase::ConstPtr, std::ostream&, bool seekable,
const io::WriteOptions& writeOptions = io::WriteOptions{}) const;
/// Write the given grid descriptor and grid metadata to an output stream
/// and update the GridDescriptor offsets, but don't write the grid's tree,
/// since it is shared with another grid.
/// @param seekable if true, the output stream supports seek operations
+ /// @param writeOptions options controlling how grid data is written
void writeGridInstance(GridDescriptor&, GridBase::ConstPtr,
std::ostream&, bool seekable, const io::WriteOptions& writeOptions = io::WriteOptions{}) const;
diff --git a/openvdb/openvdb/io/Codec.cc b/openvdb/openvdb/io/Codec.cc
index 4873f779aa..e2a3213814 100644
--- a/openvdb/openvdb/io/Codec.cc
+++ b/openvdb/openvdb/io/Codec.cc
@@ -55,6 +55,7 @@ struct RegisterConvertCodec {
void initialize()
{
+ CodecRegistry::clear();
NumericGridTypes::foreach();
Vec3GridTypes::foreach();
diff --git a/openvdb/openvdb/io/Codec.h b/openvdb/openvdb/io/Codec.h
index 4fbf3412d1..70eea1c468 100644
--- a/openvdb/openvdb/io/Codec.h
+++ b/openvdb/openvdb/io/Codec.h
@@ -79,10 +79,15 @@ enum class ReadMode {
Mask,
/// Deserialize topology only; value buffers are skipped. The resulting
/// grid has a valid tree structure (active/inactive state, node
- /// hierarchy) but leaf buffer data is left at its default (background)
- /// value. Useful when only the active-voxel mask is needed and
- /// avoiding the cost of reading large value buffers is desirable.
- TopologyOnly
+ /// hierarchy) and all leaf buffers are allocated and filled with the
+ /// grid's background value. Useful when only the active-voxel mask is
+ /// needed and avoiding the cost of reading large value buffers is
+ /// desirable.
+ TopologyOnly,
+ /// Deserialize grid metadata and transform only; no topology, no value
+ /// buffers. The codec is still used to construct the correct grid type,
+ /// but its @c readTopology()/@c readBuffers() are not called.
+ MetadataOnly
};
/// @brief Base class for per-grid-type, codec-specific read options.
@@ -144,6 +149,8 @@ struct OPENVDB_API ReadTypedOptions
/// in-place type conversion as data is read.
/// @c ReadMode::TopologyOnly skips value buffers entirely, which can be
/// significantly faster when only the active-voxel mask is needed.
+/// @c ReadMode::MetadataOnly skips both topology and value buffers,
+/// reading only grid metadata and transform.
///
/// @par Per-type options
/// @c typeData allows callers to attach codec-specific configuration for
@@ -402,8 +409,9 @@ struct OPENVDB_API Codec
/// that mode internally for safety. If @c options.clipBBox is non-empty,
/// restrict the loaded data to the region that intersects it; if the codec
/// cannot honour clipping natively, fall back to a post-process and record
- /// a warning via @a diagnostics.
- virtual void readBuffers(std::istream& /*is*/, CodecData& /*data*/,
+ /// a warning via @a diagnostics. The @c size argument is the number of bytes
+ /// occupied by the entire readBuffers data section.
+ virtual void readBuffers(std::istream& /*is*/, Index64 /*size*/, CodecData& /*data*/,
const ReadOptions& /*options*/, ReadDiagnostics& /*diagnostics*/) { }
/// @brief Serialize the grid topology (tree structure and active-voxel
diff --git a/openvdb/openvdb/io/Compression.h b/openvdb/openvdb/io/Compression.h
index ae572c4105..a81b1ef1e5 100644
--- a/openvdb/openvdb/io/Compression.h
+++ b/openvdb/openvdb/io/Compression.h
@@ -448,10 +448,11 @@ struct HalfWriter*IsReal=*/true, double> {
/// which positions in the buffer correspond to active values
/// @param fromHalf if true, read 16-bit half floats from the input stream
/// and convert them to full floats
+/// @param background optional background value used when mask compressed
template
inline void
readCompressedValues(std::istream& is, ValueT* destBuf, Index destCount,
- const MaskT& valueMask, bool fromHalf)
+ const MaskT& valueMask, bool fromHalf, const ValueT* background = nullptr)
{
checkFormatVersion(is);
@@ -475,13 +476,15 @@ readCompressedValues(std::istream& is, ValueT* destBuf, Index destCount,
}
}
- ValueT background = zeroVal();
- if (const void* bgPtr = getGridBackgroundValuePtr(is)) {
- background = *static_cast(bgPtr);
+ ValueT bgValue = zeroVal();
+ if (background) {
+ bgValue = *background;
+ } else if (const void* bgPtr = getGridBackgroundValuePtr(is)) {
+ bgValue = *static_cast(bgPtr);
}
- ValueT inactiveVal1 = background;
+ ValueT inactiveVal1 = bgValue;
ValueT inactiveVal0 =
- ((metadata == NO_MASK_OR_INACTIVE_VALS) ? background : math::negative(background));
+ ((metadata == NO_MASK_OR_INACTIVE_VALS) ? bgValue : math::negative(bgValue));
if (metadata == NO_MASK_AND_ONE_INACTIVE_VAL ||
metadata == MASK_AND_ONE_INACTIVE_VAL ||
@@ -617,10 +620,12 @@ writeCompressedValuesSize(ValueT* srcBuf, Index srcCount,
/// @param childMask a bitmask (typically, a node's child mask) indicating
/// which positions in the buffer correspond to child node pointers
/// @param toHalf if true, convert floating-point values to 16-bit half floats
+/// @param background optional background value used when mask compressed
template
inline void
writeCompressedValues(std::ostream& os, const ValueT* srcBuf, Index srcCount,
- const MaskT& valueMask, const MaskT& childMask, bool toHalf)
+ const MaskT& valueMask, const MaskT& childMask, bool toHalf,
+ const ValueT* background = nullptr)
{
// Get the stream's compression settings.
const uint32_t compress = getDataCompression(os);
@@ -642,12 +647,14 @@ writeCompressedValues(std::ostream& os, const ValueT* srcBuf, Index srcCount,
// an inside/outside bitmask.
const ValueT zero = zeroVal();
- ValueT background = zero;
- if (const void* bgPtr = getGridBackgroundValuePtr(os)) {
- background = *static_cast(bgPtr);
+ ValueT bgValue = zero;
+ if (background) {
+ bgValue = *background;
+ } else if (const void* bgPtr = getGridBackgroundValuePtr(os)) {
+ bgValue = *static_cast(bgPtr);
}
- MaskCompress maskCompressData(valueMask, childMask, srcBuf, background);
+ MaskCompress maskCompressData(valueMask, childMask, srcBuf, bgValue);
metadata = maskCompressData.metadata;
os.write(reinterpret_cast(&metadata), /*bytes=*/1);
diff --git a/openvdb/openvdb/io/File.cc b/openvdb/openvdb/io/File.cc
index 3aaa31c5b1..b783008986 100644
--- a/openvdb/openvdb/io/File.cc
+++ b/openvdb/openvdb/io/File.cc
@@ -386,7 +386,7 @@ File::readAllGridMetadata()
// Seek to the grid in the file.
gd.seekToGrid(inputStream());
io::ReadOptions readOptions;
- readOptions.readMode = io::ReadMode::TopologyOnly;
+ readOptions.readMode = io::ReadMode::MetadataOnly;
GridBase::ConstPtr grid = Archive::readGrid(gd, inputStream(), readOptions);
// Return copies of the grids, but with empty trees.
// (As of 0.98.0, at least, it would suffice to just const cast
@@ -426,7 +426,7 @@ File::readGridMetadata(const Name& name)
const GridDescriptor& gd = it->second;
gd.seekToGrid(inputStream());
io::ReadOptions readOptions;
- readOptions.readMode = io::ReadMode::TopologyOnly;
+ readOptions.readMode = io::ReadMode::MetadataOnly;
ret = Archive::readGrid(gd, inputStream(), readOptions);
}
return ret->copyGridWithNewTree();
@@ -490,11 +490,26 @@ File::readGrid(const Name& name, const io::ReadOptions& readOptions)
<< " in file " << mFilename);
}
+ // Read the parent without clipping. Archive::readGrid() converts the
+ // world-space clip region into index space using the grid's own
+ // transform, but an instance has its own transform that may differ
+ // from the parent's. Instead, read the full parent tree and clip the
+ // assembled instance below using the instance's transform, so that the
+ // retained region matches the requested world-space bbox.
+ io::ReadOptions parentOptions = readOptions;
+ parentOptions.clipBBox = BBoxd();
+
GridBase::Ptr parent;
OPENVDB_ASSERT(inputHasGridOffsets());
parentIt->second.seekToGrid(inputStream());
- parent = Archive::readGrid(parentIt->second, inputStream(), readOptions, mReadDiagnostics);
- if (parent) grid->setTree(parent->baseTreePtr());
+ parent = Archive::readGrid(parentIt->second, inputStream(), parentOptions, mReadDiagnostics);
+ if (parent) {
+ grid->setTree(parent->baseTreePtr());
+ const auto& clipBBox = readOptions.clipBBox;
+ if (clipBBox.isSorted()) {
+ grid->clipGrid(clipBBox);
+ }
+ }
}
return grid;
}
diff --git a/openvdb/openvdb/io/Stream.cc b/openvdb/openvdb/io/Stream.cc
index 938cc321b0..87f9f99058 100644
--- a/openvdb/openvdb/io/Stream.cc
+++ b/openvdb/openvdb/io/Stream.cc
@@ -21,7 +21,28 @@ namespace io {
Stream::Stream(std::istream& is)
+ : Stream(is, io::ReadOptions{})
{
+}
+
+
+Stream::Stream(std::istream& is, const io::ReadOptions& readOptions)
+{
+ // Read modes that stop before consuming all of a grid's data are not
+ // supported, because a stream is read sequentially - the position after
+ // a partial read is still inside the previous grid's data, so the next
+ // grid header would be read from the wrong offset.
+ // TODO: Skip over the unread bytes of each grid instead of disallowing
+ // these read modes. This is best implemented alongside the extension that
+ // adds support for byte skipping in non-seekable streams.
+ if (readOptions.readMode == io::ReadMode::MetadataOnly ||
+ readOptions.readMode == io::ReadMode::TopologyOnly) {
+ OPENVDB_THROW(ValueError, "io::ReadMode::"
+ << (readOptions.readMode == io::ReadMode::MetadataOnly
+ ? "MetadataOnly" : "TopologyOnly")
+ << " is not supported when reading from a stream");
+ }
+
if (!is) return;
// Delayed loading has been removed - always read directly from the stream
@@ -51,7 +72,7 @@ Stream::Stream(std::istream& is)
gd.readHeader(is);
gd.readStreamPos(is);
descriptors.push_back(gd);
- GridBase::Ptr grid = Archive::readGrid(gd, is, io::ReadOptions{});
+ GridBase::Ptr grid = Archive::readGrid(gd, is, readOptions);
mGrids->push_back(grid);
namedGrids[gd.uniqueName()] = grid;
}
@@ -83,6 +104,7 @@ Stream&
Stream::operator=(const Stream& other)
{
if (&other != this) {
+ Archive::operator=(other);
mMeta = other.mMeta;
mGrids = other.mGrids;
mOutputStream = other.mOutputStream;
diff --git a/openvdb/openvdb/io/Stream.h b/openvdb/openvdb/io/Stream.h
index 418a06b142..432e5eb9a9 100644
--- a/openvdb/openvdb/io/Stream.h
+++ b/openvdb/openvdb/io/Stream.h
@@ -23,7 +23,16 @@ class OPENVDB_API Stream: public Archive
public:
/// @brief Read grids from an input stream.
/// @param is The input stream to read from
- explicit Stream(std::istream&);
+ explicit Stream(std::istream& is);
+
+ /// @brief Read grids from an input stream using the given read options.
+ /// @param is The input stream to read from
+ /// @param readOptions Options controlling how grids are read (e.g. attribute
+ /// skipping for point data grids)
+ /// @throw ValueError if @a readOptions requests @c io::ReadMode::MetadataOnly
+ /// or @c io::ReadMode::TopologyOnly. These modes leave a grid's data
+ /// partially unread, which a sequentially read stream cannot skip over.
+ Stream(std::istream& is, const io::ReadOptions& readOptions);
OPENVDB_DEPRECATED_MESSAGE("Use Stream(std::istream&) instead. This method is deprecated and will be removed. Delayed loading is no longer supported.")
Stream(std::istream& is, bool /*delayLoad*/) : Stream(is) { }
diff --git a/openvdb/openvdb/io/io.h b/openvdb/openvdb/io/io.h
index 13a9134012..26e513b7f6 100644
--- a/openvdb/openvdb/io/io.h
+++ b/openvdb/openvdb/io/io.h
@@ -73,6 +73,11 @@ class OPENVDB_API StreamMetadata
bool countingPasses() const;
void setCountingPasses(bool);
+ /// @brief Return @c true if readTopology() should allocate and zero-fill
+ /// leaf buffers after loading the tree structure (topology-only read mode).
+ bool allocateLeafBuffers() const;
+ void setAllocateLeafBuffers(bool);
+
uint32_t pass() const;
void setPass(uint32_t);
diff --git a/openvdb/openvdb/points/AttributeArray.h b/openvdb/openvdb/points/AttributeArray.h
index 23ff8f8149..26f03b71cc 100644
--- a/openvdb/openvdb/points/AttributeArray.h
+++ b/openvdb/openvdb/points/AttributeArray.h
@@ -1611,7 +1611,13 @@ AttributeArray::skipPagedBuffers(compression::PagedInputStream& is)
std::istream& inputStream = is.getInputStream();
uint8_t bloscCompressed(0);
if (!mIsUniform) inputStream.read(reinterpret_cast(&bloscCompressed), sizeof(uint8_t));
- inputStream.seekg(mCompressedBytes, std::ios_base::cur);
+ auto meta = io::getStreamMetadataPtr(inputStream);
+ if (meta && meta->seekable()) {
+ inputStream.seekg(mCompressedBytes, std::ios_base::cur);
+ } else {
+ std::vector tempData(mCompressedBytes);
+ inputStream.read(tempData.data(), mCompressedBytes);
+ }
mCompressedBytes = 0;
mFlags = static_cast(mFlags & ~PARTIALREAD);
}
diff --git a/openvdb/openvdb/points/PointDataGrid.h b/openvdb/openvdb/points/PointDataGrid.h
index f186717799..35988239af 100644
--- a/openvdb/openvdb/points/PointDataGrid.h
+++ b/openvdb/openvdb/points/PointDataGrid.h
@@ -94,7 +94,7 @@ makeDescriptorUnique(PointDataTreeT& tree);
template
OPENVDB_DEPRECATED_MESSAGE("This method is deprecated and will be removed. Delayed loading is no longer supported.")
inline void
-setStreamingMode(PointDataTreeT&, bool /*on*/ = true) { }
+setStreamingMode(PointDataTreeT& tree, bool on = true) { (void)tree; (void)on; }
template
diff --git a/openvdb/openvdb/points/PointDataIO.h b/openvdb/openvdb/points/PointDataIO.h
index fddbaef6c4..110b93972b 100644
--- a/openvdb/openvdb/points/PointDataIO.h
+++ b/openvdb/openvdb/points/PointDataIO.h
@@ -21,7 +21,8 @@ namespace io
template<>
inline void
readCompressedValues( std::istream& is, PointDataIndex32* destBuf, Index destCount,
- const util::NodeMask<3>& /*valueMask*/, bool /*fromHalf*/)
+ const util::NodeMask<3>& /*valueMask*/, bool /*fromHalf*/,
+ const PointDataIndex32* /*background*/)
{
using compression::bloscDecompress;
@@ -82,7 +83,8 @@ template<>
inline void
writeCompressedValues( std::ostream& os, const PointDataIndex32* srcBuf, Index srcCount,
const util::NodeMask<3>& /*valueMask*/,
- const util::NodeMask<3>& /*childMask*/, bool /*toHalf*/)
+ const util::NodeMask<3>& /*childMask*/, bool /*toHalf*/,
+ const PointDataIndex32* /*background*/)
{
using compression::bloscCompress;
diff --git a/openvdb/openvdb/points/StreamCompression.cc b/openvdb/openvdb/points/StreamCompression.cc
index f18d016463..fb99554b70 100644
--- a/openvdb/openvdb/points/StreamCompression.cc
+++ b/openvdb/openvdb/points/StreamCompression.cc
@@ -7,6 +7,7 @@
#include
#include
#include