Introduce scopes (#1053)

* Implement let binding

* Add lambda inputs

* Fix tests

* Fix proto network formatting

* Generate a template Scoped network by default

* Add comment to explain the lambda parameter

* Move binding wrapping out of the template

* Fix errors cause by image frames
This commit is contained in:
Dennis Kobert
2023-03-02 17:13:28 +01:00
committed by Keavon Chambers
parent 0b813805d2
commit 7254c008f9
12 changed files with 366 additions and 118 deletions
@@ -623,7 +623,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
// Make layer the size of the image
let fit_image_size = DAffine2::from_scale_angle_translation(image_size, 0., image_size / -2.);
let transform = (center_in_viewport_layerspace * fit_image_size);
let transform = center_in_viewport_layerspace * fit_image_size;
responses.push_back(DocumentMessage::StartTransaction.into());
@@ -44,6 +44,7 @@ impl FrontendGraphDataType {
TaggedValue::Bool(_) => Self::Boolean,
TaggedValue::DVec2(_) => Self::Vector,
TaggedValue::Image(_) => Self::Raster,
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) => Self::Subpath,
_ => Self::General,
@@ -279,6 +280,8 @@ impl NodeGraphMessageHandler {
if let NodeInput::Node {
node_id: link_start,
output_index: link_start_index,
// TODO: add ui for lambdas
lambda,
} = *input
{
Some(FrontendNodeLink {
@@ -108,7 +108,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Image",
category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), false)],
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), false)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
},
@@ -130,20 +130,12 @@ fn static_nodes() -> Vec<DocumentNodeType> {
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0, 1],
outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)],
nodes: [
DocumentNode {
name: "Identity".to_string(),
inputs: vec![NodeInput::Network(concrete!(ImageFrame))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
metadata: Default::default(),
},
DocumentNode {
name: "Identity".to_string(),
inputs: vec![NodeInput::Network(concrete!(DAffine2))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
metadata: Default::default(),
},
]
nodes: [DocumentNode {
name: "Identity".to_string(),
inputs: vec![NodeInput::Network(concrete!(ImageFrame))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
metadata: Default::default(),
}]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
@@ -158,24 +150,97 @@ fn static_nodes() -> Vec<DocumentNodeType> {
},
DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
],
outputs: vec![DocumentOutputType {
name: "Image Frame",
data_type: FrontendGraphDataType::Raster,
}],
properties: node_properties::input_properties,
},
DocumentNodeType {
name: "Begin Scope",
category: "Structural",
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0, 2],
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(3, 0)],
nodes: [
DocumentNode {
name: "SetNode".to_string(),
inputs: vec![NodeInput::Network(concrete!(ImageFrame))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
metadata: Default::default(),
},
DocumentNode {
name: "LetNode".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::LetNode<_>")),
metadata: Default::default(),
},
DocumentNode {
name: "RefNode".to_string(),
inputs: vec![NodeInput::Network(concrete!(())), NodeInput::lambda(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")),
metadata: Default::default(),
},
DocumentNode {
name: "CloneNode".to_string(),
inputs: vec![NodeInput::node(2, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
metadata: Default::default(),
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}),
inputs: vec![DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
}],
outputs: vec![
DocumentOutputType {
name: "Image Frame",
data_type: FrontendGraphDataType::Raster,
name: "Scope",
data_type: FrontendGraphDataType::General,
},
DocumentOutputType {
name: "Transform",
data_type: FrontendGraphDataType::Number,
name: "Binding",
data_type: FrontendGraphDataType::Raster,
},
],
properties: node_properties::input_properties,
properties: |_document_node, _node_id, _context| node_properties::string_properties("Binds the input in a local scope as a variable"),
},
DocumentNodeType {
name: "End Scope",
category: "Structural",
identifier: NodeImplementation::proto("graphene_std::memo::EndLetNode<_>"),
inputs: vec![
DocumentInputType {
name: "Scope",
data_type: FrontendGraphDataType::General,
default: NodeInput::value(TaggedValue::None, true),
},
DocumentInputType {
name: "Data",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
},
],
outputs: vec![DocumentOutputType {
name: "Frame",
data_type: FrontendGraphDataType::Raster,
}],
properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"),
},
DocumentNodeType {
name: "Output",
category: "Ignore",
identifier: NodeImplementation::proto("graphene_core::ops::IdNode"),
inputs: vec![DocumentInputType {
name: "In",
name: "Output",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
}],
@@ -198,8 +263,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Second", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Second", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("BlendMode", TaggedValue::BlendMode(BlendMode::Normal), false),
DocumentInputType::value("Opacity", TaggedValue::F64(100.), false),
],
@@ -214,7 +279,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentInputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
},
DocumentInputType {
name: "Shadows",
@@ -253,7 +318,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentInputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
},
DocumentInputType {
name: "Tint",
@@ -299,7 +364,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@@ -336,7 +401,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
..Default::default()
}),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Radius", TaggedValue::U32(3), false),
DocumentInputType::value("Sigma", TaggedValue::F64(1.), false),
],
@@ -376,7 +441,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
.collect(),
..Default::default()
}),
inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)],
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties,
},
@@ -386,7 +451,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"),
inputs: vec![
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::new("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType {
name: "Path",
data_type: FrontendGraphDataType::Text,
@@ -405,7 +470,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentInputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
},
DocumentInputType {
name: "samples",
@@ -425,7 +490,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
name: "Invert RGB",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true)],
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties,
},
@@ -434,7 +499,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::HueSaturationNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Hue Shift", TaggedValue::F64(0.), false),
DocumentInputType::value("Saturation Shift", TaggedValue::F64(0.), false),
DocumentInputType::value("Lightness Shift", TaggedValue::F64(0.), false),
@@ -447,7 +512,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Brightness", TaggedValue::F64(0.), false),
DocumentInputType::value("Contrast", TaggedValue::F64(0.), false),
],
@@ -459,7 +524,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
DocumentInputType::value("Threshold", TaggedValue::F64(50.), false),
],
@@ -471,7 +536,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::VibranceNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Vibrance", TaggedValue::F64(0.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@@ -482,7 +547,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Factor", TaggedValue::F64(100.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@@ -493,7 +558,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::PosterizeNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Value", TaggedValue::F64(4.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@@ -504,7 +569,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::ExposureNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Exposure", TaggedValue::F64(0.), false),
DocumentInputType::value("Offset", TaggedValue::F64(0.), false),
DocumentInputType::value("Gamma Correction", TaggedValue::F64(1.), false),
@@ -650,6 +715,7 @@ impl DocumentNodeType {
}
NodeImplementation::DocumentNode(network) => network.clone(),
};
DocumentNodeImplementation::Network(inner_network)
}
@@ -663,15 +729,43 @@ impl DocumentNodeType {
}
}
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input");
let input_type = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).unwrap().clone();
let inner_network = DocumentNode {
name: "Scope".to_string(),
implementation: DocumentNodeImplementation::Network(network),
inputs: vec![NodeInput::node(0, 1)],
metadata: DocumentNodeMetadata::default(),
};
// wrap the inner network in a scope
let nodes = vec![
resolve_document_node_type("Begin Scope")
.expect("Begin Scope node type not found")
.to_document_node(vec![input_type.clone()], DocumentNodeMetadata::default()),
inner_network,
resolve_document_node_type("End Scope")
.expect("End Scope node type not found")
.to_document_node(vec![NodeInput::node(0, 0), NodeInput::node(1, 0)], DocumentNodeMetadata::default()),
];
let network = NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(2, 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (id as NodeId, node)).collect(),
..Default::default()
};
network
}
pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(1, 0)],
nodes: [
resolve_document_node_type("Input").expect("Input node does not exist").to_document_node(
[NodeInput::Network(concrete!(Image)), NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)],
DocumentNodeMetadata::position((8, 4)),
),
resolve_document_node_type("Input")
.expect("Input node does not exist")
.to_document_node([NodeInput::Network(concrete!(ImageFrame))], DocumentNodeMetadata::position((8, 4))),
resolve_document_node_type("Output")
.expect("Output node does not exist")
.to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
+10 -7
View File
@@ -1,4 +1,5 @@
use crate::messages::frontend::utility_types::FrontendImageData;
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@@ -20,16 +21,18 @@ pub struct NodeGraphExecutor {
impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
fn execute_network<T: dyn_any::StaticType>(&mut self, mut network: NodeNetwork, image_frame: ImageFrame) -> Result<T, String> {
network.duplicate_outputs(&mut generate_uuid);
network.remove_dead_nodes();
fn execute_network<T: dyn_any::StaticType>(&mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result<T, String> {
let mut scoped_network = wrap_network_in_scope(network);
debug!("Execute document network:\n{network:#?}");
scoped_network.duplicate_outputs(&mut generate_uuid);
scoped_network.remove_dead_nodes();
debug!("Execute document network:\n{scoped_network:#?}");
// We assume only one output
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = c.compile_single(network, true)?;
let proto_network = c.compile_single(scoped_network, true)?;
debug!("Execute proto network:\n{proto_network}");
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
if let Err(e) = self.executor.update(proto_network) {
@@ -74,7 +77,7 @@ impl NodeGraphExecutor {
// If the input is just a value, return that value
NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::<T>(tagged_value.clone().to_any()).map(|v| *v),
// If the input is from a node, set the node to be the output (so that is what is evaluated)
NodeInput::Node { node_id, output_index } => {
NodeInput::Node { node_id, output_index, .. } => {
inner_network.outputs[0] = NodeOutput::new(*node_id, *output_index);
break 'outer;
}