From a72e7a220d9fc7821911918bfed7bb09ecfff830 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 20 Dec 2023 15:32:52 +0100 Subject: [PATCH] Volumes: refactor volume grid storage This refactors how volume grids are stored with the following new goals in mind: * Get a **stand-alone volume grid** data structure that can be used by geometry nodes. Previously, the `VolumeGrid` data structure was tightly coupled with the `Volume` data block. * Support **implicit sharing of grids and trees**. Previously, it was possible to share data when multiple `Volume` data blocks loaded grids from the same `.vdb` files but this was not flexible enough. * Get a safe API for **lazy-loading and unloading** of grids without requiring explicit calls to some "load" function all the time. * Get a safe API for **caching grids from files** that is not coupled to the `Volume` data block. * Get a **tiered API** for different levels of `openvdb` involvement: * No `OpenVDB`: Since `WITH_OPENVDB` is optional, it's helpful to have parts of the API that still work in this case. This makes it possible to write high level code for volumes that does not require `#ifdef WITH_OPENVDB` checks everywhere. This is in `BKE_volume_grid_fwd.hh`. * Shallow `OpenVDB`: Code using this API requires `WITH_OPENVDB` checks. However, care is taken to not include the expensive parts of `OpenVDB` and to use forward declarations as much as possible. This is in `BKE_volume_grid.hh` and uses `openvdb_fwd.hh`. * "Full" `OpenVDB`: This API requires more heavy `OpenVDB` includes. Fortunately, it turned out to be not necessary for the common API. So this is only used for task specific APIs. At the core of the new API is the `VolumeGridData` type. It's a wrapper around an `openvdb::Grid` and adds some features on top like implicit sharing, lazy-loading and unloading. Then there are `GVolumeGrid` and `VolumeGrid` which are containers for a volume grid. Semantically, each `VolumeGrid` has its own independent grid, but this is cheap due to implicit sharing. At highest level we currently have the `Volume` data-block which contains a list of `VolumeGrid`. ```mermaid flowchart LR Volume --> VolumeGrid --> VolumeGridData --> openvdb::Grid ``` The loading of `.vdb` files is abstracted away behind the volume file cache API. This API makes it easy to load and reuse entire files and individual grids from disk. It also supports caching simplify levels for grids on disk. An important new concept are the "tree access tokens". Whenever some code wants to work with an openvdb tree, it has to retrieve an access token from the corresponding `VolumeGridData`. This access token has to be kept alive for as long as the code works with the grid data. The same token is valid for read and write access. The purpose of these access tokens is to make it possible to detect when some code is currently working with the openvdb tree. This allows freeing it if it's possible to reload it later on (e.g. from disk). It's possible to free a tree that is referenced by multiple owners, but only no one is actively working with. In some sense, this is similar to the existing `ImageUser` concept. The most important new files to read are `BKE_volume_grid.hh` and `BKE_volume_grid_file_cache.hh`. Most other changes are updates to existing code to use the new API. Pull Request: https://projects.blender.org/blender/blender/pulls/116315 --- intern/cycles/blender/CMakeLists.txt | 3 + intern/cycles/blender/volume.cpp | 25 +- intern/openvdb/CMakeLists.txt | 1 + intern/openvdb/openvdb_fwd.hh | 111 +++ source/blender/blenkernel/BKE_volume.hh | 63 +- source/blender/blenkernel/BKE_volume_enums.hh | 23 + source/blender/blenkernel/BKE_volume_grid.hh | 425 ++++++++++ .../blenkernel/BKE_volume_grid_file_cache.hh | 59 ++ .../blender/blenkernel/BKE_volume_grid_fwd.hh | 119 +++ .../blenkernel/BKE_volume_grid_type_traits.hh | 119 +++ .../blender/blenkernel/BKE_volume_openvdb.hh | 24 +- .../blender/blenkernel/BKE_volume_render.hh | 11 +- source/blender/blenkernel/CMakeLists.txt | 6 + source/blender/blenkernel/intern/volume.cc | 764 ++---------------- .../blender/blenkernel/intern/volume_grid.cc | 509 ++++++++++++ .../intern/volume_grid_file_cache.cc | 229 ++++++ .../blenkernel/intern/volume_render.cc | 46 +- .../blender/blenkernel/intern/volume_test.cc | 133 +++ .../blenkernel/intern/volume_to_mesh.cc | 3 +- .../blenlib/BLI_implicit_sharing_ptr.hh | 2 +- source/blender/draw/CMakeLists.txt | 14 + .../engines/workbench/workbench_volume.cc | 2 +- source/blender/draw/intern/draw_cache.h | 5 +- .../draw/intern/draw_cache_impl_volume.cc | 25 +- .../blender/draw/intern/draw_manager_data.cc | 12 +- source/blender/draw/intern/draw_resource.hh | 8 +- source/blender/draw/intern/draw_volume.cc | 6 +- .../blender/editors/space_node/CMakeLists.txt | 14 + .../editors/space_spreadsheet/CMakeLists.txt | 3 + .../spreadsheet_data_source_geometry.cc | 15 +- source/blender/geometry/GEO_mesh_to_volume.hh | 19 +- .../blender/geometry/GEO_points_to_volume.hh | 25 +- .../blender/geometry/intern/mesh_to_volume.cc | 24 +- .../geometry/intern/points_to_volume.cc | 22 +- source/blender/io/usd/CMakeLists.txt | 3 + source/blender/io/usd/hydra/volume.cc | 4 +- .../io/usd/intern/usd_writer_volume.cc | 4 +- source/blender/makesrna/intern/rna_volume.cc | 48 +- .../modifiers/intern/MOD_volume_displace.cc | 12 +- .../modifiers/intern/MOD_volume_to_mesh.cc | 12 +- source/blender/nodes/CMakeLists.txt | 3 + source/blender/nodes/geometry/CMakeLists.txt | 3 + .../node_geo_distribute_points_in_volume.cc | 22 +- .../nodes/node_geo_transform_geometry.cc | 11 +- .../geometry/nodes/node_geo_volume_to_mesh.cc | 13 +- 45 files changed, 2079 insertions(+), 925 deletions(-) create mode 100644 intern/openvdb/openvdb_fwd.hh create mode 100644 source/blender/blenkernel/BKE_volume_enums.hh create mode 100644 source/blender/blenkernel/BKE_volume_grid.hh create mode 100644 source/blender/blenkernel/BKE_volume_grid_file_cache.hh create mode 100644 source/blender/blenkernel/BKE_volume_grid_fwd.hh create mode 100644 source/blender/blenkernel/BKE_volume_grid_type_traits.hh create mode 100644 source/blender/blenkernel/intern/volume_grid.cc create mode 100644 source/blender/blenkernel/intern/volume_grid_file_cache.cc create mode 100644 source/blender/blenkernel/intern/volume_test.cc 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()) {