Geometry Nodes: improve geometry nodes evaluator internal api

This is a first step towards T87620.
It should not have any functional changes.

Goals of this refactor:
* Move the evaluator out of `MOD_nodes.cc`. That makes it easier to
  improve it in isolation.
* Extract core input/out parameter management out of `GeoNodeExecParams`.
  Managing this is the responsibility of the evaluator. This separation of
  concerns will be useful once we have lazy evaluation of certain inputs/outputs.

Differential Revision: https://developer.blender.org/D11085
This commit is contained in:
Jacques Lucke 2021-04-27 13:03:40 +02:00
parent a022cffb72
commit 908bb03630
9 changed files with 632 additions and 472 deletions

View File

@ -666,7 +666,7 @@ class CPPType : NonCopyable, NonMovable {
template<typename T> bool is() const
{
return this == &CPPType::get<T>();
return this == &CPPType::get<std::decay_t<T>>();
}
};

View File

@ -66,6 +66,16 @@ class GMutablePointer {
return type_ != nullptr && type_->is<T>();
}
template<typename T> T relocate_out()
{
BLI_assert(this->is_type<T>());
T value;
type_->relocate_to_initialized(data_, &value);
data_ = nullptr;
type_ = nullptr;
return value;
}
void destruct()
{
BLI_assert(data_ != nullptr);

View File

@ -93,6 +93,11 @@ template<typename Key> class GValueMap {
return values_.pop_as(key);
}
template<typename ForwardKey> GPointer lookup(const ForwardKey &key) const
{
return values_.lookup_as(key);
}
/* Remove the value for the given name from the container and remove it. */
template<typename T, typename ForwardKey> T extract(const ForwardKey &key)
{

View File

@ -79,6 +79,7 @@ set(SRC
intern/MOD_mirror.c
intern/MOD_multires.c
intern/MOD_nodes.cc
intern/MOD_nodes_evaluator.cc
intern/MOD_none.c
intern/MOD_normal_edit.c
intern/MOD_ocean.c
@ -118,6 +119,7 @@ set(SRC
MOD_modifiertypes.h
MOD_nodes.h
intern/MOD_meshcache_util.h
intern/MOD_nodes_evaluator.hh
intern/MOD_solidify_util.h
intern/MOD_ui_common.h
intern/MOD_util.h

View File

@ -75,16 +75,14 @@
#include "MOD_modifiertypes.h"
#include "MOD_nodes.h"
#include "MOD_nodes_evaluator.hh"
#include "MOD_ui_common.h"
#include "ED_spreadsheet.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry.h"
#include "NOD_geometry_exec.hh"
#include "NOD_node_tree_multi_function.hh"
#include "NOD_type_callbacks.hh"
#include "NOD_type_conversions.hh"
using blender::float3;
using blender::FunctionRef;
@ -100,7 +98,6 @@ using blender::bke::PersistentDataHandleMap;
using blender::bke::PersistentObjectHandle;
using blender::fn::GMutablePointer;
using blender::fn::GPointer;
using blender::fn::GValueMap;
using blender::nodes::GeoNodeExecParams;
using namespace blender::fn::multi_function_types;
using namespace blender::nodes::derived_node_tree_types;
@ -268,368 +265,6 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
return true;
}
class GeometryNodesEvaluator {
public:
using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>;
private:
blender::LinearAllocator<> allocator_;
Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_;
Vector<DInputSocket> group_outputs_;
blender::nodes::MultiFunctionByNode &mf_by_node_;
const blender::nodes::DataTypeConversions &conversions_;
const PersistentDataHandleMap &handle_map_;
const Object *self_object_;
const ModifierData *modifier_;
Depsgraph *depsgraph_;
LogSocketValueFn log_socket_value_fn_;
public:
GeometryNodesEvaluator(const Map<DOutputSocket, GMutablePointer> &group_input_data,
Vector<DInputSocket> group_outputs,
blender::nodes::MultiFunctionByNode &mf_by_node,
const PersistentDataHandleMap &handle_map,
const Object *self_object,
const ModifierData *modifier,
Depsgraph *depsgraph,
LogSocketValueFn log_socket_value_fn)
: group_outputs_(std::move(group_outputs)),
mf_by_node_(mf_by_node),
conversions_(blender::nodes::get_implicit_type_conversions()),
handle_map_(handle_map),
self_object_(self_object),
modifier_(modifier),
depsgraph_(depsgraph),
log_socket_value_fn_(std::move(log_socket_value_fn))
{
for (auto item : group_input_data.items()) {
this->log_socket_value(item.key, item.value);
this->forward_to_inputs(item.key, item.value);
}
}
Vector<GMutablePointer> execute()
{
Vector<GMutablePointer> results;
for (const DInputSocket &group_output : group_outputs_) {
Vector<GMutablePointer> result = this->get_input_values(group_output);
this->log_socket_value(group_output, result);
results.append(result[0]);
}
for (GMutablePointer value : value_by_input_.values()) {
value.destruct();
}
return results;
}
private:
Vector<GMutablePointer> get_input_values(const DInputSocket socket_to_compute)
{
Vector<DSocket> from_sockets;
socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); });
if (from_sockets.is_empty()) {
/* The input is not connected, use the value from the socket itself. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
return {get_unlinked_input_value(socket_to_compute, type)};
}
/* Multi-input sockets contain a vector of inputs. */
if (socket_to_compute->is_multi_input_socket()) {
return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets);
}
const DSocket from_socket = from_sockets[0];
GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket);
return {value};
}
Vector<GMutablePointer> get_inputs_from_incoming_links(const DInputSocket socket_to_compute,
const Span<DSocket> from_sockets)
{
Vector<GMutablePointer> values;
for (const int i : from_sockets.index_range()) {
const DSocket from_socket = from_sockets[i];
const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket);
if (first_occurence == -1) {
values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket));
}
else {
/* If the same from-socket occurs more than once, we make a copy of the first value. This
* can happen when a node linked to a multi-input-socket is muted. */
GMutablePointer value = values[first_occurence];
const CPPType *type = value.type();
void *copy_buffer = allocator_.allocate(type->size(), type->alignment());
type->copy_to_uninitialized(value.get(), copy_buffer);
values.append({type, copy_buffer});
}
}
return values;
}
GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute,
const DSocket from_socket)
{
if (from_socket->is_output()) {
const DOutputSocket from_output_socket{from_socket};
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(socket_to_compute,
from_output_socket);
std::optional<GMutablePointer> value = value_by_input_.pop_try(key);
if (value.has_value()) {
/* This input has been computed before, return it directly. */
return {*value};
}
/* Compute the socket now. */
this->compute_output_and_forward(from_output_socket);
return {value_by_input_.pop(key)};
}
/* Get value from an unlinked input socket. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
const DInputSocket from_input_socket{from_socket};
return {get_unlinked_input_value(from_input_socket, type)};
}
void compute_output_and_forward(const DOutputSocket socket_to_compute)
{
const DNode node{socket_to_compute.context(), &socket_to_compute->node()};
if (!socket_to_compute->is_available()) {
/* If the output is not available, use a default value. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
void *buffer = allocator_.allocate(type.size(), type.alignment());
type.copy_to_uninitialized(type.default_value(), buffer);
this->forward_to_inputs(socket_to_compute, {type, buffer});
return;
}
/* Prepare inputs required to execute the node. */
GValueMap<StringRef> node_inputs_map{allocator_};
for (const InputSocketRef *input_socket : node->inputs()) {
if (input_socket->is_available()) {
DInputSocket dsocket{node.context(), input_socket};
Vector<GMutablePointer> values = this->get_input_values(dsocket);
this->log_socket_value(dsocket, values);
for (int i = 0; i < values.size(); ++i) {
/* Values from Multi Input Sockets are stored in input map with the format
* <identifier>[<index>]. */
blender::StringRefNull key = allocator_.copy_string(
input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : ""));
node_inputs_map.add_new_direct(key, std::move(values[i]));
}
}
}
/* Execute the node. */
GValueMap<StringRef> node_outputs_map{allocator_};
GeoNodeExecParams params{
node, node_inputs_map, node_outputs_map, handle_map_, self_object_, modifier_, depsgraph_};
this->execute_node(node, params);
/* Forward computed outputs to linked input sockets. */
for (const OutputSocketRef *output_socket : node->outputs()) {
if (output_socket->is_available()) {
const DOutputSocket dsocket{node.context(), output_socket};
GMutablePointer value = node_outputs_map.extract(output_socket->identifier());
this->log_socket_value(dsocket, value);
this->forward_to_inputs(dsocket, value);
}
}
}
void log_socket_value(const DSocket socket, Span<GPointer> values)
{
if (log_socket_value_fn_) {
log_socket_value_fn_(socket, values);
}
}
void log_socket_value(const DSocket socket, Span<GMutablePointer> values)
{
this->log_socket_value(socket, values.cast<GPointer>());
}
void log_socket_value(const DSocket socket, GPointer value)
{
this->log_socket_value(socket, Span<GPointer>(&value, 1));
}
void execute_node(const DNode node, GeoNodeExecParams params)
{
const bNode &bnode = params.node();
/* Use the geometry-node-execute callback if it exists. */
if (bnode.typeinfo->geometry_node_execute != nullptr) {
bnode.typeinfo->geometry_node_execute(params);
return;
}
/* Use the multi-function implementation if it exists. */
const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr);
if (multi_function != nullptr) {
this->execute_multi_function_node(node, params, *multi_function);
return;
}
/* Just output default values if no implementation exists. */
this->execute_unknown_node(node, params);
}
void execute_multi_function_node(const DNode node,
GeoNodeExecParams params,
const MultiFunction &fn)
{
MFContextBuilder fn_context;
MFParamsBuilder fn_params{fn, 1};
Vector<GMutablePointer> input_data;
for (const InputSocketRef *socket_ref : node->inputs()) {
if (socket_ref->is_available()) {
GMutablePointer data = params.extract_input(socket_ref->identifier());
fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1));
input_data.append(data);
}
}
Vector<GMutablePointer> output_data;
for (const OutputSocketRef *socket_ref : node->outputs()) {
if (socket_ref->is_available()) {
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo());
void *buffer = allocator_.allocate(type.size(), type.alignment());
fn_params.add_uninitialized_single_output(GMutableSpan(type, buffer, 1));
output_data.append(GMutablePointer(type, buffer));
}
}
fn.call(IndexRange(1), fn_params, fn_context);
for (GMutablePointer value : input_data) {
value.destruct();
}
int output_index = 0;
for (const int i : node->outputs().index_range()) {
if (node->output(i).is_available()) {
GMutablePointer value = output_data[output_index];
params.set_output_by_move(node->output(i).identifier(), value);
value.destruct();
output_index++;
}
}
}
void execute_unknown_node(const DNode node, GeoNodeExecParams params)
{
for (const OutputSocketRef *socket : node->outputs()) {
if (socket->is_available()) {
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo());
params.set_output_by_copy(socket->identifier(), {type, type.default_value()});
}
}
}
void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward)
{
/* For all sockets that are linked with the from_socket push the value to their node. */
Vector<DInputSocket> to_sockets_all;
auto handle_target_socket_fn = [&](DInputSocket to_socket) {
to_sockets_all.append_non_duplicates(to_socket);
};
auto handle_skipped_socket_fn = [&, this](DSocket socket) {
this->log_socket_value(socket, value_to_forward);
};
from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn);
const CPPType &from_type = *value_to_forward.type();
Vector<DInputSocket> to_sockets_same_type;
for (const DInputSocket &to_socket : to_sockets_all) {
const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo());
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
if (from_type == to_type) {
to_sockets_same_type.append(to_socket);
}
else {
void *buffer = allocator_.allocate(to_type.size(), to_type.alignment());
if (conversions_.is_convertible(from_type, to_type)) {
conversions_.convert_to_uninitialized(
from_type, to_type, value_to_forward.get(), buffer);
}
else {
to_type.copy_to_uninitialized(to_type.default_value(), buffer);
}
add_value_to_input_socket(key, GMutablePointer{to_type, buffer});
}
}
if (to_sockets_same_type.size() == 0) {
/* This value is not further used, so destruct it. */
value_to_forward.destruct();
}
else if (to_sockets_same_type.size() == 1) {
/* This value is only used on one input socket, no need to copy it. */
const DInputSocket to_socket = to_sockets_same_type[0];
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
add_value_to_input_socket(key, value_to_forward);
}
else {
/* Multiple inputs use the value, make a copy for every input except for one. */
const DInputSocket first_to_socket = to_sockets_same_type[0];
Span<DInputSocket> other_to_sockets = to_sockets_same_type.as_span().drop_front(1);
const CPPType &type = *value_to_forward.type();
const std::pair<DInputSocket, DOutputSocket> first_key = std::make_pair(first_to_socket,
from_socket);
add_value_to_input_socket(first_key, value_to_forward);
for (const DInputSocket &to_socket : other_to_sockets) {
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
void *buffer = allocator_.allocate(type.size(), type.alignment());
type.copy_to_uninitialized(value_to_forward.get(), buffer);
add_value_to_input_socket(key, GMutablePointer{type, buffer});
}
}
}
void add_value_to_input_socket(const std::pair<DInputSocket, DOutputSocket> key,
GMutablePointer value)
{
value_by_input_.add_new(key, value);
}
GMutablePointer get_unlinked_input_value(const DInputSocket &socket,
const CPPType &required_type)
{
bNodeSocket *bsocket = socket->bsocket();
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo());
void *buffer = allocator_.allocate(type.size(), type.alignment());
if (bsocket->type == SOCK_OBJECT) {
Object *object = socket->default_value<bNodeSocketValueObject>()->value;
PersistentObjectHandle object_handle = handle_map_.lookup(object);
new (buffer) PersistentObjectHandle(object_handle);
}
else if (bsocket->type == SOCK_COLLECTION) {
Collection *collection = socket->default_value<bNodeSocketValueCollection>()->value;
PersistentCollectionHandle collection_handle = handle_map_.lookup(collection);
new (buffer) PersistentCollectionHandle(collection_handle);
}
else {
blender::nodes::socket_cpp_value_get(*bsocket, buffer);
}
if (type == required_type) {
return {type, buffer};
}
if (conversions_.is_convertible(type, required_type)) {
void *converted_buffer = allocator_.allocate(required_type.size(),
required_type.alignment());
conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer);
type.destruct(buffer);
return {required_type, converted_buffer};
}
void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment());
required_type.copy_to_uninitialized(required_type.default_value(), default_buffer);
return {required_type, default_buffer};
}
};
/**
* This code is responsible for creating the new property and also creating the group of
* properties in the prop_ui_container group for the UI info, the mapping for which is
@ -1297,21 +932,19 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
log_ui_hints(socket, values, ctx->object, nmd);
};
GeometryNodesEvaluator evaluator{group_inputs,
group_outputs,
mf_by_node,
handle_map,
ctx->object,
(ModifierData *)nmd,
ctx->depsgraph,
log_socket_value};
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
eval_params.input_values = group_inputs;
eval_params.output_sockets = group_outputs;
eval_params.mf_by_node = &mf_by_node;
eval_params.handle_map = &handle_map;
eval_params.modifier_ = nmd;
eval_params.depsgraph = ctx->depsgraph;
eval_params.log_socket_value_fn = log_socket_value;
blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params);
Vector<GMutablePointer> results = evaluator.execute();
BLI_assert(results.size() == 1);
GMutablePointer result = results[0];
GeometrySet output_geometry = std::move(*(GeometrySet *)result.get());
return output_geometry;
BLI_assert(eval_params.r_output_values.size() == 1);
GMutablePointer result = eval_params.r_output_values[0];
return result.relocate_out<GeometrySet>();
}
/**

View File

@ -0,0 +1,452 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "MOD_nodes_evaluator.hh"
#include "NOD_geometry_exec.hh"
#include "NOD_type_conversions.hh"
#include "DEG_depsgraph_query.h"
#include "FN_generic_value_map.hh"
#include "FN_multi_function.hh"
namespace blender::modifiers::geometry_nodes {
using bke::PersistentCollectionHandle;
using bke::PersistentObjectHandle;
using fn::CPPType;
using fn::GValueMap;
using nodes::GeoNodeExecParams;
using namespace fn::multi_function_types;
class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider {
public:
LinearAllocator<> *allocator;
GValueMap<StringRef> *input_values;
GValueMap<StringRef> *output_values;
bool can_get_input(StringRef identifier) const override
{
return input_values->contains(identifier);
}
bool can_set_output(StringRef identifier) const override
{
return !output_values->contains(identifier);
}
GMutablePointer extract_input(StringRef identifier) override
{
return this->input_values->extract(identifier);
}
Vector<GMutablePointer> extract_multi_input(StringRef identifier) override
{
Vector<GMutablePointer> values;
int index = 0;
while (true) {
std::string sub_identifier = identifier;
if (index > 0) {
sub_identifier += "[" + std::to_string(index) + "]";
}
if (!this->input_values->contains(sub_identifier)) {
break;
}
values.append(input_values->extract(sub_identifier));
index++;
}
return values;
}
GPointer get_input(StringRef identifier) const override
{
return this->input_values->lookup(identifier);
}
GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) override
{
void *buffer = this->allocator->allocate(type.size(), type.alignment());
GMutablePointer ptr{&type, buffer};
this->output_values->add_new_direct(identifier, ptr);
return ptr;
}
};
class GeometryNodesEvaluator {
public:
using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>;
private:
blender::LinearAllocator<> &allocator_;
Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_;
Vector<DInputSocket> group_outputs_;
blender::nodes::MultiFunctionByNode &mf_by_node_;
const blender::nodes::DataTypeConversions &conversions_;
const PersistentDataHandleMap &handle_map_;
const Object *self_object_;
const ModifierData *modifier_;
Depsgraph *depsgraph_;
LogSocketValueFn log_socket_value_fn_;
public:
GeometryNodesEvaluator(GeometryNodesEvaluationParams &params)
: allocator_(params.allocator),
group_outputs_(std::move(params.output_sockets)),
mf_by_node_(*params.mf_by_node),
conversions_(blender::nodes::get_implicit_type_conversions()),
handle_map_(*params.handle_map),
self_object_(params.self_object),
modifier_(&params.modifier_->modifier),
depsgraph_(params.depsgraph),
log_socket_value_fn_(std::move(params.log_socket_value_fn))
{
for (auto item : params.input_values.items()) {
this->log_socket_value(item.key, item.value);
this->forward_to_inputs(item.key, item.value);
}
}
Vector<GMutablePointer> execute()
{
Vector<GMutablePointer> results;
for (const DInputSocket &group_output : group_outputs_) {
Vector<GMutablePointer> result = this->get_input_values(group_output);
this->log_socket_value(group_output, result);
results.append(result[0]);
}
for (GMutablePointer value : value_by_input_.values()) {
value.destruct();
}
return results;
}
private:
Vector<GMutablePointer> get_input_values(const DInputSocket socket_to_compute)
{
Vector<DSocket> from_sockets;
socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); });
if (from_sockets.is_empty()) {
/* The input is not connected, use the value from the socket itself. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
return {get_unlinked_input_value(socket_to_compute, type)};
}
/* Multi-input sockets contain a vector of inputs. */
if (socket_to_compute->is_multi_input_socket()) {
return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets);
}
const DSocket from_socket = from_sockets[0];
GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket);
return {value};
}
Vector<GMutablePointer> get_inputs_from_incoming_links(const DInputSocket socket_to_compute,
const Span<DSocket> from_sockets)
{
Vector<GMutablePointer> values;
for (const int i : from_sockets.index_range()) {
const DSocket from_socket = from_sockets[i];
const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket);
if (first_occurence == -1) {
values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket));
}
else {
/* If the same from-socket occurs more than once, we make a copy of the first value. This
* can happen when a node linked to a multi-input-socket is muted. */
GMutablePointer value = values[first_occurence];
const CPPType *type = value.type();
void *copy_buffer = allocator_.allocate(type->size(), type->alignment());
type->copy_to_uninitialized(value.get(), copy_buffer);
values.append({type, copy_buffer});
}
}
return values;
}
GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute,
const DSocket from_socket)
{
if (from_socket->is_output()) {
const DOutputSocket from_output_socket{from_socket};
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(socket_to_compute,
from_output_socket);
std::optional<GMutablePointer> value = value_by_input_.pop_try(key);
if (value.has_value()) {
/* This input has been computed before, return it directly. */
return {*value};
}
/* Compute the socket now. */
this->compute_output_and_forward(from_output_socket);
return {value_by_input_.pop(key)};
}
/* Get value from an unlinked input socket. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
const DInputSocket from_input_socket{from_socket};
return {get_unlinked_input_value(from_input_socket, type)};
}
void compute_output_and_forward(const DOutputSocket socket_to_compute)
{
const DNode node{socket_to_compute.context(), &socket_to_compute->node()};
if (!socket_to_compute->is_available()) {
/* If the output is not available, use a default value. */
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo());
void *buffer = allocator_.allocate(type.size(), type.alignment());
type.copy_to_uninitialized(type.default_value(), buffer);
this->forward_to_inputs(socket_to_compute, {type, buffer});
return;
}
/* Prepare inputs required to execute the node. */
GValueMap<StringRef> node_inputs_map{allocator_};
for (const InputSocketRef *input_socket : node->inputs()) {
if (input_socket->is_available()) {
DInputSocket dsocket{node.context(), input_socket};
Vector<GMutablePointer> values = this->get_input_values(dsocket);
this->log_socket_value(dsocket, values);
for (int i = 0; i < values.size(); ++i) {
/* Values from Multi Input Sockets are stored in input map with the format
* <identifier>[<index>]. */
blender::StringRefNull key = allocator_.copy_string(
input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : ""));
node_inputs_map.add_new_direct(key, std::move(values[i]));
}
}
}
/* Execute the node. */
GValueMap<StringRef> node_outputs_map{allocator_};
NodeParamsProvider params_provider;
params_provider.dnode = node;
params_provider.handle_map = &handle_map_;
params_provider.self_object = self_object_;
params_provider.depsgraph = depsgraph_;
params_provider.allocator = &allocator_;
params_provider.input_values = &node_inputs_map;
params_provider.output_values = &node_outputs_map;
params_provider.modifier = modifier_;
this->execute_node(node, params_provider);
/* Forward computed outputs to linked input sockets. */
for (const OutputSocketRef *output_socket : node->outputs()) {
if (output_socket->is_available()) {
const DOutputSocket dsocket{node.context(), output_socket};
GMutablePointer value = node_outputs_map.extract(output_socket->identifier());
this->log_socket_value(dsocket, value);
this->forward_to_inputs(dsocket, value);
}
}
}
void log_socket_value(const DSocket socket, Span<GPointer> values)
{
if (log_socket_value_fn_) {
log_socket_value_fn_(socket, values);
}
}
void log_socket_value(const DSocket socket, Span<GMutablePointer> values)
{
this->log_socket_value(socket, values.cast<GPointer>());
}
void log_socket_value(const DSocket socket, GPointer value)
{
this->log_socket_value(socket, Span<GPointer>(&value, 1));
}
void execute_node(const DNode node, NodeParamsProvider &params_provider)
{
const bNode &bnode = *params_provider.dnode->bnode();
/* Use the geometry-node-execute callback if it exists. */
if (bnode.typeinfo->geometry_node_execute != nullptr) {
GeoNodeExecParams params{params_provider};
bnode.typeinfo->geometry_node_execute(params);
return;
}
/* Use the multi-function implementation if it exists. */
const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr);
if (multi_function != nullptr) {
this->execute_multi_function_node(node, params_provider, *multi_function);
return;
}
/* Just output default values if no implementation exists. */
this->execute_unknown_node(node, params_provider);
}
void execute_multi_function_node(const DNode node,
NodeParamsProvider &params_provider,
const MultiFunction &fn)
{
MFContextBuilder fn_context;
MFParamsBuilder fn_params{fn, 1};
Vector<GMutablePointer> input_data;
for (const InputSocketRef *socket_ref : node->inputs()) {
if (socket_ref->is_available()) {
GMutablePointer data = params_provider.extract_input(socket_ref->identifier());
fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1));
input_data.append(data);
}
}
Vector<GMutablePointer> output_data;
for (const OutputSocketRef *socket_ref : node->outputs()) {
if (socket_ref->is_available()) {
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo());
GMutablePointer output_value = params_provider.alloc_output_value(socket_ref->identifier(),
type);
fn_params.add_uninitialized_single_output(GMutableSpan{type, output_value.get(), 1});
output_data.append(output_value);
}
}
fn.call(IndexRange(1), fn_params, fn_context);
for (GMutablePointer value : input_data) {
value.destruct();
}
}
void execute_unknown_node(const DNode node, NodeParamsProvider &params_provider)
{
for (const OutputSocketRef *socket : node->outputs()) {
if (socket->is_available()) {
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo());
params_provider.output_values->add_new_by_copy(socket->identifier(),
{type, type.default_value()});
}
}
}
void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward)
{
/* For all sockets that are linked with the from_socket push the value to their node. */
Vector<DInputSocket> to_sockets_all;
auto handle_target_socket_fn = [&](DInputSocket to_socket) {
to_sockets_all.append_non_duplicates(to_socket);
};
auto handle_skipped_socket_fn = [&, this](DSocket socket) {
this->log_socket_value(socket, value_to_forward);
};
from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn);
const CPPType &from_type = *value_to_forward.type();
Vector<DInputSocket> to_sockets_same_type;
for (const DInputSocket &to_socket : to_sockets_all) {
const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo());
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
if (from_type == to_type) {
to_sockets_same_type.append(to_socket);
}
else {
void *buffer = allocator_.allocate(to_type.size(), to_type.alignment());
if (conversions_.is_convertible(from_type, to_type)) {
conversions_.convert_to_uninitialized(
from_type, to_type, value_to_forward.get(), buffer);
}
else {
to_type.copy_to_uninitialized(to_type.default_value(), buffer);
}
add_value_to_input_socket(key, GMutablePointer{to_type, buffer});
}
}
if (to_sockets_same_type.size() == 0) {
/* This value is not further used, so destruct it. */
value_to_forward.destruct();
}
else if (to_sockets_same_type.size() == 1) {
/* This value is only used on one input socket, no need to copy it. */
const DInputSocket to_socket = to_sockets_same_type[0];
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
add_value_to_input_socket(key, value_to_forward);
}
else {
/* Multiple inputs use the value, make a copy for every input except for one. */
const DInputSocket first_to_socket = to_sockets_same_type[0];
Span<DInputSocket> other_to_sockets = to_sockets_same_type.as_span().drop_front(1);
const CPPType &type = *value_to_forward.type();
const std::pair<DInputSocket, DOutputSocket> first_key = std::make_pair(first_to_socket,
from_socket);
add_value_to_input_socket(first_key, value_to_forward);
for (const DInputSocket &to_socket : other_to_sockets) {
const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket);
void *buffer = allocator_.allocate(type.size(), type.alignment());
type.copy_to_uninitialized(value_to_forward.get(), buffer);
add_value_to_input_socket(key, GMutablePointer{type, buffer});
}
}
}
void add_value_to_input_socket(const std::pair<DInputSocket, DOutputSocket> key,
GMutablePointer value)
{
value_by_input_.add_new(key, value);
}
GMutablePointer get_unlinked_input_value(const DInputSocket &socket,
const CPPType &required_type)
{
bNodeSocket *bsocket = socket->bsocket();
const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo());
void *buffer = allocator_.allocate(type.size(), type.alignment());
if (bsocket->type == SOCK_OBJECT) {
Object *object = socket->default_value<bNodeSocketValueObject>()->value;
PersistentObjectHandle object_handle = handle_map_.lookup(object);
new (buffer) PersistentObjectHandle(object_handle);
}
else if (bsocket->type == SOCK_COLLECTION) {
Collection *collection = socket->default_value<bNodeSocketValueCollection>()->value;
PersistentCollectionHandle collection_handle = handle_map_.lookup(collection);
new (buffer) PersistentCollectionHandle(collection_handle);
}
else {
blender::nodes::socket_cpp_value_get(*bsocket, buffer);
}
if (type == required_type) {
return {type, buffer};
}
if (conversions_.is_convertible(type, required_type)) {
void *converted_buffer = allocator_.allocate(required_type.size(),
required_type.alignment());
conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer);
type.destruct(buffer);
return {required_type, converted_buffer};
}
void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment());
required_type.copy_to_uninitialized(required_type.default_value(), default_buffer);
return {required_type, default_buffer};
}
};
void evaluate_geometry_nodes(GeometryNodesEvaluationParams &params)
{
GeometryNodesEvaluator evaluator{params};
params.r_output_values = evaluator.execute();
}
} // namespace blender::modifiers::geometry_nodes

View File

@ -0,0 +1,56 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include "BLI_map.hh"
#include "NOD_derived_node_tree.hh"
#include "NOD_node_tree_multi_function.hh"
#include "FN_generic_pointer.hh"
#include "BKE_persistent_data_handle.hh"
#include "DNA_modifier_types.h"
namespace blender::modifiers::geometry_nodes {
using namespace nodes::derived_node_tree_types;
using bke::PersistentDataHandleMap;
using fn::GMutablePointer;
using fn::GPointer;
using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>;
struct GeometryNodesEvaluationParams {
blender::LinearAllocator<> allocator;
Map<DOutputSocket, GMutablePointer> input_values;
Vector<DInputSocket> output_sockets;
nodes::MultiFunctionByNode *mf_by_node;
const PersistentDataHandleMap *handle_map;
const NodesModifierData *modifier_;
Depsgraph *depsgraph;
Object *self_object;
LogSocketValueFn log_socket_value_fn;
Vector<GMutablePointer> r_output_values;
};
void evaluate_geometry_nodes(GeometryNodesEvaluationParams &params);
} // namespace blender::modifiers::geometry_nodes

View File

@ -56,31 +56,59 @@ using fn::GVMutableArray_GSpan;
using fn::GVMutableArray_Typed;
using fn::GVMutableArrayPtr;
/**
* This class exists to separate the memory management details of the geometry nodes evaluator from
* the node execution functions and related utilities.
*/
class GeoNodeExecParamsProvider {
public:
DNode dnode;
const PersistentDataHandleMap *handle_map = nullptr;
const Object *self_object = nullptr;
const ModifierData *modifier = nullptr;
Depsgraph *depsgraph = nullptr;
/**
* Returns true when the node is allowed to get/extract the input value. The identifier is
* expected to be valid. This may return false if the input value has been consumed already.
*/
virtual bool can_get_input(StringRef identifier) const = 0;
/**
* Returns true when the node is allowed to set the output value. The identifier is expected to
* be valid. This may return false if the output value has been set already.
*/
virtual bool can_set_output(StringRef identifier) const = 0;
/**
* Take ownership of an input value. The caller is responsible for destructing the value. It does
* not have to be freed, because the memory is managed by the geometry nodes evaluator.
*/
virtual GMutablePointer extract_input(StringRef identifier) = 0;
/**
* Similar to #extract_input, but has to be used for multi-input sockets.
*/
virtual Vector<GMutablePointer> extract_multi_input(StringRef identifier) = 0;
/**
* Get the input value for the identifier without taking ownership of it.
*/
virtual GPointer get_input(StringRef identifier) const = 0;
/**
* Prepare a memory buffer for an output value of the node. The returned memory has to be
* initialized by the caller. The identifier and type are expected to be correct.
*/
virtual GMutablePointer alloc_output_value(StringRef identifier, const CPPType &type) = 0;
};
class GeoNodeExecParams {
private:
const DNode node_;
GValueMap<StringRef> &input_values_;
GValueMap<StringRef> &output_values_;
const PersistentDataHandleMap &handle_map_;
const Object *self_object_;
const ModifierData *modifier_;
Depsgraph *depsgraph_;
GeoNodeExecParamsProvider *provider_;
public:
GeoNodeExecParams(const DNode node,
GValueMap<StringRef> &input_values,
GValueMap<StringRef> &output_values,
const PersistentDataHandleMap &handle_map,
const Object *self_object,
const ModifierData *modifier,
Depsgraph *depsgraph)
: node_(node),
input_values_(input_values),
output_values_(output_values),
handle_map_(handle_map),
self_object_(self_object),
modifier_(modifier),
depsgraph_(depsgraph)
GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider)
{
}
@ -93,9 +121,9 @@ class GeoNodeExecParams {
GMutablePointer extract_input(StringRef identifier)
{
#ifdef DEBUG
this->check_extract_input(identifier);
this->check_input_access(identifier);
#endif
return input_values_.extract(identifier);
return provider_->extract_input(identifier);
}
/**
@ -106,9 +134,10 @@ class GeoNodeExecParams {
template<typename T> T extract_input(StringRef identifier)
{
#ifdef DEBUG
this->check_extract_input(identifier, &CPPType::get<T>());
this->check_input_access(identifier, &CPPType::get<T>());
#endif
return input_values_.extract<T>(identifier);
GMutablePointer gvalue = this->extract_input(identifier);
return gvalue.relocate_out<T>();
}
/**
@ -118,18 +147,10 @@ class GeoNodeExecParams {
*/
template<typename T> Vector<T> extract_multi_input(StringRef identifier)
{
Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier);
Vector<T> values;
int index = 0;
while (true) {
std::string sub_identifier = identifier;
if (index > 0) {
sub_identifier += "[" + std::to_string(index) + "]";
}
if (!input_values_.contains(sub_identifier)) {
break;
}
values.append(input_values_.extract<T, StringRef>(sub_identifier));
index++;
for (GMutablePointer gvalue : gvalues) {
values.append(gvalue.relocate_out<T>());
}
return values;
}
@ -140,33 +161,11 @@ class GeoNodeExecParams {
template<typename T> const T &get_input(StringRef identifier) const
{
#ifdef DEBUG
this->check_extract_input(identifier, &CPPType::get<T>());
this->check_input_access(identifier, &CPPType::get<T>());
#endif
return input_values_.lookup<T>(identifier);
}
/**
* Move-construct a new value based on the given value and store it for the given socket
* identifier.
*/
void set_output_by_move(StringRef identifier, GMutablePointer value)
{
#ifdef DEBUG
BLI_assert(value.type() != nullptr);
BLI_assert(value.get() != nullptr);
this->check_set_output(identifier, *value.type());
#endif
output_values_.add_new_by_move(identifier, value);
}
void set_output_by_copy(StringRef identifier, GPointer value)
{
#ifdef DEBUG
BLI_assert(value.type() != nullptr);
BLI_assert(value.get() != nullptr);
this->check_set_output(identifier, *value.type());
#endif
output_values_.add_new_by_copy(identifier, value);
GPointer gvalue = provider_->get_input(identifier);
BLI_assert(gvalue.is_type<T>());
return *(const T *)gvalue.get();
}
/**
@ -174,10 +173,13 @@ class GeoNodeExecParams {
*/
template<typename T> void set_output(StringRef identifier, T &&value)
{
using StoredT = std::decay_t<T>;
const CPPType &type = CPPType::get<std::decay_t<T>>();
#ifdef DEBUG
this->check_set_output(identifier, CPPType::get<std::decay_t<T>>());
this->check_output_access(identifier, type);
#endif
output_values_.add_new(identifier, std::forward<T>(value));
GMutablePointer gvalue = provider_->alloc_output_value(identifier, type);
new (gvalue.get()) StoredT(std::forward<T>(value));
}
/**
@ -185,22 +187,22 @@ class GeoNodeExecParams {
*/
const bNode &node() const
{
return *node_->bnode();
return *provider_->dnode->bnode();
}
const PersistentDataHandleMap &handle_map() const
{
return handle_map_;
return *provider_->handle_map;
}
const Object *self_object() const
{
return self_object_;
return provider_->self_object;
}
Depsgraph *depsgraph() const
{
return depsgraph_;
return provider_->depsgraph;
}
/**
@ -247,8 +249,8 @@ class GeoNodeExecParams {
private:
/* Utilities for detecting common errors at when using this class. */
void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const;
void check_set_output(StringRef identifier, const CPPType &value_type) const;
void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const;
void check_output_access(StringRef identifier, const CPPType &value_type) const;
/* Find the active socket socket with the input name (not the identifier). */
const bNodeSocket *find_available_socket(const StringRef name) const;

View File

@ -30,22 +30,22 @@ namespace blender::nodes {
void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const
{
bNodeTree *btree_cow = node_->btree();
bNodeTree *btree_cow = provider_->dnode->btree();
BLI_assert(btree_cow != nullptr);
if (btree_cow == nullptr) {
return;
}
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
const NodeTreeEvaluationContext context(*self_object_, *modifier_);
const NodeTreeEvaluationContext context(*provider_->self_object, *provider_->modifier);
BKE_nodetree_error_message_add(
*btree_original, context, *node_->bnode(), type, std::move(message));
*btree_original, context, *provider_->dnode->bnode(), type, std::move(message));
}
const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const
{
for (const InputSocketRef *socket : node_->inputs()) {
for (const InputSocketRef *socket : provider_->dnode->inputs()) {
if (socket->is_available() && socket->name() == name) {
return socket->bsocket();
}
@ -183,11 +183,11 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain(
return default_domain;
}
void GeoNodeExecParams::check_extract_input(StringRef identifier,
const CPPType *requested_type) const
void GeoNodeExecParams::check_input_access(StringRef identifier,
const CPPType *requested_type) const
{
bNodeSocket *found_socket = nullptr;
for (const InputSocketRef *socket : node_->inputs()) {
for (const InputSocketRef *socket : provider_->dnode->inputs()) {
if (socket->identifier() == identifier) {
found_socket = socket->bsocket();
break;
@ -197,39 +197,39 @@ void GeoNodeExecParams::check_extract_input(StringRef identifier,
if (found_socket == nullptr) {
std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n";
std::cout << "Possible identifiers are: ";
for (const InputSocketRef *socket : node_->inputs()) {
for (const InputSocketRef *socket : provider_->dnode->inputs()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier() << "', ";
}
}
std::cout << "\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else if (found_socket->flag & SOCK_UNAVAIL) {
std::cout << "The socket corresponding to the identifier '" << identifier
<< "' is disabled.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else if (!input_values_.contains(identifier)) {
else if (!provider_->can_get_input(identifier)) {
std::cout << "The identifier '" << identifier
<< "' is valid, but there is no value for it anymore.\n";
std::cout << "Most likely it has been extracted before.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else if (requested_type != nullptr) {
const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo);
if (*requested_type != expected_type) {
std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '"
<< expected_type.name() << "'.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
}
}
void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const
void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const
{
bNodeSocket *found_socket = nullptr;
for (const OutputSocketRef *socket : node_->outputs()) {
for (const OutputSocketRef *socket : provider_->dnode->outputs()) {
if (socket->identifier() == identifier) {
found_socket = socket->bsocket();
break;
@ -239,29 +239,29 @@ void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &va
if (found_socket == nullptr) {
std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n";
std::cout << "Possible identifiers are: ";
for (const OutputSocketRef *socket : node_->outputs()) {
for (const OutputSocketRef *socket : provider_->dnode->outputs()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier() << "', ";
}
}
std::cout << "\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else if (found_socket->flag & SOCK_UNAVAIL) {
std::cout << "The socket corresponding to the identifier '" << identifier
<< "' is disabled.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else if (output_values_.contains(identifier)) {
else if (!provider_->can_set_output(identifier)) {
std::cout << "The identifier '" << identifier << "' has been set already.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
else {
const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo);
if (value_type != expected_type) {
std::cout << "The value type '" << value_type.name() << "' is incorrect. Expected '"
<< expected_type.name() << "'.\n";
BLI_assert(false);
BLI_assert_unreachable();
}
}
}