mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Use canvas as target for raster rendering (#1256)
* Implement ApplicationIo * Simplify output duplication logic * Fix let node initialization for ExtractImageFrame * Async macros * Use manual node registry impl * Fix canvas insertion into the dom
This commit is contained in:
parent
57415b948b
commit
259dcdc628
27 changed files with 810 additions and 259 deletions
|
|
@ -184,7 +184,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
},
|
||||
DocumentNodeType {
|
||||
name: "Downres",
|
||||
category: "Ignore",
|
||||
category: "Raster",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
|
|
@ -234,15 +234,87 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentNodeType {
|
||||
name: "Input Frame",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::ExtractImageFrame"),
|
||||
inputs: vec![DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(EditorApi)),
|
||||
}],
|
||||
outputs: vec![DocumentOutputType {
|
||||
name: "Image Frame",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
}],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Create Canvas",
|
||||
category: "Structural",
|
||||
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!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ExtractImageFrame")),
|
||||
..Default::default()
|
||||
}]
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Create Canvas".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::CreateSurfaceNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Cache".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(EditorApi)),
|
||||
}],
|
||||
outputs: vec![DocumentOutputType {
|
||||
name: "Canvas",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
}],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Draw Canvas",
|
||||
category: "Structural",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 2],
|
||||
outputs: vec![NodeOutput::new(3, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Convert Image Frame".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Create Canvas".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::CreateSurfaceNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Cache".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Draw Canvas".to_string(),
|
||||
inputs: vec![NodeInput::node(0, 0), NodeInput::node(2, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::wasm_application_io::DrawImageFrameNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
|
|
@ -252,14 +324,18 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
inputs: vec![
|
||||
DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(ImageFrame<Color>)),
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(EditorApi)),
|
||||
},
|
||||
DocumentInputType::value("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType {
|
||||
name: "Image Frame",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
name: "Canvas",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
}],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
|
|
@ -267,12 +343,12 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
name: "Begin Scope",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 2],
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "SetNode".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -284,7 +360,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
},
|
||||
DocumentNode {
|
||||
name: "RefNode".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(())), NodeInput::lambda(1, 0)],
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -299,7 +375,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
inputs: vec![DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::EditorApi(EditorApi::empty()), true),
|
||||
default: NodeInput::Network(concrete!(EditorApi)),
|
||||
}],
|
||||
outputs: vec![
|
||||
DocumentOutputType {
|
||||
|
|
@ -1243,24 +1319,39 @@ impl DocumentNodeType {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
||||
// if the network has no inputs, it doesn't need to be wrapped in a scope
|
||||
if network.inputs.is_empty() {
|
||||
return network;
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
network.generate_node_paths(&[]);
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
|
||||
assert_eq!(network.inputs.len(), 1, "Networks wrapped in scope must have exactly one input");
|
||||
let input = network.nodes[&network.inputs[0]].inputs.iter().find(|&i| matches!(i, NodeInput::Network(_))).cloned();
|
||||
let mut network_inputs = Vec::new();
|
||||
let mut input_type = None;
|
||||
for (id, node) in network.nodes.iter() {
|
||||
for (index, input) in node.inputs.iter().enumerate() {
|
||||
if let NodeInput::Network(_) = input {
|
||||
if input_type.is_none() {
|
||||
input_type = Some(input.clone());
|
||||
}
|
||||
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type");
|
||||
network_inputs.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
let len = network_inputs.len();
|
||||
network.inputs = network_inputs;
|
||||
|
||||
// if the network has no network inputs, it doesn't need to be wrapped in a scope either
|
||||
let Some(input_type) = input else {
|
||||
// if the network has no inputs, it doesn't need to be wrapped in a scope
|
||||
if len == 0 {
|
||||
return network;
|
||||
};
|
||||
}
|
||||
|
||||
let inner_network = DocumentNode {
|
||||
name: "Scope".to_string(),
|
||||
implementation: DocumentNodeImplementation::Network(network),
|
||||
inputs: vec![NodeInput::node(0, 1)],
|
||||
inputs: core::iter::repeat(NodeInput::node(0, 1)).take(len).collect(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
@ -1268,7 +1359,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
|||
let nodes = vec![
|
||||
resolve_document_node_type("Begin Scope")
|
||||
.expect("Begin Scope node type not found")
|
||||
.to_document_node(vec![input_type], DocumentNodeMetadata::default()),
|
||||
.to_document_node(vec![input_type.unwrap()], DocumentNodeMetadata::default()),
|
||||
inner_network,
|
||||
resolve_document_node_type("End Scope")
|
||||
.expect("End Scope node type not found")
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ impl Default for BrushOptions {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
diameter: 40.,
|
||||
hardness: 50.,
|
||||
hardness: 0.,
|
||||
flow: 100.,
|
||||
spacing: 50.,
|
||||
spacing: 20.,
|
||||
color: ToolColorOptions::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ impl BrushToolData {
|
|||
|
||||
self.transform = DAffine2::IDENTITY;
|
||||
|
||||
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_)).then_some(&self.layer_path)
|
||||
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path)
|
||||
}
|
||||
|
||||
fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque<Message>) {
|
||||
|
|
|
|||
|
|
@ -7,16 +7,19 @@ use crate::messages::prelude::*;
|
|||
use document_legacy::layers::layer_info::LayerDataType;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||
use graphene_core::application_io::ApplicationIo;
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
||||
use graphene_core::text::FontCache;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
|
||||
use graphene_core::{Color, EditorApi};
|
||||
use graphene_core::wasm_application_io::{WasmApplicationIo, WasmSurfaceHandleFrame};
|
||||
use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId};
|
||||
use interpreted_executor::executor::DynamicExecutor;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
|
@ -31,7 +34,9 @@ pub struct NodeRuntime {
|
|||
font_cache: FontCache,
|
||||
receiver: Receiver<NodeRuntimeMessage>,
|
||||
sender: Sender<GenerationResponse>,
|
||||
wasm_io: WasmApplicationIo,
|
||||
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
|
||||
}
|
||||
|
||||
fn get_imaginate_index(name: &str) -> usize {
|
||||
|
|
@ -70,6 +75,8 @@ impl NodeRuntime {
|
|||
sender,
|
||||
font_cache: FontCache::default(),
|
||||
thumbnails: Default::default(),
|
||||
wasm_io: WasmApplicationIo::default(),
|
||||
canvas_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
pub async fn run(&mut self) {
|
||||
|
|
@ -94,7 +101,7 @@ impl NodeRuntime {
|
|||
}) => {
|
||||
let (network, monitor_nodes) = Self::wrap_network(graph);
|
||||
|
||||
let result = self.execute_network(network, image_frame).await;
|
||||
let result = self.execute_network(&path, network, image_frame).await;
|
||||
let mut responses = VecDeque::new();
|
||||
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
||||
let response = GenerationResponse {
|
||||
|
|
@ -113,22 +120,22 @@ impl NodeRuntime {
|
|||
fn wrap_network(network: NodeNetwork) -> (NodeNetwork, Vec<Vec<NodeId>>) {
|
||||
let mut scoped_network = wrap_network_in_scope(network);
|
||||
|
||||
scoped_network.generate_node_paths(&[]);
|
||||
//scoped_network.generate_node_paths(&[]);
|
||||
let monitor_nodes = scoped_network
|
||||
.recursive_nodes()
|
||||
.filter(|(node, _, _)| node.implementation == DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"))
|
||||
.map(|(_, _, path)| path)
|
||||
.collect();
|
||||
scoped_network.duplicate_outputs(&mut generate_uuid);
|
||||
scoped_network.remove_dead_nodes();
|
||||
//scoped_network.remove_dead_nodes();
|
||||
|
||||
(scoped_network, monitor_nodes)
|
||||
}
|
||||
|
||||
async fn execute_network<'a>(&'a mut self, scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>) -> Result<TaggedValue, String> {
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, image_frame: Option<ImageFrame<Color>>) -> Result<TaggedValue, String> {
|
||||
let editor_api = EditorApi {
|
||||
font_cache: Some(&self.font_cache),
|
||||
font_cache: &self.font_cache,
|
||||
image_frame,
|
||||
application_io: &self.wasm_io,
|
||||
};
|
||||
|
||||
// We assume only one output
|
||||
|
|
@ -150,11 +157,30 @@ impl NodeRuntime {
|
|||
Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).await.map_err(|e| e.to_string()),
|
||||
_ => Err("Invalid input type".to_string()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(result) => match TaggedValue::try_from_any(result) {
|
||||
Some(x) => Ok(x),
|
||||
None => Err("Invalid output type".to_string()),
|
||||
},
|
||||
Ok(result) => {
|
||||
if DynAny::type_id(result.as_ref()) == core::any::TypeId::of::<WasmSurfaceHandleFrame>() {
|
||||
let Ok(value) = dyn_any::downcast::<WasmSurfaceHandleFrame>(result) else { unreachable!()};
|
||||
let new_id = value.surface_handle.surface_id;
|
||||
let old_id = self.canvas_cache.insert(path.to_vec(), new_id);
|
||||
if let Some(old_id) = old_id {
|
||||
if old_id != new_id {
|
||||
self.wasm_io.destroy_surface(old_id);
|
||||
}
|
||||
}
|
||||
return Ok(TaggedValue::SurfaceFrame(SurfaceFrame {
|
||||
surface_id: new_id,
|
||||
transform: value.transform,
|
||||
}));
|
||||
}
|
||||
|
||||
let type_name = DynAny::type_name(result.as_ref());
|
||||
match TaggedValue::try_from_any(result) {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(format!("Invalid output type: {}", type_name)),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
|
@ -439,6 +465,11 @@ impl NodeGraphExecutor {
|
|||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
|
||||
}
|
||||
TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => {
|
||||
let transform = transform.to_cols_array();
|
||||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||
responses.add(Operation::SetSurface { path: layer_path, surface_id });
|
||||
}
|
||||
TaggedValue::ImageFrame(ImageFrame { image, transform }) => {
|
||||
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
||||
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue