mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
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:
parent
59a943f42f
commit
212f08c6c8
66 changed files with 1572 additions and 1577 deletions
319
Cargo.lock
generated
319
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -410,78 +410,76 @@ mod test {
|
|||
assert_eq!(layers_after_copy[5], shape_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
/// This test will fail when you make changes to the underlying serialization format for a document.
|
||||
fn check_if_demo_art_opens() {
|
||||
futures::executor::block_on(async {
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
async fn check_if_demo_art_opens() {
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
|
||||
let print_problem_to_terminal_on_failure = |value: &String| {
|
||||
println!();
|
||||
println!("-------------------------------------------------");
|
||||
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
|
||||
println!();
|
||||
println!("DisplayDialogError details:");
|
||||
println!();
|
||||
println!("Description: {value}");
|
||||
println!("-------------------------------------------------");
|
||||
println!();
|
||||
let print_problem_to_terminal_on_failure = |value: &String| {
|
||||
println!();
|
||||
println!("-------------------------------------------------");
|
||||
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
|
||||
println!();
|
||||
println!("DisplayDialogError details:");
|
||||
println!();
|
||||
println!("Description: {value}");
|
||||
println!("-------------------------------------------------");
|
||||
println!();
|
||||
|
||||
panic!()
|
||||
};
|
||||
panic!()
|
||||
};
|
||||
|
||||
init_logger();
|
||||
let mut editor = Editor::create();
|
||||
init_logger();
|
||||
let mut editor = Editor::create();
|
||||
|
||||
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
|
||||
//
|
||||
// let files = [
|
||||
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
|
||||
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
|
||||
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
|
||||
// include_str!("../../demo-artwork/red-dress.graphite"),
|
||||
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
|
||||
// ];
|
||||
// for (id, document_serialized_content) in files.iter().enumerate() {
|
||||
// let document_name = format!("document {id}");
|
||||
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
|
||||
//
|
||||
// let files = [
|
||||
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
|
||||
// include_str!("../../demo-artwork/just-a-potted-cactus.graphite"),
|
||||
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
|
||||
// include_str!("../../demo-artwork/red-dress.graphite"),
|
||||
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
|
||||
// ];
|
||||
// for (id, document_serialized_content) in files.iter().enumerate() {
|
||||
// let document_name = format!("document {id}");
|
||||
|
||||
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
|
||||
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
|
||||
for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
|
||||
let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
document_serialized_content.lines().count(),
|
||||
1,
|
||||
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
|
||||
);
|
||||
assert_eq!(
|
||||
document_serialized_content.lines().count(),
|
||||
1,
|
||||
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
|
||||
);
|
||||
|
||||
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||
document_name: document_name.into(),
|
||||
document_serialized_content: document_serialized_content.into(),
|
||||
});
|
||||
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||
document_name: document_name.into(),
|
||||
document_serialized_content,
|
||||
});
|
||||
|
||||
// Check if the graph renders
|
||||
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||
portfolio
|
||||
.executor
|
||||
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE)
|
||||
.expect("submit_node_graph_evaluation failed");
|
||||
crate::node_graph_executor::run_node_graph().await;
|
||||
let mut messages = VecDeque::new();
|
||||
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
|
||||
// Check if the graph renders
|
||||
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||
portfolio
|
||||
.executor
|
||||
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE, true)
|
||||
.expect("submit_node_graph_evaluation failed");
|
||||
crate::node_graph_executor::run_node_graph().await;
|
||||
let mut messages = VecDeque::new();
|
||||
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
|
||||
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
|
||||
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
|
||||
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
}
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
|
||||
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
|
||||
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -385,8 +385,6 @@ impl LayoutMessageHandler {
|
|||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
trace!("{layout_target:?} diff {diff:#?}");
|
||||
|
||||
let message = match layout_target {
|
||||
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
||||
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
||||
|
|
|
@ -23,7 +23,7 @@ use graphene_core::*;
|
|||
use graphene_std::application_io::RenderConfig;
|
||||
use graphene_std::wasm_application_io::WasmEditorApi;
|
||||
#[cfg(feature = "gpu")]
|
||||
use {gpu_executor::*, graphene_core::application_io::SurfaceHandle, wgpu_executor::WgpuExecutor};
|
||||
use wgpu_executor::{Bindgroup, CommandBuffer, PipelineLayout, ShaderHandle, ShaderInputFrame, WgpuShaderInput};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::VecDeque;
|
||||
|
@ -994,7 +994,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Create Uniform".to_string(),
|
||||
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UniformNode<_>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1038,7 +1038,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Create Storage".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::StorageNode<_>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1082,7 +1082,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Create Output Buffer".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateOutputBufferNode<_, _>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1134,12 +1134,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Create Compute Pass".to_string(),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
|
||||
NodeInput::network(concrete!(PipelineLayout), 0),
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
|
||||
NodeInput::network(concrete!(WgpuShaderInput), 2),
|
||||
NodeInput::network(concrete!(gpu_executor::ComputePassDimensions), 3),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateComputePassNode<_, _, _>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateComputePassNode<_, _, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1160,12 +1160,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::network(concrete!(gpu_executor::PipelineLayout<WgpuExecutor>), 0),
|
||||
default: NodeInput::network(concrete!(PipelineLayout), 0),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::network(concrete!(ShaderInput<WgpuExecutor>), 2),
|
||||
default: NodeInput::network(concrete!(WgpuShaderInput), 2),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "In",
|
||||
|
@ -1184,12 +1184,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNodeDefinition {
|
||||
name: "CreatePipelineLayout",
|
||||
category: "Gpu",
|
||||
implementation: DocumentNodeImplementation::proto("gpu_executor::CreatePipelineLayoutNode<_, _, _, _>"),
|
||||
implementation: DocumentNodeImplementation::proto("wgpu_executor::CreatePipelineLayoutNode<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType {
|
||||
name: "ShaderHandle",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::ShaderHandle), 0),
|
||||
default: NodeInput::network(concrete!(ShaderHandle), 0),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "String",
|
||||
|
@ -1199,12 +1199,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentInputType {
|
||||
name: "Bindgroup",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::network(concrete!(gpu_executor::Bindgroup<WgpuExecutor>), 2),
|
||||
default: NodeInput::network(concrete!(Bindgroup), 2),
|
||||
},
|
||||
DocumentInputType {
|
||||
name: "ArcShaderInput",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 3),
|
||||
default: NodeInput::network(concrete!(Arc<WgpuShaderInput>), 3),
|
||||
},
|
||||
],
|
||||
outputs: vec![DocumentOutputType {
|
||||
|
@ -1229,8 +1229,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNode {
|
||||
name: "Execute Compute Pipeline".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(<WgpuExecutor as GpuExecutor>::CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ExecuteComputePipelineNode<_>")),
|
||||
inputs: vec![NodeInput::network(concrete!(CommandBuffer), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ExecuteComputePipelineNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1273,8 +1273,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNode {
|
||||
name: "Read Output Buffer".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(Arc<ShaderInput<WgpuExecutor>>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::ReadOutputBufferNode<_, _>")),
|
||||
inputs: vec![NodeInput::network(concrete!(Arc<WgpuShaderInput>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::ReadOutputBufferNode<_, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1312,7 +1312,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Create Gpu Surface".to_string(),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateGpuSurfaceNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
|
@ -1350,12 +1350,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNode {
|
||||
name: "Render Texture".to_string(),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(ShaderInputFrame<WgpuExecutor>), 0),
|
||||
NodeInput::network(concrete!(Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface<'_>>>), 0),
|
||||
NodeInput::network(concrete!(ShaderInputFrame), 0),
|
||||
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::RenderTextureNode<_, _>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::RenderTextureNode<_, _, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
|
@ -1399,14 +1400,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
DocumentNode {
|
||||
name: "Upload Texture".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(ImageFrame<Color>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::UploadTextureNode<_>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Cache".to_string(),
|
||||
manual_composition: Some(concrete!(())),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
|
|
|
@ -403,7 +403,7 @@ impl LayerNodeIdentifier {
|
|||
|
||||
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
|
||||
pub const fn new_unchecked(node_id: NodeId) -> Self {
|
||||
// Safety: will always be >=1
|
||||
// # Safety: will always be >=1
|
||||
Self(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })
|
||||
}
|
||||
|
||||
|
|
|
@ -287,6 +287,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
|
||||
self.persistent_data.font_cache.insert(font, preview_url, data, is_default);
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
for document_id in self.document_ids.iter() {
|
||||
let _ = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if self.active_document_mut().is_some() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
@ -571,7 +578,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
bounds,
|
||||
transparent_background,
|
||||
} => {
|
||||
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render no existent Document");
|
||||
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
|
||||
let export_config = ExportConfig {
|
||||
file_name,
|
||||
file_type,
|
||||
|
@ -591,8 +598,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
}
|
||||
PortfolioMessage::SubmitGraphRender { document_id } => {
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(&document_id).expect("Tried to render no existent Document"),
|
||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
false,
|
||||
);
|
||||
|
||||
if let Err(description) = result {
|
||||
|
@ -666,8 +674,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
}
|
||||
|
||||
impl PortfolioMessageHandler {
|
||||
pub fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
|
||||
self.executor.introspect_node(node_path)
|
||||
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
|
||||
self.executor.introspect_node(node_path).await
|
||||
}
|
||||
|
||||
pub fn document(&self, document_id: DocumentId) -> Option<&DocumentMessageHandler> {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use gpu_executor::ShaderIO;
|
||||
use graph_craft::{proto::ProtoNetwork, Type};
|
||||
use wgpu_executor::ShaderIO;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>>>;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: []),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 }
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -1,5 +0,0 @@
|
|||
mod context;
|
||||
mod executor;
|
||||
|
||||
pub use context::Context;
|
||||
pub use executor::GpuExecutor;
|
|
@ -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 }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
10
shell.nix
10
shell.nix
|
@ -41,7 +41,15 @@ in
|
|||
pkgs.nodejs
|
||||
pkgs.cargo
|
||||
pkgs.cargo-watch
|
||||
pkgs.cargo-nextest
|
||||
pkgs.cargo-expand
|
||||
pkgs.wasm-pack
|
||||
pkgs.wasm-bindgen-cli
|
||||
pkgs.vulkan-loader
|
||||
pkgs.libxkbcommon
|
||||
pkgs.llvm
|
||||
pkgs.gcc-unwrapped.lib
|
||||
pkgs.llvmPackages.libcxxStdenv
|
||||
|
||||
pkgs.openssl
|
||||
pkgs.glib
|
||||
|
@ -57,8 +65,8 @@ in
|
|||
];
|
||||
|
||||
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [pkgs.openssl];
|
||||
# Hacky way to run cago through Mold
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [pkgs.openssl pkgs.vulkan-loader pkgs.libxkbcommon pkgs.llvmPackages.libcxxStdenv pkgs.gcc-unwrapped.lib pkgs.llvm];
|
||||
shellHook = ''
|
||||
alias cargo='mold --run cargo'
|
||||
'';
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue