OpenVDB: Porting to OpenVDB 0.98.0

OpenVDB 5.1.0

Porting to OpenVDB 0.98.0

Starting in OpenVDB 0.98.0, Tree and Transform objects (and Grid objects in the context of Houdini SOPs) are passed and accessed primarily by reference rather than by shared pointer. (This is partly for performance reasons; the overhead of copying shared pointers, especially in a threaded environment, can be significant.) Furthermore, those objects now exhibit copy-on-write semantics, so that in most cases it is no longer necessary to make explicit deep copies. These changes were, for the most part, requested and implemented by Side Effects.

Accessor methods like Grid::tree, Grid::transform and `GEO_PrimVDB::getGrid` that used to return shared pointers now return const references. Variants like `Grid::constTree`, which returned const shared pointers, have been removed, and new read/write accessors have been added. The latter, including `Grid::treeRW()`, `Grid::transformRW()` and `GEO_PrimVDB::getGridRW`, return non-const references, but they also ensure that ownership of the returned object is exclusive to the container (that is, they ensure that the grid has exclusive ownership of the tree or transform and that the primitive has exclusive ownership of the grid). The logic is as follows: if a grid, for example, has sole ownership of a tree, then `Grid::treeRW()` returns a non-const reference to that tree; but if the tree is shared (with other grids, perhaps), then `Grid::treeRW()` assigns the grid a new, deep copy of the tree and returns a non-const reference to the new tree.

Shared pointers to `Tree`, `Transform` and `Grid` objects can still be requested from their respective containers via `Grid::sharedTree()`, `Grid::sharedTransform()` and `GEO_PrimVDB::getSharedGrid`, but their use is now discouraged.

For Houdini SOPs, there are additional changes. First, VDB primitives are now normally processed in-place. That is, rather than extract a primitive's grid, make a deep copy of it and construct a new primitive to hold the copy, one now requests read/write access to a primitive's grid and then modifies the resulting grid. (SOPs that generate primitives or that replace grids of one type with another type can still do so using the old approach, however.)

Second, grid metadata such as a grid's class (level set, fog volume, etc.), value type (`float`, `vec3s`, etc.), background value and so on (the full list is hardcoded into `GEO_PrimVDB`) is now exposed via "intrinsic" attributes of grid primitives, rather than via primitive attributes as before. As a result, this information no longer appears in a SOP's geometry spreadsheet, nor does any extra metadata that a SOP might add to a grid during processing. The metadata is still preserved in the `Grid` objects, though.

Third, `openvdb_houdini::processTypedGrid`, which passes a shared grid pointer to a functor that also takes a shared pointer, is now deprecated in favor of `openvdb_houdini::UTvdbProcessTypedGrid`, which accepts shared pointers, raw pointers or references to grids (provided that the functor accepts an argument of the same kind). Below is a comparison of the old and new usage in the typical case of a SOP that processes input grids:

Old API (0.97.0 and earlier)

struct MyGridProcessor
{
template<typename GridT>
void operator()(typename GridT::Ptr grid) const
{
// Process the grid's tree.
(1) grid->tree()->pruneInactive();
}
};
SOP_OpenVDB_Example::cookMySop(OP_Context& context)
{
...
duplicateSource(0, context);
// Process each VDB primitive that belongs to the selected group.
for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) {
openvdb_houdini::GU_PrimVDB* vdbPrim = *it;
// Deep copy the primitive's grid if it is going to be modified.
openvdb_houdini::GridPtr grid = vdbPrim->getGrid()->deepCopyGrid();
// Otherwise, retrieve a read-only grid pointer.
//openvdb_houdini::GridCPtr grid = vdbPrim->getGrid();
// Process the grid.
MyGridProcessor proc;
(2) openvdb_houdini::processTypedGrid(grid, proc);
// Create a new VDB primitive that contains the modified grid,
// and in the output detail replace the original primitive with
// the new one.
(3) openvdb_houdini::replaceVdbPrimitive(*gdp, grid, *vdbPrim);
}
}

New API (0.98.0 and later)

struct MyGridProcessor {
template<typename GridT>
void operator()(GridT& grid) const
{
// Request write access to the grid's tree, then process the tree.
(1) grid.treeRW().pruneInactive();
}
};
SOP_OpenVDB_Example::cookMySop(OP_Context& context)
{
...
duplicateSource(0, context);
// Process each VDB primitive that belongs to the selected group.
for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) {
openvdb_houdini::GU_PrimVDB* vdbPrim = *it;
// Get write access to the grid associated with the primitive.
// If the grid is shared with other primitives, this will
// make a deep copy of it.
openvdb_houdini::Grid& grid = vdbPrim->getGridRW();
// If the grid is not going to be modified, get read-only access.
//const openvdb_houdini::Grid& grid = vdbPrim->getGrid();
// Process the grid.
MyGridProcessor proc;
(2) openvdb_houdini::UTvdbProcessTypedGrid(
(4) vdbPrim->getStorageType(), grid, proc);
}
}

Notes

1. In the old API, Grid::tree returned either a shared, non-const pointer to a tree or a shared const pointer, depending on whether the grid itself was non-const or const. In the new API, Grid::tree always returns a const reference, and `Grid::treeRW()` always returns a non-const reference.

2. `openvdb_houdini::processTypedGrid` (old API) accepts only shared pointers to grids (or shared pointers to const grids), together with functors that accept shared pointers to grids. `openvdb_houdini::UTvdbProcessTypedGrid` (new API) accepts const and non-const references, shared pointers and raw pointers, together with matching functors. That is, all of the following are valid pairs of grid and functor arguments to `UTvdbProcessTypedGrid()`:

   openvdb_houdini::Grid& grid = vdbPrim->getGridRW();
   struct MyProc { template<class GridT> operator()(GridT&) {...} };
   const openvdb_houdini::Grid& grid = vdbPrim->getGrid();
   struct MyProc { template<class GridT> operator()(const GridT&) {...} };
   openvdb_houdini::GridPtr grid = vdbPrim->getSharedGrid();
   struct MyProc { template<class GridT> operator()(typename GridT::Ptr) {...} };
   openvdb_houdini::GridCPtr grid = vdbPrim->getSharedConstGrid();
   struct MyProc { template<class GridT> operator()(typename GridT::ConstPtr) {...} };
   openvdb_houdini::Grid* grid = &vdbPrim->getGridRW();
   struct MyProc { template<class GridT> operator()(GridT*) {...} };
   const openvdb_houdini::Grid* grid = &vdbPrim->getGrid();
   struct MyProc { template<class GridT> operator()(const GridT*) {...} };

3. In the old API, input grid primitives were (usually) deleted after processing, and a new primitive was created for each output grid. `openvdb_houdini::replaceVdbPrimitive()` did both operations in one step and had the side effect of transferring the output grid's metadata to primitive attributes. In the new API, `replaceVdbPrimitive()` is rarely needed, because input grids can usually be processed in-place, and most commonly-used metadata is exposed via intrinsic attributes, which don't need to be manually updated.

4. The first argument to `openvdb_houdini::UTvdbProcessTypedGrid()` is the grid's storage type, which can be retrieved either by passing the grid to `openvdb_houdini::UTvdbGetGridType()` or by calling `GEO_PrimVDB::getStorageType()` on the primitive.