Skip to content

Commit 722093b

Browse files
authored
Merge pull request #2230 from danrbailey/fix_io_bugs
Fix I/O bugs
2 parents ee6c3dc + d85dead commit 722093b

31 files changed

Lines changed: 811 additions & 126 deletions

ci/install_windows.ps1

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,52 @@ $vcpkgPackages = @(
2121
"nanobind"
2222
)
2323

24+
$maxAttempts = 3
25+
26+
# curl's schannel backend reports an unreachable CRL/OCSP responder as a
27+
# certificate verification failure (error 60), which vcpkg then treats as
28+
# permanent. Downgrade a missing revocation answer to a warning; the rest of
29+
# certificate validation still applies.
30+
$env:VCPKG_SSL_REVOKE_BEST_EFFORT = "1"
31+
2432
# Update vcpkg
2533
vcpkg update
2634

27-
# Allow the vcpkg command to fail once so we can retry with the latest
28-
try {
29-
vcpkg install $vcpkgPackages
30-
} catch {
31-
Write-Host "vcpkg install failed, retrying with latest ports..."
32-
# Retry the installation with updated ports
33-
Push-Location $env:VCPKG_INSTALLATION_ROOT
34-
git pull
35-
Pop-Location
36-
vcpkg update
35+
$installed = $false
36+
37+
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
3738
vcpkg install $vcpkgPackages
39+
40+
# A failing native command does not raise a terminating error, so the exit
41+
# code has to be inspected explicitly rather than relying on try/catch.
42+
if ($LASTEXITCODE -eq 0) {
43+
$installed = $true
44+
break
45+
}
46+
47+
if ($attempt -eq $maxAttempts) {
48+
break
49+
}
50+
51+
# vcpkg fetches port sources directly from upstream hosts and won't retry
52+
# downloads it classifies as permanent failures, so a single flaky TLS
53+
# handshake aborts the whole install.
54+
Write-Host "vcpkg install failed (attempt $attempt of $maxAttempts), retrying..."
55+
Start-Sleep -Seconds 15
56+
57+
# Refresh the ports before the last attempt in case the failure is caused
58+
# by a stale port rather than the network.
59+
if ($attempt -eq ($maxAttempts - 1)) {
60+
Write-Host "Retrying with latest ports..."
61+
Push-Location $env:VCPKG_INSTALLATION_ROOT
62+
git pull
63+
Pop-Location
64+
vcpkg update
65+
}
66+
}
67+
68+
if (-not $installed) {
69+
throw "vcpkg install failed after $maxAttempts attempts"
3870
}
3971

4072
Write-Host "vcpkg install completed successfully"

doc/changes.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,7 +2034,7 @@ Bug fixes:
20342034
New features:
20352035
- Added @vdblink{tools::FindActiveValues,FindActiveValues}, which counts
20362036
the active values in a tree that intersect a given bounding box.
2037-
- Added @vdblink{io::DelayedLoadMetadata,DelayedLoadMetadata}, which stores
2037+
- Added @c io::DelayedLoadMetadata, which stores
20382038
mask offsets and compression sizes on write to accelerate delayed load
20392039
reading.
20402040

@@ -2933,7 +2933,7 @@ New features:
29332933
- Added a toggle to the @vdblink::tools::clip() clip@endlink tool
29342934
to invert the clipping mask.
29352935
- Custom leaf node implementations may now optimize their file layout
2936-
by inheriting from @vdblink::io::MultiPass io::MultiPass@endlink.
2936+
by inheriting from @c io::MultiPass.
29372937
Voxel data for grids with such leaf nodes will be written and read in
29382938
multiple passes, allowing blocks of related data to be stored contiguously.
29392939
<I>[Contributed&nbsp;by&nbsp;Double&nbsp;Negative]</I>

openvdb/openvdb/Grid.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "Types.h"
1010
#include "io/io.h"
1111
#include "math/Transform.h"
12+
#include "tree/LeafManager.h"
1213
#include "tree/Tree.h"
1314
#include "util/Assert.h"
1415
#include "util/logging.h"
@@ -1632,6 +1633,28 @@ inline void
16321633
Grid<TreeT>::readTopology(std::istream& is)
16331634
{
16341635
tree().readTopology(is, saveFloatAsHalf());
1636+
// When called from the legacy (non-codec) TopologyOnly path, the stream
1637+
// metadata carries a flag requesting that leaf buffers be allocated and
1638+
// filled with the background value (PartialCreate leaves them
1639+
// unallocated after readTopology).
1640+
if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(is)) {
1641+
if (meta->allocateLeafBuffers()) {
1642+
meta->setAllocateLeafBuffers(false);
1643+
if constexpr (!std::is_void_v<typename TreeT::LeafNodeType>) {
1644+
const auto background = tree().root().background();
1645+
tree::LeafManager<TreeT> leafManager(tree());
1646+
leafManager.foreach([&background](auto& leaf, size_t) {
1647+
using LeafType = std::decay_t<decltype(leaf)>;
1648+
if constexpr (!std::is_same_v<typename LeafType::ValueType, bool>) {
1649+
if (leaf.buffer().empty()) {
1650+
leaf.buffer().allocate();
1651+
leaf.buffer().fill(background);
1652+
}
1653+
}
1654+
});
1655+
}
1656+
}
1657+
}
16351658
}
16361659

16371660

openvdb/openvdb/codecs/BoolCodec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct BoolCodec final: public TopologyCodec<GridT>
110110

111111
static inline std::string name() { return GridT::gridType(); }
112112

113-
void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
113+
void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
114114
{
115115
GridT& grid = static_cast<GridT&>(*data.grid);
116116

openvdb/openvdb/codecs/PointDataCodec.h

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,14 @@ template <typename LeafT>
111111
inline void readPointDataVoxelData(const std::vector<LeafT*>& leaves,
112112
std::istream& is, bool saveFloatAsHalf,
113113
const typename LeafT::ValueType& background,
114-
[[maybe_unused]] const std::unordered_map<Coord, uint16_t>& voxelBufferSizes)
114+
[[maybe_unused]] const std::unordered_map<Coord, uint16_t>& voxelBufferSizes,
115+
const typename LeafT::ValueType* storageBackground = nullptr)
115116
{
116117
using BaseLeaf = typename LeafT::BaseLeaf;
117118
for (auto* leaf : leaves) {
118119
OPENVDB_ASSERT(voxelBufferSizes.find(leaf->origin()) != voxelBufferSizes.end());
119120
BaseLeaf& baseLeaf = static_cast<BaseLeaf&>(*leaf);
120-
readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background);
121+
readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr, storageBackground);
121122
}
122123
}
123124

@@ -310,7 +311,7 @@ struct PointDataCodec final: public TopologyCodec<GridT>
310311

311312
static inline std::string name() { return GridT::gridType(); }
312313

313-
void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
314+
void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
314315
{
315316
OPENVDB_ASSERT(dynamic_cast<GridT*>(data.grid.get()));
316317

@@ -334,7 +335,11 @@ struct PointDataCodec final: public TopologyCodec<GridT>
334335

335336
uint16_t numPasses = 1;
336337
is.read(reinterpret_cast<char*>(&numPasses), sizeof(uint16_t));
337-
const Index attributes = (numPasses - 4) / 2;
338+
// The pass layout is: voxel sizes (1) + descriptors (1) + attribute
339+
// sizes (N) + voxel data (1) + attribute data (N) = 2N + 4 passes.
340+
// A leafless grid stores numPasses == 0, and malformed files may store
341+
// numPasses < 4; guard against unsigned underflow in either case.
342+
const Index attributes = numPasses >= 4 ? Index(numPasses - 4) / 2 : 0;
338343

339344
using LeafT = typename GridT::TreeType::LeafNodeType;
340345
std::vector<LeafT*> leaves;
@@ -351,7 +356,17 @@ struct PointDataCodec final: public TopologyCodec<GridT>
351356
// An empty pointAttributeNames means no filtering (read all attributes).
352357
std::set<Index> skipIndices;
353358
if (!pointAttributeNames.empty() && !leaves.empty()) {
354-
const auto& nameMap = leaves[0]->attributeSet().descriptor().map();
359+
// Attribute filtering requires homogeneous descriptors across all
360+
// leaves because skip decisions are made per-index across all leaves.
361+
const auto* firstDesc = &leaves[0]->attributeSet().descriptor();
362+
for (size_t i = 1; i < leaves.size(); ++i) {
363+
if (&leaves[i]->attributeSet().descriptor() != firstDesc) {
364+
OPENVDB_THROW(IoError,
365+
"Attribute filtering is not supported for PointDataGrids "
366+
"with heterogeneous descriptors");
367+
}
368+
}
369+
const auto& nameMap = firstDesc->map();
355370
const std::set<std::string> wantedNames(
356371
pointAttributeNames.begin(),
357372
pointAttributeNames.end());
@@ -373,8 +388,10 @@ struct PointDataCodec final: public TopologyCodec<GridT>
373388
}
374389

375390
// Pass N+2: read voxel data
391+
using ValueT = typename GridT::TreeType::ValueType;
392+
auto& topoData = static_cast<TopologyCodecData<ValueT>&>(data);
376393
internal::readPointDataVoxelData(leaves, is, saveFloatAsHalf,
377-
tree.background(), voxelBufferSizes);
394+
tree.background(), voxelBufferSizes, &topoData.storageBackground);
378395

379396
// Passes N+3..2N+2: read attribute data buffers
380397
for (Index i = 0; i < attributes; ++i) {
@@ -419,7 +436,9 @@ struct PointDataCodec final: public TopologyCodec<GridT>
419436
static_cast<uint16_t>(internal::countPointDataPasses(leaves));
420437
os.write(reinterpret_cast<const char*>(&numPasses), sizeof(uint16_t));
421438

422-
const Index attributes = (numPasses - 4) / 2;
439+
// See readBuffers(): a leafless grid yields numPasses == 0, so guard
440+
// against unsigned underflow rather than computing (numPasses - 4) / 2.
441+
const Index attributes = numPasses >= 4 ? Index(numPasses - 4) / 2 : 0;
423442

424443
// Pass 0: write voxel data sizes + descriptor tracking
425444
bool matching = true;

openvdb/openvdb/codecs/PointIndexCodec.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ struct ReadPointIndexBuffersOp
2626
using ValueT = typename TreeT::ValueType;
2727

2828
ReadPointIndexBuffersOp(std::istream& _is, bool _saveFloatAsHalf,
29-
const ValueT& _background)
29+
const ValueT& _background, const ValueT* _storageBackground = nullptr)
3030
: is(_is)
3131
, saveFloatAsHalf(_saveFloatAsHalf)
32-
, background(_background) { }
32+
, background(_background)
33+
, storageBackground(_storageBackground) { }
3334

3435
template <typename NodeT>
3536
void operator()(NodeT&, size_t) { }
@@ -40,7 +41,7 @@ struct ReadPointIndexBuffersOp
4041

4142
// Read the value mask and voxel data via base class
4243
BaseLeaf& baseLeaf = static_cast<BaseLeaf&>(leaf);
43-
readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr);
44+
readScalarLeafBuffers(baseLeaf, is, saveFloatAsHalf, background, /*skip=*/false, /*clipBBox=*/nullptr, storageBackground);
4445

4546
// Read the number of indices.
4647
Index64 numIndices = Index64(0);
@@ -63,6 +64,7 @@ struct ReadPointIndexBuffersOp
6364
std::istream& is;
6465
const bool saveFloatAsHalf;
6566
const ValueT& background;
67+
const ValueT* storageBackground = nullptr;
6668
}; // struct ReadPointIndexBuffersOp
6769

6870
template <typename GridT>
@@ -114,7 +116,7 @@ struct PointIndexCodec final: public TopologyCodec<GridT>
114116

115117
static inline std::string name() { return GridT::gridType(); }
116118

117-
void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics& diagnostics) final
119+
void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics& diagnostics) final
118120
{
119121
OPENVDB_ASSERT(dynamic_cast<GridT*>(data.grid.get()));
120122

@@ -135,7 +137,9 @@ struct PointIndexCodec final: public TopologyCodec<GridT>
135137
diagnostics.addWarning(grid.getName(), "bounding box clipping is not supported for PointIndexGrids");
136138
}
137139

138-
internal::ReadPointIndexBuffersOp<GridT> readBuffersOp(is, saveFloatAsHalf, tree.background());
140+
using ValueT = typename GridT::TreeType::ValueType;
141+
auto& topoData = static_cast<TopologyCodecData<ValueT>&>(data);
142+
internal::ReadPointIndexBuffersOp<GridT> readBuffersOp(is, saveFloatAsHalf, tree.background(), &topoData.storageBackground);
139143
tools::visitNodesDepthFirst(grid.tree(), readBuffersOp, /*idx=*/0, /*topDown=*/false);
140144
}
141145

openvdb/openvdb/codecs/ScalarCodec.h

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,26 @@ template <typename TreeT>
2323
struct WriteBuffersOp
2424
{
2525
using LeafT = typename TreeT::LeafNodeType;
26+
using ValueT = typename TreeT::ValueType;
2627

27-
WriteBuffersOp(std::ostream& _os, bool _saveFloatAsHalf)
28+
WriteBuffersOp(std::ostream& _os, bool _saveFloatAsHalf, const ValueT& _background)
2829
: os(_os)
29-
, saveFloatAsHalf(_saveFloatAsHalf) { }
30+
, saveFloatAsHalf(_saveFloatAsHalf)
31+
, background(_background) { }
3032

3133
template <typename NodeT>
3234
void operator()(const NodeT&, size_t) { }
3335

3436
void operator()(const LeafT& leaf, size_t)
3537
{
36-
writeScalarLeafBuffers(leaf, os, saveFloatAsHalf);
38+
// Pass the background explicitly so leaf compression does not depend on
39+
// the stream's background pointer (which the codec path no longer sets).
40+
writeScalarLeafBuffers(leaf, os, saveFloatAsHalf, &background);
3741
}
3842

3943
std::ostream& os;
4044
const bool saveFloatAsHalf;
45+
const ValueT& background;
4146
}; // struct WriteBuffersOp
4247

4348

@@ -48,13 +53,15 @@ struct ReadBuffersOp
4853
using LeafT = typename TreeT::LeafNodeType;
4954
using ValueT = typename TreeT::ValueType;
5055
using StorageLeafT = typename StorageTreeT::LeafNodeType;
56+
using StorageValueT = typename StorageTreeT::ValueType;
5157

5258
ReadBuffersOp(std::istream& _is, bool _saveFloatAsHalf, const ValueT& _background,
53-
const CoordBBox* _clipBBox)
59+
const CoordBBox* _clipBBox, const StorageValueT* _storageBackground = nullptr)
5460
: is(_is)
5561
, saveFloatAsHalf(_saveFloatAsHalf)
5662
, background(_background)
57-
, clipBBox(_clipBBox) { }
63+
, clipBBox(_clipBBox)
64+
, storageBackground(_storageBackground) { }
5865

5966
void operator()(RootT& root, size_t)
6067
{
@@ -73,20 +80,22 @@ struct ReadBuffersOp
7380

7481
void operator()(LeafT& leaf, size_t)
7582
{
76-
readScalarLeafBuffers<LeafT, StorageLeafT>(leaf, is, saveFloatAsHalf, background, /*skip=*/false, clipBBox);
83+
readScalarLeafBuffers<LeafT, StorageLeafT>(leaf, is, saveFloatAsHalf, background, /*skip=*/false, clipBBox, storageBackground);
7784
}
7885

7986
std::istream& is;
8087
const bool saveFloatAsHalf;
8188
const ValueT& background;
8289
const CoordBBox* clipBBox = nullptr;
90+
const StorageValueT* storageBackground = nullptr;
8391
}; // struct ReadBuffersOp
8492

8593

8694
// Free-standing function for both standard and conversion codec cases
8795
// Uses StorageGridT = GridT by default, but allows different storage type for conversions
8896
template<typename GridT, typename StorageGridT = GridT>
89-
void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions& options)
97+
void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions& options,
98+
const typename StorageGridT::TreeType::ValueType* storageBackground)
9099
{
91100
if (grid.hasMultiPassIO()) {
92101
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
109118

110119
// Works for both standard (TreeT == StorageTreeT) and conversion cases
111120
ReadBuffersOp<TreeT, StorageTreeT> readBuffersOp(is, saveFloatAsHalf, tree.background(),
112-
clipIndexBBox.get());
121+
clipIndexBBox.get(), storageBackground);
113122
tools::visitNodesDepthFirst(grid.tree(), readBuffersOp, /*idx=*/0, /*topDown=*/false);
114123
}
115124

@@ -123,7 +132,7 @@ void scalarCodecWriteBuffers(const GridT& grid, std::ostream& os)
123132
OPENVDB_THROW(IoError, "Multi-pass IO is not supported in ScalarCodec");
124133
}
125134

126-
WriteBuffersOp<TreeType> writeBuffersOp(os, grid.saveFloatAsHalf());
135+
WriteBuffersOp<TreeType> writeBuffersOp(os, grid.saveFloatAsHalf(), grid.tree().background());
127136
tools::visitNodesDepthFirst(grid.tree(), writeBuffersOp);
128137
}
129138

@@ -150,18 +159,25 @@ struct ScalarCodec final: public TopologyCodec<GridT, StorageGridT, Mode>
150159
}
151160
}
152161

153-
void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
162+
void readBuffers(std::istream& is, Index64 /*size*/, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
154163
{
164+
using StorageValueT = typename StorageGridT::TreeType::ValueType;
155165
GridT& grid = static_cast<GridT&>(*data.grid);
156-
internal::scalarCodecReadBuffers<GridT, StorageGridT>(grid, is, options);
166+
auto& topoData = static_cast<TopologyCodecData<StorageValueT>&>(data);
167+
internal::scalarCodecReadBuffers<GridT, StorageGridT>(grid, is, options, &topoData.storageBackground);
157168
}
158169

159170
void writeBuffers(std::ostream& os, const GridBase& gridBase, const io::WriteOptions&) final
160171
{
161-
if constexpr (Mode == io::CodecMode::ReadOnly) return;
162-
163-
const GridT& grid = static_cast<const GridT&>(gridBase);
164-
internal::scalarCodecWriteBuffers(grid, os);
172+
// Note: the write body must live inside the negated if constexpr branch
173+
// so it is not instantiated for read-only codecs. A bare
174+
// `if constexpr (Mode == ReadOnly) return;` would still instantiate the
175+
// code that follows, which fails to compile for the scalar-to-mask/bool
176+
// convert codecs (their leaf buffers expose WordType*, not ValueType*).
177+
if constexpr (Mode != io::CodecMode::ReadOnly) {
178+
const GridT& grid = static_cast<const GridT&>(gridBase);
179+
internal::scalarCodecWriteBuffers(grid, os);
180+
}
165181
}
166182
}; // struct ScalarCodec
167183

0 commit comments

Comments
 (0)