Files
test2/source/blender/blenkernel/intern/compute_contexts.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

161 lines
6.0 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
#include "DNA_node_types.h"
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
#include "BKE_compute_contexts.hh"
#include <ostream>
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
namespace blender::bke {
ModifierComputeContext::ModifierComputeContext(const ComputeContext *parent,
std::string modifier_name)
: ComputeContext(s_static_type, parent), modifier_name_(std::move(modifier_name))
{
hash_.mix_in(s_static_type, strlen(s_static_type));
hash_.mix_in(modifier_name_.data(), modifier_name_.size());
}
void ModifierComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Modifier: " << modifier_name_;
}
GroupNodeComputeContext::GroupNodeComputeContext(
const ComputeContext *parent,
const int32_t node_id,
const std::optional<ComputeContextHash> &cached_hash)
: ComputeContext(s_static_type, parent), node_id_(node_id)
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
{
if (cached_hash.has_value()) {
hash_ = *cached_hash;
}
else {
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &node_id_, sizeof(int32_t));
hash_.mix_in(buffer, buffer_size);
}
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
}
GroupNodeComputeContext::GroupNodeComputeContext(const ComputeContext *parent,
const bNode &node,
const bNodeTree &caller_tree)
: GroupNodeComputeContext(parent, node.identifier)
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
{
caller_group_node_ = &node;
caller_tree_ = &caller_tree;
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
}
void GroupNodeComputeContext::print_current_in_line(std::ostream &stream) const
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
{
if (caller_group_node_ != nullptr) {
stream << "Node: " << caller_group_node_->name;
return;
}
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
}
Geometry Nodes: make evaluation and logging system aware of zones This refactors how a geometry nodes node tree is converted to a lazy-function graph. Previously, all nodes were inserted into a single graph. This was fine because every node was evaluated at most once per node group evaluation. However, loops (#108896) break this assumption since now nodes may be evaluated multiple times and thus a single flat graph does not work anymore. Now, a separate lazy-function is build for every zone which gives us much more flexibility for what can happen in a zone. Right now, the change only applies to simulation zones since that's the only kind of zone we have. Technically, those zones could be inlined, but turning them into a separate lazy-function also does not hurt and makes it possible to test this refactor without implementing loops first. Also, having them as separate functions might help in the future if we integrate a substep loop directly into the simulation zone. The most tricky part here is to just link everything up correctly, especially with respect to deterministic anonymous attribute lifetimes. Fortunately, correctness can be checked visually by looking at the generated graphs. The logging/viewer system also had to be refactored a bit, because now there can be multiple different `ComputeContext` in a single node tree. Each zone is in a separate `ComputeContext`. To make it work, the `ViewerPath` system now explicitly supports zones and drawing code will look up the right logger for showing inspection data. No functional changes are expected, except that the spreadsheet now shows "Simulation Zone" in the context path if the viewer is in a simulation.
2023-06-20 09:50:44 +02:00
SimulationZoneComputeContext::SimulationZoneComputeContext(const ComputeContext *parent,
const int32_t output_node_id)
: ComputeContext(s_static_type, parent), output_node_id_(output_node_id)
{
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &output_node_id_, sizeof(int32_t));
hash_.mix_in(buffer, buffer_size);
}
SimulationZoneComputeContext::SimulationZoneComputeContext(const ComputeContext *parent,
const bNode &node)
: SimulationZoneComputeContext(parent, node.identifier)
{
}
void SimulationZoneComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Simulation Zone ID: " << output_node_id_;
}
Geometry Nodes: new Repeat Zone This adds support for running a set of nodes repeatedly. The number of iterations can be controlled dynamically as an input of the repeat zone. The repeat zone can be added in via the search or from the Add > Utilities menu. The main use case is to replace long repetitive node chains with a more flexible alternative. Technically, repeat zones can also be used for many other use cases. However, due to their serial nature, performance is very sub-optimal when they are used to solve problems that could be processed in parallel. Better solutions for such use cases will be worked on separately. Repeat zones are similar to simulation zones. The major difference is that they have no concept of time and are always evaluated entirely in the current frame, while in simulations only a single iteration is evaluated per frame. Stopping the repetition early using a dynamic condition is not yet supported. "Break" functionality can be implemented manually using Switch nodes in the loop for now. It's likely that this functionality will be built into the repeat zone in the future. For now, things are kept more simple. Remaining Todos after this first version: * Improve socket inspection and viewer node support. Currently, only the first iteration is taken into account for socket inspection and the viewer. * Make loop evaluation more lazy. Currently, the evaluation is eager, meaning that it evaluates some nodes even though their output may not be required. Pull Request: https://projects.blender.org/blender/blender/pulls/109164
2023-07-11 22:36:10 +02:00
RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent,
const int32_t output_node_id,
const int iteration)
: ComputeContext(s_static_type, parent), output_node_id_(output_node_id), iteration_(iteration)
{
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t) + sizeof(int);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &output_node_id_, sizeof(int32_t));
memcpy(buffer + type_size + 1 + sizeof(int32_t), &iteration_, sizeof(int));
hash_.mix_in(buffer, buffer_size);
}
RepeatZoneComputeContext::RepeatZoneComputeContext(const ComputeContext *parent,
const bNode &node,
const int iteration)
: RepeatZoneComputeContext(parent, node.identifier, iteration)
{
}
void RepeatZoneComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Repeat Zone ID: " << output_node_id_;
}
Geometry Nodes: new For Each Geometry Element zone This adds a new type of zone to Geometry Nodes that allows executing some nodes for each element in a geometry. ## Features * The `Selection` input allows iterating over a subset of elements on the set domain. * Fields passed into the input node are available as single values inside of the zone. * The input geometry can be split up into separate (completely independent) geometries for each element (on all domains except face corner). * New attributes can be created on the input geometry by outputting a single value from each iteration. * New geometries can be generated in each iteration. * All of these geometries are joined to form the final output. * Attributes from the input geometry are propagated to the output geometries. ## Evaluation The evaluation strategy is similar to the one used for repeat zones. Namely, it dynamically builds a `lazy_function::Graph` once it knows how many iterations are necessary. It contains a separate node for each iteration. The inputs for each iteration are hardcoded into the graph. The outputs of each iteration a passed to a separate lazy-function that reduces all the values down to the final outputs. This final output can have a huge number of inputs and that is not ideal for multi-threading yet, but that can still be improved in the future. ## Performance There is a non-neglilible amount of overhead for each iteration. The overhead is way larger than the per-element overhead when just doing field evaluation. Therefore, normal field evaluation should be preferred when possible. That can partially still be optimized if there is only some number crunching going on in the zone but that optimization is not implemented yet. However, processing many small geometries (e.g. each hair of a character separately) will likely **always be slower** than working on fewer larger geoemtries. The additional flexibility you get by processing each element separately comes at the cost that Blender can't optimize the operation as well. For node groups that need to handle lots of geometry elements, we recommend trying to design the node setup so that iteration over tiny sub-geometries is not required. An opposite point is true as well though. It can be faster to process more medium sized geometries in parallel than fewer very large geometries because of more multi-threading opportunities. The exact threshold between tiny, medium and large geometries depends on a lot of factors though. Overall, this initial version of the new zone does not implement all optimization opportunities yet, but the points mentioned above will still hold true later. Pull Request: https://projects.blender.org/blender/blender/pulls/127331
2024-09-24 11:52:02 +02:00
ForeachGeometryElementZoneComputeContext::ForeachGeometryElementZoneComputeContext(
const ComputeContext *parent, const int32_t output_node_id, const int index)
: ComputeContext(s_static_type, parent), output_node_id_(output_node_id), index_(index)
{
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t) + sizeof(int);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &output_node_id_, sizeof(int32_t));
memcpy(buffer + type_size + 1 + sizeof(int32_t), &index_, sizeof(int));
hash_.mix_in(buffer, buffer_size);
}
ForeachGeometryElementZoneComputeContext::ForeachGeometryElementZoneComputeContext(
const ComputeContext *parent, const bNode &node, const int index)
: ForeachGeometryElementZoneComputeContext(parent, node.identifier, index)
{
}
void ForeachGeometryElementZoneComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Foreach Geometry Element Zone ID: " << output_node_id_;
}
OperatorComputeContext::OperatorComputeContext() : OperatorComputeContext(nullptr) {}
OperatorComputeContext::OperatorComputeContext(const ComputeContext *parent)
: ComputeContext(s_static_type, parent)
{
hash_.mix_in(s_static_type, strlen(s_static_type));
}
void OperatorComputeContext::print_current_in_line(std::ostream &stream) const
{
stream << "Operator";
}
Geometry Nodes: new evaluation system This refactors the geometry nodes evaluation system. No changes for the user are expected. At a high level the goals are: * Support using geometry nodes outside of the geometry nodes modifier. * Support using the evaluator infrastructure for other purposes like field evaluation. * Support more nodes, especially when many of them are disabled behind switch nodes. * Support doing preprocessing on node groups. For more details see T98492. There are fairly detailed comments in the code, but here is a high level overview for how it works now: * There is a new "lazy-function" system. It is similar in spirit to the multi-function system but with different goals. Instead of optimizing throughput for highly parallelizable work, this system is designed to compute only the data that is actually necessary. What data is necessary can be determined dynamically during evaluation. Many lazy-functions can be composed in a graph to form a new lazy-function, which can again be used in a graph etc. * Each geometry node group is converted into a lazy-function graph prior to evaluation. To evaluate geometry nodes, one then just has to evaluate that graph. Node groups are no longer inlined into their parents. Next steps for the evaluation system is to reduce the use of threads in some situations to avoid overhead. Many small node groups don't benefit from multi-threading at all. This is much easier to do now because not everything has to be inlined in one huge node tree anymore. Differential Revision: https://developer.blender.org/D15914
2022-09-13 08:44:26 +02:00
} // namespace blender::bke