tornavis/source/blender/blenkernel/BKE_volume_grid.hh

414 lines
13 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 beneficial 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();
/**
* 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(VolumeTreeAccessToken &r_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(VolumeTreeAccessToken &r_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(VolumeTreeAccessToken &r_token) const;
std::shared_ptr<openvdb::GridBase> grid_ptr_for_write(VolumeTreeAccessToken &r_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(VolumeTreeAccessToken &r_token) const;
OpenvdbGridType<T> &grid_for_write(VolumeTreeAccessToken &r_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(VolumeTreeAccessToken &r_token) const
{
return static_cast<const OpenvdbGridType<T> &>(data_->grid(r_token));
}
template<typename T>
inline OpenvdbGridType<T> &VolumeGrid<T>::grid_for_write(VolumeTreeAccessToken &r_token)
{
return static_cast<OpenvdbGridType<T> &>(this->get_for_write().grid_for_write(r_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 */