mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Lay groundwork for adaptive resolution system (#1395)
* Make transform node accept footprint as input and pass it along to its input use f32 instead of f64 and add default to document node definition * Add cull node * Fix types for Transform and Cull Nodes * Add render config struct * Add Render Node skeleton * Add Render Node to node_registry * Make types macro use macro hygiene * Place Render Node as output * Start making DownresNode footprint aware * Correctly calculate footprint in Transform Node * Add cropping and resizing to downres node * Fix Output node declaration * Fix image transform * Fix Vector Data rendering * Add concept of ImageRenderMode * Take base image size into account when calculating the final image size * Supply viewport transform to the node graph * Start adapting document graph to resolution agnosticism * Make document node short circuting not shift the input index * Apply clippy lints
This commit is contained in:
committed by
Keavon Chambers
parent
239ca698e5
commit
d82f133514
@@ -5,8 +5,6 @@ use graphene_core::{NodeIdentifier, Type};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::IVec2;
|
||||
pub use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::TypeDescriptor;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub mod value;
|
||||
@@ -38,6 +36,7 @@ impl DocumentNodeMetadata {
|
||||
pub struct DocumentNode {
|
||||
pub name: String,
|
||||
pub inputs: Vec<NodeInput>,
|
||||
pub manual_composition: Option<Type>,
|
||||
pub implementation: DocumentNodeImplementation,
|
||||
pub metadata: DocumentNodeMetadata,
|
||||
#[serde(default)]
|
||||
@@ -51,7 +50,7 @@ impl DocumentNode {
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_) | NodeInput::ShortCircut(_)))
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
|
||||
.nth(offset)
|
||||
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
||||
|
||||
@@ -59,10 +58,16 @@ impl DocumentNode {
|
||||
}
|
||||
|
||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||
assert_ne!(self.inputs.len(), 0, "Resolving document node {:#?} with no inputs", self);
|
||||
let first = self.inputs.remove(0);
|
||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||
let (input, mut args) = match first {
|
||||
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {:#?} with no inputs", self);
|
||||
let DocumentNodeImplementation::Unresolved(fqn) = self.implementation
|
||||
else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
};
|
||||
let (input, mut args) = if let Some(ty) = self.manual_composition {
|
||||
(ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![]))
|
||||
} else {
|
||||
let first = self.inputs.remove(0);
|
||||
match first {
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
assert_eq!(self.inputs.len(), 0, "{}, {:?}", &self.name, &self.inputs);
|
||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
||||
@@ -72,37 +77,33 @@ impl DocumentNode {
|
||||
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
|
||||
}
|
||||
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(ty), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::ShortCircut(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
&self.inputs,
|
||||
&args
|
||||
);
|
||||
}
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
&self.inputs,
|
||||
&args
|
||||
);
|
||||
|
||||
// If we have one parameter of the type inline, set it as the construction args
|
||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
||||
args = ConstructionArgs::Inline(inline.clone());
|
||||
}
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
ProtoNode {
|
||||
identifier: fqn,
|
||||
input,
|
||||
construction_args: args,
|
||||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
}
|
||||
} else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
// If we have one parameter of the type inline, set it as the construction args
|
||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
||||
args = ConstructionArgs::Inline(inline.clone());
|
||||
}
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
ProtoNode {
|
||||
identifier: fqn,
|
||||
input,
|
||||
construction_args: args,
|
||||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ pub enum NodeInput {
|
||||
/// A short circuting input represents an input that is not resolved through function composition
|
||||
/// but actually consuming the provided input instead of passing it to its predecessor.
|
||||
/// See [NodeInput] docs for more explanation.
|
||||
ShortCircut(Type),
|
||||
// TODO: Update
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
@@ -226,7 +227,6 @@ impl NodeInput {
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network(_) => false,
|
||||
NodeInput::ShortCircut(_) => false,
|
||||
NodeInput::Inline(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,6 @@ impl NodeInput {
|
||||
NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"),
|
||||
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
|
||||
NodeInput::Network(ty) => ty.clone(),
|
||||
NodeInput::ShortCircut(ty) => ty.clone(),
|
||||
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
|
||||
}
|
||||
}
|
||||
@@ -350,7 +349,7 @@ impl NodeNetwork {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Input Frame".into(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(u32))],
|
||||
manual_composition: Some(concrete!(u32)),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||
..Default::default()
|
||||
@@ -363,7 +362,7 @@ impl NodeNetwork {
|
||||
}
|
||||
|
||||
/// Appends a new node to the network after the output node and sets it as the new output
|
||||
pub fn push_node(&mut self, mut node: DocumentNode, connect_to_previous: bool) -> NodeId {
|
||||
pub fn push_node(&mut self, mut node: DocumentNode) -> NodeId {
|
||||
let id = self.nodes.len().try_into().expect("Too many nodes in network");
|
||||
// Set the correct position for the new node
|
||||
if node.metadata.position == IVec2::default() {
|
||||
@@ -371,7 +370,7 @@ impl NodeNetwork {
|
||||
node.metadata.position = pos + IVec2::new(8, 0);
|
||||
}
|
||||
}
|
||||
if connect_to_previous && !self.outputs.is_empty() {
|
||||
if !self.outputs.is_empty() {
|
||||
let input = NodeInput::node(self.outputs[0].node_id, self.outputs[0].node_output_index);
|
||||
if node.inputs.is_empty() {
|
||||
node.inputs.push(input);
|
||||
@@ -392,7 +391,7 @@ impl NodeNetwork {
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
..Default::default()
|
||||
};
|
||||
self.push_node(node, true)
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Adds a Cache and a Clone node to the network
|
||||
@@ -408,7 +407,8 @@ impl NodeNetwork {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "MemoNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)],
|
||||
manual_composition: Some(concrete!(())),
|
||||
inputs: vec![NodeInput::Network(ty)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -430,7 +430,7 @@ impl NodeNetwork {
|
||||
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||
..Default::default()
|
||||
};
|
||||
self.push_node(node, true)
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Get the nested network given by the path of node ids
|
||||
@@ -543,6 +543,14 @@ impl NodeNetwork {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn output_as_input(&self, arg: usize) -> NodeInput {
|
||||
NodeInput::Node {
|
||||
node_id: self.outputs[arg].node_id,
|
||||
output_index: self.outputs[arg].node_output_index,
|
||||
lambda: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FlowIter<'a> {
|
||||
@@ -693,9 +701,8 @@ impl NodeNetwork {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
||||
std::mem::swap(&mut dummy_input, input);
|
||||
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
||||
let previous_input = std::mem::replace(input, NodeInput::Network(concrete!(())));
|
||||
if let NodeInput::Value { tagged_value, exposed } = previous_input {
|
||||
let value_node_id = gen_id();
|
||||
let merged_node_id = map_ids(id, value_node_id);
|
||||
let path = if let Some(mut new_path) = node.path.clone() {
|
||||
@@ -721,7 +728,7 @@ impl NodeNetwork {
|
||||
lambda: false,
|
||||
};
|
||||
} else {
|
||||
*input = dummy_input;
|
||||
*input = previous_input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +751,11 @@ impl NodeNetwork {
|
||||
node.inputs,
|
||||
inner_network.inputs
|
||||
);
|
||||
assert_eq!(
|
||||
node.inputs.len(),
|
||||
inner_network.inputs.len(),
|
||||
"Document Nodes with a Network implementation should have the same number of inner network inputs as inputs declared on the Document Node"
|
||||
);
|
||||
// Match the document node input and the inputs of the inner network
|
||||
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
||||
// Keep track of how many network inputs we have already connected for each node
|
||||
@@ -760,7 +772,6 @@ impl NodeNetwork {
|
||||
self.inputs[index] = *network_input;
|
||||
}
|
||||
}
|
||||
NodeInput::ShortCircut(_) => (),
|
||||
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||
NodeInput::Inline(_) => (),
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ pub enum TaggedValue {
|
||||
Curve(graphene_core::raster::curve::Curve),
|
||||
IVec2(glam::IVec2),
|
||||
SurfaceFrame(graphene_core::SurfaceFrame),
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
RenderOutput(RenderOutput),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
@@ -134,6 +136,8 @@ impl Hash for TaggedValue {
|
||||
Self::Curve(curve) => curve.hash(state),
|
||||
Self::IVec2(v) => v.hash(state),
|
||||
Self::SurfaceFrame(surface_id) => surface_id.hash(state),
|
||||
Self::Footprint(footprint) => footprint.hash(state),
|
||||
Self::RenderOutput(render_output) => render_output.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +194,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Curve(x) => Box::new(x),
|
||||
TaggedValue::IVec2(x) => Box::new(x),
|
||||
TaggedValue::SurfaceFrame(x) => Box::new(x),
|
||||
TaggedValue::Footprint(x) => Box::new(x),
|
||||
TaggedValue::RenderOutput(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +214,6 @@ impl<'a> TaggedValue {
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
use graphene_core::TypeDescriptor;
|
||||
use std::borrow::Cow;
|
||||
match self {
|
||||
TaggedValue::None => concrete!(()),
|
||||
TaggedValue::String(_) => concrete!(String),
|
||||
@@ -259,6 +263,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
|
||||
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
|
||||
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
||||
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
|
||||
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,10 +320,12 @@ impl<'a> TaggedValue {
|
||||
x if x == TypeId::of::<graphene_core::Artboard>() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
|
||||
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
|
||||
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
||||
}
|
||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
}
|
||||
@@ -338,3 +346,11 @@ impl UpcastNode {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderOutput {
|
||||
CanvasFrame(graphene_core::SurfaceFrame),
|
||||
Svg(String),
|
||||
Raster(Vec<u8>),
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ impl ProtoNetwork {
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
let inwards_edges = self.collect_inwards_edges();
|
||||
for (id, _node) in &self.nodes {
|
||||
for (id, _) in &self.nodes {
|
||||
for &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) {
|
||||
if !visited.contains(&dependency) {
|
||||
dbg!(id, dependency);
|
||||
@@ -580,7 +580,10 @@ impl TypingContext {
|
||||
input.output.clone()
|
||||
}
|
||||
};
|
||||
let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?;
|
||||
let impls = self
|
||||
.lookup
|
||||
.get(&node.identifier)
|
||||
.ok_or(format!("No implementations found for {:?}. Other implementations found {:?}", node.identifier, self.lookup))?;
|
||||
|
||||
if matches!(input, Type::Generic(_)) {
|
||||
return Err(format!("Generic types are not supported as inputs yet {:?} occurred in {:?}", &input, node.identifier));
|
||||
|
||||
Reference in New Issue
Block a user