diff --git a/Cargo.lock b/Cargo.lock index 5c88792c20..5d463a1f0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ - "event-listener", + "event-listener 5.3.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -346,11 +346,20 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener 2.5.3", +] + [[package]] name = "async-process" version = "2.2.3" @@ -364,7 +373,7 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener", + "event-listener 5.3.1", "futures-lite", "rustix", "tracing", @@ -1016,6 +1025,7 @@ dependencies = [ "graphene-core", "reqwest 0.12.5", "serde_json", + "wgpu-executor", ] [[package]] @@ -1157,15 +1167,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -1242,6 +1243,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "d3d12" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813" +dependencies = [ + "bitflags 2.6.0", + "libloading 0.8.4", + "winapi", +] + [[package]] name = "darling" version = "0.20.9" @@ -1374,6 +1386,15 @@ dependencies = [ "libloading 0.8.4", ] +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1549,6 +1570,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "5.3.1" @@ -1566,7 +1593,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener", + "event-listener 5.3.1", "pin-project-lite", ] @@ -2213,6 +2240,7 @@ dependencies = [ "log", "serde", "serde_json", + "wgpu-executor", ] [[package]] @@ -2222,7 +2250,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.6.0", - "gpu-descriptor-types", + "gpu-descriptor-types 0.1.2", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +dependencies = [ + "bitflags 2.6.0", + "gpu-descriptor-types 0.2.0", "hashbrown 0.14.5", ] @@ -2235,6 +2274,15 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "gpu-executor" version = "0.1.0" @@ -2261,6 +2309,7 @@ dependencies = [ "bezier-rs", "bytemuck", "dyn-any", + "futures", "glam", "graphene-core", "js-sys", @@ -2299,7 +2348,7 @@ dependencies = [ "serde_json", "tokio", "wasm-bindgen", - "wgpu", + "wgpu 0.20.1", "wgpu-executor", ] @@ -2312,6 +2361,7 @@ dependencies = [ "bytemuck", "dyn-any", "glam", + "half", "image 0.25.1", "js-sys", "kurbo 0.11.0 (git+https://github.com/linebender/kurbo.git)", @@ -2364,11 +2414,10 @@ dependencies = [ "url", "usvg", "vello", - "vulkan-executor", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", + "wgpu 0.20.1", "wgpu-executor", "wgpu-types 0.17.0", "winit", @@ -2396,6 +2445,7 @@ dependencies = [ name = "graphite-editor" version = "0.0.0" dependencies = [ + "async-mutex", "bezier-rs", "bitflags 2.6.0", "derivative", @@ -2455,7 +2505,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", + "wgpu 0.20.1", ] [[package]] @@ -2567,8 +2617,10 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ + "bytemuck", "cfg-if", "crunchy", + "serde", ] [[package]] @@ -3038,6 +3090,7 @@ dependencies = [ "num-traits", "once_cell", "serde", + "wgpu 0.20.1", "wgpu-executor", ] @@ -3332,6 +3385,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -3484,6 +3543,21 @@ dependencies = [ "paste", ] +[[package]] +name = "metal" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + [[package]] name = "meval" version = "0.2.0" @@ -3533,6 +3607,27 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ + "bit-set", + "bitflags 2.6.0", + "codespan-reporting", + "hexf-parse", + "indexmap 2.2.6", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "naga" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" +dependencies = [ + "arrayvec", "bit-set", "bitflags 2.6.0", "codespan-reporting", @@ -6900,7 +6995,7 @@ dependencies = [ "raw-window-handle 0.6.2", "skrifa", "vello_encoding", - "wgpu", + "wgpu 0.19.4", ] [[package]] @@ -6933,15 +7028,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vk-parse" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6a0bda9bbe6b9e50e6456c80aa8fe4cca3b21e4311a1130c41e4915ec2e32a" -dependencies = [ - "xml-rs", -] - [[package]] name = "vswhom" version = "0.1.0" @@ -6962,49 +7048,6 @@ dependencies = [ "libc", ] -[[package]] -name = "vulkan-executor" -version = "0.1.0" -dependencies = [ - "anyhow", - "base64 0.22.1", - "bytemuck", - "dyn-any", - "glam", - "graph-craft", - "graphene-core", - "log", - "num-traits", - "serde", - "vulkano", -] - -[[package]] -name = "vulkano" -version = "0.31.0" -source = "git+https://github.com/GraphiteEditor/vulkano?branch=fix_rust_gpu#dbec4e91030c60849caccaa3b933fc2aac0d327b" -dependencies = [ - "ahash", - "ash", - "bytemuck", - "core-graphics-types", - "crossbeam-queue", - "half", - "heck 0.4.1", - "indexmap 1.9.3", - "lazy_static", - "libloading 0.7.4", - "objc", - "parking_lot", - "proc-macro2", - "quote", - "regex", - "serde", - "serde_json", - "smallvec", - "vk-parse", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -7355,7 +7398,7 @@ dependencies = [ "cfg_aliases 0.1.1", "js-sys", "log", - "naga", + "naga 0.19.2", "parking_lot", "profiling", "raw-window-handle 0.6.2", @@ -7364,11 +7407,37 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", + "wgpu-core 0.19.4", + "wgpu-hal 0.19.4", "wgpu-types 0.19.2", ] +[[package]] +name = "wgpu" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga 0.20.0", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 0.21.1", + "wgpu-hal 0.21.1", + "wgpu-types 0.20.0", +] + [[package]] name = "wgpu-core" version = "0.19.4" @@ -7382,7 +7451,7 @@ dependencies = [ "codespan-reporting", "indexmap 2.2.6", "log", - "naga", + "naga 0.19.2", "once_cell", "parking_lot", "profiling", @@ -7391,10 +7460,38 @@ dependencies = [ "smallvec", "thiserror", "web-sys", - "wgpu-hal", + "wgpu-hal 0.19.4", "wgpu-types 0.19.2", ] +[[package]] +name = "wgpu-core" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.6.0", + "bytemuck", + "cfg_aliases 0.1.1", + "codespan-reporting", + "document-features", + "indexmap 2.2.6", + "log", + "naga 0.20.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal 0.21.1", + "wgpu-types 0.20.0", +] + [[package]] name = "wgpu-executor" version = "0.1.0" @@ -7408,13 +7505,15 @@ dependencies = [ "glam", "gpu-executor", "graphene-core", + "half", "log", + "node-macro", "num-traits", "nvtx", "serde", "spirv", "web-sys", - "wgpu", + "wgpu 0.20.1", "winit", ] @@ -7432,20 +7531,20 @@ dependencies = [ "block", "cfg_aliases 0.1.1", "core-graphics-types", - "d3d12", + "d3d12 0.19.0", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", - "gpu-descriptor", + "gpu-descriptor 0.2.4", "hassle-rs", "js-sys", "khronos-egl", "libc", "libloading 0.8.4", "log", - "metal", - "naga", + "metal 0.27.0", + "naga 0.19.2", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", @@ -7463,6 +7562,51 @@ dependencies = [ "winapi", ] +[[package]] +name = "wgpu-hal" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.6.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12 0.20.0", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor 0.3.0", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.4", + "log", + "metal 0.28.0", + "naga 0.20.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types 0.20.0", + "winapi", +] + [[package]] name = "wgpu-types" version = "0.17.0" @@ -7485,6 +7629,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgpu-types" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" +dependencies = [ + "bitflags 2.6.0", + "js-sys", + "web-sys", +] + [[package]] name = "wide" version = "0.7.25" @@ -8222,7 +8377,7 @@ dependencies = [ "async-trait", "blocking", "enumflags2", - "event-listener", + "event-listener 5.3.1", "futures-core", "futures-sink", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index e3af794504..d28454b0c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "node-graph/node-macro", "node-graph/compilation-server", "node-graph/compilation-client", - "node-graph/vulkan-executor", "node-graph/wgpu-executor", "node-graph/gpu-executor", "node-graph/gpu-compiler/gpu-compiler-bin-wrapper", @@ -58,7 +57,7 @@ ron = "0.8" fastnoise-lite = "1.1" spirv-std = { git = "https://github.com/EmbarkStudios/rust-gpu.git" } wgpu-types = "0.17" -wgpu = "0.19" +wgpu = { version = "0.20", features = ["strict_asserts", "api_log_info"] } once_cell = "1.13" # Remove when `core::cell::LazyCell` () is stabilized in Rust 1.80 and we bump our MSRV wasm-bindgen = "=0.2.92" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/getting-started/_index.md`. We pin this version because wasm-bindgen upgrades may break various things. wasm-bindgen-futures = "0.4" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 29c98ff3d7..4f27ab0fe6 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-editor" publish = false version = "0.0.0" -rust-version = "1.70.0" +rust-version = "1.79" authors = ["Graphite Authors "] edition = "2021" readme = "../README.md" @@ -41,6 +41,7 @@ thiserror = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } bezier-rs = { workspace = true } +futures = { workspace = true } glam = { workspace = true, features = ["serde", "debug-glam-assert"] } derivative = { workspace = true } specta.workspace = true @@ -65,6 +66,7 @@ gpu-executor = { path = "../node-graph/gpu-executor", optional = true } # Optional workspace dependencies wasm-bindgen = { workspace = true, optional = true } wasm-bindgen-futures = { workspace = true, optional = true } +async-mutex = "1.4.0" [dev-dependencies] # Workspace dependencies diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 392ab54fb1..db4d1a208c 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -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); } } } } } - }); + } } } diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index a4883e9b8f..8bedf034c9 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -385,8 +385,6 @@ impl LayoutMessageHandler { fn send_diff(&self, mut diff: Vec, layout_target: LayoutTarget, responses: &mut VecDeque, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) { 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 }, diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs index fc0fc9dc35..07ea75a79e 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs @@ -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 { 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 { DocumentNode { name: "Create Storage".to_string(), inputs: vec![NodeInput::network(concrete!(Vec), 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 { 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 { DocumentNode { name: "Create Compute Pass".to_string(), inputs: vec![ - NodeInput::network(concrete!(gpu_executor::PipelineLayout), 0), + NodeInput::network(concrete!(PipelineLayout), 0), NodeInput::node(NodeId(0), 0), - NodeInput::network(concrete!(ShaderInput), 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 { DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(gpu_executor::PipelineLayout), 0), + default: NodeInput::network(concrete!(PipelineLayout), 0), }, DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(ShaderInput), 2), + default: NodeInput::network(concrete!(WgpuShaderInput), 2), }, DocumentInputType { name: "In", @@ -1184,12 +1184,12 @@ fn static_nodes() -> Vec { 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!(::ShaderHandle), 0), + default: NodeInput::network(concrete!(ShaderHandle), 0), }, DocumentInputType { name: "String", @@ -1199,12 +1199,12 @@ fn static_nodes() -> Vec { DocumentInputType { name: "Bindgroup", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(gpu_executor::Bindgroup), 2), + default: NodeInput::network(concrete!(Bindgroup), 2), }, DocumentInputType { name: "ArcShaderInput", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(Arc>), 3), + default: NodeInput::network(concrete!(Arc), 3), }, ], outputs: vec![DocumentOutputType { @@ -1229,8 +1229,8 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Execute Compute Pipeline".to_string(), - inputs: vec![NodeInput::network(concrete!(::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 { }, DocumentNode { name: "Read Output Buffer".to_string(), - inputs: vec![NodeInput::network(concrete!(Arc>), 0), NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ReadOutputBufferNode<_, _>")), + inputs: vec![NodeInput::network(concrete!(Arc), 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 { 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 { }, DocumentNode { name: "Render Texture".to_string(), + manual_composition: Some(concrete!(Footprint)), inputs: vec![ - NodeInput::network(concrete!(ShaderInputFrame), 0), - NodeInput::network(concrete!(Arc::Surface<'_>>>), 0), + NodeInput::network(concrete!(ShaderInputFrame), 0), + NodeInput::network(concrete!(Arc), 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 { DocumentNode { name: "Upload Texture".to_string(), inputs: vec![NodeInput::network(concrete!(ImageFrame), 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() }, ] diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 93caa8db71..c2f6cd44d2 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -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) }) } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index f6f6e00b6b..5752a3f9c1 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -287,6 +287,13 @@ impl MessageHandler> 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> 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> 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> for PortfolioMes } impl PortfolioMessageHandler { - pub fn introspect_node(&self, node_path: &[NodeId]) -> Option> { - self.executor.introspect_node(node_path) + pub async fn introspect_node(&self, node_path: &[NodeId]) -> Option> { + self.executor.introspect_node(node_path).await } pub fn document(&self, document_id: DocumentId) -> Option<&DocumentMessageHandler> { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 655ad95d79..cbfab38029 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -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, - - graph_hash: Option, node_graph_errors: GraphErrors, resolved_types: ResolvedDocumentNodeTypes, monitor_nodes: Vec>, @@ -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>, new_vector_modify: HashMap, new_upstream_transforms: HashMap, + 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); 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>> = Rc::new(RefCell::new(None)); -} +pub(crate) static NODE_RUNTIME: Lazy>> = Lazy::new(|| Mutex::new(None)); impl NodeRuntime { pub fn new(receiver: Receiver, sender: Sender) -> 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::>(); - // 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 { + 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::>(); - 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::>(); - - // 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 { 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> { - 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> { + 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 { - 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 { + let mut node_runtime = NODE_RUNTIME.lock().await; + node_runtime.replace(runtime) } #[derive(Debug)] @@ -412,6 +388,7 @@ pub struct NodeGraphExecutor { sender: Sender, receiver: Receiver, futures: HashMap, + 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> { - introspect_node(path) + pub async fn introspect_node(&self, path: &[NodeId]) -> Option> { + 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> = ::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)); } diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index d2fd6fa639..8e2b6d7ca4 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" repository = "" default-run = "graphite-desktop" edition = "2021" -rust-version = "1.70.0" +rust-version = "1.79" [features] # by default Tauri runs in production mode diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index b41bd59d87..c12ea56375 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-wasm" publish = false version = "0.0.0" -rust-version = "1.70.0" +rust-version = "1.79" authors = ["Graphite Authors "] edition = "2021" readme = "../../README.md" @@ -20,7 +20,9 @@ crate-type = ["cdylib", "rlib"] [dependencies] # Local dependencies -editor = { path = "../../editor", package = "graphite-editor" } +editor = { path = "../../editor", package = "graphite-editor", features = [ + "gpu", +] } # Workspace dependencies graph-craft = { workspace = true } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index a7e03eae1d..824e5f4c4d 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -658,7 +658,7 @@ impl EditorHandle { let (_, request_receiver) = std::sync::mpsc::channel(); let (response_sender, _) = std::sync::mpsc::channel(); - let old_runtime = replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)); + let old_runtime = replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)).await; let mut editor = Editor::new(); let document_id = DocumentId(document_id); @@ -687,7 +687,7 @@ impl EditorHandle { 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) + .submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(), glam::UVec2::ONE, true) .unwrap(); editor::node_graph_executor::run_node_graph().await; @@ -698,7 +698,7 @@ impl EditorHandle { err ); - replace_node_runtime(old_runtime.unwrap()); + replace_node_runtime(old_runtime.unwrap()).await; let document_name = document_name.clone() + "__DO_NOT_UPGRADE__"; self.dispatch(PortfolioMessage::OpenDocumentFileWithId { @@ -784,7 +784,7 @@ impl EditorHandle { let document_serialized_content = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap().serialize_document(); - replace_node_runtime(old_runtime.unwrap()); + replace_node_runtime(old_runtime.unwrap()).await; self.dispatch(PortfolioMessage::OpenDocumentFileWithId { document_id, diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index b6ab090a70..00f034d0cf 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -75,11 +75,16 @@ extern "C" { pub struct WasmLog; impl log::Log for WasmLog { + #[inline] fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= log::Level::Info + metadata.level() <= log::max_level() } fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + let (log, name, color): (fn(&str, &str), &str, &str) = match record.level() { log::Level::Trace => (log, "trace", "color:plum"), log::Level::Debug => (log, "debug", "color:cyan"), @@ -87,7 +92,10 @@ impl log::Log for WasmLog { log::Level::Info => (info, "info", "color:mediumseagreen"), log::Level::Error => (error, "error", "color:red"), }; - let msg = &format!("%c{}\t{}", name, record.args()); + + let file = record.file().unwrap_or_else(|| record.target()); + let line = record.line().map_or_else(|| "[Unknown]".to_string(), |line| line.to_string()); + let msg = &format!("%c{} {file}:{line} \n{}%c", name, record.args()); log(msg, color) } diff --git a/libraries/bezier-rs/Cargo.toml b/libraries/bezier-rs/Cargo.toml index 74807257d8..176561ca10 100644 --- a/libraries/bezier-rs/Cargo.toml +++ b/libraries/bezier-rs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bezier-rs" version = "0.4.0" -rust-version = "1.70.0" +rust-version = "1.79" edition = "2021" authors = ["Graphite Authors "] description = "Computational geometry algorithms for Bézier segments and shapes useful in the context of 2D graphics" diff --git a/libraries/dyn-any/Cargo.toml b/libraries/dyn-any/Cargo.toml index 1faaa2b153..032c37d7e6 100644 --- a/libraries/dyn-any/Cargo.toml +++ b/libraries/dyn-any/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dyn-any" version = "0.3.1" -rust-version = "1.70.0" +rust-version = "1.79" edition = "2021" authors = ["Graphite Authors "] description = "An Any trait that works for arbitrary lifetimes" diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 2c4dc911d4..840888b595 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -199,9 +199,15 @@ unsafe impl StaticType for dyn for<'i> DynAny<'_> + '_ { unsafe impl StaticType for dyn for<'i> DynAny<'_> + Send + Sync + '_ { type Static = dyn DynAny<'static> + Send + Sync; } +unsafe impl StaticType for dyn for<'i> DynAny<'_> + Send + '_ { + type Static = dyn DynAny<'static> + Sync; +} unsafe impl StaticType for dyn core::future::Future + Send + Sync + '_ { type Static = dyn core::future::Future + Send + Sync; } +unsafe impl StaticType for dyn core::future::Future + Send + '_ { + type Static = dyn core::future::Future + Send; +} unsafe impl StaticType for dyn core::future::Future + '_ { type Static = dyn core::future::Future; } @@ -332,3 +338,32 @@ fn simple_downcast_panic() { let x = Box::new(3_i32) as Box; assert_eq!(*downcast::(x).expect("attempted to perform invalid downcast"), 3_u32); } + +#[cfg(not(target_arch = "wasm32"))] +pub trait WasmNotSend: Send {} +#[cfg(target_arch = "wasm32")] +pub trait WasmNotSend {} + +#[cfg(not(target_arch = "wasm32"))] +impl WasmNotSend for T {} + +#[cfg(target_arch = "wasm32")] +impl WasmNotSend for T {} + +#[cfg(not(target_arch = "wasm32"))] +pub trait WasmNotSync: Sync {} +#[cfg(target_arch = "wasm32")] +pub trait WasmNotSync {} + +#[cfg(not(target_arch = "wasm32"))] +impl WasmNotSync for T {} + +#[cfg(target_arch = "wasm32")] +impl WasmNotSync for T {} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "alloc")] +pub type DynFuture<'n, T> = Pin + 'n + Send>>; +#[cfg(target_arch = "wasm32")] +#[cfg(feature = "alloc")] +pub type DynFuture<'n, T> = Pin + 'n>>; diff --git a/node-graph/compilation-client/Cargo.toml b/node-graph/compilation-client/Cargo.toml index 0673780cab..e27bf9966f 100644 --- a/node-graph/compilation-client/Cargo.toml +++ b/node-graph/compilation-client/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" # Local dependencies graph-craft = { path = "../graph-craft", features = ["serde"] } gpu-executor = { path = "../gpu-executor" } +wgpu-executor = { path = "../wgpu-executor" } gpu-compiler-bin-wrapper = { path = "../gpu-compiler/gpu-compiler-bin-wrapper" } # Workspace dependencies diff --git a/node-graph/compilation-client/src/lib.rs b/node-graph/compilation-client/src/lib.rs index d4f5e501af..181de42d63 100644 --- a/node-graph/compilation-client/src/lib.rs +++ b/node-graph/compilation-client/src/lib.rs @@ -1,6 +1,6 @@ use gpu_compiler_bin_wrapper::CompileRequest; -use gpu_executor::ShaderIO; use graph_craft::{proto::ProtoNetwork, Type}; +use wgpu_executor::ShaderIO; pub async fn compile(networks: Vec, inputs: Vec, outputs: Vec, io: ShaderIO) -> Result { let client = reqwest::Client::new(); diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index 77600bd187..366ad6af0d 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -1,10 +1,10 @@ use gpu_compiler_bin_wrapper::CompileRequest; -use gpu_executor::{ShaderIO, ShaderInput}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graphene_core::raster::adjustments::BlendMode; use graphene_core::Color; +use wgpu_executor::{ShaderIO, ShaderInput}; use std::time::Duration; diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index 13da969755..4b10809a4e 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -29,6 +29,7 @@ serde = [ "glam/serde", "bezier-rs/serde", "bezier-rs/serde", + "half/serde", "base64", ] @@ -44,6 +45,9 @@ glam = { workspace = true, default-features = false, features = [ "scalar-math", ] } +# Required dependencies +half = { version = "2.4.1", default-features = false, features = ["bytemuck"] } + # Optional workspace dependencies dyn-any = { workspace = true, optional = true } spirv-std = { workspace = true, optional = true } diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index 3bb50d869f..f78277f6f8 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -16,6 +16,12 @@ use glam::DAffine2; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SurfaceId(pub u64); +impl core::fmt::Display for SurfaceId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SurfaceFrame { @@ -30,6 +36,17 @@ impl Hash for SurfaceFrame { } } +impl Transform for SurfaceFrame { + fn transform(&self) -> DAffine2 { + self.transform + } +} +impl TransformMut for SurfaceFrame { + fn transform_mut(&mut self) -> &mut DAffine2 { + &mut self.transform + } +} + unsafe impl StaticType for SurfaceFrame { type Static = SurfaceFrame; } @@ -48,6 +65,10 @@ pub struct SurfaceHandle { pub surface_id: SurfaceId, pub surface: Surface, } +// #[cfg(target_arch = "wasm32")] +// unsafe impl Send for SurfaceHandle {} +// #[cfg(target_arch = "wasm32")] +// unsafe impl Sync for SurfaceHandle {} unsafe impl StaticType for SurfaceHandle { type Static = SurfaceHandle; @@ -83,7 +104,10 @@ impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> { } }*/ +#[cfg(target_arch = "wasm32")] pub type ResourceFuture = Pin, ApplicationError>>>>; +#[cfg(not(target_arch = "wasm32"))] +pub type ResourceFuture = Pin, ApplicationError>> + Send>>; pub trait ApplicationIo { type Surface; diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index fd3fc012b9..debcb416ba 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -1,15 +1,16 @@ +use crate::application_io::SurfaceHandleFrame; use crate::raster::{BlendMode, ImageFrame}; +use crate::renderer::GraphicElementRendered; use crate::transform::Footprint; use crate::vector::VectorData; -use crate::{Color, Node}; +use crate::{Color, Node, SurfaceFrame}; -use bezier_rs::BezierHandles; use dyn_any::{DynAny, StaticType}; use node_macro::node_fn; -use core::future::Future; use core::ops::{Deref, DerefMut}; use glam::{DAffine2, DVec2, IVec2, UVec2}; +use web_sys::HtmlCanvasElement; pub mod renderer; @@ -67,6 +68,8 @@ pub enum GraphicElement { VectorData(Box), /// A bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image ImageFrame(ImageFrame), + /// A Canvas evement + Surface(SurfaceFrame), } // TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it. @@ -127,10 +130,10 @@ pub struct ConstructLayerNode { } #[node_fn(ConstructLayerNode)] -async fn construct_layer, Fut1: Future, Fut2: Future>( +async fn construct_layer + Send>( footprint: crate::transform::Footprint, - mut stack: impl Node, - graphic_element: impl Node, + mut stack: impl Node, + graphic_element: impl Node, ) -> GraphicGroup { let graphic_element = self.graphic_element.eval(footprint).await; let mut stack = self.stack.eval(footprint).await; @@ -162,9 +165,9 @@ pub struct ConstructArtboardNode>( +async fn construct_artboard( mut footprint: Footprint, - contents: impl Node, + contents: impl Node, label: String, location: IVec2, dimensions: IVec2, @@ -189,11 +192,7 @@ pub struct AddArtboardNode { } #[node_fn(AddArtboardNode)] -async fn add_artboard, Fut1: Future, Fut2: Future>( - footprint: Footprint, - artboards: impl Node, - artboard: impl Node, -) -> ArtboardGroup { +async fn add_artboard + Send>(footprint: Footprint, artboards: impl Node, artboard: impl Node) -> ArtboardGroup { let artboard = self.artboard.eval(footprint).await; let mut artboards = self.artboards.eval(footprint).await; @@ -229,6 +228,25 @@ impl From for GraphicElement { GraphicElement::GraphicGroup(graphic_group) } } +impl From for GraphicElement { + fn from(surface: SurfaceFrame) -> Self { + GraphicElement::Surface(surface) + } +} +impl From>> for GraphicElement { + fn from(surface: alloc::sync::Arc>) -> Self { + let surface_id = surface.surface_handle.surface_id; + let transform = surface.transform; + GraphicElement::Surface(SurfaceFrame { surface_id, transform }) + } +} +impl From> for GraphicElement { + fn from(surface: SurfaceHandleFrame) -> Self { + let surface_id = surface.surface_handle.surface_id; + let transform = surface.transform; + GraphicElement::Surface(SurfaceFrame { surface_id, transform }) + } +} impl Deref for GraphicGroup { type Target = Vec; @@ -287,72 +305,3 @@ impl GraphicGroup { tree } } - -impl GraphicElement { - fn to_usvg_node(&self) -> usvg::Node { - fn to_transform(transform: DAffine2) -> usvg::Transform { - let cols = transform.to_cols_array(); - usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32) - } - - match self { - GraphicElement::VectorData(vector_data) => { - use usvg::tiny_skia_path::PathBuilder; - let mut builder = PathBuilder::new(); - - let transform = to_transform(vector_data.transform); - for subpath in vector_data.stroke_bezier_paths() { - let start = vector_data.transform.transform_point2(subpath[0].anchor); - builder.move_to(start.x as f32, start.y as f32); - for bezier in subpath.iter() { - bezier.apply_transformation(|pos| vector_data.transform.transform_point2(pos)); - let end = bezier.end; - match bezier.handles { - BezierHandles::Linear => builder.line_to(end.x as f32, end.y as f32), - BezierHandles::Quadratic { handle } => builder.quad_to(handle.x as f32, handle.y as f32, end.x as f32, end.y as f32), - BezierHandles::Cubic { handle_start, handle_end } => { - builder.cubic_to(handle_start.x as f32, handle_start.y as f32, handle_end.x as f32, handle_end.y as f32, end.x as f32, end.y as f32) - } - } - } - if subpath.closed { - builder.close() - } - } - let path = builder.finish().unwrap(); - let mut path = usvg::Path::new(path.into()); - path.abs_transform = transform; - // TODO: use proper style - path.fill = None; - path.stroke = Some(usvg::Stroke::default()); - usvg::Node::Path(Box::new(path)) - } - GraphicElement::ImageFrame(image_frame) => { - if image_frame.image.width * image_frame.image.height == 0 { - return usvg::Node::Group(Box::default()); - } - let png = image_frame.image.to_png(); - usvg::Node::Image(Box::new(usvg::Image { - id: String::new(), - abs_transform: to_transform(image_frame.transform), - visibility: usvg::Visibility::Visible, - view_box: usvg::ViewBox { - rect: usvg::NonZeroRect::from_xywh(0., 0., 1., 1.).unwrap(), - aspect: usvg::AspectRatio::default(), - }, - rendering_mode: usvg::ImageRendering::OptimizeSpeed, - kind: usvg::ImageKind::PNG(png.into()), - bounding_box: None, - })) - } - GraphicElement::GraphicGroup(group) => { - let mut group_element = usvg::Group::default(); - - for element in group.iter() { - group_element.children.push(element.to_usvg_node()); - } - usvg::Node::Group(Box::new(group_element)) - } - } - } -} diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index c5a9255aa1..6f6d11fe76 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -1,9 +1,11 @@ mod quad; +use crate::raster::bbox::Bbox; use crate::raster::{BlendMode, Image, ImageFrame}; use crate::transform::Transform; use crate::uuid::generate_uuid; use crate::vector::PointId; +use crate::SurfaceFrame; use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup}; pub use quad::Quad; @@ -489,6 +491,39 @@ impl GraphicElementRendered for crate::ArtboardGroup { } } +impl GraphicElementRendered for SurfaceFrame { + fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { + let transform = self.transform; + let (width, height) = (transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()); + let matrix = (transform * DAffine2::from_scale((width, height).into()).inverse()) + .to_cols_array() + .iter() + .enumerate() + .fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," })); + + let canvas = format!( + r#"
"#, + width.abs(), + height.abs(), + matrix, + self.surface_id + ); + render.svg.push(canvas.into()) + } + + fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { + let bbox = Bbox::from_transform(transform); + let aabb = bbox.to_axis_aligned_bbox(); + Some([aabb.start, aabb.end]) + } + + fn add_click_targets(&self, _click_targets: &mut Vec) {} + + fn contains_artboard(&self) -> bool { + false + } +} + impl GraphicElementRendered for ImageFrame { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { let transform: String = format_transform_matrix(self.transform * render.transform); @@ -559,6 +594,7 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params), GraphicElement::ImageFrame(image_frame) => image_frame.render_svg(render, render_params), GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params), + GraphicElement::Surface(surface) => surface.render_svg(render, render_params), } } @@ -567,6 +603,7 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::VectorData(vector_data) => GraphicElementRendered::bounding_box(&**vector_data, transform), GraphicElement::ImageFrame(image_frame) => image_frame.bounding_box(transform), GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform), + GraphicElement::Surface(surface) => surface.bounding_box(transform), } } @@ -575,6 +612,7 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::VectorData(vector_data) => vector_data.add_click_targets(click_targets), GraphicElement::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets), GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets), + GraphicElement::Surface(surface) => surface.add_click_targets(click_targets), } } @@ -583,6 +621,7 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::VectorData(vector_data) => vector_data.to_usvg_node(), GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(), + GraphicElement::Surface(surface) => surface.to_usvg_node(), } } @@ -591,6 +630,7 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(), GraphicElement::ImageFrame(image_frame) => image_frame.contains_artboard(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(), + GraphicElement::Surface(surface) => surface.contains_artboard(), } } } diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index bd4fe31146..d59be71a7d 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -6,6 +6,7 @@ extern crate alloc; #[cfg_attr(feature = "log", macro_use)] #[cfg(feature = "log")] extern crate log; +pub use crate as graphene_core; pub mod consts; pub mod generic; @@ -176,3 +177,5 @@ pub use crate::application_io::{SurfaceFrame, SurfaceId}; pub type WasmSurfaceHandle = application_io::SurfaceHandle; #[cfg(feature = "wasm")] pub type WasmSurfaceHandleFrame = application_io::SurfaceHandleFrame; + +pub use dyn_any::{WasmNotSend, WasmNotSync}; diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 2e2d229efb..d71ea19151 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -1,46 +1,49 @@ -use crate::Node; +use crate::{Node, WasmNotSend}; use core::future::Future; +use core::ops::Deref; +use std::sync::Mutex; #[cfg(feature = "alloc")] use alloc::sync::Arc; -use core::cell::Cell; -use core::pin::Pin; +use dyn_any::DynFuture; /// Caches the output of a given Node and acts as a proxy #[derive(Default)] pub struct MemoNode { - cache: Cell>, + cache: Arc>>, node: CachedNode, } -impl<'i, 'o: 'i, T: 'i + Clone + 'o, CachedNode: 'i> Node<'i, ()> for MemoNode +impl<'i, 'o: 'i, T: 'i + Clone + 'o + WasmNotSend, CachedNode: 'i> Node<'i, ()> for MemoNode where CachedNode: for<'any_input> Node<'any_input, ()>, - for<'a> >::Output: core::future::Future + 'a, + for<'a> >::Output: core::future::Future + WasmNotSend, { // TODO: This should return a reference to the cached cached_value // but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD - type Output = Pin + 'i>>; - fn eval(&'i self, input: ()) -> Pin + 'i>> { - Box::pin(async move { - if let Some(cached_value) = self.cache.take() { - self.cache.set(Some(cached_value.clone())); - cached_value - } else { - let value = self.node.eval(input).await; - self.cache.set(Some(value.clone())); + type Output = DynFuture<'i, T>; + fn eval(&'i self, input: ()) -> Self::Output { + if let Some(cached_value) = self.cache.lock().as_ref().unwrap().deref() { + let data = cached_value.clone(); + Box::pin(async move { data }) + } else { + let fut = self.node.eval(input); + let cache = self.cache.clone(); + Box::pin(async move { + let value = fut.await; + *cache.lock().unwrap() = Some(value.clone()); value - } - }) + }) + } } fn reset(&self) { - self.cache.set(None); + self.cache.lock().unwrap().take(); } } impl MemoNode { - pub const fn new(node: CachedNode) -> MemoNode { - MemoNode { cache: Cell::new(None), node } + pub fn new(node: CachedNode) -> MemoNode { + MemoNode { cache: Default::default(), node } } } @@ -50,41 +53,43 @@ impl MemoNode { /// use with caution. #[derive(Default)] pub struct ImpureMemoNode { - cache: Cell>, + cache: Arc>>, node: CachedNode, _phantom: std::marker::PhantomData, } -impl<'i, 'o: 'i, I: 'i, T: 'i + Clone + 'o, CachedNode: 'i> Node<'i, I> for ImpureMemoNode +impl<'i, 'o: 'i, I: 'i, T: 'i + Clone + 'o + WasmNotSend, CachedNode: 'i> Node<'i, I> for ImpureMemoNode where CachedNode: for<'any_input> Node<'any_input, I>, - for<'a> >::Output: core::future::Future + 'a, + for<'a> >::Output: core::future::Future + WasmNotSend, { // TODO: This should return a reference to the cached cached_value // but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD - type Output = Pin + 'i>>; - fn eval(&'i self, input: I) -> Pin + 'i>> { - Box::pin(async move { - if let Some(cached_value) = self.cache.take() { - self.cache.set(Some(cached_value.clone())); - cached_value - } else { - let value = self.node.eval(input).await; - self.cache.set(Some(value.clone())); + type Output = DynFuture<'i, T>; + fn eval(&'i self, input: I) -> Self::Output { + if let Some(cached_value) = self.cache.lock().as_ref().unwrap().deref() { + let data = cached_value.clone(); + Box::pin(async move { data }) + } else { + let fut = self.node.eval(input); + let cache = self.cache.clone(); + Box::pin(async move { + let value = fut.await; + *cache.lock().unwrap() = Some(value.clone()); value - } - }) + }) + } } fn reset(&self) { - self.cache.set(None); + self.cache.lock().unwrap().take(); } } impl ImpureMemoNode { - pub const fn new(node: CachedNode) -> ImpureMemoNode { + pub fn new(node: CachedNode) -> ImpureMemoNode { ImpureMemoNode { - cache: Cell::new(None), + cache: Default::default(), node, _phantom: core::marker::PhantomData, } @@ -102,37 +107,38 @@ pub struct IORecord { /// Caches the output of the last graph evaluation for introspection #[derive(Default)] pub struct MonitorNode { - io: Cell>>>, + #[allow(clippy::type_complexity)] + io: Arc>>>>, node: N, } #[cfg(feature = "alloc")] -impl<'i, 'a: 'i, T, I, N> Node<'i, I> for MonitorNode +impl<'i, T, I, N> Node<'i, I> for MonitorNode where - I: Clone + 'static, - >::Output: Future, - T: Clone + 'static, - N: Node<'i, I>, + I: Clone + 'static + Send + Sync, + T: Clone + 'static + Send + Sync, + for<'a> N: Node<'a, I, Output: Future + WasmNotSend> + 'i, { - type Output = Pin + 'i>>; + type Output = DynFuture<'i, T>; fn eval(&'i self, input: I) -> Self::Output { + let io = self.io.clone(); + let output_fut = self.node.eval(input.clone()); Box::pin(async move { - let output = self.node.eval(input.clone()).await; - self.io.set(Some(Arc::new(IORecord { input, output: output.clone() }))); + let output = output_fut.await; + *io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() })); output }) } fn serialize(&self) -> Option> { - let io = self.io.take(); - self.io.set(io.clone()); + let io = self.io.lock().unwrap(); (io).as_ref().map(|output| output.clone() as Arc) } } #[cfg(feature = "alloc")] impl MonitorNode { - pub const fn new(node: N) -> MonitorNode { - MonitorNode { io: Cell::new(None), node } + pub fn new(node: N) -> MonitorNode { + MonitorNode { io: Arc::new(Mutex::new(None)), node } } } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 77c9e79dc8..8ecc3e95d2 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -379,7 +379,7 @@ pub struct IntoNode { #[node_macro::node_fn(IntoNode<_I, _O>)] async fn into<_I, _O>(input: _I) -> _O where - _I: Into<_O>, + _I: Into<_O> + Sync + Send, { input.into() } diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index 0ea99b27af..54ad4064ab 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -1,4 +1,5 @@ use core::hash::Hash; +use half::f16; use dyn_any::{DynAny, StaticType}; #[cfg(feature = "serde")] @@ -14,6 +15,78 @@ use super::{ discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}, Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB, }; +#[repr(C)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, Pod, Zeroable)] +pub struct RGBA16F { + red: f16, + green: f16, + blue: f16, + alpha: f16, +} + +impl From for RGBA16F { + #[inline(always)] + fn from(c: Color) -> Self { + Self { + red: f16::from_f32(c.r()), + green: f16::from_f32(c.g()), + blue: f16::from_f32(c.b()), + alpha: f16::from_f32(c.a()), + } + } +} + +impl Luminance for RGBA16F { + type LuminanceChannel = f32; + #[inline(always)] + fn luminance(&self) -> f32 { + // TODO: verify this is correct for sRGB + 0.2126 * self.red() + 0.7152 * self.green() + 0.0722 * self.blue() + } +} + +impl RGB for RGBA16F { + type ColorChannel = f32; + #[inline(always)] + fn red(&self) -> f32 { + self.red.to_f32() + } + #[inline(always)] + fn green(&self) -> f32 { + self.green.to_f32() + } + #[inline(always)] + fn blue(&self) -> f32 { + self.blue.to_f32() + } +} + +impl Rec709Primaries for RGBA16F {} + +impl Alpha for RGBA16F { + type AlphaChannel = f32; + #[inline(always)] + fn alpha(&self) -> f32 { + self.alpha.to_f32() / 255. + } + + const TRANSPARENT: Self = RGBA16F { + red: f16::from_f32_const(0.), + green: f16::from_f32_const(0.), + blue: f16::from_f32_const(0.), + alpha: f16::from_f32_const(0.), + }; + + fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self { + let alpha = alpha * 255.; + let mut result = *self; + result.alpha = f16::from_f32(alpha * self.alpha()); + result + } +} + +impl Pixel for RGBA16F {} #[repr(C)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -33,7 +106,7 @@ impl From for SRGBA8 { red: float_to_srgb_u8(c.r()), green: float_to_srgb_u8(c.g()), blue: float_to_srgb_u8(c.b()), - alpha: (c.a() * 255.0) as u8, + alpha: (c.a() * 255.) as u8, } } } @@ -45,7 +118,7 @@ impl From for Color { red: srgb_u8_to_float(color.red), green: srgb_u8_to_float(color.green), blue: srgb_u8_to_float(color.blue), - alpha: color.alpha as f32 / 255.0, + alpha: color.alpha as f32 / 255., } } } @@ -63,15 +136,15 @@ impl RGB for SRGBA8 { type ColorChannel = f32; #[inline(always)] fn red(&self) -> f32 { - self.red as f32 / 255.0 + self.red as f32 / 255. } #[inline(always)] fn green(&self) -> f32 { - self.green as f32 / 255.0 + self.green as f32 / 255. } #[inline(always)] fn blue(&self) -> f32 { - self.blue as f32 / 255.0 + self.blue as f32 / 255. } } @@ -82,13 +155,13 @@ impl Alpha for SRGBA8 { type AlphaChannel = f32; #[inline(always)] fn alpha(&self) -> f32 { - self.alpha as f32 / 255.0 + self.alpha as f32 / 255. } const TRANSPARENT: Self = SRGBA8 { red: 0, green: 0, blue: 0, alpha: 0 }; fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self { - let alpha = alpha * 255.0; + let alpha = alpha * 255.; let mut result = *self; result.alpha = (alpha * self.alpha()) as u8; result @@ -338,7 +411,7 @@ impl Color { #[inline(always)] pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color { let alpha = alpha as f32 / 255.; - let map_range = |int_color| int_color as f32 / 255.0; + let map_range = |int_color| int_color as f32 / 255.; Color { red: map_range(red), green: map_range(green), diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index ec56b19d8d..357bf4c50f 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,5 +1,3 @@ -use core::future::Future; - use dyn_any::StaticType; use glam::DAffine2; @@ -27,6 +25,12 @@ pub trait Transform { } } +impl Transform for &T { + fn transform(&self) -> DAffine2 { + (*self).transform() + } +} + pub trait TransformMut: Transform { fn transform_mut(&mut self) -> &mut DAffine2; fn translate(&mut self, offset: DVec2) { @@ -42,14 +46,6 @@ impl Transform for ImageFrame

{ self.local_pivot(pivot) } } -impl Transform for &ImageFrame

{ - fn transform(&self) -> DAffine2 { - self.transform - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - (*self).local_pivot(pivot) - } -} impl TransformMut for ImageFrame

{ fn transform_mut(&mut self) -> &mut DAffine2 { &mut self.transform @@ -60,11 +56,6 @@ impl Transform for GraphicGroup { self.transform } } -impl Transform for &GraphicGroup { - fn transform(&self) -> DAffine2 { - self.transform - } -} impl TransformMut for GraphicGroup { fn transform_mut(&mut self) -> &mut DAffine2 { &mut self.transform @@ -76,6 +67,7 @@ impl Transform for GraphicElement { GraphicElement::VectorData(vector_shape) => vector_shape.transform(), GraphicElement::ImageFrame(image_frame) => image_frame.transform(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(), + GraphicElement::Surface(surface) => surface.transform(), } } fn local_pivot(&self, pivot: DVec2) -> DVec2 { @@ -83,13 +75,7 @@ impl Transform for GraphicElement { GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot), GraphicElement::ImageFrame(image_frame) => image_frame.local_pivot(pivot), GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot), - } - } - fn decompose_scale(&self) -> DVec2 { - match self { - GraphicElement::VectorData(vector_shape) => vector_shape.decompose_scale(), - GraphicElement::ImageFrame(image_frame) => image_frame.decompose_scale(), - GraphicElement::GraphicGroup(graphic_group) => graphic_group.decompose_scale(), + GraphicElement::Surface(surface) => surface.local_pivot(pivot), } } } @@ -99,6 +85,7 @@ impl TransformMut for GraphicElement { GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(), GraphicElement::ImageFrame(image_frame) => image_frame.transform_mut(), GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(), + GraphicElement::Surface(surface) => surface.transform_mut(), } } } @@ -137,16 +124,6 @@ impl TransformMut for DAffine2 { } } -#[derive(Debug, Clone, Copy)] -pub struct TransformNode { - pub(crate) transform_target: TransformTarget, - pub(crate) translate: Translation, - pub(crate) rotate: Rotation, - pub(crate) scale: Scale, - pub(crate) shear: Shear, - pub(crate) _pivot: Pivot, -} - #[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RenderQuality { @@ -233,19 +210,26 @@ impl TransformMut for Footprint { } } +#[derive(Debug, Clone, Copy)] +pub struct TransformNode { + pub(crate) transform_target: TransformTarget, + pub(crate) translate: Translation, + pub(crate) rotate: Rotation, + pub(crate) scale: Scale, + pub(crate) shear: Shear, + pub(crate) _pivot: Pivot, +} + #[node_macro::node_fn(TransformNode)] -pub(crate) async fn transform_vector_data( +pub(crate) async fn transform_vector_data( mut footprint: Footprint, - transform_target: impl Node, + transform_target: impl Node, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, _pivot: DVec2, -) -> Fut::Output -where - Fut::Output: TransformMut, -{ +) -> T { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); if !footprint.ignore_modifications { *footprint.transform_mut() = footprint.transform() * modification; diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 0ab926fdcb..ff5d30bca1 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -337,7 +337,7 @@ impl HandleId { } #[cfg(test)] -fn assert_subpath_eq(generated: &Vec>, expected: &[bezier_rs::Subpath]) { +fn assert_subpath_eq(generated: &[bezier_rs::Subpath], expected: &[bezier_rs::Subpath]) { assert_eq!(generated.len(), expected.len()); for (generated, expected) in generated.iter().zip(expected) { assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len()); diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 1e5f204a99..5694f06294 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -443,7 +443,7 @@ fn modify_existing() { false, ), ]; - let mut vector_data = VectorData::from_subpaths(&subpaths, false); + let mut vector_data = VectorData::from_subpaths(subpaths, false); let mut modify_new = VectorModification::create_from_vector(&vector_data); let mut modify_original = VectorModification::default(); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index a7cd6d5fc0..9652aceb8e 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -4,7 +4,6 @@ use super::{PointId, SegmentId, StrokeId, VectorData}; use crate::renderer::GraphicElementRendered; use crate::transform::{Footprint, Transform, TransformMut}; use crate::{Color, GraphicGroup, Node}; -use core::future::Future; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use glam::{DAffine2, DVec2}; @@ -212,10 +211,10 @@ pub struct CopyToPoints, FI: Future>( +async fn copy_to_points( footprint: Footprint, - points: impl Node, - instance: impl Node, + points: impl Node, + instance: impl Node, random_scale_min: f64, random_scale_max: f64, random_scale_bias: f64, @@ -280,14 +279,14 @@ pub struct SamplePoints, FL: Future>>( +async fn sample_points( footprint: Footprint, - mut vector_data: impl Node, + mut vector_data: impl Node, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, - lengths_of_segments_of_subpaths: impl Node, + lengths_of_segments_of_subpaths: impl Node>, ) -> VectorData { let vector_data = self.vector_data.eval(footprint).await; let lengths_of_segments_of_subpaths = self.lengths_of_segments_of_subpaths.eval(footprint).await; @@ -422,13 +421,7 @@ pub struct MorphNode { } #[node_macro::node_fn(MorphNode)] -async fn morph, TargetFuture: Future>( - footprint: Footprint, - source: impl Node, - target: impl Node, - start_index: u32, - time: f64, -) -> VectorData { +async fn morph(footprint: Footprint, source: impl Node, target: impl Node, start_index: u32, time: f64) -> VectorData { let source = self.source.eval(footprint).await; let target = self.target.eval(footprint).await; let mut result = VectorData::empty(); @@ -516,7 +509,7 @@ pub struct AreaNode { } #[node_macro::node_fn(AreaNode)] -async fn area_node>(empty: (), vector_data: impl Node) -> f64 { +async fn area_node(empty: (), vector_data: impl Node) -> f64 { let vector_data = self.vector_data.eval(Footprint::default()).await; let mut area = 0.; @@ -534,7 +527,7 @@ pub struct CentroidNode { } #[node_macro::node_fn(CentroidNode)] -async fn centroid_node>(empty: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { +async fn centroid_node(empty: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { let vector_data = self.vector_data.eval(Footprint::default()).await; if centroid_type == CentroidType::Area { @@ -594,11 +587,12 @@ mod test { impl<'i, T: 'i, N: Node<'i, T> + Clone> Node<'i, T> for FutureWrapperNode where - N: Node<'i, T>, + N: Node<'i, T, Output: Send>, { - type Output = Pin + 'i>>; + type Output = Pin + 'i + Send>>; fn eval(&'i self, input: T) -> Self::Output { - Box::pin(async move { self.0.eval(input) }) + let result = self.0.eval(input); + Box::pin(async move { result }) } } diff --git a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml index ac5817006e..36d3b7ab23 100644 --- a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml +++ b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml @@ -10,8 +10,9 @@ profiling = [] [dependencies] # Local dependencies -graph-craft = { path = "../../graph-craft", features = ["serde"] } +graph-craft = { path = "../../graph-craft", features = ["serde", "wgpu"] } gpu-executor = { path = "../../gpu-executor" } +wgpu-executor = { path = "../../wgpu-executor" } # Workspace dependencies log = { workspace = true } diff --git a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs index 17524071d7..b4e6369722 100644 --- a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs +++ b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs @@ -1,5 +1,5 @@ -use gpu_executor::ShaderIO; use graph_craft::{proto::ProtoNetwork, Type}; +use wgpu_executor::ShaderIO; use std::io::Write; diff --git a/node-graph/gpu-executor/src/lib.rs b/node-graph/gpu-executor/src/lib.rs index e3e338c603..216c470c1c 100644 --- a/node-graph/gpu-executor/src/lib.rs +++ b/node-graph/gpu-executor/src/lib.rs @@ -1,17 +1,10 @@ use dyn_any::{StaticType, StaticTypeSized}; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; -use graphene_core::raster::{Image, ImageFrame, Pixel, SRGBA8}; +use graphene_core::raster::{color::RGBA16F, Image, Pixel, SRGBA8}; use graphene_core::*; -use anyhow::Result; use bytemuck::{Pod, Zeroable}; -use futures::Future; -use glam::{DAffine2, UVec3}; +use glam::UVec3; use std::borrow::Cow; -use std::pin::Pin; -use std::sync::Arc; - -type ReadBackFuture = Pin>>>>; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, dyn_any::DynAny)] pub enum ComputePassDimensions { @@ -37,32 +30,6 @@ pub trait Texture { fn view(&self) -> TextureView; } -pub trait GpuExecutor { - type ShaderHandle; - type BufferHandle: Send + Sync; - type TextureHandle: Send + Sync; - type TextureView: Send + Sync; - type Surface<'window>: Send + Sync; - type Window; - type CommandBuffer; - - fn load_shader(&self, shader: Shader) -> Result; - fn create_uniform_buffer(&self, data: T) -> Result>; - fn create_storage_buffer(&self, data: T, options: StorageBufferOptions) -> Result>; - fn create_texture_buffer(&self, data: T, options: TextureBufferOptions) -> Result>; - fn create_texture_view(&self, texture: ShaderInput) -> Result>; - fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result>; - fn create_compute_pass(&self, layout: &PipelineLayout, read_back: Option>>, instances: ComputePassDimensions) -> Result; - fn create_render_pass(&self, texture: Arc>, canvas: Arc>>) -> Result<()>; - fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>; - fn read_output_buffer(&self, buffer: Arc>) -> ReadBackFuture; - fn create_surface(&self, window: SurfaceHandle) -> Result>>; -} - -// pub trait SpirVCompiler { -// fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result; -// } - #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] /// GPU constants that can be used as inputs to a shader. pub enum GPUConstant { @@ -94,164 +61,6 @@ impl GPUConstant { } } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct DummyExecutor; - -impl GpuExecutor for DummyExecutor { - type ShaderHandle = (); - type BufferHandle = (); - type TextureHandle = (); - type TextureView = (); - type Surface<'window> = (); - type Window = (); - type CommandBuffer = (); - - fn load_shader(&self, _shader: Shader) -> Result { - todo!() - } - - fn create_uniform_buffer(&self, _data: T) -> Result> { - todo!() - } - - fn create_storage_buffer(&self, _data: T, _options: StorageBufferOptions) -> Result> { - todo!() - } - - fn create_texture_buffer(&self, _data: T, _options: TextureBufferOptions) -> Result> { - todo!() - } - - fn create_output_buffer(&self, _len: usize, _ty: Type, _cpu_readable: bool) -> Result> { - todo!() - } - - fn create_compute_pass(&self, _layout: &PipelineLayout, _read_back: Option>>, _instances: ComputePassDimensions) -> Result { - todo!() - } - - fn execute_compute_pipeline(&self, _encoder: Self::CommandBuffer) -> Result<()> { - todo!() - } - - fn create_render_pass(&self, _texture: Arc>, _canvas: Arc>>) -> Result<()> { - todo!() - } - - fn read_output_buffer(&self, _buffer: Arc>) -> ReadBackFuture { - todo!() - } - - fn create_texture_view(&self, _texture: ShaderInput) -> Result> { - todo!() - } - - fn create_surface(&self, _window: SurfaceHandle) -> Result>> { - todo!() - } -} - -type AbstractShaderInput = ShaderInput; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -/// All the possible inputs to a shader. -pub enum ShaderInput { - UniformBuffer(E::BufferHandle, Type), - StorageBuffer(E::BufferHandle, Type), - TextureBuffer(E::TextureHandle, Type), - StorageTextureBuffer(E::TextureHandle, Type), - TextureView(E::TextureView, Type), - /// A struct representing a work group memory buffer. This cannot be accessed by the CPU. - WorkGroupMemory(usize, Type), - Constant(GPUConstant), - OutputBuffer(E::BufferHandle, Type), - ReadBackBuffer(E::BufferHandle, Type), -} - -unsafe impl StaticType for ShaderInput -where - E: GpuExecutor, -{ - type Static = Self; -} - -pub enum BindingType<'a, E: GpuExecutor> { - UniformBuffer(&'a E::BufferHandle), - StorageBuffer(&'a E::BufferHandle), - TextureView(&'a E::TextureView), -} - -/// Extract the buffer handle from a shader input. -impl ShaderInput { - pub fn binding(&self) -> Option> { - match self { - ShaderInput::UniformBuffer(buffer, _) => Some(BindingType::UniformBuffer(buffer)), - ShaderInput::StorageBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), - ShaderInput::WorkGroupMemory(_, _) => None, - ShaderInput::Constant(_) => None, - ShaderInput::TextureBuffer(_, _) => None, - ShaderInput::StorageTextureBuffer(_, _) => None, - ShaderInput::TextureView(tex, _) => Some(BindingType::TextureView(tex)), - ShaderInput::OutputBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), - ShaderInput::ReadBackBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), - } - } - pub fn buffer(&self) -> Option<&E::BufferHandle> { - match self { - ShaderInput::UniformBuffer(buffer, _) => Some(buffer), - ShaderInput::StorageBuffer(buffer, _) => Some(buffer), - ShaderInput::WorkGroupMemory(_, _) => None, - ShaderInput::Constant(_) => None, - ShaderInput::TextureBuffer(_, _) => None, - ShaderInput::StorageTextureBuffer(_, _) => None, - ShaderInput::TextureView(_tex, _) => None, - ShaderInput::OutputBuffer(buffer, _) => Some(buffer), - ShaderInput::ReadBackBuffer(buffer, _) => Some(buffer), - } - } - pub fn texture(&self) -> Option<&E::TextureHandle> { - match self { - ShaderInput::UniformBuffer(_, _) => None, - ShaderInput::StorageBuffer(_, _) => None, - ShaderInput::WorkGroupMemory(_, _) => None, - ShaderInput::Constant(_) => None, - ShaderInput::TextureBuffer(tex, _) => Some(tex), - ShaderInput::StorageTextureBuffer(tex, _) => Some(tex), - ShaderInput::TextureView(_, _) => None, - ShaderInput::OutputBuffer(_, _) => None, - ShaderInput::ReadBackBuffer(_, _) => None, - } - } - pub fn ty(&self) -> Type { - match self { - ShaderInput::UniformBuffer(_, ty) => ty.clone(), - ShaderInput::StorageBuffer(_, ty) => ty.clone(), - ShaderInput::WorkGroupMemory(_, ty) => ty.clone(), - ShaderInput::Constant(c) => c.ty(), - ShaderInput::TextureBuffer(_, ty) => ty.clone(), - ShaderInput::StorageTextureBuffer(_, ty) => ty.clone(), - ShaderInput::TextureView(_, ty) => ty.clone(), - ShaderInput::OutputBuffer(_, ty) => ty.clone(), - ShaderInput::ReadBackBuffer(_, ty) => ty.clone(), - } - } - - pub fn is_output(&self) -> bool { - matches!(self, ShaderInput::OutputBuffer(_, _)) - } -} - -pub struct Shader<'a> { - pub source: Cow<'a, [u32]>, - pub name: &'a str, - pub io: ShaderIO, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct ShaderIO { - pub inputs: Vec, - pub output: AbstractShaderInput, -} pub struct StorageBufferOptions { pub cpu_writable: bool, @@ -304,9 +113,16 @@ impl TextureFormat for SRGBA8 { TextureBufferType::Rgba8Srgb } } +impl TextureFormat for RGBA16F { + fn format() -> TextureBufferType { + TextureBufferType::Rgba16Float + } +} +// TODO use wgpu type pub enum TextureBufferType { Rgba32Float, + Rgba16Float, Rgba8Srgb, } @@ -334,215 +150,3 @@ where (self.width, self.height) } } - -/// Collection of all arguments that are passed to the shader. -pub struct Bindgroup { - pub buffers: Vec>>, -} - -unsafe impl StaticType for Bindgroup -where - E::Static: GpuExecutor, -{ - type Static = Bindgroup; -} - -/// A struct representing a compute pipeline. -pub struct PipelineLayout { - pub shader: Arc, - pub entry_point: String, - pub bind_group: Arc>, - pub output_buffer: Arc>, -} - -impl Clone for PipelineLayout { - fn clone(&self) -> Self { - Self { - shader: self.shader.clone(), - entry_point: self.entry_point.clone(), - bind_group: self.bind_group.clone(), - output_buffer: self.output_buffer.clone(), - } - } -} - -unsafe impl StaticType for PipelineLayout -where - E::Static: GpuExecutor, -{ - type Static = PipelineLayout; -} - -/// Extracts arguments from the function arguments and wraps them in a node. -pub struct ShaderInputNode { - data: T, -} - -impl<'i, T: 'i> Node<'i, ()> for ShaderInputNode { - type Output = &'i T; - - fn eval(&'i self, _: ()) -> Self::Output { - &self.data - } -} - -impl ShaderInputNode { - pub fn new(data: T) -> Self { - Self { data } - } -} - -pub struct UniformNode { - executor: Executor, -} - -#[node_macro::node_fn(UniformNode)] -async fn uniform_node<'a: 'input, T: ToUniformBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput { - executor.create_uniform_buffer(data).unwrap() -} - -pub struct StorageNode { - executor: Executor, -} - -#[node_macro::node_fn(StorageNode)] -async fn storage_node<'a: 'input, T: ToStorageBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput { - executor - .create_storage_buffer( - data, - StorageBufferOptions { - cpu_writable: false, - gpu_writable: true, - cpu_readable: false, - storage: true, - }, - ) - .unwrap() -} - -pub struct PushNode { - value: Value, -} - -#[node_macro::node_fn(PushNode)] -async fn push_node(mut vec: Vec, value: T) { - vec.push(value); -} - -pub struct CreateOutputBufferNode { - executor: Executor, - ty: Ty, -} - -#[node_macro::node_fn(CreateOutputBufferNode)] -async fn create_output_buffer_node<'a: 'input, E: GpuExecutor + 'a>(size: usize, executor: &'a E, ty: Type) -> Arc> { - Arc::new(executor.create_output_buffer(size, ty, true).unwrap()) -} - -pub struct CreateComputePassNode { - executor: Executor, - output: Output, - instances: Instances, -} - -#[node_macro::node_fn(CreateComputePassNode)] -async fn create_compute_pass_node<'a: 'input, E: 'a + GpuExecutor>(layout: PipelineLayout, executor: &'a E, output: ShaderInput, instances: ComputePassDimensions) -> E::CommandBuffer { - executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() -} - -pub struct CreatePipelineLayoutNode<_E, EntryPoint, Bindgroup, OutputBuffer> { - entry_point: EntryPoint, - bind_group: Bindgroup, - output_buffer: OutputBuffer, - _e: std::marker::PhantomData<_E>, -} - -#[node_macro::node_fn(CreatePipelineLayoutNode<_E>)] -async fn create_pipeline_layout_node<_E: GpuExecutor>(shader: _E::ShaderHandle, entry_point: String, bind_group: Bindgroup<_E>, output_buffer: Arc>) -> PipelineLayout<_E> { - PipelineLayout { - shader: shader.into(), - entry_point, - bind_group: bind_group.into(), - output_buffer, - } -} - -pub struct ExecuteComputePipelineNode { - executor: Executor, -} - -#[node_macro::node_fn(ExecuteComputePipelineNode)] -async fn execute_compute_pipeline_node<'a: 'input, E: 'a + GpuExecutor>(encoder: E::CommandBuffer, executor: &'a E) { - executor.execute_compute_pipeline(encoder).unwrap(); -} - -pub struct ReadOutputBufferNode { - executor: Executor, - _compute_pass: ComputePass, -} -#[node_macro::node_fn(ReadOutputBufferNode)] -async fn read_output_buffer_node<'a: 'input, E: 'a + GpuExecutor>(buffer: Arc>, executor: &'a E, _compute_pass: ()) -> Vec { - executor.read_output_buffer(buffer).await.unwrap() -} - -pub struct CreateGpuSurfaceNode {} - -#[node_macro::node_fn(CreateGpuSurfaceNode)] -async fn create_gpu_surface<'a: 'input, E: 'a + GpuExecutor, Io: ApplicationIo + 'input>(editor_api: &'a EditorApi) -> Arc>> { - let canvas = editor_api.application_io.as_ref().unwrap().create_surface(); - let executor = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); - Arc::new(executor.create_surface(canvas).unwrap()) -} - -pub struct RenderTextureNode { - surface: Surface, - executor: EditorApi, -} - -pub struct ShaderInputFrame { - shader_input: Arc>, - transform: DAffine2, -} - -impl Clone for ShaderInputFrame { - fn clone(&self) -> Self { - Self { - shader_input: self.shader_input.clone(), - transform: self.transform, - } - } -} - -unsafe impl StaticType for ShaderInputFrame -where - E::Static: GpuExecutor, -{ - type Static = ShaderInputFrame; -} - -#[node_macro::node_fn(RenderTextureNode)] -async fn render_texture_node<'a: 'input, E: 'a + GpuExecutor>(image: ShaderInputFrame, surface: Arc>>, executor: &'a E) -> SurfaceFrame { - let surface_id = surface.surface_id; - log::trace!("rendering to surface {surface_id:?}"); - - executor.create_render_pass(image.shader_input, surface).unwrap(); - - SurfaceFrame { - surface_id, - transform: image.transform, - } -} - -pub struct UploadTextureNode { - executor: E, -} - -#[node_macro::node_fn(UploadTextureNode)] -async fn upload_texture<'a: 'input, E: 'a + GpuExecutor>(input: ImageFrame, executor: &'a E) -> ShaderInputFrame { - let shader_input = executor.create_texture_buffer(input.image, TextureBufferOptions::Texture).unwrap(); - - ShaderInputFrame { - shader_input: Arc::new(shader_input), - transform: input.transform, - } -} diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index ca89e3c843..ac4c7e9e7b 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" default = ["dealloc_nodes"] serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"] dealloc_nodes = [] -wgpu = ["wgpu-executor"] +wgpu = [] [dependencies] # Local dependencies @@ -22,6 +22,7 @@ dyn-any = { path = "../../libraries/dyn-any", features = [ graphene-core = { workspace = true, features = ["std"] } num-traits = { workspace = true } log = { workspace = true } +futures = { workspace = true } glam = { workspace = true } base64 = { workspace = true } bezier-rs = { workspace = true } @@ -30,9 +31,9 @@ bytemuck = { workspace = true } rustc-hash = { workspace = true } url = { workspace = true } reqwest = { workspace = true } +wgpu-executor = { workspace = true } # Optional workspace dependencies -wgpu-executor = { workspace = true, optional = true } serde = { workspace = true, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 5cf92cf93a..dfd7fe6d5c 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -1,5 +1,4 @@ use super::DocumentNode; -use crate::graphene_compiler::Any; pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use crate::proto::{Any as DAny, FutureAny}; use crate::wasm_application_io::WasmEditorApi; @@ -47,7 +46,7 @@ macro_rules! tagged_value { } impl<'a> TaggedValue { /// Converts to a Box - this isn't very neat but I'm not sure of a better approach - pub fn to_any(self) -> Any<'a> { + pub fn to_any(self) -> DAny<'a> { match self { Self::None => Box::new(()), $( Self::$identifier(x) => Box::new(x), )* @@ -227,9 +226,9 @@ impl UpcastNode { } } #[derive(Default, Debug, Clone, Copy)] -pub struct UpcastAsRefNode, U>(pub T, PhantomData); +pub struct UpcastAsRefNode + Sync + Send, U: Sync + Send>(pub T, PhantomData); -impl<'i, T: 'i + AsRef, U: 'i + StaticType> Node<'i, DAny<'i>> for UpcastAsRefNode { +impl<'i, T: 'i + AsRef + Sync + Send, U: 'i + StaticType + Sync + Send> Node<'i, DAny<'i>> for UpcastAsRefNode { type Output = FutureAny<'i>; #[inline(always)] fn eval(&'i self, _: DAny<'i>) -> Self::Output { @@ -237,7 +236,7 @@ impl<'i, T: 'i + AsRef, U: 'i + StaticType> Node<'i, DAny<'i>> for UpcastAsRe } } -impl, U> UpcastAsRefNode { +impl + Sync + Send, U: Sync + Send> UpcastAsRefNode { pub const fn new(value: T) -> UpcastAsRefNode { UpcastAsRefNode(value, PhantomData) } diff --git a/node-graph/graph-craft/src/graphene_compiler.rs b/node-graph/graph-craft/src/graphene_compiler.rs index ee99180b6e..4a929b0858 100644 --- a/node-graph/graph-craft/src/graphene_compiler.rs +++ b/node-graph/graph-craft/src/graphene_compiler.rs @@ -1,7 +1,5 @@ use std::error::Error; -use dyn_any::DynAny; - use crate::document::NodeNetwork; use crate::proto::{LocalFuture, ProtoNetwork}; @@ -36,7 +34,6 @@ impl Compiler { Ok(proto_network) } } -pub type Any<'a> = Box + 'a>; pub trait Executor { fn execute(&self, input: I) -> LocalFuture>>; diff --git a/node-graph/graph-craft/src/imaginate_input.rs b/node-graph/graph-craft/src/imaginate_input.rs index f77d47585a..f4bf990586 100644 --- a/node-graph/graph-craft/src/imaginate_input.rs +++ b/node-graph/graph-craft/src/imaginate_input.rs @@ -29,7 +29,7 @@ impl core::hash::Hash for ImaginateCache { } } -pub trait ImaginateTerminationHandle: Debug + Send + Sync + 'static { +pub trait ImaginateTerminationHandle: Debug + Send + 'static { fn terminate(&self); } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index d8cce4cc82..fa0cdcd069 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -12,25 +12,34 @@ use std::hash::Hash; use std::ops::Deref; use std::pin::Pin; +#[cfg(not(target_arch = "wasm32"))] +pub type DynFuture<'n, T> = Pin + 'n + Send>>; +#[cfg(target_arch = "wasm32")] pub type DynFuture<'n, T> = Pin + 'n>>; pub type LocalFuture<'n, T> = Pin + 'n>>; +#[cfg(not(target_arch = "wasm32"))] +pub type Any<'n> = Box + 'n + Send>; +#[cfg(target_arch = "wasm32")] pub type Any<'n> = Box + 'n>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; // TODO: is this safe? This is assumed to be send+sync. +#[cfg(not(target_arch = "wasm32"))] +pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync; +#[cfg(target_arch = "wasm32")] pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>; pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>; pub type TypeErasedBox<'n> = Box>; pub type TypeErasedPinned<'n> = Pin>>; -pub type SharedNodeContainer = std::rc::Rc; +pub type SharedNodeContainer = std::sync::Arc; pub type NodeConstructor = fn(Vec) -> DynFuture<'static, TypeErasedBox<'static>>; #[derive(Clone)] pub struct NodeContainer { #[cfg(feature = "dealloc_nodes")] - pub node: *mut TypeErasedNode<'static>, + pub node: *const TypeErasedNode<'static>, #[cfg(not(feature = "dealloc_nodes"))] pub node: TypeErasedRef<'static>, } @@ -40,7 +49,7 @@ impl Deref for NodeContainer { #[cfg(feature = "dealloc_nodes")] fn deref(&self) -> &Self::Target { - unsafe { &*(self.node as *const TypeErasedNode) } + unsafe { &*(self.node) } #[cfg(not(feature = "dealloc_nodes"))] self.node } @@ -50,6 +59,14 @@ impl Deref for NodeContainer { } } +/// #Safety +/// Marks NodeContainer as Sync. This dissallows the use of threadlocal stroage for nodes as this would invalidate references to them. +// TODO: implement this on a higher level wrapper to avoid missuse +#[cfg(feature = "dealloc_nodes")] +unsafe impl Send for NodeContainer {} +#[cfg(feature = "dealloc_nodes")] +unsafe impl Sync for NodeContainer {} + #[cfg(feature = "dealloc_nodes")] impl Drop for NodeContainer { fn drop(&mut self) { @@ -71,7 +88,7 @@ impl NodeContainer { #[cfg(feature = "dealloc_nodes")] unsafe fn dealloc_unchecked(&mut self) { - std::mem::drop(Box::from_raw(self.node)); + std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode)); } } diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index e15ea3f5aa..faea8e5f95 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -1,20 +1,17 @@ use dyn_any::StaticType; -#[cfg(target_arch = "wasm32")] use graphene_core::application_io::SurfaceHandleFrame; use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; #[cfg(feature = "wgpu")] use wgpu_executor::WgpuExecutor; -use core::future::Future; #[cfg(target_arch = "wasm32")] use js_sys::{Object, Reflect}; use std::collections::HashMap; -use std::pin::Pin; #[cfg(target_arch = "wasm32")] use std::sync::atomic::AtomicU64; use std::sync::Arc; -#[cfg(not(target_arch = "wasm32"))] -use std::sync::Mutex; +// #[cfg(not(target_arch = "wasm32"))] +// use std::sync::Mutex; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; #[cfg(target_arch = "wasm32")] @@ -32,8 +29,8 @@ pub struct WasmApplicationIo { ids: AtomicU64, #[cfg(feature = "wgpu")] pub(crate) gpu_executor: Option, - #[cfg(not(target_arch = "wasm32"))] - windows: Mutex>>, + // #[cfg(not(target_arch = "wasm32"))] + // windows: Mutex>>, pub resources: HashMap>, } @@ -61,8 +58,8 @@ impl WasmApplicationIo { ids: AtomicU64::new(0), #[cfg(feature = "wgpu")] gpu_executor: executor, - #[cfg(not(target_arch = "wasm32"))] - windows: Vec::new().into(), + // #[cfg(not(target_arch = "wasm32"))] + // windows: Vec::new().into(), resources: HashMap::new(), }; io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec())); @@ -92,7 +89,7 @@ impl ApplicationIo for WasmApplicationIo { #[cfg(target_arch = "wasm32")] type Surface = HtmlCanvasElement; #[cfg(not(target_arch = "wasm32"))] - type Surface = Arc; + type Surface = winit::window::Window; #[cfg(feature = "wgpu")] type Executor = WgpuExecutor; #[cfg(not(feature = "wgpu"))] @@ -147,8 +144,7 @@ impl ApplicationIo for WasmApplicationIo { .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)) .build(&event_loop) .unwrap(); - let window = Arc::new(window); - self.windows.lock().as_mut().unwrap().push(window.clone()); + // self.windows.lock().as_mut().unwrap().push(window.clone()); SurfaceHandle { surface_id: SurfaceId(window.id().into()), surface: window, @@ -199,7 +195,7 @@ impl ApplicationIo for WasmApplicationIo { let mut data = Vec::new(); reader.read_to_end(&mut data).await.map_err(|_| ApplicationError::NotFound)?; Ok(Arc::from(data)) - }) as Pin, _>>>>) + }) as ResourceFuture) } "http" | "https" => { let url = url.to_string(); @@ -208,21 +204,19 @@ impl ApplicationIo for WasmApplicationIo { let response = client.get(url).send().await.map_err(|_| ApplicationError::NotFound)?; let data = response.bytes().await.map_err(|_| ApplicationError::NotFound)?; Ok(Arc::from(data.to_vec())) - }) as Pin, _>>>>) + }) as ResourceFuture) } "graphite" => { let path = url.path(); let path = path.to_owned(); log::trace!("Loading local resource: {path}"); let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone(); - Ok(Box::pin(async move { Ok(data.clone()) }) as Pin, _>>>>) + Ok(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture) } _ => Err(ApplicationError::NotFound), } } } -#[cfg(target_arch = "wasm32")] -pub type WasmSurfaceHandle = SurfaceHandle; -#[cfg(target_arch = "wasm32")] -pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; +pub type WasmSurfaceHandle = SurfaceHandle; +pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 4506975a9f..5272fb19a8 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -40,7 +40,7 @@ futures = { workspace = true } fern = { workspace = true } chrono = { workspace = true } wgpu = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } image = { workspace = true, default-features = false, features = [ "bmp", "png", diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 9e75f05649..1d6692b64e 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -1,9 +1,11 @@ -use graph_craft::document::*; -use graph_craft::graphene_compiler::Executor; +use graph_craft::document::value::TaggedValue; +use graph_craft::graphene_compiler::{Compiler, Executor}; use graph_craft::imaginate_input::ImaginatePreferences; use graph_craft::{concrete, ProtoNodeIdentifier}; +use graph_craft::{document::*, generic}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateSender}; use graphene_core::text::FontCache; +use graphene_std::transform::Footprint; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::DynamicExecutor; @@ -29,7 +31,6 @@ async fn main() -> Result<(), Box> { let document_string = std::fs::read_to_string(&document_path).expect("Failed to read document"); - let executor = create_executor(document_string)?; println!("creating gpu context",); let mut application_io = block_on(WasmApplicationIo::new()); if let Some(image_path) = image_path { @@ -42,12 +43,13 @@ async fn main() -> Result<(), Box> { device.poll(wgpu::Maintain::Poll); }); - let _editor_api = WasmEditorApi { + let editor_api = Arc::new(WasmEditorApi { font_cache: FontCache::default(), application_io: Some(application_io.into()), node_graph_message_sender: Box::new(UpdateLogger {}), imaginate_preferences: Box::new(ImaginatePreferences::default()), - }; + }); + let executor = create_executor(document_string, editor_api)?; let render_config = graphene_core::application_io::RenderConfig::default(); loop { @@ -76,42 +78,55 @@ fn init_logging() { .unwrap(); } -fn create_executor(_document_string: String) -> Result> { - // let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document"); - // let document = serde_json::from_value::(document["document_legacy"].clone()).expect("Failed to parse document"); - // let Some(LegacyLayerType::Layer(ref network)) = document.root.iter().find(|layer| matches!(layer, LegacyLayerType::Layer(_))) else { - panic!("Failed to extract node graph from document") - // }; - // let wrapped_network = wrap_network_in_scope(network.clone()); - // let compiler = Compiler {}; - // let protograph = compiler.compile_single(wrapped_network)?; - // let executor = block_on(DynamicExecutor::new(protograph))?; - // Ok(executor) +fn create_executor(document_string: String, editor_api: Arc) -> Result> { + let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document"); + let network = serde_json::from_value::(document["network"].clone()).expect("Failed to parse document"); + let wrapped_network = wrap_network_in_scope(network.clone(), editor_api); + let compiler = Compiler {}; + let protograph = compiler.compile_single(wrapped_network)?; + let executor = block_on(DynamicExecutor::new(protograph)).unwrap(); + Ok(executor) } -fn _begin_scope() -> DocumentNode { - DocumentNode { - name: "Begin Scope".to_string(), - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], +pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { + network.generate_node_paths(&[]); + + let inner_network = DocumentNode { + name: "Scope".to_string(), + implementation: DocumentNodeImplementation::Network(network), + inputs: vec![NodeInput::node(NodeId(0), 1)], + metadata: DocumentNodeMetadata::position((-10, 0)), + ..Default::default() + }; + + let render_node = graph_craft::document::DocumentNode { + name: "Output".into(), + inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 1)], + implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::node(NodeId(2), 0)], nodes: [ DocumentNode { - name: "SetNode".to_string(), - manual_composition: Some(concrete!(WasmEditorApi)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::SomeNode")), + name: "Create Canvas".to_string(), + inputs: vec![NodeInput::scope("editor-api")], + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + skip_deduplication: true, ..Default::default() }, DocumentNode { - name: "LetNode".to_string(), + name: "Cache".to_string(), + manual_composition: Some(concrete!(())), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::LetNode<_>")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, DocumentNode { - name: "RefNode".to_string(), - manual_composition: Some(concrete!(WasmEditorApi)), - inputs: vec![NodeInput::lambda(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::RefNode<_, _>")), + name: "RenderNode".to_string(), + inputs: vec![ + NodeInput::network(concrete!(WasmEditorApi), 1), + NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0), + NodeInput::node(NodeId(1), 0), + ], + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")), ..Default::default() }, ] @@ -119,10 +134,28 @@ fn _begin_scope() -> DocumentNode { .enumerate() .map(|(id, node)| (NodeId(id as u64), node)) .collect(), - ..Default::default() }), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0)], + metadata: DocumentNodeMetadata::position((-3, 0)), + ..Default::default() + }; + + // wrap the inner network in a scope + let nodes = vec![ + inner_network, + render_node, + DocumentNode { + name: "Editor Api".into(), + implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), + inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], + ..Default::default() + }, + ]; + + NodeNetwork { + exports: vec![NodeInput::node(NodeId(3), 0)], + nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(), + scope_injections: [("editor-api".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))].into_iter().collect(), ..Default::default() } } diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 7dcacf556c..148f9dc439 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -14,8 +14,7 @@ gpu = [ "compilation-client", "gpu-executor", ] -vulkan = ["gpu", "vulkan-executor"] -wgpu = ["gpu", "wgpu-executor", "dep:wgpu", "graph-craft/wgpu"] +wgpu = ["gpu", "dep:wgpu", "graph-craft/wgpu"] quantization = ["autoquant"] wasm = ["wasm-bindgen", "web-sys", "js-sys"] imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"] @@ -28,6 +27,7 @@ wayland = [] # Local dependencies dyn-any = { path = "../../libraries/dyn-any", features = ["derive"] } graph-craft = { path = "../graph-craft", features = ["serde"] } +wgpu-executor = { path = "../wgpu-executor" } graphene-core = { path = "../gcore", default-features = false, features = [ "std", "serde", @@ -60,8 +60,6 @@ image = { workspace = true, default-features = false, features = [ ] } # Optional local dependencies -vulkan-executor = { path = "../vulkan-executor", optional = true } -wgpu-executor = { path = "../wgpu-executor", optional = true } gpu-executor = { path = "../gpu-executor", optional = true } gpu-compiler-bin-wrapper = { path = "../gpu-compiler/gpu-compiler-bin-wrapper", optional = true } compilation-client = { path = "../compilation-client", optional = true } diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 58c873595d..5ec0e32343 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -1,6 +1,7 @@ pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graphene_core::NodeIO; +use graphene_core::WasmNotSend; pub use graphene_core::{generic, ops, Node}; use dyn_any::StaticType; @@ -13,7 +14,7 @@ pub struct DynAnyNode { _o: PhantomData, } -impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> +impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> where N: Node<'input, _I, Output = DynFuture<'input, _O>>, { @@ -21,9 +22,9 @@ where #[inline] fn eval(&'input self, input: Any<'input>) -> Self::Output { let node_name = core::any::type_name::(); - let output = |input| async move { - let result = self.node.eval(input).await; - Box::new(result) as Any<'input> + let output = |input| { + let result = self.node.eval(input); + async move { Box::new(result.await) as Any<'input> } }; match dyn_any::downcast(input) { Ok(input) => Box::pin(output(*input)), @@ -63,7 +64,7 @@ pub struct DynAnyRefNode { node: Node, _i: PhantomData<(I, O)>, } -impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyRefNode<_I, _O, N> +impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend + Sync, N: 'input> Node<'input, Any<'input>> for DynAnyRefNode<_I, _O, N> where N: for<'any_input> Node<'any_input, _I, Output = &'any_input _O>, { @@ -93,7 +94,7 @@ pub struct DynAnyInRefNode { node: Node, _i: PhantomData<(I, O)>, } -impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N> +impl<'input, _I: 'input + StaticType, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyInRefNode<_I, _O, N> where N: for<'any_input> Node<'any_input, &'any_input _I, Output = DynFuture<'any_input, _O>>, { @@ -117,13 +118,14 @@ pub struct FutureWrapperNode { node: Node, } -impl<'i, T: 'i, N: Node<'i, T>> Node<'i, T> for FutureWrapperNode +impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode where - N: Node<'i, T>, + N: Node<'i, T, Output: WasmNotSend> + WasmNotSend, { type Output = DynFuture<'i, N::Output>; fn eval(&'i self, input: T) -> Self::Output { - Box::pin(async move { self.node.eval(input) }) + let result = self.node.eval(input); + Box::pin(async move { result }) } fn reset(&self) { self.node.reset(); @@ -146,7 +148,7 @@ pub trait IntoTypeErasedNode<'n> { impl<'n, N: 'n> IntoTypeErasedNode<'n> for N where - N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n, + N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + Sync + WasmNotSend, { fn into_type_erased(self) -> TypeErasedBox<'n> { Box::new(self) @@ -182,7 +184,7 @@ pub struct DowncastBothNode { _i: PhantomData, _o: PhantomData, } -impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode { +impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode { type Output = DynFuture<'input, O>; #[inline] fn eval(&'input self, input: I) -> Self::Output { @@ -206,32 +208,6 @@ impl DowncastBothNode { } } } -/// Boxes the input and downcasts the output. -/// Wraps around a node taking Box and returning Box -#[derive(Clone)] -pub struct DowncastBothRefNode { - node: SharedNodeContainer, - _i: PhantomData<(I, O)>, -} -impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode { - type Output = DynFuture<'input, &'input O>; - #[inline] - fn eval(&'input self, input: I) -> Self::Output { - { - let node_name = self.node.node_name(); - let input = Box::new(input); - Box::pin(async move { - let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input).await).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e} in {node_name}")); - *out - }) - } - } -} -impl DowncastBothRefNode { - pub const fn new(node: SharedNodeContainer) -> Self { - Self { node, _i: core::marker::PhantomData } - } -} pub struct ComposeTypeErased { first: SharedNodeContainer, @@ -261,27 +237,30 @@ pub fn downcast_node(n: SharedNodeContainer) -> Do DowncastBothNode::new(n) } -pub struct PanicNode(PhantomData, PhantomData); +pub struct PanicNode(PhantomData, PhantomData); -impl<'i, I: 'i, O: 'i> Node<'i, I> for PanicNode { +impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode { type Output = O; fn eval(&'i self, _: I) -> Self::Output { unimplemented!("This node should never be evaluated") } } -impl PanicNode { +impl PanicNode { pub const fn new() -> Self { Self(PhantomData, PhantomData) } } -impl Default for PanicNode { +impl Default for PanicNode { fn default() -> Self { Self::new() } } +// TODO: Evaluate safety +unsafe impl Sync for PanicNode {} + #[cfg(test)] mod test { use super::*; diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index aff27c59f2..61cfb98e77 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -6,10 +6,10 @@ use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample}; use graphene_core::raster::{BlendMode, BlendNode}; use graphene_core::transform::{Transform, TransformMut}; -use graphene_core::value::{ClonedNode, CopiedNode, OnceCellNode, ValueNode}; +use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorData; -use graphene_core::Node; +use graphene_core::{Node, WasmNotSend}; use node_macro::node_fn; use glam::{DAffine2, DVec2}; @@ -35,10 +35,11 @@ pub struct ChainApplyNode { } #[node_fn(ChainApplyNode)] -async fn chain_apply(iter: I, mut value: T) -> T +async fn chain_apply(iter: I, value: T) -> T where I::Item: for<'a> Node<'a, T, Output = T>, { + let mut value = value; for lambda in iter { value = lambda.eval(value); } @@ -304,7 +305,7 @@ async fn brush(image: ImageFrame, bounds: ImageFrame, strokes: Vec background_bounds = bounds.transform; } - let mut actual_image = ExtendImageToBoundsNode::new(OnceCellNode::new(background_bounds)).eval(brush_plan.background); + let mut actual_image = ExtendImageToBoundsNode::new(ClonedNode::new(background_bounds)).eval(brush_plan.background); let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1); for (idx, stroke) in brush_plan.strokes.into_iter().enumerate() { // Create brush texture. diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index 16476e5020..e30071b874 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -1,7 +1,5 @@ use dyn_any::StaticTypeSized; -use glam::{DAffine2, DVec2, Mat2, Vec2}; -use gpu_executor::{Bindgroup, ComputePassDimensions, PipelineLayout, StorageBufferOptions}; -use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput}; +use gpu_executor::{ComputePassDimensions, StorageBufferOptions}; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graph_craft::proto::*; @@ -9,14 +7,16 @@ use graphene_core::application_io::ApplicationIo; use graphene_core::quantization::QuantizationChannels; use graphene_core::raster::*; use graphene_core::*; -use wgpu_executor::WgpuExecutor; +use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput}; + +use glam::{DAffine2, DVec2, Mat2, Vec2}; #[cfg(feature = "quantization")] use graphene_core::quantization::PackedPixel; -use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; +use std::sync::Mutex; use crate::wasm_application_io::WasmApplicationIo; @@ -27,7 +27,8 @@ pub struct GpuCompiler { // TODO: Move to graph-craft #[node_macro::node_fn(GpuCompiler)] -async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> Result { +async fn compile_gpu(node: &'input DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result { + let mut typing_context = typing_context; let compiler = graph_craft::graphene_compiler::Compiler {}; let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() }; let proto_networks: Vec<_> = compiler.compile(network.clone())?.collect(); @@ -50,15 +51,15 @@ async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingConte pub struct MapGpuNode { node: Node, editor_api: EditorApi, - cache: RefCell>>, + cache: Mutex>, } -struct ComputePass { - pipeline_layout: PipelineLayout, - readback_buffer: Option>>, +struct ComputePass { + pipeline_layout: PipelineLayout, + readback_buffer: Option>, } -impl Clone for ComputePass { +impl Clone for ComputePass { fn clone(&self) -> Self { Self { pipeline_layout: self.pipeline_layout.clone(), @@ -96,15 +97,15 @@ async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, edito }; // TODO: The cache should be based on the network topology not the node name - let compute_pass_descriptor = if self.cache.borrow().contains_key(&node.name) { - self.cache.borrow().get(&node.name).unwrap().clone() + let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key(&node.name) { + self.cache.lock().as_ref().unwrap().get(&node.name).unwrap().clone() } else { let name = node.name.to_string(); let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor, quantization).await else { log::error!("Error creating compute pass descriptor in 'map_gpu()"); return ImageFrame::empty(); }; - self.cache.borrow_mut().insert(name, compute_pass_descriptor.clone()); + self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); log::error!("created compute pass"); compute_pass_descriptor }; @@ -154,7 +155,7 @@ impl MapGpuNode { Self { node, editor_api, - cache: RefCell::new(HashMap::new()), + cache: Mutex::new(HashMap::new()), } } } @@ -164,7 +165,7 @@ async fn create_compute_pass_descriptor( image: &ImageFrame, executor: &&WgpuExecutor, quantization: QuantizationChannels, -) -> Result, String> { +) -> Result { let compiler = graph_craft::graphene_compiler::Compiler {}; let inner_network = NodeNetwork::value_network(node); @@ -335,7 +336,7 @@ async fn create_compute_pass_descriptor( buffers: vec![width_uniform, storage_buffer], }; - let shader = gpu_executor::Shader { + let shader = Shader { source: shader.spirv_binary.into(), name: "gpu::eval", io: shader.io, @@ -557,7 +558,7 @@ async fn blend_gpu_image(foreground: ImageFrame, background: ImageFrame { - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] common: ImaginateCommonImageRequest<'a>, override_settings: ImaginateTextToImageRequestOverrideSettings, } @@ -237,13 +237,13 @@ struct ImaginateMask { #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] struct ImaginateImageToImageRequest<'a> { - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] common: ImaginateCommonImageRequest<'a>, override_settings: ImaginateImageToImageRequestOverrideSettings, init_images: Vec, denoising_strength: f64, - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] mask: Option, } @@ -262,7 +262,7 @@ struct ImaginateCommonImageRequest<'a> { sampler_index: &'a str, } -#[cfg(feature = "imaginate")] +#[cfg(all(feature = "imaginate", feature = "serde"))] #[allow(clippy::too_many_arguments)] pub async fn imaginate<'a, P: Pixel>( image: Image

, @@ -328,7 +328,7 @@ pub async fn imaginate<'a, P: Pixel>( }) } -#[cfg(feature = "imaginate")] +#[cfg(all(feature = "imaginate", feature = "serde"))] #[allow(clippy::too_many_arguments)] async fn imaginate_maybe_fail<'a, P: Pixel, F: Fn(ImaginateStatus)>( image: Image

, diff --git a/node-graph/gstd/src/quantization.rs b/node-graph/gstd/src/quantization.rs index 66fa5e51bb..e39a9a8a2b 100644 --- a/node-graph/gstd/src/quantization.rs +++ b/node-graph/gstd/src/quantization.rs @@ -92,7 +92,7 @@ fn generate_quantization(data: Vec, samples: usize, channel }*/ fn create_distribution(data: Vec, samples: usize, channel: usize) -> Vec<(f64, f64)> { - let data: Vec = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel] as f64).collect(); + let data: Vec = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel]).collect(); let max = *data.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap(); let data: Vec = data.iter().map(|x| x / max).collect(); dbg!(max); diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 5197ca1917..9e380b4700 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -10,7 +10,7 @@ use graphene_core::raster::{ }; use graphene_core::transform::{Footprint, Transform}; use graphene_core::value::CopiedNode; -use graphene_core::{AlphaBlending, Color, Node}; +use graphene_core::{AlphaBlending, Color, Node, WasmNotSend}; use fastnoise_lite; use glam::{DAffine2, DVec2, UVec2, Vec2}; @@ -265,12 +265,15 @@ pub struct BlendImageNode { } #[node_macro::node_fn(BlendImageNode<_P>)] -async fn blend_image_node<_P: Alpha + Pixel + Debug, Forground: Sample + Transform>( +async fn blend_image_node<_P: Alpha + Pixel + Debug + WasmNotSend + Sync + 'static, MapFn, Forground: Sample + Transform + Send>( foreground: Forground, background: ImageFrame<_P>, - map_fn: impl Node<(_P, _P), Output = _P>, -) -> ImageFrame<_P> { - blend_new_image(foreground, background, &self.map_fn) + map_fn: &'input MapFn, +) -> ImageFrame<_P> +where + for<'a> MapFn: Node<'a, (_P, _P), Output = _P> + 'input, +{ + blend_new_image(foreground, background, map_fn) } #[derive(Debug, Clone, Copy)] @@ -468,13 +471,14 @@ fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> { } } +#[cfg(feature = "serde")] macro_rules! generate_imaginate_node { ($($val:ident: $t:ident: $o:ty,)*) => { pub struct ImaginateNode { editor_api: E, controller: C, $($val: $t,)* - cache: std::sync::Mutex>>, + cache: std::sync::Arc>>>, } impl<'e, P: Pixel, E, C, $($t,)*> ImaginateNode @@ -488,7 +492,7 @@ macro_rules! generate_imaginate_node { } } - impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode + impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, @@ -503,19 +507,20 @@ macro_rules! generate_imaginate_node { let mut hasher = rustc_hash::FxHasher::default(); frame.image.hash(&mut hasher); let hash = hasher.finish(); + let editor_api = self.editor_api.eval(()); + let cache = self.cache.clone(); Box::pin(async move { - let controller: std::pin::Pin>> = controller; + // let controller: std::pin::Pin + Send>> = controller; let controller: ImaginateController = controller.await; if controller.take_regenerate_trigger() { - let editor_api = self.editor_api.eval(()); let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; - self.cache.lock().unwrap().insert(hash, image.clone()); + cache.lock().unwrap().insert(hash, image.clone()); return ImageFrame { image, ..frame } } - let image = self.cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); + let image = cache.lock().unwrap().get(&hash).cloned().unwrap_or_default(); ImageFrame { image, ..frame } }) @@ -524,6 +529,7 @@ macro_rules! generate_imaginate_node { } } +#[cfg(feature = "serde")] generate_imaginate_node! { seed: Seed: f64, res: Res: Option, diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index ceede8a00f..107fab4c54 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -1,13 +1,12 @@ use crate::Node; use bezier_rs::{ManipulatorGroup, Subpath}; -use graphene_core::raster::ImageFrame; +use graphene_core::transform::Transform; pub use graphene_core::vector::*; use graphene_core::Color; use graphene_core::{transform::Footprint, GraphicGroup}; use graphene_core::{vector::misc::BooleanOperation, GraphicElement}; -use futures::Future; use glam::{DAffine2, DVec2}; use wasm_bindgen::prelude::*; @@ -17,11 +16,7 @@ pub struct BinaryBooleanOperationNode { } #[node_macro::node_fn(BinaryBooleanOperationNode)] -async fn binary_boolean_operation_node>( - upper_vector_data: VectorData, - lower_vector_data: impl Node, - boolean_operation: BooleanOperation, -) -> VectorData { +async fn binary_boolean_operation_node(upper_vector_data: VectorData, lower_vector_data: impl Node, boolean_operation: BooleanOperation) -> VectorData { let lower_vector_data = self.lower_vector_data.eval(Footprint::default()).await; let transform_of_lower_into_space_of_upper = upper_vector_data.transform.inverse() * lower_vector_data.transform; @@ -58,11 +53,11 @@ pub struct BooleanOperationNode { #[node_macro::node_fn(BooleanOperationNode)] fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: BooleanOperation) -> VectorData { - fn vector_from_image(image_frame: &ImageFrame

) -> VectorData { + fn vector_from_image(image_frame: T) -> VectorData { let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); let mut subpath = Subpath::new_rect(corner1, corner2); - subpath.apply_transform(image_frame.transform); + subpath.apply_transform(image_frame.transform()); let mut vector_data = VectorData::from_subpath(subpath); vector_data .style @@ -79,6 +74,7 @@ fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: Boolea boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union) } GraphicElement::ImageFrame(image) => vector_from_image(image), + GraphicElement::Surface(image) => vector_from_image(image), } } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 1943b0a49d..837182beb1 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,39 +1,49 @@ -use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig, SurfaceHandle, SurfaceHandleFrame}; +use dyn_any::DynFuture; +pub use graph_craft::wasm_application_io::*; +#[cfg(target_arch = "wasm32")] +use graphene_core::application_io::SurfaceHandle; +use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig}; +#[cfg(target_arch = "wasm32")] use graphene_core::raster::bbox::Bbox; use graphene_core::raster::Image; -use graphene_core::raster::{color::SRGBA8, ImageFrame}; +use graphene_core::raster::ImageFrame; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; -use graphene_core::transform::{Footprint, TransformMut}; -use graphene_core::Color; +use graphene_core::transform::Footprint; use graphene_core::Node; +use graphene_core::{Color, WasmNotSend}; +#[cfg(target_arch = "wasm32")] use base64::Engine; -use glam::DAffine2; - use core::future::Future; +#[cfg(target_arch = "wasm32")] +use glam::DAffine2; use std::marker::PhantomData; use std::sync::Arc; -use wasm_bindgen::{Clamped, JsCast}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::Clamped; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; -pub use graph_craft::wasm_application_io::*; - -pub type WasmSurfaceHandle = SurfaceHandle; -pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; - pub struct CreateSurfaceNode {} #[node_macro::node_fn(CreateSurfaceNode)] -async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc::Surface>> { - editor.application_io.as_ref().unwrap().create_surface().into() +async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc { + Arc::new(editor.application_io.as_ref().unwrap().create_surface()) } +#[cfg(target_arch = "wasm32")] pub struct DrawImageFrameNode { surface_handle: Surface, } #[node_macro::node_fn(DrawImageFrameNode)] -async fn draw_image_frame_node<'a: 'input>(image: ImageFrame, surface_handle: Arc) -> SurfaceHandleFrame { +#[cfg(target_arch = "wasm32")] +async fn draw_image_frame_node<'a: 'input>( + image: ImageFrame, + surface_handle: Arc, +) -> graphene_core::application_io::SurfaceHandleFrame { let image_data = image.image.data; let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice())); if image.image.width > 0 && image.image.height > 0 { @@ -45,7 +55,7 @@ async fn draw_image_frame_node<'a: 'input>(image: ImageFrame, surface_ha let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData"); context.put_image_data(&image_data, 0.0, 0.0).unwrap(); } - SurfaceHandleFrame { + graphene_core::application_io::SurfaceHandleFrame { surface_handle, transform: image.transform, } @@ -105,14 +115,14 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p RenderOutput::Svg(render.svg.to_svg_string()) } -#[cfg(any(feature = "resvg", feature = "vello"))] -fn _render_canvas( +#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] +fn render_canvas( data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint, editor: &'_ WasmEditorApi, - surface_handle: Arc>, + surface_handle: wgpu_executor::WindowHandle, ) -> RenderOutput { let resolution = footprint.resolution; data.render_svg(&mut render, &render_params); @@ -149,20 +159,26 @@ fn _render_canvas( wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap(); context.draw_image_with_html_image_element(&image_data, 0.0, 0.0).unwrap(); */ - let frame = SurfaceHandleFrame { + let frame = graphene_core::application_io::SurfaceHandleFrame { surface_handle, transform: glam::DAffine2::IDENTITY, }; RenderOutput::CanvasFrame(frame.into()) } +#[cfg(target_arch = "wasm32")] pub struct RasterizeNode { footprint: Footprint, surface_handle: Surface, } #[node_macro::node_fn(RasterizeNode)] -async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, footprint: Footprint, surface_handle: Arc>) -> ImageFrame { +#[cfg(target_arch = "wasm32")] +async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend>( + mut data: _T, + footprint: Footprint, + surface_handle: Arc>, +) -> ImageFrame { let mut render = SvgRender::new(); if footprint.transform.matrix2.determinant() == 0. { @@ -211,18 +227,27 @@ async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, foot } // Render with the data node taking in Footprint. -impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig> - for RenderNode +impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode where - Data: Node<'input, Footprint, Output = F>, - Surface: Node<'input, (), Output = SurfaceFuture>, - SurfaceFuture: core::future::Future::Surface>>>, + for<'a> Data: Node<'a, Footprint, Output: Future + WasmNotSend>, + for<'a> Surface: Node<'a, (), Output: Future + WasmNotSend> + 'input, { - type Output = core::pin::Pin + 'input>>; + type Output = DynFuture<'input, RenderOutput>; #[inline] fn eval(&'input self, render_config: RenderConfig) -> Self::Output { + let footprint = render_config.viewport; + + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let RenderConfig { hide_artboards, for_export, .. } = render_config; + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); + + let data_fut = self.data.eval(footprint); + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let surface_fut = self.surface_handle.eval(()); Box::pin(async move { + let data = data_fut.await; let footprint = render_config.viewport; let RenderConfig { hide_artboards, for_export, .. } = render_config; @@ -230,9 +255,9 @@ where let output_format = render_config.export_format; match output_format { - ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint), + ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await), + ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await), _ => todo!("Non-SVG render output for {output_format:?}"), } }) @@ -240,17 +265,25 @@ where } // Render with the data node taking in (). -impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig> - for RenderNode +impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode where - Data: Node<'input, (), Output = F>, - Surface: Node<'input, (), Output = SurfaceFuture>, - SurfaceFuture: core::future::Future::Surface>>>, + for<'a> Data: Node<'a, (), Output: Future + WasmNotSend>, + for<'a> Surface: Node<'a, (), Output: Future + WasmNotSend> + 'input, { - type Output = core::pin::Pin + 'input>>; + type Output = DynFuture<'input, RenderOutput>; + #[inline] fn eval(&'input self, render_config: RenderConfig) -> Self::Output { + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let RenderConfig { hide_artboards, for_export, .. } = render_config; + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); + + let data_fut = self.data.eval(()); + #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] + let surface_fut = self.surface_handle.eval(()); Box::pin(async move { + let data = data_fut.await; let footprint = render_config.viewport; let RenderConfig { hide_artboards, for_export, .. } = render_config; @@ -258,14 +291,15 @@ where let output_format = render_config.export_format; match output_format { - ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint), + ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await), + ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await), _ => todo!("Non-SVG render output for {output_format:?}"), } }) } } + #[automatically_derived] impl RenderNode { pub fn new(data: Data, _surface_handle: Surface) -> Self { diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index 337352082c..bc4b45484f 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -22,6 +22,7 @@ graphene-core = { workspace = true, features = ["std"] } dyn-any = { workspace = true, features = ["log-bad-types", "glam"] } num-traits = { workspace = true } log = { workspace = true } +wgpu = { workspace = true } glam = { workspace = true } futures = { workspace = true } once_cell = { workspace = true } diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 274d76f03d..1943e78228 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -102,7 +102,7 @@ impl DynamicExecutor { } } -impl<'a, I: StaticType + 'static> Executor for &'a DynamicExecutor { +impl<'a, I: StaticType + 'static + Send + Sync> Executor for &'a DynamicExecutor { fn execute(&self, input: I) -> LocalFuture>> { Box::pin(async move { self.tree.eval_tagged_value(self.output, input).await.map_err(|e| e.into()) }) } @@ -169,14 +169,14 @@ impl BorrowTree { } /// Evaluate the output node of the [`BorrowTree`]. - pub async fn eval<'i, I: StaticType + 'i, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option { + pub async fn eval<'i, I: StaticType + 'i + Send + Sync, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option { let node = self.nodes.get(&id).cloned()?; let output = node.eval(Box::new(input)); dyn_any::downcast::(output.await).ok().map(|o| *o) } /// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// This ensures that no borrowed data can escape the node graph. - pub async fn eval_tagged_value(&self, id: NodeId, input: I) -> Result { + pub async fn eval_tagged_value(&self, id: NodeId, input: I) -> Result { let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; let output = node.eval(Box::new(input)); TaggedValue::try_from_any(output.await) @@ -207,7 +207,7 @@ impl BorrowTree { match &proto_node.construction_args { ConstructionArgs::Value(value) => { - let node: std::rc::Rc = if let TaggedValue::EditorApi(api) = value { + let node = if let TaggedValue::EditorApi(api) = value { let editor_api = UpcastAsRefNode::new(api.clone()); let node = Box::new(editor_api) as TypeErasedBox<'_>; NodeContainer::new(node) diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index a5901e059e..8fcc1d869a 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,30 +1,28 @@ use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; +use graphene_core::fn_type; use graphene_core::ops::IdentityNode; use graphene_core::quantization::{PackedPixel, QuantizationChannels}; - use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::color::Color; +use graphene_core::raster::*; use graphene_core::structural::Then; use graphene_core::transform::Footprint; -use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; +use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::VectorData; -use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame}; +#[cfg(target_arch = "wasm32")] +use graphene_core::WasmSurfaceHandleFrame; use graphene_core::{concrete, generic, Artboard, ArtboardGroup, GraphicGroup}; -use graphene_core::{fn_type, raster::*}; use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::RenderConfig; -use graphene_std::wasm_application_io::*; - -#[cfg(feature = "gpu")] -use gpu_executor::{GpuExecutor, ShaderInput, ShaderInputFrame}; use graphene_std::raster::*; -use graphene_std::wasm_application_io::WasmEditorApi; +use graphene_std::wasm_application_io::*; +use wgpu_executor::WindowHandle; #[cfg(feature = "gpu")] -use wgpu_executor::WgpuExecutor; +use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput}; use dyn_any::StaticType; use glam::{DAffine2, DVec2, UVec2}; @@ -100,7 +98,7 @@ macro_rules! async_node { }, { let node = <$path>::new($( - graphene_std::any::PanicNode::<$arg, core::pin::Pin>>>::new() + graphene_std::any::PanicNode::<$arg, core::pin::Pin + Send>>>::new() ),*); // TODO: Propagate the future type through the node graph // let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*]; @@ -348,7 +346,8 @@ fn node_registry() -> HashMap, input: Footprint, output: Artboard, fn_params: [Footprint => Artboard]), async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: &WasmEditorApi, output: Arc<[u8]>, params: [String]), register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []), - async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: &WasmEditorApi, output: Arc::Surface>>, params: []), + async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: &WasmEditorApi, output: Arc, params: []), + #[cfg(target_arch = "wasm32")] async_node!( graphene_std::wasm_application_io::DrawImageFrameNode<_>, input: ImageFrame, @@ -356,41 +355,40 @@ fn node_registry() -> HashMap] ), #[cfg(feature = "gpu")] - async_node!(gpu_executor::UniformNode<_>, input: f32, output: ShaderInput, params: [&WgpuExecutor]), + async_node!(wgpu_executor::UniformNode<_>, input: f32, output: WgpuShaderInput, params: [&WgpuExecutor]), #[cfg(feature = "gpu")] - async_node!(gpu_executor::StorageNode<_>, input: Vec, output: ShaderInput, params: [&WgpuExecutor]), + async_node!(wgpu_executor::StorageNode<_>, input: Vec, output: WgpuShaderInput, params: [&WgpuExecutor]), #[cfg(feature = "gpu")] async_node!( - gpu_executor::PushNode<_>, - input: Vec>, - output: Vec>, - params: [ShaderInput] + wgpu_executor::PushNode<_>, + input: Vec, + output: Vec, + params: [WgpuShaderInput] ), #[cfg(feature = "gpu")] - async_node!(gpu_executor::CreateOutputBufferNode<_, _>, input: usize, output: gpu_executor::ShaderInput, params: [&WgpuExecutor, Type]), + async_node!(wgpu_executor::CreateOutputBufferNode<_, _>, input: usize, output: WgpuShaderInput, params: [&WgpuExecutor, Type]), #[cfg(feature = "gpu")] - async_node!(gpu_executor::CreateComputePassNode<_, _, _>, input: gpu_executor::PipelineLayout, output: ::CommandBuffer, params: [&WgpuExecutor, ShaderInput, gpu_executor::ComputePassDimensions]), + async_node!(wgpu_executor::CreateComputePassNode<_, _, _>, input: wgpu_executor::PipelineLayout, output: CommandBuffer, params: [&WgpuExecutor, WgpuShaderInput, gpu_executor::ComputePassDimensions]), #[cfg(feature = "gpu")] - async_node!(gpu_executor::CreatePipelineLayoutNode<_, _, _, _>, input: ::ShaderHandle, output: gpu_executor::PipelineLayout, params: [String, gpu_executor::Bindgroup, Arc>]), + async_node!(wgpu_executor::CreatePipelineLayoutNode<_, _, _>, input: ShaderHandle, output: wgpu_executor::PipelineLayout, params: [String, wgpu_executor::Bindgroup, Arc]), #[cfg(feature = "gpu")] async_node!( - gpu_executor::ExecuteComputePipelineNode<_>, - input: ::CommandBuffer, + wgpu_executor::ExecuteComputePipelineNode<_>, + input: CommandBuffer, output: (), params: [&WgpuExecutor] ), #[cfg(feature = "gpu")] - async_node!(gpu_executor::ReadOutputBufferNode<_, _>, input: Arc>, output: Vec, params: [&WgpuExecutor, ()]), + async_node!(wgpu_executor::ReadOutputBufferNode<_, _>, input: Arc, output: Vec, params: [&WgpuExecutor, ()]), #[cfg(feature = "gpu")] - async_node!(gpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: Arc::Surface<'_>>>, params: []), - // todo!(gpu) get this to compie without saying that one type is more general than the other - // #[cfg(feature = "gpu")] - // async_node!(gpu_executor::RenderTextureNode<_, _>, input: ShaderInputFrame, output: SurfaceFrame, params: [Arc::Surface<'_>>>, &WgpuExecutor]), + async_node!(wgpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: wgpu_executor::WgpuSurface, params: []), + #[cfg(feature = "gpu")] + async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => wgpu_executor::WgpuSurface, () =>&WgpuExecutor]), #[cfg(feature = "gpu")] async_node!( - gpu_executor::UploadTextureNode<_>, + wgpu_executor::UploadTextureNode<_>, input: ImageFrame, - output: ShaderInputFrame, + output: ShaderInputFrame, params: [&WgpuExecutor] ), #[cfg(feature = "gpu")] @@ -461,6 +459,7 @@ fn node_registry() -> HashMap, input: ImageFrame, params: [Color]), register_node!(graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>, input: ImageFrame, params: [Color, BlendMode, f64]), register_node!(graphene_core::raster::IndexNode<_>, input: Vec, params: [u32]), + /* vec![( ProtoNodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"), |args| { @@ -469,7 +468,7 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[1].clone()); let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone()); let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await)); - let node = graphene_std::raster::BlendImageNode::new(image, blend_node); + let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node))); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() }) @@ -479,7 +478,7 @@ fn node_registry() -> HashMap), vec![fn_type!(ImageFrame), fn_type!(BlendMode), fn_type!(f64)], ), - )], + )],*/ raster_node!(graphene_core::raster::BlackAndWhiteNode<_, _, _, _, _, _, _>, params: [Color, f64, f64, f64, f64, f64, f64]), raster_node!(graphene_core::raster::HueSaturationNode<_, _, _>, params: [f64, f64, f64]), raster_node!(graphene_core::raster::InvertRGBNode, params: []), @@ -573,8 +572,8 @@ fn node_registry() -> HashMap { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) }; - } + ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) }; + } let node: ImaginateNode = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); let any = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() @@ -607,15 +606,19 @@ fn node_registry() -> HashMap, input: (), output: ImageFrame, params: [ImageFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec, params: [Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc::Surface>>, params: [Arc::Surface>>]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc, params: [Arc]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: WindowHandle, params: [WindowHandle]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame, params: [ShaderInputFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame, params: [ShaderInputFrame]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: SurfaceFrame, params: [SurfaceFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WindowHandle, params: [wgpu_executor::WindowHandle]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: graphene_std::SurfaceFrame, params: [graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]), + #[cfg(feature = "gpu")] + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: ShaderInputFrame, fn_params: [Footprint => ShaderInputFrame]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: (), params: [UVec2, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]), @@ -624,27 +627,29 @@ fn node_registry() -> HashMap, input: Color, params: [QuantizationChannels]), register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec, () => Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec, Arc]), + #[cfg(target_arch = "wasm32")] async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame, params: [Footprint, Arc]), + #[cfg(target_arch = "wasm32")] async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame, params: [Footprint, Arc]), async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), - async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), + // async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: ImageFrame, fn_params: [Footprint => ImageFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]), @@ -670,7 +675,7 @@ fn node_registry() -> HashMap>::new(graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node"))); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); - Box::new(any) as Box NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin> + 'i>>> + '_> + any.into_type_erased() }) }, { @@ -691,7 +696,7 @@ fn node_registry() -> HashMap>::new(graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node"))); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); - Box::new(any) as Box NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin> + 'i>>> + '_> + any.into_type_erased() }) }, { @@ -710,7 +715,7 @@ fn node_registry() -> HashMap>::new(graphene_std::any::input_node::(args.pop().expect("Not enough arguments provided to construct node"))); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(node); - Box::new(any) as Box NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin> + 'i>>> + '_> + any.into_type_erased() }) }, { @@ -752,6 +757,11 @@ fn node_registry() -> HashMap, params: []), register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, params: []), + register_node!(graphene_core::ToGraphicElementNode, input: graphene_std::SurfaceFrame, params: []), + #[cfg(target_arch = "wasm32")] + register_node!(graphene_core::ToGraphicElementNode, input: Arc>, params: []), + #[cfg(target_arch = "wasm32")] + register_node!(graphene_core::ToGraphicElementNode, input: graphene_std::application_io::SurfaceHandleFrame, params: []), register_node!(graphene_core::ToGraphicGroupNode, input: graphene_core::vector::VectorData, params: []), register_node!(graphene_core::ToGraphicGroupNode, input: ImageFrame, params: []), register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []), diff --git a/node-graph/node-macro/Cargo.toml b/node-graph/node-macro/Cargo.toml index 64d7b57350..4157079de4 100644 --- a/node-graph/node-macro/Cargo.toml +++ b/node-graph/node-macro/Cargo.toml @@ -2,7 +2,7 @@ name = "node-macro" publish = false version = "0.0.0" -rust-version = "1.70.0" +rust-version = "1.79" authors = ["Graphite Authors "] edition = "2021" readme = "../../README.md" diff --git a/node-graph/node-macro/src/lib.rs b/node-graph/node-macro/src/lib.rs index fd5f1810c2..00a03fcbc8 100644 --- a/node-graph/node-macro/src/lib.rs +++ b/node-graph/node-macro/src/lib.rs @@ -3,7 +3,7 @@ use proc_macro2::Span; use quote::{format_ident, quote, ToTokens}; use syn::{ parse_macro_input, punctuated::Punctuated, token::Comma, AngleBracketedGenericArguments, AssocType, FnArg, GenericArgument, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments, - PredicateType, ReturnType, Token, TraitBound, Type, TypeImplTrait, TypeParam, TypeParamBound, TypeTuple, WhereClause, WherePredicate, + PathSegment, PredicateType, ReturnType, Token, TraitBound, Type, TypeImplTrait, TypeParam, TypeParamBound, TypeTuple, WhereClause, WherePredicate, }; /// A macro used to construct a proto node implementation from the given struct and the decorated function. @@ -238,67 +238,53 @@ fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) -> let num_inputs = parameter_inputs.len(); let struct_generics = (0..num_inputs).map(|x| format_ident!("S{x}")).collect::>(); - let future_generics = (0..num_inputs).map(|x| format_ident!("F{x}")).collect::>(); let parameter_types = parameter_inputs.iter().map(|x| *x.ty.clone()).collect::>(); - let future_types = future_generics - .iter() - .enumerate() - .map(|(i, x)| match parameter_types[i].clone() { - Type::ImplTrait(x) => Type::ImplTrait(x), - _ => Type::Verbatim(x.to_token_stream()), - }) - .collect::>(); for ident in struct_generics.iter() { args.push(Type::Verbatim(quote::quote!(#ident))); } // Generics are simply `S0` through to `Sn-1` where n is the number of secondary inputs - let node_generics = construct_node_generics(&struct_generics); - let future_generic_params = construct_node_generics(&future_generics); - let (future_parameter_types, future_generic_params): (Vec<_>, Vec<_>) = parameter_types.iter().cloned().zip(future_generic_params).filter(|(ty, _)| !matches!(ty, Type::ImplTrait(_))).unzip(); - - let generics = if async_in { - type_generics - .into_iter() - .chain(node_generics.iter().cloned()) - .chain(future_generic_params.iter().cloned()) - .collect::>() - } else { - type_generics.into_iter().chain(node_generics.iter().cloned()).collect::>() - }; + let node_generics = construct_node_generics(&struct_generics, true); + let generics = type_generics.into_iter().chain(node_generics.iter().cloned()).collect::>(); // Bindings for all of the above generics to a node with an input of `()` and an output of the type in the function let node_bounds = if async_in { - let mut node_bounds = input_node_bounds(future_types, node_generics, |lifetime, in_ty, out_ty| quote! {Node<#lifetime, #in_ty, Output = #out_ty>}); - let future_bounds = input_node_bounds(future_parameter_types, future_generic_params, |_, _, out_ty| quote! { core::future::Future}); - node_bounds.extend(future_bounds); - node_bounds + input_node_bounds(parameter_types, node_generics, |lifetime, in_ty, out_ty| { + quote! { + Node<'any_input, #in_ty, Output: core::future::Future + ::dyn_any::WasmNotSend > + ::dyn_any::WasmNotSend + #lifetime + + } + }) } else { input_node_bounds(parameter_types, node_generics, |lifetime, in_ty, out_ty| quote! {Node<#lifetime, #in_ty, Output = #out_ty>}) }; where_clause.predicates.extend(node_bounds); let output = if async_out { - quote::quote!(core::pin::Pin + 'input>>) + quote::quote!( ::dyn_any::DynFuture<'input, #output>) } else { quote::quote!(#output) }; let parameter_idents = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.ident).collect::>(); - let parameter_mutability = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.mutability); + let parameter_mutability = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.mutability).collect::>(); + let futures = quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());)*); let parameters = if matches!(asyncness, Asyncness::AllAsync) { - quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(()).await;)*) + quote::quote!(#(let #parameter_mutability #parameter_idents = #parameter_idents.await;)*) } else { - quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());)*) + quote::quote!(#(let #parameter_mutability #parameter_idents = #parameter_idents;)*) }; let mut body_with_inputs = quote::quote!( - #parameters + #futures #body ); - if async_out { - body_with_inputs = quote::quote!(Box::pin(async move { #body_with_inputs })); + if async_out && !body.to_token_stream().to_string().contains("async") { + body_with_inputs = quote::quote!( + #futures + Box::pin(async move { #parameters #body }) + ); } quote::quote! { @@ -338,7 +324,34 @@ fn parse_inputs(function: &ItemFn, remove_impl_node: bool) -> (&syn::PatType, Ve (primary_input, parameter_inputs, parameter_pat_ident_patterns) } -fn construct_node_generics(struct_generics: &[Ident]) -> Vec { +fn path(elements: &[&str]) -> syn::Path { + syn::Path { + leading_colon: None, + segments: Punctuated::from_iter(elements.iter().map(|element| PathSegment { + ident: Ident::new(element, Span::mixed_site()), + arguments: PathArguments::None, + })), + } +} + +fn construct_node_generics(struct_generics: &[Ident], add_sync: bool) -> Vec { + let mut bounds = vec![ + TypeParamBound::Lifetime(Lifetime::new("'input", Span::call_site())), + TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: path(&["dyn_any", "WasmNotSend"]), + }), + ]; + if add_sync { + bounds.push(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: path(&["dyn_any", "WasmNotSync"]), + })) + }; struct_generics .iter() .cloned() @@ -347,7 +360,7 @@ fn construct_node_generics(struct_generics: &[Ident]) -> Vec { attrs: vec![], ident, colon_token: Some(Default::default()), - bounds: Punctuated::from_iter([TypeParamBound::Lifetime(Lifetime::new("'input", Span::call_site()))].iter().cloned()), + bounds: Punctuated::from_iter(bounds.clone()), eq_token: None, default: None, }) @@ -403,15 +416,10 @@ fn input_node_bounds(parameter_inputs: Vec, node_generics: Vec)), bounded_ty: Type::Verbatim(ident.to_token_stream()), colon_token: Default::default(), - bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: syn::TraitBoundModifier::None, - lifetimes: None, // syn::parse_quote!(for<'any_input>), - path: syn::parse_quote!(#bound), - })]), + bounds: syn::parse_quote!(#bound), }) }) .collect() diff --git a/node-graph/vulkan-executor/Cargo.toml b/node-graph/vulkan-executor/Cargo.toml deleted file mode 100644 index 025ead3314..0000000000 --- a/node-graph/vulkan-executor/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "vulkan-executor" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" - -[features] -default = [] - -[dependencies] -# Local dependencies -graphene-core = { path = "../gcore", features = ["std", "alloc", "gpu"] } -graph-craft = { path = "../graph-craft" } -dyn-any = { path = "../../libraries/dyn-any", features = [ - "log-bad-types", - "rc", - "glam", -] } - -# Workspace dependencies -num-traits = { workspace = true } -log = { workspace = true } -glam = { workspace = true } -base64 = { workspace = true } -bytemuck = { workspace = true } -anyhow = { workspace = true } - -# Required dependencies -vulkano = { git = "https://github.com/GraphiteEditor/vulkano", branch = "fix_rust_gpu" } - -# Optional workspace dependencies -serde = { workspace = true, optional = true } diff --git a/node-graph/vulkan-executor/src/context.rs b/node-graph/vulkan-executor/src/context.rs deleted file mode 100644 index 10ec02887c..0000000000 --- a/node-graph/vulkan-executor/src/context.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::sync::Arc; -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, DeviceCreateInfo, Queue, QueueCreateInfo}, - instance::{Instance, InstanceCreateInfo}, - memory::allocator::StandardMemoryAllocator, - VulkanLibrary, -}; - -#[derive(Debug)] -pub struct Context { - pub instance: Arc, - pub device: Arc, - pub queue: Arc, - pub allocator: StandardMemoryAllocator, - pub command_buffer_allocator: StandardCommandBufferAllocator, - pub descriptor_set_allocator: StandardDescriptorSetAllocator, -} - -impl Context { - pub fn new() -> Self { - let library = VulkanLibrary::new().unwrap(); - let instance = Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); - let physical = instance.enumerate_physical_devices().expect("could not enumerate devices").next().expect("no device available"); - for family in physical.queue_family_properties() { - println!("Found a queue family with {:?} queue(s)", family.queue_count); - } - let queue_family_index = physical - .queue_family_properties() - .iter() - .enumerate() - .position(|(_, q)| q.queue_flags.graphics) - .expect("couldn't find a graphical queue family") as u32; - - let (device, mut queues) = Device::new( - physical, - DeviceCreateInfo { - // here we pass the desired queue family to use by index - queue_create_infos: vec![QueueCreateInfo { - queue_family_index, - ..Default::default() - }], - ..Default::default() - }, - ) - .expect("failed to create device"); - let queue = queues.next().unwrap(); - let alloc = StandardMemoryAllocator::new_default(device.clone()); - let calloc = StandardCommandBufferAllocator::new(device.clone()); - let dalloc = StandardDescriptorSetAllocator::new(device.clone()); - - Self { - instance, - device, - queue, - allocator: alloc, - command_buffer_allocator: calloc, - descriptor_set_allocator: dalloc, - } - } -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} diff --git a/node-graph/vulkan-executor/src/executor.rs b/node-graph/vulkan-executor/src/executor.rs deleted file mode 100644 index 325df00a60..0000000000 --- a/node-graph/vulkan-executor/src/executor.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::error::Error; - -use super::context::Context; - -use graph_craft::graphene_compiler::Executor; - -use graph_craft::proto::LocalFuture; -use graphene_core::gpu::PushConstants; - -use bytemuck::Pod; -use dyn_any::StaticTypeSized; -use vulkano::buffer::{self, BufferUsage, CpuAccessibleBuffer}; -use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; -use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage}; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; -use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; -use vulkano::device::Device; -use vulkano::memory::allocator::StandardMemoryAllocator; -use vulkano::pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}; -use vulkano::sync::GpuFuture; - -#[derive(Debug)] -pub struct GpuExecutor { - context: Context, - entry_point: String, - shader: std::sync::Arc, - _phantom: std::marker::PhantomData<(I, O)>, -} - -impl GpuExecutor { - pub fn new(context: Context, shader: &[u8], entry_point: String) -> anyhow::Result { - let shader = unsafe { vulkano::shader::ShaderModule::from_bytes(context.device.clone(), shader)? }; - - Ok(Self { - context, - entry_point, - shader, - _phantom: std::marker::PhantomData, - }) - } -} - -impl<'a, I: StaticTypeSized + Sync + Pod + Send + 'a, O: StaticTypeSized + Send + Sync + Pod + 'a> Executor, Vec> for &'a GpuExecutor { - fn execute(&self, input: Vec) -> LocalFuture, Box>> { - let context = &self.context; - let result: Vec = execute_shader( - context.device.clone(), - context.queue.clone(), - self.shader.entry_point(&self.entry_point).expect("Entry point not found in shader"), - &context.allocator, - &context.command_buffer_allocator, - input, - ); - Box::pin(async move { Ok(result) }) - } -} - -// TODO: make async -fn execute_shader( - device: std::sync::Arc, - queue: std::sync::Arc, - entry_point: vulkano::shader::EntryPoint, - alloc: &StandardMemoryAllocator, - calloc: &StandardCommandBufferAllocator, - data: Vec, -) -> Vec { - let constants = PushConstants { n: data.len() as u32, node: 0 }; - - let dest_data: Vec<_> = (0..constants.n).map(|_| O::zeroed()).collect(); - let source_buffer = create_buffer(data, alloc).expect("failed to create buffer"); - let dest_buffer = create_buffer(dest_data, alloc).expect("failed to create buffer"); - - let compute_pipeline = ComputePipeline::new(device.clone(), entry_point, &(), None, |_| {}).expect("failed to create compute pipeline"); - let layout = compute_pipeline.layout().set_layouts().first().unwrap(); - let dalloc = StandardDescriptorSetAllocator::new(device.clone()); - let set = PersistentDescriptorSet::new( - &dalloc, - layout.clone(), - [ - WriteDescriptorSet::buffer(0, source_buffer), // 0 is the binding - WriteDescriptorSet::buffer(1, dest_buffer.clone()), - ], - ) - .unwrap(); - let mut builder = AutoCommandBufferBuilder::primary(calloc, queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit).unwrap(); - - builder - .bind_pipeline_compute(compute_pipeline.clone()) - .bind_descriptor_sets(PipelineBindPoint::Compute, compute_pipeline.layout().clone(), 0, set) - .push_constants(compute_pipeline.layout().clone(), 0, constants) - .dispatch([((constants.n as isize - 1) / 1024 + 1) as u32 * 1024, 1, 1]) - .unwrap(); - let command_buffer = builder.build().unwrap(); - - let future = vulkano::sync::now(device).then_execute(queue, command_buffer).unwrap().then_signal_fence_and_flush().unwrap(); - #[cfg(feature = "profiling")] - nvtx::range_push!("compute"); - future.wait(None).unwrap(); - #[cfg(feature = "profiling")] - nvtx::range_pop!(); - let content = dest_buffer.read().unwrap(); - content.to_vec() -} - -fn create_buffer(data: Vec, alloc: &StandardMemoryAllocator) -> Result>, vulkano::memory::allocator::AllocationCreationError> { - let buffer_usage = BufferUsage { - storage_buffer: true, - transfer_src: true, - transfer_dst: true, - ..Default::default() - }; - - buffer::CpuAccessibleBuffer::from_iter(alloc, buffer_usage, false, data) -} - -// TODO: Fix this test -// #[cfg(test)] -// mod test { -// use graph_craft::proto::{ConstructionArgs, ProtoNodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput, Type}; -// use graph_craft::{concrete, generic}; - -// fn inc_network() -> ProtoNetwork { -// let mut construction_network = ProtoNetwork { -// inputs: vec![NodeId(10)], -// output: NodeId(1), -// nodes: [ -// ( -// NodeId(1), -// ProtoNode { -// identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode", &[generic!("u32")]), -// input: ProtoNodeInput::Node(11), -// construction_args: ConstructionArgs::Nodes(vec![]), -// }, -// ), -// ( -// NodeId(10), -// ProtoNode { -// identifier: ProtoNodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("&ValueNode"), generic!("()")]), -// input: ProtoNodeInput::Network, -// construction_args: ConstructionArgs::Nodes(vec![14]), -// }, -// ), -// ( -// NodeId(11), -// ProtoNode { -// identifier: ProtoNodeIdentifier::new("graphene_core::ops::AddPairNode", &[generic!("u32"), generic!("u32")]), -// input: ProtoNodeInput::Node(10), -// construction_args: ConstructionArgs::Nodes(vec![]), -// }, -// ), -// ( -// NodeId(14), -// ProtoNode { -// identifier: ProtoNodeIdentifier::new("graphene_core::value::ValueNode", &[concrete!("u32")]), -// input: ProtoNodeInput::None, -// construction_args: ConstructionArgs::Value(Box::new(3_u32)), -// }, -// ), -// ] -// .into_iter() -// .collect(), -// }; -// construction_network.resolve_inputs(); -// construction_network.reorder_ids(); -// construction_network -// } - -// #[test] -// fn add_on_gpu() { -// use crate::executor::Executor; -// let m = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]); -// let network = inc_network(); -// let temp_dir = tempfile::tempdir().expect("failed to create tempdir"); - -// let executor: GpuExecutor = GpuExecutor::new(Context::new(), network, m, temp_dir.path()).unwrap(); - -// let data: Vec<_> = (0..1024).map(|x| x as u32).collect(); -// let result = executor.execute(Box::new(data)).unwrap(); -// let result = dyn_any::downcast::>(result).unwrap(); -// for (i, r) in result.iter().enumerate() { -// assert_eq!(*r, i as u32 + 3); -// } -// } -// } diff --git a/node-graph/vulkan-executor/src/lib.rs b/node-graph/vulkan-executor/src/lib.rs deleted file mode 100644 index b9aa033841..0000000000 --- a/node-graph/vulkan-executor/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod context; -mod executor; - -pub use context::Context; -pub use executor::GpuExecutor; diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index d61bfe387f..cdfea1bd13 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -16,23 +16,27 @@ gpu-executor = { path = "../gpu-executor" } # Workspace dependencies graphene-core = { workspace = true, features = ["std", "alloc", "gpu"] } dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } +node-macro = { workspace = true } num-traits = { workspace = true } log = { workspace = true } glam = { workspace = true } base64 = { workspace = true } bytemuck = { workspace = true } anyhow = { workspace = true } -wgpu = { workspace = true, features = ["spirv"] } +wgpu = { workspace = true, features = [ + "spirv", + "strict_asserts", + "api_log_info", +] } spirv = { workspace = true } futures = { workspace = true } web-sys = { workspace = true, features = ["HtmlCanvasElement"] } winit = { workspace = true } +serde = { workspace = true } # Required dependencies -futures-intrusive = "0.5.0" - -# Optional workspace dependencies -serde = { workspace = true, optional = true } +futures-intrusive = { version = "0.5.0", features = ["alloc"] } +half = "2.4.1" # Optional dependencies nvtx = { version = "1.3", optional = true } diff --git a/node-graph/wgpu-executor/src/executor.rs b/node-graph/wgpu-executor/src/executor.rs index 96f8bf3fc2..4c23b02495 100644 --- a/node-graph/wgpu-executor/src/executor.rs +++ b/node-graph/wgpu-executor/src/executor.rs @@ -103,6 +103,7 @@ async fn execute_shader(device: Arc< layout: None, module: &cs_module, entry_point: entry_point.as_str(), + compilation_options: Default::default(), }); // Instantiates the bind group, once again specifying the binding of buffers. diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 9e6b34d71d..6fccc5473a 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -1,22 +1,24 @@ mod context; mod executor; - pub use context::Context; -use dyn_any::{DynAny, StaticType}; pub use executor::GpuExecutor; -pub use gpu_executor::ShaderIO; -use gpu_executor::{ComputePassDimensions, Shader, ShaderInput, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; + +use dyn_any::{DynAny, StaticType}; +use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; +use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_core::raster::color::RGBA16F; +use graphene_core::raster::{Image, ImageFrame}; +use graphene_core::transform::{Footprint, Transform}; use graphene_core::Type; +use graphene_core::{Color, Cow, Node, SurfaceFrame}; use anyhow::{bail, Result}; use futures::Future; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; - +use glam::DAffine2; use std::pin::Pin; use std::sync::Arc; - use wgpu::util::DeviceExt; -use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, SurfaceError, Texture, TextureView}; +use wgpu::{Buffer, BufferDescriptor, ShaderModule, SurfaceConfiguration, SurfaceError, Texture, TextureView}; #[cfg(target_arch = "wasm32")] use web_sys::HtmlCanvasElement; @@ -42,7 +44,8 @@ impl<'a, T: ApplicationIo> From<&'a EditorApi> for & } } -pub type WgpuSurface<'window> = Arc>>; +pub type WgpuSurface = Arc>; +pub type WgpuWindow = Arc>; #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] @@ -94,29 +97,32 @@ const VERTICES: &[Vertex] = &[ const INDICES: &[u16] = &[0, 1, 2, 2, 1, 3]; -type WgpuShaderInput = ShaderInput; - #[derive(Debug, DynAny)] #[repr(transparent)] -pub struct CommandBufferWrapper(CommandBuffer); +pub struct CommandBuffer(wgpu::CommandBuffer); #[derive(Debug, DynAny)] #[repr(transparent)] pub struct ShaderModuleWrapper(ShaderModule); +pub type ShaderHandle = ShaderModuleWrapper; +pub type BufferHandle = Buffer; +pub type TextureHandle = Texture; +pub struct Surface(wgpu::Surface<'static>); +#[cfg(target_arch = "wasm32")] +pub type Window = HtmlCanvasElement; +#[cfg(not(target_arch = "wasm32"))] +pub type Window = winit::window::Window; -impl gpu_executor::GpuExecutor for WgpuExecutor { - type ShaderHandle = ShaderModuleWrapper; - type BufferHandle = Buffer; - type TextureHandle = Texture; - type TextureView = TextureView; - type CommandBuffer = CommandBufferWrapper; - type Surface<'window> = wgpu::Surface<'window>; - #[cfg(target_arch = "wasm32")] - type Window = HtmlCanvasElement; - #[cfg(not(target_arch = "wasm32"))] - type Window = Arc; +unsafe impl StaticType for Surface { + type Static = Surface; +} - fn load_shader(&self, shader: Shader) -> Result { +// pub trait SpirVCompiler { +// fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result; +// } + +impl WgpuExecutor { + pub fn load_shader(&self, shader: Shader) -> Result { #[cfg(not(feature = "passthrough"))] let shader_module = self.context.device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some(shader.name), @@ -132,7 +138,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { Ok(ShaderModuleWrapper(shader_module)) } - fn create_uniform_buffer(&self, data: T) -> Result { + pub fn create_uniform_buffer(&self, data: T) -> Result { let bytes = data.to_bytes(); let buffer = self.context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, @@ -142,7 +148,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { Ok(ShaderInput::UniformBuffer(buffer, Type::new::())) } - fn create_storage_buffer(&self, data: T, options: StorageBufferOptions) -> Result { + pub fn create_storage_buffer(&self, data: T, options: StorageBufferOptions) -> Result { let bytes = data.to_bytes(); let mut usage = wgpu::BufferUsages::empty(); @@ -167,15 +173,17 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { }); Ok(ShaderInput::StorageBuffer(buffer, data.ty())) } - fn create_texture_buffer(&self, data: T, options: TextureBufferOptions) -> Result { + pub fn create_texture_buffer(&self, data: T, options: TextureBufferOptions) -> Result { let bytes = data.to_bytes(); let usage = match options { TextureBufferOptions::Storage => wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, TextureBufferOptions::Texture => wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, TextureBufferOptions::Surface => wgpu::TextureUsages::RENDER_ATTACHMENT, }; + let format = match T::format() { TextureBufferType::Rgba32Float => wgpu::TextureFormat::Rgba32Float, + TextureBufferType::Rgba16Float => wgpu::TextureFormat::Rgba16Float, TextureBufferType::Rgba8Srgb => wgpu::TextureFormat::Bgra8UnormSrgb, }; @@ -205,7 +213,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { } } - fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result { + pub fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result { log::warn!("Creating output buffer with len: {len}"); let create_buffer = |usage| { Ok::<_, anyhow::Error>(self.context.device.create_buffer(&BufferDescriptor { @@ -221,12 +229,13 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { }; Ok(buffer) } - fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout, read_back: Option>, instances: ComputePassDimensions) -> Result { + pub fn create_compute_pass(&self, layout: &PipelineLayout, read_back: Option>, instances: ComputePassDimensions) -> Result { let compute_pipeline = self.context.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &layout.shader.0, entry_point: layout.entry_point.as_str(), + compilation_options: Default::default(), }); let bind_group_layout = compute_pipeline.get_bind_group_layout(0); @@ -240,9 +249,9 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { .map(|(i, buffer)| wgpu::BindGroupEntry { binding: i as u32, resource: match buffer { - gpu_executor::BindingType::UniformBuffer(buf) => buf.as_entire_binding(), - gpu_executor::BindingType::StorageBuffer(buf) => buf.as_entire_binding(), - gpu_executor::BindingType::TextureView(buf) => wgpu::BindingResource::TextureView(buf), + BindingType::UniformBuffer(buf) => buf.as_entire_binding(), + BindingType::StorageBuffer(buf) => buf.as_entire_binding(), + BindingType::TextureView(buf) => wgpu::BindingResource::TextureView(buf), }, }) .collect::>(); @@ -280,23 +289,39 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { } // Submits command encoder for processing - Ok(CommandBufferWrapper(encoder.finish())) + Ok(CommandBuffer(encoder.finish())) } - fn create_render_pass(&self, texture: Arc>, canvas: Arc>) -> Result<()> { - let texture = texture.texture().expect("Expected texture input"); - let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let result = canvas.as_ref().surface.get_current_texture(); + pub fn create_render_pass(&self, _footprint: Footprint, texture: ShaderInputFrame, canvas: Arc>) -> Result<()> { + let transform = texture.transform; + let texture = texture.shader_input.texture().expect("Expected texture input"); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Rgba16Float), + ..Default::default() + }); - let surface = &canvas.as_ref().surface; + let surface = &canvas.as_ref().surface.0; let surface_caps = surface.get_capabilities(&self.context.adapter); - println!("{surface_caps:?}"); if surface_caps.formats.is_empty() { log::warn!("No surface formats available"); // return Ok(()); } - // let new_config = config.clone(); - // self.surface_config.replace(Some(config)); + // TODO: + let resolution = transform.decompose_scale().as_uvec2(); + let surface_format = wgpu::TextureFormat::Bgra8Unorm; + let config = SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: resolution.x, + height: resolution.y, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&self.context.device, &config); + let result = surface.get_current_texture(); + let output = match result { Err(SurfaceError::Timeout) => { log::warn!("Timeout when getting current texture"); @@ -347,7 +372,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { view: &view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Load, + load: wgpu::LoadOp::Clear(wgpu::Color::RED), store: wgpu::StoreOp::Store, }, })], @@ -376,13 +401,13 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { Ok(()) } - fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()> { + pub fn execute_compute_pipeline(&self, encoder: CommandBuffer) -> Result<()> { self.context.queue.submit(Some(encoder.0)); Ok(()) } - fn read_output_buffer(&self, buffer: Arc>) -> Pin>>>> { + pub fn read_output_buffer(&self, buffer: Arc) -> Pin>> + Send>> { Box::pin(async move { if let ShaderInput::ReadBackBuffer(buffer, _) = buffer.as_ref() { let buffer_slice = buffer.slice(..); @@ -420,7 +445,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { }) } - fn create_texture_view(&self, texture: ShaderInput) -> Result> { + pub fn create_texture_view(&self, texture: WgpuShaderInput) -> Result { // Ok(ShaderInput::TextureView(texture.create_view(&wgpu::TextureViewDescriptor::default()), ) ) let ShaderInput::TextureBuffer(texture, ty) = &texture else { bail!("Tried to create a texture view from a non texture"); @@ -430,36 +455,37 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { } #[cfg(target_arch = "wasm32")] - fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result> { + fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result> { let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?; - let surface_caps = surface.get_capabilities(&self.context.adapter); - let surface_format = wgpu::TextureFormat::Bgra8Unorm; - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: 1920, - height: 1080, - present_mode: surface_caps.present_modes[0], - alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied, - view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb], - desired_maximum_frame_latency: 2, - }; - surface.configure(&self.context.device, &config); + // let surface_caps = surface.get_capabilities(&self.context.adapter); + // let surface_format = wgpu::TextureFormat::Rgba16Float; + // let config = wgpu::SurfaceConfiguration { + // usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + // format: surface_format, + // width: 1920, + // height: 1080, + // present_mode: surface_caps.present_modes[0], + // alpha_mode: surface_caps.alpha_modes[0], + // view_formats: vec![], + // desired_maximum_frame_latency: 2, + // }; + // surface.configure(&self.context.device, &config); + // self.surface_config.set(Some(config)); Ok(SurfaceHandle { surface_id: canvas.surface_id, - surface, + surface: Surface(surface), }) } #[cfg(not(target_arch = "wasm32"))] - fn create_surface(&self, window: SurfaceHandle) -> Result> { + fn create_surface(&self, window: SurfaceHandle) -> Result> { let size = window.surface.inner_size(); let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?; let surface_caps = surface.get_capabilities(&self.context.adapter); println!("{surface_caps:?}"); - let surface_format = wgpu::TextureFormat::Bgra8Unorm; - let config = wgpu::SurfaceConfiguration { + let surface_format = wgpu::TextureFormat::Rgba16Float; + let _config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: size.width, @@ -469,10 +495,13 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { view_formats: vec![], desired_maximum_frame_latency: 2, }; - surface.configure(&self.context.device, &config); + // surface.configure(&self.context.device, &config); let surface_id = window.surface_id; - Ok(SurfaceHandle { surface_id, surface }) + Ok(SurfaceHandle { + surface_id, + surface: Surface(surface), + }) } } @@ -531,6 +560,7 @@ impl WgpuExecutor { module: &shader, entry_point: "vs_main", buffers: &[Vertex::desc()], + compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -543,6 +573,7 @@ impl WgpuExecutor { }), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: Default::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -601,3 +632,287 @@ struct RenderConfiguration { texture_bind_group_layout: wgpu::BindGroupLayout, sampler: wgpu::Sampler, } + +pub type WgpuShaderInput = ShaderInput; +pub type AbstractShaderInput = ShaderInput<(), (), ()>; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +/// All the possible inputs to a shader. +pub enum ShaderInput { + UniformBuffer(BufferHandle, Type), + StorageBuffer(BufferHandle, Type), + TextureBuffer(TextureHandle, Type), + StorageTextureBuffer(TextureHandle, Type), + TextureView(TextureView, Type), + /// A struct representing a work group memory buffer. This cannot be accessed by the CPU. + WorkGroupMemory(usize, Type), + Constant(GPUConstant), + OutputBuffer(BufferHandle, Type), + ReadBackBuffer(BufferHandle, Type), +} + +unsafe impl StaticType for ShaderInput { + type Static = ShaderInput; +} + +pub enum BindingType<'a> { + UniformBuffer(&'a BufferHandle), + StorageBuffer(&'a BufferHandle), + TextureView(&'a TextureView), +} + +/// Extract the buffer handle from a shader input. +impl ShaderInput { + pub fn binding(&self) -> Option { + match self { + ShaderInput::UniformBuffer(buffer, _) => Some(BindingType::UniformBuffer(buffer)), + ShaderInput::StorageBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), + ShaderInput::WorkGroupMemory(_, _) => None, + ShaderInput::Constant(_) => None, + ShaderInput::TextureBuffer(_, _) => None, + ShaderInput::StorageTextureBuffer(_, _) => None, + ShaderInput::TextureView(tex, _) => Some(BindingType::TextureView(tex)), + ShaderInput::OutputBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), + ShaderInput::ReadBackBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)), + } + } + pub fn buffer(&self) -> Option<&BufferHandle> { + match self { + ShaderInput::UniformBuffer(buffer, _) => Some(buffer), + ShaderInput::StorageBuffer(buffer, _) => Some(buffer), + ShaderInput::WorkGroupMemory(_, _) => None, + ShaderInput::Constant(_) => None, + ShaderInput::TextureBuffer(_, _) => None, + ShaderInput::StorageTextureBuffer(_, _) => None, + ShaderInput::TextureView(_tex, _) => None, + ShaderInput::OutputBuffer(buffer, _) => Some(buffer), + ShaderInput::ReadBackBuffer(buffer, _) => Some(buffer), + } + } + pub fn texture(&self) -> Option<&TextureHandle> { + match self { + ShaderInput::UniformBuffer(_, _) => None, + ShaderInput::StorageBuffer(_, _) => None, + ShaderInput::WorkGroupMemory(_, _) => None, + ShaderInput::Constant(_) => None, + ShaderInput::TextureBuffer(tex, _) => Some(tex), + ShaderInput::StorageTextureBuffer(tex, _) => Some(tex), + ShaderInput::TextureView(_, _) => None, + ShaderInput::OutputBuffer(_, _) => None, + ShaderInput::ReadBackBuffer(_, _) => None, + } + } +} +impl ShaderInput { + pub fn ty(&self) -> Type { + match self { + ShaderInput::UniformBuffer(_, ty) => ty.clone(), + ShaderInput::StorageBuffer(_, ty) => ty.clone(), + ShaderInput::WorkGroupMemory(_, ty) => ty.clone(), + ShaderInput::Constant(c) => c.ty(), + ShaderInput::TextureBuffer(_, ty) => ty.clone(), + ShaderInput::StorageTextureBuffer(_, ty) => ty.clone(), + ShaderInput::TextureView(_, ty) => ty.clone(), + ShaderInput::OutputBuffer(_, ty) => ty.clone(), + ShaderInput::ReadBackBuffer(_, ty) => ty.clone(), + } + } + + pub fn is_output(&self) -> bool { + matches!(self, ShaderInput::OutputBuffer(_, _)) + } +} + +pub struct Shader<'a> { + pub source: Cow<'a, [u32]>, + pub name: &'a str, + pub io: ShaderIO, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +pub struct ShaderIO { + pub inputs: Vec, + pub output: AbstractShaderInput, +} + +/// Collection of all arguments that are passed to the shader. +#[derive(DynAny)] +pub struct Bindgroup { + pub buffers: Vec>, +} + +/// A struct representing a compute pipeline. +#[derive(DynAny, Clone)] +pub struct PipelineLayout { + pub shader: Arc, + pub entry_point: String, + pub bind_group: Arc, + pub output_buffer: Arc, +} + +/// Extracts arguments from the function arguments and wraps them in a node. +pub struct ShaderInputNode { + data: T, +} + +impl<'i, T: 'i> Node<'i, ()> for ShaderInputNode { + type Output = &'i T; + + fn eval(&'i self, _: ()) -> Self::Output { + &self.data + } +} + +impl ShaderInputNode { + pub fn new(data: T) -> Self { + Self { data } + } +} + +pub struct UniformNode { + executor: Executor, +} + +#[node_macro::node_fn(UniformNode)] +async fn uniform_node<'a: 'input, T: ToUniformBuffer + Send>(data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { + executor.create_uniform_buffer(data).unwrap() +} + +pub struct StorageNode { + executor: Executor, +} + +#[node_macro::node_fn(StorageNode)] +async fn storage_node<'a: 'input, T: ToStorageBuffer + Send>(data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { + executor + .create_storage_buffer( + data, + StorageBufferOptions { + cpu_writable: false, + gpu_writable: true, + cpu_readable: false, + storage: true, + }, + ) + .unwrap() +} + +pub struct PushNode { + value: Value, +} + +#[node_macro::node_fn(PushNode)] +async fn push_node(mut vec: Vec, value: T) { + vec.push(value); +} + +pub struct CreateOutputBufferNode { + executor: Executor, + ty: Ty, +} + +#[node_macro::node_fn(CreateOutputBufferNode)] +async fn create_output_buffer_node<'a: 'input>(size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc { + Arc::new(executor.create_output_buffer(size, ty, true).unwrap()) +} + +pub struct CreateComputePassNode { + executor: Executor, + output: Output, + instances: Instances, +} + +#[node_macro::node_fn(CreateComputePassNode)] +async fn create_compute_pass_node<'a: 'input>(layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer { + executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() +} + +pub struct CreatePipelineLayoutNode { + entry_point: EntryPoint, + bind_group: Bindgroup, + output_buffer: OutputBuffer, +} + +#[node_macro::node_fn(CreatePipelineLayoutNode)] +async fn create_pipeline_layout_node(shader: ShaderHandle, entry_point: String, bind_group: Bindgroup, output_buffer: Arc) -> PipelineLayout { + PipelineLayout { + shader: shader.into(), + entry_point, + bind_group: bind_group.into(), + output_buffer, + } +} + +pub struct ExecuteComputePipelineNode { + executor: Executor, +} + +#[node_macro::node_fn(ExecuteComputePipelineNode)] +async fn execute_compute_pipeline_node<'a: 'input>(encoder: CommandBuffer, executor: &'a WgpuExecutor) { + executor.execute_compute_pipeline(encoder).unwrap(); +} + +pub struct ReadOutputBufferNode { + executor: Executor, + _compute_pass: ComputePass, +} +#[node_macro::node_fn(ReadOutputBufferNode)] +async fn read_output_buffer_node<'a: 'input>(buffer: Arc, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec { + executor.read_output_buffer(buffer).await.unwrap() +} + +pub struct CreateGpuSurfaceNode {} + +pub type WindowHandle = Arc>; + +#[node_macro::node_fn(CreateGpuSurfaceNode)] +async fn create_gpu_surface<'a: 'input, Io: ApplicationIo + Send + Sync>(editor_api: &'a EditorApi) -> WgpuSurface { + let canvas = editor_api.application_io.as_ref().unwrap().create_surface(); + let executor = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); + Arc::new(executor.create_surface(canvas).unwrap()) +} + +pub struct RenderTextureNode { + image: Image, + surface: Surface, + executor: EditorApi, +} + +#[derive(DynAny, Clone, Debug)] +pub struct ShaderInputFrame { + shader_input: Arc, + transform: DAffine2, +} + +#[node_macro::node_fn(RenderTextureNode)] +async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node, surface: WgpuSurface, executor: &'a WgpuExecutor) -> SurfaceFrame { + let surface_id = surface.surface_id; + let image = self.image.eval(footprint).await; + let transform = image.transform; + + executor.create_render_pass(footprint, image, surface).unwrap(); + + SurfaceFrame { surface_id, transform } +} + +pub struct UploadTextureNode { + executor: Executor, +} + +#[node_macro::node_fn(UploadTextureNode)] +async fn upload_texture<'a: 'input>(input: ImageFrame, executor: &'a WgpuExecutor) -> ShaderInputFrame { + let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); + let new_image = Image { + width: input.image.width, + height: input.image.height, + data: new_data, + base64_string: None, + }; + + let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); + + ShaderInputFrame { + shader_input: Arc::new(shader_input), + transform: input.transform, + } +} diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 3afa94de8b..59f5e5382d 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-proc-macros" publish = false version = "0.0.0" -rust-version = "1.70.0" +rust-version = "1.79" authors = ["Graphite Authors "] edition = "2021" readme = "../README.md" diff --git a/shell.nix b/shell.nix index 59ed310de4..305e75a778 100644 --- a/shell.nix +++ b/shell.nix @@ -41,7 +41,15 @@ in pkgs.nodejs pkgs.cargo pkgs.cargo-watch + pkgs.cargo-nextest + pkgs.cargo-expand pkgs.wasm-pack + pkgs.wasm-bindgen-cli + pkgs.vulkan-loader + pkgs.libxkbcommon + pkgs.llvm + pkgs.gcc-unwrapped.lib + pkgs.llvmPackages.libcxxStdenv pkgs.openssl pkgs.glib @@ -57,8 +65,8 @@ in ]; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [pkgs.openssl]; # Hacky way to run cago through Mold + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [pkgs.openssl pkgs.vulkan-loader pkgs.libxkbcommon pkgs.llvmPackages.libcxxStdenv pkgs.gcc-unwrapped.lib pkgs.llvm]; shellHook = '' alias cargo='mold --run cargo' ''; diff --git a/website/other/bezier-rs-demos/wasm/Cargo.toml b/website/other/bezier-rs-demos/wasm/Cargo.toml index 8a77aba84c..7131a27bf8 100644 --- a/website/other/bezier-rs-demos/wasm/Cargo.toml +++ b/website/other/bezier-rs-demos/wasm/Cargo.toml @@ -2,7 +2,7 @@ name = "bezier-rs-wasm" publish = false version = "0.0.0" -rust-version = "1.70.0" +rust-version = "1.79" authors = ["Graphite Authors "] edition = "2021" readme = "../../README.md"