Files
test/source/blender/blenkernel/intern/volume_grid.cc
Lukas Tönne 11ceddb9df New Grid Info node for reading grid transforms and background value
These are generic properties of grids (not stored in voxels) which are
useful to know in geometry nodes. The transform in particular defines
the voxel size. Background value is used outside of active voxels.

Pull Request: https://projects.blender.org/blender/blender/pulls/138592
2025-05-12 13:46:40 +02:00

506 lines
12 KiB
C++

/* 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_memory_counter.hh"
#include "BLI_task.hh"
#ifdef WITH_OPENVDB
# include <openvdb/Grid.h>
#endif
namespace blender::bke::volume_grid {
#ifdef WITH_OPENVDB
VolumeGridData::VolumeGridData()
{
tree_access_token_ = std::make_shared<AccessToken>(*this);
}
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_.use_count() == 1);
BLI_assert(grid_->isTreeUnique());
tree_sharing_info_ = OpenvdbTreeSharingInfo::make(grid_->baseTreePtr());
tree_access_token_ = std::make_shared<AccessToken>(*this);
}
VolumeGridData::VolumeGridData(std::function<LazyLoadedGrid()> 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>(*this);
}
VolumeGridData::~VolumeGridData() = default;
void VolumeGridData::delete_self()
{
MEM_delete(this);
}
const openvdb::GridBase &VolumeGridData::grid(VolumeTreeAccessToken &r_token) const
{
return *this->grid_ptr(r_token);
}
openvdb::GridBase &VolumeGridData::grid_for_write(VolumeTreeAccessToken &r_token)
{
return *this->grid_ptr_for_write(r_token);
}
std::shared_ptr<const openvdb::GridBase> VolumeGridData::grid_ptr(
VolumeTreeAccessToken &r_token) const
{
std::lock_guard lock{mutex_};
this->ensure_grid_loaded();
r_token.token_ = tree_access_token_;
return grid_;
}
std::shared_ptr<openvdb::GridBase> VolumeGridData::grid_ptr_for_write(
VolumeTreeAccessToken &r_token)
{
BLI_assert(this->is_mutable());
std::lock_guard lock{mutex_};
this->ensure_grid_loaded();
r_token.token_ = tree_access_token_;
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_ = OpenvdbTreeSharingInfo::make(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_;
}
void VolumeGridData::count_memory(MemoryCounter &memory) const
{
std::lock_guard lock{mutex_};
if (!tree_loaded_) {
return;
}
const openvdb::TreeBase &tree = grid_->baseTree();
memory.add_shared(tree_sharing_info_.get(),
[&](MemoryCounter &shared_memory) { shared_memory.add(tree.memUsage()); });
}
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 (!tree_loaded_) {
return;
}
if (!this->is_reloadable()) {
return;
}
if (tree_access_token_.use_count() != 1) {
/* Some code is using the tree currently, so it can't be freed. */
return;
}
grid_->newTree();
tree_loaded_ = false;
tree_sharing_info_.reset();
}
GVolumeGrid VolumeGridData::copy() const
{
std::lock_guard lock{mutex_};
this->ensure_grid_loaded();
/* Can't use #MEM_new because the default constructor 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_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_);
LazyLoadedGrid 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.grid) {
BLI_assert(!loaded_grid.tree_sharing_info);
if (grid_) {
const openvdb::Name &grid_type = grid_->type();
if (openvdb::GridBase::isRegistered(grid_type)) {
/* Create a dummy grid of the expected type. */
loaded_grid.grid = openvdb::GridBase::createGrid(grid_type);
}
}
}
if (!loaded_grid.grid) {
/* Create a dummy grid. We can't really know the expected data type here. */
loaded_grid.grid = openvdb::FloatGrid::create();
}
BLI_assert(loaded_grid.grid);
BLI_assert(loaded_grid.grid.use_count() == 1);
if (!loaded_grid.tree_sharing_info) {
BLI_assert(loaded_grid.grid->isTreeUnique());
loaded_grid.tree_sharing_info = OpenvdbTreeSharingInfo::make(loaded_grid.grid->baseTreePtr());
}
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.grid->baseTreePtr());
if (!transform_loaded_) {
grid_->setTransform(loaded_grid.grid->transformPtr());
}
}
else {
grid_ = std::move(loaded_grid.grid);
}
BLI_assert(!tree_sharing_info_);
BLI_assert(loaded_grid.tree_sharing_info);
tree_sharing_info_ = std::move(loaded_grid.tree_sharing_info);
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;
}
ImplicitSharingPtr<> OpenvdbTreeSharingInfo::make(std::shared_ptr<openvdb::tree::TreeBase> tree)
{
return ImplicitSharingPtr<>{MEM_new<OpenvdbTreeSharingInfo>(__func__, std::move(tree))};
}
OpenvdbTreeSharingInfo::OpenvdbTreeSharingInfo(std::shared_ptr<openvdb::tree::TreeBase> tree)
: tree_(std::move(tree))
{
}
void OpenvdbTreeSharingInfo::delete_self_with_data()
{
MEM_delete(this);
}
void OpenvdbTreeSharingInfo::delete_data_only()
{
tree_.reset();
}
VolumeTreeAccessToken::~VolumeTreeAccessToken()
{
const VolumeGridData *grid = token_ ? &token_->grid : nullptr;
token_.reset();
if (grid) {
/* Unload immediately when the value is not used anymore. However, the tree may still be cached
* at a deeper level and thus usually does not have to be loaded from disk again. */
grid->unload_tree_if_possible();
}
}
#endif /* WITH_OPENVDB */
std::string get_name(const VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
return grid.name();
#else
UNUSED_VARS(grid);
return "density";
#endif
}
VolumeGridType get_type(const VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
return grid.grid_type();
#else
UNUSED_VARS(grid);
return VOLUME_GRID_UNKNOWN;
#endif
}
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;
}
float4x4 get_transform_matrix(const VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
return BKE_volume_transform_to_blender(grid.transform());
#else
UNUSED_VARS(grid);
return float4x4::identity();
#endif
}
void set_transform_matrix(VolumeGridData &grid, const float4x4 &matrix)
{
#ifdef WITH_OPENVDB
grid.transform_for_write() = BKE_volume_transform_to_openvdb(matrix);
#else
UNUSED_VARS(grid, matrix);
#endif
}
void clear_tree(VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
VolumeTreeAccessToken tree_token;
grid.grid_for_write(tree_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 count_memory(const VolumeGridData &grid, MemoryCounter &memory)
{
#ifdef WITH_OPENVDB
grid.count_memory(memory);
#else
UNUSED_VARS(grid, memory);
#endif
}
void load(const VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
VolumeTreeAccessToken tree_token;
/* Just "touch" the grid, so that it is loaded. */
grid.grid(tree_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