Skip to content

Commit 906eef6

Browse files
committed
Add Scalar and ValueMask codecs
Signed-off-by: Dan Bailey <danbailey@ilm.com>
1 parent fa84a40 commit 906eef6

10 files changed

Lines changed: 477 additions & 3 deletions

File tree

openvdb/openvdb/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ set(OPENVDB_LIBRARY_INCLUDE_FILES
367367

368368
set(OPENVDB_LIBRARY_CODECS_INCLUDE_FILES
369369
codecs/BoolCodec.h
370+
codecs/ScalarCodec.h
370371
codecs/TopologyCodec.h
372+
codecs/ValueMaskCodec.h
371373
)
372374

373375
set(OPENVDB_LIBRARY_IO_INCLUDE_FILES
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright Contributors to the OpenVDB Project
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#ifndef OPENVDB_IO_CODECS_SCALARCODEC_HAS_BEEN_INCLUDED
5+
#define OPENVDB_IO_CODECS_SCALARCODEC_HAS_BEEN_INCLUDED
6+
7+
#include <openvdb/openvdb.h>
8+
#include <openvdb/tree/Tree.h>
9+
#include <openvdb/tools/Clip.h>
10+
#include <openvdb/tools/NodeVisitor.h>
11+
#include <openvdb/io/Codec.h>
12+
13+
#include "ScalarLeafCodec.h"
14+
#include "TopologyCodec.h"
15+
16+
namespace openvdb {
17+
OPENVDB_USE_VERSION_NAMESPACE
18+
namespace OPENVDB_VERSION_NAME {
19+
namespace codecs {
20+
namespace internal {
21+
22+
template <typename TreeT>
23+
struct WriteBuffersOp
24+
{
25+
using LeafT = typename TreeT::LeafNodeType;
26+
27+
WriteBuffersOp(std::ostream& _os, bool _saveFloatAsHalf)
28+
: os(_os)
29+
, saveFloatAsHalf(_saveFloatAsHalf) { }
30+
31+
template <typename NodeT>
32+
void operator()(const NodeT&, size_t) { }
33+
34+
void operator()(const LeafT& leaf, size_t)
35+
{
36+
writeScalarLeafBuffers(leaf, os, saveFloatAsHalf);
37+
}
38+
39+
std::ostream& os;
40+
const bool saveFloatAsHalf;
41+
}; // struct WriteBuffersOp
42+
43+
44+
template <typename TreeT, typename StorageTreeT>
45+
struct ReadBuffersOp
46+
{
47+
using RootT = typename TreeT::RootNodeType;
48+
using LeafT = typename TreeT::LeafNodeType;
49+
using ValueT = typename TreeT::ValueType;
50+
using StorageLeafT = typename StorageTreeT::LeafNodeType;
51+
52+
ReadBuffersOp(std::istream& _is, bool _saveFloatAsHalf, const ValueT& _background,
53+
const CoordBBox* _clipBBox)
54+
: is(_is)
55+
, saveFloatAsHalf(_saveFloatAsHalf)
56+
, background(_background)
57+
, clipBBox(_clipBBox) { }
58+
59+
void operator()(RootT& root, size_t)
60+
{
61+
if (clipBBox) {
62+
root.clip(*clipBBox);
63+
}
64+
}
65+
66+
template <typename NodeT>
67+
void operator()(NodeT& node, size_t)
68+
{
69+
if (clipBBox) {
70+
node.clip(*clipBBox, background);
71+
}
72+
}
73+
74+
void operator()(LeafT& leaf, size_t)
75+
{
76+
readScalarLeafBuffers<LeafT, StorageLeafT>(leaf, is, saveFloatAsHalf, background, /*skip=*/false, clipBBox);
77+
}
78+
79+
std::istream& is;
80+
const bool saveFloatAsHalf;
81+
const ValueT& background;
82+
const CoordBBox* clipBBox = nullptr;
83+
}; // struct ReadBuffersOp
84+
85+
86+
// Free-standing function for both standard and conversion codec cases
87+
// Uses StorageGridT = GridT by default, but allows different storage type for conversions
88+
template<typename GridT, typename StorageGridT = GridT>
89+
void scalarCodecReadBuffers(GridT& grid, std::istream& is, const io::ReadOptions& options)
90+
{
91+
if (grid.hasMultiPassIO()) {
92+
OPENVDB_THROW(IoError, "Multi-pass IO is not supported in ScalarCodec");
93+
}
94+
95+
using TreeT = typename GridT::TreeType;
96+
using StorageTreeT = typename StorageGridT::TreeType;
97+
98+
io::checkFormatVersion(is);
99+
100+
bool saveFloatAsHalf = grid.saveFloatAsHalf();
101+
102+
auto& tree = grid.tree();
103+
tree.clearAllAccessors();
104+
105+
std::unique_ptr<CoordBBox> clipIndexBBox;
106+
if (options.clipBBox.isSorted()) {
107+
clipIndexBBox = std::make_unique<CoordBBox>(grid.constTransform().worldToIndexNodeCentered(options.clipBBox));
108+
}
109+
110+
// Works for both standard (TreeT == StorageTreeT) and conversion cases
111+
ReadBuffersOp<TreeT, StorageTreeT> readBuffersOp(is, saveFloatAsHalf, tree.background(),
112+
clipIndexBBox.get());
113+
tools::visitNodesDepthFirst(grid.tree(), readBuffersOp, /*idx=*/0, /*topDown=*/false);
114+
}
115+
116+
// Free-standing function for write case (no StorageGridT needed)
117+
template<typename GridT>
118+
void scalarCodecWriteBuffers(const GridT& grid, std::ostream& os)
119+
{
120+
using TreeType = typename GridT::TreeType;
121+
122+
if (grid.hasMultiPassIO()) {
123+
OPENVDB_THROW(IoError, "Multi-pass IO is not supported in ScalarCodec");
124+
}
125+
126+
WriteBuffersOp<TreeType> writeBuffersOp(os, grid.saveFloatAsHalf());
127+
tools::visitNodesDepthFirst(grid.tree(), writeBuffersOp);
128+
}
129+
130+
} // namespace internal
131+
132+
template <typename GridT, typename StorageGridT = GridT, io::CodecMode Mode = io::CodecMode::ReadWrite>
133+
struct ScalarCodec : public TopologyCodec<GridT, StorageGridT, Mode>
134+
{
135+
static_assert(GridT::TreeType::RootNodeType::template SameConfiguration<
136+
typename StorageGridT::TreeType::RootNodeType>::value,
137+
"GridT and StorageGridT must have the same configuration");
138+
139+
using Ptr = std::unique_ptr<ScalarCodec<GridT, StorageGridT, Mode>>;
140+
141+
struct ScalarCodecData : public io::CodecData
142+
{
143+
std::vector<int> leafCoords;
144+
};
145+
146+
~ScalarCodec() noexcept = default;
147+
148+
io::CodecData::Ptr createData() final
149+
{
150+
auto data = std::make_unique<ScalarCodecData>();
151+
data->grid = GridT::create();
152+
return data;
153+
}
154+
155+
static inline std::string name()
156+
{
157+
if constexpr (std::is_same_v<GridT, StorageGridT>) {
158+
return GridT::gridType();
159+
} else {
160+
std::string buildType = typeNameAsString<typename GridT::BuildType>();
161+
return StorageGridT::gridType() + "_to_" + buildType;
162+
}
163+
}
164+
165+
void readBuffers(std::istream& is, io::CodecData& data, const io::ReadOptions& options, io::ReadDiagnostics&) final
166+
{
167+
GridT& grid = static_cast<GridT&>(*data.grid);
168+
internal::scalarCodecReadBuffers<GridT, StorageGridT>(grid, is, options);
169+
}
170+
171+
void writeBuffers(std::ostream& os, const GridBase& gridBase, const io::WriteOptions&) final
172+
{
173+
if constexpr (Mode == io::CodecMode::ReadOnly) return;
174+
175+
const GridT& grid = static_cast<const GridT&>(gridBase);
176+
internal::scalarCodecWriteBuffers(grid, os);
177+
}
178+
}; // struct ScalarCodec
179+
180+
181+
} // namespace codecs
182+
} // namespace OPENVDB_VERSION_NAME
183+
} // namespace openvdb
184+
185+
#endif // OPENVDB_IO_CODECS_SCALARCODEC_HAS_BEEN_INCLUDED
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright Contributors to the OpenVDB Project
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#ifndef OPENVDB_IO_CODECS_SCALARLEAFCODEC_HAS_BEEN_INCLUDED
5+
#define OPENVDB_IO_CODECS_SCALARLEAFCODEC_HAS_BEEN_INCLUDED
6+
7+
#include <openvdb/io/Compression.h>
8+
9+
namespace openvdb {
10+
OPENVDB_USE_VERSION_NAMESPACE
11+
namespace OPENVDB_VERSION_NAME {
12+
namespace codecs {
13+
namespace internal {
14+
15+
template <typename LeafT>
16+
void writeScalarLeafBuffers(const LeafT& leaf, std::ostream& os, bool saveFloatAsHalf)
17+
{
18+
using NodeMaskT = typename LeafT::NodeMaskType;
19+
20+
// Write out the value mask.
21+
leaf.getValueMask().save(os);
22+
23+
leaf.buffer().data(); // load values
24+
25+
io::writeCompressedValues(os, leaf.buffer().data(), LeafT::SIZE,
26+
leaf.getValueMask(), /*childMask=*/NodeMaskT(), saveFloatAsHalf);
27+
}
28+
29+
30+
template <typename LeafT, typename StorageLeafT = LeafT>
31+
void readScalarLeafBuffers(LeafT& leaf, std::istream& is, bool saveFloatAsHalf,
32+
const typename LeafT::ValueType& background, bool skip = false,
33+
const math::CoordBBox* clipBBox = nullptr)
34+
{
35+
using ValueT = typename LeafT::ValueType;
36+
using NodeMaskT = typename LeafT::NodeMaskType;
37+
using StorageBufferT = typename StorageLeafT::Buffer;
38+
using StorageValueT = typename StorageLeafT::ValueType;
39+
40+
constexpr Index SIZE = LeafT::SIZE;
41+
42+
SharedPtr<io::StreamMetadata> meta = io::getStreamMetadataPtr(is);
43+
const bool seekable = meta && meta->seekable();
44+
45+
auto& valueMask = leaf.getValueMask();
46+
47+
// Load or seek the value mask
48+
if (seekable) valueMask.seek(is);
49+
else valueMask.load(is);
50+
51+
if (skip) {
52+
if (seekable) {
53+
io::readCompressedValues<StorageValueT, NodeMaskT>(is, nullptr, SIZE, valueMask, saveFloatAsHalf);
54+
} else {
55+
StorageBufferT storageTemp;
56+
io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
57+
}
58+
// Clear the value mask so that the skipped leaf has no active
59+
// voxels. Without this, the leaf retains its on-disk active
60+
// topology even though no data was read into its buffer.
61+
valueMask.setOff();
62+
return;
63+
}
64+
65+
if constexpr (std::is_same_v<typename LeafT::BuildType, ValueMask>) {
66+
// ValueMask leaf: value == active state, already captured in the value mask above.
67+
// Seek/consume past the storage buffer without populating any separate leaf buffer.
68+
if (seekable) {
69+
io::readCompressedValues<StorageValueT, NodeMaskT>(is, nullptr, SIZE, valueMask, saveFloatAsHalf);
70+
} else {
71+
StorageBufferT storageTemp;
72+
io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
73+
}
74+
} else if constexpr (std::is_same_v<ValueT, bool>) {
75+
// Bool leaf: must read storage values regardless of seekability, then convert.
76+
StorageBufferT storageTemp;
77+
io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
78+
for (Index i = 0; i < SIZE; ++i) {
79+
leaf.buffer().setValue(i, static_cast<bool>(storageTemp.getValue(i)));
80+
}
81+
} else {
82+
leaf.buffer().allocate();
83+
if constexpr (std::is_same_v<StorageValueT, ValueT>) {
84+
io::readCompressedValues(is, leaf.buffer().data(), SIZE, valueMask, saveFloatAsHalf);
85+
} else {
86+
StorageBufferT storageTemp;
87+
io::readCompressedValues(is, storageTemp.data(), SIZE, valueMask, saveFloatAsHalf);
88+
for (Index i = 0; i < SIZE; ++i) {
89+
leaf.buffer().setValue(i, static_cast<ValueT>(storageTemp.getValue(i)));
90+
}
91+
}
92+
}
93+
94+
if (clipBBox) {
95+
leaf.clip(*clipBBox, background);
96+
}
97+
}
98+
99+
} // namespace internal
100+
} // namespace codecs
101+
} // namespace OPENVDB_VERSION_NAME
102+
} // namespace openvdb
103+
104+
#endif // OPENVDB_IO_CODECS_SCALARLEAFCODEC_HAS_BEEN_INCLUDED

openvdb/openvdb/codecs/TopologyCodec.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ void topologyCodecWriteTopology(const GridBase& gridBase, std::ostream& os)
300300

301301
} // namespace internal
302302

303-
template <typename GridT, typename StorageGridT = GridT>
303+
template <typename GridT, typename StorageGridT = GridT, io::CodecMode Mode = io::CodecMode::ReadWrite>
304304
struct TopologyCodec : public io::Codec
305305
{
306-
using Ptr = std::unique_ptr<TopologyCodec<GridT, StorageGridT>>;
306+
using Ptr = std::unique_ptr<TopologyCodec<GridT, StorageGridT, Mode>>;
307307

308308
~TopologyCodec() noexcept = default;
309309

@@ -322,6 +322,9 @@ struct TopologyCodec : public io::Codec
322322

323323
void writeTopology(std::ostream& os, const GridBase& gridBase, const io::WriteOptions&) final
324324
{
325+
// disable implementation when read only
326+
if constexpr (Mode == io::CodecMode::ReadOnly) return;
327+
325328
internal::topologyCodecWriteTopology<GridT>(gridBase, os);
326329
}
327330
}; // struct TopologyCodec

0 commit comments

Comments
 (0)