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
This commit is contained in:
Jacques Lucke
2023-12-20 15:32:52 +01:00
parent 5f527fdc89
commit a72e7a220d
45 changed files with 2079 additions and 925 deletions

View File

@@ -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}
)

View File

@@ -11,11 +11,8 @@
#include "blender/sync.h"
#include "blender/util.h"
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
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<const blender::bke::VolumeGridData *>(
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,

View File

@@ -11,6 +11,7 @@ set(INC_SYS
set(SRC
openvdb_capi.h
openvdb_fwd.hh
)
set(LIB

View File

@@ -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 <openvdb/Types.h>
#include <openvdb/version.h>
/* -------------------------------------------------------------------- */
/** \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<typename TreeType> class Grid;
namespace math {
class Transform;
}
namespace tree {
class TreeBase;
template<typename T, Index Log2Dim> class LeafNode;
template<typename ChildNodeType, Index Log2Dim> class InternalNode;
template<typename ChildNodeType> class RootNode;
template<typename RootNodeType> class Tree;
/* Forward-declared version of Tree4, can't use the actual Tree4 alias because it can't be
* forward-declared. */
template<typename T, Index N1 = 5, Index N2 = 4, Index N3 = 3> struct Tree4Fwd {
using Type = openvdb::tree::Tree<openvdb::tree::RootNode<
openvdb::tree::InternalNode<openvdb::tree::InternalNode<openvdb::tree::LeafNode<T, N3>, N2>,
N1>>>;
};
} // namespace tree
namespace tools {
template<typename T, Index Log2Dim> struct PointIndexLeafNode;
using PointIndexTree = tree::Tree<tree::RootNode<
tree::InternalNode<tree::InternalNode<PointIndexLeafNode<PointIndex32, 3>, 4>, 5>>>;
using PointIndexGrid = Grid<PointIndexTree>;
} // namespace tools
namespace points {
template<typename T, Index Log2Dim> class PointDataLeafNode;
using PointDataTree = tree::Tree<tree::RootNode<
tree::InternalNode<tree::InternalNode<PointDataLeafNode<PointDataIndex32, 3>, 4>, 5>>>;
using PointDataGrid = Grid<PointDataTree>;
struct NullCodec;
template<typename ValueType, typename Codec> class TypedAttributeArray;
} // namespace points
/// Common tree types
using BoolTree = tree::Tree4Fwd<bool, 5, 4, 3>::Type;
using DoubleTree = tree::Tree4Fwd<double, 5, 4, 3>::Type;
using FloatTree = tree::Tree4Fwd<float, 5, 4, 3>::Type;
using Int8Tree = tree::Tree4Fwd<int8_t, 5, 4, 3>::Type;
using Int32Tree = tree::Tree4Fwd<int32_t, 5, 4, 3>::Type;
using Int64Tree = tree::Tree4Fwd<int64_t, 5, 4, 3>::Type;
using MaskTree = tree::Tree4Fwd<ValueMask, 5, 4, 3>::Type;
using UInt32Tree = tree::Tree4Fwd<uint32_t, 5, 4, 3>::Type;
using Vec2DTree = tree::Tree4Fwd<Vec2d, 5, 4, 3>::Type;
using Vec2ITree = tree::Tree4Fwd<Vec2i, 5, 4, 3>::Type;
using Vec2STree = tree::Tree4Fwd<Vec2s, 5, 4, 3>::Type;
using Vec3DTree = tree::Tree4Fwd<Vec3d, 5, 4, 3>::Type;
using Vec3ITree = tree::Tree4Fwd<Vec3i, 5, 4, 3>::Type;
using Vec3STree = tree::Tree4Fwd<Vec3f, 5, 4, 3>::Type;
using Vec4STree = tree::Tree4Fwd<Vec4f, 5, 4, 3>::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<BoolTree>;
using DoubleGrid = Grid<DoubleTree>;
using FloatGrid = Grid<FloatTree>;
using Int8Grid = Grid<Int8Tree>;
using Int32Grid = Grid<Int32Tree>;
using Int64Grid = Grid<Int64Tree>;
using UInt32Grid = Grid<UInt32Tree>;
using MaskGrid = Grid<MaskTree>;
using Vec3DGrid = Grid<Vec3DTree>;
using Vec2IGrid = Grid<Vec2ITree>;
using Vec3IGrid = Grid<Vec3ITree>;
using Vec2SGrid = Grid<Vec2STree>;
using Vec3SGrid = Grid<Vec3STree>;
using Vec4SGrid = Grid<Vec4STree>;
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
/** \} */

View File

@@ -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<blender::Bounds<blender::float3>> BKE_volume_min_max(const Volume *volume);

View File

@@ -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,
};

View File

@@ -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 <functional>
# include <mutex>
# include <optional>
# include "BKE_volume_enums.hh"
# include "BKE_volume_grid_type_traits.hh"
# include "BLI_implicit_sharing_ptr.hh"
# include "BLI_string_ref.hh"
# include "openvdb_fwd.hh"
namespace blender::bke::volume_grid {
/**
* Main volume grid data structure. It wraps an OpenVDB grid and adds some features on top of it.
*
* A grid contains the following:
* - Transform: The mapping between index and object space. It also determines e.g. the voxel size.
* - Meta-data: Contains e.g. the name and grid class (fog volume or sdf) and potentially other
* data.
* - Tree: This is the heavy data that contains all the voxel values.
*
* Features:
* - Implicit sharing of the #VolumeGridData: This makes it cheap to copy e.g. a #VolumeGrid<T>,
* because it just increases the number of users. An actual copy is only done when the grid is
* modified.
* - Implicit sharing of the referenced OpenVDB tree (not grid): The tree is the heavy piece of
* data that contains all the voxel values. Multiple #VolumeGridData can reference the same tree
* with independent meta-data and transforms. The tree is only actually copied when necessary.
* - Lazy loading of the entire grid or just the tree: When constructing the #VolumeGridData it is
* possible to provide a callback that lazy-loads the grid when it is first accessed. This is
* especially benefitial when loading grids from a file and it's not clear in the beginning if
* the tree is actually needed. It's also supported to just load the meta-data and transform
* first and to load the tree only when it's used. This allows e.g. transforming or renaming the
* grid without loading the tree.
* - Unloading of the tree: It's possible to unload the tree data when it is not in use. This is
* only supported on a shared grid if the tree could be reloaded (e.g. by reading it from a vdb
* file) and if no one is currently accessing the grid data.
*/
class VolumeGridData : public ImplicitSharingMixin {
private:
/**
* Empty struct that exists so that it can be used as token in #VolumeTreeAccessToken.
*/
struct AccessToken {
};
/**
* A mutex that needs to be locked whenever working with the data members below.
*/
mutable std::mutex mutex_;
/**
* The actual grid. Depending on the current state, is in one of multiple possible states:
* - Empty: When the grid is lazy-loaded and no meta-data is provided.
* - Only meta-data and transform: When the grid is lazy-loaded and initial meta-data is
* provided.
* - Complete: When the grid is fully loaded. It then contains the meta-data, transform and tree.
*
* `std::shared_ptr` is used, because some OpenVDB APIs expect the use of those. Unfortunately,
* one can not insert and release data from a `shared_ptr`. Therefore, the grid has to be wrapped
* by the `shared_ptr` at all times.
*
* However, this #VolumeGridData is considered to be the only actual owner of the grid. It is
* also considered to be the only owner of the meta-data and transform in the grid. It is
* possible to share the tree though.
*/
mutable std::shared_ptr<openvdb::GridBase> grid_;
/**
* Keeps track of whether the tree in `grid_` is current mutable or shared.
*/
mutable const ImplicitSharingInfo *tree_sharing_info_ = nullptr;
/** The tree stored in the grid is valid. */
mutable bool tree_loaded_ = false;
/** The transform stored in the grid is valid. */
mutable bool transform_loaded_ = false;
/** The meta-data stored in the grid is valid. */
mutable bool meta_data_loaded_ = false;
/**
* A function that can load the full grid or also just the tree lazily.
*/
std::function<std::shared_ptr<openvdb::GridBase>()> lazy_load_grid_;
/**
* An error produced while trying to lazily load the grid.
*/
mutable std::string error_message_;
/**
* A token that allows detecting whether some code is currently accessing the tree (not grid) or
* not. If this variable is the only owner of the `shared_ptr`, no one else has access to the
* tree. `shared_ptr` is used here because it makes it very easy to manage a user-count without
* much boilerplate.
*/
std::shared_ptr<AccessToken> tree_access_token_;
friend class VolumeTreeAccessToken;
/** Private default constructor for internal purposes. */
VolumeGridData();
public:
/**
* Constructs a new volume grid of the given type where all voxels are inactive and the
* background value is the default value (generally zero).
*/
explicit VolumeGridData(VolumeGridType grid_type);
/**
* Constructs a new volume grid from the provided OpenVDB grid of which it takes ownership. The
* grid must be moved into this constructor and must not be shared currently.
*/
explicit VolumeGridData(std::shared_ptr<openvdb::GridBase> grid);
/**
* Constructs a new volume grid that loads the underlying OpenVDB data lazily.
* \param lazy_load_grid: Function that is called when the data is first needed. It returns the
* new grid or may raise an exception. The returned meta-data and transform are ignored if the
* second parameter is provided here.
* \param meta_data_and_transform_grid: An initial grid where the tree may be null. This grid
* might come from e.g. #readAllGridMetadata. This allows working with the transform and
* meta-data without actually loading the tree.
*/
explicit VolumeGridData(std::function<std::shared_ptr<openvdb::GridBase>()> lazy_load_grid,
std::shared_ptr<openvdb::GridBase> meta_data_and_transform_grid = {});
~VolumeGridData();
/**
* Get an access token for the underlying tree. This is necessary to be able to detect whether
* the grid is currently unused so that it can be safely unloaded.
*/
VolumeTreeAccessToken tree_access_token() const;
/**
* Create a copy of the volume grid. This should generally only be done when the current grid is
* shared and one owner wants to modify it.
*
* This makes a deep copy of the transform and meta-data, but the tree remains shared and is only
* copied later if necessary.
*/
GVolumeGrid copy() const;
/**
* Get the underlying OpenVDB grid for read-only access. This may load the tree lazily if it's
* not loaded already.
*/
const openvdb::GridBase &grid(const VolumeTreeAccessToken &tree_access_token) const;
/**
* Get the underlying OpenVDB grid for read and write access. This may load the tree lazily if
* it's not loaded already. It may also make a copy of the tree if it's currently shared.
*/
openvdb::GridBase &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
/**
* Same as #grid and #grid_for_write but returns the grid as a `shared_ptr` so that it can be
* used with APIs that only support grids wrapped into one. This method is not supposed to
* actually transfer ownership of the grid.
*/
std::shared_ptr<const openvdb::GridBase> grid_ptr(
const VolumeTreeAccessToken &tree_access_token) const;
std::shared_ptr<openvdb::GridBase> grid_ptr_for_write(
const VolumeTreeAccessToken &tree_access_token);
/**
* Get the name of the grid that's stored in the grid meta-data.
*/
std::string name() const;
/**
* Replace the name of the grid that's stored in the meta-data.
*/
void set_name(StringRef name);
/**
* Get the transform of the grid for read-only access. This may lazily load the data if it's not
* yet available.
*/
const openvdb::math::Transform &transform() const;
/**
* Get the transform of the grid for read and write access. This may lazily load the data if it's
* not yet available.
*/
openvdb::math::Transform &transform_for_write();
/**
* Grid type that's derived from the OpenVDB tree type.
*/
VolumeGridType grid_type() const;
/**
* Same as #grid_type() but does not potentially call the lazy-load function to figure out the
* grid type. This can be used e.g. by asserts.
*/
std::optional<VolumeGridType> grid_type_without_load() const;
/**
* Grid class that is stored in the grid's meta data.
*/
openvdb::GridClass grid_class() const;
/**
* True if the grid is fully loaded (including the meta-data, transform and tree).
*/
bool is_loaded() const;
/**
* Non-empty string if there was some error when trying to load the volume.
*/
std::string error_message() const;
/**
* Tree if the tree can be loaded again after it has been unloaded.
*/
bool is_reloadable() const;
/**
* Unloads the tree data if it's reloadable and no one is using it right now.
*/
void unload_tree_if_possible() const;
private:
void ensure_grid_loaded() const;
void delete_self();
};
class VolumeTreeAccessToken {
private:
std::shared_ptr<VolumeGridData::AccessToken> token_;
friend VolumeGridData;
public:
/** True if the access token can be used with the given grid. */
bool valid_for(const VolumeGridData &grid) const;
/** Revoke the access token to indicating that the tree is not used anymore. */
void reset();
};
/**
* A #GVolumeGrid owns a volume grid. Semantically, each #GVolumeGrid is independent but implicit
* sharing is used to avoid unnecessary deep copies.
*/
class GVolumeGrid {
protected:
ImplicitSharingPtr<VolumeGridData> data_;
public:
/**
* Constructs a #GVolumeGrid that does not contain any data.
*/
GVolumeGrid() = default;
/**
* Take (shared) ownership of the given grid data. The caller is responsible for making sure that
* the user count includes a user for the newly constructed #GVolumeGrid.
*/
explicit GVolumeGrid(const VolumeGridData *data);
/**
* Constructs a new volume grid that takes unique ownership of the passed in OpenVDB grid.
*/
explicit GVolumeGrid(std::shared_ptr<openvdb::GridBase> grid);
/**
* Constructs an empty grid of the given type, where all voxels are inactive and the background
* is the default value (generally zero).
*/
explicit GVolumeGrid(VolumeGridType grid_type);
/**
* Get the underlying (potentially shared) volume grid data for read-only access.
*/
const VolumeGridData &get() const;
/**
* Get the underlying volume grid data for read and write access. This may make a copy of the
* grid data is shared.
*/
VolumeGridData &get_for_write();
/**
* Move ownership of the underlying grid data to the caller.
*/
const VolumeGridData *release();
/** Makes it more convenient to retrieve data from the grid. */
const VolumeGridData *operator->() const;
/** True if this contains a grid. */
operator bool() const;
/** Converts to a typed VolumeGrid. This asserts if the type is wrong. */
template<typename T> VolumeGrid<T> typed() const;
};
/**
* Same as #GVolumeGrid but makes it easier to work with the grid if the type is known at compile
* time.
*/
template<typename T> class VolumeGrid : public GVolumeGrid {
public:
using base_type = T;
VolumeGrid() = default;
explicit VolumeGrid(const VolumeGridData *data);
explicit VolumeGrid(std::shared_ptr<OpenvdbGridType<T>> grid);
/**
* Wraps the same methods on #VolumeGridData but casts to the correct OpenVDB type.
*/
const OpenvdbGridType<T> &grid(const VolumeTreeAccessToken &tree_access_token) const;
OpenvdbGridType<T> &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
private:
void assert_correct_type() const;
};
/**
* Get the volume grid type based on the tree type in the grid.
*/
VolumeGridType get_type(const openvdb::GridBase &grid);
/* -------------------------------------------------------------------- */
/** \name Inline Methods
* \{ */
inline GVolumeGrid::GVolumeGrid(const VolumeGridData *data) : data_(data) {}
inline const VolumeGridData &GVolumeGrid::get() const
{
BLI_assert(*this);
return *data_.get();
}
inline const VolumeGridData *GVolumeGrid::release()
{
return data_.release();
}
inline GVolumeGrid::operator bool() const
{
return bool(data_);
}
template<typename T> inline VolumeGrid<T> GVolumeGrid::typed() const
{
if (data_) {
data_->add_user();
}
return VolumeGrid<T>(data_.get());
}
inline const VolumeGridData *GVolumeGrid::operator->() const
{
BLI_assert(*this);
return data_.get();
}
template<typename T>
inline VolumeGrid<T>::VolumeGrid(const VolumeGridData *data) : GVolumeGrid(data)
{
this->assert_correct_type();
}
template<typename T>
inline VolumeGrid<T>::VolumeGrid(std::shared_ptr<OpenvdbGridType<T>> grid)
: GVolumeGrid(std::move(grid))
{
this->assert_correct_type();
}
template<typename T>
inline const OpenvdbGridType<T> &VolumeGrid<T>::grid(
const VolumeTreeAccessToken &tree_access_token) const
{
return static_cast<const OpenvdbGridType<T> &>(data_->grid(tree_access_token));
}
template<typename T>
inline OpenvdbGridType<T> &VolumeGrid<T>::grid_for_write(
const VolumeTreeAccessToken &tree_access_token)
{
return static_cast<OpenvdbGridType<T> &>(
this->get_for_write().grid_for_write(tree_access_token));
}
template<typename T> inline void VolumeGrid<T>::assert_correct_type() const
{
# ifndef NDEBUG
if (data_) {
const VolumeGridType expected_type = VolumeGridTraits<T>::EnumType;
if (const std::optional<VolumeGridType> actual_type = data_->grid_type_without_load()) {
BLI_assert(expected_type == *actual_type);
}
}
# endif
}
inline bool VolumeTreeAccessToken::valid_for(const VolumeGridData &grid) const
{
return grid.tree_access_token_ == token_;
}
inline void VolumeTreeAccessToken::reset()
{
token_.reset();
}
/** \} */
} // namespace blender::bke::volume_grid
#endif /* WITH_OPENVDB */

View File

@@ -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<openvdb::MetaMap> file_meta_data;
/**
* All grids stored in the file.
*/
Vector<GVolumeGrid> 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

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*/
#include <string>
#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<typename T> 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<int>` and `VolumeGrid<float>`.
*/
template<typename T> static constexpr bool is_VolumeGrid_v = false;
template<typename T> static constexpr bool is_VolumeGrid_v<VolumeGrid<T>> = 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

View File

@@ -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<float3>` 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<typename T> 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<bool> {
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<int> {
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<float> {
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<float3> {
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<typename T> using OpenvdbTreeType = typename VolumeGridTraits<T>::TreeType;
template<typename T> using OpenvdbGridType = openvdb::Grid<OpenvdbTreeType<T>>;
} // namespace blender::bke
#endif /* WITH_OPENVDB */

View File

@@ -8,14 +8,20 @@
# include <openvdb/openvdb.h>
# include <openvdb/points/PointDataGrid.h>
# include <optional>
# 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<blender::Bounds<blender::float3>> BKE_volume_grid_bounds(
openvdb::GridBase::ConstPtr grid);
@@ -28,18 +34,6 @@ std::optional<blender::Bounds<blender::float3>> 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<typename OpType>
auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op)
{

View File

@@ -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);

View File

@@ -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

View File

@@ -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 <atomic>
@@ -77,403 +80,19 @@ using blender::StringRefNull;
# include <openvdb/points/PointDataGrid.h>
# include <openvdb/tools/GridTransformer.h>
/* 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<std::mutex> 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<int, openvdb::GridBase::Ptr> 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<std::string> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<Entry, EntryHasher, EntryEqual>;
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<std::mutex> 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<std::mutex> 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::StringMetadata>(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<VolumeGrid> {
struct VolumeGridVector : public std::list<GVolumeGrid> {
VolumeGridVector() : metadata(new openvdb::MetaMap())
{
filepath[0] = '\0';
}
VolumeGridVector(const VolumeGridVector &other)
: std::list<VolumeGrid>(other), error_msg(other.error_msg), metadata(other.metadata)
: std::list<GVolumeGrid>(other), error_msg(other.error_msg), metadata(other.metadata)
{
memcpy(filepath, other.filepath, sizeof(filepath));
}
@@ -485,7 +104,7 @@ struct VolumeGridVector : public std::list<VolumeGrid> {
void clear_all()
{
std::list<VolumeGrid>::clear();
std::list<GVolumeGrid>::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<blender::bke::VolumeTreeAccessToken> 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<blender::Bounds<blender::float3>> BKE_volume_min_max(const Volume
if (BKE_volume_load(const_cast<Volume *>(volume), G.main)) {
std::optional<blender::Bounds<blender::float3>> 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<GVolumeGrid> 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<typename Grid> void operator()()
{
static_cast<Grid &>(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<openvdb::FloatGrid>()) {
return VOLUME_GRID_FLOAT;
}
if (grid.isType<openvdb::Vec3fGrid>()) {
return VOLUME_GRID_VECTOR_FLOAT;
}
if (grid.isType<openvdb::BoolGrid>()) {
return VOLUME_GRID_BOOLEAN;
}
if (grid.isType<openvdb::DoubleGrid>()) {
return VOLUME_GRID_DOUBLE;
}
if (grid.isType<openvdb::Int32Grid>()) {
return VOLUME_GRID_INT;
}
if (grid.isType<openvdb::Int64Grid>()) {
return VOLUME_GRID_INT64;
}
if (grid.isType<openvdb::Vec3IGrid>()) {
return VOLUME_GRID_VECTOR_INT;
}
if (grid.isType<openvdb::Vec3dGrid>()) {
return VOLUME_GRID_VECTOR_DOUBLE;
}
if (grid.isType<openvdb::MaskGrid>()) {
return VOLUME_GRID_MASK;
}
if (grid.isType<openvdb::points::PointDataGrid>()) {
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<openvdb::math::Transform>(
std::make_shared<openvdb::math::AffineMap>(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. */
/**

View File

@@ -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 <openvdb/Grid.h>
#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<openvdb::tree::TreeBase> tree_;
public:
OpenvdbTreeSharingInfo(std::shared_ptr<openvdb::tree::TreeBase> 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<AccessToken>();
}
struct CreateGridOp {
template<typename GridT> 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<openvdb::GridBase> 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<OpenvdbTreeSharingInfo>(__func__, grid_->baseTreePtr());
tree_access_token_ = std::make_shared<AccessToken>();
}
VolumeGridData::VolumeGridData(std::function<std::shared_ptr<openvdb::GridBase>()> lazy_load_grid,
std::shared_ptr<openvdb::GridBase> 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<AccessToken>();
}
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<const openvdb::GridBase> 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<openvdb::GridBase> 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<OpenvdbTreeSharingInfo>(__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<VolumeGridType> 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<openvdb::GridBase> 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<OpenvdbTreeSharingInfo>(__func__, grid_->baseTreePtr());
tree_loaded_ = true;
transform_loaded_ = true;
meta_data_loaded_ = true;
}
GVolumeGrid::GVolumeGrid(std::shared_ptr<openvdb::GridBase> grid)
{
data_ = ImplicitSharingPtr(MEM_new<VolumeGridData>(__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<VolumeGridData &>(*data_);
}
VolumeGridType get_type(const openvdb::GridBase &grid)
{
if (grid.isType<openvdb::FloatGrid>()) {
return VOLUME_GRID_FLOAT;
}
if (grid.isType<openvdb::Vec3fGrid>()) {
return VOLUME_GRID_VECTOR_FLOAT;
}
if (grid.isType<openvdb::BoolGrid>()) {
return VOLUME_GRID_BOOLEAN;
}
if (grid.isType<openvdb::DoubleGrid>()) {
return VOLUME_GRID_DOUBLE;
}
if (grid.isType<openvdb::Int32Grid>()) {
return VOLUME_GRID_INT;
}
if (grid.isType<openvdb::Int64Grid>()) {
return VOLUME_GRID_INT64;
}
if (grid.isType<openvdb::Vec3IGrid>()) {
return VOLUME_GRID_VECTOR_INT;
}
if (grid.isType<openvdb::Vec3dGrid>()) {
return VOLUME_GRID_VECTOR_DOUBLE;
}
if (grid.isType<openvdb::MaskGrid>()) {
return VOLUME_GRID_MASK;
}
if (grid.isType<openvdb::points::PointDataGrid>()) {
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<openvdb::math::AffineMap>(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

View File

@@ -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 <openvdb/openvdb.h>
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<int, GVolumeGrid> 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<GridCache> 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<std::string, FileCache> 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<VolumeGridData>(
__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<openvdb::MetaMap>(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 */

View File

@@ -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<float *>(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<openvdb::CoordBBox> 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<blender::float3> verts;
blender::Vector<std::array<int, 2>> 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<openvdb::CoordBBox> 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<blender::float3> 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<blender::float3> 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<openvdb::CoordBBox> boxes = get_bounding_boxes(
BKE_volume_grid_type(volume_grid), *grid, true);
volume_grid->grid_type(), grid, true);
blender::Vector<blender::float3> verts;
blender::Vector<std::array<int, 3>> 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
}

View File

@@ -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<Volume *>(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<Volume *>(BKE_id_new(bmain, ID_VO, nullptr));
Volume *volume_b = static_cast<Volume *>(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<float> volume_grid{MEM_new<VolumeGridData>(__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<float> volume_grid{
MEM_new<VolumeGridData>(__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 */

View File

@@ -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)) {

View File

@@ -93,7 +93,7 @@ template<typename T> class ImplicitSharingPtr {
const T *release()
{
T *data = data_;
const T *data = data_;
data_ = nullptr;
return data;
}

View File

@@ -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

View File

@@ -45,7 +45,7 @@ void VolumePass::object_sync_volume(Manager &manager,
/* Create 3D textures. */
Volume *volume = static_cast<Volume *>(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;
}

View File

@@ -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);

View File

@@ -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<DRWVolumeGrid>(__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);

View File

@@ -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<const Volume *>(ob_data);
const blender::Bounds<blender::float3> 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<blender::Bounds<blender::float3>> 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:

View File

@@ -103,10 +103,12 @@ inline void ObjectInfos::sync(const blender::draw::ObjectRef ref, bool is_active
switch (GS(reinterpret_cast<ID *>(ref.object->data)->name)) {
case ID_VO: {
const blender::Bounds<float3> bounds = *BKE_volume_min_max(
std::optional<const blender::Bounds<float3>> bounds = BKE_volume_min_max(
static_cast<const Volume *>(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: {

View File

@@ -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;

View File

@@ -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)

View File

@@ -57,6 +57,9 @@ set(LIB
)
if(WITH_OPENVDB)
list(APPEND INC
../../../../intern/openvdb
)
list(APPEND INC_SYS
${OPENVDB_INCLUDE_DIRS}
)

View File

@@ -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<ColumnValues> VolumeDataSource::get_column_values(
if (STREQ(column_id.name, "Grid Name")) {
return std::make_unique<ColumnValues>(
IFACE_("Grid Name"), VArray<std::string>::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<ColumnValues>(
IFACE_("Data Type"), VArray<std::string>::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<ColumnValues> VolumeDataSource::get_column_values(
if (STREQ(column_id.name, "Class")) {
return std::make_unique<ColumnValues>(
IFACE_("Class"), VArray<std::string>::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");
}

View File

@@ -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

View File

@@ -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<float3> positions,
Span<float> radii,
float voxel_size,
float density);
bke::VolumeGridData *fog_volume_grid_add_from_points(Volume *volume,
StringRefNull name,
Span<float3> positions,
Span<float> 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<float3> positions,
Span<float> radii,
float voxel_size);
bke::VolumeGridData *sdf_volume_grid_add_from_points(Volume *volume,
StringRefNull name,
Span<float3> positions,
Span<float> radii,
float voxel_size);
#endif
} // namespace blender::geometry

View File

@@ -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;

View File

@@ -59,12 +59,12 @@ static openvdb::FloatGrid::Ptr points_to_sdf_grid(const Span<float3> positions,
return new_grid;
}
VolumeGrid *fog_volume_grid_add_from_points(Volume *volume,
const StringRefNull name,
const Span<float3> positions,
const Span<float> radii,
const float voxel_size,
const float density)
bke::VolumeGridData *fog_volume_grid_add_from_points(Volume *volume,
const StringRefNull name,
const Span<float3> positions,
const Span<float> 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<float3> positions,
const Span<float> radii,
const float voxel_size)
bke::VolumeGridData *sdf_volume_grid_add_from_points(Volume *volume,
const StringRefNull name,
const Span<float3> positions,
const Span<float> radii,
const float voxel_size)
{
openvdb::FloatGrid::Ptr new_grid = points_to_sdf_grid(positions, radii);
new_grid->transform().postScale(voxel_size);

View File

@@ -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}
)

View File

@@ -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,

View File

@@ -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);

View File

@@ -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<VolumeGrid *>(ptr->data);
strcpy(value, BKE_volume_grid_name(grid));
auto *grid = static_cast<const blender::bke::VolumeGridData *>(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<VolumeGrid *>(ptr->data);
return strlen(BKE_volume_grid_name(grid));
auto *grid = static_cast<const blender::bke::VolumeGridData *>(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<VolumeGrid *>(ptr->data);
return BKE_volume_grid_type(grid);
const auto *grid = static_cast<const blender::bke::VolumeGridData *>(ptr->data);
return blender::bke::volume_grid::get_type(*grid);
}
static int rna_VolumeGrid_channels_get(PointerRNA *ptr)
{
const VolumeGrid *grid = static_cast<VolumeGrid *>(ptr->data);
return BKE_volume_grid_channels(grid);
const auto *grid = static_cast<const blender::bke::VolumeGridData *>(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<VolumeGrid *>(ptr->data);
BKE_volume_grid_transform_matrix(grid, (float(*)[4])value);
auto *grid = static_cast<const blender::bke::VolumeGridData *>(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<VolumeGrid *>(ptr->data);
return BKE_volume_grid_is_loaded(grid);
auto *grid = static_cast<const blender::bke::VolumeGridData *>(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<const blender::bke::VolumeGridData *>(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<const blender::bke::VolumeGridData *>(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<Volume *>(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;

View File

@@ -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);
}

View File

@@ -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<Volume *>(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;

View File

@@ -185,6 +185,9 @@ if(WITH_GMP)
endif()
if(WITH_OPENVDB)
list(APPEND INC
../../../intern/openvdb
)
list(APPEND INC_SYS
${OPENVDB_INCLUDE_DIRS}
)

View File

@@ -260,6 +260,9 @@ if(WITH_GMP)
endif()
if(WITH_OPENVDB)
list(APPEND INC
../../../../intern/openvdb
)
list(APPEND INC_SYS
${OPENVDB_INCLUDE_DIRS}
)

View File

@@ -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<float3> 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<openvdb::FloatGrid>()) {
continue;
}
if (!base_grid->isType<openvdb::FloatGrid>()) {
continue;
}
const openvdb::FloatGrid::ConstPtr grid = openvdb::gridConstPtrCast<openvdb::FloatGrid>(
base_grid);
const openvdb::FloatGrid &grid = static_cast<const openvdb::FloatGrid &>(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);
}
}

View File

@@ -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 &params,
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 &params,
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,

View File

@@ -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<openvdb::GridBase::ConstPtr> grids,
static Mesh *create_mesh_from_volume_grids(Span<const openvdb::GridBase *> 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<openvdb::GridBase::ConstPtr> grids;
Vector<bke::VolumeTreeAccessToken> access_tokens;
Vector<const openvdb::GridBase *> 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()) {