From debafb6f7e34571ec140d1c64e9236365cea471c Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Thu, 5 Jun 2025 13:02:00 +0200 Subject: [PATCH 01/10] gitignore .idea/ --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 815f5c3986..5f1f0aaff2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ target/ perf.data* profile.json flamegraph.svg - +.idea/ From 6f4556033501678c7e61b20e2b2ac841a735676f Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Tue, 3 Jun 2025 14:38:59 +0200 Subject: [PATCH 02/10] move "glam/serde" dependency to individual crates --- Cargo.toml | 2 +- frontend/wasm/Cargo.toml | 2 +- node-graph/gcore/Cargo.toml | 2 +- node-graph/gpu-executor/Cargo.toml | 2 +- node-graph/graph-craft/Cargo.toml | 2 +- node-graph/graphene-cli/Cargo.toml | 2 +- node-graph/interpreted-executor/Cargo.toml | 2 +- node-graph/wgpu-executor/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c50e83ee1..67e450b5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ resvg = "0.44" usvg = "0.44" rand = { version = "0.9", default-features = false } rand_chacha = "0.9" -glam = { version = "0.29", default-features = false, features = ["serde"] } +glam = { version = "0.29", default-features = false } base64 = "0.22" image = { version = "0.25", default-features = false, features = ["png"] } rustybuzz = "0.20" diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index 09e91dd4f4..d5356e663e 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -36,7 +36,7 @@ serde-wasm-bindgen = { workspace = true } js-sys = { workspace = true } wasm-bindgen-futures = { workspace = true } bezier-rs = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } futures = { workspace = true } math-parser = { workspace = true } wgpu = { workspace = true, features = [ diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index fa9abbec60..d85494d739 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -47,7 +47,7 @@ num-traits = { workspace = true, default-features = false, features = ["i128"] } usvg = { workspace = true } rand = { workspace = true, default-features = false, features = ["std_rng"] } glam = { workspace = true, default-features = false, features = [ - "scalar-math", + "scalar-math", "serde" ] } serde_json = { workspace = true } petgraph = { workspace = true, default-features = false, features = [ diff --git a/node-graph/gpu-executor/Cargo.toml b/node-graph/gpu-executor/Cargo.toml index 38c6b77b38..6870add2c0 100644 --- a/node-graph/gpu-executor/Cargo.toml +++ b/node-graph/gpu-executor/Cargo.toml @@ -17,7 +17,7 @@ dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } num-traits = { workspace = true } log = { workspace = true } serde = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } base64 = { workspace = true } bytemuck = { workspace = true } anyhow = { workspace = true } diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 35faf28618..653cacf90b 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -26,7 +26,7 @@ graphene-core = { workspace = true, features = ["std"] } num-traits = { workspace = true } log = { workspace = true } futures = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } base64 = { workspace = true } bezier-rs = { workspace = true } specta = { workspace = true } diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 40f52e7071..a872878579 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -31,7 +31,7 @@ bitflags = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } bezier-rs = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } graph-craft = { workspace = true, features = ["loading"] } dyn-any = { workspace = true } graphene-core = { workspace = true } diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index bafdf68ff4..41150b7cab 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -22,7 +22,7 @@ dyn-any = { workspace = true, features = ["log-bad-types", "glam"] } num-traits = { workspace = true } log = { workspace = true } wgpu = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } futures = { workspace = true } once_cell = { workspace = true } diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index fd53617130..582413752f 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -19,7 +19,7 @@ dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } node-macro = { workspace = true } num-traits = { workspace = true } log = { workspace = true } -glam = { workspace = true } +glam = { workspace = true, features = ["serde"] } base64 = { workspace = true } bytemuck = { workspace = true } anyhow = { workspace = true } From eb8605a0b3cd2a03895f250daa2ed21c1a8fbcab Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Thu, 5 Jun 2025 14:21:26 +0200 Subject: [PATCH 03/10] Instances extension --- node-graph/gcore/src/instances.rs | 55 ++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index 8c90777aa8..a65a0c102c 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -18,11 +18,24 @@ pub struct Instances { impl Instances { pub fn new(instance: T) -> Self { + Self::from(Instance::new(instance)) + } + + pub fn empty() -> Self { + Self { + instance: Vec::new(), + transform: Vec::new(), + alpha_blending: Vec::new(), + source_node_id: Vec::new(), + } + } + + pub fn with_capacity(capacity: usize) -> Self { Self { - instance: vec![instance], - transform: vec![DAffine2::IDENTITY], - alpha_blending: vec![AlphaBlending::default()], - source_node_id: vec![None], + instance: Vec::with_capacity(capacity), + transform: Vec::with_capacity(capacity), + alpha_blending: Vec::with_capacity(capacity), + source_node_id: Vec::with_capacity(capacity), } } @@ -121,7 +134,7 @@ impl Default for Instances { } } -impl core::hash::Hash for Instances { +impl Hash for Instances { fn hash(&self, state: &mut H) { for instance in &self.instance { instance.hash(state); @@ -140,6 +153,29 @@ unsafe impl StaticType for Instances { type Static = Instances; } +impl From> for Instances { + fn from(instance: Instance) -> Self { + Self { + instance: vec![instance.instance], + transform: vec![instance.transform], + alpha_blending: vec![instance.alpha_blending], + source_node_id: vec![instance.source_node_id], + } + } +} + +impl FromIterator> for Instances { + fn from_iter>>(iter: I) -> Self { + let iter = iter.into_iter(); + let (lower, _) = iter.size_hint(); + let mut instances = Self::with_capacity(lower); + for instance in iter { + instances.push(instance); + } + instances + } +} + fn one_daffine2_default() -> Vec { vec![DAffine2::IDENTITY] } @@ -189,6 +225,15 @@ pub struct Instance { } impl Instance { + pub fn new(instance: T) -> Self { + Self { + instance, + transform: DAffine2::IDENTITY, + alpha_blending: AlphaBlending::default(), + source_node_id: None, + } + } + pub fn to_graphic_element(self) -> Instance where T: Into, From bf2673428ae3260e4b55204bc930ab01ed8591d3 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Thu, 5 Jun 2025 16:00:36 +0200 Subject: [PATCH 04/10] gpu invert node demo --- Cargo.lock | 409 ++++++++- Cargo.toml | 4 +- .../node_graph/document_node_definitions.rs | 86 ++ node-graph/gcore-shader/Cargo.toml | 27 + node-graph/gcore-shader/src/color.rs | 782 ++++++++++++++++++ .../gcore-shader/src/fullscreen_vertex.rs | 14 + node-graph/gcore-shader/src/gpu_invert.rs | 39 + node-graph/gcore-shader/src/lib.rs | 5 + node-graph/wgpu-executor/Cargo.toml | 6 + node-graph/wgpu-executor/build.rs | 34 + .../wgpu-executor/src/gcore_shader_nodes.rs | 150 ++++ node-graph/wgpu-executor/src/lib.rs | 1 + 12 files changed, 1531 insertions(+), 26 deletions(-) create mode 100644 node-graph/gcore-shader/Cargo.toml create mode 100644 node-graph/gcore-shader/src/color.rs create mode 100644 node-graph/gcore-shader/src/fullscreen_vertex.rs create mode 100644 node-graph/gcore-shader/src/gpu_invert.rs create mode 100644 node-graph/gcore-shader/src/lib.rs create mode 100644 node-graph/wgpu-executor/build.rs create mode 100644 node-graph/wgpu-executor/src/gcore_shader_nodes.rs diff --git a/Cargo.lock b/Cargo.lock index 0c347cf8f7..f04ee9c183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.5.2" @@ -637,6 +643,27 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-gpu" +version = "0.1.0" +source = "git+https://github.com/rust-gpu/cargo-gpu?rev=7f5358bf3bac7363c8017409942464365ca61fd8#7f5358bf3bac7363c8017409942464365ca61fd8" +dependencies = [ + "anyhow", + "cargo_metadata", + "clap", + "crossterm", + "directories", + "env_logger 0.10.2", + "log", + "naga 25.0.1", + "relative-path", + "rustc_codegen_spirv-target-specs", + "semver", + "serde", + "serde_json", + "spirv-builder", +] + [[package]] name = "cargo-platform" version = "0.1.9" @@ -776,9 +803,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", "clap_derive", @@ -786,9 +813,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", @@ -798,9 +825,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -824,6 +851,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color" version = "0.1.0" @@ -1119,6 +1157,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.9.0", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.3" @@ -1269,13 +1332,34 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -1286,7 +1370,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.0", "windows-sys 0.59.0", ] @@ -1457,14 +1541,27 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -1568,6 +1665,18 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1691,6 +1800,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futf" version = "0.1.5" @@ -2298,6 +2416,15 @@ dependencies = [ "wgpu", ] +[[package]] +name = "graphene-core-shader" +version = "0.1.0" +dependencies = [ + "bytemuck", + "glam", + "spirv-std", +] + [[package]] name = "graphene-std" version = "0.1.0" @@ -2371,7 +2498,7 @@ dependencies = [ "convert_case 0.7.1", "derivative", "dyn-any", - "env_logger", + "env_logger 0.11.8", "futures", "glam", "gpu-executor", @@ -2516,13 +2643,14 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", "crunchy", + "num-traits", "serde", ] @@ -2538,6 +2666,8 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -3044,6 +3174,35 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -3172,6 +3331,30 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "jni" version = "0.21.1" @@ -3269,6 +3452,26 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchikiki" version = "0.8.2" @@ -3362,7 +3565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3573,6 +3776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -3608,7 +3812,7 @@ dependencies = [ "bit-set", "bitflags 2.9.0", "cfg_aliases 0.1.1", - "codespan-reporting", + "codespan-reporting 0.11.1", "hexf-parse", "indexmap 2.7.1", "log", @@ -3620,6 +3824,29 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.0", + "cfg_aliases 0.2.1", + "codespan-reporting 0.12.0", + "half", + "hashbrown 0.15.2", + "indexmap 2.7.1", + "log", + "num-traits", + "once_cell", + "petgraph 0.8.1", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.12", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -3748,6 +3975,34 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.9.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4092,9 +4347,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -4347,6 +4602,18 @@ dependencies = [ "indexmap 2.7.1", ] +[[package]] +name = "petgraph" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.2", + "indexmap 2.7.1", + "serde", +] + [[package]] name = "phf" version = "0.8.0" @@ -5011,6 +5278,12 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-string" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -5071,6 +5344,17 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -5111,6 +5395,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -5229,6 +5519,16 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rspirv" +version = "0.12.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cf3a93856b6e5946537278df0d3075596371b1950ccff012f02b0f7eafec8d" +dependencies = [ + "rustc-hash 1.1.0", + "spirv", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -5247,6 +5547,23 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_codegen_spirv-target-specs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c89eaf493b3dfc730cda42a77014aad65e03213992c7afe0dff60a9f7d3dd94" + +[[package]] +name = "rustc_codegen_spirv-types" +version = "0.9.0" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919" +dependencies = [ + "rspirv", + "serde", + "serde_json", + "spirv", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -5691,6 +6008,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -5904,15 +6242,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ "bitflags 2.9.0", + "serde", +] + +[[package]] +name = "spirv-builder" +version = "0.9.0" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919" +dependencies = [ + "clap", + "memchr", + "notify", + "raw-string", + "rustc_codegen_spirv-types", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", ] [[package]] name = "spirv-std" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" dependencies = [ "bitflags 1.3.2", "glam", + "libm", "num-traits", "spirv-std-macros", "spirv-std-types", @@ -5921,7 +6277,7 @@ dependencies = [ [[package]] name = "spirv-std-macros" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" dependencies = [ "proc-macro2", "quote", @@ -5932,7 +6288,7 @@ dependencies = [ [[package]] name = "spirv-std-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" [[package]] name = "stable_deref_trait" @@ -7134,7 +7490,7 @@ version = "0.3.0" source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5" dependencies = [ "bytemuck", - "naga", + "naga 23.1.0", "thiserror 2.0.12", "vello_encoding", ] @@ -7540,7 +7896,7 @@ dependencies = [ "document-features", "js-sys", "log", - "naga", + "naga 23.1.0", "parking_lot", "profiling", "raw-window-handle", @@ -7568,7 +7924,7 @@ dependencies = [ "document-features", "indexmap 2.7.1", "log", - "naga", + "naga 23.1.0", "once_cell", "parking_lot", "profiling", @@ -7587,12 +7943,15 @@ dependencies = [ "anyhow", "base64 0.22.1", "bytemuck", + "cargo-gpu", "dyn-any", + "env_logger 0.11.8", "futures", "futures-intrusive", "glam", "gpu-executor", "graphene-core", + "graphene-core-shader", "half", "log", "node-macro", @@ -7632,7 +7991,7 @@ dependencies = [ "libloading 0.8.6", "log", "metal", - "naga", + "naga 23.1.0", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 67e450b5a1..c8a9f2dac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "frontend/wasm", "frontend/src-tauri", "node-graph/gcore", + "node-graph/gcore-shader", "node-graph/gstd", "node-graph/graph-craft", "node-graph/graphene-cli", @@ -69,7 +70,7 @@ axum = "0.8" chrono = "0.4" ron = "0.8" fastnoise-lite = "1.1" -spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" } +spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "9a357691334b9cdd13c82a740ced97c5d857bf4d" } wgpu-types = "23" wgpu = "23" once_cell = "1.13" # Remove when `core::cell::LazyCell` () is stabilized in Rust 1.80 and we bump our MSRV @@ -108,6 +109,7 @@ kurbo = { version = "0.11.0", features = ["serde"] } petgraph = { version = "0.7.1", default-features = false, features = [ "graphmap", ] } +cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "7f5358bf3bac7363c8017409942464365ca61fd8", features = ["wgsl-out"] } [profile.dev] opt-level = 1 diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index bd5fab4e51..f79ed56eec 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -14,6 +14,7 @@ use graph_craft::ProtoNodeIdentifier; use graph_craft::concrete; use graph_craft::document::value::*; use graph_craft::document::*; +use graphene_core::application_io::TextureFrameTable; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; @@ -1850,6 +1851,91 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("TODO"), properties: None, }, + #[cfg(feature = "gpu")] + DocumentNodeDefinition { + identifier: "GPU Invert", + category: "Debug: GPU", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::node(NodeId(2), 0)], + nodes: [ + DocumentNode { + inputs: vec![NodeInput::scope("editor-api")], + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")), + ..Default::default() + }, + DocumentNode { + inputs: vec![NodeInput::network(concrete!(TextureFrameTable), 0), NodeInput::node(NodeId(0), 0)], + manual_composition: Some(generic!(T)), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::gcore_shader_nodes::GpuInvertNode")), + ..Default::default() + }, + DocumentNode { + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::node(NodeId(1), 0)], + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), + ..Default::default() + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (NodeId(id as u64), node)) + .collect(), + ..Default::default() + }), + // experimentally determined to work + inputs: vec![NodeInput::value(TaggedValue::None, true)], + // inputs: vec![NodeInput::value(TaggedValue::TextureFrame(TextureFrameTable::default()), true)], + // inputs: vec![], + // inputs: vec![NodeInput::network(concrete!(TextureFrameTable), 0)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_properties: vec![("In", "TODO").into()], + output_names: vec!["Texture".to_string()], + network_metadata: Some(NodeNetworkMetadata { + persistent_metadata: NodeNetworkPersistentMetadata { + node_metadata: [ + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "Extract Executor".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), + ..Default::default() + }, + ..Default::default() + }, + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "Upload Texture".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), + ..Default::default() + }, + ..Default::default() + }, + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "Cache".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), + ..Default::default() + }, + ..Default::default() + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (NodeId(id as u64), node)) + .collect(), + ..Default::default() + }, + ..Default::default() + }), + ..Default::default() + }, + }, + description: Cow::Borrowed("TODO"), + properties: None, + }, DocumentNodeDefinition { identifier: "Extract", category: "Debug", diff --git a/node-graph/gcore-shader/Cargo.toml b/node-graph/gcore-shader/Cargo.toml new file mode 100644 index 0000000000..edfcd1a1c2 --- /dev/null +++ b/node-graph/gcore-shader/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "graphene-core-shader" +version = "0.1.0" +edition = "2024" +description = "Graphene nodes compiled to shaders" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +gpu = ["glam/libm"] + +[dependencies] +# Workspace dependencies +spirv-std = { workspace = true } +bytemuck = { workspace = true } +glam = { workspace = true, features = [ + "scalar-math", "bytemuck" +] } + +[lints.rust] +# the spirv target is not in the list of common cfgs so must be added manually +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(target_arch, values("spirv"))', +] } diff --git a/node-graph/gcore-shader/src/color.rs b/node-graph/gcore-shader/src/color.rs new file mode 100644 index 0000000000..7f3f25bc24 --- /dev/null +++ b/node-graph/gcore-shader/src/color.rs @@ -0,0 +1,782 @@ +use bytemuck::{Pod, Zeroable}; +use core::hash::Hash; +use glam::Vec4; +#[cfg(target_arch = "spirv")] +use spirv_std::num_traits::Euclid; +#[cfg(target_arch = "spirv")] +use spirv_std::num_traits::float::Float; + +// ----------------------------------------------------- +// custom color start +// ----------------------------------------------------- + +impl From for Color { + fn from(value: Vec4) -> Self { + Color { + red: value.x, + green: value.y, + blue: value.z, + alpha: value.w, + } + } +} + +impl From for Vec4 { + fn from(value: Color) -> Self { + Vec4::new(value.red, value.green, value.blue, value.alpha) + } +} + +// ----------------------------------------------------- +// custom color end +// ----------------------------------------------------- + +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)] +pub struct SRGBA8 { + red: u8, + green: u8, + blue: u8, + alpha: u8, +} + +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)] +pub struct Luma(pub f32); + +/// Structure that represents a color. +/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque). +/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`, +/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color. +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)] +pub struct Color { + red: f32, + green: f32, + blue: f32, + alpha: f32, +} + +#[allow(clippy::derived_hash_with_manual_eq)] +impl Hash for Color { + fn hash(&self, state: &mut H) { + self.red.to_bits().hash(state); + self.green.to_bits().hash(state); + self.blue.to_bits().hash(state); + self.alpha.to_bits().hash(state); + } +} + +impl Color { + pub const BLACK: Color = Color::from_rgbf32_unchecked(0., 0., 0.); + pub const WHITE: Color = Color::from_rgbf32_unchecked(1., 1., 1.); + pub const RED: Color = Color::from_rgbf32_unchecked(1., 0., 0.); + pub const GREEN: Color = Color::from_rgbf32_unchecked(0., 1., 0.); + pub const BLUE: Color = Color::from_rgbf32_unchecked(0., 0., 1.); + pub const YELLOW: Color = Color::from_rgbf32_unchecked(1., 1., 0.); + pub const CYAN: Color = Color::from_rgbf32_unchecked(0., 1., 1.); + pub const MAGENTA: Color = Color::from_rgbf32_unchecked(1., 0., 1.); + pub const TRANSPARENT: Color = Self { + red: 0., + green: 0., + blue: 0., + alpha: 0., + }; + + /// Returns `Some(Color)` if `red`, `green`, `blue` and `alpha` have a valid value. Negative numbers (including `-0.0`), NaN, and infinity are not valid values and return `None`. + /// Alpha values greater than `1.0` are not valid. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.3, 0.14, 0.15, 0.92).unwrap(); + /// assert!(color.components() == (0.3, 0.14, 0.15, 0.92)); + /// + /// let color = Color::from_rgbaf32(1., 1., 1., f32::NAN); + /// assert!(color == None); + /// ``` + #[inline(always)] + pub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Option { + if alpha > 1. || [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) { + return None; + } + let color = Color { red, green, blue, alpha }; + Some(color) + } + + /// Return an opaque `Color` from given `f32` RGB channels. + #[inline(always)] + pub const fn from_rgbf32_unchecked(red: f32, green: f32, blue: f32) -> Color { + Color { red, green, blue, alpha: 1. } + } + + /// Return an opaque `Color` from given `f32` RGB channels. + #[inline(always)] + pub const fn from_rgbaf32_unchecked(red: f32, green: f32, blue: f32, alpha: f32) -> Color { + Color { red, green, blue, alpha } + } + + /// Return an opaque `Color` from given `f32` RGB channels. + #[inline(always)] + pub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color { + Color::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha) + } + + /// Return an opaque SDR `Color` given RGB channels from `0` to `255`, premultiplied by alpha. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgb8_srgb(0x72, 0x67, 0x62); + /// let color2 = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0xFF); + /// assert_eq!(color, color2) + /// ``` + #[inline(always)] + pub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color { + Color::from_rgba8_srgb(red, green, blue, 255) + } + + // TODO: Should this be premult? + /// Return an SDR `Color` given RGBA channels from `0` to `255`, premultiplied by alpha. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0x61); + /// ``` + #[inline(always)] + pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color { + let map_range = |int_color| int_color as f32 / 255.; + + let red = map_range(red); + let green = map_range(green); + let blue = map_range(blue); + let alpha = map_range(alpha); + Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha) + } + + /// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1) + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.); + /// ``` + pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color { + let temp1 = if lightness < 0.5 { + lightness * (saturation + 1.) + } else { + lightness + saturation - lightness * saturation + }; + let temp2 = 2. * lightness - temp1; + #[cfg(not(target_arch = "spirv"))] + let rem = |x: f32| x.rem_euclid(1.); + #[cfg(target_arch = "spirv")] + let rem = |x: f32| x.rem_euclid(&1.); + + let mut red = rem(hue + 1. / 3.); + let mut green = rem(hue); + let mut blue = rem(hue - 1. / 3.); + + fn map_channel(channel: &mut f32, temp2: f32, temp1: f32) { + *channel = if *channel * 6. < 1. { + temp2 + (temp1 - temp2) * 6. * *channel + } else if *channel * 2. < 1. { + temp1 + } else if *channel * 3. < 2. { + temp2 + (temp1 - temp2) * (2. / 3. - *channel) * 6. + } else { + temp2 + } + .clamp(0., 1.); + } + map_channel(&mut red, temp2, temp1); + map_channel(&mut green, temp2, temp1); + map_channel(&mut blue, temp2, temp1); + + Color { red, green, blue, alpha } + } + + /// Return the `red` component. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// assert!(color.r() == 0.114); + /// ``` + #[inline(always)] + pub fn r(&self) -> f32 { + self.red + } + + /// Return the `green` component. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// assert!(color.g() == 0.103); + /// ``` + #[inline(always)] + pub fn g(&self) -> f32 { + self.green + } + + /// Return the `blue` component. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// assert!(color.b() == 0.98); + /// ``` + #[inline(always)] + pub fn b(&self) -> f32 { + self.blue + } + + /// Return the `alpha` component without checking its expected `0.0` to `1.0` range. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// assert!(color.a() == 0.97); + /// ``` + #[inline(always)] + pub fn a(&self) -> f32 { + self.alpha + } + + #[inline(always)] + pub fn average_rgb_channels(&self) -> f32 { + (self.red + self.green + self.blue) / 3. + } + + #[inline(always)] + pub fn minimum_rgb_channels(&self) -> f32 { + self.red.min(self.green).min(self.blue) + } + + #[inline(always)] + pub fn maximum_rgb_channels(&self) -> f32 { + self.red.max(self.green).max(self.blue) + } + + // From https://stackoverflow.com/a/56678483/775283 + #[inline(always)] + pub fn luminance_srgb(&self) -> f32 { + 0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue + } + + // From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients + #[inline(always)] + pub fn luminance_rec_601(&self) -> f32 { + 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue + } + + // From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients + #[inline(always)] + pub fn luminance_rec_601_rounded(&self) -> f32 { + 0.3 * self.red + 0.59 * self.green + 0.11 * self.blue + } + + // From https://stackoverflow.com/a/56678483/775283 + #[inline(always)] + pub fn luminance_perceptual(&self) -> f32 { + let luminance = self.luminance_srgb(); + + if luminance <= 0.008856 { + (luminance * 903.3) / 100. + } else { + (luminance.cbrt() * 116. - 16.) / 100. + } + } + + #[inline(always)] + pub fn from_luminance(luminance: f32) -> Color { + Color { + red: luminance, + green: luminance, + blue: luminance, + alpha: 1., + } + } + + #[inline(always)] + pub fn with_luminance(&self, luminance: f32) -> Color { + let delta = luminance - self.luminance_rec_601_rounded(); + self.map_rgb(|c| (c + delta).clamp(0., 1.)) + } + + #[inline(always)] + pub fn saturation(&self) -> f32 { + let max = (self.red).max(self.green).max(self.blue); + let min = (self.red).min(self.green).min(self.blue); + + max - min + } + + #[inline(always)] + pub fn with_saturation(&self, saturation: f32) -> Color { + let [hue, _, lightness, alpha] = self.to_hsla(); + Color::from_hsla(hue, saturation, lightness, alpha) + } + + pub fn with_alpha(&self, alpha: f32) -> Color { + Color { + red: self.red, + green: self.green, + blue: self.blue, + alpha, + } + } + + pub fn with_red(&self, red: f32) -> Color { + Color { + red, + green: self.green, + blue: self.blue, + alpha: self.alpha, + } + } + + pub fn with_green(&self, green: f32) -> Color { + Color { + red: self.red, + green, + blue: self.blue, + alpha: self.alpha, + } + } + + pub fn with_blue(&self, blue: f32) -> Color { + Color { + red: self.red, + green: self.green, + blue, + alpha: self.alpha, + } + } + + #[inline(always)] + pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 { + c_s + } + + #[inline(always)] + pub fn blend_multiply(c_b: f32, c_s: f32) -> f32 { + c_s * c_b + } + + #[inline(always)] + pub fn blend_darken(c_b: f32, c_s: f32) -> f32 { + c_s.min(c_b) + } + + #[inline(always)] + pub fn blend_color_burn(c_b: f32, c_s: f32) -> f32 { + if c_b == 1. { + 1. + } else if c_s == 0. { + 0. + } else { + 1. - ((1. - c_b) / c_s).min(1.) + } + } + + #[inline(always)] + pub fn blend_linear_burn(c_b: f32, c_s: f32) -> f32 { + c_b + c_s - 1. + } + + #[inline(always)] + pub fn blend_darker_color(&self, other: Color) -> Color { + if self.average_rgb_channels() <= other.average_rgb_channels() { *self } else { other } + } + + #[inline(always)] + pub fn blend_screen(c_b: f32, c_s: f32) -> f32 { + 1. - (1. - c_s) * (1. - c_b) + } + + #[inline(always)] + pub fn blend_lighten(c_b: f32, c_s: f32) -> f32 { + c_s.max(c_b) + } + + #[inline(always)] + pub fn blend_color_dodge(c_b: f32, c_s: f32) -> f32 { + if c_s == 1. { 1. } else { (c_b / (1. - c_s)).min(1.) } + } + + #[inline(always)] + pub fn blend_linear_dodge(c_b: f32, c_s: f32) -> f32 { + c_b + c_s + } + + #[inline(always)] + pub fn blend_lighter_color(&self, other: Color) -> Color { + if self.average_rgb_channels() >= other.average_rgb_channels() { *self } else { other } + } + + pub fn blend_softlight(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + c_b - (1. - 2. * c_s) * c_b * (1. - c_b) + } else { + let d: fn(f32) -> f32 = |x| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() }; + c_b + (2. * c_s - 1.) * (d(c_b) - c_b) + } + } + + pub fn blend_hardlight(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_multiply(2. * c_s, c_b) + } else { + Color::blend_screen(2. * c_s - 1., c_b) + } + } + + pub fn blend_vivid_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_color_burn(2. * c_s, c_b) + } else { + Color::blend_color_dodge(2. * c_s - 1., c_b) + } + } + + pub fn blend_linear_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_linear_burn(2. * c_s, c_b) + } else { + Color::blend_linear_dodge(2. * c_s - 1., c_b) + } + } + + pub fn blend_pin_light(c_b: f32, c_s: f32) -> f32 { + if c_s <= 0.5 { + Color::blend_darken(2. * c_s, c_b) + } else { + Color::blend_lighten(2. * c_s - 1., c_b) + } + } + + pub fn blend_hard_mix(c_b: f32, c_s: f32) -> f32 { + if Color::blend_linear_light(c_b, c_s) < 0.5 { 0. } else { 1. } + } + + pub fn blend_difference(c_b: f32, c_s: f32) -> f32 { + (c_b - c_s).abs() + } + + pub fn blend_exclusion(c_b: f32, c_s: f32) -> f32 { + c_b + c_s - 2. * c_b * c_s + } + + pub fn blend_subtract(c_b: f32, c_s: f32) -> f32 { + c_b - c_s + } + + pub fn blend_divide(c_b: f32, c_s: f32) -> f32 { + if c_b == 0. { 1. } else { c_b / c_s } + } + + pub fn blend_hue(&self, c_s: Color) -> Color { + let sat_b = self.saturation(); + let lum_b = self.luminance_rec_601(); + c_s.with_saturation(sat_b).with_luminance(lum_b) + } + + pub fn blend_saturation(&self, c_s: Color) -> Color { + let sat_s = c_s.saturation(); + let lum_b = self.luminance_rec_601(); + + self.with_saturation(sat_s).with_luminance(lum_b) + } + + pub fn blend_color(&self, c_s: Color) -> Color { + let lum_b = self.luminance_rec_601(); + + c_s.with_luminance(lum_b) + } + + pub fn blend_luminosity(&self, c_s: Color) -> Color { + let lum_s = c_s.luminance_rec_601(); + + self.with_luminance(lum_s) + } + + /// Return the all components as a tuple, first component is red, followed by green, followed by blue, followed by alpha. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// assert_eq!(color.components(), (0.114, 0.103, 0.98, 0.97)); + /// ``` + #[inline(always)] + pub fn components(&self) -> (f32, f32, f32, f32) { + (self.red, self.green, self.blue, self.alpha) + } + + /// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in linear space. + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); + /// // TODO: Add test + /// ``` + #[inline(always)] + pub fn to_rgba8_srgb(&self) -> [u8; 4] { + let gamma = self.to_gamma_srgb(); + [(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8] + } + + // https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ + /// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1) + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla(); + /// ``` + pub fn to_hsla(&self) -> [f32; 4] { + let min_channel = self.red.min(self.green).min(self.blue); + let max_channel = self.red.max(self.green).max(self.blue); + + let lightness = (min_channel + max_channel) / 2.; + let saturation = if min_channel == max_channel { + 0. + } else if lightness <= 0.5 { + (max_channel - min_channel) / (max_channel + min_channel) + } else { + (max_channel - min_channel) / (2. - max_channel - min_channel) + }; + let hue = if self.red >= self.green && self.red >= self.blue { + (self.green - self.blue) / (max_channel - min_channel) + } else if self.green >= self.red && self.green >= self.blue { + 2. + (self.blue - self.red) / (max_channel - min_channel) + } else { + 4. + (self.red - self.green) / (max_channel - min_channel) + } / 6.; + #[cfg(not(target_arch = "spirv"))] + let hue = hue.rem_euclid(1.); + #[cfg(target_arch = "spirv")] + let hue = hue.rem_euclid(&1.); + + [hue, saturation, lightness, self.alpha] + } + + // TODO: Readd formatting + + /// Creates a color from a 8-character RGBA hex string (without a # prefix). + /// + /// # Examples + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgba_str("7C67FA61").unwrap(); + /// ``` + pub fn from_rgba_str(color_str: &str) -> Option { + if color_str.len() != 8 { + return None; + } + let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; + let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; + let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; + let a = u8::from_str_radix(&color_str[6..8], 16).ok()?; + + Some(Color::from_rgba8_srgb(r, g, b, a)) + } + + /// Creates a color from a 6-character RGB hex string (without a # prefix). + /// + /// ``` + /// use graphene_core::raster::color::Color; + /// let color = Color::from_rgb_str("7C67FA").unwrap(); + /// ``` + pub fn from_rgb_str(color_str: &str) -> Option { + if color_str.len() != 6 { + return None; + } + let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; + let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; + let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; + + Some(Color::from_rgb8_srgb(r, g, b)) + } + + /// Linearly interpolates between two colors based on t. + /// + /// T must be between 0 and 1. + #[inline(always)] + pub fn lerp(&self, other: &Color, t: f32) -> Self { + assert!((0. ..=1.).contains(&t)); + Color::from_rgbaf32_unchecked( + self.red + ((other.red - self.red) * t), + self.green + ((other.green - self.green) * t), + self.blue + ((other.blue - self.blue) * t), + self.alpha + ((other.alpha - self.alpha) * t), + ) + } + + #[inline(always)] + pub fn gamma(&self, gamma: f32) -> Color { + let gamma = gamma.max(0.0001); + + // From https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/ + let inverse_gamma = 1. / gamma; + self.map_rgb(|c: f32| c.powf(inverse_gamma)) + } + + #[inline(always)] + pub fn to_linear_srgb(&self) -> Self { + Self { + red: Self::srgb_to_linear(self.red), + green: Self::srgb_to_linear(self.green), + blue: Self::srgb_to_linear(self.blue), + alpha: self.alpha, + } + } + + #[inline(always)] + pub fn to_gamma_srgb(&self) -> Self { + Self { + red: Self::linear_to_srgb(self.red), + green: Self::linear_to_srgb(self.green), + blue: Self::linear_to_srgb(self.blue), + alpha: self.alpha, + } + } + + #[inline(always)] + pub fn srgb_to_linear(channel: f32) -> f32 { + if channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) } + } + + #[inline(always)] + pub fn linear_to_srgb(channel: f32) -> f32 { + if channel <= 0.0031308 { channel * 12.92 } else { 1.055 * channel.powf(1. / 2.4) - 0.055 } + } + + #[inline(always)] + pub fn map_rgba f32>(&self, f: F) -> Self { + Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), f(self.a())) + } + + #[inline(always)] + pub fn map_rgb f32>(&self, f: F) -> Self { + Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a()) + } + + #[inline(always)] + pub fn apply_opacity(&self, opacity: f32) -> Self { + Self::from_rgbaf32_unchecked(self.r() * opacity, self.g() * opacity, self.b() * opacity, self.a() * opacity) + } + + #[inline(always)] + pub fn to_associated_alpha(&self, alpha: f32) -> Self { + Self { + red: self.red * alpha, + green: self.green * alpha, + blue: self.blue * alpha, + alpha: self.alpha * alpha, + } + } + + #[inline(always)] + pub fn to_unassociated_alpha(&self) -> Self { + if self.alpha == 0. { + return *self; + } + let unmultiply = 1. / self.alpha; + Self { + red: self.red * unmultiply, + green: self.green * unmultiply, + blue: self.blue * unmultiply, + alpha: self.alpha, + } + } + + #[inline(always)] + pub fn blend_rgb f32>(&self, other: Color, f: F) -> Self { + let background = self.to_unassociated_alpha(); + Color { + red: f(background.red, other.red).clamp(0., 1.), + green: f(background.green, other.green).clamp(0., 1.), + blue: f(background.blue, other.blue).clamp(0., 1.), + alpha: other.alpha, + } + } + + #[inline(always)] + pub fn alpha_blend(&self, other: Color) -> Self { + let inv_alpha = 1. - other.alpha; + Self { + red: self.red * inv_alpha + other.red, + green: self.green * inv_alpha + other.green, + blue: self.blue * inv_alpha + other.blue, + alpha: self.alpha * inv_alpha + other.alpha, + } + } + + #[inline(always)] + pub fn alpha_add(&self, other: Color) -> Self { + Self { + alpha: (self.alpha + other.alpha).clamp(0., 1.), + ..*self + } + } + + #[inline(always)] + pub fn alpha_subtract(&self, other: Color) -> Self { + Self { + alpha: (self.alpha - other.alpha).clamp(0., 1.), + ..*self + } + } + + #[inline(always)] + pub fn alpha_multiply(&self, other: Color) -> Self { + Self { + alpha: (self.alpha * other.alpha).clamp(0., 1.), + ..*self + } + } +} + +#[test] +fn hsl_roundtrip() { + for (red, green, blue) in [ + (24, 98, 118), + (69, 11, 89), + (54, 82, 38), + (47, 76, 50), + (25, 15, 73), + (62, 57, 33), + (55, 2, 18), + (12, 3, 82), + (91, 16, 98), + (91, 39, 82), + (97, 53, 32), + (76, 8, 91), + (54, 87, 19), + (56, 24, 88), + (14, 82, 34), + (61, 86, 31), + (73, 60, 75), + (95, 79, 88), + (13, 34, 4), + (82, 84, 84), + (255, 255, 178), + ] { + let col = Color::from_rgb8_srgb(red, green, blue); + let [hue, saturation, lightness, alpha] = col.to_hsla(); + let result = Color::from_hsla(hue, saturation, lightness, alpha); + assert!((col.r() - result.r()) < f32::EPSILON * 100.); + assert!((col.g() - result.g()) < f32::EPSILON * 100.); + assert!((col.b() - result.b()) < f32::EPSILON * 100.); + assert!((col.a() - result.a()) < f32::EPSILON * 100.); + } +} diff --git a/node-graph/gcore-shader/src/fullscreen_vertex.rs b/node-graph/gcore-shader/src/fullscreen_vertex.rs new file mode 100644 index 0000000000..b8ef775b9f --- /dev/null +++ b/node-graph/gcore-shader/src/fullscreen_vertex.rs @@ -0,0 +1,14 @@ +use glam::{Vec2, Vec4}; +use spirv_std::spirv; + +/// webgpu NDC is like OpenGL: (-1.0 .. 1.0, -1.0 .. 1.0, 0.0 .. 1.0) +/// https://www.w3.org/TR/webgpu/#coordinate-systems +const FULLSCREEN_VERTICES: [Vec2; 3] = [Vec2::new(-1., -1.), Vec2::new(-1., 3.), Vec2::new(3., -1.)]; + +#[spirv(vertex)] +pub fn fullscreen_vertex(#[spirv(vertex_index)] vertex_index: u32, #[spirv(position)] gl_position: &mut Vec4) { + // broken on edition 2024 branch + // let vertex = unsafe { *FULLSCREEN_VERTICES.index_unchecked(vertex_index as usize) }; + let vertex = FULLSCREEN_VERTICES[vertex_index as usize]; + *gl_position = Vec4::from((vertex, 0., 1.)); +} diff --git a/node-graph/gcore-shader/src/gpu_invert.rs b/node-graph/gcore-shader/src/gpu_invert.rs new file mode 100644 index 0000000000..09fe596710 --- /dev/null +++ b/node-graph/gcore-shader/src/gpu_invert.rs @@ -0,0 +1,39 @@ +use crate::color::Color; + +// exact copy of the invert node +// #[node_macro::node(category("Raster: Adjustment"))] +fn invert_copy( + // _: impl Ctx, + // #[implementations( + // Color, + // ImageFrameTable, + // GradientStops, + // )] + // mut input: T, + color: Color, +) -> Color { + // input.adjust(|color| { + let color = color.to_gamma_srgb(); + + let color = color.map_rgb(|c| color.a() - c); + + color.to_linear_srgb() + // }); + // input +} + +pub mod gpu_invert_shader { + use crate::color::Color; + use crate::gpu_invert::invert_copy; + use glam::{Vec4, Vec4Swizzles}; + use spirv_std::image::sample_with::lod; + use spirv_std::image::{Image2d, ImageWithMethods}; + use spirv_std::spirv; + + #[spirv(fragment)] + pub fn gpu_invert_fragment(#[spirv(frag_coord)] frag_coord: Vec4, #[spirv(descriptor_set = 0, binding = 0)] texture: &Image2d, color_out: &mut Vec4) { + let color = Color::from(texture.fetch_with(frag_coord.xy().as_uvec2(), lod(0))); + let color = invert_copy(color); + *color_out = Vec4::from(color); + } +} diff --git a/node-graph/gcore-shader/src/lib.rs b/node-graph/gcore-shader/src/lib.rs new file mode 100644 index 0000000000..91a4cde3c2 --- /dev/null +++ b/node-graph/gcore-shader/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] + +pub mod color; +pub mod fullscreen_vertex; +pub mod gpu_invert; diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index 582413752f..60ac0e61bd 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -15,6 +15,8 @@ gpu-executor = { path = "../gpu-executor" } # Workspace dependencies graphene-core = { workspace = true, features = ["std", "alloc", "gpu", "wgpu"] } +# required for cargo watch to pick up changes +graphene-core-shader = { path = "../gcore-shader" } dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } node-macro = { workspace = true } num-traits = { workspace = true } @@ -40,3 +42,7 @@ half = "2.4.1" # Optional dependencies nvtx = { version = "1.3", optional = true } + +[build-dependencies] +cargo-gpu = { workspace = true } +env_logger = { workspace = true } diff --git a/node-graph/wgpu-executor/build.rs b/node-graph/wgpu-executor/build.rs new file mode 100644 index 0000000000..3991a32d48 --- /dev/null +++ b/node-graph/wgpu-executor/build.rs @@ -0,0 +1,34 @@ +use cargo_gpu::CompileResultNagaExt; +use cargo_gpu::naga::back::wgsl::WriterFlags; +use cargo_gpu::naga::valid::Capabilities; +use cargo_gpu::spirv_builder::{MetadataPrintout, SpirvMetadata}; +use std::path::PathBuf; + +pub fn main() -> Result<(), Box> { + env_logger::builder().init(); + + let shader_crate = PathBuf::from("../gcore-shader"); + + // install the toolchain and build the `rustc_codegen_spirv` codegen backend with it + let backend = cargo_gpu::Install::from_shader_crate(shader_crate.clone()).run()?; + + // build the shader crate + let mut builder = backend.to_spirv_builder(shader_crate, "spirv-unknown-vulkan1.2"); + builder.print_metadata = MetadataPrintout::DependencyOnly; + builder.spirv_metadata = SpirvMetadata::Full; + builder.shader_crate_features.default_features = false; + builder.shader_crate_features.features = vec![String::from("gpu")]; + let spv_result = builder.build()?; + + // transpile the spv binaries to wgsl + let wgsl_result = spv_result.naga_transpile(Capabilities::empty())?.to_wgsl(WriterFlags::empty())?; + let path_to_wgsl = wgsl_result.module.unwrap_single(); + + // emit path to wgsl into env var, used in `quad.rs` like this: + // > include_str!(env!("WGSL_SHADER_PATH")) + println!("cargo::rustc-env=WGSL_SHADER_PATH={}", path_to_wgsl.display()); + + // you could also generate some rust source code into the `std::env::var("OUT_DIR")` dir + // and use `include!(concat!(env!("OUT_DIR"), "/shader_symbols.rs"));` to include it + Ok(()) +} diff --git a/node-graph/wgpu-executor/src/gcore_shader_nodes.rs b/node-graph/wgpu-executor/src/gcore_shader_nodes.rs new file mode 100644 index 0000000000..b8716ef314 --- /dev/null +++ b/node-graph/wgpu-executor/src/gcore_shader_nodes.rs @@ -0,0 +1,150 @@ +use crate::{Context, WgpuExecutor}; +use graphene_core::Ctx; +use graphene_core::application_io::{ImageTexture, TextureFrameTable}; +use graphene_core::instances::Instance; +use std::borrow::Cow; +use std::sync::Arc; +use wgpu::{ + BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Device, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, Queue, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, + TextureViewDescriptor, VertexState, +}; + +const WGSL_SHADER: &str = include_str!(env!("WGSL_SHADER_PATH")); + +#[node_macro::node(category(""))] +async fn gpu_invert<'a: 'n>(_: impl Ctx, input: TextureFrameTable, executor: &'a WgpuExecutor) -> TextureFrameTable { + let Context { device, queue, .. } = &executor.context; + // this should be cached + let graphics_pipeline = GraphitePerPixelGraphicsPipeline::new(device, "gpu_invertgpu_invert_shadergpu_invert_fragment"); + graphics_pipeline.run(input, queue) +} + +pub struct GraphitePerPixelGraphicsPipeline { + device: Arc, + render_pipeline_f32: wgpu::RenderPipeline, + render_pipeline_f16: wgpu::RenderPipeline, + render_pipeline_srgb8: wgpu::RenderPipeline, +} + +impl GraphitePerPixelGraphicsPipeline { + pub fn new(device: &Arc, fragment_shader_name: &str) -> Self { + let shader_module = device.create_shader_module(ShaderModuleDescriptor { + label: Some("graphite wgsl shader"), + source: ShaderSource::Wgsl(Cow::Borrowed(WGSL_SHADER)), + }); + let create_render_pipeline = |format| { + device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("gpu_invert"), + layout: None, + vertex: VertexState { + module: &shader_module, + entry_point: Some("fullscreen_vertexfullscreen_vertex"), + compilation_options: Default::default(), + buffers: &[], + }, + primitive: PrimitiveState { + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: FrontFace::Ccw, + cull_mode: Some(Face::Back), + unclipped_depth: false, + polygon_mode: PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: Default::default(), + fragment: Some(FragmentState { + module: &shader_module, + entry_point: Some(fragment_shader_name), + compilation_options: Default::default(), + targets: &[Some(ColorTargetState { + format, + blend: None, + write_mask: Default::default(), + })], + }), + multiview: None, + cache: None, + }) + }; + Self { + device: device.clone(), + render_pipeline_f32: create_render_pipeline(TextureFormat::Rgba32Float), + render_pipeline_f16: create_render_pipeline(TextureFormat::Rgba16Float), + render_pipeline_srgb8: create_render_pipeline(TextureFormat::Rgba8UnormSrgb), + } + } + + pub fn get(&self, format: TextureFormat) -> &wgpu::RenderPipeline { + match format { + TextureFormat::Rgba32Float => &self.render_pipeline_f32, + TextureFormat::Rgba16Float => &self.render_pipeline_f16, + TextureFormat::Rgba8UnormSrgb => &self.render_pipeline_srgb8, + _ => panic!("unsupported"), + } + } + + pub fn run(&self, input: TextureFrameTable, queue: &Arc) -> TextureFrameTable { + let device = &self.device; + let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") }); + let out = input + .instance_ref_iter() + .map(|instance| { + let view_in = instance.instance.texture.create_view(&TextureViewDescriptor::default()); + let format = instance.instance.texture.format(); + let pipeline = self.get(format); + + let bind_group = device.create_bind_group(&BindGroupDescriptor { + label: Some("gpu_invert bind group"), + // `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that + layout: &pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view_in), + }], + }); + + let tex_out = device.create_texture(&TextureDescriptor { + label: Some("gpu_invert_out"), + size: instance.instance.texture.size(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[format], + }); + + let view_out = tex_out.create_view(&TextureViewDescriptor::default()); + let mut rp = cmd.begin_render_pass(&RenderPassDescriptor { + label: Some("gpu_invert rp"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &view_out, + resolve_target: None, + ops: Operations { + // should be dont_care but wgpu doesn't expose that + load: LoadOp::Clear(wgpu::Color::BLACK), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rp.set_pipeline(&pipeline); + rp.set_bind_group(0, Some(&bind_group), &[]); + rp.draw(0..3, 0..1); + + Instance { + instance: ImageTexture { texture: Arc::new(tex_out) }, + transform: *instance.transform, + alpha_blending: *instance.alpha_blending, + source_node_id: *instance.source_node_id, + } + }) + .collect::(); + queue.submit([cmd.finish()]); + out + } +} diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 9960f9e0c8..6bcf6fb69a 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -1,5 +1,6 @@ mod context; mod executor; +mod gcore_shader_nodes; use anyhow::{Result, bail}; pub use context::Context; From 709a40799f7afc4168ef6bb9740e14b1ca895bfe Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Thu, 5 Jun 2025 16:31:35 +0200 Subject: [PATCH 05/10] update rust-gpu to fix ci fail --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f04ee9c183..cbe08b97c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,7 +646,7 @@ dependencies = [ [[package]] name = "cargo-gpu" version = "0.1.0" -source = "git+https://github.com/rust-gpu/cargo-gpu?rev=7f5358bf3bac7363c8017409942464365ca61fd8#7f5358bf3bac7363c8017409942464365ca61fd8" +source = "git+https://github.com/rust-gpu/cargo-gpu?rev=d8ef9f58257622c4f3eae215dc950f0c628fa3c1#d8ef9f58257622c4f3eae215dc950f0c628fa3c1" dependencies = [ "anyhow", "cargo_metadata", diff --git a/Cargo.toml b/Cargo.toml index c8a9f2dac9..2f595c980e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,7 @@ kurbo = { version = "0.11.0", features = ["serde"] } petgraph = { version = "0.7.1", default-features = false, features = [ "graphmap", ] } -cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "7f5358bf3bac7363c8017409942464365ca61fd8", features = ["wgsl-out"] } +cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "d8ef9f58257622c4f3eae215dc950f0c628fa3c1", features = ["wgsl-out"] } [profile.dev] opt-level = 1 From 17f1fa1f48dd3c5964ae6de248861db1baa579d8 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Sat, 7 Jun 2025 12:27:25 +0200 Subject: [PATCH 06/10] make spirv-std git repo lowercase --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbe08b97c6..34d54b3074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6264,7 +6264,7 @@ dependencies = [ [[package]] name = "spirv-std" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" dependencies = [ "bitflags 1.3.2", "glam", @@ -6277,7 +6277,7 @@ dependencies = [ [[package]] name = "spirv-std-macros" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" dependencies = [ "proc-macro2", "quote", @@ -6288,7 +6288,7 @@ dependencies = [ [[package]] name = "spirv-std-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d" [[package]] name = "stable_deref_trait" diff --git a/Cargo.toml b/Cargo.toml index 2f595c980e..759c50b3db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ axum = "0.8" chrono = "0.4" ron = "0.8" fastnoise-lite = "1.1" -spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "9a357691334b9cdd13c82a740ced97c5d857bf4d" } +spirv-std = { git = "https://github.com/rust-gpu/rust-gpu", rev = "9a357691334b9cdd13c82a740ced97c5d857bf4d" } wgpu-types = "23" wgpu = "23" once_cell = "1.13" # Remove when `core::cell::LazyCell` () is stabilized in Rust 1.80 and we bump our MSRV From 9cf82cc7d1a9ac43168cc51784a04ceb90e41775 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Sat, 7 Jun 2025 12:36:21 +0200 Subject: [PATCH 07/10] retrigger ci From 239082baa0d58a9169f43120133d43cc490e8db2 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 7 Jun 2025 03:40:17 -0700 Subject: [PATCH 08/10] another attempt to trigger CI From e15080cacd9269632a50b2c863fb37d6c3275068 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Sat, 7 Jun 2025 12:42:03 +0200 Subject: [PATCH 09/10] retrigger ci2 From 6e5f28632a1f754fd127858a939b998906aecad1 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Sat, 7 Jun 2025 13:16:59 +0200 Subject: [PATCH 10/10] ci: run on PRs not targeting master --- .github/workflows/build-dev-and-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-dev-and-ci.yml b/.github/workflows/build-dev-and-ci.yml index 4e443817ce..adad7d842b 100644 --- a/.github/workflows/build-dev-and-ci.yml +++ b/.github/workflows/build-dev-and-ci.yml @@ -5,8 +5,7 @@ on: branches: - master pull_request: - branches: - - master + env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_REPLACEMENT: