WIP: Volume grid attribute support in geometry nodes #110044

Closed
Lukas Tönne wants to merge 130 commits from LukasTonne/blender:geometry-nodes-flip into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 699 additions and 0 deletions
Showing only changes of commit 6aa68ee85b - Show all commits

View File

@ -0,0 +1,692 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <optional>
#include "BLI_any.hh"
#include "BLI_volume.hh"
namespace blender {
/** Forward declarations for generic virtual grids. */
class GVGrid;
class GVMutableGrid;
/**
* Is used to quickly check if a vgrid is a grid or single value..
*/
struct CommonVGridInfo {
enum class Type : uint8_t {
/* Is not one of the common special types below. */
Any,
Grid,
Single,
};
Type type = Type::Any;
/** True when the #data becomes a dangling pointer when the virtual grid is destructed. */
bool may_have_ownership = true;
/**
* Points either to nothing, a single value or OpenVDB grid, depending on #type.
* If this is a mutable virtual grid, it is safe to cast away const.
*/
const void *data;
CommonVGridInfo() = default;
CommonVGridInfo(const Type _type, const bool _may_have_ownership, const void *_data)
: type(_type), may_have_ownership(_may_have_ownership), data(_data)
{
}
};
/**
* Implements the specifics of how the elements of a virtual array are accessed. It contains a
* bunch of virtual methods that are wrapped by #VGrid.
*/
template<typename T> class VGridImpl {
public:
VGridImpl() {}
virtual ~VGridImpl() = default;
virtual CommonVGridInfo common_info() const
{
return {};
}
/**
* Get the element at #index. This does not return a reference, because the value may be computed
* on the fly.
*/
// virtual T get(int64_t index) const = 0;
/**
* Copy values from the virtual array into the provided span. The index of the value in the
* virtual array is the same as the index in the span.
*/
// virtual void materialize(const IndexMask &mask, T *dst) const
//{
// mask.foreach_index([&](const int64_t i) { dst[i] = this->get(i); });
// }
/**
* Same as #materialize but #r_span is expected to be uninitialized.
*/
// virtual void materialize_to_uninitialized(const IndexMask &mask, T *dst) const
//{
// mask.foreach_index([&](const int64_t i) { new (dst + i) T(this->get(i)); });
// }
/**
* Copy values from the virtual array into the provided span. Contrary to #materialize, the index
* in virtual array is not the same as the index in the output span. Instead, the span is filled
* without gaps.
*/
// virtual void materialize_compressed(const IndexMask &mask, T *dst) const
//{
// mask.foreach_index([&](const int64_t i, const int64_t pos) { dst[pos] = this->get(i); });
// }
/**
* Same as #materialize_compressed but #r_span is expected to be uninitialized.
*/
// virtual void materialize_compressed_to_uninitialized(const IndexMask &mask, T *dst) const
//{
// mask.foreach_index(
// [&](const int64_t i, const int64_t pos) { new (dst + pos) T(this->get(i)); });
// }
/**
* If this virtual grid wraps another #GVGrid, this method should assign the wrapped array to the
* provided reference. This allows losslessly converting between generic and typed virtual arrays
* in all cases. Return true when the virtual grid was assigned and false when nothing was done.
*/
virtual bool try_assign_GVGrid(GVGrid & /*vgrid*/) const
{
return false;
}
};
/** Similar to #VGridImpl, but adds methods that allow modifying the referenced elements. */
template<typename T> class VMutableGridImpl : public VGridImpl<T> {
public:
using VGridImpl<T>::VGridImpl;
/**
* Assign the provided #value to the #index.
*/
// virtual void set(int64_t index, T value) = 0;
/**
* Copy all elements from the provided span into the virtual array.
*/
// virtual void set_all(Span<T> src)
//{
// const CommonVGridInfo info = this->common_info();
// if (info.type == CommonVGridInfo::Type::Span) {
// initialized_copy_n(
// src.data(), this->size_, const_cast<T *>(static_cast<const T *>(info.data)));
// }
// else {
// const int64_t size = this->size_;
// for (int64_t i = 0; i < size; i++) {
// this->set(i, src[i]);
// }
// }
// }
/**
* Similar to #VGridImpl::try_assign_GVGrid but for mutable virtual grids.
*/
virtual bool try_assign_GVMutableGrid(GVMutableGrid & /*vgrid*/) const
{
return false;
}
};
/**
* A virtual grid implementation that wraps a OpenVDB grid. This implementation is used by
* mutable and immutable grids to avoid code duplication.
*/
template<typename T> class VGridImpl_For_Grid : public VMutableGridImpl<T> {
protected:
#ifdef WITH_OPENVDB
using GridType = volume::grid_types::AttributeGrid<T>;
GridType *grid_ = nullptr;
#endif
public:
VGridImpl_For_Grid(GridType &grid) : grid_(&grid) {}
protected:
CommonVGridInfo common_info() const override
{
return CommonVGridInfo(CommonVGridInfo::Type::Grid, true, grid_);
}
};
/**
* A version of #VArrayImpl_For_Grid that can not be subclassed. This allows safely overwriting the
* #may_have_ownership method.
*/
template<typename T> class VGridImpl_For_Grid_final final : public VGridImpl_For_Grid<T> {
public:
using VGridImpl_For_Grid<T>::VGridImpl_For_Grid;
#ifdef WITH_OPENVDB
using VGridImpl_For_Grid<T>::GridType;
#endif
VGridImpl_For_Grid_final(GridType &grid)
/* Cast const away, because the implementation for const and non const spans is shared. */
: VGridImpl_For_Grid<T>(grid)
{
}
private:
CommonVGridInfo common_info() const final
{
return CommonVGridInfo(CommonVGridInfo::Type::Grid, false, this->grid_);
}
};
/**
* A virtual array implementation that returns the same value for every location. This class is
* final so that it can be devirtualized by the compiler in some cases (e.g. when
* #devirtualize_VGrid is used).
*/
template<typename T> class VGridImpl_For_Single final : public VGridImpl<T> {
private:
T value_;
public:
VGridImpl_For_Single(T value) : VGridImpl<T>(), value_(std::move(value)) {}
protected:
CommonVGridInfo common_info() const override
{
return CommonVGridInfo(CommonVGridInfo::Type::Single, true, &value_);
}
};
/**
* This class makes it easy to create a virtual array for an existing function or lambda. The
* `GetFunc` should take a single position argument and return the value at that index.
*/
template<typename T, typename GetFunc> class VGridImpl_For_Func final : public VGridImpl<T> {
private:
GetFunc get_func_;
public:
VGridImpl_For_Func(const int64_t size, GetFunc get_func)
: VGridImpl<T>(size), get_func_(std::move(get_func))
{
}
};
namespace detail {
/**
* Struct that can be passed as `ExtraInfo` into an #Any.
* This struct is only intended to be used by #VGridCommon.
*/
template<typename T> struct VGridAnyExtraInfo {
/**
* Gets the virtual grid that is stored at the given pointer.
*/
const VGridImpl<T> *(*get_vgrid)(const void *buffer);
template<typename StorageT> static constexpr VGridAnyExtraInfo get()
{
/* These are the only allowed types in the #Any. */
static_assert(
std::is_base_of_v<VGridImpl<T>, StorageT> ||
is_same_any_v<StorageT, const VGridImpl<T> *, std::shared_ptr<const VGridImpl<T>>>);
/* Depending on how the virtual grid implementation is stored in the #Any, a different
* #get_vgrid function is required. */
if constexpr (std::is_base_of_v<VGridImpl<T>, StorageT>) {
return {[](const void *buffer) {
return static_cast<const VGridImpl<T> *>((const StorageT *)buffer);
}};
}
else if constexpr (std::is_same_v<StorageT, const VGridImpl<T> *>) {
return {[](const void *buffer) { return *(const StorageT *)buffer; }};
}
else if constexpr (std::is_same_v<StorageT, std::shared_ptr<const VGridImpl<T>>>) {
return {[](const void *buffer) { return ((const StorageT *)buffer)->get(); }};
}
else {
BLI_assert_unreachable();
return {};
}
}
};
} // namespace detail
/**
* Utility class to reduce code duplication for methods available on #VGrid and #VMutableGrid.
* Deriving #VMutableGrid from #VGrid would have some issues:
* - Static methods on #VGrid would also be available on #VMutableGrid.
* - It would allow assigning a #VGrid to a #VMutableGrid under some circumstances which is not
* allowed and could result in hard to find bugs.
*/
template<typename T> class VGridCommon {
public:
#ifdef WITH_OPENVDB
using GridType = volume::grid_types::AttributeGrid<T>;
using TreeType = typename GridType::TreeType;
using GridPtr = typename GridType::Ptr;
using GridConstPtr = typename GridType::ConstPtr;
using GridValueType = typename GridType::ValueType;
using Converter = volume::grid_types::Converter<GridType>;
#endif
protected:
/**
* Store the virtual grid implementation in an #Any. This makes it easy to avoid a memory
* allocation if the implementation is small enough and is copyable. This is the case for the
* most common virtual grids.
* Other virtual grid implementations are typically stored as #std::shared_ptr. That works even
* when the implementation itself is not copyable and makes copying #VGridCommon cheaper.
*/
using Storage = Any<blender::detail::VGridAnyExtraInfo<T>, 24, 8>;
/**
* Pointer to the currently contained virtual grid implementation. This is allowed to be null.
*/
const VGridImpl<T> *impl_ = nullptr;
/**
* Does the memory management for the virtual grid implementation. It contains one of the
* following:
* - Inlined subclass of #VGridImpl.
* - Non-owning pointer to a #VGridImpl.
* - Shared pointer to a #VGridImpl.
*/
Storage storage_;
protected:
VGridCommon() = default;
/** Copy constructor. */
VGridCommon(const VGridCommon &other) : storage_(other.storage_)
{
impl_ = this->impl_from_storage();
}
/** Move constructor. */
VGridCommon(VGridCommon &&other) noexcept : storage_(std::move(other.storage_))
{
impl_ = this->impl_from_storage();
other.storage_.reset();
other.impl_ = nullptr;
}
/**
* Wrap an existing #VGridImpl and don't take ownership of it. This should rarely be used in
* practice.
*/
VGridCommon(const VGridImpl<T> *impl) : impl_(impl)
{
storage_ = impl_;
}
/**
* Wrap an existing #VGridImpl that is contained in a #std::shared_ptr. This takes ownership.
*/
VGridCommon(std::shared_ptr<const VGridImpl<T>> impl) : impl_(impl.get())
{
if (impl) {
storage_ = std::move(impl);
}
}
/**
* Replace the contained #VGridImpl.
*/
template<typename ImplT, typename... Args> void emplace(Args &&...args)
{
/* Make sure we are actually constructing a #VGridImpl. */
static_assert(std::is_base_of_v<VGridImpl<T>, ImplT>);
if constexpr (std::is_copy_constructible_v<ImplT> && Storage::template is_inline_v<ImplT>) {
/* Only inline the implementation when it is copyable and when it fits into the inline
* buffer of the storage. */
impl_ = &storage_.template emplace<ImplT>(std::forward<Args>(args)...);
}
else {
/* If it can't be inlined, create a new #std::shared_ptr instead and store that in the
* storage. */
std::shared_ptr<const VGridImpl<T>> ptr = std::make_shared<ImplT>(
std::forward<Args>(args)...);
impl_ = &*ptr;
storage_ = std::move(ptr);
}
}
/** Utility to implement a copy assignment operator in a subclass. */
void copy_from(const VGridCommon &other)
{
if (this == &other) {
return;
}
storage_ = other.storage_;
impl_ = this->impl_from_storage();
}
/** Utility to implement a move assignment operator in a subclass. */
void move_from(VGridCommon &&other) noexcept
{
if (this == &other) {
return;
}
storage_ = std::move(other.storage_);
impl_ = this->impl_from_storage();
other.storage_.reset();
other.impl_ = nullptr;
}
/** Get a pointer to the virtual array implementation that is currently stored in #storage_, or
* null. */
const VGridImpl<T> *impl_from_storage() const
{
if (!storage_.has_value()) {
return nullptr;
}
return storage_.extra_info().get_vgrid(storage_.get());
}
public:
/** Return false when there is no virtual array implementation currently. */
operator bool() const
{
return impl_ != nullptr;
}
CommonVGridInfo common_info() const
{
BLI_assert(*this);
return impl_->common_info();
}
/** Return true when the virtual grid is stored as a OpenVDB grid internally. */
bool is_grid() const
{
BLI_assert(*this);
const CommonVGridInfo info = impl_->common_info();
return info.type == CommonVGridInfo::Type::Grid;
}
#ifdef WITH_OPENVDB
/**
* Returns the internally used grid of the virtual grid. This invokes undefined behavior if the
* virtual grid is not stored as a grid internally.
*/
GridType *get_internal_grid() const
{
BLI_assert(this->is_span());
const CommonVGridInfo info = impl_->common_info();
return static_cast<GridType *>(info.data);
}
#endif
/** Return true when the virtual grid returns the same value for every index. */
bool is_single() const
{
BLI_assert(*this);
const CommonVGridInfo info = impl_->common_info();
return info.type == CommonVGridInfo::Type::Single;
}
/**
* Return the value that is returned for every location. This invokes undefined behavior if the
* virtual grid would not return the same value for every location.
*/
T get_internal_single() const
{
BLI_assert(this->is_single());
const CommonVGridInfo info = impl_->common_info();
return *static_cast<const T *>(info.data);
}
/**
* Return the value that is returned for every index, if the array is stored as a single value.
*/
std::optional<T> get_if_single() const
{
const CommonVGridInfo info = impl_->common_info();
if (info.type != CommonVGridInfo::Type::Single) {
return std::nullopt;
}
return *static_cast<const T *>(info.data);
}
/** See #GVGridImpl::try_assign_GVGrid. */
bool try_assign_GVGrid(GVGrid &vgrid) const
{
return impl_->try_assign_GVGrid(vgrid);
}
const VGridImpl<T> *get_implementation() const
{
return impl_;
}
};
template<typename T> class VMutableGrid;
/**
* Various tags to disambiguate constructors of virtual grids.
* Generally it is easier to use `VGrid::For*` functions to construct virtual grids, but
* sometimes being able to use the constructor can result in better performance For example, when
* constructing the virtual grid directly in a vector. Without the constructor one would have to
* construct the virtual grid first and then move it into the vector.
*/
namespace vgrid_tag {
struct grid {};
struct single_ref {};
struct single {};
} // namespace vgrid_tag
/**
* A #VGrid wraps a virtual grid implementation and provides easy access to its elements. It can
* be copied and moved. While it is relatively small, it should still be passed by reference if
* possible.
*/
template<typename T> class VGrid : public VGridCommon<T> {
friend VMutableGrid<T>;
public:
#ifdef WITH_OPENVDB
using GridType = typename VGridCommon<T>::GridType;
using TreeType = typename VGridCommon<T>::TreeType;
using GridPtr = typename VGridCommon<T>::GridPtr;
using GridConstPtr = typename VGridCommon<T>::GridConstPtr;
using GridValueType = typename VGridCommon<T>::GridValueType;
using Converter = typename VGridCommon<T>::Converter;
#endif
public:
VGrid() = default;
VGrid(const VGrid &other) = default;
VGrid(VGrid &&other) noexcept = default;
VGrid(const VGridImpl<T> *impl) : VGridCommon<T>(impl) {}
VGrid(std::shared_ptr<const VGridImpl<T>> impl) : VGridCommon<T>(std::move(impl)) {}
VGrid(vgrid_tag::grid /* tag */, const GridType &grid)
{
this->template emplace<VGridImpl_For_Grid_final<T>>(const_cast<GridType &>(grid));
}
VGrid(vgrid_tag::single /* tag */, T value)
{
this->template emplace<VGridImpl_For_Single<T>>(std::move(value));
}
/**
* Construct a new virtual grid for a custom #VGridImpl.
*/
template<typename ImplT, typename... Args> static VGrid For(Args &&...args)
{
static_assert(std::is_base_of_v<VGridImpl<T>, ImplT>);
VGrid VGrid;
VGrid.template emplace<ImplT>(std::forward<Args>(args)...);
return VGrid;
}
/**
* Construct a new virtual grid that has the same value at every location.
*/
static VGrid ForSingle(T value)
{
return VGrid(vgrid_tag::single{}, std::move(value));
}
/**
* Construct a new virtual array for an existing span. This does not take ownership of the
* underlying memory.
*/
static VGrid ForGrid(const openvdb::Grid<T> &grid)
{
return VGrid(vgrid_tag::grid{}, grid);
}
/**
* Construct a new virtual that will invoke the provided function whenever an element is
* accessed.
*/
template<typename GetFunc> static VGrid ForFunc(GetFunc get_func)
{
return VGrid::For<VGridImpl_For_Func<T, decltype(get_func)>>(std::move(get_func));
}
VGrid &operator=(const VGrid &other)
{
this->copy_from(other);
return *this;
}
VGrid &operator=(VGrid &&other) noexcept
{
this->move_from(std::move(other));
return *this;
}
};
/**
* Similar to #VGrid but references a virtual grid that can be modified.
*/
template<typename T> class VMutableGrid : public VGridCommon<T> {
public:
#ifdef WITH_OPENVDB
using GridType = typename VGridCommon<T>::GridType;
using TreeType = typename VGridCommon<T>::TreeType;
using GridPtr = typename VGridCommon<T>::GridPtr;
using GridConstPtr = typename VGridCommon<T>::GridConstPtr;
using GridValueType = typename VGridCommon<T>::GridValueType;
using Converter = typename VGridCommon<T>::Converter;
#endif
public:
VMutableGrid() = default;
VMutableGrid(const VMutableGrid &other) = default;
VMutableGrid(VMutableGrid &&other) noexcept = default;
VMutableGrid(const VMutableGridImpl<T> *impl) : VGridCommon<T>(impl) {}
VMutableGrid(std::shared_ptr<const VMutableGridImpl<T>> impl) : VGridCommon<T>(std::move(impl))
{
}
/**
* Construct a new virtual grid for a custom #VMutableGridImpl.
*/
template<typename ImplT, typename... Args> static VMutableGrid For(Args &&...args)
{
static_assert(std::is_base_of_v<VMutableGridImpl<T>, ImplT>);
VMutableGrid VGrid;
VGrid.template emplace<ImplT>(std::forward<Args>(args)...);
return VGrid;
}
/**
* Construct a new virtual array for an existing span. This does not take ownership of the span.
*/
static VMutableGrid ForGrid(GridType &grid)
{
return VMutableGrid::For<VGridImpl_For_Grid_final<T>>(grid);
}
/** Convert to a #VGrid by copying. */
operator VGrid<T>() const &
{
VGrid<T> VGrid;
VGrid.copy_from(*this);
return VGrid;
}
/** Convert to a #VGrid by moving. */
operator VGrid<T>() &&noexcept
{
VGrid<T> VGrid;
VGrid.move_from(std::move(*this));
return VGrid;
}
VMutableGrid &operator=(const VMutableGrid &other)
{
this->copy_from(other);
return *this;
}
VMutableGrid &operator=(VMutableGrid &&other) noexcept
{
this->move_from(std::move(other));
return *this;
}
/**
* Get access to the internal grid. This invokes undefined behavior if the #is_grid returned
* false.
*/
GridType &get_internal_grid() const
{
BLI_assert(this->is_span());
const CommonVGridInfo info = this->get_impl()->common_info();
return *const_cast<GridType *>(static_cast<const GridType *>(info.data));
}
/** See #GVMutableGridImpl::try_assign_GVMutableGrid. */
bool try_assign_GVMutableGrid(GVMutableGrid &vgrid) const
{
return this->get_impl()->try_assign_GVMutableGrid(vgrid);
}
private:
/** Utility to get the pointer to the wrapped #VMutableGridImpl. */
VMutableGridImpl<T> *get_impl() const
{
/* This cast is valid by the invariant that a #VMutableGrid->impl_ is always a
* #VMutableGridImpl. */
return (VMutableGridImpl<T> *)this->impl_;
}
};
template<typename T> static constexpr bool is_VGrid_v = false;
template<typename T> static constexpr bool is_VGrid_v<VGrid<T>> = true;
template<typename T> static constexpr bool is_VMutableGrid_v = false;
template<typename T> static constexpr bool is_VMutableGrid_v<VMutableGrid<T>> = true;
} // namespace blender

View File

@ -159,6 +159,7 @@ set(SRC
intern/uvproject.c
intern/vector.cc
intern/virtual_array.cc
intern/virtual_grid.cc
intern/volume.cc
intern/voronoi_2d.c
intern/voxel.c
@ -381,6 +382,7 @@ set(SRC
BLI_vector_set.hh
BLI_vector_set_slots.hh
BLI_virtual_array.hh
BLI_virtual_grid.hh
BLI_virtual_vector_array.hh
BLI_volume.hh
BLI_volume_openvdb.hh

View File

@ -0,0 +1,5 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_virtual_grid.hh"