diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index 0384e763d20..517ce4c6407 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -114,6 +114,9 @@ endif() if(WITH_OPENVDB) add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS}) + list(APPEND INC + ../../../intern/openvdb + ) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} ) diff --git a/intern/cycles/blender/volume.cpp b/intern/cycles/blender/volume.cpp index 0d003d7cb3f..e6a4033462e 100644 --- a/intern/cycles/blender/volume.cpp +++ b/intern/cycles/blender/volume.cpp @@ -11,11 +11,8 @@ #include "blender/sync.h" #include "blender/util.h" -#ifdef WITH_OPENVDB -# include -openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const struct Volume *volume, - const struct VolumeGrid *grid); -#endif +#include "BKE_volume.hh" +#include "BKE_volume_grid.hh" CCL_NAMESPACE_BEGIN @@ -232,16 +229,10 @@ class BlenderVolumeLoader : public VDBImageLoader { #ifdef WITH_OPENVDB for (BL::VolumeGrid &b_volume_grid : b_volume.grids) { if (b_volume_grid.name() == grid_name) { - const bool unload = !b_volume_grid.is_loaded(); - - ::Volume *volume = (::Volume *)b_volume.ptr.data; - const VolumeGrid *volume_grid = (VolumeGrid *)b_volume_grid.ptr.data; - grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); - - if (unload) { - b_volume_grid.unload(); - } - + const auto *volume_grid = static_cast( + b_volume_grid.ptr.data); + tree_access_token = volume_grid->tree_access_token(); + grid = volume_grid->grid_ptr(tree_access_token); break; } } @@ -265,6 +256,10 @@ class BlenderVolumeLoader : public VDBImageLoader { } BL::Volume b_volume; +#ifdef WITH_OPENVDB + /* Store tree user so that the openvdb grid that is shared with Blender is not unloaded. */ + blender::bke::VolumeTreeAccessToken tree_access_token; +#endif }; static void sync_volume_object(BL::BlendData &b_data, diff --git a/intern/openvdb/CMakeLists.txt b/intern/openvdb/CMakeLists.txt index e006304420e..d03b7623d1e 100644 --- a/intern/openvdb/CMakeLists.txt +++ b/intern/openvdb/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC_SYS set(SRC openvdb_capi.h + openvdb_fwd.hh ) set(LIB diff --git a/intern/openvdb/openvdb_fwd.hh b/intern/openvdb/openvdb_fwd.hh new file mode 100644 index 00000000000..6d569e09590 --- /dev/null +++ b/intern/openvdb/openvdb_fwd.hh @@ -0,0 +1,111 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/* Note: version header included here to enable correct forward declaration of some types. No other + * OpenVDB headers should be included here, especially openvdb.h, to avoid affecting other + * compilation units. */ +#include +#include + +/* -------------------------------------------------------------------- */ +/** \name OpenVDB Forward Declaration + * \{ */ + +/* Forward declaration for basic OpenVDB types. */ +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { + +class GridBase; +class MetaMap; +template class Grid; + +namespace math { +class Transform; +} + +namespace tree { +class TreeBase; +template class LeafNode; +template class InternalNode; +template class RootNode; +template class Tree; + +/* Forward-declared version of Tree4, can't use the actual Tree4 alias because it can't be + * forward-declared. */ +template struct Tree4Fwd { + using Type = openvdb::tree::Tree, N2>, + N1>>>; +}; +} // namespace tree + +namespace tools { +template struct PointIndexLeafNode; +using PointIndexTree = tree::Tree, 4>, 5>>>; +using PointIndexGrid = Grid; +} // namespace tools + +namespace points { +template class PointDataLeafNode; +using PointDataTree = tree::Tree, 4>, 5>>>; +using PointDataGrid = Grid; +struct NullCodec; +template class TypedAttributeArray; +} // namespace points + +/// Common tree types +using BoolTree = tree::Tree4Fwd::Type; +using DoubleTree = tree::Tree4Fwd::Type; +using FloatTree = tree::Tree4Fwd::Type; +using Int8Tree = tree::Tree4Fwd::Type; +using Int32Tree = tree::Tree4Fwd::Type; +using Int64Tree = tree::Tree4Fwd::Type; +using MaskTree = tree::Tree4Fwd::Type; +using UInt32Tree = tree::Tree4Fwd::Type; +using Vec2DTree = tree::Tree4Fwd::Type; +using Vec2ITree = tree::Tree4Fwd::Type; +using Vec2STree = tree::Tree4Fwd::Type; +using Vec3DTree = tree::Tree4Fwd::Type; +using Vec3ITree = tree::Tree4Fwd::Type; +using Vec3STree = tree::Tree4Fwd::Type; +using Vec4STree = tree::Tree4Fwd::Type; +using ScalarTree = FloatTree; +using TopologyTree = MaskTree; +using Vec3dTree = Vec3DTree; +using Vec3fTree = Vec3STree; +using Vec4fTree = Vec4STree; +using VectorTree = Vec3fTree; + +/// Common grid types +using BoolGrid = Grid; +using DoubleGrid = Grid; +using FloatGrid = Grid; +using Int8Grid = Grid; +using Int32Grid = Grid; +using Int64Grid = Grid; +using UInt32Grid = Grid; +using MaskGrid = Grid; +using Vec3DGrid = Grid; +using Vec2IGrid = Grid; +using Vec3IGrid = Grid; +using Vec2SGrid = Grid; +using Vec3SGrid = Grid; +using Vec4SGrid = Grid; +using ScalarGrid = FloatGrid; +using TopologyGrid = MaskGrid; +using Vec3dGrid = Vec3DGrid; +using Vec2fGrid = Vec2SGrid; +using Vec3fGrid = Vec3SGrid; +using Vec4fGrid = Vec4SGrid; +using VectorGrid = Vec3fGrid; + +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +/** \} */ diff --git a/source/blender/blenkernel/BKE_volume.hh b/source/blender/blenkernel/BKE_volume.hh index 54990791a83..09978d2690c 100644 --- a/source/blender/blenkernel/BKE_volume.hh +++ b/source/blender/blenkernel/BKE_volume.hh @@ -14,13 +14,14 @@ #include "BLI_bounds_types.hh" #include "BLI_math_vector_types.hh" +#include "BKE_volume_grid_fwd.hh" + struct Depsgraph; struct Main; struct Object; struct ReportList; struct Scene; struct Volume; -struct VolumeGrid; struct VolumeGridVector; /* Module */ @@ -69,54 +70,18 @@ bool BKE_volume_is_loaded(const Volume *volume); int BKE_volume_num_grids(const Volume *volume); const char *BKE_volume_grids_error_msg(const Volume *volume); const char *BKE_volume_grids_frame_filepath(const Volume *volume); -const VolumeGrid *BKE_volume_grid_get_for_read(const Volume *volume, int grid_index); -VolumeGrid *BKE_volume_grid_get_for_write(Volume *volume, int grid_index); -const VolumeGrid *BKE_volume_grid_active_get_for_read(const Volume *volume); +const blender::bke::VolumeGridData *BKE_volume_grid_get(const Volume *volume, int grid_index); +blender::bke::VolumeGridData *BKE_volume_grid_get_for_write(Volume *volume, int grid_index); +const blender::bke::VolumeGridData *BKE_volume_grid_active_get_for_read(const Volume *volume); /* Tries to find a grid with the given name. Make sure that the volume has been loaded. */ -const VolumeGrid *BKE_volume_grid_find_for_read(const Volume *volume, const char *name); -VolumeGrid *BKE_volume_grid_find_for_write(Volume *volume, const char *name); +const blender::bke::VolumeGridData *BKE_volume_grid_find(const Volume *volume, const char *name); +blender::bke::VolumeGridData *BKE_volume_grid_find_for_write(Volume *volume, const char *name); /* Tries to set the name of the velocity field. If no such grid exists with the given base name, * this will try common post-fixes in order to detect velocity fields split into multiple grids. * Return false if neither finding with the base name nor with the post-fixes succeeded. */ bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const char *base_name); -/* Grid - * - * By default only grid metadata is loaded, for access to the tree and voxels - * BKE_volume_grid_load must be called first. */ - -enum VolumeGridType : int8_t { - VOLUME_GRID_UNKNOWN = 0, - VOLUME_GRID_BOOLEAN, - VOLUME_GRID_FLOAT, - VOLUME_GRID_DOUBLE, - VOLUME_GRID_INT, - VOLUME_GRID_INT64, - VOLUME_GRID_MASK, - VOLUME_GRID_VECTOR_FLOAT, - VOLUME_GRID_VECTOR_DOUBLE, - VOLUME_GRID_VECTOR_INT, - VOLUME_GRID_POINTS, -}; - -bool BKE_volume_grid_load(const Volume *volume, const VolumeGrid *grid); -void BKE_volume_grid_unload(const Volume *volume, const VolumeGrid *grid); -bool BKE_volume_grid_is_loaded(const VolumeGrid *grid); - -/* Metadata */ - -const char *BKE_volume_grid_name(const VolumeGrid *grid); -VolumeGridType BKE_volume_grid_type(const VolumeGrid *grid); -int BKE_volume_grid_channels(const VolumeGrid *grid); -/** - * Transformation from index space to object space. - */ -void BKE_volume_grid_transform_matrix(const VolumeGrid *grid, float mat[4][4]); -void BKE_volume_grid_transform_matrix_set(const Volume *volume, - VolumeGrid *volume_grid, - const float mat[4][4]); - /* Volume Editing * * These are intended for modifiers to use on evaluated data-blocks. @@ -130,8 +95,13 @@ void BKE_volume_grid_transform_matrix_set(const Volume *volume, Volume *BKE_volume_new_for_eval(const Volume *volume_src); Volume *BKE_volume_copy_for_eval(const Volume *volume_src); -VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType type); -void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid); +void BKE_volume_grid_remove(Volume *volume, const blender::bke::VolumeGridData *grid); + +/** + * Adds a new grid to the volume with the name stored in the grid. The caller is responsible for + * making sure that the user count already contains the volume as a user. + */ +void BKE_volume_grid_add(Volume *volume, const blender::bke::VolumeGridData &grid); /** * OpenVDB crashes when the determinant of the transform matrix becomes too small. @@ -148,9 +118,4 @@ bool BKE_volume_save(const Volume *volume, ReportList *reports, const char *filepath); -/* OpenVDB Grid Access - * - * Access to OpenVDB grid for C++. These will automatically load grids from - * file or copy shared grids to make them writeable. */ - std::optional> BKE_volume_min_max(const Volume *volume); diff --git a/source/blender/blenkernel/BKE_volume_enums.hh b/source/blender/blenkernel/BKE_volume_enums.hh new file mode 100644 index 00000000000..3817b52e8cf --- /dev/null +++ b/source/blender/blenkernel/BKE_volume_enums.hh @@ -0,0 +1,23 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +enum VolumeGridType : int8_t { + VOLUME_GRID_UNKNOWN = 0, + VOLUME_GRID_BOOLEAN, + VOLUME_GRID_FLOAT, + VOLUME_GRID_DOUBLE, + VOLUME_GRID_INT, + VOLUME_GRID_INT64, + VOLUME_GRID_MASK, + VOLUME_GRID_VECTOR_FLOAT, + VOLUME_GRID_VECTOR_DOUBLE, + VOLUME_GRID_VECTOR_INT, + VOLUME_GRID_POINTS, +}; diff --git a/source/blender/blenkernel/BKE_volume_grid.hh b/source/blender/blenkernel/BKE_volume_grid.hh new file mode 100644 index 00000000000..dffd0a79564 --- /dev/null +++ b/source/blender/blenkernel/BKE_volume_grid.hh @@ -0,0 +1,425 @@ +/* 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 +# include +# include + +# 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, + * 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 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()> 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 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 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()> lazy_load_grid, + std::shared_ptr 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 grid_ptr( + const VolumeTreeAccessToken &tree_access_token) const; + std::shared_ptr 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 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 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 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 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 VolumeGrid typed() const; +}; + +/** + * Same as #GVolumeGrid but makes it easier to work with the grid if the type is known at compile + * time. + */ +template class VolumeGrid : public GVolumeGrid { + public: + using base_type = T; + + VolumeGrid() = default; + explicit VolumeGrid(const VolumeGridData *data); + explicit VolumeGrid(std::shared_ptr> grid); + + /** + * Wraps the same methods on #VolumeGridData but casts to the correct OpenVDB type. + */ + const OpenvdbGridType &grid(const VolumeTreeAccessToken &tree_access_token) const; + OpenvdbGridType &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 inline VolumeGrid GVolumeGrid::typed() const +{ + if (data_) { + data_->add_user(); + } + return VolumeGrid(data_.get()); +} + +inline const VolumeGridData *GVolumeGrid::operator->() const +{ + BLI_assert(*this); + return data_.get(); +} + +template +inline VolumeGrid::VolumeGrid(const VolumeGridData *data) : GVolumeGrid(data) +{ + this->assert_correct_type(); +} + +template +inline VolumeGrid::VolumeGrid(std::shared_ptr> grid) + : GVolumeGrid(std::move(grid)) +{ + this->assert_correct_type(); +} + +template +inline const OpenvdbGridType &VolumeGrid::grid( + const VolumeTreeAccessToken &tree_access_token) const +{ + return static_cast &>(data_->grid(tree_access_token)); +} + +template +inline OpenvdbGridType &VolumeGrid::grid_for_write( + const VolumeTreeAccessToken &tree_access_token) +{ + return static_cast &>( + this->get_for_write().grid_for_write(tree_access_token)); +} + +template inline void VolumeGrid::assert_correct_type() const +{ +# ifndef NDEBUG + if (data_) { + const VolumeGridType expected_type = VolumeGridTraits::EnumType; + if (const std::optional 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 */ diff --git a/source/blender/blenkernel/BKE_volume_grid_file_cache.hh b/source/blender/blenkernel/BKE_volume_grid_file_cache.hh new file mode 100644 index 00000000000..60406731852 --- /dev/null +++ b/source/blender/blenkernel/BKE_volume_grid_file_cache.hh @@ -0,0 +1,59 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#ifdef WITH_OPENVDB + +# include "BLI_vector.hh" + +# include "BKE_volume_grid.hh" + +/** + * The global volume grid file cache makes it easy to load volumes only once from disk and to then + * reuse the loaded volume across Blender. Additionally, this also supports caching simplify + * levels which are used when the "volume resolution" simplify scene setting is reduced. Working + * with reduced resolution can improve performance and uses less memory. + */ +namespace blender::bke::volume_grid::file_cache { + +/** + * Get the volume grid identified by the parameters from a cache. This does not load the tree data + * in grid because that is done on demand when it is accessed. + */ +GVolumeGrid get_grid_from_file(StringRef file_path, StringRef grid_name, int simplify_level = 0); + +struct GridsFromFile { + /** + * Empty on success. Otherwise it contains information about why loading the file failed. + */ + std::string error_message; + /** + * Meta data for the entire file (not for individual grids). + */ + std::shared_ptr file_meta_data; + /** + * All grids stored in the file. + */ + Vector grids; +}; + +/** + * Get all the data stored in a .vdb file. This does not actually load the tree data, which is done + * on demand. + */ +GridsFromFile get_all_grids_from_file(StringRef file_path, int simplify_level = 0); + +/** + * Remove all cached volume grids that are currently not referenced outside of the cache. + */ +void unload_unused(); + +} // namespace blender::bke::volume_grid::file_cache + +#endif diff --git a/source/blender/blenkernel/BKE_volume_grid_fwd.hh b/source/blender/blenkernel/BKE_volume_grid_fwd.hh new file mode 100644 index 00000000000..2904e986d81 --- /dev/null +++ b/source/blender/blenkernel/BKE_volume_grid_fwd.hh @@ -0,0 +1,119 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include + +#include "BKE_volume_enums.hh" + +#include "BLI_math_matrix_types.hh" + +/** + * This header gives contains declarations for dealing with volume grids without requiring + * including any OpenVDB headers (which can have a significant impact on compile times). + * + * These functions are available even if `WITH_OPENVDB` is false, but they may just be empty. + */ + +namespace blender::bke::volume_grid { + +/** + * Wraps an OpenVDB grid and adds features like implicit sharing and lazy-loading. + */ +class VolumeGridData; + +/** + * Owning container for a #VolumeGridData instance. + */ +class GVolumeGrid; + +/** + * Same as #GVolumeGrid but means that the contained grid is of a specific type. + */ +template class VolumeGrid; + +/** + * Access token required to use the tree stored in a volume grid. This allows detecting whether a + * tree is currently used or not, for the purpose of safely freeing unused trees. + */ +class VolumeTreeAccessToken; + +/** + * Compile time check to see of a type is a #VolumeGrid. This is false for e.g. `float` or + * `GVolumeGrid` and true for e.g. `VolumeGrid` and `VolumeGrid`. + */ +template static constexpr bool is_VolumeGrid_v = false; +template static constexpr bool is_VolumeGrid_v> = true; + +/** + * Get the name stored in the volume grid, e.g. "density". + */ +std::string get_name(const VolumeGridData &grid); + +/** + * Get the data type stored in the volume grid. + */ +VolumeGridType get_type(const VolumeGridData &grid); + +/** + * Get the number of primitive values stored per voxel. For example, for a float-grid this is 1 and + * for a vector-grid it is 3 (for x, y and z). + */ +int get_channels_num(VolumeGridType type); + +/** + * Unloads the tree data if no one is using it right now and it could be reloaded later on. + */ +void unload_tree_if_possible(const VolumeGridData &grid); + +/** + * Get the transform of the grid as an affine matrix. + */ +float4x4 get_transform_matrix(const VolumeGridData &grid); + +/** + * Replaces the transform matrix with the given one. + */ +void set_transform_matrix(VolumeGridData &grid, const float4x4 &matrix); + +/** + * Clears the tree data in the grid, but keeps meta-data and the transform intact. + */ +void clear_tree(VolumeGridData &grid); + +/** + * Makes sure that the volume grid is loaded afterwards. This is necessary to call this for + * correctness, because the grid will be loaded on demand anyway. Sometimes it may be benefitial + * for performance to load the grid eagerly though. + */ +void load(const VolumeGridData &grid); + +/** + * Returns a non-empty string if there was some error when the grid was loaded. + */ +std::string error_message_from_load(const VolumeGridData &grid); + +/** + * True if the full grid (including meta-data, transform and the tree) is already available and + * does not have to be loaded lazily anymore. + */ +bool is_loaded(const VolumeGridData &grid); + +} // namespace blender::bke::volume_grid + +/** + * Put the most common types directly into the `blender::bke` namespace. + */ +namespace blender::bke { +using volume_grid::GVolumeGrid; +using volume_grid::is_VolumeGrid_v; +using volume_grid::VolumeGrid; +using volume_grid::VolumeGridData; +using volume_grid::VolumeTreeAccessToken; +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_volume_grid_type_traits.hh b/source/blender/blenkernel/BKE_volume_grid_type_traits.hh new file mode 100644 index 00000000000..49605364b1e --- /dev/null +++ b/source/blender/blenkernel/BKE_volume_grid_type_traits.hh @@ -0,0 +1,119 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#ifdef WITH_OPENVDB + +# include "BLI_math_quaternion_types.hh" +# include "BLI_math_vector_types.hh" + +# include "BKE_volume_enums.hh" +# include "openvdb_fwd.hh" + +namespace blender::bke { + +/** + * We uses the native math types from OpenVDB when working with grids. For example, vector grids + * contain `openvdb::Vec3f`. #VolumeGridTraits allows mapping between Blender's math types and the + * ones from OpenVDB. This allows e.g. using `VolumeGrid` when the actual grid is a + * `openvdb::Vec3SGrid`. The benefit of this is that most places in Blender can keep using our own + * types, while only the code that deals with OpenVDB specifically has to care about the mapping + * between math type representations. + * + * \param T The Blender type that we want to get the grid traits for (e.g. `blender::float3`). + */ +template struct VolumeGridTraits { + /** + * The type that Blender uses to represent values of the voxel type (e.g. `blender::float3`). + */ + using BlenderType = void; + /** + * The type that OpenVDB uses. + */ + using PrimitiveType = void; + /** + * The standard tree type we use for grids of the given type. + */ + using TreeType = void; + /** + * The corresponding #VolumeGridType for the type. + */ + static constexpr VolumeGridType EnumType = VOLUME_GRID_UNKNOWN; +}; + +template<> struct VolumeGridTraits { + using BlenderType = bool; + using PrimitiveType = bool; + using TreeType = openvdb::BoolTree; + static constexpr VolumeGridType EnumType = VOLUME_GRID_BOOLEAN; + + static bool to_openvdb(const bool &value) + { + return value; + } + static bool to_blender(const bool &value) + { + return value; + } +}; + +template<> struct VolumeGridTraits { + using BlenderType = int; + using PrimitiveType = int; + using TreeType = openvdb::Int32Tree; + static constexpr VolumeGridType EnumType = VOLUME_GRID_INT; + + static int to_openvdb(const int &value) + { + return value; + } + static int to_blender(const int &value) + { + return value; + } +}; + +template<> struct VolumeGridTraits { + using BlenderType = float; + using PrimitiveType = float; + using TreeType = openvdb::FloatTree; + static constexpr VolumeGridType EnumType = VOLUME_GRID_FLOAT; + + static float to_openvdb(const float &value) + { + return value; + } + static float to_blender(const float &value) + { + return value; + } +}; + +template<> struct VolumeGridTraits { + using BlenderType = float3; + using PrimitiveType = openvdb::Vec3f; + using TreeType = openvdb::Vec3STree; + static constexpr VolumeGridType EnumType = VOLUME_GRID_VECTOR_FLOAT; + + static openvdb::Vec3f to_openvdb(const float3 &value) + { + return openvdb::Vec3f(*value); + } + static float3 to_blender(const openvdb::Vec3f &value) + { + return float3(value.asV()); + } +}; + +template using OpenvdbTreeType = typename VolumeGridTraits::TreeType; +template using OpenvdbGridType = openvdb::Grid>; + +} // namespace blender::bke + +#endif /* WITH_OPENVDB */ diff --git a/source/blender/blenkernel/BKE_volume_openvdb.hh b/source/blender/blenkernel/BKE_volume_openvdb.hh index 91ca433bec4..47cd65fcb52 100644 --- a/source/blender/blenkernel/BKE_volume_openvdb.hh +++ b/source/blender/blenkernel/BKE_volume_openvdb.hh @@ -8,14 +8,20 @@ # include # include +# include +# include "BLI_bounds_types.hh" # include "BLI_math_matrix_types.hh" # include "BLI_math_vector_types.hh" # include "BLI_string_ref.hh" -VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume, - blender::StringRef name, - openvdb::GridBase::Ptr vdb_grid); +# include "BKE_volume_enums.hh" + +struct Volume; + +blender::bke::VolumeGridData *BKE_volume_grid_add_vdb(Volume &volume, + blender::StringRef name, + openvdb::GridBase::Ptr vdb_grid); std::optional> BKE_volume_grid_bounds( openvdb::GridBase::ConstPtr grid); @@ -28,18 +34,6 @@ std::optional> BKE_volume_grid_bounds( openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid, const blender::float4x4 &transform); -openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const VolumeGrid *grid); -openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const Volume *volume, - const VolumeGrid *grid); -openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const Volume *volume, - VolumeGrid *grid, - bool clear); - -void BKE_volume_grid_clear_tree(Volume &volume, VolumeGrid &volume_grid); -void BKE_volume_grid_clear_tree(openvdb::GridBase &grid); - -VolumeGridType BKE_volume_grid_type_openvdb(const openvdb::GridBase &grid); - template auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op) { diff --git a/source/blender/blenkernel/BKE_volume_render.hh b/source/blender/blenkernel/BKE_volume_render.hh index 1fb9c213b79..0de46651603 100644 --- a/source/blender/blenkernel/BKE_volume_render.hh +++ b/source/blender/blenkernel/BKE_volume_render.hh @@ -13,9 +13,10 @@ #include "DNA_volume_types.h" +#include "BKE_volume_enums.hh" +#include "BKE_volume_grid_fwd.hh" + struct Volume; -struct VolumeGrid; -enum VolumeGridType : int8_t; /* Dense Voxels */ @@ -28,7 +29,7 @@ struct DenseFloatVolumeGrid { }; bool BKE_volume_grid_dense_floats(const Volume *volume, - const VolumeGrid *volume_grid, + const blender::bke::VolumeGridData *volume_grid, DenseFloatVolumeGrid *r_dense_grid); void BKE_volume_dense_float_grid_clear(DenseFloatVolumeGrid *dense_grid); @@ -38,7 +39,7 @@ typedef void (*BKE_volume_wireframe_cb)( void *userdata, const float (*verts)[3], const int (*edges)[2], int totvert, int totedge); void BKE_volume_grid_wireframe(const Volume *volume, - const VolumeGrid *volume_grid, + const blender::bke::VolumeGridData *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata); @@ -48,7 +49,7 @@ using BKE_volume_selection_surface_cb = void (*)(void *userdata, float (*verts)[3], int (*tris)[3], int totvert, int tottris); void BKE_volume_grid_selection_surface(const Volume *volume, - const VolumeGrid *volume_grid, + const blender::bke::VolumeGridData *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index d0281e75682..433ec5a2766 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -309,6 +309,8 @@ set(SRC intern/vfontdata_freetype.cc intern/viewer_path.cc intern/volume.cc + intern/volume_grid.cc + intern/volume_grid_file_cache.cc intern/volume_render.cc intern/volume_to_mesh.cc intern/workspace.cc @@ -508,6 +510,9 @@ set(SRC BKE_vfontdata.hh BKE_viewer_path.hh BKE_volume.hh + BKE_volume_enums.hh + BKE_volume_grid.hh + BKE_volume_grid_file_cache.hh BKE_volume_openvdb.hh BKE_volume_render.hh BKE_volume_to_mesh.hh @@ -853,6 +858,7 @@ if(WITH_GTESTS) intern/main_test.cc intern/nla_test.cc intern/tracking_test.cc + intern/volume_test.cc ) set(TEST_INC ../editors/include diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 7250e8fffab..e0ddffc684b 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -45,6 +45,8 @@ #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_volume.hh" +#include "BKE_volume_grid.hh" +#include "BKE_volume_grid_file_cache.hh" #include "BKE_volume_openvdb.hh" #include "BLT_translation.h" @@ -66,6 +68,7 @@ using blender::float4x4; using blender::IndexRange; using blender::StringRef; using blender::StringRefNull; +using blender::bke::GVolumeGrid; #ifdef WITH_OPENVDB # include @@ -77,403 +80,19 @@ using blender::StringRefNull; # include # include -/* Global Volume File Cache - * - * Global cache of grids read from VDB files. This is used for sharing grids - * between multiple volume datablocks with the same filepath, and sharing grids - * between original and copy-on-write datablocks created by the depsgraph. - * - * There are two types of users. Some datablocks only need the grid metadata, - * example an original datablock volume showing the list of grids in the - * properties editor. Other datablocks also need the tree and voxel data, for - * rendering for example. So, depending on the users the grid in the cache may - * have a tree or not. - * - * When the number of users drops to zero, the grid data is immediately deleted. - * - * TODO: also add a cache for OpenVDB files rather than individual grids, - * so getting the list of grids is also cached. - * TODO: Further, we could cache openvdb::io::File so that loading a grid - * does not re-open it every time. But then we have to take care not to run - * out of file descriptors or prevent other applications from writing to it. - */ - -static struct VolumeFileCache { - /* Cache Entry */ - struct Entry { - Entry(const std::string &filepath, const openvdb::GridBase::Ptr &grid) - : filepath(filepath), grid_name(grid->getName()), grid(grid) - { - } - - Entry(const Entry &other) - : filepath(other.filepath), - grid_name(other.grid_name), - grid(other.grid), - is_loaded(other.is_loaded) - { - } - - /* Returns the original grid or a simplified version depending on the given #simplify_level. */ - openvdb::GridBase::Ptr simplified_grid(const int simplify_level) - { - BLI_assert(simplify_level >= 0); - if (simplify_level == 0 || !is_loaded) { - return grid; - } - - std::lock_guard lock(mutex); - openvdb::GridBase::Ptr simple_grid; - - /* Isolate creating grid since that's multithreaded and we are - * holding a mutex lock. */ - blender::threading::isolate_task([&] { - simple_grid = simplified_grids.lookup_or_add_cb(simplify_level, [&]() { - const float resolution_factor = 1.0f / (1 << simplify_level); - const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(*grid); - return BKE_volume_grid_create_with_changed_resolution( - grid_type, *grid, resolution_factor); - }); - }); - return simple_grid; - } - - /* Unique key: filename + grid name. */ - std::string filepath; - std::string grid_name; - - /* OpenVDB grid. */ - openvdb::GridBase::Ptr grid; - - /* Simplified versions of #grid. The integer key is the simplification level. */ - blender::Map simplified_grids; - - /* Has the grid tree been loaded? */ - mutable bool is_loaded = false; - /* Error message if an error occurred while loading. */ - std::string error_msg; - /* User counting. */ - int num_metadata_users = 0; - int num_tree_users = 0; - /* Mutex for on-demand reading of tree. */ - mutable std::mutex mutex; - }; - - struct EntryHasher { - std::size_t operator()(const Entry &entry) const - { - std::hash string_hasher; - return BLI_ghashutil_combine_hash(string_hasher(entry.filepath), - string_hasher(entry.grid_name)); - } - }; - - struct EntryEqual { - bool operator()(const Entry &a, const Entry &b) const - { - return a.filepath == b.filepath && a.grid_name == b.grid_name; - } - }; - - /* Cache */ - ~VolumeFileCache() - { - BLI_assert(cache.empty()); - } - - Entry *add_metadata_user(const Entry &template_entry) - { - std::lock_guard lock(mutex); - EntrySet::iterator it = cache.find(template_entry); - if (it == cache.end()) { - it = cache.emplace(template_entry).first; - } - - /* Casting const away is weak, but it's convenient having key and value in one. */ - Entry &entry = (Entry &)*it; - entry.num_metadata_users++; - - /* NOTE: pointers to unordered_set values are not invalidated when adding - * or removing other values. */ - return &entry; - } - - void copy_user(Entry &entry, const bool tree_user) - { - std::lock_guard lock(mutex); - if (tree_user) { - entry.num_tree_users++; - } - else { - entry.num_metadata_users++; - } - } - - void remove_user(Entry &entry, const bool tree_user) - { - std::lock_guard lock(mutex); - if (tree_user) { - entry.num_tree_users--; - } - else { - entry.num_metadata_users--; - } - update_for_remove_user(entry); - } - - void change_to_tree_user(Entry &entry) - { - std::lock_guard lock(mutex); - entry.num_tree_users++; - entry.num_metadata_users--; - update_for_remove_user(entry); - } - - void change_to_metadata_user(Entry &entry) - { - std::lock_guard lock(mutex); - entry.num_metadata_users++; - entry.num_tree_users--; - update_for_remove_user(entry); - } - - protected: - void update_for_remove_user(Entry &entry) - { - /* Isolate file unloading since that's multithreaded and we are - * holding a mutex lock. */ - blender::threading::isolate_task([&] { - if (entry.num_metadata_users + entry.num_tree_users == 0) { - cache.erase(entry); - } - else if (entry.num_tree_users == 0) { - /* Note we replace the grid rather than clearing, so that if there is - * any other shared pointer to the grid it will keep the tree. */ - entry.grid = entry.grid->copyGridWithNewTree(); - entry.simplified_grids.clear(); - entry.is_loaded = false; - } - }); - } - - /* Cache contents */ - using EntrySet = std::unordered_set; - EntrySet cache; - /* Mutex for multithreaded access. */ - std::mutex mutex; -} GLOBAL_CACHE; - -/* VolumeGrid - * - * Wrapper around OpenVDB grid. Grids loaded from OpenVDB files are always - * stored in the global cache. Procedurally generated grids are not. */ - -struct VolumeGrid { - VolumeGrid(const VolumeFileCache::Entry &template_entry, const int simplify_level) - : entry(nullptr), simplify_level(simplify_level), is_loaded(false) - { - entry = GLOBAL_CACHE.add_metadata_user(template_entry); - } - - VolumeGrid(const openvdb::GridBase::Ptr &grid) - : entry(nullptr), local_grid(grid), is_loaded(true) - { - } - - VolumeGrid(const VolumeGrid &other) - : entry(other.entry), - simplify_level(other.simplify_level), - local_grid(other.local_grid), - is_loaded(other.is_loaded) - { - if (entry) { - GLOBAL_CACHE.copy_user(*entry, is_loaded); - } - } - - ~VolumeGrid() - { - if (entry) { - GLOBAL_CACHE.remove_user(*entry, is_loaded); - } - } - - void load(const char *volume_name, const char *filepath) const - { - /* If already loaded or not file-backed, nothing to do. */ - if (is_loaded || entry == nullptr) { - return; - } - - /* Double-checked lock. */ - std::lock_guard lock(entry->mutex); - if (is_loaded) { - return; - } - - /* Change metadata user to tree user. */ - GLOBAL_CACHE.change_to_tree_user(*entry); - - /* If already loaded by another user, nothing further to do. */ - if (entry->is_loaded) { - is_loaded = true; - return; - } - - /* Load grid from file. */ - CLOG_INFO(&LOG, 1, "Volume %s: load grid '%s'", volume_name, name()); - - openvdb::io::File file(filepath); - - /* Isolate file loading since that's potentially multi-threaded and we are - * holding a mutex lock. */ - blender::threading::isolate_task([&] { - try { - /* Disable delay loading and file copying, this has poor performance - * on network drivers. */ - const bool delay_load = false; - file.setCopyMaxBytes(0); - file.open(delay_load); - openvdb::GridBase::Ptr vdb_grid = file.readGrid(name()); - entry->grid->setTree(vdb_grid->baseTreePtr()); - } - catch (const openvdb::IoError &e) { - entry->error_msg = e.what(); - } - catch (...) { - entry->error_msg = "Unknown error reading VDB file"; - } - }); - - std::atomic_thread_fence(std::memory_order_release); - entry->is_loaded = true; - is_loaded = true; - } - - void unload(const char *volume_name) const - { - /* Not loaded or not file-backed, nothing to do. */ - if (!is_loaded || entry == nullptr) { - return; - } - - /* Double-checked lock. */ - std::lock_guard lock(entry->mutex); - if (!is_loaded) { - return; - } - - CLOG_INFO(&LOG, 1, "Volume %s: unload grid '%s'", volume_name, name()); - - /* Change tree user to metadata user. */ - GLOBAL_CACHE.change_to_metadata_user(*entry); - - /* Indicate we no longer have a tree. The actual grid may still - * have it due to another user. */ - std::atomic_thread_fence(std::memory_order_release); - is_loaded = false; - } - - void clear_reference(const char * /*volume_name*/) - { - /* Clear any reference to a grid in the file cache. */ - local_grid = grid()->copyGridWithNewTree(); - if (entry) { - GLOBAL_CACHE.remove_user(*entry, is_loaded); - entry = nullptr; - } - is_loaded = true; - } - - void duplicate_reference(const char *volume_name, const char *filepath) - { - /* Make a deep copy of the grid and remove any reference to a grid in the - * file cache. Load file grid into memory first if needed. */ - load(volume_name, filepath); - /* TODO: avoid deep copy if we are the only user. */ - local_grid = grid()->deepCopyGrid(); - if (entry) { - GLOBAL_CACHE.remove_user(*entry, is_loaded); - entry = nullptr; - } - is_loaded = true; - } - - const char *name() const - { - /* Don't use vdb.getName() since it copies the string, we want a pointer to the - * original so it doesn't get freed out of scope. */ - openvdb::StringMetadata::ConstPtr name_meta = - main_grid()->getMetadata(openvdb::GridBase::META_GRID_NAME); - return (name_meta) ? name_meta->value().c_str() : ""; - } - - const char *error_message() const - { - if (is_loaded && entry && !entry->error_msg.empty()) { - return entry->error_msg.c_str(); - } - - return nullptr; - } - - bool grid_is_loaded() const - { - return is_loaded; - } - - openvdb::GridBase::Ptr grid() const - { - if (entry) { - return entry->simplified_grid(simplify_level); - } - return local_grid; - } - - void set_simplify_level(const int simplify_level) - { - BLI_assert(simplify_level >= 0); - this->simplify_level = simplify_level; - } - - private: - const openvdb::GridBase::Ptr &main_grid() const - { - return (entry) ? entry->grid : local_grid; - } - - protected: - /* File cache entry when grid comes directly from a file and may be shared - * with other volume datablocks. */ - VolumeFileCache::Entry *entry; - /* If this volume grid is in the global file cache, we can reference a simplified version of it, - * instead of the original high resolution grid. */ - int simplify_level = 0; - /* OpenVDB grid if it's not shared through the file cache. */ - openvdb::GridBase::Ptr local_grid; - /** - * Indicates if the tree has been loaded for this grid. Note that vdb.tree() - * may actually be loaded by another user while this is false. But only after - * calling load() and is_loaded changes to true is it safe to access. - * - * `const` write access to this must be protected by `entry->mutex`. - */ - mutable bool is_loaded; -}; - /* Volume Grid Vector * * List of grids contained in a volume datablock. This is runtime-only data, * the actual grids are always saved in a VDB file. */ -struct VolumeGridVector : public std::list { +struct VolumeGridVector : public std::list { VolumeGridVector() : metadata(new openvdb::MetaMap()) { filepath[0] = '\0'; } VolumeGridVector(const VolumeGridVector &other) - : std::list(other), error_msg(other.error_msg), metadata(other.metadata) + : std::list(other), error_msg(other.error_msg), metadata(other.metadata) { memcpy(filepath, other.filepath, sizeof(filepath)); } @@ -485,7 +104,7 @@ struct VolumeGridVector : public std::list { void clear_all() { - std::list::clear(); + std::list::clear(); filepath[0] = '\0'; error_msg.clear(); metadata.reset(); @@ -555,6 +174,8 @@ static void volume_free_data(ID *id) #ifdef WITH_OPENVDB MEM_delete(volume->runtime.grids); volume->runtime.grids = nullptr; + /* Deleting the volume might have made some grids completely unused, so they can be freed. */ + blender::bke::volume_grid::file_cache::unload_unused(); #endif } @@ -783,7 +404,7 @@ bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const char *base_name) { const StringRefNull ref_base_name = base_name; - if (BKE_volume_grid_find_for_read(volume, base_name)) { + if (BKE_volume_grid_find(volume, base_name)) { STRNCPY(volume->velocity_grid, base_name); volume->runtime.velocity_x_grid[0] = '\0'; volume->runtime.velocity_y_grid[0] = '\0'; @@ -798,7 +419,7 @@ bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const char *base_name) bool found = true; for (int i = 0; i < 3; i++) { std::string post_fixed_name = ref_base_name + postfix[i]; - if (!BKE_volume_grid_find_for_read(volume, post_fixed_name.c_str())) { + if (!BKE_volume_grid_find(volume, post_fixed_name.c_str())) { found = false; break; } @@ -864,34 +485,18 @@ bool BKE_volume_load(const Volume *volume, const Main *bmain) return false; } - /* Open OpenVDB file. */ - openvdb::io::File file(filepath); - openvdb::GridPtrVec vdb_grids; + blender::bke::volume_grid::file_cache::GridsFromFile grids_from_file = + blender::bke::volume_grid::file_cache::get_all_grids_from_file(filepath, 0); - try { - /* Disable delay loading and file copying, this has poor performance - * on network drives. */ - const bool delay_load = false; - file.setCopyMaxBytes(0); - file.open(delay_load); - vdb_grids = *(file.readAllGridMetadata()); - grids.metadata = file.getMetadata(); - } - catch (const openvdb::IoError &e) { - grids.error_msg = e.what(); - CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str()); - } - catch (...) { - grids.error_msg = "Unknown error reading VDB file"; + if (!grids_from_file.error_message.empty()) { + grids.error_msg = grids_from_file.error_message; CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str()); + return false; } - /* Add grids read from file to own vector, filtering out any null pointers. */ - for (const openvdb::GridBase::Ptr &vdb_grid : vdb_grids) { - if (vdb_grid) { - VolumeFileCache::Entry template_entry(filepath, vdb_grid); - grids.emplace_back(template_entry, volume->runtime.default_simplify_level); - } + grids.metadata = std::move(grids_from_file.file_meta_data); + for (GVolumeGrid &volume_grid : grids_from_file.grids) { + grids.emplace_back(std::move(volume_grid)); } /* Try to detect the velocity grid. */ @@ -941,8 +546,12 @@ bool BKE_volume_save(const Volume *volume, VolumeGridVector &grids = *volume->runtime.grids; openvdb::GridCPtrVec vdb_grids; - for (VolumeGrid &grid : grids) { - vdb_grids.push_back(BKE_volume_grid_openvdb_for_read(volume, &grid)); + /* Tree users need to be kept alive for as long as the grids may be accessed. */ + blender::Vector tree_users; + + for (const GVolumeGrid &grid : grids) { + tree_users.append(grid->tree_access_token()); + vdb_grids.push_back(grid->grid_ptr(tree_users.last())); } try { @@ -975,9 +584,10 @@ std::optional> BKE_volume_min_max(const Volume if (BKE_volume_load(const_cast(volume), G.main)) { std::optional> result; for (const int i : IndexRange(BKE_volume_num_grids(volume))) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); - result = blender::bounds::merge(result, BKE_volume_grid_bounds(grid)); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i); + blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + result = blender::bounds::merge(result, + BKE_volume_grid_bounds(volume_grid->grid_ptr(access_token))); } return result; } @@ -1015,8 +625,8 @@ bool BKE_volume_is_points_only(const Volume *volume) } for (int i = 0; i < num_grids; i++) { - const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); - if (BKE_volume_grid_type(grid) != VOLUME_GRID_POINTS) { + const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i); + if (blender::bke::volume_grid::get_type(*grid) != VOLUME_GRID_POINTS) { return false; } } @@ -1026,18 +636,26 @@ bool BKE_volume_is_points_only(const Volume *volume) /* Dependency Graph */ -static void volume_update_simplify_level(Volume *volume, const Depsgraph *depsgraph) +static void volume_update_simplify_level(Main *bmain, Volume *volume, const Depsgraph *depsgraph) { #ifdef WITH_OPENVDB const int simplify_level = BKE_volume_simplify_level(depsgraph); - if (volume->runtime.grids) { - for (VolumeGrid &grid : *volume->runtime.grids) { - grid.set_simplify_level(simplify_level); - } - } volume->runtime.default_simplify_level = simplify_level; + + /* Replace grids with the new simplify level variants from the cache. */ + if (BKE_volume_load(volume, bmain)) { + VolumeGridVector &grids = *volume->runtime.grids; + std::list new_grids; + for (const GVolumeGrid &old_grid : grids) { + GVolumeGrid simple_grid = blender::bke::volume_grid::file_cache::get_grid_from_file( + grids.filepath, old_grid->name(), simplify_level); + BLI_assert(simple_grid); + new_grids.push_back(std::move(simple_grid)); + } + grids.swap(new_grids); + } #else - UNUSED_VARS(volume, depsgraph); + UNUSED_VARS(bmain, volume, depsgraph); #endif } @@ -1078,7 +696,8 @@ static void volume_evaluate_modifiers(Depsgraph *depsgraph, void BKE_volume_eval_geometry(Depsgraph *depsgraph, Volume *volume) { - volume_update_simplify_level(volume, depsgraph); + Main *bmain = DEG_get_bmain(depsgraph); + volume_update_simplify_level(bmain, volume, depsgraph); /* TODO: can we avoid modifier re-evaluation when frame did not change? */ int frame = volume_sequence_frame(depsgraph, volume); @@ -1217,13 +836,13 @@ const char *BKE_volume_grids_frame_filepath(const Volume *volume) #endif } -const VolumeGrid *BKE_volume_grid_get_for_read(const Volume *volume, int grid_index) +const blender::bke::VolumeGridData *BKE_volume_grid_get(const Volume *volume, int grid_index) { #ifdef WITH_OPENVDB const VolumeGridVector &grids = *volume->runtime.grids; - for (const VolumeGrid &grid : grids) { + for (const GVolumeGrid &grid : grids) { if (grid_index-- == 0) { - return &grid; + return &grid.get(); } } return nullptr; @@ -1233,13 +852,13 @@ const VolumeGrid *BKE_volume_grid_get_for_read(const Volume *volume, int grid_in #endif } -VolumeGrid *BKE_volume_grid_get_for_write(Volume *volume, int grid_index) +blender::bke::VolumeGridData *BKE_volume_grid_get_for_write(Volume *volume, int grid_index) { #ifdef WITH_OPENVDB VolumeGridVector &grids = *volume->runtime.grids; - for (VolumeGrid &grid : grids) { + for (GVolumeGrid &grid_ptr : grids) { if (grid_index-- == 0) { - return &grid; + return &grid_ptr.get_for_write(); } } return nullptr; @@ -1249,7 +868,7 @@ VolumeGrid *BKE_volume_grid_get_for_write(Volume *volume, int grid_index) #endif } -const VolumeGrid *BKE_volume_grid_active_get_for_read(const Volume *volume) +const blender::bke::VolumeGridData *BKE_volume_grid_active_get_for_read(const Volume *volume) { const int num_grids = BKE_volume_num_grids(volume); if (num_grids == 0) { @@ -1257,15 +876,15 @@ const VolumeGrid *BKE_volume_grid_active_get_for_read(const Volume *volume) } const int index = clamp_i(volume->active_grid, 0, num_grids - 1); - return BKE_volume_grid_get_for_read(volume, index); + return BKE_volume_grid_get(volume, index); } -const VolumeGrid *BKE_volume_grid_find_for_read(const Volume *volume, const char *name) +const blender::bke::VolumeGridData *BKE_volume_grid_find(const Volume *volume, const char *name) { int num_grids = BKE_volume_num_grids(volume); for (int i = 0; i < num_grids; i++) { - const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); - if (STREQ(BKE_volume_grid_name(grid), name)) { + const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i); + if (blender::bke::volume_grid::get_name(*grid) == name) { return grid; } } @@ -1273,201 +892,19 @@ const VolumeGrid *BKE_volume_grid_find_for_read(const Volume *volume, const char return nullptr; } -VolumeGrid *BKE_volume_grid_find_for_write(Volume *volume, const char *name) +blender::bke::VolumeGridData *BKE_volume_grid_find_for_write(Volume *volume, const char *name) { int num_grids = BKE_volume_num_grids(volume); for (int i = 0; i < num_grids; i++) { - VolumeGrid *grid = BKE_volume_grid_get_for_write(volume, i); - if (STREQ(BKE_volume_grid_name(grid), name)) { - return grid; + const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i); + if (blender::bke::volume_grid::get_name(*grid) == name) { + return BKE_volume_grid_get_for_write(volume, i); } } return nullptr; } -/* Grid Loading */ - -bool BKE_volume_grid_load(const Volume *volume, const VolumeGrid *grid) -{ -#ifdef WITH_OPENVDB - VolumeGridVector &grids = *volume->runtime.grids; - const char *volume_name = volume->id.name + 2; - grid->load(volume_name, grids.filepath); - const char *error_msg = grid->error_message(); - if (error_msg) { - grids.error_msg = error_msg; - return false; - } - return true; -#else - UNUSED_VARS(volume, grid); - return true; -#endif -} - -void BKE_volume_grid_unload(const Volume *volume, const VolumeGrid *grid) -{ -#ifdef WITH_OPENVDB - const char *volume_name = volume->id.name + 2; - grid->unload(volume_name); -#else - UNUSED_VARS(volume, grid); -#endif -} - -bool BKE_volume_grid_is_loaded(const VolumeGrid *grid) -{ -#ifdef WITH_OPENVDB - return grid->grid_is_loaded(); -#else - UNUSED_VARS(grid); - return true; -#endif -} - -/* Grid Metadata */ - -const char *BKE_volume_grid_name(const VolumeGrid *volume_grid) -{ -#ifdef WITH_OPENVDB - return volume_grid->name(); -#else - UNUSED_VARS(volume_grid); - return "density"; -#endif -} - -#ifdef WITH_OPENVDB -struct ClearGridOp { - openvdb::GridBase &grid; - - template void operator()() - { - static_cast(grid).clear(); - } -}; -void BKE_volume_grid_clear_tree(openvdb::GridBase &grid) -{ - const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid); - ClearGridOp op{grid}; - BKE_volume_grid_type_operation(grid_type, op); -} -void BKE_volume_grid_clear_tree(Volume &volume, VolumeGrid &volume_grid) -{ - openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(&volume, &volume_grid, false); - BKE_volume_grid_clear_tree(*grid); -} - -VolumeGridType BKE_volume_grid_type_openvdb(const openvdb::GridBase &grid) -{ - if (grid.isType()) { - return VOLUME_GRID_FLOAT; - } - if (grid.isType()) { - return VOLUME_GRID_VECTOR_FLOAT; - } - if (grid.isType()) { - return VOLUME_GRID_BOOLEAN; - } - if (grid.isType()) { - return VOLUME_GRID_DOUBLE; - } - if (grid.isType()) { - return VOLUME_GRID_INT; - } - if (grid.isType()) { - return VOLUME_GRID_INT64; - } - if (grid.isType()) { - return VOLUME_GRID_VECTOR_INT; - } - if (grid.isType()) { - return VOLUME_GRID_VECTOR_DOUBLE; - } - if (grid.isType()) { - return VOLUME_GRID_MASK; - } - if (grid.isType()) { - return VOLUME_GRID_POINTS; - } - return VOLUME_GRID_UNKNOWN; -} -#endif - -VolumeGridType BKE_volume_grid_type(const VolumeGrid *volume_grid) -{ -#ifdef WITH_OPENVDB - const openvdb::GridBase::Ptr grid = volume_grid->grid(); - return BKE_volume_grid_type_openvdb(*grid); -#else - UNUSED_VARS(volume_grid); -#endif - return VOLUME_GRID_UNKNOWN; -} - -int BKE_volume_grid_channels(const VolumeGrid *grid) -{ - switch (BKE_volume_grid_type(grid)) { - case VOLUME_GRID_BOOLEAN: - case VOLUME_GRID_FLOAT: - case VOLUME_GRID_DOUBLE: - case VOLUME_GRID_INT: - case VOLUME_GRID_INT64: - case VOLUME_GRID_MASK: - return 1; - case VOLUME_GRID_VECTOR_FLOAT: - case VOLUME_GRID_VECTOR_DOUBLE: - case VOLUME_GRID_VECTOR_INT: - return 3; - case VOLUME_GRID_POINTS: - case VOLUME_GRID_UNKNOWN: - return 0; - } - - return 0; -} - -void BKE_volume_grid_transform_matrix(const VolumeGrid *volume_grid, float mat[4][4]) -{ -#ifdef WITH_OPENVDB - const openvdb::GridBase::Ptr grid = volume_grid->grid(); - const openvdb::math::Transform &transform = grid->transform(); - - /* Perspective not supported for now, getAffineMap() will leave out the - * perspective part of the transform. */ - openvdb::math::Mat4f matrix = transform.baseMap()->getAffineMap()->getMat4(); - /* Blender column-major and OpenVDB right-multiplication conventions match. */ - for (int col = 0; col < 4; col++) { - for (int row = 0; row < 4; row++) { - mat[col][row] = matrix(col, row); - } - } -#else - unit_m4(mat); - UNUSED_VARS(volume_grid); -#endif -} - -void BKE_volume_grid_transform_matrix_set(const Volume *volume, - VolumeGrid *volume_grid, - const float mat[4][4]) -{ -#ifdef WITH_OPENVDB - openvdb::math::Mat4f mat_openvdb; - for (int col = 0; col < 4; col++) { - for (int row = 0; row < 4; row++) { - mat_openvdb(col, row) = mat[col][row]; - } - } - openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); - grid->setTransform(std::make_shared( - std::make_shared(mat_openvdb))); -#else - UNUSED_VARS(volume, volume_grid, mat); -#endif -} - /* Grid Tree and Voxels */ /* Volume Editing */ @@ -1505,48 +942,27 @@ struct CreateGridOp { }; #endif -VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType type) -{ #ifdef WITH_OPENVDB - VolumeGridVector &grids = *volume->runtime.grids; - BLI_assert(BKE_volume_grid_find_for_read(volume, name) == nullptr); - BLI_assert(type != VOLUME_GRID_UNKNOWN); - - openvdb::GridBase::Ptr vdb_grid = BKE_volume_grid_type_operation(type, CreateGridOp{}); - if (!vdb_grid) { - return nullptr; - } - - vdb_grid->setName(name); - grids.emplace_back(vdb_grid); - return &grids.back(); -#else - UNUSED_VARS(volume, name, type); - return nullptr; -#endif -} - -#ifdef WITH_OPENVDB -VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume, - const StringRef name, - openvdb::GridBase::Ptr vdb_grid) +blender::bke::VolumeGridData *BKE_volume_grid_add_vdb(Volume &volume, + const StringRef name, + openvdb::GridBase::Ptr vdb_grid) { VolumeGridVector &grids = *volume.runtime.grids; - BLI_assert(BKE_volume_grid_find_for_read(&volume, name.data()) == nullptr); - BLI_assert(BKE_volume_grid_type_openvdb(*vdb_grid) != VOLUME_GRID_UNKNOWN); + BLI_assert(BKE_volume_grid_find(&volume, name.data()) == nullptr); + BLI_assert(blender::bke::volume_grid::get_type(*vdb_grid) != VOLUME_GRID_UNKNOWN); vdb_grid->setName(name); - grids.emplace_back(vdb_grid); - return &grids.back(); + grids.emplace_back(GVolumeGrid(std::move(vdb_grid))); + return &grids.back().get_for_write(); } #endif -void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid) +void BKE_volume_grid_remove(Volume *volume, const blender::bke::VolumeGridData *grid) { #ifdef WITH_OPENVDB VolumeGridVector &grids = *volume->runtime.grids; for (VolumeGridVector::iterator it = grids.begin(); it != grids.end(); it++) { - if (&*it == grid) { + if (&it->get() == grid) { grids.erase(it); break; } @@ -1556,6 +972,16 @@ void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid) #endif } +void BKE_volume_grid_add(Volume *volume, const blender::bke::VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + VolumeGridVector &grids = *volume->runtime.grids; + grids.push_back(GVolumeGrid(&grid)); +#else + UNUSED_VARS(volume, grid); +#endif +} + bool BKE_volume_grid_determinant_valid(const double determinant) { #ifdef WITH_OPENVDB @@ -1621,34 +1047,6 @@ openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase: return grid->copyGridReplacingTransform(grid_transform); } -openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const VolumeGrid *grid) -{ - return grid->grid(); -} - -openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const Volume *volume, - const VolumeGrid *grid) -{ - BKE_volume_grid_load(volume, grid); - return grid->grid(); -} - -openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const Volume *volume, - VolumeGrid *grid, - const bool clear) -{ - const char *volume_name = volume->id.name + 2; - if (clear) { - grid->clear_reference(volume_name); - } - else { - VolumeGridVector &grids = *volume->runtime.grids; - grid->duplicate_reference(volume_name, grids.filepath); - } - - return grid->grid(); -} - /* Changing the resolution of a grid. */ /** diff --git a/source/blender/blenkernel/intern/volume_grid.cc b/source/blender/blenkernel/intern/volume_grid.cc new file mode 100644 index 00000000000..4dcba497952 --- /dev/null +++ b/source/blender/blenkernel/intern/volume_grid.cc @@ -0,0 +1,509 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_volume_grid.hh" +#include "BKE_volume_openvdb.hh" + +#include "BLI_task.hh" + +#ifdef WITH_OPENVDB +# include +#endif + +namespace blender::bke::volume_grid { + +#ifdef WITH_OPENVDB + +/** + * Multiple #VolumeDataGrid can implictly share the same underlying tree with different + * meta-data/transforms. + */ +class OpenvdbTreeSharingInfo : public ImplicitSharingInfo { + private: + std::shared_ptr tree_; + + public: + OpenvdbTreeSharingInfo(std::shared_ptr tree) : tree_(std::move(tree)) {} + + void delete_self_with_data() override + { + MEM_freeN(this); + } + + void delete_data_only() override + { + tree_.reset(); + } +}; + +VolumeGridData::VolumeGridData() +{ + tree_access_token_ = std::make_shared(); +} + +struct CreateGridOp { + template openvdb::GridBase::Ptr operator()() const + { + return GridT::create(); + } +}; + +static openvdb::GridBase::Ptr create_grid_for_type(const VolumeGridType grid_type) +{ + return BKE_volume_grid_type_operation(grid_type, CreateGridOp{}); +} + +VolumeGridData::VolumeGridData(const VolumeGridType grid_type) + : VolumeGridData(create_grid_for_type(grid_type)) +{ +} + +VolumeGridData::VolumeGridData(std::shared_ptr grid) + : grid_(std::move(grid)), tree_loaded_(true), transform_loaded_(true), meta_data_loaded_(true) +{ + BLI_assert(grid_); + BLI_assert(grid_.unique()); + BLI_assert(grid_->isTreeUnique()); + + tree_sharing_info_ = MEM_new(__func__, grid_->baseTreePtr()); + tree_access_token_ = std::make_shared(); +} + +VolumeGridData::VolumeGridData(std::function()> lazy_load_grid, + std::shared_ptr meta_data_and_transform_grid) + : grid_(std::move(meta_data_and_transform_grid)), lazy_load_grid_(std::move(lazy_load_grid)) +{ + if (grid_) { + transform_loaded_ = true; + meta_data_loaded_ = true; + } + tree_access_token_ = std::make_shared(); +} + +VolumeGridData::~VolumeGridData() +{ + if (tree_sharing_info_) { + tree_sharing_info_->remove_user_and_delete_if_last(); + } +} + +void VolumeGridData::delete_self() +{ + MEM_delete(this); +} + +VolumeTreeAccessToken VolumeGridData::tree_access_token() const +{ + VolumeTreeAccessToken user; + user.token_ = tree_access_token_; + return user; +} + +const openvdb::GridBase &VolumeGridData::grid(const VolumeTreeAccessToken &access_token) const +{ + return *this->grid_ptr(access_token); +} + +openvdb::GridBase &VolumeGridData::grid_for_write(const VolumeTreeAccessToken &access_token) +{ + return *this->grid_ptr_for_write(access_token); +} + +std::shared_ptr VolumeGridData::grid_ptr( + const VolumeTreeAccessToken &access_token) const +{ + BLI_assert(access_token.valid_for(*this)); + UNUSED_VARS_NDEBUG(access_token); + std::lock_guard lock{mutex_}; + this->ensure_grid_loaded(); + return grid_; +} + +std::shared_ptr VolumeGridData::grid_ptr_for_write( + const VolumeTreeAccessToken &access_token) +{ + BLI_assert(access_token.valid_for(*this)); + UNUSED_VARS_NDEBUG(access_token); + BLI_assert(this->is_mutable()); + std::lock_guard lock{mutex_}; + this->ensure_grid_loaded(); + if (tree_sharing_info_->is_mutable()) { + tree_sharing_info_->tag_ensured_mutable(); + } + else { + auto tree_copy = grid_->baseTree().copy(); + grid_->setTree(tree_copy); + tree_sharing_info_->remove_user_and_delete_if_last(); + tree_sharing_info_ = MEM_new(__func__, std::move(tree_copy)); + } + /* Can't reload the grid anymore if it has been changed. */ + lazy_load_grid_ = {}; + return grid_; +} + +const openvdb::math::Transform &VolumeGridData::transform() const +{ + std::lock_guard lock{mutex_}; + if (!transform_loaded_) { + this->ensure_grid_loaded(); + } + return grid_->transform(); +} + +openvdb::math::Transform &VolumeGridData::transform_for_write() +{ + BLI_assert(this->is_mutable()); + std::lock_guard lock{mutex_}; + if (!transform_loaded_) { + this->ensure_grid_loaded(); + } + return grid_->transform(); +} + +std::string VolumeGridData::name() const +{ + std::lock_guard lock{mutex_}; + if (!meta_data_loaded_) { + this->ensure_grid_loaded(); + } + return grid_->getName(); +} + +void VolumeGridData::set_name(const StringRef name) +{ + BLI_assert(this->is_mutable()); + std::lock_guard lock{mutex_}; + if (!meta_data_loaded_) { + this->ensure_grid_loaded(); + } + grid_->setName(name); +} + +VolumeGridType VolumeGridData::grid_type() const +{ + std::lock_guard lock{mutex_}; + if (!meta_data_loaded_) { + this->ensure_grid_loaded(); + } + return get_type(*grid_); +} + +std::optional VolumeGridData::grid_type_without_load() const +{ + std::lock_guard lock{mutex_}; + if (!meta_data_loaded_) { + return std::nullopt; + } + return get_type(*grid_); +} + +openvdb::GridClass VolumeGridData::grid_class() const +{ + std::lock_guard lock{mutex_}; + if (!meta_data_loaded_) { + this->ensure_grid_loaded(); + } + return grid_->getGridClass(); +} + +bool VolumeGridData::is_reloadable() const +{ + return bool(lazy_load_grid_); +} + +bool VolumeGridData::is_loaded() const +{ + std::lock_guard lock{mutex_}; + return tree_loaded_ && transform_loaded_ && meta_data_loaded_; +} + +std::string VolumeGridData::error_message() const +{ + std::lock_guard lock{mutex_}; + return error_message_; +} + +void VolumeGridData::unload_tree_if_possible() const +{ + std::lock_guard lock{mutex_}; + if (!grid_) { + return; + } + if (!this->is_reloadable()) { + return; + } + if (!tree_access_token_.unique()) { + /* Some code is using the tree currently, so it can't be freed. */ + return; + } + grid_->newTree(); + tree_loaded_ = false; + tree_sharing_info_->remove_user_and_delete_if_last(); + tree_sharing_info_ = nullptr; +} + +GVolumeGrid VolumeGridData::copy() const +{ + std::lock_guard lock{mutex_}; + this->ensure_grid_loaded(); + /* Can't use #MEM_new because the default construtor is private. */ + VolumeGridData *new_copy = new (MEM_mallocN(sizeof(VolumeGridData), __func__)) VolumeGridData(); + /* Makes a deep copy of the meta-data but shares the tree. */ + new_copy->grid_ = grid_->copyGrid(); + new_copy->tree_sharing_info_ = tree_sharing_info_; + new_copy->tree_sharing_info_->add_user(); + new_copy->tree_loaded_ = tree_loaded_; + new_copy->transform_loaded_ = transform_loaded_; + new_copy->meta_data_loaded_ = meta_data_loaded_; + return GVolumeGrid(new_copy); +} + +void VolumeGridData::ensure_grid_loaded() const +{ + /* Assert that the mutex is locked. */ + BLI_assert(!mutex_.try_lock()); + + if (tree_loaded_ && transform_loaded_ && meta_data_loaded_) { + return; + } + BLI_assert(lazy_load_grid_); + std::shared_ptr loaded_grid; + /* Isolate because the a mutex is locked. */ + threading::isolate_task([&]() { + error_message_.clear(); + try { + loaded_grid = lazy_load_grid_(); + } + catch (const openvdb::IoError &e) { + error_message_ = e.what(); + } + catch (...) { + error_message_ = "Unknown error reading VDB file"; + } + }); + if (!loaded_grid) { + if (grid_) { + /* Create a dummy grid of the expected type. */ + loaded_grid = grid_->createGrid(""); + } + else { + /* Create a dummy grid. We can't really know the expected data type here. */ + loaded_grid = openvdb::FloatGrid::create(); + } + } + BLI_assert(loaded_grid); + BLI_assert(loaded_grid.unique()); + BLI_assert(loaded_grid->isTreeUnique()); + + if (grid_) { + /* Keep the existing grid pointer and just insert the newly loaded data. */ + BLI_assert(!tree_loaded_); + BLI_assert(meta_data_loaded_); + grid_->setTree(loaded_grid->baseTreePtr()); + if (!transform_loaded_) { + grid_->setTransform(loaded_grid->transformPtr()); + } + } + else { + grid_ = std::move(loaded_grid); + } + + BLI_assert(tree_sharing_info_ == nullptr); + tree_sharing_info_ = MEM_new(__func__, grid_->baseTreePtr()); + + tree_loaded_ = true; + transform_loaded_ = true; + meta_data_loaded_ = true; +} + +GVolumeGrid::GVolumeGrid(std::shared_ptr grid) +{ + data_ = ImplicitSharingPtr(MEM_new(__func__, std::move(grid))); +} + +GVolumeGrid::GVolumeGrid(const VolumeGridType grid_type) + : GVolumeGrid(create_grid_for_type(grid_type)) +{ +} + +VolumeGridData &GVolumeGrid::get_for_write() +{ + BLI_assert(*this); + if (data_->is_mutable()) { + data_->tag_ensured_mutable(); + } + else { + *this = data_->copy(); + } + return const_cast(*data_); +} + +VolumeGridType get_type(const openvdb::GridBase &grid) +{ + if (grid.isType()) { + return VOLUME_GRID_FLOAT; + } + if (grid.isType()) { + return VOLUME_GRID_VECTOR_FLOAT; + } + if (grid.isType()) { + return VOLUME_GRID_BOOLEAN; + } + if (grid.isType()) { + return VOLUME_GRID_DOUBLE; + } + if (grid.isType()) { + return VOLUME_GRID_INT; + } + if (grid.isType()) { + return VOLUME_GRID_INT64; + } + if (grid.isType()) { + return VOLUME_GRID_VECTOR_INT; + } + if (grid.isType()) { + return VOLUME_GRID_VECTOR_DOUBLE; + } + if (grid.isType()) { + return VOLUME_GRID_MASK; + } + if (grid.isType()) { + return VOLUME_GRID_POINTS; + } + return VOLUME_GRID_UNKNOWN; +} + +#endif /* WITH_OPENVDB */ + +std::string get_name(const VolumeGridData &volume_grid) +{ +#ifdef WITH_OPENVDB + return volume_grid.name(); +#else + UNUSED_VARS(volume_grid); + return "density"; +#endif +} + +VolumeGridType get_type(const VolumeGridData &volume_grid) +{ +#ifdef WITH_OPENVDB + return volume_grid.grid_type(); +#else + UNUSED_VARS(volume_grid); +#endif + return VOLUME_GRID_UNKNOWN; +} + +int get_channels_num(const VolumeGridType type) +{ + switch (type) { + case VOLUME_GRID_BOOLEAN: + case VOLUME_GRID_FLOAT: + case VOLUME_GRID_DOUBLE: + case VOLUME_GRID_INT: + case VOLUME_GRID_INT64: + case VOLUME_GRID_MASK: + return 1; + case VOLUME_GRID_VECTOR_FLOAT: + case VOLUME_GRID_VECTOR_DOUBLE: + case VOLUME_GRID_VECTOR_INT: + return 3; + case VOLUME_GRID_POINTS: + case VOLUME_GRID_UNKNOWN: + return 0; + } + return 0; +} + +void unload_tree_if_possible(const VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + grid.unload_tree_if_possible(); +#else + UNUSED_VARS(grid); +#endif +} + +float4x4 get_transform_matrix(const VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + const openvdb::math::Transform &transform = grid.transform(); + + /* Perspective not supported for now, getAffineMap() will leave out the + * perspective part of the transform. */ + openvdb::math::Mat4f matrix = transform.baseMap()->getAffineMap()->getMat4(); + /* Blender column-major and OpenVDB right-multiplication conventions match. */ + float4x4 result; + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + result[col][row] = matrix(col, row); + } + } + return result; +#else + UNUSED_VARS(grid); + return float4x4::identity(); +#endif +} + +void set_transform_matrix(VolumeGridData &grid, const float4x4 &matrix) +{ +#ifdef WITH_OPENVDB + openvdb::math::Mat4f matrix_openvdb; + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + matrix_openvdb(col, row) = matrix[col][row]; + } + } + + grid.transform_for_write() = openvdb::math::Transform( + std::make_shared(matrix_openvdb)); +#else + UNUSED_VARS(grid, matrix); +#endif +} + +void clear_tree(VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + VolumeTreeAccessToken access_token = grid.tree_access_token(); + grid.grid_for_write(access_token).clear(); +#else + UNUSED_VARS(grid); +#endif +} + +bool is_loaded(const VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + return grid.is_loaded(); +#else + UNUSED_VARS(grid); + return false; +#endif +} + +void load(const VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + VolumeTreeAccessToken access_token = grid.tree_access_token(); + /* Just "touch" the grid, so that it is loaded. */ + grid.grid(access_token); +#else + UNUSED_VARS(grid); +#endif +} + +std::string error_message_from_load(const VolumeGridData &grid) +{ +#ifdef WITH_OPENVDB + return grid.error_message(); +#else + UNUSED_VARS(grid); + return ""; +#endif +} + +} // namespace blender::bke::volume_grid diff --git a/source/blender/blenkernel/intern/volume_grid_file_cache.cc b/source/blender/blenkernel/intern/volume_grid_file_cache.cc new file mode 100644 index 00000000000..50818e4fbe1 --- /dev/null +++ b/source/blender/blenkernel/intern/volume_grid_file_cache.cc @@ -0,0 +1,229 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB + +# include "BKE_volume_grid_file_cache.hh" +# include "BKE_volume_openvdb.hh" + +# include "BLI_map.hh" + +# include + +namespace blender::bke::volume_grid::file_cache { + +/** + * Cache for a single grid stored in a file. + */ +struct GridCache { + /** + * Grid returned by #readAllGridMetadata. This only contains a the meta-data and transform of + * the grid, but not the tree. + */ + openvdb::GridBase::Ptr meta_data_grid; + /** + * Cached simplify levels. + */ + Map grid_by_simplify_level; +}; + +/** + * Cache for a file that contains potentially multiple grids. + */ +struct FileCache { + /** + * Empty on success, otherwise an error message that was generated when trying to load the file. + */ + std::string error_message; + /** + * Meta data of the file (not of an individual grid). + */ + openvdb::MetaMap meta_data; + /** + * Caches for grids in the same order they are stored in the file. + */ + Vector grids; + + GridCache *grid_cache_by_name(const StringRef name) + { + for (GridCache &grid_cache : this->grids) { + if (grid_cache.meta_data_grid->getName() == name) { + return &grid_cache; + } + } + return nullptr; + } +}; + +/** + * Singleton cache that's shared throughout the application. + */ +struct GlobalCache { + std::mutex mutex; + Map file_map; +}; + +/** + * Uses the "construct on first use" idiom to get the cache. + */ +static GlobalCache &get_global_cache() +{ + static GlobalCache global_cache; + return global_cache; +} + +/** + * Tries to load the file at the given path and creates a cache for it. This only reads meta-data, + * but not the actual trees, which will be loaded on-demand. + */ +static FileCache create_file_cache(const StringRef file_path) +{ + FileCache file_cache; + + openvdb::io::File file(file_path); + openvdb::GridPtrVec vdb_grids; + try { + /* Disable delay loading and file copying, this has poor performance + * on network drives. */ + const bool delay_load = false; + file.setCopyMaxBytes(0); + file.open(delay_load); + vdb_grids = *(file.readAllGridMetadata()); + file_cache.meta_data = *file.getMetadata(); + } + catch (const openvdb::IoError &e) { + file_cache.error_message = e.what(); + } + catch (...) { + file_cache.error_message = "Unknown error reading VDB file"; + } + if (!file_cache.error_message.empty()) { + return file_cache; + } + + for (openvdb::GridBase::Ptr &vdb_grid : vdb_grids) { + if (!vdb_grid) { + continue; + } + GridCache grid_cache; + grid_cache.meta_data_grid = vdb_grid; + file_cache.grids.append(std::move(grid_cache)); + } + + return file_cache; +} + +static FileCache &get_file_cache(const StringRef file_path) +{ + GlobalCache &global_cache = get_global_cache(); + /* Assumes that the cache is locked already. */ + BLI_assert(!global_cache.mutex.try_lock()); + return global_cache.file_map.lookup_or_add_cb_as(file_path, + [&]() { return create_file_cache(file_path); }); +} + +/** + * Load a single grid by name from a file. This loads the full grid including meta-data, transforms + * and the tree. + */ +static openvdb::GridBase::Ptr load_single_grid_from_disk(const StringRef file_path, + const StringRef grid_name) +{ + /* Disable delay loading and file copying, this has poor performance + * on network drivers. */ + const bool delay_load = false; + + openvdb::io::File file(file_path); + file.setCopyMaxBytes(0); + file.open(delay_load); + return file.readGrid(grid_name); +} + +/** + * Checks if there is already a cached grid for the parameters and creates it otherwise. This does + * not load the tree, because that is done on-demand. + */ +static GVolumeGrid get_cached_grid(const StringRef file_path, + GridCache &grid_cache, + const int simplify_level) +{ + if (GVolumeGrid *grid = grid_cache.grid_by_simplify_level.lookup_ptr(simplify_level)) { + return *grid; + } + /* A callback that actually loads the full grid including the tree when it's accessed. */ + auto load_grid_fn = [file_path = std::string(file_path), + grid_name = std::string(grid_cache.meta_data_grid->getName()), + simplify_level]() { + if (simplify_level == 0) { + return load_single_grid_from_disk(file_path, grid_name); + } + /* Build the simplified grid from the main grid. */ + const GVolumeGrid main_grid = get_grid_from_file(file_path, grid_name, 0); + const VolumeGridType grid_type = main_grid->grid_type(); + const float resolution_factor = 1.0f / (1 << simplify_level); + const VolumeTreeAccessToken access_token = main_grid->tree_access_token(); + return BKE_volume_grid_create_with_changed_resolution( + grid_type, main_grid->grid(access_token), resolution_factor); + }; + /* This allows the returned grid to already contain meta-data and transforms, even if the tree is + * not loaded yet. */ + openvdb::GridBase::Ptr meta_data_and_transform_grid; + if (simplify_level == 0) { + /* Only pass the meta-data grid when there is no simplification for now. For simplified grids, + * the transform would have to be updated here already. */ + meta_data_and_transform_grid = grid_cache.meta_data_grid->copyGrid(); + } + VolumeGridData *grid_data = MEM_new( + __func__, load_grid_fn, meta_data_and_transform_grid); + GVolumeGrid grid{grid_data}; + grid_cache.grid_by_simplify_level.add(simplify_level, grid); + return grid; +} + +GVolumeGrid get_grid_from_file(const StringRef file_path, + const StringRef grid_name, + const int simplify_level) +{ + GlobalCache &global_cache = get_global_cache(); + std::lock_guard lock{global_cache.mutex}; + FileCache &file_cache = get_file_cache(file_path); + if (GridCache *grid_cache = file_cache.grid_cache_by_name(grid_name)) { + return get_cached_grid(file_path, *grid_cache, simplify_level); + } + return {}; +} + +GridsFromFile get_all_grids_from_file(const StringRef file_path, const int simplify_level) +{ + GridsFromFile result; + GlobalCache &global_cache = get_global_cache(); + std::lock_guard lock{global_cache.mutex}; + FileCache &file_cache = get_file_cache(file_path); + + if (!file_cache.error_message.empty()) { + result.error_message = file_cache.error_message; + return result; + } + result.file_meta_data = std::make_shared(file_cache.meta_data); + for (GridCache &grid_cache : file_cache.grids) { + result.grids.append(get_cached_grid(file_path, grid_cache, simplify_level)); + } + return result; +} + +void unload_unused() +{ + GlobalCache &global_cache = get_global_cache(); + std::lock_guard lock{global_cache.mutex}; + for (FileCache &file_cache : global_cache.file_map.values()) { + for (GridCache &grid_cache : file_cache.grids) { + grid_cache.grid_by_simplify_level.remove_if( + [&](const auto &item) { return item.value->is_mutable(); }); + } + } +} + +} // namespace blender::bke::volume_grid::file_cache + +#endif /* WITH_OPENVDB */ diff --git a/source/blender/blenkernel/intern/volume_render.cc b/source/blender/blenkernel/intern/volume_render.cc index 7bf0ae98224..8f0738402f7 100644 --- a/source/blender/blenkernel/intern/volume_render.cc +++ b/source/blender/blenkernel/intern/volume_render.cc @@ -17,6 +17,7 @@ #include "DNA_volume_types.h" #include "BKE_volume.hh" +#include "BKE_volume_grid.hh" #include "BKE_volume_openvdb.hh" #include "BKE_volume_render.hh" @@ -92,14 +93,15 @@ static void create_texture_to_object_matrix(const openvdb::Mat4d &grid_transform #endif bool BKE_volume_grid_dense_floats(const Volume *volume, - const VolumeGrid *volume_grid, + const blender::bke::VolumeGridData *volume_grid, DenseFloatVolumeGrid *r_dense_grid) { #ifdef WITH_OPENVDB - const VolumeGridType grid_type = BKE_volume_grid_type(volume_grid); - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + const VolumeGridType grid_type = volume_grid->grid_type(); + blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + const openvdb::GridBase &grid = volume_grid->grid(access_token); - const openvdb::CoordBBox bbox = grid->evalActiveVoxelBoundingBox(); + const openvdb::CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); if (bbox.empty()) { return false; } @@ -107,15 +109,15 @@ bool BKE_volume_grid_dense_floats(const Volume *volume, const openvdb::Vec3i resolution = bbox.dim().asVec3i(); const int64_t num_voxels = int64_t(resolution[0]) * int64_t(resolution[1]) * int64_t(resolution[2]); - const int channels = BKE_volume_grid_channels(volume_grid); + const int channels = blender::bke::volume_grid::get_channels_num(grid_type); const int elem_size = sizeof(float) * channels; float *voxels = static_cast(MEM_malloc_arrayN(num_voxels, elem_size, __func__)); if (voxels == nullptr) { return false; } - extract_dense_float_voxels(grid_type, *grid, bbox, voxels); - create_texture_to_object_matrix(grid->transform().baseMap()->getAffineMap()->getMat4(), + extract_dense_float_voxels(grid_type, grid, bbox, voxels); + create_texture_to_object_matrix(grid.transform().baseMap()->getAffineMap()->getMat4(), bbox, r_dense_grid->texture_to_object); @@ -321,7 +323,7 @@ static void boxes_to_cube_mesh(blender::Span boxes, #endif void BKE_volume_grid_wireframe(const Volume *volume, - const VolumeGrid *volume_grid, + const blender::bke::VolumeGridData *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata) { @@ -331,15 +333,16 @@ void BKE_volume_grid_wireframe(const Volume *volume, } #ifdef WITH_OPENVDB - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + const openvdb::GridBase &grid = volume_grid->grid(access_token); if (volume->display.wireframe_type == VOLUME_WIREFRAME_BOUNDS) { /* Bounding box. */ openvdb::CoordBBox box; blender::Vector verts; blender::Vector> edges; - if (grid->baseTree().evalLeafBoundingBox(box)) { - boxes_to_edge_mesh({box}, grid->transform(), verts, edges); + if (grid.baseTree().evalLeafBoundingBox(box)) { + boxes_to_edge_mesh({box}, grid.transform(), verts, edges); } cb(cb_userdata, (float(*)[3])verts.data(), @@ -349,8 +352,8 @@ void BKE_volume_grid_wireframe(const Volume *volume, } else { blender::Vector boxes = get_bounding_boxes( - BKE_volume_grid_type(volume_grid), - *grid, + volume_grid->grid_type(), + grid, volume->display.wireframe_detail == VOLUME_WIREFRAME_COARSE); blender::Vector verts; @@ -358,10 +361,10 @@ void BKE_volume_grid_wireframe(const Volume *volume, if (volume->display.wireframe_type == VOLUME_WIREFRAME_POINTS) { verts.resize(boxes.size()); - boxes_to_center_points(boxes, grid->transform(), verts); + boxes_to_center_points(boxes, grid.transform(), verts); } else { - boxes_to_edge_mesh(boxes, grid->transform(), verts, edges); + boxes_to_edge_mesh(boxes, grid.transform(), verts, edges); } cb(cb_userdata, @@ -403,19 +406,20 @@ static void grow_triangles(blender::MutableSpan verts, } #endif /* WITH_OPENVDB */ -void BKE_volume_grid_selection_surface(const Volume *volume, - const VolumeGrid *volume_grid, +void BKE_volume_grid_selection_surface(const Volume * /*volume*/, + const blender::bke::VolumeGridData *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata) { #ifdef WITH_OPENVDB - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); + blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + const openvdb::GridBase &grid = volume_grid->grid(access_token); blender::Vector boxes = get_bounding_boxes( - BKE_volume_grid_type(volume_grid), *grid, true); + volume_grid->grid_type(), grid, true); blender::Vector verts; blender::Vector> tris; - boxes_to_cube_mesh(boxes, grid->transform(), verts, tris); + boxes_to_cube_mesh(boxes, grid.transform(), verts, tris); /* By slightly scaling the individual boxes up, we can avoid some artifacts when drawing the * selection outline. */ @@ -424,7 +428,7 @@ void BKE_volume_grid_selection_surface(const Volume *volume, cb(cb_userdata, (float(*)[3])verts.data(), (int(*)[3])tris.data(), verts.size(), tris.size()); #else - UNUSED_VARS(volume, volume_grid); + UNUSED_VARS(volume_grid); cb(cb_userdata, nullptr, nullptr, 0, 0); #endif } diff --git a/source/blender/blenkernel/intern/volume_test.cc b/source/blender/blenkernel/intern/volume_test.cc new file mode 100644 index 00000000000..6d637739fb9 --- /dev/null +++ b/source/blender/blenkernel/intern/volume_test.cc @@ -0,0 +1,133 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: Apache-2.0 */ + +#ifdef WITH_OPENVDB + +# include "openvdb/openvdb.h" + +# include "testing/testing.h" + +# include "DNA_volume_types.h" + +# include "BKE_idtype.h" +# include "BKE_lib_id.h" +# include "BKE_main.hh" +# include "BKE_volume.hh" +# include "BKE_volume_grid.hh" + +namespace blender::bke::tests { + +class VolumeTest : public ::testing::Test { + public: + Main *bmain; + + static void SetUpTestSuite() + { + BKE_idtype_init(); + } + + static void TearDownTestSuite() {} + + void SetUp() override + { + bmain = BKE_main_new(); + } + + void TearDown() override + { + BKE_main_free(bmain); + } +}; + +TEST_F(VolumeTest, add_grid_with_name_and_find) +{ + Volume *volume = static_cast(BKE_id_new(bmain, ID_VO, nullptr)); + GVolumeGrid grid{VOLUME_GRID_FLOAT}; + grid.get_for_write().set_name("My Grid"); + const VolumeGridData *grid_data = grid.release(); + BKE_volume_grid_add(volume, *grid_data); + EXPECT_EQ(grid_data, BKE_volume_grid_find(volume, "My Grid")); + EXPECT_TRUE(grid_data->is_mutable()); + BKE_id_free(bmain, volume); +} + +TEST_F(VolumeTest, add_grid_in_two_volumes) +{ + Volume *volume_a = static_cast(BKE_id_new(bmain, ID_VO, nullptr)); + Volume *volume_b = static_cast(BKE_id_new(bmain, ID_VO, nullptr)); + GVolumeGrid grid{VOLUME_GRID_FLOAT}; + grid.get_for_write().set_name("My Grid"); + const VolumeGridData *grid_data = grid.release(); + BKE_volume_grid_add(volume_a, *grid_data); + EXPECT_TRUE(grid_data->is_mutable()); + grid_data->add_user(); + BKE_volume_grid_add(volume_b, *grid_data); + EXPECT_FALSE(grid_data->is_mutable()); + + VolumeGridData *grid_from_a = BKE_volume_grid_get_for_write(volume_a, 0); + const VolumeGridData *grid_from_b = BKE_volume_grid_get(volume_b, 0); + EXPECT_NE(grid_data, grid_from_a); + EXPECT_TRUE(grid_from_a->is_mutable()); + EXPECT_TRUE(grid_from_b->is_mutable()); + + BKE_id_free(bmain, volume_a); + BKE_id_free(bmain, volume_b); +} + +TEST_F(VolumeTest, lazy_load_grid) +{ + int load_counter = 0; + auto load_grid = [&]() { + load_counter++; + return openvdb::FloatGrid::create(10.0f); + }; + VolumeGrid volume_grid{MEM_new(__func__, load_grid)}; + EXPECT_EQ(load_counter, 0); + EXPECT_FALSE(volume_grid->is_loaded()); + VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f); + EXPECT_EQ(load_counter, 1); + EXPECT_TRUE(volume_grid->is_loaded()); + EXPECT_TRUE(volume_grid->is_reloadable()); + EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f); + EXPECT_EQ(load_counter, 1); + volume_grid->unload_tree_if_possible(); + EXPECT_TRUE(volume_grid->is_loaded()); + access_token.reset(); + volume_grid->unload_tree_if_possible(); + EXPECT_FALSE(volume_grid->is_loaded()); + access_token = volume_grid->tree_access_token(); + EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f); + EXPECT_TRUE(volume_grid->is_loaded()); + EXPECT_EQ(load_counter, 2); + volume_grid.grid_for_write(access_token).getAccessor().setValue({0, 0, 0}, 1.0f); + EXPECT_EQ(volume_grid.grid(access_token).getAccessor().getValue({0, 0, 0}), 1.0f); + EXPECT_FALSE(volume_grid->is_reloadable()); +} + +TEST_F(VolumeTest, lazy_load_tree_only) +{ + bool load_run = false; + auto load_grid = [&]() { + load_run = true; + return openvdb::FloatGrid::create(10.0f); + }; + VolumeGrid volume_grid{ + MEM_new(__func__, load_grid, openvdb::FloatGrid::create(0.0f))}; + EXPECT_FALSE(volume_grid->is_loaded()); + EXPECT_EQ(volume_grid->name(), ""); + EXPECT_FALSE(load_run); + volume_grid.get_for_write().set_name("Test"); + EXPECT_FALSE(load_run); + EXPECT_EQ(volume_grid->name(), "Test"); + VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + volume_grid.grid_for_write(access_token); + EXPECT_TRUE(load_run); + EXPECT_EQ(volume_grid->name(), "Test"); + EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f); +} + +} // namespace blender::bke::tests + +#endif /* WITH_OPENVDB */ diff --git a/source/blender/blenkernel/intern/volume_to_mesh.cc b/source/blender/blenkernel/intern/volume_to_mesh.cc index 0058f7a09cb..40690c0b3b8 100644 --- a/source/blender/blenkernel/intern/volume_to_mesh.cc +++ b/source/blender/blenkernel/intern/volume_to_mesh.cc @@ -14,6 +14,7 @@ #include "BKE_mesh.hh" #include "BKE_volume.hh" +#include "BKE_volume_grid.hh" #include "BKE_volume_openvdb.hh" #ifdef WITH_OPENVDB @@ -149,7 +150,7 @@ bke::OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid, const float threshold, const float adaptivity) { - const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid); + const VolumeGridType grid_type = bke::volume_grid::get_type(grid); VolumeToMeshOp to_mesh_op{grid, resolution, threshold, adaptivity}; if (!BKE_volume_grid_type_operation(grid_type, to_mesh_op)) { diff --git a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh index 6a7fe34d592..ad265f8476d 100644 --- a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh +++ b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh @@ -93,7 +93,7 @@ template class ImplicitSharingPtr { const T *release() { - T *data = data_; + const T *data = data_; data_ = nullptr; return data; } diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index c38bf3e3466..e32a183d678 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -914,6 +914,20 @@ if(WITH_OPENSUBDIV) ) endif() +if(WITH_OPENVDB) + list(APPEND INC + ../../../intern/openvdb + ) + list(APPEND INC_SYS + ${OPENVDB_INCLUDE_DIRS} + ) + list(APPEND LIB + bf_intern_openvdb + ${OPENVDB_LIBRARIES} + ) + add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS}) +endif() + if(WITH_MOD_FLUID) list(APPEND INC ../../../intern/mantaflow/extern diff --git a/source/blender/draw/engines/workbench/workbench_volume.cc b/source/blender/draw/engines/workbench/workbench_volume.cc index 3bec73c0455..01944449c2b 100644 --- a/source/blender/draw/engines/workbench/workbench_volume.cc +++ b/source/blender/draw/engines/workbench/workbench_volume.cc @@ -45,7 +45,7 @@ void VolumePass::object_sync_volume(Manager &manager, /* Create 3D textures. */ Volume *volume = static_cast(ob->data); BKE_volume_load(volume, G.main); - const VolumeGrid *volume_grid = BKE_volume_grid_active_get_for_read(volume); + const bke::VolumeGridData *volume_grid = BKE_volume_grid_active_get_for_read(volume); if (volume_grid == nullptr) { return; } diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index ace1577dd2f..ba18e912708 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -8,6 +8,8 @@ #pragma once +#include "BKE_volume_grid_fwd.hh" + #ifdef __cplusplus extern "C" { #endif @@ -20,7 +22,6 @@ struct Object; struct PTCacheEdit; struct ParticleSystem; struct Volume; -struct VolumeGrid; struct bGPDstroke; struct bGPdata; struct Scene; @@ -251,7 +252,7 @@ typedef struct DRWVolumeGrid { } DRWVolumeGrid; DRWVolumeGrid *DRW_volume_batch_cache_get_grid(struct Volume *volume, - const struct VolumeGrid *grid); + const blender::bke::VolumeGridData *grid); struct GPUBatch *DRW_cache_volume_face_wireframe_get(struct Object *ob); struct GPUBatch *DRW_cache_volume_selection_surface_get(struct Object *ob); diff --git a/source/blender/draw/intern/draw_cache_impl_volume.cc b/source/blender/draw/intern/draw_cache_impl_volume.cc index eb84cebd78e..b26b91cb7b9 100644 --- a/source/blender/draw/intern/draw_cache_impl_volume.cc +++ b/source/blender/draw/intern/draw_cache_impl_volume.cc @@ -22,6 +22,7 @@ #include "BKE_global.h" #include "BKE_volume.hh" +#include "BKE_volume_grid_fwd.hh" #include "BKE_volume_render.hh" #include "GPU_batch.h" @@ -210,7 +211,7 @@ GPUBatch *DRW_volume_batch_cache_get_wireframes_face(Volume *volume) VolumeBatchCache *cache = volume_batch_cache_get(volume); if (cache->face_wire.batch == nullptr) { - const VolumeGrid *volume_grid = BKE_volume_grid_active_get_for_read(volume); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_active_get_for_read(volume); if (volume_grid == nullptr) { return nullptr; } @@ -259,7 +260,7 @@ GPUBatch *DRW_volume_batch_cache_get_selection_surface(Volume *volume) { VolumeBatchCache *cache = volume_batch_cache_get(volume); if (cache->selection_surface == nullptr) { - const VolumeGrid *volume_grid = BKE_volume_grid_active_get_for_read(volume); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_active_get_for_read(volume); if (volume_grid == nullptr) { return nullptr; } @@ -270,21 +271,21 @@ GPUBatch *DRW_volume_batch_cache_get_selection_surface(Volume *volume) } static DRWVolumeGrid *volume_grid_cache_get(const Volume *volume, - const VolumeGrid *grid, + const blender::bke::VolumeGridData *grid, VolumeBatchCache *cache) { - const char *name = BKE_volume_grid_name(grid); + const std::string name = blender::bke::volume_grid::get_name(*grid); /* Return cached grid. */ LISTBASE_FOREACH (DRWVolumeGrid *, cache_grid, &cache->grids) { - if (STREQ(cache_grid->name, name)) { + if (cache_grid->name == name) { return cache_grid; } } /* Allocate new grid. */ DRWVolumeGrid *cache_grid = MEM_cnew(__func__); - cache_grid->name = BLI_strdup(name); + cache_grid->name = BLI_strdup(name.c_str()); BLI_addtail(&cache->grids, cache_grid); /* TODO: can we load this earlier, avoid accessing the global and take @@ -292,14 +293,13 @@ static DRWVolumeGrid *volume_grid_cache_get(const Volume *volume, BKE_volume_load(volume, G.main); /* Test if we support textures with the number of channels. */ - size_t channels = BKE_volume_grid_channels(grid); + size_t channels = blender::bke::volume_grid::get_channels_num( + blender::bke::volume_grid::get_type(*grid)); if (!ELEM(channels, 1, 3)) { return cache_grid; } - /* Remember if grid was loaded. If it was not, we want to unload it after the GPUTexture has been - * created. */ - const bool was_loaded = BKE_volume_grid_is_loaded(grid); + const bool was_loaded = blender::bke::volume_grid::is_loaded(*grid); DenseFloatVolumeGrid dense_grid; if (BKE_volume_grid_dense_floats(volume, grid, &dense_grid)) { @@ -329,13 +329,14 @@ static DRWVolumeGrid *volume_grid_cache_get(const Volume *volume, /* Free grid from memory if it wasn't previously loaded. */ if (!was_loaded) { - BKE_volume_grid_unload(volume, grid); + blender::bke::volume_grid::unload_tree_if_possible(*grid); } return cache_grid; } -DRWVolumeGrid *DRW_volume_batch_cache_get_grid(Volume *volume, const VolumeGrid *volume_grid) +DRWVolumeGrid *DRW_volume_batch_cache_get_grid(Volume *volume, + const blender::bke::VolumeGridData *volume_grid) { VolumeBatchCache *cache = volume_batch_cache_get(volume); DRWVolumeGrid *grid = volume_grid_cache_get(volume, volume_grid, cache); diff --git a/source/blender/draw/intern/draw_manager_data.cc b/source/blender/draw/intern/draw_manager_data.cc index 9e4138460ca..68941ae5f27 100644 --- a/source/blender/draw/intern/draw_manager_data.cc +++ b/source/blender/draw/intern/draw_manager_data.cc @@ -644,11 +644,15 @@ static void drw_call_calc_orco(const Object *ob, float (*r_orcofacs)[4]) switch (GS(ob_data->name)) { case ID_VO: { const Volume &volume = *reinterpret_cast(ob_data); - const blender::Bounds bounds = *BKE_volume_min_max(&volume); - mid_v3_v3v3(static_buf.texspace_location, bounds.max, bounds.min); - sub_v3_v3v3(static_buf.texspace_size, bounds.max, bounds.min); + const std::optional> bounds = BKE_volume_min_max(&volume); + if (bounds) { + mid_v3_v3v3(static_buf.texspace_location, bounds->max, bounds->min); + sub_v3_v3v3(static_buf.texspace_size, bounds->max, bounds->min); + } + static_buf.texspace_size[0] = std::max(static_buf.texspace_size[0], 0.001f); + static_buf.texspace_size[1] = std::max(static_buf.texspace_size[1], 0.001f); + static_buf.texspace_size[2] = std::max(static_buf.texspace_size[2], 0.001f); texspace_location = static_buf.texspace_location; - texspace_size = static_buf.texspace_size; break; } case ID_ME: diff --git a/source/blender/draw/intern/draw_resource.hh b/source/blender/draw/intern/draw_resource.hh index 54b3cf378f8..56520094adc 100644 --- a/source/blender/draw/intern/draw_resource.hh +++ b/source/blender/draw/intern/draw_resource.hh @@ -103,10 +103,12 @@ inline void ObjectInfos::sync(const blender::draw::ObjectRef ref, bool is_active switch (GS(reinterpret_cast(ref.object->data)->name)) { case ID_VO: { - const blender::Bounds bounds = *BKE_volume_min_max( + std::optional> bounds = BKE_volume_min_max( static_cast(ref.object->data)); - orco_add = blender::math::midpoint(bounds.min, bounds.max); - orco_mul = (bounds.max - bounds.min) * 0.5f; + if (bounds) { + orco_add = blender::math::midpoint(bounds->min, bounds->max); + orco_mul = (bounds->max - bounds->min) * 0.5f; + } break; } case ID_ME: { diff --git a/source/blender/draw/intern/draw_volume.cc b/source/blender/draw/intern/draw_volume.cc index 9967e7cc986..321da982e9e 100644 --- a/source/blender/draw/intern/draw_volume.cc +++ b/source/blender/draw/intern/draw_volume.cc @@ -137,7 +137,7 @@ static DRWShadingGroup *drw_volume_object_grids_init(Object *ob, /* Bind volume grid textures. */ int grid_id = 0, grids_len = 0; LISTBASE_FOREACH (GPUMaterialAttribute *, attr, attrs) { - const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, attr->name); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_find(volume, attr->name); const DRWVolumeGrid *drw_grid = (volume_grid) ? DRW_volume_batch_cache_get_grid(volume, volume_grid) : nullptr; @@ -312,7 +312,7 @@ PassType *volume_object_grids_init(PassType &ps, bool has_grids = false; for (const GPUMaterialAttribute *attr : attrs) { - if (BKE_volume_grid_find_for_read(volume, attr->name) != nullptr) { + if (BKE_volume_grid_find(volume, attr->name) != nullptr) { has_grids = true; break; } @@ -328,7 +328,7 @@ PassType *volume_object_grids_init(PassType &ps, /* Bind volume grid textures. */ int grid_id = 0; for (const GPUMaterialAttribute *attr : attrs) { - const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, attr->name); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_find(volume, attr->name); const DRWVolumeGrid *drw_grid = (volume_grid) ? DRW_volume_batch_cache_get_grid(volume, volume_grid) : nullptr; diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index f1d25b7e929..7b85ca33ee3 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -73,6 +73,20 @@ if(WITH_OPENSUBDIV) add_definitions(-DWITH_OPENSUBDIV) endif() +if(WITH_OPENVDB) + list(APPEND INC + ../../../../intern/openvdb + ) + list(APPEND INC_SYS + ${OPENVDB_INCLUDE_DIRS} + ) + list(APPEND LIB + bf_intern_openvdb + ${OPENVDB_LIBRARIES} + ) + add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS}) +endif() + if(WITH_TBB) add_definitions(-DWITH_TBB) if(WIN32) diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index dba272f8698..8b1cfbb2b6b 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -57,6 +57,9 @@ set(LIB ) if(WITH_OPENVDB) + list(APPEND INC + ../../../../intern/openvdb + ) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} ) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 0000883244c..014d07f8fd9 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -22,7 +22,7 @@ #include "BKE_node_socket_value.hh" #include "BKE_object_types.hh" #include "BKE_volume.hh" -#include "BKE_volume_openvdb.hh" +#include "BKE_volume_grid.hh" #include "DNA_ID.h" #include "DNA_mesh_types.h" @@ -489,15 +489,15 @@ std::unique_ptr VolumeDataSource::get_column_values( if (STREQ(column_id.name, "Grid Name")) { return std::make_unique( IFACE_("Grid Name"), VArray::ForFunc(size, [volume](int64_t index) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index); - return BKE_volume_grid_name(volume_grid); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, index); + return volume_grid->name(); })); } if (STREQ(column_id.name, "Data Type")) { return std::make_unique( IFACE_("Data Type"), VArray::ForFunc(size, [volume](int64_t index) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index); - const VolumeGridType type = BKE_volume_grid_type(volume_grid); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, index); + const VolumeGridType type = volume_grid->grid_type(); const char *name = nullptr; RNA_enum_name_from_value(rna_enum_volume_grid_data_type_items, type, &name); return IFACE_(name); @@ -506,9 +506,8 @@ std::unique_ptr VolumeDataSource::get_column_values( if (STREQ(column_id.name, "Class")) { return std::make_unique( IFACE_("Class"), VArray::ForFunc(size, [volume](int64_t index) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index); - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); - openvdb::GridClass grid_class = grid->getGridClass(); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, index); + openvdb::GridClass grid_class = volume_grid->grid_class(); if (grid_class == openvdb::GridClass::GRID_FOG_VOLUME) { return IFACE_("Fog Volume"); } diff --git a/source/blender/geometry/GEO_mesh_to_volume.hh b/source/blender/geometry/GEO_mesh_to_volume.hh index 7ac7bb76496..c35a88e0924 100644 --- a/source/blender/geometry/GEO_mesh_to_volume.hh +++ b/source/blender/geometry/GEO_mesh_to_volume.hh @@ -11,10 +11,11 @@ #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" +#include "BKE_volume_grid_fwd.hh" + #pragma once struct Volume; -struct VolumeGrid; struct Depsgraph; /** \file @@ -45,17 +46,17 @@ float volume_compute_voxel_size(const Depsgraph *depsgraph, /** * Add a new fog VolumeGrid to the Volume by converting the supplied mesh. */ -VolumeGrid *fog_volume_grid_add_from_mesh(Volume *volume, - StringRefNull name, - const Mesh *mesh, - const float4x4 &mesh_to_volume_space_transform, - float voxel_size, - float interior_band_width, - float density); +bke::VolumeGridData *fog_volume_grid_add_from_mesh(Volume *volume, + StringRefNull name, + const Mesh *mesh, + const float4x4 &mesh_to_volume_space_transform, + float voxel_size, + float interior_band_width, + float density); /** * Add a new SDF VolumeGrid to the Volume by converting the supplied mesh. */ -VolumeGrid *sdf_volume_grid_add_from_mesh( +bke::VolumeGridData *sdf_volume_grid_add_from_mesh( Volume *volume, StringRefNull name, const Mesh &mesh, float voxel_size, float half_band_width); #endif } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_points_to_volume.hh b/source/blender/geometry/GEO_points_to_volume.hh index c041762e17c..89b3a101f91 100644 --- a/source/blender/geometry/GEO_points_to_volume.hh +++ b/source/blender/geometry/GEO_points_to_volume.hh @@ -8,10 +8,11 @@ #include "DNA_modifier_types.h" +#include "BKE_volume_grid_fwd.hh" + #pragma once struct Volume; -struct VolumeGrid; /** \file * \ingroup geo @@ -24,19 +25,19 @@ namespace blender::geometry { /** * Add a new fog VolumeGrid to the Volume by converting the supplied points. */ -VolumeGrid *fog_volume_grid_add_from_points(Volume *volume, - StringRefNull name, - Span positions, - Span radii, - float voxel_size, - float density); +bke::VolumeGridData *fog_volume_grid_add_from_points(Volume *volume, + StringRefNull name, + Span positions, + Span radii, + float voxel_size, + float density); /** * Add a new SDF VolumeGrid to the Volume by converting the supplied points. */ -VolumeGrid *sdf_volume_grid_add_from_points(Volume *volume, - StringRefNull name, - Span positions, - Span radii, - float voxel_size); +bke::VolumeGridData *sdf_volume_grid_add_from_points(Volume *volume, + StringRefNull name, + Span positions, + Span radii, + float voxel_size); #endif } // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_to_volume.cc b/source/blender/geometry/intern/mesh_to_volume.cc index faa5da19d43..cf137d347b3 100644 --- a/source/blender/geometry/intern/mesh_to_volume.cc +++ b/source/blender/geometry/intern/mesh_to_volume.cc @@ -178,24 +178,24 @@ static openvdb::FloatGrid::Ptr mesh_to_sdf_volume_grid(const Mesh &mesh, return new_grid; } -VolumeGrid *fog_volume_grid_add_from_mesh(Volume *volume, - const StringRefNull name, - const Mesh *mesh, - const float4x4 &mesh_to_volume_space_transform, - const float voxel_size, - const float interior_band_width, - const float density) +bke::VolumeGridData *fog_volume_grid_add_from_mesh(Volume *volume, + const StringRefNull name, + const Mesh *mesh, + const float4x4 &mesh_to_volume_space_transform, + const float voxel_size, + const float interior_band_width, + const float density) { openvdb::FloatGrid::Ptr mesh_grid = mesh_to_fog_volume_grid( mesh, mesh_to_volume_space_transform, voxel_size, interior_band_width, density); return mesh_grid ? BKE_volume_grid_add_vdb(*volume, name, std::move(mesh_grid)) : nullptr; } -VolumeGrid *sdf_volume_grid_add_from_mesh(Volume *volume, - const StringRefNull name, - const Mesh &mesh, - const float voxel_size, - const float half_band_width) +bke::VolumeGridData *sdf_volume_grid_add_from_mesh(Volume *volume, + const StringRefNull name, + const Mesh &mesh, + const float voxel_size, + const float half_band_width) { openvdb::FloatGrid::Ptr mesh_grid = mesh_to_sdf_volume_grid(mesh, voxel_size, half_band_width); return mesh_grid ? BKE_volume_grid_add_vdb(*volume, name, std::move(mesh_grid)) : nullptr; diff --git a/source/blender/geometry/intern/points_to_volume.cc b/source/blender/geometry/intern/points_to_volume.cc index 44495cfb406..9a62d91904a 100644 --- a/source/blender/geometry/intern/points_to_volume.cc +++ b/source/blender/geometry/intern/points_to_volume.cc @@ -59,12 +59,12 @@ static openvdb::FloatGrid::Ptr points_to_sdf_grid(const Span positions, return new_grid; } -VolumeGrid *fog_volume_grid_add_from_points(Volume *volume, - const StringRefNull name, - const Span positions, - const Span radii, - const float voxel_size, - const float density) +bke::VolumeGridData *fog_volume_grid_add_from_points(Volume *volume, + const StringRefNull name, + const Span positions, + const Span radii, + const float voxel_size, + const float density) { openvdb::FloatGrid::Ptr new_grid = points_to_sdf_grid(positions, radii); new_grid->transform().postScale(voxel_size); @@ -83,11 +83,11 @@ VolumeGrid *fog_volume_grid_add_from_points(Volume *volume, return BKE_volume_grid_add_vdb(*volume, name, std::move(new_grid)); } -VolumeGrid *sdf_volume_grid_add_from_points(Volume *volume, - const StringRefNull name, - const Span positions, - const Span radii, - const float voxel_size) +bke::VolumeGridData *sdf_volume_grid_add_from_points(Volume *volume, + const StringRefNull name, + const Span positions, + const Span radii, + const float voxel_size) { openvdb::FloatGrid::Ptr new_grid = points_to_sdf_grid(positions, radii); new_grid->transform().postScale(voxel_size); diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index cc35680b152..c420eeceac6 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -200,6 +200,9 @@ list(APPEND LIB if(WITH_OPENVDB) add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS}) + list(APPEND INC + ../../../../intern/openvdb + ) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} ) diff --git a/source/blender/io/usd/hydra/volume.cc b/source/blender/io/usd/hydra/volume.cc index 2b3d1f14221..c2b60db1568 100644 --- a/source/blender/io/usd/hydra/volume.cc +++ b/source/blender/io/usd/hydra/volume.cc @@ -41,8 +41,8 @@ void VolumeData::init() const int num_grids = BKE_volume_num_grids(volume); if (num_grids) { for (const int i : IndexRange(num_grids)) { - const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); - const std::string grid_name = BKE_volume_grid_name(grid); + const bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i); + const std::string grid_name = bke::volume_grid::get_name(*grid); field_descriptors_.emplace_back(pxr::TfToken(grid_name), pxr::UsdVolImagingTokens->openvdbAsset, diff --git a/source/blender/io/usd/intern/usd_writer_volume.cc b/source/blender/io/usd/intern/usd_writer_volume.cc index 77447277dfc..7a7663b9ffb 100644 --- a/source/blender/io/usd/intern/usd_writer_volume.cc +++ b/source/blender/io/usd/intern/usd_writer_volume.cc @@ -73,8 +73,8 @@ void USDVolumeWriter::do_write(HierarchyContext &context) pxr::UsdVolVolume usd_volume = pxr::UsdVolVolume::Define(stage, volume_path); for (const int i : IndexRange(num_grids)) { - const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i); - const std::string grid_name = BKE_volume_grid_name(grid); + const bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i); + const std::string grid_name = bke::volume_grid::get_name(*grid); const std::string grid_id = pxr::TfMakeValidIdentifier(grid_name); const pxr::SdfPath grid_path = volume_path.AppendPath(pxr::SdfPath(grid_id)); pxr::UsdVolOpenVDBAsset usd_grid = pxr::UsdVolOpenVDBAsset::Define(stage, grid_path); diff --git a/source/blender/makesrna/intern/rna_volume.cc b/source/blender/makesrna/intern/rna_volume.cc index 7ac7706b2ef..c2c7e9e9bd8 100644 --- a/source/blender/makesrna/intern/rna_volume.cc +++ b/source/blender/makesrna/intern/rna_volume.cc @@ -43,6 +43,14 @@ const EnumPropertyItem rna_enum_volume_grid_data_type_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +/** + * Dummy type used as a stand-in for the actual #VolumeGridData class. Generated RNA callbacks need + * a C struct as the main "self" argument. The struct does not have to be an actual DNA struct. + * This dummy struct is used as a placeholder for the callbacks and reinterpreted as the actual + * VolumeGrid type. + */ +struct DummyVolumeGridData; + #ifdef RNA_RUNTIME # include "DEG_depsgraph.hh" @@ -96,48 +104,51 @@ static void rna_Volume_velocity_grid_set(PointerRNA *ptr, const char *value) static void rna_VolumeGrid_name_get(PointerRNA *ptr, char *value) { - VolumeGrid *grid = static_cast(ptr->data); - strcpy(value, BKE_volume_grid_name(grid)); + auto *grid = static_cast(ptr->data); + strcpy(value, blender::bke::volume_grid::get_name(*grid).c_str()); } static int rna_VolumeGrid_name_length(PointerRNA *ptr) { - VolumeGrid *grid = static_cast(ptr->data); - return strlen(BKE_volume_grid_name(grid)); + auto *grid = static_cast(ptr->data); + return blender::bke::volume_grid::get_name(*grid).size(); } static int rna_VolumeGrid_data_type_get(PointerRNA *ptr) { - const VolumeGrid *grid = static_cast(ptr->data); - return BKE_volume_grid_type(grid); + const auto *grid = static_cast(ptr->data); + return blender::bke::volume_grid::get_type(*grid); } static int rna_VolumeGrid_channels_get(PointerRNA *ptr) { - const VolumeGrid *grid = static_cast(ptr->data); - return BKE_volume_grid_channels(grid); + const auto *grid = static_cast(ptr->data); + return blender::bke::volume_grid::get_channels_num(blender::bke::volume_grid::get_type(*grid)); } static void rna_VolumeGrid_matrix_object_get(PointerRNA *ptr, float *value) { - VolumeGrid *grid = static_cast(ptr->data); - BKE_volume_grid_transform_matrix(grid, (float(*)[4])value); + auto *grid = static_cast(ptr->data); + *(blender::float4x4 *)value = blender::bke::volume_grid::get_transform_matrix(*grid); } static bool rna_VolumeGrid_is_loaded_get(PointerRNA *ptr) { - VolumeGrid *grid = static_cast(ptr->data); - return BKE_volume_grid_is_loaded(grid); + auto *grid = static_cast(ptr->data); + return blender::bke::volume_grid::is_loaded(*grid); } -static bool rna_VolumeGrid_load(ID *id, VolumeGrid *grid) +static bool rna_VolumeGrid_load(ID * /*id*/, DummyVolumeGridData *dummy_grid) { - return BKE_volume_grid_load((Volume *)id, grid); + auto *grid = reinterpret_cast(dummy_grid); + blender::bke::volume_grid::load(*grid); + return blender::bke::volume_grid::error_message_from_load(*grid).empty(); } -static void rna_VolumeGrid_unload(ID *id, VolumeGrid *grid) +static void rna_VolumeGrid_unload(ID * /*id*/, DummyVolumeGridData *dummy_grid) { - BKE_volume_grid_unload((Volume *)id, grid); + auto *grid = reinterpret_cast(dummy_grid); + blender::bke::volume_grid::unload_tree_if_possible(*grid); } /* Grids Iterator */ @@ -164,7 +175,8 @@ static void rna_Volume_grids_end(CollectionPropertyIterator * /*iter*/) {} static PointerRNA rna_Volume_grids_get(CollectionPropertyIterator *iter) { Volume *volume = static_cast(iter->internal.count.ptr); - const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, iter->internal.count.item); + const blender::bke::VolumeGridData *grid = BKE_volume_grid_get(volume, + iter->internal.count.item); return rna_pointer_inherit_refine(&iter->parent, &RNA_VolumeGrid, (void *)grid); } @@ -252,6 +264,7 @@ static void rna_def_volume_grid(BlenderRNA *brna) PropertyRNA *prop; srna = RNA_def_struct(brna, "VolumeGrid", nullptr); + RNA_def_struct_sdna(srna, "DummyVolumeGridData"); RNA_def_struct_ui_text(srna, "Volume Grid", "3D volume grid"); RNA_def_struct_ui_icon(srna, ICON_VOLUME_DATA); @@ -285,7 +298,6 @@ static void rna_def_volume_grid(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_boolean_funcs(prop, "rna_VolumeGrid_is_loaded_get", nullptr); RNA_def_property_ui_text(prop, "Is Loaded", "Grid tree is loaded in memory"); - /* API */ FunctionRNA *func; PropertyRNA *parm; diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index 62b9003f45a..9ea43da60ca 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -13,6 +13,7 @@ #include "BKE_object.hh" #include "BKE_texture.h" #include "BKE_volume.hh" +#include "BKE_volume_grid.hh" #include "BKE_volume_openvdb.hh" #include "BLT_translation.h" @@ -286,13 +287,14 @@ static void displace_volume(ModifierData *md, const ModifierEvalContext *ctx, Vo BKE_volume_load(volume, DEG_get_bmain(ctx->depsgraph)); const int grid_amount = BKE_volume_num_grids(volume); for (int grid_index = 0; grid_index < grid_amount; grid_index++) { - VolumeGrid *volume_grid = BKE_volume_grid_get_for_write(volume, grid_index); - BLI_assert(volume_grid != nullptr); + blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get_for_write(volume, grid_index); + BLI_assert(volume_grid); - openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); - VolumeGridType grid_type = BKE_volume_grid_type(volume_grid); + blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + openvdb::GridBase &grid = volume_grid->grid_for_write(access_token); + VolumeGridType grid_type = volume_grid->grid_type(); - DisplaceGridOp displace_grid_op{*grid, *vdmd, *ctx}; + DisplaceGridOp displace_grid_op{grid, *vdmd, *ctx}; BKE_volume_grid_type_operation(grid_type, displace_grid_op); } diff --git a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc index c4986d4cbed..7c9ddc428be 100644 --- a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc +++ b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc @@ -12,7 +12,7 @@ #include "BKE_mesh.hh" #include "BKE_modifier.hh" #include "BKE_volume.hh" -#include "BKE_volume_openvdb.hh" +#include "BKE_volume_grid.hh" #include "BKE_volume_to_mesh.hh" #include "BLT_translation.h" @@ -153,16 +153,16 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh const Volume *volume = static_cast(vmmd->object->data); BKE_volume_load(volume, DEG_get_bmain(ctx->depsgraph)); - const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, vmmd->grid_name); + const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_find(volume, vmmd->grid_name); if (volume_grid == nullptr) { BKE_modifier_set_error(ctx->object, md, "Cannot find '%s' grid", vmmd->grid_name); return create_empty_mesh(input_mesh); } - const openvdb::GridBase::ConstPtr local_grid = BKE_volume_grid_openvdb_for_read(volume, - volume_grid); + const blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + const openvdb::GridBase &local_grid = volume_grid->grid(access_token); - openvdb::math::Transform::Ptr transform = local_grid->transform().copy(); + openvdb::math::Transform::Ptr transform = local_grid.transform().copy(); transform->postMult(openvdb::Mat4d((float *)vmmd->object->object_to_world)); openvdb::Mat4d imat = openvdb::Mat4d((float *)ctx->object->world_to_object); /* `imat` had floating point issues and wasn't affine. */ @@ -170,7 +170,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh transform->postMult(imat); /* Create a temporary transformed grid. The underlying tree is shared. */ - openvdb::GridBase::ConstPtr transformed_grid = local_grid->copyGridReplacingTransform(transform); + openvdb::GridBase::ConstPtr transformed_grid = local_grid.copyGridReplacingTransform(transform); bke::VolumeToMeshResolution resolution; resolution.mode = (VolumeToMeshResolutionMode)vmmd->resolution_mode; diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 2d9335c9c5d..e780d42b027 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -185,6 +185,9 @@ if(WITH_GMP) endif() if(WITH_OPENVDB) + list(APPEND INC + ../../../intern/openvdb + ) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} ) diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 30023a7afd5..aba9cc08926 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -260,6 +260,9 @@ if(WITH_GMP) endif() if(WITH_OPENVDB) + list(APPEND INC + ../../../../intern/openvdb + ) list(APPEND INC_SYS ${OPENVDB_INCLUDE_DIRS} ) diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc index 86cb80c88ba..c4bccec4d5e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_volume.cc @@ -13,7 +13,7 @@ #include "BKE_pointcloud.h" #include "BKE_volume.hh" -#include "BKE_volume_openvdb.hh" +#include "BKE_volume_grid.hh" #include "NOD_rna_define.hh" @@ -220,29 +220,25 @@ static void node_geo_exec(GeoNodeExecParams params) Vector positions; for (const int i : IndexRange(BKE_volume_num_grids(volume))) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); + const bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i); if (volume_grid == nullptr) { continue; } - openvdb::GridBase::ConstPtr base_grid = BKE_volume_grid_openvdb_for_read(volume, - volume_grid); - if (!base_grid) { + bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token(); + const openvdb::GridBase &base_grid = volume_grid->grid(access_token); + + if (!base_grid.isType()) { continue; } - if (!base_grid->isType()) { - continue; - } - - const openvdb::FloatGrid::ConstPtr grid = openvdb::gridConstPtrCast( - base_grid); + const openvdb::FloatGrid &grid = static_cast(base_grid); if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_RANDOM) { - point_scatter_density_random(*grid, density, seed, positions); + point_scatter_density_random(grid, density, seed, positions); } else if (mode == GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME_DENSITY_GRID) { - point_scatter_density_grid(*grid, spacing, threshold, positions); + point_scatter_density_grid(grid, spacing, threshold, positions); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc index c8bd5ed42e6..53f2b3b44f6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc @@ -21,7 +21,6 @@ #include "BKE_mesh.hh" #include "BKE_pointcloud.h" #include "BKE_volume.hh" -#include "BKE_volume_openvdb.hh" #include "DEG_depsgraph_query.hh" @@ -157,15 +156,15 @@ static void transform_volume(GeoNodeExecParams ¶ms, bool found_too_small_scale = false; const int grids_num = BKE_volume_num_grids(&volume); for (const int i : IndexRange(grids_num)) { - VolumeGrid *volume_grid = BKE_volume_grid_get_for_write(&volume, i); - float4x4 grid_matrix; - BKE_volume_grid_transform_matrix(volume_grid, grid_matrix.ptr()); + bke::VolumeGridData *volume_grid = BKE_volume_grid_get_for_write(&volume, i); + + float4x4 grid_matrix = bke::volume_grid::get_transform_matrix(*volume_grid); grid_matrix = transform * grid_matrix; const float determinant = math::determinant(grid_matrix); if (!BKE_volume_grid_determinant_valid(determinant)) { found_too_small_scale = true; /* Clear the tree because it is too small. */ - BKE_volume_grid_clear_tree(volume, *volume_grid); + bke::volume_grid::clear_tree(*volume_grid); if (determinant == 0) { /* Reset rotation and scale. */ grid_matrix.x_axis() = float3(1, 0, 0); @@ -179,7 +178,7 @@ static void transform_volume(GeoNodeExecParams ¶ms, grid_matrix.z_axis() = math::normalize(grid_matrix.z_axis()); } } - BKE_volume_grid_transform_matrix_set(&volume, volume_grid, grid_matrix.ptr()); + bke::volume_grid::set_transform_matrix(*volume_grid, grid_matrix); } if (found_too_small_scale) { params.error_message_add(NodeWarningType::Warning, diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index 732d33537ff..2014f8edde2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -15,7 +15,7 @@ #include "BKE_mesh.hh" #include "BKE_mesh_runtime.hh" #include "BKE_volume.hh" -#include "BKE_volume_openvdb.hh" +#include "BKE_volume_grid.hh" #include "BKE_volume_to_mesh.hh" #include "DNA_mesh_types.h" @@ -105,7 +105,7 @@ static bke::VolumeToMeshResolution get_resolution_param(const GeoNodeExecParams return resolution; } -static Mesh *create_mesh_from_volume_grids(Span grids, +static Mesh *create_mesh_from_volume_grids(Span grids, const float threshold, const float adaptivity, const bke::VolumeToMeshResolution &resolution) @@ -183,11 +183,12 @@ static Mesh *create_mesh_from_volume(GeometrySet &geometry_set, GeoNodeExecParam const Main *bmain = DEG_get_bmain(params.depsgraph()); BKE_volume_load(volume, bmain); - Vector grids; + Vector access_tokens; + Vector grids; for (const int i : IndexRange(BKE_volume_num_grids(volume))) { - const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i); - openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); - grids.append(std::move(grid)); + const bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i); + access_tokens.append(volume_grid->tree_access_token()); + grids.append(&volume_grid->grid(access_tokens.last())); } if (grids.is_empty()) {