618 lines
17 KiB
C++
618 lines
17 KiB
C++
/* SPDX-FileCopyrightText: 2013 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <set>
|
|
|
|
#include "BLI_multi_value_map.hh"
|
|
|
|
#include "BKE_node_runtime.hh"
|
|
|
|
#include "COM_Converter.h"
|
|
#include "COM_Debug.h"
|
|
|
|
#include "COM_PreviewOperation.h"
|
|
#include "COM_SetColorOperation.h"
|
|
#include "COM_SetValueOperation.h"
|
|
#include "COM_SetVectorOperation.h"
|
|
#include "COM_ViewerOperation.h"
|
|
|
|
#include "COM_ConstantFolder.h"
|
|
#include "COM_NodeOperationBuilder.h" /* own include */
|
|
|
|
namespace blender::compositor {
|
|
|
|
NodeOperationBuilder::NodeOperationBuilder(const CompositorContext *context,
|
|
bNodeTree *b_nodetree,
|
|
ExecutionSystem *system)
|
|
: context_(context), exec_system_(system), current_node_(nullptr), active_viewer_(nullptr)
|
|
{
|
|
graph_.from_bNodeTree(*context, b_nodetree);
|
|
}
|
|
|
|
void NodeOperationBuilder::convert_to_operations(ExecutionSystem *system)
|
|
{
|
|
/* interface handle for nodes */
|
|
NodeConverter converter(this);
|
|
|
|
for (Node *node : graph_.nodes()) {
|
|
current_node_ = node;
|
|
|
|
DebugInfo::node_to_operations(node);
|
|
node->convert_to_operations(converter, *context_);
|
|
}
|
|
|
|
current_node_ = nullptr;
|
|
|
|
/* The input map constructed by nodes maps operation inputs to node inputs.
|
|
* Inverting yields a map of node inputs to all connected operation inputs,
|
|
* so multiple operations can use the same node input.
|
|
*/
|
|
blender::MultiValueMap<NodeInput *, NodeOperationInput *> inverse_input_map;
|
|
for (MutableMapItem<NodeOperationInput *, NodeInput *> item : input_map_.items()) {
|
|
inverse_input_map.add(item.value, item.key);
|
|
}
|
|
|
|
for (const NodeGraph::Link &link : graph_.links()) {
|
|
NodeOutput *from = link.from;
|
|
NodeInput *to = link.to;
|
|
|
|
NodeOperationOutput *op_from = output_map_.lookup_default(from, nullptr);
|
|
|
|
const blender::Span<NodeOperationInput *> op_to_list = inverse_input_map.lookup(to);
|
|
if (!op_from || op_to_list.is_empty()) {
|
|
/* XXX allow this? error/debug message? */
|
|
// BLI_assert(false);
|
|
/* XXX NOTE: this can happen with certain nodes (e.g. OutputFile)
|
|
* which only generate operations in certain circumstances (rendering)
|
|
* just let this pass silently for now ...
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
for (NodeOperationInput *op_to : op_to_list) {
|
|
add_link(op_from, op_to);
|
|
}
|
|
}
|
|
|
|
add_operation_input_constants();
|
|
|
|
resolve_proxies();
|
|
|
|
add_datatype_conversions();
|
|
|
|
save_graphviz("compositor_prior_folding");
|
|
ConstantFolder folder(*this);
|
|
folder.fold_operations();
|
|
|
|
determine_canvases();
|
|
|
|
save_graphviz("compositor_prior_merging");
|
|
merge_equal_operations();
|
|
|
|
/* links not available from here on */
|
|
/* XXX make links_ a local variable to avoid confusion! */
|
|
links_.clear();
|
|
|
|
prune_operations();
|
|
|
|
/* ensure topological (link-based) order of nodes */
|
|
// sort_operations(); /* not needed yet. */
|
|
|
|
/* transfer resulting operations to the system */
|
|
system->set_operations(operations_);
|
|
}
|
|
|
|
void NodeOperationBuilder::add_operation(NodeOperation *operation)
|
|
{
|
|
operation->set_id(operations_.size());
|
|
operations_.append(operation);
|
|
if (current_node_) {
|
|
operation->set_name(current_node_->get_bnode()->name);
|
|
operation->set_node_instance_key(current_node_->get_instance_key());
|
|
}
|
|
operation->set_execution_system(exec_system_);
|
|
}
|
|
|
|
void NodeOperationBuilder::replace_operation_with_constant(NodeOperation *operation,
|
|
ConstantOperation *constant_operation)
|
|
{
|
|
BLI_assert(constant_operation->get_number_of_input_sockets() == 0);
|
|
unlink_inputs_and_relink_outputs(operation, constant_operation);
|
|
add_operation(constant_operation);
|
|
}
|
|
|
|
void NodeOperationBuilder::unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op,
|
|
NodeOperation *linked_op)
|
|
{
|
|
int i = 0;
|
|
while (i < links_.size()) {
|
|
Link &link = links_[i];
|
|
if (&link.to()->get_operation() == unlinked_op) {
|
|
link.to()->set_link(nullptr);
|
|
links_.remove(i);
|
|
continue;
|
|
}
|
|
|
|
if (&link.from()->get_operation() == unlinked_op) {
|
|
link.to()->set_link(linked_op->get_output_socket());
|
|
links_[i] = Link(linked_op->get_output_socket(), link.to());
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::map_input_socket(NodeInput *node_socket,
|
|
NodeOperationInput *operation_socket)
|
|
{
|
|
BLI_assert(current_node_);
|
|
BLI_assert(node_socket->get_node() == current_node_);
|
|
|
|
/* NOTE: this maps operation sockets to node sockets.
|
|
* for resolving links the map will be inverted first in convert_to_operations,
|
|
* to get a list of links for each node input socket.
|
|
*/
|
|
input_map_.add_new(operation_socket, node_socket);
|
|
}
|
|
|
|
void NodeOperationBuilder::map_output_socket(NodeOutput *node_socket,
|
|
NodeOperationOutput *operation_socket)
|
|
{
|
|
BLI_assert(current_node_);
|
|
BLI_assert(node_socket->get_node() == current_node_);
|
|
|
|
output_map_.add_new(node_socket, operation_socket);
|
|
}
|
|
|
|
void NodeOperationBuilder::add_link(NodeOperationOutput *from, NodeOperationInput *to)
|
|
{
|
|
if (to->is_connected()) {
|
|
return;
|
|
}
|
|
|
|
links_.append(Link(from, to));
|
|
|
|
/* register with the input */
|
|
to->set_link(from);
|
|
}
|
|
|
|
void NodeOperationBuilder::remove_input_link(NodeOperationInput *to)
|
|
{
|
|
int index = 0;
|
|
for (Link &link : links_) {
|
|
if (link.to() == to) {
|
|
/* unregister with the input */
|
|
to->set_link(nullptr);
|
|
|
|
links_.remove(index);
|
|
return;
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
PreviewOperation *NodeOperationBuilder::make_preview_operation() const
|
|
{
|
|
BLI_assert(current_node_);
|
|
|
|
if (!(current_node_->get_bnode()->flag & NODE_PREVIEW)) {
|
|
return nullptr;
|
|
}
|
|
/* previews only in the active group */
|
|
if (!current_node_->is_in_active_group()) {
|
|
return nullptr;
|
|
}
|
|
/* do not calculate previews of hidden nodes */
|
|
if (current_node_->get_bnode()->flag & NODE_HIDDEN) {
|
|
return nullptr;
|
|
}
|
|
|
|
bNodeInstanceHash *previews = context_->get_preview_hash();
|
|
if (previews) {
|
|
Scene *scene = context_->get_scene();
|
|
PreviewOperation *operation = new PreviewOperation(
|
|
&scene->view_settings,
|
|
&scene->display_settings,
|
|
current_node_->get_bnode()->runtime->preview_xsize,
|
|
current_node_->get_bnode()->runtime->preview_ysize);
|
|
operation->set_bnodetree(context_->get_bnodetree());
|
|
operation->verify_preview(previews, current_node_->get_instance_key());
|
|
return operation;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void NodeOperationBuilder::add_preview(NodeOperationOutput *output)
|
|
{
|
|
PreviewOperation *operation = make_preview_operation();
|
|
if (operation) {
|
|
add_operation(operation);
|
|
|
|
add_link(output, operation->get_input_socket(0));
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::add_node_input_preview(NodeInput *input)
|
|
{
|
|
PreviewOperation *operation = make_preview_operation();
|
|
if (operation) {
|
|
add_operation(operation);
|
|
|
|
map_input_socket(input, operation->get_input_socket(0));
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::register_viewer(ViewerOperation *viewer)
|
|
{
|
|
if (active_viewer_) {
|
|
if (current_node_->is_in_active_group()) {
|
|
/* deactivate previous viewer */
|
|
active_viewer_->set_active(false);
|
|
|
|
active_viewer_ = viewer;
|
|
viewer->set_active(true);
|
|
}
|
|
}
|
|
else {
|
|
if (current_node_->get_bnodetree() == context_->get_bnodetree()) {
|
|
active_viewer_ = viewer;
|
|
viewer->set_active(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************
|
|
**** Optimization Steps ****
|
|
****************************/
|
|
|
|
void NodeOperationBuilder::add_datatype_conversions()
|
|
{
|
|
Vector<Link> convert_links;
|
|
for (const Link &link : links_) {
|
|
/* proxy operations can skip data type conversion */
|
|
NodeOperation *from_op = &link.from()->get_operation();
|
|
NodeOperation *to_op = &link.to()->get_operation();
|
|
if (!(from_op->get_flags().use_datatype_conversion ||
|
|
to_op->get_flags().use_datatype_conversion))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (link.from()->get_data_type() != link.to()->get_data_type()) {
|
|
convert_links.append(link);
|
|
}
|
|
}
|
|
for (const Link &link : convert_links) {
|
|
NodeOperation *converter = COM_convert_data_type(*link.from(), *link.to());
|
|
if (converter) {
|
|
add_operation(converter);
|
|
|
|
remove_input_link(link.to());
|
|
add_link(link.from(), converter->get_input_socket(0));
|
|
add_link(converter->get_output_socket(0), link.to());
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::add_operation_input_constants()
|
|
{
|
|
/* NOTE: unconnected inputs cached first to avoid modifying
|
|
* operations_ while iterating over it
|
|
*/
|
|
Vector<NodeOperationInput *> pending_inputs;
|
|
for (NodeOperation *op : operations_) {
|
|
for (int k = 0; k < op->get_number_of_input_sockets(); ++k) {
|
|
NodeOperationInput *input = op->get_input_socket(k);
|
|
if (!input->is_connected()) {
|
|
pending_inputs.append(input);
|
|
}
|
|
}
|
|
}
|
|
for (NodeOperationInput *input : pending_inputs) {
|
|
add_input_constant_value(input, input_map_.lookup_default(input, nullptr));
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::add_input_constant_value(NodeOperationInput *input,
|
|
const NodeInput *node_input)
|
|
{
|
|
switch (input->get_data_type()) {
|
|
case DataType::Value: {
|
|
float value;
|
|
if (node_input && node_input->get_bnode_socket()) {
|
|
value = node_input->get_editor_value_float();
|
|
}
|
|
else {
|
|
value = 0.0f;
|
|
}
|
|
|
|
SetValueOperation *op = new SetValueOperation();
|
|
op->set_value(value);
|
|
add_operation(op);
|
|
add_link(op->get_output_socket(), input);
|
|
break;
|
|
}
|
|
case DataType::Color: {
|
|
float value[4];
|
|
if (node_input && node_input->get_bnode_socket()) {
|
|
node_input->get_editor_value_color(value);
|
|
}
|
|
else {
|
|
zero_v4(value);
|
|
}
|
|
|
|
SetColorOperation *op = new SetColorOperation();
|
|
op->set_channels(value);
|
|
add_operation(op);
|
|
add_link(op->get_output_socket(), input);
|
|
break;
|
|
}
|
|
case DataType::Vector: {
|
|
float value[3];
|
|
if (node_input && node_input->get_bnode_socket()) {
|
|
node_input->get_editor_value_vector(value);
|
|
}
|
|
else {
|
|
zero_v3(value);
|
|
}
|
|
|
|
SetVectorOperation *op = new SetVectorOperation();
|
|
op->set_vector(value);
|
|
add_operation(op);
|
|
add_link(op->get_output_socket(), input);
|
|
break;
|
|
}
|
|
case DataType::Float2:
|
|
/* An internal type that needn't be handled. */
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::resolve_proxies()
|
|
{
|
|
Vector<Link> proxy_links;
|
|
for (const Link &link : links_) {
|
|
/* don't replace links from proxy to proxy, since we may need them for replacing others! */
|
|
if (link.from()->get_operation().get_flags().is_proxy_operation &&
|
|
!link.to()->get_operation().get_flags().is_proxy_operation)
|
|
{
|
|
proxy_links.append(link);
|
|
}
|
|
}
|
|
|
|
for (const Link &link : proxy_links) {
|
|
NodeOperationInput *to = link.to();
|
|
NodeOperationOutput *from = link.from();
|
|
do {
|
|
/* walk upstream bypassing the proxy operation */
|
|
from = from->get_operation().get_input_socket(0)->get_link();
|
|
} while (from && from->get_operation().get_flags().is_proxy_operation);
|
|
|
|
remove_input_link(to);
|
|
/* we may not have a final proxy input link,
|
|
* in that case it just gets dropped
|
|
*/
|
|
if (from) {
|
|
add_link(from, to);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::determine_canvases()
|
|
{
|
|
/* Determine all canvas areas of the operations. */
|
|
const rcti &preferred_area = COM_AREA_NONE;
|
|
for (NodeOperation *op : operations_) {
|
|
if (op->is_output_operation(context_->is_rendering()) && !op->get_flags().is_preview_operation)
|
|
{
|
|
rcti canvas = COM_AREA_NONE;
|
|
op->determine_canvas(preferred_area, canvas);
|
|
op->set_canvas(canvas);
|
|
}
|
|
}
|
|
|
|
for (NodeOperation *op : operations_) {
|
|
if (op->is_output_operation(context_->is_rendering()) && op->get_flags().is_preview_operation)
|
|
{
|
|
rcti canvas = COM_AREA_NONE;
|
|
op->determine_canvas(preferred_area, canvas);
|
|
op->set_canvas(canvas);
|
|
}
|
|
}
|
|
|
|
/* Convert operation canvases when needed. */
|
|
{
|
|
Vector<Link> convert_links;
|
|
for (const Link &link : links_) {
|
|
if (link.to()->get_resize_mode() != ResizeMode::None) {
|
|
const rcti &from_canvas = link.from()->get_operation().get_canvas();
|
|
const rcti &to_canvas = link.to()->get_operation().get_canvas();
|
|
|
|
bool needs_conversion;
|
|
if (link.to()->get_resize_mode() == ResizeMode::Align) {
|
|
needs_conversion = from_canvas.xmin != to_canvas.xmin ||
|
|
from_canvas.ymin != to_canvas.ymin;
|
|
}
|
|
else {
|
|
needs_conversion = !BLI_rcti_compare(&from_canvas, &to_canvas);
|
|
}
|
|
|
|
if (needs_conversion) {
|
|
convert_links.append(link);
|
|
}
|
|
}
|
|
}
|
|
for (const Link &link : convert_links) {
|
|
COM_convert_canvas(*this, link.from(), link.to());
|
|
}
|
|
}
|
|
}
|
|
|
|
static Vector<NodeOperationHash> generate_hashes(Span<NodeOperation *> operations)
|
|
{
|
|
Vector<NodeOperationHash> hashes;
|
|
for (NodeOperation *op : operations) {
|
|
std::optional<NodeOperationHash> hash = op->generate_hash();
|
|
if (hash) {
|
|
hashes.append(std::move(*hash));
|
|
}
|
|
}
|
|
return hashes;
|
|
}
|
|
|
|
void NodeOperationBuilder::merge_equal_operations()
|
|
{
|
|
bool check_for_next_merge = true;
|
|
while (check_for_next_merge) {
|
|
/* Re-generate hashes with any change. */
|
|
Vector<NodeOperationHash> hashes = generate_hashes(operations_);
|
|
|
|
/* Make hashes be consecutive when they are equal. */
|
|
std::sort(hashes.begin(), hashes.end());
|
|
|
|
bool any_merged = false;
|
|
const NodeOperationHash *prev_hash = nullptr;
|
|
for (const NodeOperationHash &hash : hashes) {
|
|
if (prev_hash && *prev_hash == hash) {
|
|
merge_equal_operations(prev_hash->get_operation(), hash.get_operation());
|
|
any_merged = true;
|
|
}
|
|
prev_hash = &hash;
|
|
}
|
|
|
|
check_for_next_merge = any_merged;
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::merge_equal_operations(NodeOperation *from, NodeOperation *into)
|
|
{
|
|
unlink_inputs_and_relink_outputs(from, into);
|
|
operations_.remove_first_occurrence_and_reorder(from);
|
|
delete from;
|
|
}
|
|
|
|
Vector<NodeOperationInput *> NodeOperationBuilder::cache_output_links(
|
|
NodeOperationOutput *output) const
|
|
{
|
|
Vector<NodeOperationInput *> inputs;
|
|
for (const Link &link : links_) {
|
|
if (link.from() == output) {
|
|
inputs.append(link.to());
|
|
}
|
|
}
|
|
return inputs;
|
|
}
|
|
|
|
using Tags = std::set<NodeOperation *>;
|
|
|
|
static void find_reachable_operations_recursive(Tags &reachable, NodeOperation *op)
|
|
{
|
|
if (reachable.find(op) != reachable.end()) {
|
|
return;
|
|
}
|
|
reachable.insert(op);
|
|
|
|
for (int i = 0; i < op->get_number_of_input_sockets(); i++) {
|
|
NodeOperationInput *input = op->get_input_socket(i);
|
|
if (input->is_connected()) {
|
|
find_reachable_operations_recursive(reachable, &input->get_link()->get_operation());
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeOperationBuilder::prune_operations()
|
|
{
|
|
Tags reachable;
|
|
for (NodeOperation *op : operations_) {
|
|
/* output operations are primary executed operations */
|
|
if (op->is_output_operation(context_->is_rendering())) {
|
|
find_reachable_operations_recursive(reachable, op);
|
|
}
|
|
}
|
|
|
|
/* delete unreachable operations */
|
|
Vector<NodeOperation *> reachable_ops;
|
|
for (NodeOperation *op : operations_) {
|
|
if (reachable.find(op) != reachable.end()) {
|
|
reachable_ops.append(op);
|
|
}
|
|
else {
|
|
delete op;
|
|
}
|
|
}
|
|
/* finally replace the operations list with the pruned list */
|
|
operations_ = reachable_ops;
|
|
}
|
|
|
|
/* topological (depth-first) sorting of operations */
|
|
static void sort_operations_recursive(Vector<NodeOperation *> &sorted,
|
|
Tags &visited,
|
|
NodeOperation *op)
|
|
{
|
|
if (visited.find(op) != visited.end()) {
|
|
return;
|
|
}
|
|
visited.insert(op);
|
|
|
|
for (int i = 0; i < op->get_number_of_input_sockets(); i++) {
|
|
NodeOperationInput *input = op->get_input_socket(i);
|
|
if (input->is_connected()) {
|
|
sort_operations_recursive(sorted, visited, &input->get_link()->get_operation());
|
|
}
|
|
}
|
|
|
|
sorted.append(op);
|
|
}
|
|
|
|
void NodeOperationBuilder::sort_operations()
|
|
{
|
|
Vector<NodeOperation *> sorted;
|
|
sorted.reserve(operations_.size());
|
|
Tags visited;
|
|
|
|
for (NodeOperation *operation : operations_) {
|
|
sort_operations_recursive(sorted, visited, operation);
|
|
}
|
|
|
|
operations_ = sorted;
|
|
}
|
|
|
|
void NodeOperationBuilder::save_graphviz(StringRefNull name)
|
|
{
|
|
if (COM_EXPORT_GRAPHVIZ) {
|
|
exec_system_->set_operations(operations_);
|
|
DebugInfo::graphviz(exec_system_, name);
|
|
}
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder)
|
|
{
|
|
os << "# Builder start\n";
|
|
os << "digraph G {\n";
|
|
os << " rankdir=LR;\n";
|
|
os << " node [shape=box];\n";
|
|
for (const NodeOperation *operation : builder.get_operations()) {
|
|
os << " op" << operation->get_id() << " [label=\"" << *operation << "\"];\n";
|
|
}
|
|
|
|
os << "\n";
|
|
for (const NodeOperationBuilder::Link &link : builder.get_links()) {
|
|
os << " op" << link.from()->get_operation().get_id() << " -> op"
|
|
<< link.to()->get_operation().get_id() << ";\n";
|
|
}
|
|
|
|
os << "}\n";
|
|
os << "# Builder end\n";
|
|
return os;
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder::Link &link)
|
|
{
|
|
os << link.from()->get_operation().get_id() << " -> " << link.to()->get_operation().get_id();
|
|
return os;
|
|
}
|
|
|
|
} // namespace blender::compositor
|