Geometry Nodes: initial support for volume grids in function nodes
This patch implements basic support for evaluating function nodes on volume grids. Conceptually, a function node always creates a new grid for the output, though the output is often a modified version of the input. The topology of the output grid is a union of all the input grids. All input grids have to have the same transform. Otherwise one has to use resampling to make grids compatible. Non-grid inputs are allowed to be single values or fields. The fields are evaluated in a voxel/tile context, so they compute a value per voxel or per tile. One optimization is missing that will probably be key in the future: the ability to merge multiple function nodes and execute them at the same time. Currently the entire function evaluation is started and finished for every function node that outputs a grid. This will add significant overhead in some situations. Implementing this optimization requires some more changes outside of the scope of this patch though. It's good to have something that works first. Note: Not all function nodes are supported yet, because we don't have grid types for all of them yet. Most notably, there are no color/float4 grids yet. Implementing those properly is not super straight forward and may require some more changes, because there isn't a 1-to-1 mapping between grid types and socket types (a float4 grid may correspond to a color or vector socket later on). Using grids with function nodes and fields can result in false positive warnings in the UI currently. That's a limitation of our current socket type inferencing and can be improved once we have better socket shape inferencing. Pull Request: https://projects.blender.org/blender/blender/pulls/125110
This commit is contained in:
48
source/blender/blenkernel/BKE_volume_grid_fields.hh
Normal file
48
source/blender/blenkernel/BKE_volume_grid_fields.hh
Normal file
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
#include "openvdb_fwd.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* A field context that allows computing a value per voxel. Each voxel is defined by a 3D integer
|
||||
* coordinate and a transform matrix.
|
||||
*/
|
||||
class VoxelFieldContext : public fn::FieldContext {
|
||||
private:
|
||||
const openvdb::math::Transform &transform_;
|
||||
Span<openvdb::Coord> voxels_;
|
||||
|
||||
public:
|
||||
VoxelFieldContext(const openvdb::math::Transform &transform, Span<openvdb::Coord> voxels);
|
||||
|
||||
GVArray get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Similar to #VoxelFieldContext, but allows computing values for tiles. A tile contains multiple
|
||||
* voxels.
|
||||
*/
|
||||
class TilesFieldContext : public fn::FieldContext {
|
||||
private:
|
||||
const openvdb::math::Transform &transform_;
|
||||
Span<openvdb::CoordBBox> tiles_;
|
||||
|
||||
public:
|
||||
TilesFieldContext(const openvdb::math::Transform &transform,
|
||||
const Span<openvdb::CoordBBox> tiles);
|
||||
|
||||
GVArray get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -17,11 +17,14 @@
|
||||
# include "BLI_bounds_types.hh"
|
||||
# include "BLI_math_matrix_types.hh"
|
||||
# include "BLI_math_vector_types.hh"
|
||||
# include "BLI_parameter_pack_utils.hh"
|
||||
# include "BLI_string_ref.hh"
|
||||
|
||||
# include "BKE_volume_enums.hh"
|
||||
# include "BKE_volume_grid_fwd.hh"
|
||||
|
||||
# include "openvdb_fwd.hh"
|
||||
|
||||
struct Volume;
|
||||
|
||||
blender::bke::VolumeGridData *BKE_volume_grid_add_vdb(Volume &volume,
|
||||
@@ -77,6 +80,36 @@ auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op)
|
||||
return op.template operator()<openvdb::FloatGrid>();
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
void BKE_volume_grid_type_to_static_type(const VolumeGridType grid_type, Fn &&fn)
|
||||
{
|
||||
switch (grid_type) {
|
||||
case VOLUME_GRID_FLOAT:
|
||||
return fn(blender::TypeTag<openvdb::FloatGrid>());
|
||||
case VOLUME_GRID_VECTOR_FLOAT:
|
||||
return fn(blender::TypeTag<openvdb::Vec3fGrid>());
|
||||
case VOLUME_GRID_BOOLEAN:
|
||||
return fn(blender::TypeTag<openvdb::BoolGrid>());
|
||||
case VOLUME_GRID_DOUBLE:
|
||||
return fn(blender::TypeTag<openvdb::DoubleGrid>());
|
||||
case VOLUME_GRID_INT:
|
||||
return fn(blender::TypeTag<openvdb::Int32Grid>());
|
||||
case VOLUME_GRID_INT64:
|
||||
return fn(blender::TypeTag<openvdb::Int64Grid>());
|
||||
case VOLUME_GRID_VECTOR_INT:
|
||||
return fn(blender::TypeTag<openvdb::Vec3IGrid>());
|
||||
case VOLUME_GRID_VECTOR_DOUBLE:
|
||||
return fn(blender::TypeTag<openvdb::Vec3dGrid>());
|
||||
case VOLUME_GRID_MASK:
|
||||
return fn(blender::TypeTag<openvdb::MaskGrid>());
|
||||
case VOLUME_GRID_POINTS:
|
||||
return fn(blender::TypeTag<openvdb::points::PointDataGrid>());
|
||||
case VOLUME_GRID_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
openvdb::GridBase::Ptr BKE_volume_grid_create_with_changed_resolution(
|
||||
const VolumeGridType grid_type, const openvdb::GridBase &old_grid, float resolution_factor);
|
||||
|
||||
|
||||
@@ -312,6 +312,7 @@ set(SRC
|
||||
intern/viewer_path.cc
|
||||
intern/volume.cc
|
||||
intern/volume_grid.cc
|
||||
intern/volume_grid_fields.cc
|
||||
intern/volume_grid_file_cache.cc
|
||||
intern/volume_render.cc
|
||||
intern/volume_to_mesh.cc
|
||||
@@ -530,6 +531,7 @@ set(SRC
|
||||
BKE_volume.hh
|
||||
BKE_volume_enums.hh
|
||||
BKE_volume_grid.hh
|
||||
BKE_volume_grid_fields.hh
|
||||
BKE_volume_grid_file_cache.hh
|
||||
BKE_volume_grid_fwd.hh
|
||||
BKE_volume_grid_type_traits.hh
|
||||
|
||||
73
source/blender/blenkernel/intern/volume_grid_fields.cc
Normal file
73
source/blender/blenkernel/intern/volume_grid_fields.cc
Normal file
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_geometry_fields.hh"
|
||||
#include "BKE_volume_grid_fields.hh"
|
||||
#include <openvdb/math/Transform.h>
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
VoxelFieldContext::VoxelFieldContext(const openvdb::math::Transform &transform,
|
||||
const Span<openvdb::Coord> voxels)
|
||||
: transform_(transform), voxels_(voxels)
|
||||
{
|
||||
}
|
||||
|
||||
GVArray VoxelFieldContext::get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask & /*mask*/,
|
||||
ResourceScope & /*scope*/) const
|
||||
{
|
||||
const bke::AttributeFieldInput *attribute_field_input =
|
||||
dynamic_cast<const bke::AttributeFieldInput *>(&field_input);
|
||||
if (!attribute_field_input) {
|
||||
return {};
|
||||
}
|
||||
if (attribute_field_input->attribute_name() != "position") {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Support retrieving voxel positions. */
|
||||
Array<float3> positions(voxels_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::Coord &voxel = voxels_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(voxel);
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
}
|
||||
});
|
||||
return VArray<float3>::ForContainer(std::move(positions));
|
||||
}
|
||||
|
||||
TilesFieldContext::TilesFieldContext(const openvdb::math::Transform &transform,
|
||||
const Span<openvdb::CoordBBox> tiles)
|
||||
: transform_(transform), tiles_(tiles)
|
||||
{
|
||||
}
|
||||
|
||||
GVArray TilesFieldContext::get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask & /*mask*/,
|
||||
ResourceScope & /*scope*/) const
|
||||
{
|
||||
const bke::AttributeFieldInput *attribute_field_input =
|
||||
dynamic_cast<const bke::AttributeFieldInput *>(&field_input);
|
||||
if (attribute_field_input == nullptr) {
|
||||
return {};
|
||||
}
|
||||
if (attribute_field_input->attribute_name() != "position") {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Support retrieving tile positions. */
|
||||
Array<float3> positions(tiles_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::CoordBBox &tile = tiles_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(tile.getCenter());
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
}
|
||||
});
|
||||
return VArray<float3>::ForContainer(std::move(positions));
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -46,6 +46,12 @@ class GSpan {
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
GSpan(MutableSpan<T> array)
|
||||
: GSpan(CPPType::get<T>(), static_cast<const void *>(array.data()), array.size())
|
||||
{
|
||||
}
|
||||
|
||||
const CPPType &type() const
|
||||
{
|
||||
BLI_assert(type_ != nullptr);
|
||||
|
||||
@@ -95,6 +95,7 @@ set(SRC
|
||||
intern/socket_search_link.cc
|
||||
intern/socket_usage_inference.cc
|
||||
intern/value_elem.cc
|
||||
intern/volume_grid_function_eval.cc
|
||||
|
||||
NOD_common.hh
|
||||
NOD_composite.hh
|
||||
@@ -139,6 +140,7 @@ set(SRC
|
||||
intern/node_common.h
|
||||
intern/node_exec.hh
|
||||
intern/node_util.hh
|
||||
intern/volume_grid_function_eval.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "volume_grid_function_eval.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -530,26 +532,36 @@ static void execute_multi_function_on_value_variant__field(
|
||||
* Executes a multi-function. If all inputs are single values, the results will also be single
|
||||
* values. If any input is a field, the outputs will also be fields.
|
||||
*/
|
||||
static void execute_multi_function_on_value_variant(const MultiFunction &fn,
|
||||
const std::shared_ptr<MultiFunction> &owned_fn,
|
||||
const Span<SocketValueVariant *> input_values,
|
||||
const Span<SocketValueVariant *> output_values)
|
||||
[[nodiscard]] static bool execute_multi_function_on_value_variant(
|
||||
const MultiFunction &fn,
|
||||
const std::shared_ptr<MultiFunction> &owned_fn,
|
||||
const Span<SocketValueVariant *> input_values,
|
||||
const Span<SocketValueVariant *> output_values,
|
||||
std::string &r_error_message)
|
||||
{
|
||||
/* Check input types which determine how the function is evaluated. */
|
||||
bool any_input_is_field = false;
|
||||
bool any_input_is_volume_grid = false;
|
||||
for (const int i : input_values.index_range()) {
|
||||
const SocketValueVariant &value = *input_values[i];
|
||||
if (value.is_context_dependent_field()) {
|
||||
any_input_is_field = true;
|
||||
}
|
||||
else if (value.is_volume_grid()) {
|
||||
any_input_is_volume_grid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_input_is_volume_grid) {
|
||||
return execute_multi_function_on_value_variant__volume_grid(
|
||||
fn, input_values, output_values, r_error_message);
|
||||
}
|
||||
if (any_input_is_field) {
|
||||
execute_multi_function_on_value_variant__field(fn, owned_fn, input_values, output_values);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
execute_multi_function_on_value_variant__single(fn, input_values, output_values);
|
||||
}
|
||||
execute_multi_function_on_value_variant__single(fn, input_values, output_values);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
|
||||
@@ -573,7 +585,13 @@ bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
|
||||
mf::DataType::ForSingle(*from_cpp_type), mf::DataType::ForSingle(*to_cpp_type));
|
||||
SocketValueVariant input_variant = *static_cast<const SocketValueVariant *>(from_value);
|
||||
SocketValueVariant *output_variant = new (r_to_value) SocketValueVariant();
|
||||
execute_multi_function_on_value_variant(multi_fn, {}, {&input_variant}, {output_variant});
|
||||
std::string error_message;
|
||||
if (!execute_multi_function_on_value_variant(
|
||||
multi_fn, {}, {&input_variant}, {output_variant}, error_message))
|
||||
{
|
||||
std::destroy_at(output_variant);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -582,9 +600,11 @@ bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
|
||||
class LazyFunctionForImplicitConversion : public LazyFunction {
|
||||
private:
|
||||
const MultiFunction &fn_;
|
||||
const bke::bNodeSocketType &dst_type_;
|
||||
|
||||
public:
|
||||
LazyFunctionForImplicitConversion(const MultiFunction &fn) : fn_(fn)
|
||||
LazyFunctionForImplicitConversion(const MultiFunction &fn, const bke::bNodeSocketType &dst_type)
|
||||
: fn_(fn), dst_type_(dst_type)
|
||||
{
|
||||
debug_name_ = "Convert";
|
||||
inputs_.append_as("From", CPPType::get<SocketValueVariant>());
|
||||
@@ -597,7 +617,12 @@ class LazyFunctionForImplicitConversion : public LazyFunction {
|
||||
SocketValueVariant *to_value = new (params.get_output_data_ptr(0)) SocketValueVariant();
|
||||
BLI_assert(from_value != nullptr);
|
||||
BLI_assert(to_value != nullptr);
|
||||
execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value});
|
||||
std::string error_message;
|
||||
if (!execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value}, error_message))
|
||||
{
|
||||
std::destroy_at(to_value);
|
||||
construct_socket_default_value(dst_type_, to_value);
|
||||
}
|
||||
params.output_set(0);
|
||||
}
|
||||
};
|
||||
@@ -618,7 +643,7 @@ const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSock
|
||||
if (conversions.is_convertible(from_base_type, to_base_type)) {
|
||||
const MultiFunction &multi_fn = *conversions.get_conversion_multi_function(
|
||||
mf::DataType::ForSingle(from_base_type), mf::DataType::ForSingle(to_base_type));
|
||||
return &scope.construct<LazyFunctionForImplicitConversion>(multi_fn);
|
||||
return &scope.construct<LazyFunctionForImplicitConversion>(multi_fn, to_type);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -703,20 +728,21 @@ class LazyFunctionForMutedNode : public LazyFunction {
|
||||
*/
|
||||
class LazyFunctionForMultiFunctionNode : public LazyFunction {
|
||||
private:
|
||||
const bNode &node_;
|
||||
const NodeMultiFunctions::Item fn_item_;
|
||||
|
||||
public:
|
||||
LazyFunctionForMultiFunctionNode(const bNode &node,
|
||||
NodeMultiFunctions::Item fn_item,
|
||||
MutableSpan<int> r_lf_index_by_bsocket)
|
||||
: fn_item_(std::move(fn_item))
|
||||
: node_(node), fn_item_(std::move(fn_item))
|
||||
{
|
||||
BLI_assert(fn_item_.fn != nullptr);
|
||||
debug_name_ = node.name;
|
||||
lazy_function_interface_from_node(node, inputs_, outputs_, r_lf_index_by_bsocket);
|
||||
}
|
||||
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override
|
||||
void execute_impl(lf::Params ¶ms, const lf::Context &context) const override
|
||||
{
|
||||
Vector<SocketValueVariant *> input_values(inputs_.size());
|
||||
Vector<SocketValueVariant *> output_values(outputs_.size());
|
||||
@@ -731,8 +757,37 @@ class LazyFunctionForMultiFunctionNode : public LazyFunction {
|
||||
output_values[i] = nullptr;
|
||||
}
|
||||
}
|
||||
execute_multi_function_on_value_variant(
|
||||
*fn_item_.fn, fn_item_.owned_fn, input_values, output_values);
|
||||
std::string error_message;
|
||||
if (!execute_multi_function_on_value_variant(
|
||||
*fn_item_.fn, fn_item_.owned_fn, input_values, output_values, error_message))
|
||||
{
|
||||
int available_output_index = 0;
|
||||
for (const bNodeSocket *bsocket : node_.output_sockets()) {
|
||||
if (!bsocket->is_available()) {
|
||||
continue;
|
||||
}
|
||||
SocketValueVariant *output_value = output_values[available_output_index];
|
||||
if (!output_value) {
|
||||
continue;
|
||||
}
|
||||
std::destroy_at(output_value);
|
||||
construct_socket_default_value(*bsocket->typeinfo, output_value);
|
||||
available_output_index++;
|
||||
}
|
||||
|
||||
if (!error_message.empty()) {
|
||||
const auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
|
||||
const auto &local_user_data = *static_cast<GeoNodesLocalUserData *>(
|
||||
context.local_user_data);
|
||||
if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(
|
||||
user_data))
|
||||
{
|
||||
tree_logger->node_warnings.append(
|
||||
*tree_logger->allocator,
|
||||
{node_.identifier, {NodeWarningType::Error, error_message}});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const int i : outputs_.index_range()) {
|
||||
if (params.get_output_usage(i) != lf::ValueUsage::Unused) {
|
||||
params.output_set(i);
|
||||
|
||||
713
source/blender/nodes/intern/volume_grid_function_eval.cc
Normal file
713
source/blender/nodes/intern/volume_grid_function_eval.cc
Normal file
@@ -0,0 +1,713 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BLT_translation.hh"
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_make.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "BKE_volume_grid.hh"
|
||||
#include "BKE_volume_grid_fields.hh"
|
||||
#include "BKE_volume_openvdb.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <openvdb/Grid.h>
|
||||
#include <openvdb/math/Transform.h>
|
||||
#include <openvdb/tools/Merge.h>
|
||||
|
||||
#include "volume_grid_function_eval.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
template<typename GridT>
|
||||
static constexpr bool is_supported_grid_type = is_same_any_v<GridT,
|
||||
openvdb::FloatGrid,
|
||||
openvdb::Vec3fGrid,
|
||||
openvdb::BoolGrid,
|
||||
openvdb::Int32Grid,
|
||||
openvdb::Vec4fGrid>;
|
||||
|
||||
template<typename Fn> static void to_typed_grid(const openvdb::GridBase &grid_base, Fn &&fn)
|
||||
{
|
||||
const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base);
|
||||
BKE_volume_grid_type_to_static_type(grid_type, [&](auto type_tag) {
|
||||
using GridT = typename decltype(type_tag)::type;
|
||||
if constexpr (is_supported_grid_type<GridT>) {
|
||||
fn(static_cast<const GridT &>(grid_base));
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Fn> static void to_typed_grid(openvdb::GridBase &grid_base, Fn &&fn)
|
||||
{
|
||||
const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base);
|
||||
BKE_volume_grid_type_to_static_type(grid_type, [&](auto type_tag) {
|
||||
using GridT = typename decltype(type_tag)::type;
|
||||
if constexpr (is_supported_grid_type<GridT>) {
|
||||
fn(static_cast<GridT &>(grid_base));
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::optional<VolumeGridType> cpp_type_to_grid_type(const CPPType &cpp_type)
|
||||
{
|
||||
const std::optional<eCustomDataType> cd_type = bke::cpp_type_to_custom_data_type(cpp_type);
|
||||
if (!cd_type) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return bke::custom_data_type_to_volume_grid_type(*cd_type);
|
||||
}
|
||||
|
||||
using LeafNodeMask = openvdb::util::NodeMask<3u>;
|
||||
using GetVoxelsFn = FunctionRef<void(MutableSpan<openvdb::Coord> r_voxels)>;
|
||||
using ProcessLeafFn = FunctionRef<void(const LeafNodeMask &leaf_node_mask,
|
||||
const openvdb::CoordBBox &leaf_bbox,
|
||||
GetVoxelsFn get_voxels_fn)>;
|
||||
using ProcessTilesFn = FunctionRef<void(Span<openvdb::CoordBBox> tiles)>;
|
||||
using ProcessVoxelsFn = FunctionRef<void(Span<openvdb::Coord> voxels)>;
|
||||
|
||||
/**
|
||||
* Call #process_leaf_fn on the leaf node if it has a certain minimum number of active voxels. If
|
||||
* there are only a few active voxels, gather those in #r_coords for later batch processing.
|
||||
*/
|
||||
template<typename LeafNodeT>
|
||||
static void parallel_grid_topology_tasks_leaf_node(const LeafNodeT &node,
|
||||
const ProcessLeafFn process_leaf_fn,
|
||||
Vector<openvdb::Coord, 1024> &r_coords)
|
||||
{
|
||||
using NodeMaskT = typename LeafNodeT::NodeMaskType;
|
||||
|
||||
const int on_count = node.onVoxelCount();
|
||||
/* This number is somewhat arbitrary. 64 is a 1/8th of the number of voxels in a standard leaf
|
||||
* which is 8x8x8. It's a trade-off between benefitting from the better performance of
|
||||
* leaf-processing vs. processing more voxels in a batch. */
|
||||
const int on_count_threshold = 64;
|
||||
if (on_count <= on_count_threshold) {
|
||||
/* The leaf contains only a few active voxels. It's beneficial to process them in a batch with
|
||||
* active voxels from other leafs. So only gather them here for later processing. */
|
||||
for (auto value_iter = node.cbeginValueOn(); value_iter.test(); ++value_iter) {
|
||||
const openvdb::Coord coord = value_iter.getCoord();
|
||||
r_coords.append(coord);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* Process entire leaf at once. This is especially beneficial when very many of the voxels in
|
||||
* the leaf are active. In that case, one can work on the openvdb arrays stored in the leafs
|
||||
* directly. */
|
||||
const NodeMaskT &value_mask = node.getValueMask();
|
||||
const openvdb::CoordBBox bbox = node.getNodeBoundingBox();
|
||||
process_leaf_fn(value_mask, bbox, [&](MutableSpan<openvdb::Coord> r_voxels) {
|
||||
for (auto value_iter = node.cbeginValueOn(); value_iter.test(); ++value_iter) {
|
||||
r_voxels[value_iter.pos()] = value_iter.getCoord();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the process functions on all the active tiles and voxels within the given internal node.
|
||||
*/
|
||||
template<typename InternalNodeT>
|
||||
static void parallel_grid_topology_tasks_internal_node(const InternalNodeT &node,
|
||||
const ProcessLeafFn process_leaf_fn,
|
||||
const ProcessVoxelsFn process_voxels_fn,
|
||||
const ProcessTilesFn process_tiles_fn)
|
||||
{
|
||||
using ChildNodeT = typename InternalNodeT::ChildNodeType;
|
||||
using LeafNodeT = typename InternalNodeT::LeafNodeType;
|
||||
using NodeMaskT = typename InternalNodeT::NodeMaskType;
|
||||
using UnionT = typename InternalNodeT::UnionType;
|
||||
|
||||
/* Gather the active sub-nodes first, to be able to parallelize over them more easily. */
|
||||
const NodeMaskT &child_mask = node.getChildMask();
|
||||
const UnionT *table = node.getTable();
|
||||
Vector<int, 512> child_indices;
|
||||
for (auto child_mask_iter = child_mask.beginOn(); child_mask_iter.test(); ++child_mask_iter) {
|
||||
child_indices.append(child_mask_iter.pos());
|
||||
}
|
||||
|
||||
threading::parallel_for(child_indices.index_range(), 8, [&](const IndexRange range) {
|
||||
/* Voxels collected from potentially multiple leaf nodes to be processed in one batch. This
|
||||
* inline buffer size is sufficient to avoid an allocation in all cases (a single standard leaf
|
||||
* has 512 voxels). */
|
||||
Vector<openvdb::Coord, 1024> gathered_voxels;
|
||||
for (const int child_index : child_indices.as_span().slice(range)) {
|
||||
const ChildNodeT &child = *table[child_index].getChild();
|
||||
if constexpr (std::is_same_v<ChildNodeT, LeafNodeT>) {
|
||||
parallel_grid_topology_tasks_leaf_node(child, process_leaf_fn, gathered_voxels);
|
||||
/* If enough voxels have been gathered, process them in one batch. */
|
||||
if (gathered_voxels.size() >= 512) {
|
||||
process_voxels_fn(gathered_voxels);
|
||||
gathered_voxels.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Recurse into lower-level internal nodes. */
|
||||
parallel_grid_topology_tasks_internal_node(
|
||||
child, process_leaf_fn, process_voxels_fn, process_tiles_fn);
|
||||
}
|
||||
}
|
||||
/* Process any remaining voxels. */
|
||||
if (!gathered_voxels.is_empty()) {
|
||||
process_voxels_fn(gathered_voxels);
|
||||
gathered_voxels.clear();
|
||||
}
|
||||
});
|
||||
|
||||
/* Process the active tiles within the internal node. Note that these are not processed above
|
||||
* already because there only sub-nodes are handled, but tiles are "inlined" into internal nodes.
|
||||
* All tiles are first gathered and then processed in one batch. */
|
||||
const NodeMaskT &value_mask = node.getValueMask();
|
||||
Vector<openvdb::CoordBBox> tile_bboxes;
|
||||
for (auto value_mask_iter = value_mask.beginOn(); value_mask_iter.test(); ++value_mask_iter) {
|
||||
const openvdb::Index32 index = value_mask_iter.pos();
|
||||
const openvdb::Coord tile_origin = node.offsetToGlobalCoord(index);
|
||||
const openvdb::CoordBBox tile_bbox = openvdb::CoordBBox::createCube(tile_origin,
|
||||
ChildNodeT::DIM);
|
||||
tile_bboxes.append(tile_bbox);
|
||||
}
|
||||
if (!tile_bboxes.is_empty()) {
|
||||
process_tiles_fn(tile_bboxes);
|
||||
}
|
||||
}
|
||||
|
||||
/* Call the process functions on all active tiles and voxels in the given tree. */
|
||||
static void parallel_grid_topology_tasks(const openvdb::MaskTree &mask_tree,
|
||||
const ProcessLeafFn process_leaf_fn,
|
||||
const ProcessVoxelsFn process_voxels_fn,
|
||||
const ProcessTilesFn process_tiles_fn)
|
||||
{
|
||||
/* Iterate over the root internal nodes. */
|
||||
for (auto root_child_iter = mask_tree.cbeginRootChildren(); root_child_iter.test();
|
||||
++root_child_iter)
|
||||
{
|
||||
const auto &internal_node = *root_child_iter;
|
||||
parallel_grid_topology_tasks_internal_node(
|
||||
internal_node, process_leaf_fn, process_voxels_fn, process_tiles_fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the multi-function in a batch on all active voxels in a leaf node.
|
||||
*
|
||||
* \param fn: The multi-function to call.
|
||||
* \param input_values: All input values which may be grids, fields or single values.
|
||||
* \param input_grids: The input grids already extracted from #input_values.
|
||||
* \param output_grids: The output grids to be filled with the results of the multi-function. The
|
||||
* topology of these grids is initialized already.
|
||||
* \param transform: The transform of all input and output grids.
|
||||
* \param leaf_node_mask: Indicates which voxels in the leaf should be computed.
|
||||
* \param leaf_bbox: The bounding box of the leaf node.
|
||||
* \param get_voxels_fn: A function that extracts the active voxels from the leaf node. This
|
||||
* function knows the order of voxels in the leaf.
|
||||
*/
|
||||
BLI_NOINLINE static void process_leaf_node(const mf::MultiFunction &fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<const openvdb::GridBase *> input_grids,
|
||||
MutableSpan<openvdb::GridBase::Ptr> output_grids,
|
||||
const openvdb::math::Transform &transform,
|
||||
const LeafNodeMask &leaf_node_mask,
|
||||
const openvdb::CoordBBox &leaf_bbox,
|
||||
const GetVoxelsFn get_voxels_fn)
|
||||
{
|
||||
/* Create an index mask for all the active voxels in the leaf. */
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask index_mask = IndexMask::from_predicate(
|
||||
IndexRange(LeafNodeMask::SIZE), GrainSize(LeafNodeMask::SIZE), memory, [&](const int64_t i) {
|
||||
return leaf_node_mask.isOn(i);
|
||||
});
|
||||
|
||||
AlignedBuffer<8192, 8> allocation_buffer;
|
||||
ResourceScope scope;
|
||||
scope.allocator().provide_buffer(allocation_buffer);
|
||||
mf::ParamsBuilder params{fn, &index_mask};
|
||||
mf::ContextBuilder context;
|
||||
|
||||
/* We need to find the corresponding leaf nodes in all the input and output grids. That's done by
|
||||
* finding the leaf that contains this voxel. */
|
||||
const openvdb::Coord any_voxel_in_leaf = leaf_bbox.min();
|
||||
|
||||
std::optional<MutableSpan<openvdb::Coord>> voxel_coords_opt;
|
||||
auto ensure_voxel_coords = [&]() {
|
||||
if (!voxel_coords_opt.has_value()) {
|
||||
voxel_coords_opt = scope.allocator().allocate_array<openvdb::Coord>(
|
||||
index_mask.min_array_size());
|
||||
get_voxels_fn(voxel_coords_opt.value());
|
||||
}
|
||||
return *voxel_coords_opt;
|
||||
};
|
||||
|
||||
for (const int input_i : input_values.index_range()) {
|
||||
const bke::SocketValueVariant &value_variant = *input_values[input_i];
|
||||
const mf::ParamType param_type = fn.param_type(params.next_param_index());
|
||||
const CPPType ¶m_cpp_type = param_type.data_type().single_type();
|
||||
|
||||
if (const openvdb::GridBase *grid_base = input_grids[input_i]) {
|
||||
/* The input is a grid, so we can attempt to reference the grid values directly. */
|
||||
to_typed_grid(*grid_base, [&](const auto &grid) {
|
||||
using GridT = typename std::decay_t<decltype(grid)>;
|
||||
using ValueT = typename GridT::ValueType;
|
||||
BLI_assert(param_cpp_type.size == sizeof(ValueT));
|
||||
const auto &tree = grid.tree();
|
||||
|
||||
if (const auto *leaf_node = tree.probeLeaf(any_voxel_in_leaf)) {
|
||||
/* Boolean grids are special because they encode the values as bitmask. So create a
|
||||
* temporary buffer for the inputs. */
|
||||
if constexpr (std::is_same_v<ValueT, bool>) {
|
||||
const Span<openvdb::Coord> voxels = ensure_voxel_coords();
|
||||
MutableSpan<bool> values = scope.allocator().allocate_array<bool>(
|
||||
index_mask.min_array_size());
|
||||
index_mask.foreach_index([&](const int64_t i) {
|
||||
const openvdb::Coord &coord = voxels[i];
|
||||
values[i] = tree.getValue(coord);
|
||||
});
|
||||
params.add_readonly_single_input(values);
|
||||
}
|
||||
else {
|
||||
const Span<ValueT> values(leaf_node->buffer().data(), LeafNodeMask::SIZE);
|
||||
const LeafNodeMask &input_leaf_mask = leaf_node->valueMask();
|
||||
const LeafNodeMask missing_mask = leaf_node_mask & !input_leaf_mask;
|
||||
if (missing_mask.isOff()) {
|
||||
/* All values availables, so reference the data directly. */
|
||||
params.add_readonly_single_input(
|
||||
GSpan(param_cpp_type, values.data(), values.size()));
|
||||
}
|
||||
else {
|
||||
/* Fill in the missing values with the background value. */
|
||||
MutableSpan copied_values = scope.allocator().construct_array_copy(values);
|
||||
const auto &background = tree.background();
|
||||
for (auto missing_it = missing_mask.beginOn(); missing_it.test(); ++missing_it) {
|
||||
const int index = missing_it.pos();
|
||||
copied_values[index] = background;
|
||||
}
|
||||
params.add_readonly_single_input(
|
||||
GSpan(param_cpp_type, copied_values.data(), copied_values.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* The input does not have this leaf node, so just get the value that's used for the
|
||||
* entire leaf. The leaf may be in a tile or is inactive in which case the background
|
||||
* value is used. */
|
||||
const auto single_value = tree.getValue(any_voxel_in_leaf);
|
||||
params.add_readonly_single_input(GPointer(param_cpp_type, &single_value));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (value_variant.is_context_dependent_field()) {
|
||||
/* Compute the field on all active voxels in the leaf and pass the result to the
|
||||
* multi-function. */
|
||||
const fn::GField field = value_variant.get<fn::GField>();
|
||||
const CPPType &type = field.cpp_type();
|
||||
const Span<openvdb::Coord> voxels = ensure_voxel_coords();
|
||||
bke::VoxelFieldContext field_context{transform, voxels};
|
||||
fn::FieldEvaluator evaluator{field_context, &index_mask};
|
||||
GMutableSpan values{
|
||||
type, scope.allocator().allocate_array(type, voxels.size()), voxels.size()};
|
||||
evaluator.add_with_destination(field, values);
|
||||
evaluator.evaluate();
|
||||
params.add_readonly_single_input(values);
|
||||
}
|
||||
else {
|
||||
/* Pass the single value directly to the multi-function. */
|
||||
params.add_readonly_single_input(value_variant.get_single_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
for (const int output_i : output_grids.index_range()) {
|
||||
const mf::ParamType param_type = fn.param_type(params.next_param_index());
|
||||
const CPPType ¶m_cpp_type = param_type.data_type().single_type();
|
||||
|
||||
openvdb::GridBase &grid_base = *output_grids[output_i];
|
||||
to_typed_grid(grid_base, [&](auto &grid) {
|
||||
using GridT = typename std::decay_t<decltype(grid)>;
|
||||
using ValueT = typename GridT::ValueType;
|
||||
|
||||
auto &tree = grid.tree();
|
||||
auto *leaf_node = tree.probeLeaf(any_voxel_in_leaf);
|
||||
/* Should have been added before. */
|
||||
BLI_assert(leaf_node);
|
||||
|
||||
/* Boolean grids are special because they encode the values as bitmask. */
|
||||
if constexpr (std::is_same_v<ValueT, bool>) {
|
||||
MutableSpan<bool> values = scope.allocator().allocate_array<bool>(
|
||||
index_mask.min_array_size());
|
||||
params.add_uninitialized_single_output(values);
|
||||
}
|
||||
else {
|
||||
/* Write directly into the buffer of the output leaf node. */
|
||||
ValueT *values = leaf_node->buffer().data();
|
||||
params.add_uninitialized_single_output(
|
||||
GMutableSpan(param_cpp_type, values, LeafNodeMask::SIZE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Actually call the multi-function which will write the results into the output grids (except
|
||||
* for boolean grids). */
|
||||
fn.call_auto(index_mask, params, context);
|
||||
|
||||
for (const int output_i : output_grids.index_range()) {
|
||||
const int param_index = input_values.size() + output_i;
|
||||
const mf::ParamType param_type = fn.param_type(param_index);
|
||||
const CPPType ¶m_cpp_type = param_type.data_type().single_type();
|
||||
if (!param_cpp_type.is<bool>()) {
|
||||
continue;
|
||||
}
|
||||
openvdb::BoolGrid &grid = static_cast<openvdb::BoolGrid &>(*output_grids[output_i]);
|
||||
const Span<bool> values = params.computed_array(param_index).typed<bool>();
|
||||
auto accessor = grid.getUnsafeAccessor();
|
||||
const Span<openvdb::Coord> voxels = ensure_voxel_coords();
|
||||
index_mask.foreach_index([&](const int64_t i) {
|
||||
const openvdb::Coord &coord = voxels[i];
|
||||
accessor.setValue(coord, values[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the multi-function in a batch on all the given voxels.
|
||||
*
|
||||
* \param fn: The multi-function to call.
|
||||
* \param input_values: All input values which may be grids, fields or single values.
|
||||
* \param input_grids: The input grids already extracted from #input_values.
|
||||
* \param output_grids: The output grids to be filled with the results of the multi-function. The
|
||||
* topology of these grids is initialized already.
|
||||
* \param transform: The transform of all input and output grids.
|
||||
* \param voxels: The voxels to process.
|
||||
*/
|
||||
BLI_NOINLINE static void process_voxels(const mf::MultiFunction &fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<const openvdb::GridBase *> input_grids,
|
||||
MutableSpan<openvdb::GridBase::Ptr> output_grids,
|
||||
const openvdb::math::Transform &transform,
|
||||
const Span<openvdb::Coord> voxels)
|
||||
{
|
||||
const int64_t voxels_num = voxels.size();
|
||||
const IndexMask index_mask{voxels_num};
|
||||
AlignedBuffer<8192, 8> allocation_buffer;
|
||||
ResourceScope scope;
|
||||
scope.allocator().provide_buffer(allocation_buffer);
|
||||
mf::ParamsBuilder params{fn, &index_mask};
|
||||
mf::ContextBuilder context;
|
||||
|
||||
for (const int input_i : input_values.index_range()) {
|
||||
const bke::SocketValueVariant &value_variant = *input_values[input_i];
|
||||
const mf::ParamType param_type = fn.param_type(params.next_param_index());
|
||||
const CPPType ¶m_cpp_type = param_type.data_type().single_type();
|
||||
|
||||
if (const openvdb::GridBase *grid_base = input_grids[input_i]) {
|
||||
/* Retrieve all voxel values from the input grid. */
|
||||
to_typed_grid(*grid_base, [&](const auto &grid) {
|
||||
using ValueType = typename std::decay_t<decltype(grid)>::ValueType;
|
||||
const auto &tree = grid.tree();
|
||||
/* Could try to cache the accessor across batches, but it's not straight forward since its
|
||||
* type depends on the grid type and thread-safety has to be maintained. It's likely not
|
||||
* worth it because the cost is already negilible since we are processing a full batch. */
|
||||
auto accessor = grid.getConstUnsafeAccessor();
|
||||
|
||||
MutableSpan<ValueType> values = scope.allocator().allocate_array<ValueType>(voxels_num);
|
||||
for (const int64_t i : IndexRange(voxels_num)) {
|
||||
const openvdb::Coord &coord = voxels[i];
|
||||
values[i] = tree.getValue(coord, accessor);
|
||||
}
|
||||
BLI_assert(param_cpp_type.size == sizeof(ValueType));
|
||||
params.add_readonly_single_input(GSpan(param_cpp_type, values.data(), voxels_num));
|
||||
});
|
||||
}
|
||||
else if (value_variant.is_context_dependent_field()) {
|
||||
/* Evaluate the field on all voxels. */
|
||||
const fn::GField field = value_variant.get<fn::GField>();
|
||||
const CPPType &type = field.cpp_type();
|
||||
bke::VoxelFieldContext field_context{transform, voxels};
|
||||
fn::FieldEvaluator evaluator{field_context, voxels_num};
|
||||
GMutableSpan values{type, scope.allocator().allocate_array(type, voxels_num), voxels_num};
|
||||
evaluator.add_with_destination(field, values);
|
||||
evaluator.evaluate();
|
||||
params.add_readonly_single_input(values);
|
||||
}
|
||||
else {
|
||||
/* Pass the single value directly to the multi-function. */
|
||||
params.add_readonly_single_input(value_variant.get_single_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare temporary output buffers for the field evaluation. Those will later be copied into the
|
||||
* output grids. */
|
||||
for ([[maybe_unused]] const int output_i : output_grids.index_range()) {
|
||||
const int param_index = input_values.size() + output_i;
|
||||
const mf::ParamType param_type = fn.param_type(param_index);
|
||||
const CPPType &type = param_type.data_type().single_type();
|
||||
void *buffer = scope.allocator().allocate_array(type, voxels_num);
|
||||
params.add_uninitialized_single_output(GMutableSpan{type, buffer, voxels_num});
|
||||
}
|
||||
|
||||
/* Actually call the multi-function which will fill the temporary output buffers. */
|
||||
fn.call_auto(index_mask, params, context);
|
||||
|
||||
/* Copy the values from the temporary buffers into the output grids. */
|
||||
for (const int output_i : output_grids.index_range()) {
|
||||
openvdb::GridBase &grid_base = *output_grids[output_i];
|
||||
to_typed_grid(grid_base, [&](auto &grid) {
|
||||
using GridT = std::decay_t<decltype(grid)>;
|
||||
using ValueType = typename GridT::ValueType;
|
||||
const int param_index = input_values.size() + output_i;
|
||||
const ValueType *computed_values = static_cast<const ValueType *>(
|
||||
params.computed_array(param_index).data());
|
||||
|
||||
auto accessor = grid.getUnsafeAccessor();
|
||||
for (const int64_t i : IndexRange(voxels_num)) {
|
||||
const openvdb::Coord &coord = voxels[i];
|
||||
const ValueType &value = computed_values[i];
|
||||
accessor.setValue(coord, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the multi-function in a batch on all the given tiles. It is assumed that all input grids
|
||||
* are constant within the given tiles.
|
||||
*
|
||||
* \param fn: The multi-function to call.
|
||||
* \param input_values: All input values which may be grids, fields or single values.
|
||||
* \param input_grids: The input grids already extracted from #input_values.
|
||||
* \param output_grids: The output grids to be filled with the results of the multi-function. The
|
||||
* topology of these grids is initialized already.
|
||||
* \param transform: The transform of all input and output grids.
|
||||
* \param tiles: The tiles to process.
|
||||
*/
|
||||
BLI_NOINLINE static void process_tiles(const mf::MultiFunction &fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<const openvdb::GridBase *> input_grids,
|
||||
MutableSpan<openvdb::GridBase::Ptr> output_grids,
|
||||
const openvdb::math::Transform &transform,
|
||||
const Span<openvdb::CoordBBox> tiles)
|
||||
{
|
||||
const int64_t tiles_num = tiles.size();
|
||||
const IndexMask index_mask{tiles_num};
|
||||
|
||||
AlignedBuffer<8192, 8> allocation_buffer;
|
||||
ResourceScope scope;
|
||||
scope.allocator().provide_buffer(allocation_buffer);
|
||||
mf::ParamsBuilder params{fn, &index_mask};
|
||||
mf::ContextBuilder context;
|
||||
|
||||
for (const int input_i : input_values.index_range()) {
|
||||
const bke::SocketValueVariant &value_variant = *input_values[input_i];
|
||||
const mf::ParamType param_type = fn.param_type(params.next_param_index());
|
||||
const CPPType ¶m_cpp_type = param_type.data_type().single_type();
|
||||
|
||||
if (const openvdb::GridBase *grid_base = input_grids[input_i]) {
|
||||
/* Sample the tile values from the input grid. */
|
||||
to_typed_grid(*grid_base, [&](const auto &grid) {
|
||||
using GridT = std::decay_t<decltype(grid)>;
|
||||
using ValueType = typename GridT::ValueType;
|
||||
const auto &tree = grid.tree();
|
||||
auto accessor = grid.getConstUnsafeAccessor();
|
||||
|
||||
MutableSpan<ValueType> values = scope.allocator().allocate_array<ValueType>(tiles_num);
|
||||
for (const int64_t i : IndexRange(tiles_num)) {
|
||||
const openvdb::CoordBBox &tile = tiles[i];
|
||||
/* The tile is assumed to have a single constant value. Therefore, we can get the value
|
||||
* from any voxel in that tile as representative. */
|
||||
const openvdb::Coord any_coord_in_tile = tile.min();
|
||||
values[i] = tree.getValue(any_coord_in_tile, accessor);
|
||||
}
|
||||
BLI_assert(param_cpp_type.size == sizeof(ValueType));
|
||||
params.add_readonly_single_input(GSpan(param_cpp_type, values.data(), tiles_num));
|
||||
});
|
||||
}
|
||||
else if (value_variant.is_context_dependent_field()) {
|
||||
/* Evaluate the field on all tiles. */
|
||||
const fn::GField field = value_variant.get<fn::GField>();
|
||||
const CPPType &type = field.cpp_type();
|
||||
bke::TilesFieldContext field_context{transform, tiles};
|
||||
fn::FieldEvaluator evaluator{field_context, tiles_num};
|
||||
GMutableSpan values{type, scope.allocator().allocate_array(type, tiles_num), tiles_num};
|
||||
evaluator.add_with_destination(field, values);
|
||||
evaluator.evaluate();
|
||||
params.add_readonly_single_input(values);
|
||||
}
|
||||
else {
|
||||
/* Pass the single value directly to the multi-function. */
|
||||
params.add_readonly_single_input(value_variant.get_single_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare temporary output buffers for the field evaluation. Those will later be copied into the
|
||||
* output grids. */
|
||||
for ([[maybe_unused]] const int output_i : output_grids.index_range()) {
|
||||
const int param_index = input_values.size() + output_i;
|
||||
const mf::ParamType param_type = fn.param_type(param_index);
|
||||
const CPPType &type = param_type.data_type().single_type();
|
||||
void *buffer = scope.allocator().allocate_array(type, tiles_num);
|
||||
params.add_uninitialized_single_output(GMutableSpan{type, buffer, tiles_num});
|
||||
}
|
||||
|
||||
/* Actually call the multi-function which will fill the temporary output buffers. */
|
||||
fn.call_auto(index_mask, params, context);
|
||||
|
||||
/* Copy the values from the temporary buffers into the output grids. */
|
||||
for (const int output_i : output_grids.index_range()) {
|
||||
const int param_index = input_values.size() + output_i;
|
||||
openvdb::GridBase &grid_base = *output_grids[output_i];
|
||||
to_typed_grid(grid_base, [&](auto &grid) {
|
||||
using GridT = typename std::decay_t<decltype(grid)>;
|
||||
using TreeT = typename GridT::TreeType;
|
||||
using ValueType = typename GridT::ValueType;
|
||||
auto &tree = grid.tree();
|
||||
|
||||
const ValueType *computed_values = static_cast<const ValueType *>(
|
||||
params.computed_array(param_index).data());
|
||||
|
||||
const auto set_tile_value =
|
||||
[&](auto &node, const openvdb::Coord &coord_in_tile, auto value) {
|
||||
const openvdb::Index n = node.coordToOffset(coord_in_tile);
|
||||
BLI_assert(node.isChildMaskOff(n));
|
||||
/* TODO: Figure out how to do this without const_cast, although the same is done in
|
||||
* `openvdb_ax/openvdb_ax/compiler/VolumeExecutable.cc` which has a similar purpose.
|
||||
* It seems like OpenVDB generally allows that, but it does not have a proper public
|
||||
* API for this yet. */
|
||||
using UnionType = typename std::decay_t<decltype(node)>::UnionType;
|
||||
auto *table = const_cast<UnionType *>(node.getTable());
|
||||
table[n].setValue(value);
|
||||
};
|
||||
|
||||
for (const int i : IndexRange(tiles_num)) {
|
||||
const openvdb::CoordBBox tile = tiles[i];
|
||||
const openvdb::Coord coord_in_tile = tile.min();
|
||||
const auto &computed_value = computed_values[i];
|
||||
using InternalNode1 = typename TreeT::RootNodeType::ChildNodeType;
|
||||
using InternalNode2 = typename InternalNode1::ChildNodeType;
|
||||
/* Find the internal node that contains the tile and update the value in there. */
|
||||
if (auto *node = tree.template probeNode<InternalNode2>(coord_in_tile)) {
|
||||
set_tile_value(*node, coord_in_tile, computed_value);
|
||||
}
|
||||
else if (auto *node = tree.template probeNode<InternalNode1>(coord_in_tile)) {
|
||||
set_tile_value(*node, coord_in_tile, computed_value);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool execute_multi_function_on_value_variant__volume_grid(
|
||||
const mf::MultiFunction &fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<bke::SocketValueVariant *> output_values,
|
||||
std::string &r_error_message)
|
||||
{
|
||||
const int inputs_num = input_values.size();
|
||||
Array<bke::VolumeTreeAccessToken> input_volume_tokens(inputs_num);
|
||||
Array<const openvdb::GridBase *> input_grids(inputs_num, nullptr);
|
||||
|
||||
for (const int input_i : IndexRange(inputs_num)) {
|
||||
bke::SocketValueVariant &value_variant = *input_values[input_i];
|
||||
if (value_variant.is_volume_grid()) {
|
||||
const bke::GVolumeGrid g_volume_grid = value_variant.get<bke::GVolumeGrid>();
|
||||
input_grids[input_i] = &g_volume_grid->grid(input_volume_tokens[input_i]);
|
||||
}
|
||||
else if (value_variant.is_context_dependent_field()) {
|
||||
/* Nothing to do here. The field is evaluated later. */
|
||||
}
|
||||
else {
|
||||
value_variant.convert_to_single();
|
||||
}
|
||||
}
|
||||
|
||||
const openvdb::math::Transform *transform = nullptr;
|
||||
for (const openvdb::GridBase *grid : input_grids) {
|
||||
if (!grid) {
|
||||
continue;
|
||||
}
|
||||
const openvdb::math::Transform &other_transform = grid->transform();
|
||||
if (!transform) {
|
||||
transform = &other_transform;
|
||||
continue;
|
||||
}
|
||||
if (*transform != other_transform) {
|
||||
r_error_message = TIP_("Input grids have incompatible transforms");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (transform == nullptr) {
|
||||
r_error_message = TIP_("No input grid found that can determine the topology");
|
||||
return false;
|
||||
}
|
||||
|
||||
openvdb::MaskTree mask_tree;
|
||||
for (const openvdb::GridBase *grid : input_grids) {
|
||||
if (!grid) {
|
||||
continue;
|
||||
}
|
||||
to_typed_grid(*grid, [&](const auto &grid) { mask_tree.topologyUnion(grid.tree()); });
|
||||
}
|
||||
|
||||
Array<openvdb::GridBase::Ptr> output_grids(output_values.size());
|
||||
for (const int i : output_values.index_range()) {
|
||||
const int param_index = input_values.size() + i;
|
||||
const mf::ParamType param_type = fn.param_type(param_index);
|
||||
const CPPType &cpp_type = param_type.data_type().single_type();
|
||||
const std::optional<VolumeGridType> grid_type = cpp_type_to_grid_type(cpp_type);
|
||||
if (!grid_type) {
|
||||
r_error_message = TIP_("Grid type not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
openvdb::GridBase::Ptr grid;
|
||||
BKE_volume_grid_type_to_static_type(*grid_type, [&](auto type_tag) {
|
||||
using GridT = typename decltype(type_tag)::type;
|
||||
using TreeT = typename GridT::TreeType;
|
||||
using ValueType = typename TreeT::ValueType;
|
||||
const ValueType background{};
|
||||
auto tree = std::make_shared<TreeT>(mask_tree, background, openvdb::TopologyCopy());
|
||||
grid = openvdb::createGrid(std::move(tree));
|
||||
});
|
||||
|
||||
grid->setTransform(transform->copy());
|
||||
output_grids[i] = std::move(grid);
|
||||
}
|
||||
|
||||
parallel_grid_topology_tasks(
|
||||
mask_tree,
|
||||
[&](const LeafNodeMask &leaf_node_mask,
|
||||
const openvdb::CoordBBox &leaf_bbox,
|
||||
const GetVoxelsFn get_voxels_fn) {
|
||||
process_leaf_node(fn,
|
||||
input_values,
|
||||
input_grids,
|
||||
output_grids,
|
||||
*transform,
|
||||
leaf_node_mask,
|
||||
leaf_bbox,
|
||||
get_voxels_fn);
|
||||
},
|
||||
[&](const Span<openvdb::Coord> voxels) {
|
||||
process_voxels(fn, input_values, input_grids, output_grids, *transform, voxels);
|
||||
},
|
||||
[&](const Span<openvdb::CoordBBox> tiles) {
|
||||
process_tiles(fn, input_values, input_grids, output_grids, *transform, tiles);
|
||||
});
|
||||
|
||||
for (const int i : output_values.index_range()) {
|
||||
if (bke::SocketValueVariant *output_value = output_values[i]) {
|
||||
output_value->set(bke::GVolumeGrid(std::move(output_grids[i])));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
34
source/blender/nodes/intern/volume_grid_function_eval.hh
Normal file
34
source/blender/nodes/intern/volume_grid_function_eval.hh
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup nodes
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FN_multi_function.hh"
|
||||
|
||||
#include "NOD_geometry_exec.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Execute the multi-function with the given parameters. It is assumed that at least one of the
|
||||
* inputs is a grid. Otherwise the topology of the output grids is not known.
|
||||
*
|
||||
* \param fn: The multi-function to call.
|
||||
* \param input_values: All input values which may be grids, fields or single values.
|
||||
* \param output_values: Where the output grids will be stored.
|
||||
* \param r_error_message: An error message that is set if false is returned.
|
||||
*
|
||||
* \return False if an error occurred. In this case the output values should not be used.
|
||||
*/
|
||||
[[nodiscard]] bool execute_multi_function_on_value_variant__volume_grid(
|
||||
const mf::MultiFunction &fn,
|
||||
const Span<SocketValueVariant *> input_values,
|
||||
const Span<SocketValueVariant *> output_values,
|
||||
std::string &r_error_message);
|
||||
|
||||
} // namespace blender::nodes
|
||||
Reference in New Issue
Block a user