Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/modifiers/intern/MOD_nodes_evaluator.cc')
-rw-r--r--source/blender/modifiers/intern/MOD_nodes_evaluator.cc363
1 files changed, 256 insertions, 107 deletions
diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
index 094649baa87..33a5da7ccb7 100644
--- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
+++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
@@ -35,13 +35,17 @@
#include "BLI_task.hh"
#include "BLI_vector_set.hh"
+#include <chrono>
+
namespace blender::modifiers::geometry_nodes {
using fn::CPPType;
using fn::Field;
-using fn::FieldCPPType;
using fn::GField;
using fn::GValueMap;
+using fn::GVArray;
+using fn::ValueOrField;
+using fn::ValueOrFieldCPPType;
using nodes::GeoNodeExecParams;
using namespace fn::multi_function_types;
@@ -309,10 +313,10 @@ class LockedNode : NonCopyable, NonMovable {
static const CPPType *get_socket_cpp_type(const SocketRef &socket)
{
const bNodeSocketType *typeinfo = socket.typeinfo();
- if (typeinfo->get_geometry_nodes_cpp_type == nullptr) {
+ if (typeinfo->geometry_nodes_cpp_type == nullptr) {
return nullptr;
}
- const CPPType *type = typeinfo->get_geometry_nodes_cpp_type();
+ const CPPType *type = typeinfo->geometry_nodes_cpp_type;
if (type == nullptr) {
return nullptr;
}
@@ -348,18 +352,19 @@ static bool get_implicit_socket_input(const SocketRef &socket, void *r_value)
GEO_NODE_CURVE_HANDLE_LEFT ?
"handle_left" :
"handle_right";
- new (r_value) Field<float3>(bke::AttributeFieldInput::Create<float3>(side));
+ new (r_value) ValueOrField<float3>(bke::AttributeFieldInput::Create<float3>(side));
return true;
}
- new (r_value) Field<float3>(bke::AttributeFieldInput::Create<float3>("position"));
+ new (r_value) ValueOrField<float3>(bke::AttributeFieldInput::Create<float3>("position"));
return true;
}
if (socket.typeinfo()->type == SOCK_INT) {
if (ELEM(bnode.type, FN_NODE_RANDOM_VALUE, GEO_NODE_INSTANCE_ON_POINTS)) {
- new (r_value) Field<int>(std::make_shared<bke::IDAttributeFieldInput>());
+ new (r_value)
+ ValueOrField<int>(Field<int>(std::make_shared<bke::IDAttributeFieldInput>()));
return true;
}
- new (r_value) Field<int>(std::make_shared<fn::IndexFieldInput>());
+ new (r_value) ValueOrField<int>(Field<int>(std::make_shared<fn::IndexFieldInput>()));
return true;
}
}
@@ -381,14 +386,23 @@ static bool node_supports_laziness(const DNode node)
return node->typeinfo()->geometry_node_execute_supports_laziness;
}
+struct NodeTaskRunState {
+ /** The node that should be run on the same thread after the current node finished. */
+ DNode next_node_to_run;
+};
+
/** Implements the callbacks that might be called when a node is executed. */
class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider {
private:
GeometryNodesEvaluator &evaluator_;
NodeState &node_state_;
+ NodeTaskRunState *run_state_;
public:
- NodeParamsProvider(GeometryNodesEvaluator &evaluator, DNode dnode, NodeState &node_state);
+ NodeParamsProvider(GeometryNodesEvaluator &evaluator,
+ DNode dnode,
+ NodeState &node_state,
+ NodeTaskRunState *run_state);
bool can_get_input(StringRef identifier) const override;
bool can_set_output(StringRef identifier) const override;
@@ -640,7 +654,7 @@ class GeometryNodesEvaluator {
value.destruct();
continue;
}
- this->forward_output(socket, value);
+ this->forward_output(socket, value, nullptr);
}
}
@@ -649,7 +663,7 @@ class GeometryNodesEvaluator {
for (const DInputSocket &socket : params_.output_sockets) {
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, nullptr, [&](LockedNode &locked_node) {
/* Setting an input as required will schedule any linked node. */
this->set_input_required(locked_node, socket);
});
@@ -657,7 +671,7 @@ class GeometryNodesEvaluator {
for (const DSocket socket : params_.force_compute_sockets) {
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, nullptr, [&](LockedNode &locked_node) {
if (socket->is_input()) {
this->set_input_required(locked_node, DInputSocket(socket));
}
@@ -702,12 +716,24 @@ class GeometryNodesEvaluator {
{
void *user_data = BLI_task_pool_user_data(task_pool);
GeometryNodesEvaluator &evaluator = *(GeometryNodesEvaluator *)user_data;
- const NodeWithState *node_with_state = (const NodeWithState *)task_data;
-
- evaluator.node_task_run(node_with_state->node, *node_with_state->state);
+ const NodeWithState *root_node_with_state = (const NodeWithState *)task_data;
+
+ /* First, the node provided by the task pool is executed. During the execution other nodes
+ * might be scheduled. One of those nodes is not added to the task pool but is executed in the
+ * loop below directly. This has two main benefits:
+ * - Fewer round trips through the task pool which add threading overhead.
+ * - Helps with cpu cache efficiency, because a thread is more likely to process data that it
+ * has processed shortly before.
+ */
+ DNode next_node_to_run = root_node_with_state->node;
+ while (next_node_to_run) {
+ NodeTaskRunState run_state;
+ evaluator.node_task_run(next_node_to_run, &run_state);
+ next_node_to_run = run_state.next_node_to_run;
+ }
}
- void node_task_run(const DNode node, NodeState &node_state)
+ void node_task_run(const DNode node, NodeTaskRunState *run_state)
{
/* These nodes are sometimes scheduled. We could also check for them in other places, but
* it's the easiest to do it here. */
@@ -715,21 +741,25 @@ class GeometryNodesEvaluator {
return;
}
- const bool do_execute_node = this->node_task_preprocessing(node, node_state);
+ NodeState &node_state = *node_states_.lookup_key_as(node).state;
+
+ const bool do_execute_node = this->node_task_preprocessing(node, node_state, run_state);
/* Only execute the node if all prerequisites are met. There has to be an output that is
* required and all required inputs have to be provided already. */
if (do_execute_node) {
- this->execute_node(node, node_state);
+ this->execute_node(node, node_state, run_state);
}
- this->node_task_postprocessing(node, node_state, do_execute_node);
+ this->node_task_postprocessing(node, node_state, do_execute_node, run_state);
}
- bool node_task_preprocessing(const DNode node, NodeState &node_state)
+ bool node_task_preprocessing(const DNode node,
+ NodeState &node_state,
+ NodeTaskRunState *run_state)
{
bool do_execute_node = false;
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, run_state, [&](LockedNode &locked_node) {
BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled);
node_state.schedule_state = NodeScheduleState::Running;
@@ -888,7 +918,7 @@ class GeometryNodesEvaluator {
* Actually execute the node. All the required inputs are available and at least one output is
* required.
*/
- void execute_node(const DNode node, NodeState &node_state)
+ void execute_node(const DNode node, NodeState &node_state, NodeTaskRunState *run_state)
{
const bNode &bnode = *node->bnode();
@@ -902,40 +932,49 @@ class GeometryNodesEvaluator {
/* Use the geometry node execute callback if it exists. */
if (bnode.typeinfo->geometry_node_execute != nullptr) {
- this->execute_geometry_node(node, node_state);
+ this->execute_geometry_node(node, node_state, run_state);
return;
}
/* Use the multi-function implementation if it exists. */
const nodes::NodeMultiFunctions::Item &fn_item = params_.mf_by_node->try_get(node);
if (fn_item.fn != nullptr) {
- this->execute_multi_function_node(node, fn_item, node_state);
+ this->execute_multi_function_node(node, fn_item, node_state, run_state);
return;
}
- this->execute_unknown_node(node, node_state);
+ this->execute_unknown_node(node, node_state, run_state);
}
- void execute_geometry_node(const DNode node, NodeState &node_state)
+ void execute_geometry_node(const DNode node, NodeState &node_state, NodeTaskRunState *run_state)
{
const bNode &bnode = *node->bnode();
- NodeParamsProvider params_provider{*this, node, node_state};
+ NodeParamsProvider params_provider{*this, node, node_state, run_state};
GeoNodeExecParams params{params_provider};
if (node->idname().find("Legacy") != StringRef::not_found) {
params.error_message_add(geo_log::NodeWarningType::Legacy,
TIP_("Legacy node will be removed before Blender 4.0"));
}
+ using Clock = std::chrono::steady_clock;
+ Clock::time_point begin = Clock::now();
bnode.typeinfo->geometry_node_execute(params);
+ Clock::time_point end = Clock::now();
+ const std::chrono::microseconds duration =
+ std::chrono::duration_cast<std::chrono::microseconds>(end - begin);
+ if (params_.geo_logger != nullptr) {
+ params_.geo_logger->local().log_execution_time(node, duration);
+ }
}
void execute_multi_function_node(const DNode node,
const nodes::NodeMultiFunctions::Item &fn_item,
- NodeState &node_state)
+ NodeState &node_state,
+ NodeTaskRunState *run_state)
{
if (node->idname().find("Legacy") != StringRef::not_found) {
/* Create geometry nodes params just for creating an error message. */
- NodeParamsProvider params_provider{*this, node, node_state};
+ NodeParamsProvider params_provider{*this, node, node_state, run_state};
GeoNodeExecParams params{params_provider};
params.error_message_add(geo_log::NodeWarningType::Legacy,
TIP_("Legacy node will be removed before Blender 4.0"));
@@ -943,8 +982,9 @@ class GeometryNodesEvaluator {
LinearAllocator<> &allocator = local_allocators_.local();
- /* Prepare the inputs for the multi function. */
- Vector<GField> input_fields;
+ bool any_input_is_field = false;
+ Vector<const void *, 16> input_values;
+ Vector<const ValueOrFieldCPPType *, 16> input_types;
for (const int i : node->inputs().index_range()) {
const InputSocketRef &socket_ref = node->input(i);
if (!socket_ref.is_available()) {
@@ -955,7 +995,38 @@ class GeometryNodesEvaluator {
BLI_assert(input_state.was_ready_for_execution);
SingleInputValue &single_value = *input_state.value.single;
BLI_assert(single_value.value != nullptr);
- input_fields.append(std::move(*(GField *)single_value.value));
+ const ValueOrFieldCPPType &field_cpp_type = static_cast<const ValueOrFieldCPPType &>(
+ *input_state.type);
+ input_values.append(single_value.value);
+ input_types.append(&field_cpp_type);
+ if (field_cpp_type.is_field(single_value.value)) {
+ any_input_is_field = true;
+ }
+ }
+
+ if (any_input_is_field) {
+ this->execute_multi_function_node__field(
+ node, fn_item, node_state, allocator, input_values, input_types, run_state);
+ }
+ else {
+ this->execute_multi_function_node__value(
+ node, *fn_item.fn, node_state, allocator, input_values, input_types, run_state);
+ }
+ }
+
+ void execute_multi_function_node__field(const DNode node,
+ const nodes::NodeMultiFunctions::Item &fn_item,
+ NodeState &node_state,
+ LinearAllocator<> &allocator,
+ Span<const void *> input_values,
+ Span<const ValueOrFieldCPPType *> input_types,
+ NodeTaskRunState *run_state)
+ {
+ Vector<GField> input_fields;
+ for (const int i : input_values.index_range()) {
+ const void *input_value_or_field = input_values[i];
+ const ValueOrFieldCPPType &field_cpp_type = *input_types[i];
+ input_fields.append(field_cpp_type.as_field(input_value_or_field));
}
std::shared_ptr<fn::FieldOperation> operation;
@@ -966,7 +1037,6 @@ class GeometryNodesEvaluator {
operation = std::make_shared<fn::FieldOperation>(*fn_item.fn, std::move(input_fields));
}
- /* Forward outputs. */
int output_index = 0;
for (const int i : node->outputs().index_range()) {
const OutputSocketRef &socket_ref = node->output(i);
@@ -975,17 +1045,70 @@ class GeometryNodesEvaluator {
}
OutputState &output_state = node_state.outputs[i];
const DOutputSocket socket{node.context(), &socket_ref};
- const CPPType *cpp_type = get_socket_cpp_type(socket_ref);
+ const ValueOrFieldCPPType *cpp_type = static_cast<const ValueOrFieldCPPType *>(
+ get_socket_cpp_type(socket_ref));
GField new_field{operation, output_index};
- new_field = fn::make_field_constant_if_possible(std::move(new_field));
- GField &field_to_forward = *allocator.construct<GField>(std::move(new_field)).release();
- this->forward_output(socket, {cpp_type, &field_to_forward});
+ void *buffer = allocator.allocate(cpp_type->size(), cpp_type->alignment());
+ cpp_type->construct_from_field(buffer, std::move(new_field));
+ this->forward_output(socket, {cpp_type, buffer}, run_state);
output_state.has_been_computed = true;
output_index++;
}
}
- void execute_unknown_node(const DNode node, NodeState &node_state)
+ void execute_multi_function_node__value(const DNode node,
+ const MultiFunction &fn,
+ NodeState &node_state,
+ LinearAllocator<> &allocator,
+ Span<const void *> input_values,
+ Span<const ValueOrFieldCPPType *> input_types,
+ NodeTaskRunState *run_state)
+ {
+ MFParamsBuilder params{fn, 1};
+ for (const int i : input_values.index_range()) {
+ const void *input_value_or_field = input_values[i];
+ const ValueOrFieldCPPType &field_cpp_type = *input_types[i];
+ const CPPType &base_type = field_cpp_type.base_type();
+ const void *input_value = field_cpp_type.get_value_ptr(input_value_or_field);
+ params.add_readonly_single_input(GVArray::ForSingleRef(base_type, 1, input_value));
+ }
+
+ Vector<GMutablePointer, 16> output_buffers;
+ for (const int i : node->outputs().index_range()) {
+ const DOutputSocket socket = node.output(i);
+ if (!socket->is_available()) {
+ output_buffers.append({});
+ continue;
+ }
+ const ValueOrFieldCPPType *value_or_field_type = static_cast<const ValueOrFieldCPPType *>(
+ get_socket_cpp_type(socket));
+ const CPPType &base_type = value_or_field_type->base_type();
+ void *value_or_field_buffer = allocator.allocate(value_or_field_type->size(),
+ value_or_field_type->alignment());
+ value_or_field_type->default_construct(value_or_field_buffer);
+ void *value_buffer = value_or_field_type->get_value_ptr(value_or_field_buffer);
+ base_type.destruct(value_buffer);
+ params.add_uninitialized_single_output(GMutableSpan{base_type, value_buffer, 1});
+ output_buffers.append({value_or_field_type, value_or_field_buffer});
+ }
+
+ MFContextBuilder context;
+ fn.call(IndexRange(1), params, context);
+
+ for (const int i : output_buffers.index_range()) {
+ GMutablePointer buffer = output_buffers[i];
+ if (buffer.get() == nullptr) {
+ continue;
+ }
+ const DOutputSocket socket = node.output(i);
+ this->forward_output(socket, buffer, run_state);
+
+ OutputState &output_state = node_state.outputs[i];
+ output_state.has_been_computed = true;
+ }
+ }
+
+ void execute_unknown_node(const DNode node, NodeState &node_state, NodeTaskRunState *run_state)
{
LinearAllocator<> &allocator = local_allocators_.local();
for (const OutputSocketRef *socket : node->outputs()) {
@@ -1002,13 +1125,16 @@ class GeometryNodesEvaluator {
output_state.has_been_computed = true;
void *buffer = allocator.allocate(type->size(), type->alignment());
this->construct_default_value(*type, buffer);
- this->forward_output({node.context(), socket}, {*type, buffer});
+ this->forward_output({node.context(), socket}, {*type, buffer}, run_state);
}
}
- void node_task_postprocessing(const DNode node, NodeState &node_state, bool was_executed)
+ void node_task_postprocessing(const DNode node,
+ NodeState &node_state,
+ bool was_executed,
+ NodeTaskRunState *run_state)
{
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, run_state, [&](LockedNode &locked_node) {
const bool node_has_finished = this->finish_node_if_possible(locked_node);
const bool reschedule_requested = node_state.schedule_state ==
NodeScheduleState::RunningAndRescheduled;
@@ -1089,10 +1215,9 @@ class GeometryNodesEvaluator {
/**
* Load the required input from the socket or trigger nodes to the left to compute the value.
- * When this function is called, the node will always be executed again eventually (either
- * immediately, or when all required inputs have been computed by other nodes).
+ * \return True when the node will be triggered by another node again when the value is computed.
*/
- void set_input_required(LockedNode &locked_node, const DInputSocket input_socket)
+ bool set_input_required(LockedNode &locked_node, const DInputSocket input_socket)
{
BLI_assert(locked_node.node == input_socket.node());
InputState &input_state = locked_node.node_state.inputs[input_socket->index()];
@@ -1100,19 +1225,16 @@ class GeometryNodesEvaluator {
/* Value set as unused cannot become used again. */
BLI_assert(input_state.usage != ValueUsage::Unused);
- if (input_state.usage == ValueUsage::Required) {
- /* The value is already required, but the node might expect to be evaluated again. */
- this->schedule_node(locked_node);
- /* Returning here also ensure that the code below is executed at most once per input. */
- return;
+ if (input_state.was_ready_for_execution) {
+ return false;
}
- input_state.usage = ValueUsage::Required;
- if (input_state.was_ready_for_execution) {
- /* The value was already ready, but the node might expect to be evaluated again. */
- this->schedule_node(locked_node);
- return;
+ if (input_state.usage == ValueUsage::Required) {
+ /* If the input was not ready for execution but is required, the node will be triggered again
+ * once the input has been computed. */
+ return true;
}
+ input_state.usage = ValueUsage::Required;
/* Count how many values still have to be added to this input until it is "complete". */
int missing_values = 0;
@@ -1127,9 +1249,7 @@ class GeometryNodesEvaluator {
}
}
if (missing_values == 0) {
- /* The input is fully available already, but the node might expect to be evaluated again. */
- this->schedule_node(locked_node);
- return;
+ return false;
}
/* Increase the total number of missing required inputs. This ensures that the node will be
* scheduled correctly when all inputs have been provided. */
@@ -1144,30 +1264,28 @@ class GeometryNodesEvaluator {
/* If there are no origin sockets, just load the value from the socket directly. */
this->load_unlinked_input_value(locked_node, input_socket, input_state, input_socket);
locked_node.node_state.missing_required_inputs -= 1;
- this->schedule_node(locked_node);
- return;
+ return false;
}
- bool will_be_triggered_by_other_node = false;
+ bool requested_from_other_node = false;
for (const DSocket &origin_socket : origin_sockets) {
if (origin_socket->is_input()) {
/* Load the value directly from the origin socket. In most cases this is an unlinked
* group input. */
this->load_unlinked_input_value(locked_node, input_socket, input_state, origin_socket);
locked_node.node_state.missing_required_inputs -= 1;
- this->schedule_node(locked_node);
}
else {
/* The value has not been computed yet, so when it will be forwarded by another node, this
* node will be triggered. */
- will_be_triggered_by_other_node = true;
-
+ requested_from_other_node = true;
locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket));
}
}
/* If this node will be triggered by another node, we don't have to schedule it now. */
- if (!will_be_triggered_by_other_node) {
- this->schedule_node(locked_node);
+ if (requested_from_other_node) {
+ return true;
}
+ return false;
}
void set_input_unused(LockedNode &locked_node, const DInputSocket socket)
@@ -1203,13 +1321,13 @@ class GeometryNodesEvaluator {
});
}
- void send_output_required_notification(const DOutputSocket socket)
+ void send_output_required_notification(const DOutputSocket socket, NodeTaskRunState *run_state)
{
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
OutputState &output_state = node_state.outputs[socket->index()];
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, run_state, [&](LockedNode &locked_node) {
if (output_state.output_usage == ValueUsage::Required) {
/* Output is marked as required already. So the node is scheduled already. */
return;
@@ -1221,13 +1339,13 @@ class GeometryNodesEvaluator {
});
}
- void send_output_unused_notification(const DOutputSocket socket)
+ void send_output_unused_notification(const DOutputSocket socket, NodeTaskRunState *run_state)
{
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
OutputState &output_state = node_state.outputs[socket->index()];
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, run_state, [&](LockedNode &locked_node) {
output_state.potential_users -= 1;
if (output_state.potential_users == 0) {
/* The socket might be required even though the output is not used by other sockets. That
@@ -1254,7 +1372,9 @@ class GeometryNodesEvaluator {
/**
* Moves a newly computed value from an output socket to all the inputs that might need it.
*/
- void forward_output(const DOutputSocket from_socket, GMutablePointer value_to_forward)
+ void forward_output(const DOutputSocket from_socket,
+ GMutablePointer value_to_forward,
+ NodeTaskRunState *run_state)
{
BLI_assert(value_to_forward.get() != nullptr);
@@ -1307,12 +1427,12 @@ class GeometryNodesEvaluator {
}
else {
/* The value has been converted. */
- this->add_value_to_input_socket(to_socket, from_socket, current_value);
+ this->add_value_to_input_socket(to_socket, from_socket, current_value, run_state);
}
});
this->log_socket_value(log_original_value_sockets, value_to_forward);
this->forward_to_sockets_with_same_type(
- allocator, forward_original_value_sockets, value_to_forward, from_socket);
+ allocator, forward_original_value_sockets, value_to_forward, from_socket, run_state);
}
bool should_forward_to_socket(const DInputSocket socket)
@@ -1334,7 +1454,8 @@ class GeometryNodesEvaluator {
void forward_to_sockets_with_same_type(LinearAllocator<> &allocator,
Span<DInputSocket> to_sockets,
GMutablePointer value_to_forward,
- const DOutputSocket from_socket)
+ const DOutputSocket from_socket,
+ NodeTaskRunState *run_state)
{
if (to_sockets.is_empty()) {
/* Value is not used anymore, so it can be destructed. */
@@ -1343,7 +1464,7 @@ class GeometryNodesEvaluator {
else if (to_sockets.size() == 1) {
/* Value is only used by one input socket, no need to copy it. */
const DInputSocket to_socket = to_sockets[0];
- this->add_value_to_input_socket(to_socket, from_socket, value_to_forward);
+ this->add_value_to_input_socket(to_socket, from_socket, value_to_forward, run_state);
}
else {
/* Multiple inputs use the value, make a copy for every input except for one. */
@@ -1353,17 +1474,18 @@ class GeometryNodesEvaluator {
for (const DInputSocket &to_socket : to_sockets.drop_front(1)) {
void *buffer = allocator.allocate(type.size(), type.alignment());
type.copy_construct(value_to_forward.get(), buffer);
- this->add_value_to_input_socket(to_socket, from_socket, {type, buffer});
+ this->add_value_to_input_socket(to_socket, from_socket, {type, buffer}, run_state);
}
/* Forward the original value to one of the targets. */
const DInputSocket to_socket = to_sockets[0];
- this->add_value_to_input_socket(to_socket, from_socket, value_to_forward);
+ this->add_value_to_input_socket(to_socket, from_socket, value_to_forward, run_state);
}
}
void add_value_to_input_socket(const DInputSocket socket,
const DOutputSocket origin,
- GMutablePointer value)
+ GMutablePointer value,
+ NodeTaskRunState *run_state)
{
BLI_assert(socket->is_available());
@@ -1371,7 +1493,7 @@ class GeometryNodesEvaluator {
NodeState &node_state = this->get_node_state(node);
InputState &input_state = node_state.inputs[socket->index()];
- this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ this->with_locked_node(node, node_state, run_state, [&](LockedNode &locked_node) {
if (socket->is_multi_input_socket()) {
/* Add a new value to the multi-input. */
MultiInputValue &multi_value = *input_state.value.multi;
@@ -1482,19 +1604,28 @@ class GeometryNodesEvaluator {
from_type.copy_construct(from_value, to_value);
return;
}
-
- const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type);
- const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type);
+ const ValueOrFieldCPPType *from_field_type = dynamic_cast<const ValueOrFieldCPPType *>(
+ &from_type);
+ const ValueOrFieldCPPType *to_field_type = dynamic_cast<const ValueOrFieldCPPType *>(&to_type);
if (from_field_type != nullptr && to_field_type != nullptr) {
- const CPPType &from_base_type = from_field_type->field_type();
- const CPPType &to_base_type = to_field_type->field_type();
+ const CPPType &from_base_type = from_field_type->base_type();
+ const CPPType &to_base_type = to_field_type->base_type();
if (conversions_.is_convertible(from_base_type, to_base_type)) {
- const MultiFunction &fn = *conversions_.get_conversion_multi_function(
- MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type));
- const GField &from_field = *(const GField *)from_value;
- auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field});
- new (to_value) GField(std::move(operation), 0);
+ if (from_field_type->is_field(from_value)) {
+ const GField &from_field = *from_field_type->get_field_ptr(from_value);
+ const MultiFunction &fn = *conversions_.get_conversion_multi_function(
+ MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type));
+ auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field});
+ to_field_type->construct_from_field(to_value, GField(std::move(operation), 0));
+ }
+ else {
+ to_field_type->default_construct(to_value);
+ const void *from_value_ptr = from_field_type->get_value_ptr(from_value);
+ void *to_value_ptr = to_field_type->get_value_ptr(to_value);
+ conversions_.get_conversion_functions(from_base_type, to_base_type)
+ ->convert_single_to_initialized(from_value_ptr, to_value_ptr);
+ }
return;
}
}
@@ -1510,14 +1641,6 @@ class GeometryNodesEvaluator {
void construct_default_value(const CPPType &type, void *r_value)
{
- if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) {
- const CPPType &base_type = field_cpp_type->field_type();
- auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>(
- base_type, base_type.default_value(), false);
- auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn));
- new (r_value) GField(std::move(operation), 0);
- return;
- }
type.copy_construct(type.default_value(), r_value);
}
@@ -1549,10 +1672,21 @@ class GeometryNodesEvaluator {
params_.geo_logger->local().log_value_for_sockets(sockets, value);
}
+ void log_debug_message(DNode node, std::string message)
+ {
+ if (params_.geo_logger == nullptr) {
+ return;
+ }
+ params_.geo_logger->local().log_debug_message(node, std::move(message));
+ }
+
/* In most cases when `NodeState` is accessed, the node has to be locked first to avoid race
* conditions. */
template<typename Function>
- void with_locked_node(const DNode node, NodeState &node_state, const Function &function)
+ void with_locked_node(const DNode node,
+ NodeState &node_state,
+ NodeTaskRunState *run_state,
+ const Function &function)
{
LockedNode locked_node{node, node_state};
@@ -1565,21 +1699,32 @@ class GeometryNodesEvaluator {
/* Then send notifications to the other nodes after the node state is unlocked. This avoids
* locking two nodes at the same time on this thread and helps to prevent deadlocks. */
for (const DOutputSocket &socket : locked_node.delayed_required_outputs) {
- this->send_output_required_notification(socket);
+ this->send_output_required_notification(socket, run_state);
}
for (const DOutputSocket &socket : locked_node.delayed_unused_outputs) {
- this->send_output_unused_notification(socket);
- }
- for (const DNode &node : locked_node.delayed_scheduled_nodes) {
- this->add_node_to_task_pool(node);
+ this->send_output_unused_notification(socket, run_state);
+ }
+ for (const DNode &node_to_schedule : locked_node.delayed_scheduled_nodes) {
+ if (run_state != nullptr && !run_state->next_node_to_run) {
+ /* Execute the node on the same thread after the current node finished. */
+ /* Currently, this assumes that it is always best to run the first node that is scheduled
+ * on the same thread. That is usually correct, because the geometry socket which carries
+ * the most data usually comes first in nodes. */
+ run_state->next_node_to_run = node_to_schedule;
+ }
+ else {
+ /* Push the node to the task pool so that another thread can start working on it. */
+ this->add_node_to_task_pool(node_to_schedule);
+ }
}
}
};
NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator,
DNode dnode,
- NodeState &node_state)
- : evaluator_(evaluator), node_state_(node_state)
+ NodeState &node_state,
+ NodeTaskRunState *run_state)
+ : evaluator_(evaluator), node_state_(node_state), run_state_(run_state)
{
this->dnode = dnode;
this->self_object = evaluator.params_.self_object;
@@ -1687,7 +1832,7 @@ void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value)
OutputState &output_state = node_state_.outputs[socket->index()];
BLI_assert(!output_state.has_been_computed);
- evaluator_.forward_output(socket, value);
+ evaluator_.forward_output(socket, value, run_state_);
output_state.has_been_computed = true;
}
@@ -1701,8 +1846,12 @@ bool NodeParamsProvider::lazy_require_input(StringRef identifier)
if (input_state.was_ready_for_execution) {
return false;
}
- evaluator_.with_locked_node(this->dnode, node_state_, [&](LockedNode &locked_node) {
- evaluator_.set_input_required(locked_node, socket);
+ evaluator_.with_locked_node(this->dnode, node_state_, run_state_, [&](LockedNode &locked_node) {
+ if (!evaluator_.set_input_required(locked_node, socket)) {
+ /* Schedule the currently executed node again because the value is available now but was not
+ * ready for the current execution. */
+ evaluator_.schedule_node(locked_node);
+ }
});
return true;
}
@@ -1712,7 +1861,7 @@ void NodeParamsProvider::set_input_unused(StringRef identifier)
const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
- evaluator_.with_locked_node(this->dnode, node_state_, [&](LockedNode &locked_node) {
+ evaluator_.with_locked_node(this->dnode, node_state_, run_state_, [&](LockedNode &locked_node) {
evaluator_.set_input_unused(locked_node, socket);
});
}