Restore functionality of GPU infrastructure (#1797)

* Update gpu nodes to compile again

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

And libssl to nix shell

Fix graphene-cli and add half percision color format

Fix texture scaling

Remove vulkan executor

Fix compile errors

Improve execution request deduplication

* Fix warnings

* Fix graph compile issues

* Code review

* Remove test file

* Fix lint

* Wip make node futures send

* Make futures Send on non wasm targets

* Fix warnings

* Fix nested use of block_on

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert 2024-07-15 15:14:48 +02:00 committed by GitHub
parent 59a943f42f
commit 212f08c6c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 1572 additions and 1577 deletions

319
Cargo.lock generated
View file

@ -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",

View file

@ -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` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) 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"

View file

@ -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 <contact@graphite.rs>"]
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

View file

@ -410,78 +410,76 @@ mod test {
assert_eq!(layers_after_copy[5], shape_id);
}
#[test]
#[tokio::test]
/// This test will fail when you make changes to the underlying serialization format for a document.
fn check_if_demo_art_opens() {
futures::executor::block_on(async {
use crate::messages::layout::utility_types::widget_prelude::*;
async fn check_if_demo_art_opens() {
use crate::messages::layout::utility_types::widget_prelude::*;
let print_problem_to_terminal_on_failure = |value: &String| {
println!();
println!("-------------------------------------------------");
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
println!();
println!("DisplayDialogError details:");
println!();
println!("Description: {value}");
println!("-------------------------------------------------");
println!();
let print_problem_to_terminal_on_failure = |value: &String| {
println!();
println!("-------------------------------------------------");
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
println!();
println!("DisplayDialogError details:");
println!();
println!("Description: {value}");
println!("-------------------------------------------------");
println!();
panic!()
};
panic!()
};
init_logger();
let mut editor = Editor::create();
init_logger();
let mut editor = Editor::create();
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
//
// let files = [
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
// include_str!("../../demo-artwork/red-dress.graphite"),
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
// ];
// for (id, document_serialized_content) in files.iter().enumerate() {
// let document_name = format!("document {id}");
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
//
// let files = [
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
// include_str!("../../demo-artwork/red-dress.graphite"),
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
// ];
// for (id, document_serialized_content) in files.iter().enumerate() {
// let document_name = format!("document {id}");
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
assert_eq!(
document_serialized_content.lines().count(),
1,
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
);
assert_eq!(
document_serialized_content.lines().count(),
1,
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
);
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_serialized_content: document_serialized_content.into(),
});
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_serialized_content,
});
// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE)
.expect("submit_node_graph_evaluation failed");
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE, true)
.expect("submit_node_graph_evaluation failed");
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
print_problem_to_terminal_on_failure(value);
}
for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
print_problem_to_terminal_on_failure(value);
}
}
}
}
}
});
}
}
}

View file

@ -385,8 +385,6 @@ impl LayoutMessageHandler {
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
trace!("{layout_target:?} diff {diff:#?}");
let message = match layout_target {
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },

View file

@ -23,7 +23,7 @@ use graphene_core::*;
use graphene_std::application_io::RenderConfig;
use graphene_std::wasm_application_io::WasmEditorApi;
#[cfg(feature = "gpu")]
use {gpu_executor::*, graphene_core::application_io::SurfaceHandle, wgpu_executor::WgpuExecutor};
use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput};
use once_cell::sync::Lazy;
use std::collections::VecDeque;
@ -994,7 +994,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Uniform".to_string(),
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UniformNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode<_>")),
..Default::default()
},
DocumentNode {
@ -1038,7 +1038,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Storage".to_string(),
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::StorageNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode<_>")),
..Default::default()
},
DocumentNode {
@ -1082,7 +1082,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Output Buffer".to_string(),
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateOutputBufferNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode<_, _>")),
..Default::default()
},
DocumentNode {
@ -1134,12 +1134,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Compute Pass".to_string(),
inputs: vec![
NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
NodeInput::network(concrete!(PipelineLayout), 0),
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
NodeInput::network(concrete!(WgpuShaderInput), 2),
NodeInput::network(concrete!(gpu_executor::ComputePassDimensions), 3),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateComputePassNode<_, _, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateComputePassNode<_, _, _>")),
..Default::default()
},
DocumentNode {
@ -1160,12 +1160,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
default: NodeInput::network(concrete!(PipelineLayout), 0),
},
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
default: NodeInput::network(concrete!(WgpuShaderInput), 2),
},
DocumentInputType {
name: "In",
@ -1184,12 +1184,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNodeDefinition {
name: "CreatePipelineLayout",
category: "Gpu",
implementation: DocumentNodeImplementation::proto("gpu_executor::CreatePipelineLayoutNode<_, _, _, _>"),
implementation: DocumentNodeImplementation::proto("wgpu_executor::CreatePipelineLayoutNode<_, _, _>"),
inputs: vec![
DocumentInputType {
name: "ShaderHandle",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::ShaderHandle), 0),
default: NodeInput::network(concrete!(ShaderHandle), 0),
},
DocumentInputType {
name: "String",
@ -1199,12 +1199,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType {
name: "Bindgroup",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(gpu_executor::Bindgroup<WgpuExecutor>), 2),
default: NodeInput::network(concrete!(Bindgroup), 2),
},
DocumentInputType {
name: "ArcShaderInput",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 3),
default: NodeInput::network(concrete!(Arc<WgpuShaderInput>), 3),
},
],
outputs: vec![DocumentOutputType {
@ -1229,8 +1229,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Execute Compute Pipeline".to_string(),
inputs: vec![NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ExecuteComputePipelineNode<_>")),
inputs: vec![NodeInput::network(concrete!(CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ExecuteComputePipelineNode<_>")),
..Default::default()
},
DocumentNode {
@ -1273,8 +1273,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Read Output Buffer".to_string(),
inputs: vec![NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ReadOutputBufferNode<_, _>")),
inputs: vec![NodeInput::network(concrete!(Arc<WgpuShaderInput>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ReadOutputBufferNode<_, _>")),
..Default::default()
},
DocumentNode {
@ -1312,7 +1312,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Create Gpu Surface".to_string(),
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateGpuSurfaceNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
..Default::default()
},
DocumentNode {
@ -1350,12 +1350,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
name: "Render Texture".to_string(),
manual_composition: Some(concrete!(Footprint)),
inputs: vec![
NodeInput::network(concrete!(ShaderInputFrame<WgpuExecutor>), 0),
NodeInput::network(concrete!(Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface<'_>>>), 0),
NodeInput::network(concrete!(ShaderInputFrame), 0),
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
NodeInput::node(NodeId(0), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::RenderTextureNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::RenderTextureNode<_, _, _>")),
..Default::default()
},
]
@ -1399,14 +1400,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
name: "Upload Texture".to_string(),
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UploadTextureNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode<_>")),
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")),
..Default::default()
},
]

View file

@ -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) })
}

View file

@ -287,6 +287,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
self.persistent_data.font_cache.insert(font, preview_url, data, is_default);
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
for document_id in self.document_ids.iter() {
let _ = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
true,
);
}
if self.active_document_mut().is_some() {
responses.add(NodeGraphMessage::RunDocumentGraph);
@ -571,7 +578,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
bounds,
transparent_background,
} => {
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render no existent Document");
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
let export_config = ExportConfig {
file_name,
file_type,
@ -591,8 +598,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
PortfolioMessage::SubmitGraphRender { document_id } => {
let result = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(&document_id).expect("Tried to render no existent Document"),
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
false,
);
if let Err(description) = result {
@ -666,8 +674,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
impl PortfolioMessageHandler {
pub fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect_node(node_path)
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
self.executor.introspect_node(node_path).await
}
pub fn document(&self, document_id: DocumentId) -> Option<&DocumentMessageHandler> {

View file

@ -4,6 +4,7 @@ use crate::messages::portfolio::document::node_graph::document_node_types::wrap_
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use futures::lock::Mutex;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
@ -24,11 +25,7 @@ use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, ResolvedDocumentNodeTypes};
use glam::{DAffine2, DVec2, UVec2};
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::rc::Rc;
use once_cell::sync::Lazy;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::Arc;
@ -43,8 +40,6 @@ pub struct NodeRuntime {
recompile_graph: bool,
editor_api: Arc<WasmEditorApi>,
graph_hash: Option<u64>,
node_graph_errors: GraphErrors,
resolved_types: ResolvedDocumentNodeTypes,
monitor_nodes: Vec<Vec<NodeId>>,
@ -62,6 +57,7 @@ pub struct NodeRuntime {
/// Messages passed from the editor thread to the node runtime thread.
pub enum NodeRuntimeMessage {
GraphUpdate(NodeNetwork),
ExecutionRequest(ExecutionRequest),
FontCacheUpdate(FontCache),
ImaginatePreferencesUpdate(ImaginatePreferences),
@ -79,7 +75,6 @@ pub struct ExportConfig {
pub struct ExecutionRequest {
execution_id: u64,
graph: NodeNetwork,
render_config: RenderConfig,
}
@ -90,13 +85,18 @@ pub struct ExecutionResponse {
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_vector_modify: HashMap<NodeId, VectorData>,
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
transform: DAffine2,
}
pub struct CompilationResponse {
result: Result<(), String>,
resolved_types: ResolvedDocumentNodeTypes,
node_graph_errors: GraphErrors,
transform: DAffine2,
}
pub enum NodeGraphUpdate {
ExecutionResponse(ExecutionResponse),
CompilationResponse(CompilationResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
}
@ -104,7 +104,11 @@ pub enum NodeGraphUpdate {
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender {
fn send_generation_response(&self, response: ExecutionResponse) {
fn send_generation_response(&self, response: CompilationResponse) {
self.0.send(NodeGraphUpdate::CompilationResponse(response)).expect("Failed to send response")
}
fn send_execution_response(&self, response: ExecutionResponse) {
self.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect("Failed to send response")
}
}
@ -115,9 +119,7 @@ impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
}
}
thread_local! {
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
}
pub(crate) static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
impl NodeRuntime {
pub fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
@ -137,7 +139,6 @@ impl NodeRuntime {
}
.into(),
graph_hash: None,
node_graph_errors: Vec::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
monitor_nodes: Vec::new(),
@ -150,12 +151,22 @@ impl NodeRuntime {
}
pub async fn run(&mut self) {
let mut requests = self.receiver.try_iter().collect::<Vec<_>>();
// TODO: Currently we still render the document after we submit the node graph execution request.
// This should be avoided in the future.
requests.reverse();
requests.dedup_by(|a, b| matches!(a, NodeRuntimeMessage::ExecutionRequest(_)) && matches!(b, NodeRuntimeMessage::ExecutionRequest(_)));
requests.reverse();
// TODO: Currently we still render the document after we submit the node graph execution request. This should be avoided in the future.
let mut font = None;
let mut imaginate = None;
let mut graph = None;
let mut execution = None;
for request in self.receiver.try_iter() {
match request {
NodeRuntimeMessage::GraphUpdate(_) => graph = Some(request),
NodeRuntimeMessage::ExecutionRequest(_) => execution = Some(request),
NodeRuntimeMessage::FontCacheUpdate(_) => font = Some(request),
NodeRuntimeMessage::ImaginatePreferencesUpdate(_) => imaginate = Some(request),
}
}
let requests = [font, imaginate, graph, execution].into_iter().flatten();
for request in requests {
match request {
NodeRuntimeMessage::FontCacheUpdate(font_cache) => {
@ -178,25 +189,31 @@ impl NodeRuntime {
.into();
self.recompile_graph = true;
}
NodeRuntimeMessage::ExecutionRequest(ExecutionRequest {
execution_id, graph, render_config, ..
}) => {
NodeRuntimeMessage::GraphUpdate(graph) => {
self.node_graph_errors.clear();
let result = self.update_network(graph).await;
self.sender.send_generation_response(CompilationResponse {
result,
resolved_types: self.resolved_types.clone(),
node_graph_errors: self.node_graph_errors.clone(),
});
self.recompile_graph = true;
}
NodeRuntimeMessage::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
let transform = render_config.viewport.transform;
let result = self.execute_network(graph, render_config).await;
let result = self.execute_network(render_config).await;
let mut responses = VecDeque::new();
self.process_monitor_nodes(&mut responses);
self.sender.send_generation_response(ExecutionResponse {
self.sender.send_execution_response(ExecutionResponse {
execution_id,
result,
responses,
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_vector_modify: self.vector_modify.clone(),
new_upstream_transforms: self.upstream_transforms.clone(),
resolved_types: self.resolved_types.clone(),
node_graph_errors: core::mem::take(&mut self.node_graph_errors),
transform,
});
}
@ -204,7 +221,7 @@ impl NodeRuntime {
}
}
async fn execute_network(&mut self, graph: NodeNetwork, render_config: RenderConfig) -> Result<TaggedValue, String> {
async fn update_network(&mut self, graph: NodeNetwork) -> Result<(), String> {
if self.editor_api.application_io.is_none() {
self.editor_api = WasmEditorApi {
application_io: Some(WasmApplicationIo::new().await.into()),
@ -215,44 +232,31 @@ impl NodeRuntime {
.into();
}
let editor_api = &self.editor_api;
// Required to ensure that the appropriate proto nodes are reinserted when the Editor API changes.
let mut graph_input_hash = DefaultHasher::new();
editor_api.font_cache.hash(&mut graph_input_hash);
let _font_hash_code = graph_input_hash.finish();
graph.hash(&mut graph_input_hash);
let hash_code = graph_input_hash.finish();
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
self.monitor_nodes = scoped_network
.recursive_nodes()
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
if self.graph_hash != Some(hash_code) {
self.graph_hash = None;
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) {
Ok(network) => network,
Err(e) => return Err(e),
};
assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
if let Err(e) = self.executor.update(proto_network).await {
self.node_graph_errors = e;
}
self.resolved_types = self.executor.document_node_types();
if self.graph_hash.is_none() || self.recompile_graph {
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
self.monitor_nodes = scoped_network
.recursive_nodes()
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
.collect::<Vec<_>>();
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
let c = Compiler {};
let proto_network = match c.compile_single(scoped_network) {
Ok(network) => network,
Err(e) => return Err(e),
};
assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
if let Err(e) = self.executor.update(proto_network).await {
self.node_graph_errors = e;
} else {
self.graph_hash = Some(hash_code);
}
self.resolved_types = self.executor.document_node_types();
}
Ok(())
}
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
use graph_craft::graphene_compiler::Executor;
let result = match self.executor.input_type() {
@ -266,16 +270,6 @@ impl NodeRuntime {
Err(e) => return Err(e),
};
// if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
// let old_id = self.canvas_cache.insert(path.to_vec(), surface_id);
// if let Some(old_id) = old_id {
// if old_id != surface_id {
// if let Some(io) = self.wasm_io.as_ref() {
// io.destroy_surface(old_id)
// }
// }
// }
// }
Ok(result)
}
@ -369,42 +363,24 @@ impl NodeRuntime {
}
}
pub fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
NODE_RUNTIME
.try_with(|runtime| {
let runtime = runtime.try_borrow();
if let Ok(ref runtime) = runtime {
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path).flatten();
}
}
None
})
.unwrap_or(None)
pub async fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
let runtime = NODE_RUNTIME.lock().await;
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path).flatten();
}
None
}
pub async fn run_node_graph() {
let result = NODE_RUNTIME.try_with(|runtime| {
let runtime = runtime.clone();
async move {
let mut runtime = runtime.try_borrow_mut();
if let Ok(ref mut runtime) = runtime {
if let Some(ref mut runtime) = runtime.as_mut() {
runtime.run().await;
}
}
}
});
if let Ok(result) = result {
result.await;
let mut runtime = NODE_RUNTIME.lock().await;
if let Some(ref mut runtime) = runtime.as_mut() {
runtime.run().await;
}
}
pub fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
NODE_RUNTIME.with(|node_runtime| {
let mut node_runtime = node_runtime.borrow_mut();
node_runtime.replace(runtime)
})
pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
let mut node_runtime = NODE_RUNTIME.lock().await;
node_runtime.replace(runtime)
}
#[derive(Debug)]
@ -412,6 +388,7 @@ pub struct NodeGraphExecutor {
sender: Sender<NodeRuntimeMessage>,
receiver: Receiver<NodeGraphUpdate>,
futures: HashMap<u64, ExecutionContext>,
node_graph_hash: u64,
}
#[derive(Debug, Clone)]
@ -423,32 +400,29 @@ impl Default for NodeGraphExecutor {
fn default() -> Self {
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (response_sender, response_receiver) = std::sync::mpsc::channel();
replace_node_runtime(NodeRuntime::new(request_receiver, response_sender));
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));
Self {
futures: Default::default(),
sender: request_sender,
receiver: response_receiver,
node_graph_hash: 0,
}
}
}
impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, network: NodeNetwork, render_config: RenderConfig) -> u64 {
fn queue_execution(&self, render_config: RenderConfig) -> u64 {
let execution_id = generate_uuid();
let request = ExecutionRequest {
graph: network,
execution_id,
render_config,
};
let request = ExecutionRequest { execution_id, render_config };
self.sender.send(NodeRuntimeMessage::ExecutionRequest(request)).expect("Failed to send generation request");
execution_id
}
pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
introspect_node(path)
pub async fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
introspect_node(path).await
}
pub fn update_font_cache(&self, font_cache: FontCache) {
@ -473,7 +447,7 @@ impl NodeGraphExecutor {
return None;
};
let introspection_node = find_node(wrapped_network)?;
let introspection = self.introspect_node(&[node_path, &[introspection_node]].concat())?;
let introspection = futures::executor::block_on(self.introspect_node(&[node_path, &[introspection_node]].concat()))?;
let Some(downcasted): Option<&T> = <dyn std::any::Any>::downcast_ref(introspection.as_ref()) else {
log::warn!("Failed to downcast type for introspection");
return None;
@ -482,9 +456,13 @@ impl NodeGraphExecutor {
}
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2) -> Result<(), String> {
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, ignore_hash: bool) -> Result<(), String> {
// Get the node graph layer
let network = document.network().clone();
let network_hash = document.network().current_hash();
if network_hash != self.node_graph_hash || ignore_hash {
self.node_graph_hash = network_hash;
self.sender.send(NodeRuntimeMessage::GraphUpdate(document.network.clone())).map_err(|e| e.to_string())?;
}
let render_config = RenderConfig {
viewport: Footprint {
@ -502,7 +480,7 @@ impl NodeGraphExecutor {
};
// Execute the node graph
let execution_id = self.queue_execution(network, render_config);
let execution_id = self.queue_execution(render_config);
self.futures.insert(execution_id, ExecutionContext { export_config: None });
@ -537,7 +515,8 @@ impl NodeGraphExecutor {
export_config.size = size;
// Execute the node graph
let execution_id = self.queue_execution(network, render_config);
self.sender.send(NodeRuntimeMessage::GraphUpdate(network)).map_err(|e| e.to_string())?;
let execution_id = self.queue_execution(render_config);
let execution_context = ExecutionContext { export_config: Some(export_config) };
self.futures.insert(execution_id, execution_context);
@ -585,13 +564,10 @@ impl NodeGraphExecutor {
new_click_targets,
new_vector_modify,
new_upstream_transforms,
resolved_types,
node_graph_errors,
transform,
} = execution_response;
responses.extend(existing_responses.into_iter().map(Into::into));
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
responses.add(NodeGraphMessage::SendGraph);
responses.add(OverlaysMessage::Draw);
@ -616,6 +592,22 @@ impl NodeGraphExecutor {
self.process_node_graph_output(node_graph_output, transform, responses)?
}
}
NodeGraphUpdate::CompilationResponse(execution_response) => {
let CompilationResponse {
resolved_types,
node_graph_errors,
result,
} = execution_response;
if let Err(e) = result {
// Clear the click targets while the graph is in an un-renderable state
document.metadata.update_from_monitor(HashMap::new(), HashMap::new());
log::trace!("{e}");
return Err("Node graph evaluation failed".to_string());
};
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));
}

View file

@ -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

View file

@ -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 <contact@graphite.rs>"]
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 }

View file

@ -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,

View file

@ -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)
}

View file

@ -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 <contact@graphite.rs>"]
description = "Computational geometry algorithms for Bézier segments and shapes useful in the context of 2D graphics"

View file

@ -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 <contact@graphite.rs>"]
description = "An Any trait that works for arbitrary lifetimes"

View file

@ -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<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + Send + Sync + '_ {
type Static = dyn core::future::Future<Output = T::Static> + Send + Sync;
}
unsafe impl<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + Send + '_ {
type Static = dyn core::future::Future<Output = T::Static> + Send;
}
unsafe impl<T: StaticTypeSized> StaticType for dyn core::future::Future<Output = T> + '_ {
type Static = dyn core::future::Future<Output = T::Static>;
}
@ -332,3 +338,32 @@ fn simple_downcast_panic() {
let x = Box::new(3_i32) as Box<dyn DynAny>;
assert_eq!(*downcast::<u32>(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<T: Send> WasmNotSend for T {}
#[cfg(target_arch = "wasm32")]
impl<T> 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<T: Sync> WasmNotSync for T {}
#[cfg(target_arch = "wasm32")]
impl<T> WasmNotSync for T {}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "alloc")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "alloc")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;

View file

@ -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

View file

@ -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<ProtoNetwork>, inputs: Vec<Type>, outputs: Vec<Type>, io: ShaderIO) -> Result<Shader, reqwest::Error> {
let client = reqwest::Client::new();

View file

@ -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;

View file

@ -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 }

View file

@ -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<Surface> {
pub surface_id: SurfaceId,
pub surface: Surface,
}
// #[cfg(target_arch = "wasm32")]
// unsafe impl<T: dyn_any::WasmNotSend> Send for SurfaceHandle<T> {}
// #[cfg(target_arch = "wasm32")]
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
type Static = SurfaceHandle<T>;
@ -83,7 +104,10 @@ impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
}
}*/
#[cfg(target_arch = "wasm32")]
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>;
#[cfg(not(target_arch = "wasm32"))]
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>;
pub trait ApplicationIo {
type Surface;

View file

@ -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<VectorData>),
/// A bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrame<Color>),
/// 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<Stack, GraphicElement> {
}
#[node_fn(ConstructLayerNode)]
async fn construct_layer<Data: Into<GraphicElement>, Fut1: Future<Output = GraphicGroup>, Fut2: Future<Output = Data>>(
async fn construct_layer<Data: Into<GraphicElement> + Send>(
footprint: crate::transform::Footprint,
mut stack: impl Node<crate::transform::Footprint, Output = Fut1>,
graphic_element: impl Node<crate::transform::Footprint, Output = Fut2>,
mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>,
graphic_element: impl Node<crate::transform::Footprint, Output = Data>,
) -> 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<Contents, Label, Location, Dimensions, Backgrou
}
#[node_fn(ConstructArtboardNode)]
async fn construct_artboard<Fut: Future<Output = GraphicGroup>>(
async fn construct_artboard(
mut footprint: Footprint,
contents: impl Node<Footprint, Output = Fut>,
contents: impl Node<Footprint, Output = GraphicGroup>,
label: String,
location: IVec2,
dimensions: IVec2,
@ -189,11 +192,7 @@ pub struct AddArtboardNode<ArtboardGroup, Artboard> {
}
#[node_fn(AddArtboardNode)]
async fn add_artboard<Data: Into<Artboard>, Fut1: Future<Output = ArtboardGroup>, Fut2: Future<Output = Data>>(
footprint: Footprint,
artboards: impl Node<Footprint, Output = Fut1>,
artboard: impl Node<Footprint, Output = Fut2>,
) -> ArtboardGroup {
async fn add_artboard<Data: Into<Artboard> + Send>(footprint: Footprint, artboards: impl Node<Footprint, Output = ArtboardGroup>, artboard: impl Node<Footprint, Output = Data>) -> ArtboardGroup {
let artboard = self.artboard.eval(footprint).await;
let mut artboards = self.artboards.eval(footprint).await;
@ -229,6 +228,25 @@ impl From<GraphicGroup> for GraphicElement {
GraphicElement::GraphicGroup(graphic_group)
}
}
impl From<SurfaceFrame> for GraphicElement {
fn from(surface: SurfaceFrame) -> Self {
GraphicElement::Surface(surface)
}
}
impl From<alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>> for GraphicElement {
fn from(surface: alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>) -> Self {
let surface_id = surface.surface_handle.surface_id;
let transform = surface.transform;
GraphicElement::Surface(SurfaceFrame { surface_id, transform })
}
}
impl From<SurfaceHandleFrame<HtmlCanvasElement>> for GraphicElement {
fn from(surface: SurfaceHandleFrame<HtmlCanvasElement>) -> 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<GraphicElement>;
@ -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))
}
}
}
}

View file

@ -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#"<foreignObject width="{}" height="{}" transform="matrix({})"><div data-canvas-placeholder="canvas{}"></div></foreignObject>"#,
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<ClickTarget>) {}
fn contains_artboard(&self) -> bool {
false
}
}
impl GraphicElementRendered for ImageFrame<Color> {
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(),
}
}
}

View file

@ -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<web_sys::HtmlCanvasElement>;
#[cfg(feature = "wasm")]
pub type WasmSurfaceHandleFrame = application_io::SurfaceHandleFrame<web_sys::HtmlCanvasElement>;
pub use dyn_any::{WasmNotSend, WasmNotSync};

View file

@ -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<T, CachedNode> {
cache: Cell<Option<T>>,
cache: Arc<Mutex<Option<T>>>,
node: CachedNode,
}
impl<'i, 'o: 'i, T: 'i + Clone + 'o, CachedNode: 'i> Node<'i, ()> for MemoNode<T, CachedNode>
impl<'i, 'o: 'i, T: 'i + Clone + 'o + WasmNotSend, CachedNode: 'i> Node<'i, ()> for MemoNode<T, CachedNode>
where
CachedNode: for<'any_input> Node<'any_input, ()>,
for<'a> <CachedNode as Node<'a, ()>>::Output: core::future::Future<Output = T> + 'a,
for<'a> <CachedNode as Node<'a, ()>>::Output: core::future::Future<Output = T> + 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<Box<dyn Future<Output = T> + 'i>>;
fn eval(&'i self, input: ()) -> Pin<Box<dyn Future<Output = T> + '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<T, CachedNode> MemoNode<T, CachedNode> {
pub const fn new(node: CachedNode) -> MemoNode<T, CachedNode> {
MemoNode { cache: Cell::new(None), node }
pub fn new(node: CachedNode) -> MemoNode<T, CachedNode> {
MemoNode { cache: Default::default(), node }
}
}
@ -50,41 +53,43 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
/// use with caution.
#[derive(Default)]
pub struct ImpureMemoNode<I, T, CachedNode> {
cache: Cell<Option<T>>,
cache: Arc<Mutex<Option<T>>>,
node: CachedNode,
_phantom: std::marker::PhantomData<I>,
}
impl<'i, 'o: 'i, I: 'i, T: 'i + Clone + 'o, CachedNode: 'i> Node<'i, I> for ImpureMemoNode<I, T, CachedNode>
impl<'i, 'o: 'i, I: 'i, T: 'i + Clone + 'o + WasmNotSend, CachedNode: 'i> Node<'i, I> for ImpureMemoNode<I, T, CachedNode>
where
CachedNode: for<'any_input> Node<'any_input, I>,
for<'a> <CachedNode as Node<'a, I>>::Output: core::future::Future<Output = T> + 'a,
for<'a> <CachedNode as Node<'a, I>>::Output: core::future::Future<Output = T> + 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<Box<dyn Future<Output = T> + 'i>>;
fn eval(&'i self, input: I) -> Pin<Box<dyn Future<Output = T> + '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<T, I, CachedNode> ImpureMemoNode<I, T, CachedNode> {
pub const fn new(node: CachedNode) -> ImpureMemoNode<I, T, CachedNode> {
pub fn new(node: CachedNode) -> ImpureMemoNode<I, T, CachedNode> {
ImpureMemoNode {
cache: Cell::new(None),
cache: Default::default(),
node,
_phantom: core::marker::PhantomData,
}
@ -102,37 +107,38 @@ pub struct IORecord<I, O> {
/// Caches the output of the last graph evaluation for introspection
#[derive(Default)]
pub struct MonitorNode<I, T, N> {
io: Cell<Option<Arc<IORecord<I, T>>>>,
#[allow(clippy::type_complexity)]
io: Arc<Mutex<Option<Arc<IORecord<I, T>>>>>,
node: N,
}
#[cfg(feature = "alloc")]
impl<'i, 'a: 'i, T, I, N> Node<'i, I> for MonitorNode<I, T, N>
impl<'i, T, I, N> Node<'i, I> for MonitorNode<I, T, N>
where
I: Clone + 'static,
<N as Node<'i, I>>::Output: Future<Output = T>,
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<Output = T> + WasmNotSend> + 'i,
{
type Output = Pin<Box<dyn Future<Output = T> + '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<Arc<dyn core::any::Any>> {
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<dyn core::any::Any>)
}
}
#[cfg(feature = "alloc")]
impl<I, T, N> MonitorNode<I, T, N> {
pub const fn new(node: N) -> MonitorNode<I, T, N> {
MonitorNode { io: Cell::new(None), node }
pub fn new(node: N) -> MonitorNode<I, T, N> {
MonitorNode { io: Arc::new(Mutex::new(None)), node }
}
}

View file

@ -379,7 +379,7 @@ pub struct IntoNode<I, O> {
#[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()
}

View file

@ -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<Color> 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<Color> 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<SRGBA8> 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),

View file

@ -1,5 +1,3 @@
use core::future::Future;
use dyn_any::StaticType;
use glam::DAffine2;
@ -27,6 +25,12 @@ pub trait Transform {
}
}
impl<T: Transform> 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<P: Pixel> Transform for ImageFrame<P> {
self.local_pivot(pivot)
}
}
impl<P: Pixel> Transform for &ImageFrame<P> {
fn transform(&self) -> DAffine2 {
self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
(*self).local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrame<P> {
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<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
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<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
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<Fut: Future>(
pub(crate) async fn transform_vector_data<T: TransformMut>(
mut footprint: Footprint,
transform_target: impl Node<Footprint, Output = Fut>,
transform_target: impl Node<Footprint, Output = T>,
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;

View file

@ -337,7 +337,7 @@ impl HandleId {
}
#[cfg(test)]
fn assert_subpath_eq(generated: &Vec<bezier_rs::Subpath<PointId>>, expected: &[bezier_rs::Subpath<PointId>]) {
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());

View file

@ -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();

View file

@ -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<Points, Instance, RandomScaleMin, RandomScaleMax, Random
}
#[node_macro::node_fn(CopyToPoints)]
async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + TransformMut, FP: Future<Output = VectorData>, FI: Future<Output = I>>(
async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
footprint: Footprint,
points: impl Node<Footprint, Output = FP>,
instance: impl Node<Footprint, Output = FI>,
points: impl Node<Footprint, Output = VectorData>,
instance: impl Node<Footprint, Output = I>,
random_scale_min: f64,
random_scale_max: f64,
random_scale_bias: f64,
@ -280,14 +279,14 @@ pub struct SamplePoints<VectorData, Spacing, StartOffset, StopOffset, AdaptiveSp
}
#[node_macro::node_fn(SamplePoints)]
async fn sample_points<FV: Future<Output = VectorData>, FL: Future<Output = Vec<f64>>>(
async fn sample_points(
footprint: Footprint,
mut vector_data: impl Node<Footprint, Output = FV>,
mut vector_data: impl Node<Footprint, Output = VectorData>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
adaptive_spacing: bool,
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = FL>,
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = Vec<f64>>,
) -> 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<Source, Target, StartIndex, Time> {
}
#[node_macro::node_fn(MorphNode)]
async fn morph<SourceFuture: Future<Output = VectorData>, TargetFuture: Future<Output = VectorData>>(
footprint: Footprint,
source: impl Node<Footprint, Output = SourceFuture>,
target: impl Node<Footprint, Output = TargetFuture>,
start_index: u32,
time: f64,
) -> VectorData {
async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = VectorData>, target: impl Node<Footprint, Output = VectorData>, 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<VectorData> {
}
#[node_macro::node_fn(AreaNode)]
async fn area_node<Fut: Future<Output = VectorData>>(empty: (), vector_data: impl Node<Footprint, Output = Fut>) -> f64 {
async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
let vector_data = self.vector_data.eval(Footprint::default()).await;
let mut area = 0.;
@ -534,7 +527,7 @@ pub struct CentroidNode<VectorData, CentroidType> {
}
#[node_macro::node_fn(CentroidNode)]
async fn centroid_node<Fut: Future<Output = VectorData>>(empty: (), vector_data: impl Node<Footprint, Output = Fut>, centroid_type: CentroidType) -> DVec2 {
async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>, 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<N>
where
N: Node<'i, T>,
N: Node<'i, T, Output: Send>,
{
type Output = Pin<Box<dyn core::future::Future<Output = N::Output> + 'i>>;
type Output = Pin<Box<dyn core::future::Future<Output = N::Output> + '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 })
}
}

View file

@ -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 }

View file

@ -1,5 +1,5 @@
use gpu_executor::ShaderIO;
use graph_craft::{proto::ProtoNetwork, Type};
use wgpu_executor::ShaderIO;
use std::io::Write;

View file

@ -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<Box<dyn Future<Output = Result<Vec<u8>>>>>;
#[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<TextureView>(&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<Self::ShaderHandle>;
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self>>;
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self>>;
fn create_texture_buffer<T: ToTextureBuffer>(&self, data: T, options: TextureBufferOptions) -> Result<ShaderInput<Self>>;
fn create_texture_view(&self, texture: ShaderInput<Self>) -> Result<ShaderInput<Self>>;
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self>>;
fn create_compute_pass(&self, layout: &PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self>>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer>;
fn create_render_pass(&self, texture: Arc<ShaderInput<Self>>, canvas: Arc<SurfaceHandle<Self::Surface<'_>>>) -> Result<()>;
fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>;
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self>>) -> ReadBackFuture;
fn create_surface(&self, window: SurfaceHandle<Self::Window>) -> Result<SurfaceHandle<Self::Surface<'_>>>;
}
// pub trait SpirVCompiler {
// fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result<Shader>;
// }
#[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<Self::ShaderHandle> {
todo!()
}
fn create_uniform_buffer<T: ToUniformBuffer>(&self, _data: T) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_storage_buffer<T: ToStorageBuffer>(&self, _data: T, _options: StorageBufferOptions) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_texture_buffer<T: ToTextureBuffer>(&self, _data: T, _options: TextureBufferOptions) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_output_buffer(&self, _len: usize, _ty: Type, _cpu_readable: bool) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_compute_pass(&self, _layout: &PipelineLayout<Self>, _read_back: Option<Arc<ShaderInput<Self>>>, _instances: ComputePassDimensions) -> Result<Self::CommandBuffer> {
todo!()
}
fn execute_compute_pipeline(&self, _encoder: Self::CommandBuffer) -> Result<()> {
todo!()
}
fn create_render_pass(&self, _texture: Arc<ShaderInput<Self>>, _canvas: Arc<SurfaceHandle<Self::Surface<'_>>>) -> Result<()> {
todo!()
}
fn read_output_buffer(&self, _buffer: Arc<ShaderInput<Self>>) -> ReadBackFuture {
todo!()
}
fn create_texture_view(&self, _texture: ShaderInput<Self>) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_surface(&self, _window: SurfaceHandle<Self::Window>) -> Result<SurfaceHandle<Self::Surface<'_>>> {
todo!()
}
}
type AbstractShaderInput = ShaderInput<DummyExecutor>;
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
/// All the possible inputs to a shader.
pub enum ShaderInput<E: GpuExecutor + ?Sized> {
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<E: 'static> StaticType for ShaderInput<E>
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<E: GpuExecutor> ShaderInput<E> {
pub fn binding(&self) -> Option<BindingType<E>> {
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<AbstractShaderInput>,
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<E: GpuExecutor + ?Sized> {
pub buffers: Vec<Arc<ShaderInput<E>>>,
}
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for Bindgroup<E>
where
E::Static: GpuExecutor,
{
type Static = Bindgroup<E::Static>;
}
/// A struct representing a compute pipeline.
pub struct PipelineLayout<E: GpuExecutor + ?Sized> {
pub shader: Arc<E::ShaderHandle>,
pub entry_point: String,
pub bind_group: Arc<Bindgroup<E>>,
pub output_buffer: Arc<ShaderInput<E>>,
}
impl<E: GpuExecutor + ?Sized> Clone for PipelineLayout<E> {
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<E: GpuExecutor + ?Sized + StaticType> StaticType for PipelineLayout<E>
where
E::Static: GpuExecutor,
{
type Static = PipelineLayout<E::Static>;
}
/// Extracts arguments from the function arguments and wraps them in a node.
pub struct ShaderInputNode<T> {
data: T,
}
impl<'i, T: 'i> Node<'i, ()> for ShaderInputNode<T> {
type Output = &'i T;
fn eval(&'i self, _: ()) -> Self::Output {
&self.data
}
}
impl<T> ShaderInputNode<T> {
pub fn new(data: T) -> Self {
Self { data }
}
}
pub struct UniformNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(UniformNode)]
async fn uniform_node<'a: 'input, T: ToUniformBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput<E> {
executor.create_uniform_buffer(data).unwrap()
}
pub struct StorageNode<Executor> {
executor: Executor,
}
#[node_macro::node_fn(StorageNode)]
async fn storage_node<'a: 'input, T: ToStorageBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput<E> {
executor
.create_storage_buffer(
data,
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
cpu_readable: false,
storage: true,
},
)
.unwrap()
}
pub struct PushNode<Value> {
value: Value,
}
#[node_macro::node_fn(PushNode)]
async fn push_node<T>(mut vec: Vec<T>, value: T) {
vec.push(value);
}
pub struct CreateOutputBufferNode<Executor, Ty> {
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<ShaderInput<E>> {
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
}
pub struct CreateComputePassNode<Executor, Output, Instances> {
executor: Executor,
output: Output,
instances: Instances,
}
#[node_macro::node_fn(CreateComputePassNode)]
async fn create_compute_pass_node<'a: 'input, E: 'a + GpuExecutor>(layout: PipelineLayout<E>, executor: &'a E, output: ShaderInput<E>, 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<ShaderInput<_E>>) -> PipelineLayout<_E> {
PipelineLayout {
shader: shader.into(),
entry_point,
bind_group: bind_group.into(),
output_buffer,
}
}
pub struct ExecuteComputePipelineNode<Executor> {
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, ComputePass> {
executor: Executor,
_compute_pass: ComputePass,
}
#[node_macro::node_fn(ReadOutputBufferNode)]
async fn read_output_buffer_node<'a: 'input, E: 'a + GpuExecutor>(buffer: Arc<ShaderInput<E>>, executor: &'a E, _compute_pass: ()) -> Vec<u8> {
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<Window = Io::Surface>, Io: ApplicationIo<Executor = E> + 'input>(editor_api: &'a EditorApi<Io>) -> Arc<SurfaceHandle<E::Surface<'a>>> {
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, EditorApi> {
surface: Surface,
executor: EditorApi,
}
pub struct ShaderInputFrame<E: GpuExecutor + ?Sized> {
shader_input: Arc<ShaderInput<E>>,
transform: DAffine2,
}
impl<E: GpuExecutor + ?Sized> Clone for ShaderInputFrame<E> {
fn clone(&self) -> Self {
Self {
shader_input: self.shader_input.clone(),
transform: self.transform,
}
}
}
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for ShaderInputFrame<E>
where
E::Static: GpuExecutor,
{
type Static = ShaderInputFrame<E::Static>;
}
#[node_macro::node_fn(RenderTextureNode)]
async fn render_texture_node<'a: 'input, E: 'a + GpuExecutor>(image: ShaderInputFrame<E>, surface: Arc<SurfaceHandle<E::Surface<'input>>>, 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<E> {
executor: E,
}
#[node_macro::node_fn(UploadTextureNode)]
async fn upload_texture<'a: 'input, E: 'a + GpuExecutor>(input: ImageFrame<Color>, executor: &'a E) -> ShaderInputFrame<E> {
let shader_input = executor.create_texture_buffer(input.image, TextureBufferOptions::Texture).unwrap();
ShaderInputFrame {
shader_input: Arc::new(shader_input),
transform: input.transform,
}
}

View file

@ -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]

View file

@ -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<dyn DynAny> - 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<T: AsRef<U>, U>(pub T, PhantomData<U>);
pub struct UpcastAsRefNode<T: AsRef<U> + Sync + Send, U: Sync + Send>(pub T, PhantomData<U>);
impl<'i, T: 'i + AsRef<U>, U: 'i + StaticType> Node<'i, DAny<'i>> for UpcastAsRefNode<T, U> {
impl<'i, T: 'i + AsRef<U> + Sync + Send, U: 'i + StaticType + Sync + Send> Node<'i, DAny<'i>> for UpcastAsRefNode<T, U> {
type Output = FutureAny<'i>;
#[inline(always)]
fn eval(&'i self, _: DAny<'i>) -> Self::Output {
@ -237,7 +236,7 @@ impl<'i, T: 'i + AsRef<U>, U: 'i + StaticType> Node<'i, DAny<'i>> for UpcastAsRe
}
}
impl<T: AsRef<U>, U> UpcastAsRefNode<T, U> {
impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
pub const fn new(value: T) -> UpcastAsRefNode<T, U> {
UpcastAsRefNode(value, PhantomData)
}

View file

@ -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<dyn DynAny<'a> + 'a>;
pub trait Executor<I, O> {
fn execute(&self, input: I) -> LocalFuture<Result<O, Box<dyn Error>>>;

View file

@ -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);
}

View file

@ -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<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
pub type LocalFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
#[cfg(not(target_arch = "wasm32"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_arch = "wasm32")]
pub type Any<'n> = Box<dyn DynAny<'n> + '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<TypeErasedNode<'n>>;
pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::rc::Rc<NodeContainer>;
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> 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));
}
}

View file

@ -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<WgpuExecutor>,
#[cfg(not(target_arch = "wasm32"))]
windows: Mutex<Vec<Arc<winit::window::Window>>>,
// #[cfg(not(target_arch = "wasm32"))]
// windows: Mutex<Vec<Arc<winit::window::Window>>>,
pub resources: HashMap<String, Arc<[u8]>>,
}
@ -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<winit::window::Window>;
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<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
}) 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<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
}) 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<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
Ok(Box::pin(async move { Ok(data.clone()) }) as ResourceFuture)
}
_ => Err(ApplicationError::NotFound),
}
}
}
#[cfg(target_arch = "wasm32")]
pub type WasmSurfaceHandle = SurfaceHandle<HtmlCanvasElement>;
#[cfg(target_arch = "wasm32")]
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<HtmlCanvasElement>;
pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<wgpu_executor::Window>;

View file

@ -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",

View file

@ -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<dyn Error>> {
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<dyn Error>> {
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<DynamicExecutor, Box<dyn Error>> {
// let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document");
// let document = serde_json::from_value::<Document>(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<WasmEditorApi>) -> Result<DynamicExecutor, Box<dyn Error>> {
let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document");
let network = serde_json::from_value::<NodeNetwork>(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<WasmEditorApi>) -> 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()
}
}

View file

@ -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 }

View file

@ -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<I, O, Node> {
_o: PhantomData<O>,
}
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::<N>();
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<I, O, Node> {
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<I, O, Node> {
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: Node,
}
impl<'i, T: 'i, N: Node<'i, T>> Node<'i, T> for FutureWrapperNode<N>
impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>
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, O> {
_i: PhantomData<I>,
_o: PhantomData<O>,
}
impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode<I, O> {
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> {
type Output = DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
@ -206,32 +208,6 @@ impl<I, O> DowncastBothNode<I, O> {
}
}
}
/// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone)]
pub struct DowncastBothRefNode<I, O> {
node: SharedNodeContainer,
_i: PhantomData<(I, O)>,
}
impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode<I, O> {
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<I, O> DowncastBothRefNode<I, O> {
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<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> Do
DowncastBothNode::new(n)
}
pub struct PanicNode<I, O>(PhantomData<I>, PhantomData<O>);
pub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);
impl<'i, I: 'i, O: 'i> Node<'i, I> for PanicNode<I, O> {
impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {
type Output = O;
fn eval(&'i self, _: I) -> Self::Output {
unimplemented!("This node should never be evaluated")
}
}
impl<I, O> PanicNode<I, O> {
impl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {
pub const fn new() -> Self {
Self(PhantomData, PhantomData)
}
}
impl<I, O> Default for PanicNode<I, O> {
impl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {
fn default() -> Self {
Self::new()
}
}
// TODO: Evaluate safety
unsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}
#[cfg(test)]
mod test {
use super::*;

View file

@ -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<Value> {
}
#[node_fn(ChainApplyNode)]
async fn chain_apply<I: Iterator, T>(iter: I, mut value: T) -> T
async fn chain_apply<I: Iterator + WasmNotSend, T: WasmNotSend>(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<Color>, bounds: ImageFrame<Color>, 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.

View file

@ -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<TypingContext, ShaderIO> {
// TODO: Move to graph-craft
#[node_macro::node_fn(GpuCompiler)]
async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
async fn compile_gpu(node: &'input DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result<compilation_client::Shader, String> {
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, EditorApi> {
node: Node,
editor_api: EditorApi,
cache: RefCell<HashMap<String, ComputePass<WgpuExecutor>>>,
cache: Mutex<HashMap<String, ComputePass>>,
}
struct ComputePass<T: GpuExecutor> {
pipeline_layout: PipelineLayout<T>,
readback_buffer: Option<Arc<ShaderInput<T>>>,
struct ComputePass {
pipeline_layout: PipelineLayout,
readback_buffer: Option<Arc<WgpuShaderInput>>,
}
impl<T: GpuExecutor> Clone for ComputePass<T> {
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<Color>, 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<Node, EditorApi> MapGpuNode<Node, EditorApi> {
Self {
node,
editor_api,
cache: RefCell::new(HashMap::new()),
cache: Mutex::new(HashMap::new()),
}
}
}
@ -164,7 +165,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
image: &ImageFrame<T>,
executor: &&WgpuExecutor,
quantization: QuantizationChannels,
) -> Result<ComputePass<WgpuExecutor>, String> {
) -> Result<ComputePass, String> {
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
@ -335,7 +336,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
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<Color>, background: ImageFrame<C
],
};
let shader = gpu_executor::Shader {
let shader = Shader {
source: shader.spirv_binary.into(),
name: "gpu::eval",
io: shader.io,

View file

@ -219,7 +219,7 @@ impl Default for ImaginateImageToImageRequestOverrideSettings {
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct ImaginateTextToImageRequest<'a> {
#[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<String>,
denoising_strength: f64,
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
mask: Option<ImaginateMask>,
}
@ -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<P>,
@ -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<P>,

View file

@ -92,7 +92,7 @@ fn generate_quantization<const N: usize>(data: Vec<f64>, samples: usize, channel
}*/
fn create_distribution(data: Vec<f64>, samples: usize, channel: usize) -> Vec<(f64, f64)> {
let data: Vec<f64> = data.chunks(4 * (data.len() / (4 * samples.min(data.len() / 4)))).map(|x| x[channel] as f64).collect();
let data: Vec<f64> = 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<f64> = data.iter().map(|x| x / max).collect();
dbg!(max);

View file

@ -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<P, Background, MapFn> {
}
#[node_macro::node_fn(BlendImageNode<_P>)]
async fn blend_image_node<_P: Alpha + Pixel + Debug, Forground: Sample<Pixel = _P> + Transform>(
async fn blend_image_node<_P: Alpha + Pixel + Debug + WasmNotSend + Sync + 'static, MapFn, Forground: Sample<Pixel = _P> + 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<P: Pixel, E, C, $($t,)*> {
editor_api: E,
controller: C,
$($val: $t,)*
cache: std::sync::Mutex<HashMap<u64, Image<P>>>,
cache: std::sync::Arc<std::sync::Mutex<HashMap<u64, Image<P>>>>,
}
impl<'e, P: Pixel, E, C, $($t,)*> ImaginateNode<P, E, C, $($t,)*>
@ -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<P>> for ImaginateNode<P, E, C, $($t,)*>
impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default + Send, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame<P>> for ImaginateNode<P, E, C, $($t,)*>
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<Box<dyn std::future::Future<Output = ImaginateController>>> = controller;
// let controller: std::pin::Pin<Box<dyn std::future::Future<Output = ImaginateController> + 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<DVec2>,

View file

@ -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<LowerVectorData, BooleanOp> {
}
#[node_macro::node_fn(BinaryBooleanOperationNode)]
async fn binary_boolean_operation_node<Fut: Future<Output = VectorData>>(
upper_vector_data: VectorData,
lower_vector_data: impl Node<Footprint, Output = Fut>,
boolean_operation: BooleanOperation,
) -> VectorData {
async fn binary_boolean_operation_node(upper_vector_data: VectorData, lower_vector_data: impl Node<Footprint, Output = VectorData>, 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<BooleanOp> {
#[node_macro::node_fn(BooleanOperationNode)]
fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: BooleanOperation) -> VectorData {
fn vector_from_image<P: graphene_core::raster::Pixel>(image_frame: &ImageFrame<P>) -> VectorData {
fn vector_from_image<T: Transform>(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),
}
}

View file

@ -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<HtmlCanvasElement>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<HtmlCanvasElement>;
pub struct CreateSurfaceNode {}
#[node_macro::node_fn(CreateSurfaceNode)]
async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc<SurfaceHandle<<WasmApplicationIo as ApplicationIo>::Surface>> {
editor.application_io.as_ref().unwrap().create_surface().into()
async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
Arc::new(editor.application_io.as_ref().unwrap().create_surface())
}
#[cfg(target_arch = "wasm32")]
pub struct DrawImageFrameNode<Surface> {
surface_handle: Surface,
}
#[node_macro::node_fn(DrawImageFrameNode)]
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> SurfaceHandleFrame<HtmlCanvasElement> {
#[cfg(target_arch = "wasm32")]
async fn draw_image_frame_node<'a: 'input>(
image: ImageFrame<graphene_core::raster::SRGBA8>,
surface_handle: Arc<WasmSurfaceHandle>,
) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
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<SRGBA8>, 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<SurfaceHandle<HtmlCanvasElement>>,
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, Surface> {
footprint: Footprint,
surface_handle: Surface,
}
#[node_macro::node_fn(RasterizeNode)]
async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, footprint: Footprint, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> ImageFrame<Color> {
#[cfg(target_arch = "wasm32")]
async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend>(
mut data: _T,
footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrame<Color> {
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<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig>
for RenderNode<Data, Surface, Footprint>
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, Footprint>
where
Data: Node<'input, Footprint, Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
for<'a> Data: Node<'a, Footprint, Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + '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<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig>
for RenderNode<Data, Surface, ()>
impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode<Data, Surface, ()>
where
Data: Node<'input, (), Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
for<'a> Data: Node<'a, (), Output: Future<Output = T> + WasmNotSend>,
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + WasmNotSend> + 'input,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + '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<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
pub fn new(data: Data, _surface_handle: Surface) -> Self {

View file

@ -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 }

View file

@ -102,7 +102,7 @@ impl DynamicExecutor {
}
}
impl<'a, I: StaticType + 'static> Executor<I, TaggedValue> for &'a DynamicExecutor {
impl<'a, I: StaticType + 'static + Send + Sync> Executor<I, TaggedValue> for &'a DynamicExecutor {
fn execute(&self, input: I) -> LocalFuture<Result<TaggedValue, Box<dyn Error>>> {
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<O> {
pub async fn eval<'i, I: StaticType + 'i + Send + Sync, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option<O> {
let node = self.nodes.get(&id).cloned()?;
let output = node.eval(Box::new(input));
dyn_any::downcast::<O>(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<I: StaticType + 'static>(&self, id: NodeId, input: I) -> Result<TaggedValue, String> {
pub async fn eval_tagged_value<I: StaticType + 'static + Send + Sync>(&self, id: NodeId, input: I) -> Result<TaggedValue, String> {
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<NodeContainer> = 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)

View file

@ -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<Box<dyn core::future::Future<Output = $type>>>>::new()
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type> + 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, 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<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: &WasmEditorApi, output: Arc<WasmSurfaceHandle>, params: []),
#[cfg(target_arch = "wasm32")]
async_node!(
graphene_std::wasm_application_io::DrawImageFrameNode<_>,
input: ImageFrame<SRGBA8>,
@ -356,41 +355,40 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
params: [Arc<WasmSurfaceHandle>]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::UniformNode<_>, input: f32, output: ShaderInput<WgpuExecutor>, params: [&WgpuExecutor]),
async_node!(wgpu_executor::UniformNode<_>, input: f32, output: WgpuShaderInput, params: [&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::StorageNode<_>, input: Vec<u8>, output: ShaderInput<WgpuExecutor>, params: [&WgpuExecutor]),
async_node!(wgpu_executor::StorageNode<_>, input: Vec<u8>, output: WgpuShaderInput, params: [&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
gpu_executor::PushNode<_>,
input: Vec<ShaderInput<WgpuExecutor>>,
output: Vec<ShaderInput<WgpuExecutor>>,
params: [ShaderInput<WgpuExecutor>]
wgpu_executor::PushNode<_>,
input: Vec<WgpuShaderInput>,
output: Vec<WgpuShaderInput>,
params: [WgpuShaderInput]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::CreateOutputBufferNode<_, _>, input: usize, output: gpu_executor::ShaderInput<WgpuExecutor>, 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<WgpuExecutor>, output: <WgpuExecutor as GpuExecutor>::CommandBuffer, params: [&WgpuExecutor, ShaderInput<WgpuExecutor>, 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: <WgpuExecutor as GpuExecutor>::ShaderHandle, output: gpu_executor::PipelineLayout<WgpuExecutor>, params: [String, gpu_executor::Bindgroup<WgpuExecutor>, Arc<ShaderInput<WgpuExecutor>>]),
async_node!(wgpu_executor::CreatePipelineLayoutNode<_, _, _>, input: ShaderHandle, output: wgpu_executor::PipelineLayout, params: [String, wgpu_executor::Bindgroup, Arc<WgpuShaderInput>]),
#[cfg(feature = "gpu")]
async_node!(
gpu_executor::ExecuteComputePipelineNode<_>,
input: <WgpuExecutor as GpuExecutor>::CommandBuffer,
wgpu_executor::ExecuteComputePipelineNode<_>,
input: CommandBuffer,
output: (),
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::ReadOutputBufferNode<_, _>, input: Arc<ShaderInput<WgpuExecutor>>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
async_node!(wgpu_executor::ReadOutputBufferNode<_, _>, input: Arc<WgpuShaderInput>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::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<WgpuExecutor>, output: SurfaceFrame, params: [Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::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<Color>,
output: ShaderInputFrame<WgpuExecutor>,
output: ShaderInputFrame,
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
@ -461,6 +459,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::raster::adjustments::ColorFillNode<_>, input: ImageFrame<Color>, params: [Color]),
register_node!(graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>, input: ImageFrame<Color>, params: [Color, BlendMode, f64]),
register_node!(graphene_core::raster::IndexNode<_>, input: Vec<Color>, params: [u32]),
/*
vec![(
ProtoNodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|args| {
@ -469,7 +468,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
let blend_mode: DowncastBothNode<(), BlendMode> = 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<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased()
})
@ -479,7 +478,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
Box::pin(async move {
use graphene_std::raster::ImaginateNode;
macro_rules! instantiate_imaginate_node {
($($i:expr,)*) => { 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<Color, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _> = 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec<DVec2>, params: [Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: [Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<WasmSurfaceHandle>, params: [Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: WindowHandle, params: [WindowHandle]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame<WgpuExecutor>, params: [ShaderInputFrame<WgpuExecutor>]),
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<Color>, params: [&str]),
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::quantization::QuantizeNode<_>, 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<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
#[cfg(target_arch = "wasm32")]
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
#[cfg(target_arch = "wasm32")]
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
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<Color>, fn_params: [Footprint => ImageFrame<Color>, () => 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<VectorData>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>> + '_>
any.into_type_erased()
})
},
{
@ -691,7 +696,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<ArtboardGroup>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>> + '_>
any.into_type_erased()
})
},
{
@ -710,7 +715,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
args.reverse();
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<GraphicGroup>(args.pop().expect("Not enough arguments provided to construct node")));
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>> + '_>
any.into_type_erased()
})
},
{
@ -752,6 +757,11 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, 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<graphene_std::application_io::SurfaceHandleFrame<wgpu::web_sys::HtmlCanvasElement>>, params: []),
#[cfg(target_arch = "wasm32")]
register_node!(graphene_core::ToGraphicElementNode, input: graphene_std::application_io::SurfaceHandleFrame<wgpu::web_sys::HtmlCanvasElement>, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []),

View file

@ -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 <contact@graphite.rs>"]
edition = "2021"
readme = "../../README.md"

View file

@ -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::<Vec<_>>();
let future_generics = (0..num_inputs).map(|x| format_ident!("F{x}")).collect::<Vec<_>>();
let parameter_types = parameter_inputs.iter().map(|x| *x.ty.clone()).collect::<Vec<Type>>();
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::<Vec<_>>();
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::<Punctuated<_, Comma>>()
} else {
type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>()
};
let node_generics = construct_node_generics(&struct_generics, true);
let generics = type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
// 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<Output = #out_ty>});
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<Output = #out_ty> + ::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<Box<dyn core::future::Future< Output = #output> + '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::<Vec<_>>();
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::<Vec<_>>();
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<GenericParam> {
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<GenericParam> {
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<GenericParam> {
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<Type>, node_generics: Vec<GenericPara
let bound = trait_bound(lifetime, in_ty, out_ty);
WherePredicate::Type(PredicateType {
lifetimes: None,
lifetimes: Some(syn::parse_quote!(for<'any_input>)),
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()

View file

@ -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 }

View file

@ -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<Instance>,
pub device: Arc<Device>,
pub queue: Arc<Queue>,
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()
}
}

View file

@ -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<I: StaticTypeSized, O> {
context: Context,
entry_point: String,
shader: std::sync::Arc<vulkano::shader::ShaderModule>,
_phantom: std::marker::PhantomData<(I, O)>,
}
impl<I: StaticTypeSized, O> GpuExecutor<I, O> {
pub fn new(context: Context, shader: &[u8], entry_point: String) -> anyhow::Result<Self> {
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<I>, Vec<O>> for &'a GpuExecutor<I, O> {
fn execute(&self, input: Vec<I>) -> LocalFuture<Result<Vec<O>, Box<dyn Error>>> {
let context = &self.context;
let result: Vec<O> = 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<I: Pod + Send + Sync, O: Pod + Send + Sync>(
device: std::sync::Arc<Device>,
queue: std::sync::Arc<vulkano::device::Queue>,
entry_point: vulkano::shader::EntryPoint,
alloc: &StandardMemoryAllocator,
calloc: &StandardCommandBufferAllocator,
data: Vec<I>,
) -> Vec<O> {
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<T: Pod + Send + Sync>(data: Vec<T>, alloc: &StandardMemoryAllocator) -> Result<std::sync::Arc<CpuAccessibleBuffer<[T]>>, 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<u32>"), 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<u32, u32> = 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::<Vec<u32>>(result).unwrap();
// for (i, r) in result.iter().enumerate() {
// assert_eq!(*r, i as u32 + 3);
// }
// }
// }

View file

@ -1,5 +0,0 @@
mod context;
mod executor;
pub use context::Context;
pub use executor::GpuExecutor;

View file

@ -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 }

View file

@ -103,6 +103,7 @@ async fn execute_shader<I: Pod + Send + Sync, O: Pod + Send + Sync>(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.

View file

@ -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<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
}
}
pub type WgpuSurface<'window> = Arc<SurfaceHandle<wgpu::Surface<'window>>>;
pub type WgpuSurface = Arc<SurfaceHandle<Surface>>;
pub type WgpuWindow = Arc<SurfaceHandle<WindowHandle>>;
#[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<WgpuExecutor>;
#[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<winit::window::Window>;
unsafe impl StaticType for Surface {
type Static = Surface;
}
fn load_shader(&self, shader: Shader) -> Result<Self::ShaderHandle> {
// pub trait SpirVCompiler {
// fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result<Shader>;
// }
impl WgpuExecutor {
pub fn load_shader(&self, shader: Shader) -> Result<ShaderHandle> {
#[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<T: ToUniformBuffer>(&self, data: T) -> Result<WgpuShaderInput> {
pub fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<WgpuShaderInput> {
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::<T>()))
}
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<WgpuShaderInput> {
pub fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<WgpuShaderInput> {
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<T: gpu_executor::ToTextureBuffer>(&self, data: T, options: TextureBufferOptions) -> Result<WgpuShaderInput> {
pub fn create_texture_buffer<T: gpu_executor::ToTextureBuffer>(&self, data: T, options: TextureBufferOptions) -> Result<WgpuShaderInput> {
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<WgpuShaderInput> {
pub fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<WgpuShaderInput> {
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<Self>, read_back: Option<Arc<WgpuShaderInput>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer> {
pub fn create_compute_pass(&self, layout: &PipelineLayout, read_back: Option<Arc<WgpuShaderInput>>, instances: ComputePassDimensions) -> Result<CommandBuffer> {
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::<Vec<_>>();
@ -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<ShaderInput<Self>>, canvas: Arc<SurfaceHandle<wgpu::Surface>>) -> 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<SurfaceHandle<Surface>>) -> 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<ShaderInput<Self>>) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>>>> {
pub fn read_output_buffer(&self, buffer: Arc<WgpuShaderInput>) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>> + 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<Self>) -> Result<ShaderInput<Self>> {
pub fn create_texture_view(&self, texture: WgpuShaderInput) -> Result<WgpuShaderInput> {
// 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<SurfaceHandle<wgpu::Surface>> {
fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
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<Self::Window>) -> Result<SurfaceHandle<wgpu::Surface>> {
fn create_surface(&self, window: SurfaceHandle<Window>) -> Result<SurfaceHandle<Surface>> {
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<BufferHandle, TextureHandle, TextureView>;
pub type AbstractShaderInput = ShaderInput<(), (), ()>;
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
/// All the possible inputs to a shader.
pub enum ShaderInput<BufferHandle, TextureHandle, TextureView> {
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<T: 'static, U: 'static, V: 'static> StaticType for ShaderInput<T, U, V> {
type Static = ShaderInput<T, U, V>;
}
pub enum BindingType<'a> {
UniformBuffer(&'a BufferHandle),
StorageBuffer(&'a BufferHandle),
TextureView(&'a TextureView),
}
/// Extract the buffer handle from a shader input.
impl ShaderInput<BufferHandle, TextureHandle, TextureView> {
pub fn binding(&self) -> Option<BindingType> {
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<T, U, V> ShaderInput<T, U, V> {
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<AbstractShaderInput>,
pub output: AbstractShaderInput,
}
/// Collection of all arguments that are passed to the shader.
#[derive(DynAny)]
pub struct Bindgroup {
pub buffers: Vec<Arc<WgpuShaderInput>>,
}
/// A struct representing a compute pipeline.
#[derive(DynAny, Clone)]
pub struct PipelineLayout {
pub shader: Arc<ShaderHandle>,
pub entry_point: String,
pub bind_group: Arc<Bindgroup>,
pub output_buffer: Arc<WgpuShaderInput>,
}
/// Extracts arguments from the function arguments and wraps them in a node.
pub struct ShaderInputNode<T> {
data: T,
}
impl<'i, T: 'i> Node<'i, ()> for ShaderInputNode<T> {
type Output = &'i T;
fn eval(&'i self, _: ()) -> Self::Output {
&self.data
}
}
impl<T> ShaderInputNode<T> {
pub fn new(data: T) -> Self {
Self { data }
}
}
pub struct UniformNode<Executor> {
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: 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: Value,
}
#[node_macro::node_fn(PushNode)]
async fn push_node<T: Send>(mut vec: Vec<T>, value: T) {
vec.push(value);
}
pub struct CreateOutputBufferNode<Executor, Ty> {
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<WgpuShaderInput> {
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
}
pub struct CreateComputePassNode<Executor, Output, Instances> {
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<EntryPoint, Bindgroup, OutputBuffer> {
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<WgpuShaderInput>) -> PipelineLayout {
PipelineLayout {
shader: shader.into(),
entry_point,
bind_group: bind_group.into(),
output_buffer,
}
}
pub struct ExecuteComputePipelineNode<Executor> {
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, ComputePass> {
executor: Executor,
_compute_pass: ComputePass,
}
#[node_macro::node_fn(ReadOutputBufferNode)]
async fn read_output_buffer_node<'a: 'input>(buffer: Arc<WgpuShaderInput>, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec<u8> {
executor.read_output_buffer(buffer).await.unwrap()
}
pub struct CreateGpuSurfaceNode {}
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
#[node_macro::node_fn(CreateGpuSurfaceNode)]
async fn create_gpu_surface<'a: 'input, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + Send + Sync>(editor_api: &'a EditorApi<Io>) -> 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, Surface, EditorApi> {
image: Image,
surface: Surface,
executor: EditorApi,
}
#[derive(DynAny, Clone, Debug)]
pub struct ShaderInputFrame {
shader_input: Arc<WgpuShaderInput>,
transform: DAffine2,
}
#[node_macro::node_fn(RenderTextureNode)]
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, 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: Executor,
}
#[node_macro::node_fn(UploadTextureNode)]
async fn upload_texture<'a: 'input>(input: ImageFrame<Color>, executor: &'a WgpuExecutor) -> ShaderInputFrame {
let new_data: Vec<RGBA16F> = 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,
}
}

View file

@ -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 <contact@graphite.rs>"]
edition = "2021"
readme = "../README.md"

View file

@ -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'
'';

View file

@ -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 <contact@graphite.rs>"]
edition = "2021"
readme = "../../README.md"