Restore functionality of GPU infrastructure (#1797)

* Update gpu nodes to compile again

Restructure `gpu-executor` and `wgpu-executor`

And libssl to nix shell

Fix graphene-cli and add half percision color format

Fix texture scaling

Remove vulkan executor

Fix compile errors

Improve execution request deduplication

* Fix warnings

* Fix graph compile issues

* Code review

* Remove test file

* Fix lint

* Wip make node futures send

* Make futures Send on non wasm targets

* Fix warnings

* Fix nested use of block_on

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-07-15 13:14:48 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent 59a943f42f
commit 212f08c6c8
66 changed files with 1572 additions and 1577 deletions
+56 -58
View File
@@ -410,78 +410,76 @@ mod test {
assert_eq!(layers_after_copy[5], shape_id);
}
#[test]
#[tokio::test]
/// This test will fail when you make changes to the underlying serialization format for a document.
fn check_if_demo_art_opens() {
futures::executor::block_on(async {
use crate::messages::layout::utility_types::widget_prelude::*;
async fn check_if_demo_art_opens() {
use crate::messages::layout::utility_types::widget_prelude::*;
let print_problem_to_terminal_on_failure = |value: &String| {
println!();
println!("-------------------------------------------------");
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
println!();
println!("DisplayDialogError details:");
println!();
println!("Description: {value}");
println!("-------------------------------------------------");
println!();
let print_problem_to_terminal_on_failure = |value: &String| {
println!();
println!("-------------------------------------------------");
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
println!();
println!("DisplayDialogError details:");
println!();
println!("Description: {value}");
println!("-------------------------------------------------");
println!();
panic!()
};
panic!()
};
init_logger();
let mut editor = Editor::create();
init_logger();
let mut editor = Editor::create();
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
//
// let files = [
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
// include_str!("../../demo-artwork/red-dress.graphite"),
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
// ];
// for (id, document_serialized_content) in files.iter().enumerate() {
// let document_name = format!("document {id}");
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
//
// let files = [
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
// include_str!("../../demo-artwork/red-dress.graphite"),
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
// ];
// for (id, document_serialized_content) in files.iter().enumerate() {
// let document_name = format!("document {id}");
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
assert_eq!(
document_serialized_content.lines().count(),
1,
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
);
assert_eq!(
document_serialized_content.lines().count(),
1,
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
);
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_serialized_content: document_serialized_content.into(),
});
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_serialized_content,
});
// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE)
.expect("submit_node_graph_evaluation failed");
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE, true)
.expect("submit_node_graph_evaluation failed");
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
print_problem_to_terminal_on_failure(value);
}
for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
print_problem_to_terminal_on_failure(value);
}
}
}
}
}
});
}
}
}
@@ -385,8 +385,6 @@ impl LayoutMessageHandler {
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
trace!("{layout_target:?} diff {diff:#?}");
let message = match layout_target {
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
@@ -23,7 +23,7 @@ use graphene_core::*;
use graphene_std::application_io::RenderConfig;
use graphene_std::wasm_application_io::WasmEditorApi;
#[cfg(feature = "gpu")]
use {gpu_executor::*, graphene_core::application_io::SurfaceHandle, wgpu_executor::WgpuExecutor};
use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput};
use once_cell::sync::Lazy;
use std::collections::VecDeque;
@@ -994,7 +994,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Uniform".to_string(),
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UniformNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode<_>")),
..Default::default()
},
DocumentNode {
@@ -1038,7 +1038,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Storage".to_string(),
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::StorageNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode<_>")),
..Default::default()
},
DocumentNode {
@@ -1082,7 +1082,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Output Buffer".to_string(),
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateOutputBufferNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode<_, _>")),
..Default::default()
},
DocumentNode {
@@ -1134,12 +1134,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Compute Pass".to_string(),
inputs: vec![
NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
NodeInput::network(concrete!(PipelineLayout), 0),
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
NodeInput::network(concrete!(WgpuShaderInput), 2),
NodeInput::network(concrete!(gpu_executor::ComputePassDimensions), 3),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateComputePassNode<_, _, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateComputePassNode<_, _, _>")),
..Default::default()
},
DocumentNode {
@@ -1160,12 +1160,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
default: NodeInput::network(concrete!(PipelineLayout), 0),
},
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
default: NodeInput::network(concrete!(WgpuShaderInput), 2),
},
DocumentInputType {
name: "In",
@@ -1184,12 +1184,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNodeDefinition {
name: "CreatePipelineLayout",
category: "Gpu",
implementation: DocumentNodeImplementation::proto("gpu_executor::CreatePipelineLayoutNode<_, _, _, _>"),
implementation: DocumentNodeImplementation::proto("wgpu_executor::CreatePipelineLayoutNode<_, _, _>"),
inputs: vec![
DocumentInputType {
name: "ShaderHandle",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::ShaderHandle), 0),
default: NodeInput::network(concrete!(ShaderHandle), 0),
},
DocumentInputType {
name: "String",
@@ -1199,12 +1199,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType {
name: "Bindgroup",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(gpu_executor::Bindgroup<WgpuExecutor>), 2),
default: NodeInput::network(concrete!(Bindgroup), 2),
},
DocumentInputType {
name: "ArcShaderInput",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 3),
default: NodeInput::network(concrete!(Arc<WgpuShaderInput>), 3),
},
],
outputs: vec![DocumentOutputType {
@@ -1229,8 +1229,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Execute Compute Pipeline".to_string(),
inputs: vec![NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ExecuteComputePipelineNode<_>")),
inputs: vec![NodeInput::network(concrete!(CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ExecuteComputePipelineNode<_>")),
..Default::default()
},
DocumentNode {
@@ -1273,8 +1273,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Read Output Buffer".to_string(),
inputs: vec![NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ReadOutputBufferNode<_, _>")),
inputs: vec![NodeInput::network(concrete!(Arc<WgpuShaderInput>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ReadOutputBufferNode<_, _>")),
..Default::default()
},
DocumentNode {
@@ -1312,7 +1312,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Gpu Surface".to_string(),
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateGpuSurfaceNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
..Default::default()
},
DocumentNode {
@@ -1350,12 +1350,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Render Texture".to_string(),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![
NodeInput::network(concrete!(ShaderInputFrame<WgpuExecutor>), 0),
NodeInput::network(concrete!(Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface<'_>>>), 0),
NodeInput::network(concrete!(ShaderInputFrame), 0),
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
NodeInput::node(NodeId(0), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::RenderTextureNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::RenderTextureNode<_, _, _>")),
..Default::default()
},
]
@@ -1399,14 +1400,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Upload Texture".to_string(),
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UploadTextureNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode<_>")),
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")),
..Default::default()
},
]
@@ -403,7 +403,7 @@ impl LayerNodeIdentifier {
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
pub const fn new_unchecked(node_id: NodeId) -> Self {
// Safety: will always be >=1
// # Safety: will always be >=1
Self(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })
}
@@ -287,6 +287,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
self.persistent_data.font_cache.insert(font, preview_url, data, is_default);
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
for document_id in self.document_ids.iter() {
let _ = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
true,
);
}
if self.active_document_mut().is_some() {
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -571,7 +578,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
bounds,
transparent_background,
} => {
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render no existent Document");
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
let export_config = ExportConfig {
file_name,
file_type,
@@ -591,8 +598,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
PortfolioMessage::SubmitGraphRender { document_id } => {
let result = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(&document_id).expect("Tried to render no existent Document"),
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
false,
);
if let Err(description) = result {
@@ -666,8 +674,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
impl PortfolioMessageHandler {
pub fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect_node(node_path)
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect_node(node_path).await
}
pub fn document(&self, document_id: DocumentId) -> Option<&DocumentMessageHandler> {
+111 -119
View File
@@ -4,6 +4,7 @@ use crate::messages::portfolio::document::node_graph::document_node_types::wrap_
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use futures::lock::Mutex;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
@@ -24,11 +25,7 @@ use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, ResolvedDocumentNodeTypes};
use glam::{DAffine2, DVec2, UVec2};
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::rc::Rc;
use once_cell::sync::Lazy;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::Arc;
@@ -43,8 +40,6 @@ pub struct NodeRuntime {
recompile_graph: bool,
editor_api: Arc<WasmEditorApi>,
graph_hash: Option<u64>,
node_graph_errors: GraphErrors,
resolved_types: ResolvedDocumentNodeTypes,
monitor_nodes: Vec<Vec<NodeId>>,
@@ -62,6 +57,7 @@ pub struct NodeRuntime {
/// Messages passed from the editor thread to the node runtime thread.
pub enum NodeRuntimeMessage {
GraphUpdate(NodeNetwork),
ExecutionRequest(ExecutionRequest),
FontCacheUpdate(FontCache),
ImaginatePreferencesUpdate(ImaginatePreferences),
@@ -79,7 +75,6 @@ pub struct ExportConfig {
pub struct ExecutionRequest {
execution_id: u64,
graph: NodeNetwork,
render_config: RenderConfig,
}
@@ -90,13 +85,18 @@ pub struct ExecutionResponse {
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_vector_modify: HashMap<NodeId, VectorData>,
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
transform: DAffine2,
}
pub struct CompilationResponse {
result: Result<(), String>,
resolved_types: ResolvedDocumentNodeTypes,
node_graph_errors: GraphErrors,
transform: DAffine2,
}
pub enum NodeGraphUpdate {
ExecutionResponse(ExecutionResponse),
CompilationResponse(CompilationResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
}
@@ -104,7 +104,11 @@ pub enum NodeGraphUpdate {
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender {
fn send_generation_response(&self, response: ExecutionResponse) {
fn send_generation_response(&self, response: CompilationResponse) {
self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response")
}
fn send_execution_response(&self, response: ExecutionResponse) {
self.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect("Failed to send response")
}
}
@@ -115,9 +119,7 @@ impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
}
}
thread_local! {
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
}
pub(crate) static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
impl NodeRuntime {
pub fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
@@ -137,7 +139,6 @@ impl NodeRuntime {
}
.into(),
graph_hash: None,
node_graph_errors: Vec::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
monitor_nodes: Vec::new(),
@@ -150,12 +151,22 @@ impl NodeRuntime {
}
pub async fn run(&mut self) {
let mut requests = self.receiver.try_iter().collect::<Vec<_>>();
// TODO: Currently we still render the document after we submit the node graph execution request.
// This should be avoided in the future.
requests.reverse();
requests.dedup_by(|a, b| matches!(a, NodeRuntimeMessage::ExecutionRequest(_)) && matches!(b, NodeRuntimeMessage::ExecutionRequest(_)));
requests.reverse();
// TODO: Currently we still render the document after we submit the node graph execution request. This should be avoided in the future.
let mut font = None;
let mut imaginate = None;
let mut graph = None;
let mut execution = None;
for request in self.receiver.try_iter() {
match request {
NodeRuntimeMessage::GraphUpdate(_) => graph = Some(request),
NodeRuntimeMessage::ExecutionRequest(_) => execution = Some(request),
NodeRuntimeMessage::FontCacheUpdate(_) => font = Some(request),
NodeRuntimeMessage::ImaginatePreferencesUpdate(_) => imaginate = Some(request),
}
}
let requests = [font, imaginate, graph, execution].into_iter().flatten();
for request in requests {
match request {
NodeRuntimeMessage::FontCacheUpdate(font_cache) => {
@@ -178,25 +189,31 @@ impl NodeRuntime {
.into();
self.recompile_graph = true;
}
NodeRuntimeMessage::ExecutionRequest(ExecutionRequest {
execution_id, graph, render_config, ..
}) => {
NodeRuntimeMessage::GraphUpdate(graph) => {
self.node_graph_errors.clear();
let result = self.update_network(graph).await;
self.sender.send_generation_response(CompilationResponse {
result,
resolved_types: self.resolved_types.clone(),
node_graph_errors: self.node_graph_errors.clone(),
});
self.recompile_graph = true;
}
NodeRuntimeMessage::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
let transform = render_config.viewport.transform;
let result = self.execute_network(graph, render_config).await;
let result = self.execute_network(render_config).await;
let mut responses = VecDeque::new();
self.process_monitor_nodes(&mut responses);
self.sender.send_generation_response(ExecutionResponse {
self.sender.send_execution_response(ExecutionResponse {
execution_id,
result,
responses,
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_vector_modify: self.vector_modify.clone(),
new_upstream_transforms: self.upstream_transforms.clone(),
resolved_types: self.resolved_types.clone(),
node_graph_errors: core::mem::take(&mut self.node_graph_errors),
transform,
});
}
@@ -204,7 +221,7 @@ impl NodeRuntime {
}
}
async fn execute_network(&mut self, graph: NodeNetwork, render_config: RenderConfig) -> Result<TaggedValue, String> {
async fn update_network(&mut self, graph: NodeNetwork) -> Result<(), String> {
if self.editor_api.application_io.is_none() {
self.editor_api = WasmEditorApi {
application_io: Some(WasmApplicationIo::new().await.into()),
@@ -215,44 +232,31 @@ impl NodeRuntime {
.into();
}
let editor_api = &self.editor_api;
// Required to ensure that the appropriate proto nodes are reinserted when the Editor API changes.
let mut graph_input_hash = DefaultHasher::new();
editor_api.font_cache.hash(&mut graph_input_hash);
let _font_hash_code = graph_input_hash.finish();
graph.hash(&mut graph_input_hash);
let hash_code = graph_input_hash.finish();
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
self.monitor_nodes = scoped_network
.recursive_nodes()
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
if self.graph_hash != Some(hash_code) {
self.graph_hash = None;
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) {
Ok(network) => network,
Err(e) => return Err(e),
};
assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
if let Err(e) = self.executor.update(proto_network).await {
self.node_graph_errors = e;
}
self.resolved_types = self.executor.document_node_types();
if self.graph_hash.is_none() || self.recompile_graph {
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
self.monitor_nodes = scoped_network
.recursive_nodes()
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) {
Ok(network) => network,
Err(e) => return Err(e),
};
assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
if let Err(e) = self.executor.update(proto_network).await {
self.node_graph_errors = e;
} else {
self.graph_hash = Some(hash_code);
}
self.resolved_types = self.executor.document_node_types();
}
Ok(())
}
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
use graph_craft::graphene_compiler::Executor;
let result = match self.executor.input_type() {
@@ -266,16 +270,6 @@ impl NodeRuntime {
Err(e) => return Err(e),
};
// if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
// let old_id = self.canvas_cache.insert(path.to_vec(), surface_id);
// if let Some(old_id) = old_id {
// if old_id != surface_id {
// if let Some(io) = self.wasm_io.as_ref() {
// io.destroy_surface(old_id)
// }
// }
// }
// }
Ok(result)
}
@@ -369,42 +363,24 @@ impl NodeRuntime {
}
}
pub fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
NODE_RUNTIME
.try_with(|runtime| {
let runtime = runtime.try_borrow();
if let Ok(ref runtime) = runtime {
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path).flatten();
}
}
None
})
.unwrap_or(None)
pub async fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
let runtime = NODE_RUNTIME.lock().await;
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path).flatten();
}
None
}
pub async fn run_node_graph() {
let result = NODE_RUNTIME.try_with(|runtime| {
let runtime = runtime.clone();
async move {
let mut runtime = runtime.try_borrow_mut();
if let Ok(ref mut runtime) = runtime {
if let Some(ref mut runtime) = runtime.as_mut() {
runtime.run().await;
}
}
}
});
if let Ok(result) = result {
result.await;
let mut runtime = NODE_RUNTIME.lock().await;
if let Some(ref mut runtime) = runtime.as_mut() {
runtime.run().await;
}
}
pub fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
NODE_RUNTIME.with(|node_runtime| {
let mut node_runtime = node_runtime.borrow_mut();
node_runtime.replace(runtime)
})
pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
let mut node_runtime = NODE_RUNTIME.lock().await;
node_runtime.replace(runtime)
}
#[derive(Debug)]
@@ -412,6 +388,7 @@ pub struct NodeGraphExecutor {
sender: Sender<NodeRuntimeMessage>,
receiver: Receiver<NodeGraphUpdate>,
futures: HashMap<u64, ExecutionContext>,
node_graph_hash: u64,
}
#[derive(Debug, Clone)]
@@ -423,32 +400,29 @@ impl Default for NodeGraphExecutor {
fn default() -> Self {
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (response_sender, response_receiver) = std::sync::mpsc::channel();
replace_node_runtime(NodeRuntime::new(request_receiver, response_sender));
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));
Self {
futures: Default::default(),
sender: request_sender,
receiver: response_receiver,
node_graph_hash: 0,
}
}
}
impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, network: NodeNetwork, render_config: RenderConfig) -> u64 {
fn queue_execution(&self, render_config: RenderConfig) -> u64 {
let execution_id = generate_uuid();
let request = ExecutionRequest {
graph: network,
execution_id,
render_config,
};
let request = ExecutionRequest { execution_id, render_config };
self.sender.send(NodeRuntimeMessage::ExecutionRequest(request)).expect("Failed to send generation request");
execution_id
}
pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
introspect_node(path)
pub async fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
introspect_node(path).await
}
pub fn update_font_cache(&self, font_cache: FontCache) {
@@ -473,7 +447,7 @@ impl NodeGraphExecutor {
return None;
};
let introspection_node = find_node(wrapped_network)?;
let introspection = self.introspect_node(&[node_path, &[introspection_node]].concat())?;
let introspection = futures::executor::block_on(self.introspect_node(&[node_path, &[introspection_node]].concat()))?;
let Some(downcasted): Option<&T> = <dyn std::any::Any>::downcast_ref(introspection.as_ref()) else {
log::warn!("Failed to downcast type for introspection");
return None;
@@ -482,9 +456,13 @@ impl NodeGraphExecutor {
}
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2) -> Result<(), String> {
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, ignore_hash: bool) -> Result<(), String> {
// Get the node graph layer
let network = document.network().clone();
let network_hash = document.network().current_hash();
if network_hash != self.node_graph_hash || ignore_hash {
self.node_graph_hash = network_hash;
self.sender.send(NodeRuntimeMessage::GraphUpdate(document.network.clone())).map_err(|e| e.to_string())?;
}
let render_config = RenderConfig {
viewport: Footprint {
@@ -502,7 +480,7 @@ impl NodeGraphExecutor {
};
// Execute the node graph
let execution_id = self.queue_execution(network, render_config);
let execution_id = self.queue_execution(render_config);
self.futures.insert(execution_id, ExecutionContext { export_config: None });
@@ -537,7 +515,8 @@ impl NodeGraphExecutor {
export_config.size = size;
// Execute the node graph
let execution_id = self.queue_execution(network, render_config);
self.sender.send(NodeRuntimeMessage::GraphUpdate(network)).map_err(|e| e.to_string())?;
let execution_id = self.queue_execution(render_config);
let execution_context = ExecutionContext { export_config: Some(export_config) };
self.futures.insert(execution_id, execution_context);
@@ -585,13 +564,10 @@ impl NodeGraphExecutor {
new_click_targets,
new_vector_modify,
new_upstream_transforms,
resolved_types,
node_graph_errors,
transform,
} = execution_response;
responses.extend(existing_responses.into_iter().map(Into::into));
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
responses.add(NodeGraphMessage::SendGraph);
responses.add(OverlaysMessage::Draw);
@@ -616,6 +592,22 @@ impl NodeGraphExecutor {
self.process_node_graph_output(node_graph_output, transform, responses)?
}
}
NodeGraphUpdate::CompilationResponse(execution_response) => {
let CompilationResponse {
resolved_types,
node_graph_errors,
result,
} = execution_response;
if let Err(e) = result {
// Clear the click targets while the graph is in an un-renderable state
document.metadata.update_from_monitor(HashMap::new(), HashMap::new());
log::trace!("{e}");
return Err("Node graph evaluation failed".to_string());
};
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));
}