This refactors how volume grids are stored with the following new goals in mind:
* Get a **stand-alone volume grid** data structure that can be used by geometry nodes.
Previously, the `VolumeGrid` data structure was tightly coupled with the `Volume` data block.
* Support **implicit sharing of grids and trees**. Previously, it was possible to share data
when multiple `Volume` data blocks loaded grids from the same `.vdb` files but this was
not flexible enough.
* Get a safe API for **lazy-loading and unloading** of grids without requiring explicit calls
to some "load" function all the time.
* Get a safe API for **caching grids from files** that is not coupled to the `Volume` data block.
* Get a **tiered API** for different levels of `openvdb` involvement:
* No `OpenVDB`: Since `WITH_OPENVDB` is optional, it's helpful to have parts of the API that
still work in this case. This makes it possible to write high level code for volumes that does
not require `#ifdef WITH_OPENVDB` checks everywhere. This is in `BKE_volume_grid_fwd.hh`.
* Shallow `OpenVDB`: Code using this API requires `WITH_OPENVDB` checks. However, care
is taken to not include the expensive parts of `OpenVDB` and to use forward declarations as
much as possible. This is in `BKE_volume_grid.hh` and uses `openvdb_fwd.hh`.
* "Full" `OpenVDB`: This API requires more heavy `OpenVDB` includes. Fortunately, it turned
out to be not necessary for the common API. So this is only used for task specific APIs.
At the core of the new API is the `VolumeGridData` type. It's a wrapper around an
`openvdb::Grid` and adds some features on top like implicit sharing, lazy-loading and unloading.
Then there are `GVolumeGrid` and `VolumeGrid` which are containers for a volume grid.
Semantically, each `VolumeGrid` has its own independent grid, but this is cheap due to implicit
sharing. At highest level we currently have the `Volume` data-block which contains a list of
`VolumeGrid`.
```mermaid
flowchart LR
Volume --> VolumeGrid --> VolumeGridData --> openvdb::Grid
```
The loading of `.vdb` files is abstracted away behind the volume file cache API. This API makes
it easy to load and reuse entire files and individual grids from disk. It also supports caching
simplify levels for grids on disk.
An important new concept are the "tree access tokens". Whenever some code wants to work
with an openvdb tree, it has to retrieve an access token from the corresponding `VolumeGridData`.
This access token has to be kept alive for as long as the code works with the grid data. The same
token is valid for read and write access. The purpose of these access tokens is to make it possible
to detect when some code is currently working with the openvdb tree. This allows freeing it if it's
possible to reload it later on (e.g. from disk). It's possible to free a tree that is referenced by
multiple owners, but only no one is actively working with. In some sense, this is similar to the
existing `ImageUser` concept.
The most important new files to read are `BKE_volume_grid.hh` and `BKE_volume_grid_file_cache.hh`.
Most other changes are updates to existing code to use the new API.
Pull Request: https://projects.blender.org/blender/blender/pulls/116315
426 lines
14 KiB
C++
426 lines
14 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include "BKE_volume_grid_fwd.hh"
|
|
|
|
#ifdef WITH_OPENVDB
|
|
|
|
# include <functional>
|
|
# include <mutex>
|
|
# include <optional>
|
|
|
|
# include "BKE_volume_enums.hh"
|
|
# include "BKE_volume_grid_type_traits.hh"
|
|
|
|
# include "BLI_implicit_sharing_ptr.hh"
|
|
# include "BLI_string_ref.hh"
|
|
|
|
# include "openvdb_fwd.hh"
|
|
|
|
namespace blender::bke::volume_grid {
|
|
|
|
/**
|
|
* Main volume grid data structure. It wraps an OpenVDB grid and adds some features on top of it.
|
|
*
|
|
* A grid contains the following:
|
|
* - Transform: The mapping between index and object space. It also determines e.g. the voxel size.
|
|
* - Meta-data: Contains e.g. the name and grid class (fog volume or sdf) and potentially other
|
|
* data.
|
|
* - Tree: This is the heavy data that contains all the voxel values.
|
|
*
|
|
* Features:
|
|
* - Implicit sharing of the #VolumeGridData: This makes it cheap to copy e.g. a #VolumeGrid<T>,
|
|
* because it just increases the number of users. An actual copy is only done when the grid is
|
|
* modified.
|
|
* - Implicit sharing of the referenced OpenVDB tree (not grid): The tree is the heavy piece of
|
|
* data that contains all the voxel values. Multiple #VolumeGridData can reference the same tree
|
|
* with independent meta-data and transforms. The tree is only actually copied when necessary.
|
|
* - Lazy loading of the entire grid or just the tree: When constructing the #VolumeGridData it is
|
|
* possible to provide a callback that lazy-loads the grid when it is first accessed. This is
|
|
* especially benefitial when loading grids from a file and it's not clear in the beginning if
|
|
* the tree is actually needed. It's also supported to just load the meta-data and transform
|
|
* first and to load the tree only when it's used. This allows e.g. transforming or renaming the
|
|
* grid without loading the tree.
|
|
* - Unloading of the tree: It's possible to unload the tree data when it is not in use. This is
|
|
* only supported on a shared grid if the tree could be reloaded (e.g. by reading it from a vdb
|
|
* file) and if no one is currently accessing the grid data.
|
|
*/
|
|
class VolumeGridData : public ImplicitSharingMixin {
|
|
private:
|
|
/**
|
|
* Empty struct that exists so that it can be used as token in #VolumeTreeAccessToken.
|
|
*/
|
|
struct AccessToken {
|
|
};
|
|
|
|
/**
|
|
* A mutex that needs to be locked whenever working with the data members below.
|
|
*/
|
|
mutable std::mutex mutex_;
|
|
/**
|
|
* The actual grid. Depending on the current state, is in one of multiple possible states:
|
|
* - Empty: When the grid is lazy-loaded and no meta-data is provided.
|
|
* - Only meta-data and transform: When the grid is lazy-loaded and initial meta-data is
|
|
* provided.
|
|
* - Complete: When the grid is fully loaded. It then contains the meta-data, transform and tree.
|
|
*
|
|
* `std::shared_ptr` is used, because some OpenVDB APIs expect the use of those. Unfortunately,
|
|
* one can not insert and release data from a `shared_ptr`. Therefore, the grid has to be wrapped
|
|
* by the `shared_ptr` at all times.
|
|
*
|
|
* However, this #VolumeGridData is considered to be the only actual owner of the grid. It is
|
|
* also considered to be the only owner of the meta-data and transform in the grid. It is
|
|
* possible to share the tree though.
|
|
*/
|
|
mutable std::shared_ptr<openvdb::GridBase> grid_;
|
|
/**
|
|
* Keeps track of whether the tree in `grid_` is current mutable or shared.
|
|
*/
|
|
mutable const ImplicitSharingInfo *tree_sharing_info_ = nullptr;
|
|
|
|
/** The tree stored in the grid is valid. */
|
|
mutable bool tree_loaded_ = false;
|
|
/** The transform stored in the grid is valid. */
|
|
mutable bool transform_loaded_ = false;
|
|
/** The meta-data stored in the grid is valid. */
|
|
mutable bool meta_data_loaded_ = false;
|
|
|
|
/**
|
|
* A function that can load the full grid or also just the tree lazily.
|
|
*/
|
|
std::function<std::shared_ptr<openvdb::GridBase>()> lazy_load_grid_;
|
|
/**
|
|
* An error produced while trying to lazily load the grid.
|
|
*/
|
|
mutable std::string error_message_;
|
|
/**
|
|
* A token that allows detecting whether some code is currently accessing the tree (not grid) or
|
|
* not. If this variable is the only owner of the `shared_ptr`, no one else has access to the
|
|
* tree. `shared_ptr` is used here because it makes it very easy to manage a user-count without
|
|
* much boilerplate.
|
|
*/
|
|
std::shared_ptr<AccessToken> tree_access_token_;
|
|
|
|
friend class VolumeTreeAccessToken;
|
|
|
|
/** Private default constructor for internal purposes. */
|
|
VolumeGridData();
|
|
|
|
public:
|
|
/**
|
|
* Constructs a new volume grid of the given type where all voxels are inactive and the
|
|
* background value is the default value (generally zero).
|
|
*/
|
|
explicit VolumeGridData(VolumeGridType grid_type);
|
|
|
|
/**
|
|
* Constructs a new volume grid from the provided OpenVDB grid of which it takes ownership. The
|
|
* grid must be moved into this constructor and must not be shared currently.
|
|
*/
|
|
explicit VolumeGridData(std::shared_ptr<openvdb::GridBase> grid);
|
|
|
|
/**
|
|
* Constructs a new volume grid that loads the underlying OpenVDB data lazily.
|
|
* \param lazy_load_grid: Function that is called when the data is first needed. It returns the
|
|
* new grid or may raise an exception. The returned meta-data and transform are ignored if the
|
|
* second parameter is provided here.
|
|
* \param meta_data_and_transform_grid: An initial grid where the tree may be null. This grid
|
|
* might come from e.g. #readAllGridMetadata. This allows working with the transform and
|
|
* meta-data without actually loading the tree.
|
|
*/
|
|
explicit VolumeGridData(std::function<std::shared_ptr<openvdb::GridBase>()> lazy_load_grid,
|
|
std::shared_ptr<openvdb::GridBase> meta_data_and_transform_grid = {});
|
|
|
|
~VolumeGridData();
|
|
|
|
/**
|
|
* Get an access token for the underlying tree. This is necessary to be able to detect whether
|
|
* the grid is currently unused so that it can be safely unloaded.
|
|
*/
|
|
VolumeTreeAccessToken tree_access_token() const;
|
|
|
|
/**
|
|
* Create a copy of the volume grid. This should generally only be done when the current grid is
|
|
* shared and one owner wants to modify it.
|
|
*
|
|
* This makes a deep copy of the transform and meta-data, but the tree remains shared and is only
|
|
* copied later if necessary.
|
|
*/
|
|
GVolumeGrid copy() const;
|
|
|
|
/**
|
|
* Get the underlying OpenVDB grid for read-only access. This may load the tree lazily if it's
|
|
* not loaded already.
|
|
*/
|
|
const openvdb::GridBase &grid(const VolumeTreeAccessToken &tree_access_token) const;
|
|
/**
|
|
* Get the underlying OpenVDB grid for read and write access. This may load the tree lazily if
|
|
* it's not loaded already. It may also make a copy of the tree if it's currently shared.
|
|
*/
|
|
openvdb::GridBase &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
|
|
|
|
/**
|
|
* Same as #grid and #grid_for_write but returns the grid as a `shared_ptr` so that it can be
|
|
* used with APIs that only support grids wrapped into one. This method is not supposed to
|
|
* actually transfer ownership of the grid.
|
|
*/
|
|
std::shared_ptr<const openvdb::GridBase> grid_ptr(
|
|
const VolumeTreeAccessToken &tree_access_token) const;
|
|
std::shared_ptr<openvdb::GridBase> grid_ptr_for_write(
|
|
const VolumeTreeAccessToken &tree_access_token);
|
|
|
|
/**
|
|
* Get the name of the grid that's stored in the grid meta-data.
|
|
*/
|
|
std::string name() const;
|
|
/**
|
|
* Replace the name of the grid that's stored in the meta-data.
|
|
*/
|
|
void set_name(StringRef name);
|
|
|
|
/**
|
|
* Get the transform of the grid for read-only access. This may lazily load the data if it's not
|
|
* yet available.
|
|
*/
|
|
const openvdb::math::Transform &transform() const;
|
|
/**
|
|
* Get the transform of the grid for read and write access. This may lazily load the data if it's
|
|
* not yet available.
|
|
*/
|
|
openvdb::math::Transform &transform_for_write();
|
|
|
|
/**
|
|
* Grid type that's derived from the OpenVDB tree type.
|
|
*/
|
|
VolumeGridType grid_type() const;
|
|
|
|
/**
|
|
* Same as #grid_type() but does not potentially call the lazy-load function to figure out the
|
|
* grid type. This can be used e.g. by asserts.
|
|
*/
|
|
std::optional<VolumeGridType> grid_type_without_load() const;
|
|
|
|
/**
|
|
* Grid class that is stored in the grid's meta data.
|
|
*/
|
|
openvdb::GridClass grid_class() const;
|
|
|
|
/**
|
|
* True if the grid is fully loaded (including the meta-data, transform and tree).
|
|
*/
|
|
bool is_loaded() const;
|
|
|
|
/**
|
|
* Non-empty string if there was some error when trying to load the volume.
|
|
*/
|
|
std::string error_message() const;
|
|
|
|
/**
|
|
* Tree if the tree can be loaded again after it has been unloaded.
|
|
*/
|
|
bool is_reloadable() const;
|
|
|
|
/**
|
|
* Unloads the tree data if it's reloadable and no one is using it right now.
|
|
*/
|
|
void unload_tree_if_possible() const;
|
|
|
|
private:
|
|
void ensure_grid_loaded() const;
|
|
void delete_self();
|
|
};
|
|
|
|
class VolumeTreeAccessToken {
|
|
private:
|
|
std::shared_ptr<VolumeGridData::AccessToken> token_;
|
|
|
|
friend VolumeGridData;
|
|
|
|
public:
|
|
/** True if the access token can be used with the given grid. */
|
|
bool valid_for(const VolumeGridData &grid) const;
|
|
|
|
/** Revoke the access token to indicating that the tree is not used anymore. */
|
|
void reset();
|
|
};
|
|
|
|
/**
|
|
* A #GVolumeGrid owns a volume grid. Semantically, each #GVolumeGrid is independent but implicit
|
|
* sharing is used to avoid unnecessary deep copies.
|
|
*/
|
|
class GVolumeGrid {
|
|
protected:
|
|
ImplicitSharingPtr<VolumeGridData> data_;
|
|
|
|
public:
|
|
/**
|
|
* Constructs a #GVolumeGrid that does not contain any data.
|
|
*/
|
|
GVolumeGrid() = default;
|
|
/**
|
|
* Take (shared) ownership of the given grid data. The caller is responsible for making sure that
|
|
* the user count includes a user for the newly constructed #GVolumeGrid.
|
|
*/
|
|
explicit GVolumeGrid(const VolumeGridData *data);
|
|
/**
|
|
* Constructs a new volume grid that takes unique ownership of the passed in OpenVDB grid.
|
|
*/
|
|
explicit GVolumeGrid(std::shared_ptr<openvdb::GridBase> grid);
|
|
/**
|
|
* Constructs an empty grid of the given type, where all voxels are inactive and the background
|
|
* is the default value (generally zero).
|
|
*/
|
|
explicit GVolumeGrid(VolumeGridType grid_type);
|
|
|
|
/**
|
|
* Get the underlying (potentially shared) volume grid data for read-only access.
|
|
*/
|
|
const VolumeGridData &get() const;
|
|
|
|
/**
|
|
* Get the underlying volume grid data for read and write access. This may make a copy of the
|
|
* grid data is shared.
|
|
*/
|
|
VolumeGridData &get_for_write();
|
|
|
|
/**
|
|
* Move ownership of the underlying grid data to the caller.
|
|
*/
|
|
const VolumeGridData *release();
|
|
|
|
/** Makes it more convenient to retrieve data from the grid. */
|
|
const VolumeGridData *operator->() const;
|
|
|
|
/** True if this contains a grid. */
|
|
operator bool() const;
|
|
|
|
/** Converts to a typed VolumeGrid. This asserts if the type is wrong. */
|
|
template<typename T> VolumeGrid<T> typed() const;
|
|
};
|
|
|
|
/**
|
|
* Same as #GVolumeGrid but makes it easier to work with the grid if the type is known at compile
|
|
* time.
|
|
*/
|
|
template<typename T> class VolumeGrid : public GVolumeGrid {
|
|
public:
|
|
using base_type = T;
|
|
|
|
VolumeGrid() = default;
|
|
explicit VolumeGrid(const VolumeGridData *data);
|
|
explicit VolumeGrid(std::shared_ptr<OpenvdbGridType<T>> grid);
|
|
|
|
/**
|
|
* Wraps the same methods on #VolumeGridData but casts to the correct OpenVDB type.
|
|
*/
|
|
const OpenvdbGridType<T> &grid(const VolumeTreeAccessToken &tree_access_token) const;
|
|
OpenvdbGridType<T> &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
|
|
|
|
private:
|
|
void assert_correct_type() const;
|
|
};
|
|
|
|
/**
|
|
* Get the volume grid type based on the tree type in the grid.
|
|
*/
|
|
VolumeGridType get_type(const openvdb::GridBase &grid);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Inline Methods
|
|
* \{ */
|
|
|
|
inline GVolumeGrid::GVolumeGrid(const VolumeGridData *data) : data_(data) {}
|
|
|
|
inline const VolumeGridData &GVolumeGrid::get() const
|
|
{
|
|
BLI_assert(*this);
|
|
return *data_.get();
|
|
}
|
|
|
|
inline const VolumeGridData *GVolumeGrid::release()
|
|
{
|
|
return data_.release();
|
|
}
|
|
|
|
inline GVolumeGrid::operator bool() const
|
|
{
|
|
return bool(data_);
|
|
}
|
|
|
|
template<typename T> inline VolumeGrid<T> GVolumeGrid::typed() const
|
|
{
|
|
if (data_) {
|
|
data_->add_user();
|
|
}
|
|
return VolumeGrid<T>(data_.get());
|
|
}
|
|
|
|
inline const VolumeGridData *GVolumeGrid::operator->() const
|
|
{
|
|
BLI_assert(*this);
|
|
return data_.get();
|
|
}
|
|
|
|
template<typename T>
|
|
inline VolumeGrid<T>::VolumeGrid(const VolumeGridData *data) : GVolumeGrid(data)
|
|
{
|
|
this->assert_correct_type();
|
|
}
|
|
|
|
template<typename T>
|
|
inline VolumeGrid<T>::VolumeGrid(std::shared_ptr<OpenvdbGridType<T>> grid)
|
|
: GVolumeGrid(std::move(grid))
|
|
{
|
|
this->assert_correct_type();
|
|
}
|
|
|
|
template<typename T>
|
|
inline const OpenvdbGridType<T> &VolumeGrid<T>::grid(
|
|
const VolumeTreeAccessToken &tree_access_token) const
|
|
{
|
|
return static_cast<const OpenvdbGridType<T> &>(data_->grid(tree_access_token));
|
|
}
|
|
|
|
template<typename T>
|
|
inline OpenvdbGridType<T> &VolumeGrid<T>::grid_for_write(
|
|
const VolumeTreeAccessToken &tree_access_token)
|
|
{
|
|
return static_cast<OpenvdbGridType<T> &>(
|
|
this->get_for_write().grid_for_write(tree_access_token));
|
|
}
|
|
|
|
template<typename T> inline void VolumeGrid<T>::assert_correct_type() const
|
|
{
|
|
# ifndef NDEBUG
|
|
if (data_) {
|
|
const VolumeGridType expected_type = VolumeGridTraits<T>::EnumType;
|
|
if (const std::optional<VolumeGridType> actual_type = data_->grid_type_without_load()) {
|
|
BLI_assert(expected_type == *actual_type);
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
inline bool VolumeTreeAccessToken::valid_for(const VolumeGridData &grid) const
|
|
{
|
|
return grid.tree_access_token_ == token_;
|
|
}
|
|
|
|
inline void VolumeTreeAccessToken::reset()
|
|
{
|
|
token_.reset();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::bke::volume_grid
|
|
|
|
#endif /* WITH_OPENVDB */
|