From fe9645f90e5cca9d3a20f84b380a347c5a7e8879 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 28 Aug 2025 11:32:06 +0200 Subject: [PATCH 01/31] cargo: remove unused `editor/ron` feature --- desktop/wrapper/Cargo.toml | 1 - editor/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/desktop/wrapper/Cargo.toml b/desktop/wrapper/Cargo.toml index 0a8704978a..5e61337515 100644 --- a/desktop/wrapper/Cargo.toml +++ b/desktop/wrapper/Cargo.toml @@ -16,7 +16,6 @@ gpu = ["graphite-editor/gpu"] # Local dependencies graphite-editor = { path = "../../editor", features = [ "gpu", - "ron", "vello", ] } graphene-std = { workspace = true } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 6b36914df7..faf444b1a1 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -16,7 +16,6 @@ wasm = ["wasm-bindgen", "graphene-std/wasm"] gpu = ["interpreted-executor/gpu", "wgpu-executor"] resvg = ["graphene-std/resvg"] vello = ["graphene-std/vello", "resvg"] -ron = [] [dependencies] # Local dependencies From 8ae6cf0e7f67a883e72b5b0211056c0ecb7a26e5 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 28 Aug 2025 15:28:18 +0200 Subject: [PATCH 02/31] cargo: add wgpu-executor as a member, sort list --- Cargo.toml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a19b8907a..67c3d132cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,28 @@ [workspace] members = [ - "editor", "desktop", "desktop/wrapper", - "proc-macros", + "editor", "frontend/wasm", + "libraries/dyn-any", + "libraries/path-bool", + "libraries/math-parser", "node-graph/gapplication-io", "node-graph/gbrush", "node-graph/gcore", "node-graph/gcore-shaders", - "node-graph/gstd", "node-graph/gmath-nodes", "node-graph/gpath-bool", "node-graph/graph-craft", "node-graph/graphene-cli", "node-graph/graster-nodes", + "node-graph/gstd", "node-graph/gsvg-renderer", "node-graph/interpreted-executor", "node-graph/node-macro", "node-graph/preprocessor", - "libraries/dyn-any", - "libraries/path-bool", - "libraries/math-parser", + "node-graph/wgpu-executor", + "proc-macros", ] default-members = [ "editor", From 2b7bcd285ad101fbd7626ecb8f120c61ce37a793 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 28 Aug 2025 15:30:38 +0200 Subject: [PATCH 03/31] cargo: test every crate by default --- .github/workflows/build-dev-and-ci.yml | 2 +- Cargo.toml | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dev-and-ci.yml b/.github/workflows/build-dev-and-ci.yml index c55019fa2b..52beaa02fb 100644 --- a/.github/workflows/build-dev-and-ci.yml +++ b/.github/workflows/build-dev-and-ci.yml @@ -108,7 +108,7 @@ jobs: - name: ๐Ÿงช Run Rust tests run: | - mold -run cargo test --all-features --workspace + mold -run cargo test --all-features - name: ๐Ÿ“ƒ Generate code documentation info for website if: github.ref == 'refs/heads/master' diff --git a/Cargo.toml b/Cargo.toml index 67c3d132cf..4ccbb6423c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,20 +25,29 @@ members = [ "proc-macros", ] default-members = [ + "desktop", + "desktop/wrapper", "editor", "frontend/wasm", + "libraries/dyn-any", + "libraries/path-bool", + "libraries/math-parser", + "node-graph/gapplication-io", "node-graph/gbrush", "node-graph/gcore", "node-graph/gcore-shaders", - "node-graph/gstd", "node-graph/gmath-nodes", "node-graph/gpath-bool", "node-graph/graph-craft", "node-graph/graphene-cli", "node-graph/graster-nodes", + "node-graph/gstd", "node-graph/gsvg-renderer", "node-graph/interpreted-executor", "node-graph/node-macro", + "node-graph/preprocessor", + "node-graph/wgpu-executor", + "proc-macros", ] resolver = "2" From 0d81b4e37b0c4b83f8978df2885aba494ceac968 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 28 Aug 2025 15:30:53 +0200 Subject: [PATCH 04/31] cargo: disable testing of `graphite-proc-macros` --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4ccbb6423c..3bdba5d6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,8 @@ default-members = [ "node-graph/node-macro", "node-graph/preprocessor", "node-graph/wgpu-executor", - "proc-macros", + # blocked by https://github.com/rust-lang/cargo/issues/15890 +# "proc-macros", ] resolver = "2" From 4d0d7229b166597567ef576d07dc277c03ffee83 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 7 Aug 2025 20:19:24 +0000 Subject: [PATCH 05/31] Try providing cargo-rust-gpu to nix devs --- .nix/flake.nix | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/.nix/flake.nix b/.nix/flake.nix index ba9bd62714..da61ceafae 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -34,11 +34,48 @@ inherit system overlays; }; - rustc-wasm = pkgs.rust-bin.stable.latest.default.override { + rustExtensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; + + rust = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; - extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; + extensions = rustExtensions; + }; + + rustNightlyPkg = pkgs.rust-bin.nightly."2025-06-23".default.override { + extensions = rustExtensions ++ [ "rustc-dev" "llvm-tools" ]; }; + rustPlatformNightly = pkgs.makeRustPlatform { + cargo = rustNightlyPkg; + rustc = rustNightlyPkg; + }; + + rustc_codegen_spirv = (rustPlatformNightly.buildRustPackage.override { + stdenv = pkgs.llvmPackages.stdenv; + }) (finalAttrs: { + pname = "rustc_codegen_spirv"; + version = "0-unstable-2025-08-04"; + src = pkgs.fetchFromGitHub { + owner = "Rust-GPU"; + repo = "rust-gpu"; + rev = "df1628a032d22c864397417c2871b74d602af986"; + hash = "sha256-AFt3Nc+NqK8DxNUhDBcOUmk3XDVcoToVeFIMYNszdbY="; + }; + cargoHash = "sha256-en3BYJWQabH064xeAwYQrvcr6EuWg/QjvsG+Jd6HHCk"; + + cargoBuildFlags = [ "-p" "rustc_codegen_spirv" ]; + + doCheck = false; + }); + + cargoRustGpuBuild = pkgs.writeShellScriptBin "cargo-rust-gpu" '' + #!${pkgs.bash}/bin/bash + + export PATH="${pkgs.lib.makeBinPath [rustNightlyPkg]}" + export RUSTFLAGS="-Zcodegen-backend=${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so" + exec cargo +nightly $@ + ''; + libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { version = "139.0.17"; gitRevision = "6c347eb"; @@ -85,7 +122,7 @@ # Development tools that don't need to be in LD_LIBRARY_PATH buildTools = [ - rustc-wasm + rust pkgs.nodejs pkgs.nodePackages.npm pkgs.binaryen @@ -97,6 +134,8 @@ # Linker pkgs.mold + + cargoRustGpuBuild ]; # Development tools that don't need to be in LD_LIBRARY_PATH devTools = with pkgs; [ From a54fa860918aa26ac9e61f5ca2d7eadc10e9dbaf Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 7 Aug 2025 22:29:18 +0000 Subject: [PATCH 06/31] Try providing cargo-rust-gpu to nix devs --- .nix/flake.nix | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.nix/flake.nix b/.nix/flake.nix index da61ceafae..78ef0a372b 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -50,9 +50,7 @@ rustc = rustNightlyPkg; }; - rustc_codegen_spirv = (rustPlatformNightly.buildRustPackage.override { - stdenv = pkgs.llvmPackages.stdenv; - }) (finalAttrs: { + rustc_codegen_spirv = rustPlatformNightly.buildRustPackage (finalAttrs: { pname = "rustc_codegen_spirv"; version = "0-unstable-2025-08-04"; src = pkgs.fetchFromGitHub { @@ -63,19 +61,32 @@ }; cargoHash = "sha256-en3BYJWQabH064xeAwYQrvcr6EuWg/QjvsG+Jd6HHCk"; - cargoBuildFlags = [ "-p" "rustc_codegen_spirv" ]; + cargoBuildFlags = [ "-p" "rustc_codegen_spirv" "--features=use-installed-tools" "--no-default-features" ]; doCheck = false; }); - cargoRustGpuBuild = pkgs.writeShellScriptBin "cargo-rust-gpu" '' + cargoGpuPkg = rustPlatformNightly.buildRustPackage (finalAttrs: { + pname = "cargo-gpu"; + version = "0-unstable-2025-07-24"; + src = pkgs.fetchFromGitHub { + owner = "Rust-GPU"; + repo = "cargo-gpu"; + rev = "a2ad3574dd32142ff661994e0d79448a45d18f47"; + hash = "sha256-YGu9Cuw+pcN9/rCuCxImouzsQ3ScHF+cW6zgxMm0XGI="; + }; + cargoHash = "sha256-tyad9kO90uwAnMQYa09takIBXifrumSx2C4rpSK95aM="; + + doCheck = false; + }); + + cargoNightlyPkg = pkgs.writeShellScriptBin "cargo-nightly" '' #!${pkgs.bash}/bin/bash - export PATH="${pkgs.lib.makeBinPath [rustNightlyPkg]}" - export RUSTFLAGS="-Zcodegen-backend=${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so" - exec cargo +nightly $@ + exec ${rustNightlyPkg}/bin/cargo $@ ''; + libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { version = "139.0.17"; gitRevision = "6c347eb"; @@ -135,7 +146,9 @@ # Linker pkgs.mold - cargoRustGpuBuild + pkgs.spirv-tools + cargoNightlyPkg + cargoGpuPkg ]; # Development tools that don't need to be in LD_LIBRARY_PATH devTools = with pkgs; [ @@ -158,6 +171,8 @@ CEF_PATH = libcefPath; XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS"; + RUSTC_CODEGEN_SPIRV="${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so"; + shellHook = '' alias cargo='mold --run cargo' ''; From 0fd1e8757f3f71e38288cf990ba6258870e99e93 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 8 Aug 2025 18:19:36 +0000 Subject: [PATCH 07/31] Add exec with rust-gpu env script to flake --- .nix/flake.nix | 52 +++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/.nix/flake.nix b/.nix/flake.nix index 78ef0a372b..eac77dd2c8 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -35,22 +35,19 @@ }; rustExtensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; - rust = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; extensions = rustExtensions; }; - rustNightlyPkg = pkgs.rust-bin.nightly."2025-06-23".default.override { + rustGPUToolchainPkg = pkgs.rust-bin.nightly."2025-06-23".default.override { extensions = rustExtensions ++ [ "rustc-dev" "llvm-tools" ]; }; - - rustPlatformNightly = pkgs.makeRustPlatform { - cargo = rustNightlyPkg; - rustc = rustNightlyPkg; + rustGPUToolchainRustPlatform = pkgs.makeRustPlatform { + cargo = rustGPUToolchainPkg; + rustc = rustGPUToolchainPkg; }; - - rustc_codegen_spirv = rustPlatformNightly.buildRustPackage (finalAttrs: { + rustc_codegen_spirv = rustGPUToolchainRustPlatform.buildRustPackage (finalAttrs: { pname = "rustc_codegen_spirv"; version = "0-unstable-2025-08-04"; src = pkgs.fetchFromGitHub { @@ -60,33 +57,29 @@ hash = "sha256-AFt3Nc+NqK8DxNUhDBcOUmk3XDVcoToVeFIMYNszdbY="; }; cargoHash = "sha256-en3BYJWQabH064xeAwYQrvcr6EuWg/QjvsG+Jd6HHCk"; - cargoBuildFlags = [ "-p" "rustc_codegen_spirv" "--features=use-installed-tools" "--no-default-features" ]; - doCheck = false; }); - cargoGpuPkg = rustPlatformNightly.buildRustPackage (finalAttrs: { - pname = "cargo-gpu"; - version = "0-unstable-2025-07-24"; - src = pkgs.fetchFromGitHub { - owner = "Rust-GPU"; - repo = "cargo-gpu"; - rev = "a2ad3574dd32142ff661994e0d79448a45d18f47"; - hash = "sha256-YGu9Cuw+pcN9/rCuCxImouzsQ3ScHF+cW6zgxMm0XGI="; - }; - cargoHash = "sha256-tyad9kO90uwAnMQYa09takIBXifrumSx2C4rpSK95aM="; + # Wrapper script for running rust commands with the rust toolchain used by rust-gpu. + # For example `rust-gpu cargo --version` or `rust-gpu rustc --version`. + execWithRustGPUEnvironment = pkgs.writeShellScriptBin "rust-gpu" '' + #!${pkgs.lib.getExe pkgs.bash} - doCheck = false; - }); + filtered_args=() + for arg in "$@"; do + case "$arg" in + +nightly|+nightly-*) ;; + *) filtered_args+=("$arg") ;; + esac + done - cargoNightlyPkg = pkgs.writeShellScriptBin "cargo-nightly" '' - #!${pkgs.bash}/bin/bash + export PATH="${pkgs.lib.makeBinPath [ rustGPUToolchainPkg pkgs.spirv-tools ]}:$PATH" + export RUSTC_CODEGEN_SPIRV_PATH="${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so" - exec ${rustNightlyPkg}/bin/cargo $@ + exec ${"\${filtered_args[@]}"} ''; - libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { version = "139.0.17"; gitRevision = "6c347eb"; @@ -99,7 +92,6 @@ strip $out/lib/* ''; }); - libcefPath = pkgs.runCommand "libcef-path" {} '' mkdir -p $out @@ -146,9 +138,7 @@ # Linker pkgs.mold - pkgs.spirv-tools - cargoNightlyPkg - cargoGpuPkg + execWithRustGPUEnvironment ]; # Development tools that don't need to be in LD_LIBRARY_PATH devTools = with pkgs; [ @@ -171,8 +161,6 @@ CEF_PATH = libcefPath; XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS"; - RUSTC_CODEGEN_SPIRV="${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so"; - shellHook = '' alias cargo='mold --run cargo' ''; From d60f9e55cbc95e74b509be2d2e042c93fa7e5792 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Mon, 4 Aug 2025 20:45:10 +0200 Subject: [PATCH 08/31] shaders: shader compilation setup --- Cargo.lock | 431 +++++++++++++++++++- Cargo.toml | 4 + node-graph/graster-nodes/Cargo.toml | 6 + node-graph/graster-nodes/shaders/Cargo.toml | 13 + node-graph/graster-nodes/shaders/build.rs | 25 ++ node-graph/graster-nodes/shaders/src/lib.rs | 1 + 6 files changed, 472 insertions(+), 8 deletions(-) create mode 100644 node-graph/graster-nodes/shaders/Cargo.toml create mode 100644 node-graph/graster-nodes/shaders/build.rs create mode 100644 node-graph/graster-nodes/shaders/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f23a90db8b..1e44bd8cd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -643,6 +643,75 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-gpu" +version = "0.1.0" +source = "git+https://github.com/rust-gpu/cargo-gpu?rev=e8ba9a0421c27a715277da116a6f2d59cdd3266d#e8ba9a0421c27a715277da116a6f2d59cdd3266d" +dependencies = [ + "anyhow", + "cargo_metadata", + "clap", + "crossterm", + "directories", + "env_logger", + "log", + "relative-path", + "rustc_codegen_spirv-target-specs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver", + "serde", + "serde_json", + "spirv-builder", +] + +[[package]] +name = "cargo-platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 2.0.12", + "toml", + "unicode-xid", + "url", +] + +[[package]] +name = "cargo_metadata" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868" +dependencies = [ + "camino", + "cargo-platform", + "cargo-util-schemas", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cast" version = "0.3.0" @@ -1038,6 +1107,33 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.9.1", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.0.7", + "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.4" @@ -1099,6 +1195,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "diff" version = "0.1.13" @@ -1115,6 +1232,15 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1333,6 +1459,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.13" @@ -1584,6 +1720,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 = "futures" version = "0.3.31" @@ -2020,6 +2165,7 @@ dependencies = [ "glam", "graphene-core", "graphene-core-shaders", + "graphene-raster-nodes-shaders", "image", "kurbo", "ndarray", @@ -2029,9 +2175,18 @@ dependencies = [ "rand_chacha 0.9.0", "serde", "specta", + "spirv-std", "tokio", ] +[[package]] +name = "graphene-raster-nodes-shaders" +version = "0.1.0" +dependencies = [ + "cargo-gpu", + "env_logger", +] + [[package]] name = "graphene-std" version = "0.1.0" @@ -2662,6 +2817,26 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.1", + "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 = "interpolate_name" version = "0.2.4" @@ -2868,6 +3043,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 = "kurbo" version = "0.11.2" @@ -2925,9 +3120,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -3130,6 +3325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -3155,7 +3351,7 @@ dependencies = [ "petgraph 0.8.2", "rustc-hash 1.1.0", "spirv", - "strum", + "strum 0.26.3", "thiserror 2.0.12", "unicode-ident", ] @@ -3261,7 +3457,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "strum", + "strum 0.26.3", "syn 2.0.104", ] @@ -3281,6 +3477,30 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.9.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3768,6 +3988,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "4.6.0" @@ -4440,6 +4669,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" @@ -4565,6 +4800,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0" +dependencies = [ + "serde", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -4705,6 +4949,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.25" @@ -4723,6 +4977,33 @@ 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-target-specs" +version = "0.9.0" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" +dependencies = [ + "serde", + "strum 0.27.2", + "thiserror 2.0.12", +] + +[[package]] +name = "rustc_codegen_spirv-types" +version = "0.9.0" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" +dependencies = [ + "rspirv", + "serde", + "serde_json", + "spirv", +] + [[package]] name = "rustix" version = "0.38.44" @@ -4895,6 +5176,9 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -4905,6 +5189,27 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -4929,9 +5234,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -5003,6 +5308,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.5" @@ -5180,8 +5506,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ "bitflags 2.9.1", + "serde", +] + +[[package]] +name = "spirv-builder" +version = "0.9.0" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" +dependencies = [ + "cargo_metadata", + "clap", + "log", + "memchr", + "notify", + "raw-string", + "rustc_codegen_spirv-target-specs 0.9.0 (git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c)", + "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?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" +dependencies = [ + "bitflags 1.3.2", + "glam", + "libm", + "num-traits", + "spirv-std-macros", + "spirv-std-types", +] + +[[package]] +name = "spirv-std-macros" +version = "0.9.0" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" +dependencies = [ + "proc-macro2", + "quote", + "spirv-std-types", + "syn 2.0.104", ] +[[package]] +name = "spirv-std-types" +version = "0.9.0" +source = "git+https://github.com/rust-gpu/rust-gpu?rev=3f05f5482824e3b1fbb44c9ef90a8795a0204c7c#3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5215,7 +5590,16 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -5231,6 +5615,18 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5639,9 +6035,16 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -5769,6 +6172,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" @@ -5846,6 +6255,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unit-prefix" version = "0.5.1" @@ -6427,7 +6842,7 @@ dependencies = [ "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", - "ordered-float", + "ordered-float 4.6.0", "parking_lot", "portable-atomic", "profiling", diff --git a/Cargo.toml b/Cargo.toml index 3bdba5d6bb..4062302992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "node-graph/graph-craft", "node-graph/graphene-cli", "node-graph/graster-nodes", + "node-graph/graster-nodes/shaders", "node-graph/gstd", "node-graph/gsvg-renderer", "node-graph/interpreted-executor", @@ -36,6 +37,7 @@ default-members = [ "node-graph/gbrush", "node-graph/gcore", "node-graph/gcore-shaders", + "node-graph/graster-nodes/shaders", "node-graph/gmath-nodes", "node-graph/gpath-bool", "node-graph/graph-craft", @@ -189,6 +191,8 @@ tracing = "0.1.41" rfd = "0.15.4" open = "5.3.2" poly-cool = "0.2.0" +spirv-std = { git = "https://github.com/rust-gpu/rust-gpu", rev = "3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" } +cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "e8ba9a0421c27a715277da116a6f2d59cdd3266d" } [profile.dev] opt-level = 1 diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index c0844b98b1..da848d39b3 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -6,10 +6,14 @@ description = "graphene raster data format" authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" +[lib] +crate-type = ["rlib", "dylib"] + [features] default = ["std"] std = [ "dep:graphene-core", + "dep:graphene-raster-nodes-shaders", "dep:dyn-any", "dep:image", "dep:ndarray", @@ -31,10 +35,12 @@ node-macro = { workspace = true } # Local std dependencies dyn-any = { workspace = true, optional = true } graphene-core = { workspace = true, optional = true } +graphene-raster-nodes-shaders = { path = "./shaders", optional = true } # Workspace dependencies bytemuck = { workspace = true } glam = { workspace = true } +spirv-std = { workspace = true } num-traits = { workspace = true } # Workspace std dependencies diff --git a/node-graph/graster-nodes/shaders/Cargo.toml b/node-graph/graster-nodes/shaders/Cargo.toml new file mode 100644 index 0000000000..65a4fed409 --- /dev/null +++ b/node-graph/graster-nodes/shaders/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "graphene-raster-nodes-shaders" +version = "0.1.0" +edition = "2024" +description = "graphene raster data format" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[dependencies] + +[build-dependencies] +cargo-gpu = { workspace = true } +env_logger = { workspace = true } diff --git a/node-graph/graster-nodes/shaders/build.rs b/node-graph/graster-nodes/shaders/build.rs new file mode 100644 index 0000000000..6369056adf --- /dev/null +++ b/node-graph/graster-nodes/shaders/build.rs @@ -0,0 +1,25 @@ +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(concat!(env!("CARGO_MANIFEST_DIR"), "/..")); + + // 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-naga-wgsl"); + builder.print_metadata = MetadataPrintout::DependencyOnly; + builder.spirv_metadata = SpirvMetadata::Full; + builder.shader_crate_features.default_features = false; + let wgsl_result = builder.build()?; + let path_to_spv = wgsl_result.module.unwrap_single(); + + // needs to be fixed upstream + let path_to_wgsl = path_to_spv.with_extension("wgsl"); + + println!("cargo::rustc-env=WGSL_SHADER_PATH={}", path_to_wgsl.display()); + Ok(()) +} diff --git a/node-graph/graster-nodes/shaders/src/lib.rs b/node-graph/graster-nodes/shaders/src/lib.rs new file mode 100644 index 0000000000..51053ca7ee --- /dev/null +++ b/node-graph/graster-nodes/shaders/src/lib.rs @@ -0,0 +1 @@ +pub const WGSL_SHADER: &str = include_str!(env!("WGSL_SHADER_PATH")); From 8853d4e8ab50f72dcf02419293c10cd1a3918746 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 8 Aug 2025 16:16:35 +0000 Subject: [PATCH 09/31] nix: use rustc_codegen_spirv.so from nix --- node-graph/graster-nodes/shaders/build.rs | 17 +++++++++--- .../shaders/spirv-unknown-naga-wgsl.json | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 node-graph/graster-nodes/shaders/spirv-unknown-naga-wgsl.json diff --git a/node-graph/graster-nodes/shaders/build.rs b/node-graph/graster-nodes/shaders/build.rs index 6369056adf..50061aefcd 100644 --- a/node-graph/graster-nodes/shaders/build.rs +++ b/node-graph/graster-nodes/shaders/build.rs @@ -6,11 +6,22 @@ pub fn main() -> Result<(), Box> { let shader_crate = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/..")); - // 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()?; + let rustc_codegen_spirv_path = std::env::var("RUSTC_CODEGEN_SPIRV_PATH").unwrap_or_default(); + let mut builder = if rustc_codegen_spirv_path.is_empty() { + // install the toolchain and build the `rustc_codegen_spirv` codegen backend with it + cargo_gpu::Install::from_shader_crate(shader_crate.clone()) + .run()? + .to_spirv_builder(shader_crate, "spirv-unknown-naga-wgsl") + } else { + // use the `RUSTC_CODEGEN_SPIRV` environment variable to find the codegen backend + let mut builder = cargo_gpu::spirv_builder::SpirvBuilder::new(shader_crate.clone(), "spirv-unknown-naga-wgsl"); + builder.rustc_codegen_spirv_location = Some(PathBuf::from(rustc_codegen_spirv_path)); + builder.toolchain_overwrite = Some("nightly".to_string()); + builder.path_to_target_spec = Some(PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/spirv-unknown-naga-wgsl.json"))); + builder + }; // build the shader crate - let mut builder = backend.to_spirv_builder(shader_crate, "spirv-unknown-naga-wgsl"); builder.print_metadata = MetadataPrintout::DependencyOnly; builder.spirv_metadata = SpirvMetadata::Full; builder.shader_crate_features.default_features = false; diff --git a/node-graph/graster-nodes/shaders/spirv-unknown-naga-wgsl.json b/node-graph/graster-nodes/shaders/spirv-unknown-naga-wgsl.json new file mode 100644 index 0000000000..00f17162c0 --- /dev/null +++ b/node-graph/graster-nodes/shaders/spirv-unknown-naga-wgsl.json @@ -0,0 +1,26 @@ +{ + "allows-weak-linkage": false, + "arch": "spirv", + "crt-objects-fallback": "false", + "crt-static-allows-dylibs": true, + "crt-static-respected": true, + "data-layout": "e-m:e-p:32:32:32-i64:64-n8:16:32:64", + "dll-prefix": "", + "dll-suffix": ".spv.json", + "dynamic-linking": true, + "emit-debug-gdb-scripts": false, + "env": "naga-wgsl", + "linker-flavor": "unix", + "linker-is-gnu": false, + "llvm-target": "spirv-unknown-naga-wgsl", + "main-needs-argc-argv": false, + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "simd-types-indirect": false, + "target-pointer-width": "32" +} From f0174b1b3be5a28c649a0c715a6d4ff70663e74b Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 30 Jul 2025 17:38:09 +0200 Subject: [PATCH 10/31] shaders: codegen for per_pixel_adjust shader nodes --- .../gcore-shaders/src/color/color_types.rs | 16 +++ node-graph/graster-nodes/src/adjustments.rs | 15 +++ .../graster-nodes/src/blending_nodes.rs | 3 + node-graph/node-macro/src/codegen.rs | 6 +- node-graph/node-macro/src/parsing.rs | 6 + .../{shader_nodes.rs => shader_nodes/mod.rs} | 29 ++++- .../src/shader_nodes/per_pixel_adjust.rs | 110 ++++++++++++++++++ 7 files changed, 179 insertions(+), 6 deletions(-) rename node-graph/node-macro/src/{shader_nodes.rs => shader_nodes/mod.rs} (50%) create mode 100644 node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs diff --git a/node-graph/gcore-shaders/src/color/color_types.rs b/node-graph/gcore-shaders/src/color/color_types.rs index 2cb4d231bd..63517341e8 100644 --- a/node-graph/gcore-shaders/src/color/color_types.rs +++ b/node-graph/gcore-shaders/src/color/color_types.rs @@ -3,6 +3,7 @@ use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}; use bytemuck::{Pod, Zeroable}; use core::fmt::Debug; use core::hash::Hash; +use glam::Vec4; use half::f16; #[cfg(not(feature = "std"))] use num_traits::Euclid; @@ -1075,6 +1076,21 @@ impl Color { ..*self } } + + #[inline(always)] + pub const fn from_vec4(vec: Vec4) -> Self { + Self { + red: vec.x, + green: vec.y, + blue: vec.z, + alpha: vec.w, + } + } + + #[inline(always)] + pub fn to_vec4(&self) -> Vec4 { + Vec4::new(self.red, self.green, self.blue, self.alpha) + } } #[cfg(test)] diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 1274ef5fe4..f0f841b4bd 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -52,6 +52,7 @@ fn luminance>( Table, GradientStops, )] + #[gpu_image] mut input: T, luminance_calc: LuminanceCalculation, ) -> T { @@ -77,6 +78,7 @@ fn gamma_correction>( Table, GradientStops, )] + #[gpu_image] mut input: T, #[default(2.2)] #[range((0.01, 10.))] @@ -98,6 +100,7 @@ fn extract_channel>( Table, GradientStops, )] + #[gpu_image] mut input: T, channel: RedGreenBlueAlpha, ) -> T { @@ -122,6 +125,7 @@ fn make_opaque>( Table, GradientStops, )] + #[gpu_image] mut input: T, ) -> T { input.adjust(|color| { @@ -148,6 +152,7 @@ fn brightness_contrast>( Table, GradientStops, )] + #[gpu_image] mut input: T, brightness: SignedPercentageF32, contrast: SignedPercentageF32, @@ -238,6 +243,7 @@ fn levels>( Table, GradientStops, )] + #[gpu_image] mut image: T, #[default(0.)] shadows: PercentageF32, #[default(50.)] midtones: PercentageF32, @@ -306,6 +312,7 @@ fn black_and_white>( Table, GradientStops, )] + #[gpu_image] mut image: T, #[default(Color::BLACK)] tint: Color, #[default(40.)] @@ -379,6 +386,7 @@ fn hue_saturation>( Table, GradientStops, )] + #[gpu_image] mut input: T, hue_shift: AngleF32, saturation_shift: SignedPercentageF32, @@ -414,6 +422,7 @@ fn invert>( Table, GradientStops, )] + #[gpu_image] mut input: T, ) -> T { input.adjust(|color| { @@ -437,6 +446,7 @@ fn threshold>( Table, GradientStops, )] + #[gpu_image] mut image: T, #[default(50.)] min_luminance: PercentageF32, #[default(100.)] max_luminance: PercentageF32, @@ -483,6 +493,7 @@ fn vibrance>( Table, GradientStops, )] + #[gpu_image] mut image: T, vibrance: SignedPercentageF32, ) -> T { @@ -649,6 +660,7 @@ fn channel_mixer>( Table, GradientStops, )] + #[gpu_image] mut image: T, monochrome: bool, @@ -778,6 +790,7 @@ fn selective_color>( Table, GradientStops, )] + #[gpu_image] mut image: T, mode: RelativeAbsolute, @@ -921,6 +934,7 @@ fn posterize>( Table, GradientStops, )] + #[gpu_image] mut input: T, #[default(4)] #[hard_min(2.)] @@ -955,6 +969,7 @@ fn exposure>( Table, GradientStops, )] + #[gpu_image] mut input: T, exposure: f32, offset: f32, diff --git a/node-graph/graster-nodes/src/blending_nodes.rs b/node-graph/graster-nodes/src/blending_nodes.rs index 6efc335fbb..182f6a802d 100644 --- a/node-graph/graster-nodes/src/blending_nodes.rs +++ b/node-graph/graster-nodes/src/blending_nodes.rs @@ -141,6 +141,7 @@ fn blend + Send>( Table, GradientStops, )] + #[gpu_image] over: T, #[expose] #[implementations( @@ -149,6 +150,7 @@ fn blend + Send>( Table, GradientStops, )] + #[gpu_image] under: T, blend_mode: BlendMode, #[default(100.)] opacity: PercentageF32, @@ -165,6 +167,7 @@ fn color_overlay>( Table, GradientStops, )] + #[gpu_image] mut image: T, #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 57a8b75cfd..1c0c4f6517 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1,7 +1,7 @@ use crate::parsing::*; use convert_case::{Case, Casing}; use proc_macro_crate::FoundCrate; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{TokenStream as TokenStream2, TokenStream}; use quote::{ToTokens, format_ident, quote, quote_spanned}; use std::sync::atomic::AtomicU64; use syn::punctuated::Punctuated; @@ -295,6 +295,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result, pub number_mode_range: Option, pub implementations: Punctuated, + pub gpu_image: bool, } +/// a param of `impl Node` with `#[implementation(in -> out)]` #[derive(Clone, Debug)] pub struct NodeParsedField { pub input_type: Type, @@ -529,6 +533,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul .map_err(|e| Error::new_spanned(attr, format!("Invalid `step` for argument '{ident}': {e}\nUSAGE EXAMPLE: #[step(2.)]"))) }) .transpose()?; + let gpu_image = extract_attribute(attrs, "gpu_image").is_some(); let (is_node, node_input_type, node_output_type) = parse_node_type(&ty); let description = attrs @@ -590,6 +595,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul ty, value_source, implementations, + gpu_image, }), name, description, diff --git a/node-graph/node-macro/src/shader_nodes.rs b/node-graph/node-macro/src/shader_nodes/mod.rs similarity index 50% rename from node-graph/node-macro/src/shader_nodes.rs rename to node-graph/node-macro/src/shader_nodes/mod.rs index 919d3ef878..0720869d01 100644 --- a/node-graph/node-macro/src/shader_nodes.rs +++ b/node-graph/node-macro/src/shader_nodes/mod.rs @@ -1,10 +1,13 @@ -use crate::parsing::NodeFnAttributes; +use crate::parsing::{NodeFnAttributes, ParsedNodeFn}; +use crate::shader_nodes::per_pixel_adjust::PerPixelAdjust; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use strum::{EnumString, VariantNames}; +use strum::VariantNames; use syn::Error; use syn::parse::{Parse, ParseStream}; +pub mod per_pixel_adjust; + pub const STD_FEATURE_GATE: &str = "std"; pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream { @@ -16,17 +19,33 @@ pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream { } } -#[derive(Debug, EnumString, VariantNames)] +#[derive(Debug, VariantNames)] pub(crate) enum ShaderNodeType { - PerPixelAdjust, + PerPixelAdjust(PerPixelAdjust), } impl Parse for ShaderNodeType { fn parse(input: ParseStream) -> syn::Result { let ident: Ident = input.parse()?; Ok(match ident.to_string().as_str() { - "PerPixelAdjust" => ShaderNodeType::PerPixelAdjust, + "PerPixelAdjust" => ShaderNodeType::PerPixelAdjust(PerPixelAdjust::parse(input)?), _ => return Err(Error::new_spanned(&ident, format!("attr 'shader_node' must be one of {:?}", Self::VARIANTS))), }) } } + +pub trait CodegenShaderEntryPoint { + fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result; +} + +impl CodegenShaderEntryPoint for ShaderNodeType { + fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { + if parsed.is_async { + return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async")); + } + + match self { + ShaderNodeType::PerPixelAdjust(x) => x.codegen_shader_entry_point(parsed), + } + } +} diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs new file mode 100644 index 0000000000..0e220c8aae --- /dev/null +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -0,0 +1,110 @@ +use crate::parsing::{ParsedFieldType, ParsedNodeFn, RegularParsedField}; +use crate::shader_nodes::CodegenShaderEntryPoint; +use proc_macro2::{Ident, TokenStream}; +use quote::{ToTokens, format_ident, quote}; +use std::borrow::Cow; +use syn::parse::{Parse, ParseStream}; + +#[derive(Debug)] +pub struct PerPixelAdjust {} + +impl Parse for PerPixelAdjust { + fn parse(_input: ParseStream) -> syn::Result { + Ok(Self {}) + } +} + +impl CodegenShaderEntryPoint for PerPixelAdjust { + fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { + let fn_name = &parsed.fn_name; + let gpu_mod = format_ident!("{}_gpu", parsed.fn_name); + let spirv_image_ty = quote!(Image2d); + + // bindings for images start at 1 + let mut binding_cnt = 0; + let params = parsed + .fields + .iter() + .map(|f| { + let ident = &f.pat_ident; + match &f.ty { + ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), + ParsedFieldType::Regular(RegularParsedField { gpu_image: false, ty, .. }) => Ok(Param { + ident: Cow::Borrowed(&ident.ident), + ty: Cow::Owned(ty.to_token_stream()), + param_type: ParamType::Uniform, + }), + ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => { + binding_cnt += 1; + Ok(Param { + ident: Cow::Owned(format_ident!("image_{}", &ident.ident)), + ty: Cow::Borrowed(&spirv_image_ty), + param_type: ParamType::Image { binding: binding_cnt }, + }) + } + } + }) + .collect::>>()?; + + let uniform_members = params + .iter() + .filter_map(|Param { ident, ty, param_type }| match param_type { + ParamType::Image { .. } => None, + ParamType::Uniform => Some(quote! {#ident: #ty}), + }) + .collect::>(); + let image_params = params + .iter() + .filter_map(|Param { ident, ty, param_type }| match param_type { + ParamType::Image { binding } => Some(quote! {#[spirv(descriptor_set = 0, binding = #binding)] #ident: &#ty}), + ParamType::Uniform => None, + }) + .collect::>(); + let call_args = params + .iter() + .map(|Param { ident, param_type, .. }| match param_type { + ParamType::Image { .. } => quote!(Color::from_vec4(#ident.fetch_with(texel_coord, lod(0)))), + ParamType::Uniform => quote!(uniform.#ident), + }) + .collect::>(); + let context = quote!(()); + + Ok(quote! { + pub mod #gpu_mod { + use super::*; + use graphene_core_shaders::color::Color; + use spirv_std::spirv; + use spirv_std::glam::{Vec4, Vec4Swizzles}; + use spirv_std::image::{Image2d, ImageWithMethods}; + use spirv_std::image::sample_with::lod; + + pub struct Uniform { + #(#uniform_members),* + } + + #[spirv(fragment)] + pub fn entry_point( + #[spirv(frag_coord)] frag_coord: Vec4, + color_out: &mut Vec4, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] uniform: &Uniform, + #(#image_params),* + ) { + let texel_coord = frag_coord.xy().as_uvec2(); + let color: Color = #fn_name(#context, #(#call_args),*); + *color_out = color.to_vec4(); + } + } + }) + } +} + +struct Param<'a> { + ident: Cow<'a, Ident>, + ty: Cow<'a, TokenStream>, + param_type: ParamType, +} + +enum ParamType { + Image { binding: u32 }, + Uniform, +} From e6c391b85143503d0a72a850c55f8f24490316a3 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Sun, 3 Aug 2025 22:52:49 +0200 Subject: [PATCH 11/31] shaders: disable nodes needing bool --- node-graph/graster-nodes/src/adjustments.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index f0f841b4bd..14dc9ce384 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -69,7 +69,7 @@ fn luminance>( input } -#[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))] +#[node_macro::node(category("Raster"), cfg(feature = "std"))] fn gamma_correction>( _: impl Ctx, #[implementations( @@ -143,7 +143,7 @@ fn make_opaque>( // // Some further analysis available at: // https://geraldbakker.nl/psnumbers/brightness-contrast.html -#[node_macro::node(name("Brightness/Contrast"), category("Raster: Adjustment"), properties("brightness_contrast_properties"), shader_node(PerPixelAdjust))] +#[node_macro::node(name("Brightness/Contrast"), category("Raster: Adjustment"), properties("brightness_contrast_properties"), cfg(feature = "std"))] fn brightness_contrast>( _: impl Ctx, #[implementations( @@ -651,7 +651,7 @@ pub enum DomainWarpType { // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr -#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"), shader_node(PerPixelAdjust))] +#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"), cfg(feature = "std"))] fn channel_mixer>( _: impl Ctx, #[implementations( @@ -781,7 +781,7 @@ pub enum SelectiveColorChoice { // // Algorithm based on: // https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html -#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"), shader_node(PerPixelAdjust))] +#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"), cfg(feature = "std"))] fn selective_color>( _: impl Ctx, #[implementations( From 14a4b5582cccc9c0eeaa866aa8739be8cd89f920 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Sun, 3 Aug 2025 23:29:17 +0200 Subject: [PATCH 12/31] shaders: `#[repr(u32)]` some enums --- node-graph/graster-nodes/src/adjustments.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 14dc9ce384..35de34c76b 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -33,6 +33,7 @@ use num_traits::float::Float; #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] +#[repr(u32)] pub enum LuminanceCalculation { #[default] #[label("sRGB")] @@ -562,6 +563,7 @@ pub enum RedGreenBlue { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Radio)] +#[repr(u32)] pub enum RedGreenBlueAlpha { #[default] Red, From 1b6987a4a05c76b3cabe327b1655397ff0ca15f5 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Sun, 3 Aug 2025 23:30:22 +0200 Subject: [PATCH 13/31] shaders: add lint ignores from rust-gpu --- Cargo.toml | 3 +++ node-graph/graster-nodes/Cargo.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4062302992..4f8c82784a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,6 +194,9 @@ poly-cool = "0.2.0" spirv-std = { git = "https://github.com/rust-gpu/rust-gpu", rev = "3f05f5482824e3b1fbb44c9ef90a8795a0204c7c" } cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "e8ba9a0421c27a715277da116a6f2d59cdd3266d" } +[workspace.lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] } + [profile.dev] opt-level = 1 diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index da848d39b3..6db3aeec5f 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -9,6 +9,9 @@ license = "MIT OR Apache-2.0" [lib] crate-type = ["rlib", "dylib"] +[lints] +workspace = true + [features] default = ["std"] std = [ From 7e496bd8be0a024399668a580337e6d0f2538e38 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 15:27:59 +0200 Subject: [PATCH 14/31] shaders: fix node-macro tests --- node-graph/node-macro/src/parsing.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 873bf6bd97..4cef3112c8 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -835,6 +835,7 @@ mod tests { number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), + gpu_image: false, }), number_display_decimal_places: None, number_step: None, @@ -915,6 +916,7 @@ mod tests { number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), + gpu_image: false, }), number_display_decimal_places: None, number_step: None, @@ -978,6 +980,7 @@ mod tests { number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), + gpu_image: false, }), number_display_decimal_places: None, number_step: None, @@ -1044,6 +1047,7 @@ mod tests { p.push(parse_quote!(f64)); p }, + gpu_image: false, }), number_display_decimal_places: None, number_step: None, @@ -1112,6 +1116,7 @@ mod tests { number_hard_max: None, number_mode_range: Some(parse_quote!((0., 100.))), implementations: Punctuated::new(), + gpu_image: false, }), number_display_decimal_places: None, number_step: None, @@ -1173,6 +1178,7 @@ mod tests { number_hard_max: None, number_mode_range: None, implementations: Punctuated::new(), + gpu_image: false, }), number_display_decimal_places: None, number_step: None, From d312fd25b17c74545e955592b546e583bd42d28c Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 27 Aug 2025 15:25:52 +0200 Subject: [PATCH 15/31] gcore-shaders: toml cleanup --- node-graph/gcore-shaders/Cargo.toml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/node-graph/gcore-shaders/Cargo.toml b/node-graph/gcore-shaders/Cargo.toml index ebb23e1234..7278b62acc 100644 --- a/node-graph/gcore-shaders/Cargo.toml +++ b/node-graph/gcore-shaders/Cargo.toml @@ -7,7 +7,23 @@ authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" [features] -std = ["dep:dyn-any", "dep:serde", "dep:specta", "dep:log", "glam/debug-glam-assert", "glam/std", "glam/serde", "half/std", "half/serde", "num-traits/std"] +# any feature that +# * must be usable in shaders +# * but requires std +# * and should be on by default +# should be in this list instead of `[workspace.dependency]` +std = [ + "dep:dyn-any", + "dep:serde", + "dep:specta", + "dep:log", + "glam/debug-glam-assert", + "glam/std", + "glam/serde", + "half/std", + "half/serde", + "num-traits/std" +] [dependencies] # Local std dependencies From bddfb24690ad3e2a2eb4c7f28ced2b72e5c457a5 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 14 Aug 2025 15:35:21 +0200 Subject: [PATCH 16/31] shader-rt: initial --- node-graph/gcore/src/raster_types.rs | 2 +- .../graster-nodes/src/fullscreen_vertex.rs | 14 ++ node-graph/graster-nodes/src/lib.rs | 1 + node-graph/node-macro/src/codegen.rs | 9 +- node-graph/node-macro/src/parsing.rs | 2 +- node-graph/node-macro/src/shader_nodes/mod.rs | 9 +- .../src/shader_nodes/per_pixel_adjust.rs | 54 +++++- node-graph/wgpu-executor/src/lib.rs | 1 + .../wgpu-executor/src/shader_runtime/mod.rs | 20 +++ .../per_pixel_adjust_runtime.rs | 155 ++++++++++++++++++ 10 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 node-graph/graster-nodes/src/fullscreen_vertex.rs create mode 100644 node-graph/wgpu-executor/src/shader_runtime/mod.rs create mode 100644 node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs diff --git a/node-graph/gcore/src/raster_types.rs b/node-graph/gcore/src/raster_types.rs index 97dd138145..7efae73fb8 100644 --- a/node-graph/gcore/src/raster_types.rs +++ b/node-graph/gcore/src/raster_types.rs @@ -137,7 +137,7 @@ mod gpu { #[derive(Clone, Debug, PartialEq, Hash)] pub struct GPU { - texture: wgpu::Texture, + pub texture: wgpu::Texture, } impl Sealed for Raster {} diff --git a/node-graph/graster-nodes/src/fullscreen_vertex.rs b/node-graph/graster-nodes/src/fullscreen_vertex.rs new file mode 100644 index 0000000000..b8ef775b9f --- /dev/null +++ b/node-graph/graster-nodes/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/graster-nodes/src/lib.rs b/node-graph/graster-nodes/src/lib.rs index 8dc169cea6..793b041205 100644 --- a/node-graph/graster-nodes/src/lib.rs +++ b/node-graph/graster-nodes/src/lib.rs @@ -4,6 +4,7 @@ pub mod adjust; pub mod adjustments; pub mod blending_nodes; pub mod cubic_spline; +pub mod fullscreen_vertex; #[cfg(feature = "std")] pub mod curve; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 1c0c4f6517..da7f0b6a2c 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -295,7 +295,12 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result, _>(|n| Ok((n.codegen_shader_entry_point(parsed)?, n.codegen_gpu_node(parsed)?))) + .unwrap_or(Ok((TokenStream::new(), TokenStream::new())))?; + Ok(quote! { /// Underlying implementation for [#struct_name] #[inline] @@ -387,6 +392,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result, pub(crate) display_name: Option, diff --git a/node-graph/node-macro/src/shader_nodes/mod.rs b/node-graph/node-macro/src/shader_nodes/mod.rs index 0720869d01..3eff7fed14 100644 --- a/node-graph/node-macro/src/shader_nodes/mod.rs +++ b/node-graph/node-macro/src/shader_nodes/mod.rs @@ -19,7 +19,7 @@ pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream { } } -#[derive(Debug, VariantNames)] +#[derive(Debug, Clone, VariantNames)] pub(crate) enum ShaderNodeType { PerPixelAdjust(PerPixelAdjust), } @@ -36,6 +36,7 @@ impl Parse for ShaderNodeType { pub trait CodegenShaderEntryPoint { fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result; + fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result; } impl CodegenShaderEntryPoint for ShaderNodeType { @@ -48,4 +49,10 @@ impl CodegenShaderEntryPoint for ShaderNodeType { ShaderNodeType::PerPixelAdjust(x) => x.codegen_shader_entry_point(parsed), } } + + fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result { + match self { + ShaderNodeType::PerPixelAdjust(x) => x.codegen_gpu_node(parsed), + } + } } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 0e220c8aae..7e3a644175 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -1,11 +1,13 @@ -use crate::parsing::{ParsedFieldType, ParsedNodeFn, RegularParsedField}; +use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField}; use crate::shader_nodes::CodegenShaderEntryPoint; +use convert_case::{Case, Casing}; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; use syn::parse::{Parse, ParseStream}; +use syn::{Path, Type, TypePath}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PerPixelAdjust {} impl Parse for PerPixelAdjust { @@ -17,7 +19,7 @@ impl Parse for PerPixelAdjust { impl CodegenShaderEntryPoint for PerPixelAdjust { fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { let fn_name = &parsed.fn_name; - let gpu_mod = format_ident!("{}_gpu", parsed.fn_name); + let gpu_mod = format_ident!("{}_gpu_entry_point", parsed.fn_name); let spirv_image_ty = quote!(Image2d); // bindings for images start at 1 @@ -96,6 +98,52 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { } }) } + + fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result { + let fn_name = format_ident!("{}_gpu", parsed.fn_name); + let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); + let mod_name = fn_name.clone(); + + let fields = parsed + .fields + .iter() + .map(|f| match &f.ty { + ParsedFieldType::Regular(reg) => Ok(ParsedField { + ty: ParsedFieldType::Regular(RegularParsedField { gpu_image: false, ..reg.clone() }), + ..f.clone() + }), + ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), + }) + .collect::>()?; + let body = quote! {}; + + crate::codegen::generate_node_code(&ParsedNodeFn { + vis: parsed.vis.clone(), + attributes: NodeFnAttributes { + shader_node: None, + ..parsed.attributes.clone() + }, + fn_name, + struct_name, + mod_name, + fn_generics: vec![], + where_clause: None, + input: Input { + pat_ident: parsed.input.pat_ident.clone(), + ty: Type::Path(TypePath { + path: Path::from(format_ident!("Ctx")), + qself: None, + }), + implementations: Default::default(), + }, + output_type: parsed.output_type.clone(), + is_async: true, + fields, + body, + crate_name: parsed.crate_name.clone(), + description: "".to_string(), + }) + } } struct Param<'a> { diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 920a002c4e..b45db35a38 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -1,4 +1,5 @@ mod context; +pub mod shader_runtime; pub mod texture_upload; use anyhow::Result; diff --git a/node-graph/wgpu-executor/src/shader_runtime/mod.rs b/node-graph/wgpu-executor/src/shader_runtime/mod.rs new file mode 100644 index 0000000000..e7e0df8d94 --- /dev/null +++ b/node-graph/wgpu-executor/src/shader_runtime/mod.rs @@ -0,0 +1,20 @@ +use crate::Context; +use crate::shader_runtime::per_pixel_adjust_runtime::PerPixelAdjustShaderRuntime; + +pub mod per_pixel_adjust_runtime; + +pub const FULLSCREEN_VERTEX_SHADER_NAME: &str = "fullscreen_vertexfullscreen_vertex"; + +pub struct ShaderRuntime { + context: Context, + per_pixel_adjust: PerPixelAdjustShaderRuntime, +} + +impl ShaderRuntime { + pub fn new(context: &Context) -> Self { + Self { + context: context.clone(), + per_pixel_adjust: PerPixelAdjustShaderRuntime::new(), + } + } +} diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs new file mode 100644 index 0000000000..604a2c5bfd --- /dev/null +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -0,0 +1,155 @@ +use crate::Context; +use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; +use futures::lock::Mutex; +use graphene_core::raster_types::{GPU, Raster}; +use graphene_core::table::{Table, TableRow}; +use std::borrow::Cow; +use std::collections::HashMap; +use wgpu::{ + BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, + TextureViewDescriptor, VertexState, +}; + +pub struct PerPixelAdjustShaderRuntime { + // TODO: PerPixelAdjustGraphicsPipeline already contains the key as `name` + pipeline_cache: Mutex>, +} + +impl PerPixelAdjustShaderRuntime { + pub fn new() -> Self { + Self { + pipeline_cache: Mutex::new(HashMap::new()), + } + } +} + +impl ShaderRuntime { + pub async fn run_per_pixel_adjust(&self, input: Table>, info: &PerPixelAdjustInfo<'_>) -> Table> { + let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; + let pipeline = cache + .entry(info.fragment_shader_name.to_owned()) + .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, &info)); + pipeline.run(&self.context, input) + } +} + +pub struct PerPixelAdjustInfo<'a> { + shader_wgsl: &'a str, + fragment_shader_name: &'a str, +} + +pub struct PerPixelAdjustGraphicsPipeline { + name: String, + pipeline: wgpu::RenderPipeline, +} + +impl PerPixelAdjustGraphicsPipeline { + pub fn new(context: &Context, info: &PerPixelAdjustInfo) -> Self { + let device = &context.device; + let name = info.fragment_shader_name.to_owned(); + let shader_module = device.create_shader_module(ShaderModuleDescriptor { + label: Some(&format!("PerPixelAdjust {} wgsl shader", name)), + source: ShaderSource::Wgsl(Cow::Borrowed(info.shader_wgsl)), + }); + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some(&format!("PerPixelAdjust {} Pipeline", name)), + layout: None, + vertex: VertexState { + module: &shader_module, + entry_point: Some(FULLSCREEN_VERTEX_SHADER_NAME), + 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(&name), + compilation_options: Default::default(), + targets: &[Some(ColorTargetState { + format: TextureFormat::Rgba32Float, + blend: None, + write_mask: Default::default(), + })], + }), + multiview: None, + cache: None, + }); + Self { pipeline, name } + } + + pub fn run(&self, context: &Context, input: Table>) -> Table> { + let device = &context.device; + let name = self.name.as_str(); + + let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") }); + let out = input + .iter() + .map(|instance| { + let tex_in = &instance.element.texture; + let view_in = tex_in.create_view(&TextureViewDescriptor::default()); + let format = tex_in.format(); + + let bind_group = device.create_bind_group(&BindGroupDescriptor { + label: Some(&format!("{name} bind group")), + // `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that + layout: &self.pipeline.get_bind_group_layout(0), + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view_in), + }], + }); + + let tex_out = device.create_texture(&TextureDescriptor { + label: Some(&format!("{name} texture out")), + size: tex_in.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(&format!("{name} render pipeline")), + 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(&self.pipeline); + rp.set_bind_group(0, Some(&bind_group), &[]); + rp.draw(0..3, 0..1); + + TableRow { + element: Raster::new(GPU { texture: tex_out }), + transform: *instance.transform, + alpha_blending: *instance.alpha_blending, + source_node_id: *instance.source_node_id, + } + }) + .collect::>(); + context.queue.submit([cmd.finish()]); + out + } +} From eff23706b75a837ba7ad4b56e88ec6223138c48f Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 12:41:56 +0200 Subject: [PATCH 17/31] shader-rt: fix recursion when generating shader node --- node-graph/node-macro/src/shader_nodes/mod.rs | 15 +++++++++-- .../src/shader_nodes/per_pixel_adjust.rs | 26 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/mod.rs b/node-graph/node-macro/src/shader_nodes/mod.rs index 3eff7fed14..26e1ebba87 100644 --- a/node-graph/node-macro/src/shader_nodes/mod.rs +++ b/node-graph/node-macro/src/shader_nodes/mod.rs @@ -21,6 +21,10 @@ pub fn modify_cfg(attributes: &NodeFnAttributes) -> TokenStream { #[derive(Debug, Clone, VariantNames)] pub(crate) enum ShaderNodeType { + /// Marker for this node being a generated gpu node implementation, that should not emit anything to prevent + /// recursively generating more gpu nodes. But it still counts as a gpu node and will get the + /// `#[cfg(feature = "std")]` feature gate around it's impl. + GpuNode, PerPixelAdjust(PerPixelAdjust), } @@ -41,17 +45,24 @@ pub trait CodegenShaderEntryPoint { impl CodegenShaderEntryPoint for ShaderNodeType { fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { - if parsed.is_async { - return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async")); + match self { + ShaderNodeType::GpuNode => (), + _ => { + if parsed.is_async { + return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async")); + } + } } match self { + ShaderNodeType::GpuNode => Ok(TokenStream::new()), ShaderNodeType::PerPixelAdjust(x) => x.codegen_shader_entry_point(parsed), } } fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result { match self { + ShaderNodeType::GpuNode => Ok(TokenStream::new()), ShaderNodeType::PerPixelAdjust(x) => x.codegen_gpu_node(parsed), } } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 7e3a644175..36cbff50b1 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -1,11 +1,12 @@ use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField}; -use crate::shader_nodes::CodegenShaderEntryPoint; +use crate::shader_nodes::{CodegenShaderEntryPoint, ShaderNodeType}; use convert_case::{Case, Casing}; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; use syn::parse::{Parse, ParseStream}; -use syn::{Path, Type, TypePath}; +use syn::punctuated::Punctuated; +use syn::{Path, Token, TraitBound, TraitBoundModifier, Type, TypeImplTrait, TypeParamBound}; #[derive(Debug, Clone)] pub struct PerPixelAdjust {} @@ -115,12 +116,16 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), }) .collect::>()?; - let body = quote! {}; + let body = quote! { + { + + } + }; crate::codegen::generate_node_code(&ParsedNodeFn { vis: parsed.vis.clone(), attributes: NodeFnAttributes { - shader_node: None, + shader_node: Some(ShaderNodeType::GpuNode), ..parsed.attributes.clone() }, fn_name, @@ -130,9 +135,14 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { where_clause: None, input: Input { pat_ident: parsed.input.pat_ident.clone(), - ty: Type::Path(TypePath { - path: Path::from(format_ident!("Ctx")), - qself: None, + ty: Type::ImplTrait(TypeImplTrait { + impl_token: Token![impl](Span::call_site()), + bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: Path::from(format_ident!("Ctx")), + })]), }), implementations: Default::default(), }, From fa83935443cb33eee9a2089501ce97c0b0fa6ddd Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 15:46:53 +0200 Subject: [PATCH 18/31] shader-rt: replace gpu node's args and ret types with `Raster` --- .../src/shader_nodes/per_pixel_adjust.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 36cbff50b1..b263a2c157 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -1,6 +1,7 @@ use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField}; use crate::shader_nodes::{CodegenShaderEntryPoint, ShaderNodeType}; use convert_case::{Case, Casing}; +use proc_macro_crate::FoundCrate; use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; @@ -105,14 +106,24 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); let mod_name = fn_name.clone(); + let gcore = match &parsed.crate_name { + FoundCrate::Itself => format_ident!("crate"), + FoundCrate::Name(name) => format_ident!("{name}"), + }; + let raster_gpu = syn::parse2::(quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>))?; + let fields = parsed .fields .iter() .map(|f| match &f.ty { - ParsedFieldType::Regular(reg) => Ok(ParsedField { - ty: ParsedFieldType::Regular(RegularParsedField { gpu_image: false, ..reg.clone() }), + ParsedFieldType::Regular(reg @ RegularParsedField { gpu_image: true, .. }) => Ok(ParsedField { + ty: ParsedFieldType::Regular(RegularParsedField { + ty: raster_gpu.clone(), + ..reg.clone() + }), ..f.clone() }), + ParsedFieldType::Regular(RegularParsedField { gpu_image: false, .. }) => Ok(f.clone()), ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), }) .collect::>()?; @@ -146,7 +157,7 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { }), implementations: Default::default(), }, - output_type: parsed.output_type.clone(), + output_type: raster_gpu, is_async: true, fields, body, From 236e63baf8b3d2e17aa253a48bb839868d507448 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 16:08:39 +0200 Subject: [PATCH 19/31] shader-rt: properly cfg out the gpu node --- node-graph/node-macro/src/codegen.rs | 12 +++---- node-graph/node-macro/src/shader_nodes/mod.rs | 24 ++++++------- .../src/shader_nodes/per_pixel_adjust.rs | 35 ++++++++++++++----- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index da7f0b6a2c..b3a1b28be3 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1,7 +1,7 @@ use crate::parsing::*; use convert_case::{Case, Casing}; use proc_macro_crate::FoundCrate; -use proc_macro2::{TokenStream as TokenStream2, TokenStream}; +use proc_macro2::TokenStream as TokenStream2; use quote::{ToTokens, format_ident, quote, quote_spanned}; use std::sync::atomic::AtomicU64; use syn::punctuated::Punctuated; @@ -295,11 +295,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result, _>(|n| Ok((n.codegen_shader_entry_point(parsed)?, n.codegen_gpu_node(parsed)?))) - .unwrap_or(Ok((TokenStream::new(), TokenStream::new())))?; + let ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(parsed, &cfg)).unwrap_or(Ok(ShaderTokens::default()))?; Ok(quote! { /// Underlying implementation for [#struct_name] @@ -393,7 +389,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result; - fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result; +pub trait ShaderCodegen { + fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result; } -impl CodegenShaderEntryPoint for ShaderNodeType { - fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { +impl ShaderCodegen for ShaderNodeType { + fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { match self { ShaderNodeType::GpuNode => (), _ => { @@ -55,15 +54,14 @@ impl CodegenShaderEntryPoint for ShaderNodeType { } match self { - ShaderNodeType::GpuNode => Ok(TokenStream::new()), - ShaderNodeType::PerPixelAdjust(x) => x.codegen_shader_entry_point(parsed), + ShaderNodeType::GpuNode => Ok(ShaderTokens::default()), + ShaderNodeType::PerPixelAdjust(x) => x.codegen(parsed, node_cfg), } } +} - fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result { - match self { - ShaderNodeType::GpuNode => Ok(TokenStream::new()), - ShaderNodeType::PerPixelAdjust(x) => x.codegen_gpu_node(parsed), - } - } +#[derive(Clone, Default)] +pub struct ShaderTokens { + pub shader_entry_point: TokenStream, + pub gpu_node: TokenStream, } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index b263a2c157..e21e3cdfb6 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -1,5 +1,5 @@ use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField}; -use crate::shader_nodes::{CodegenShaderEntryPoint, ShaderNodeType}; +use crate::shader_nodes::{ShaderCodegen, ShaderNodeType, ShaderTokens}; use convert_case::{Case, Casing}; use proc_macro_crate::FoundCrate; use proc_macro2::{Ident, Span, TokenStream}; @@ -7,7 +7,7 @@ use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{Path, Token, TraitBound, TraitBoundModifier, Type, TypeImplTrait, TypeParamBound}; +use syn::{Token, TraitBound, TraitBoundModifier, Type, TypeImplTrait, TypeParamBound}; #[derive(Debug, Clone)] pub struct PerPixelAdjust {} @@ -18,10 +18,19 @@ impl Parse for PerPixelAdjust { } } -impl CodegenShaderEntryPoint for PerPixelAdjust { +impl ShaderCodegen for PerPixelAdjust { + fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { + Ok(ShaderTokens { + shader_entry_point: self.codegen_shader_entry_point(parsed)?, + gpu_node: self.codegen_gpu_node(parsed, node_cfg)?, + }) + } +} + +impl PerPixelAdjust { fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { let fn_name = &parsed.fn_name; - let gpu_mod = format_ident!("{}_gpu_entry_point", parsed.fn_name); + let gpu_mod = format_ident!("{}_gpu_entry_point", fn_name); let spirv_image_ty = quote!(Image2d); // bindings for images start at 1 @@ -101,7 +110,7 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { }) } - fn codegen_gpu_node(&self, parsed: &ParsedNodeFn) -> syn::Result { + fn codegen_gpu_node(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { let fn_name = format_ident!("{}_gpu", parsed.fn_name); let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); let mod_name = fn_name.clone(); @@ -127,13 +136,14 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), }) .collect::>()?; + let body = quote! { { } }; - crate::codegen::generate_node_code(&ParsedNodeFn { + let gpu_node = crate::codegen::generate_node_code(&ParsedNodeFn { vis: parsed.vis.clone(), attributes: NodeFnAttributes { shader_node: Some(ShaderNodeType::GpuNode), @@ -141,7 +151,7 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { }, fn_name, struct_name, - mod_name, + mod_name: mod_name.clone(), fn_generics: vec![], where_clause: None, input: Input { @@ -152,7 +162,7 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { paren_token: None, modifier: TraitBoundModifier::None, lifetimes: None, - path: Path::from(format_ident!("Ctx")), + path: syn::parse2(quote!(#gcore::context::Ctx))?, })]), }), implementations: Default::default(), @@ -163,6 +173,15 @@ impl CodegenShaderEntryPoint for PerPixelAdjust { body, crate_name: parsed.crate_name.clone(), description: "".to_string(), + })?; + + Ok(quote! { + #node_cfg + mod #mod_name { + use super::*; + + #gpu_node + } }) } } From aa84a829fc667e721d2fee95565bbe548fbbebf7 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 16:37:36 +0200 Subject: [PATCH 20/31] shader-rt: fix `impl Context` in the wrong places --- node-graph/node-macro/src/parsing.rs | 4 ++-- .../src/shader_nodes/per_pixel_adjust.rs | 23 ++++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 498cee8eab..69e035d9f3 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -144,7 +144,7 @@ pub struct NodeParsedField { pub implementations: Punctuated, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct Input { pub(crate) pat_ident: PatIdent, pub(crate) ty: Type, @@ -663,7 +663,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { } impl ParsedNodeFn { - fn replace_impl_trait_in_input(&mut self) { + pub fn replace_impl_trait_in_input(&mut self) { if let Type::ImplTrait(impl_trait) = self.input.ty.clone() { let ident = Ident::new("_Input", impl_trait.span()); let mut bounds = impl_trait.bounds; diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index e21e3cdfb6..d1f4c99a1d 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -2,12 +2,12 @@ use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, Pars use crate::shader_nodes::{ShaderCodegen, ShaderNodeType, ShaderTokens}; use convert_case::{Case, Casing}; use proc_macro_crate::FoundCrate; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{Token, TraitBound, TraitBoundModifier, Type, TypeImplTrait, TypeParamBound}; +use syn::{Type, parse_quote}; #[derive(Debug, Clone)] pub struct PerPixelAdjust {} @@ -119,7 +119,7 @@ impl PerPixelAdjust { FoundCrate::Itself => format_ident!("crate"), FoundCrate::Name(name) => format_ident!("{name}"), }; - let raster_gpu = syn::parse2::(quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>))?; + let raster_gpu: Type = parse_quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>); let fields = parsed .fields @@ -128,6 +128,7 @@ impl PerPixelAdjust { ParsedFieldType::Regular(reg @ RegularParsedField { gpu_image: true, .. }) => Ok(ParsedField { ty: ParsedFieldType::Regular(RegularParsedField { ty: raster_gpu.clone(), + implementations: Punctuated::default(), ..reg.clone() }), ..f.clone() @@ -143,7 +144,7 @@ impl PerPixelAdjust { } }; - let gpu_node = crate::codegen::generate_node_code(&ParsedNodeFn { + let mut parsed_node_fn = ParsedNodeFn { vis: parsed.vis.clone(), attributes: NodeFnAttributes { shader_node: Some(ShaderNodeType::GpuNode), @@ -156,15 +157,7 @@ impl PerPixelAdjust { where_clause: None, input: Input { pat_ident: parsed.input.pat_ident.clone(), - ty: Type::ImplTrait(TypeImplTrait { - impl_token: Token![impl](Span::call_site()), - bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path: syn::parse2(quote!(#gcore::context::Ctx))?, - })]), - }), + ty: parse_quote!(impl #gcore::context::Ctx), implementations: Default::default(), }, output_type: raster_gpu, @@ -173,7 +166,9 @@ impl PerPixelAdjust { body, crate_name: parsed.crate_name.clone(), description: "".to_string(), - })?; + }; + parsed_node_fn.replace_impl_trait_in_input(); + let gpu_node = crate::codegen::generate_node_code(&parsed_node_fn)?; Ok(quote! { #node_cfg From bbe5d46068a3148bdaf8fac4f66cf3d5bff6c58d Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 19 Aug 2025 16:40:08 +0200 Subject: [PATCH 21/31] shader-rt: disable gpu blend node, needs two images --- node-graph/graster-nodes/src/blending_nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/graster-nodes/src/blending_nodes.rs b/node-graph/graster-nodes/src/blending_nodes.rs index 182f6a802d..13f93773d4 100644 --- a/node-graph/graster-nodes/src/blending_nodes.rs +++ b/node-graph/graster-nodes/src/blending_nodes.rs @@ -132,7 +132,7 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM } } -#[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))] +#[node_macro::node(category("Raster"), cfg(feature = "std"))] fn blend + Send>( _: impl Ctx, #[implementations( From e5e865af95e864c51f6d069bb15b9df672c0a09d Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 21 Aug 2025 11:08:14 +0200 Subject: [PATCH 22/31] shader-rt: connect shader runtime --- Cargo.lock | 1 + node-graph/graster-nodes/Cargo.toml | 2 + node-graph/graster-nodes/src/lib.rs | 4 + .../src/shader_nodes/per_pixel_adjust.rs | 75 ++++++++++++++++--- node-graph/wgpu-executor/src/lib.rs | 3 + .../per_pixel_adjust_runtime.rs | 11 ++- 6 files changed, 80 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e44bd8cd4..3898d2c07c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2177,6 +2177,7 @@ dependencies = [ "specta", "spirv-std", "tokio", + "wgpu-executor", ] [[package]] diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index 6db3aeec5f..75eb666202 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -17,6 +17,7 @@ default = ["std"] std = [ "dep:graphene-core", "dep:graphene-raster-nodes-shaders", + "dep:wgpu-executor", "dep:dyn-any", "dep:image", "dep:ndarray", @@ -38,6 +39,7 @@ node-macro = { workspace = true } # Local std dependencies dyn-any = { workspace = true, optional = true } graphene-core = { workspace = true, optional = true } +wgpu-executor = { workspace = true, optional = true } graphene-raster-nodes-shaders = { path = "./shaders", optional = true } # Workspace dependencies diff --git a/node-graph/graster-nodes/src/lib.rs b/node-graph/graster-nodes/src/lib.rs index 793b041205..d5383df034 100644 --- a/node-graph/graster-nodes/src/lib.rs +++ b/node-graph/graster-nodes/src/lib.rs @@ -6,6 +6,10 @@ pub mod blending_nodes; pub mod cubic_spline; pub mod fullscreen_vertex; +/// required by shader macro +#[cfg(feature = "std")] +pub use graphene_raster_nodes_shaders::WGSL_SHADER; + #[cfg(feature = "std")] pub mod curve; #[cfg(feature = "std")] diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index d1f4c99a1d..84780538d2 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -7,7 +7,7 @@ use quote::{ToTokens, format_ident, quote}; use std::borrow::Cow; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{Type, parse_quote}; +use syn::{PatIdent, Type, parse_quote}; #[derive(Debug, Clone)] pub struct PerPixelAdjust {} @@ -20,15 +20,14 @@ impl Parse for PerPixelAdjust { impl ShaderCodegen for PerPixelAdjust { fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { - Ok(ShaderTokens { - shader_entry_point: self.codegen_shader_entry_point(parsed)?, - gpu_node: self.codegen_gpu_node(parsed, node_cfg)?, - }) + let (shader_entry_point, entry_point_name) = self.codegen_shader_entry_point(parsed)?; + let gpu_node = self.codegen_gpu_node(parsed, node_cfg, &entry_point_name)?; + Ok(ShaderTokens { shader_entry_point, gpu_node }) } } impl PerPixelAdjust { - fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result { + fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result<(TokenStream, TokenStream)> { let fn_name = &parsed.fn_name; let gpu_mod = format_ident!("{}_gpu_entry_point", fn_name); let spirv_image_ty = quote!(Image2d); @@ -82,7 +81,10 @@ impl PerPixelAdjust { .collect::>(); let context = quote!(()); - Ok(quote! { + let entry_point_name = format_ident!("ENTRY_POINT_NAME"); + let entry_point_sym = quote!(#gpu_mod::#entry_point_name); + + let shader_entry_point = quote! { pub mod #gpu_mod { use super::*; use graphene_core_shaders::color::Color; @@ -91,6 +93,8 @@ impl PerPixelAdjust { use spirv_std::image::{Image2d, ImageWithMethods}; use spirv_std::image::sample_with::lod; + pub const #entry_point_name: &str = core::concat!(core::module_path!(), "::entry_point"); + pub struct Uniform { #(#uniform_members),* } @@ -107,10 +111,11 @@ impl PerPixelAdjust { *color_out = color.to_vec4(); } } - }) + }; + Ok((shader_entry_point, entry_point_sym)) } - fn codegen_gpu_node(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { + fn codegen_gpu_node(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream, entry_point_name: &TokenStream) -> syn::Result { let fn_name = format_ident!("{}_gpu", parsed.fn_name); let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); let mod_name = fn_name.clone(); @@ -121,7 +126,8 @@ impl PerPixelAdjust { }; let raster_gpu: Type = parse_quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>); - let fields = parsed + // adapt fields for gpu node + let mut fields = parsed .fields .iter() .map(|f| match &f.ty { @@ -136,11 +142,55 @@ impl PerPixelAdjust { ParsedFieldType::Regular(RegularParsedField { gpu_image: false, .. }) => Ok(f.clone()), ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), }) - .collect::>()?; + .collect::>>()?; + + // wgpu_executor field + let wgpu_executor = format_ident!("__wgpu_executor"); + fields.push(ParsedField { + pat_ident: PatIdent { + attrs: vec![], + by_ref: None, + mutability: None, + ident: parse_quote!(#wgpu_executor), + subpat: None, + }, + name: None, + description: "".to_string(), + widget_override: Default::default(), + ty: ParsedFieldType::Regular(RegularParsedField { + ty: parse_quote!(WgpuExecutor), + exposed: false, + value_source: Default::default(), + number_soft_min: None, + number_soft_max: None, + number_hard_min: None, + number_hard_max: None, + number_mode_range: None, + implementations: Default::default(), + gpu_image: false, + }), + number_display_decimal_places: None, + number_step: None, + unit: None, + }); + + // exactly one gpu_image field, may be expanded later + let gpu_image_field = { + let mut iter = fields.iter().filter(|f| matches!(f.ty, ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }))); + match (iter.next(), iter.next()) { + (Some(v), None) => Ok(v), + (Some(_), Some(more)) => Err(syn::Error::new_spanned(&more.pat_ident, "No more than one parameter must be annotated with `#[gpu_image]`")), + (None, _) => Err(syn::Error::new_spanned(&parsed.fn_name, "At least one parameter must be annotated with `#[gpu_image]`")), + }? + }; + let gpu_image = &gpu_image_field.pat_ident.ident; let body = quote! { { - + #wgpu_executor.shader_runtime.run_per_pixel_adjust(#gpu_image, &::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::PerPixelAdjustInfo { + wgsl_shader: crate::WGSL_SHADER, + fragment_shader_name: super::#entry_point_name, + }).await } }; @@ -174,6 +224,7 @@ impl PerPixelAdjust { #node_cfg mod #mod_name { use super::*; + use wgpu_executor::WgpuExecutor; #gpu_node } diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index b45db35a38..0b42dd631e 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -2,6 +2,7 @@ mod context; pub mod shader_runtime; pub mod texture_upload; +use crate::shader_runtime::ShaderRuntime; use anyhow::Result; pub use context::Context; use dyn_any::StaticType; @@ -19,6 +20,7 @@ use wgpu::{Origin3d, SurfaceConfiguration, TextureAspect}; pub struct WgpuExecutor { pub context: Context, vello_renderer: Mutex, + pub shader_runtime: ShaderRuntime, } impl std::fmt::Debug for WgpuExecutor { @@ -196,6 +198,7 @@ impl WgpuExecutor { .ok()?; Some(Self { + shader_runtime: ShaderRuntime::new(&context), context, vello_renderer: vello_renderer.into(), }) diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index 604a2c5bfd..2b01bfc705 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -35,8 +35,8 @@ impl ShaderRuntime { } pub struct PerPixelAdjustInfo<'a> { - shader_wgsl: &'a str, - fragment_shader_name: &'a str, + pub wgsl_shader: &'a str, + pub fragment_shader_name: &'a str, } pub struct PerPixelAdjustGraphicsPipeline { @@ -48,9 +48,12 @@ impl PerPixelAdjustGraphicsPipeline { pub fn new(context: &Context, info: &PerPixelAdjustInfo) -> Self { let device = &context.device; let name = info.fragment_shader_name.to_owned(); + // TODO workaround to naga removing `:` + let fragment_name = name.replace(":", ""); + let shader_module = device.create_shader_module(ShaderModuleDescriptor { label: Some(&format!("PerPixelAdjust {} wgsl shader", name)), - source: ShaderSource::Wgsl(Cow::Borrowed(info.shader_wgsl)), + source: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)), }); let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { label: Some(&format!("PerPixelAdjust {} Pipeline", name)), @@ -74,7 +77,7 @@ impl PerPixelAdjustGraphicsPipeline { multisample: Default::default(), fragment: Some(FragmentState { module: &shader_module, - entry_point: Some(&name), + entry_point: Some(&fragment_name), compilation_options: Default::default(), targets: &[Some(ColorTargetState { format: TextureFormat::Rgba32Float, From 34f327b4f2ce388ed0590374ddda133df7a29858 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 20 Aug 2025 15:51:22 +0200 Subject: [PATCH 23/31] shader-rt: pass WgpuExecutor by reference --- node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 84780538d2..8dc97eba2e 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -158,7 +158,7 @@ impl PerPixelAdjust { description: "".to_string(), widget_override: Default::default(), ty: ParsedFieldType::Regular(RegularParsedField { - ty: parse_quote!(WgpuExecutor), + ty: parse_quote!(&'a WgpuExecutor), exposed: false, value_source: Default::default(), number_soft_min: None, @@ -203,7 +203,7 @@ impl PerPixelAdjust { fn_name, struct_name, mod_name: mod_name.clone(), - fn_generics: vec![], + fn_generics: vec![parse_quote!('a: 'n)], where_clause: None, input: Input { pat_ident: parsed.input.pat_ident.clone(), From fef2894a0ccfb860def42e177c9f880002bab536 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 20 Aug 2025 16:44:54 +0200 Subject: [PATCH 24/31] shader-rt: correct bindings with derpy arg buffer --- .../src/shader_nodes/per_pixel_adjust.rs | 4 +- .../wgpu-executor/src/shader_runtime/mod.rs | 5 ++ .../per_pixel_adjust_runtime.rs | 57 ++++++++++++------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 8dc97eba2e..06a4508015 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -187,10 +187,10 @@ impl PerPixelAdjust { let body = quote! { { - #wgpu_executor.shader_runtime.run_per_pixel_adjust(#gpu_image, &::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::PerPixelAdjustInfo { + #wgpu_executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::Shaders { wgsl_shader: crate::WGSL_SHADER, fragment_shader_name: super::#entry_point_name, - }).await + }, #gpu_image, &()).await } }; diff --git a/node-graph/wgpu-executor/src/shader_runtime/mod.rs b/node-graph/wgpu-executor/src/shader_runtime/mod.rs index e7e0df8d94..2745d5bda8 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/mod.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/mod.rs @@ -18,3 +18,8 @@ impl ShaderRuntime { } } } + +pub struct Shaders<'a> { + pub wgsl_shader: &'a str, + pub fragment_shader_name: &'a str, +} diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index 2b01bfc705..352adb7e9e 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -1,13 +1,15 @@ use crate::Context; -use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; +use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime, Shaders}; +use bytemuck::NoUninit; use futures::lock::Mutex; use graphene_core::raster_types::{GPU, Raster}; use graphene_core::table::{Table, TableRow}; use std::borrow::Cow; use std::collections::HashMap; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::{ - BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, - RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, + BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferUsages, ColorTargetState, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, + PrimitiveTopology, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureViewDescriptor, VertexState, }; @@ -25,18 +27,20 @@ impl PerPixelAdjustShaderRuntime { } impl ShaderRuntime { - pub async fn run_per_pixel_adjust(&self, input: Table>, info: &PerPixelAdjustInfo<'_>) -> Table> { + pub async fn run_per_pixel_adjust(&self, shaders: &Shaders<'_>, textures: Table>, args: &T) -> Table> { let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; let pipeline = cache - .entry(info.fragment_shader_name.to_owned()) - .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, &info)); - pipeline.run(&self.context, input) - } -} + .entry(shaders.fragment_shader_name.to_owned()) + .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, &shaders)); -pub struct PerPixelAdjustInfo<'a> { - pub wgsl_shader: &'a str, - pub fragment_shader_name: &'a str, + let device = &self.context.device; + let arg_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some(&format!("{} arg buffer", pipeline.name.as_str())), + usage: BufferUsages::STORAGE, + contents: bytemuck::bytes_of(args), + }); + pipeline.dispatch(&self.context, textures, &arg_buffer) + } } pub struct PerPixelAdjustGraphicsPipeline { @@ -45,11 +49,14 @@ pub struct PerPixelAdjustGraphicsPipeline { } impl PerPixelAdjustGraphicsPipeline { - pub fn new(context: &Context, info: &PerPixelAdjustInfo) -> Self { + pub fn new(context: &Context, info: &Shaders) -> Self { let device = &context.device; let name = info.fragment_shader_name.to_owned(); + // TODO workaround to naga removing `:` - let fragment_name = name.replace(":", ""); + let fragment_name = &name; + let fragment_name = &fragment_name[(fragment_name.find("::").unwrap() + 2)..]; + let fragment_name = fragment_name.replace(":", ""); let shader_module = device.create_shader_module(ShaderModuleDescriptor { label: Some(&format!("PerPixelAdjust {} wgsl shader", name)), @@ -91,12 +98,12 @@ impl PerPixelAdjustGraphicsPipeline { Self { pipeline, name } } - pub fn run(&self, context: &Context, input: Table>) -> Table> { + pub fn dispatch(&self, context: &Context, textures: Table>, arg_buffer: &Buffer) -> Table> { let device = &context.device; let name = self.name.as_str(); let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") }); - let out = input + let out = textures .iter() .map(|instance| { let tex_in = &instance.element.texture; @@ -107,10 +114,20 @@ impl PerPixelAdjustGraphicsPipeline { label: Some(&format!("{name} bind group")), // `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that layout: &self.pipeline.get_bind_group_layout(0), - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&view_in), - }], + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(BufferBinding { + buffer: arg_buffer, + offset: 0, + size: None, + }), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(&view_in), + }, + ], }); let tex_out = device.create_texture(&TextureDescriptor { From acedce259c1e162bf42c15224dd504ca37d69eb7 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 20 Aug 2025 17:18:03 +0200 Subject: [PATCH 25/31] shader-rt: manual pipeline layout, fixing errors when bindings got DCE'd --- .../src/shader_nodes/per_pixel_adjust.rs | 2 +- .../per_pixel_adjust_runtime.rs | 42 ++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 06a4508015..3203e4328d 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -190,7 +190,7 @@ impl PerPixelAdjust { #wgpu_executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::Shaders { wgsl_shader: crate::WGSL_SHADER, fragment_shader_name: super::#entry_point_name, - }, #gpu_image, &()).await + }, #gpu_image, &1u32).await } }; diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index 352adb7e9e..746e1a900e 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -8,9 +8,9 @@ use std::borrow::Cow; use std::collections::HashMap; use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::{ - BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferUsages, ColorTargetState, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, - PrimitiveTopology, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, - TextureViewDescriptor, VertexState, + BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBinding, BufferBindingType, BufferUsages, ColorTargetState, Face, + FragmentState, FrontFace, LoadOp, Operations, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, + ShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureViewDescriptor, TextureViewDimension, VertexState, }; pub struct PerPixelAdjustShaderRuntime { @@ -53,18 +53,48 @@ impl PerPixelAdjustGraphicsPipeline { let device = &context.device; let name = info.fragment_shader_name.to_owned(); - // TODO workaround to naga removing `:` let fragment_name = &name; let fragment_name = &fragment_name[(fragment_name.find("::").unwrap() + 2)..]; + // TODO workaround to naga removing `:` let fragment_name = fragment_name.replace(":", ""); - let shader_module = device.create_shader_module(ShaderModuleDescriptor { label: Some(&format!("PerPixelAdjust {} wgsl shader", name)), source: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)), }); + + let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some(&format!("PerPixelAdjust {} PipelineLayout", name)), + bind_group_layouts: &[&device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some(&format!("PerPixelAdjust {} BindGroupLayout 0", name)), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + })], + push_constant_ranges: &[], + }); + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { label: Some(&format!("PerPixelAdjust {} Pipeline", name)), - layout: None, + layout: Some(&pipeline_layout), vertex: VertexState { module: &shader_module, entry_point: Some(FULLSCREEN_VERTEX_SHADER_NAME), From d7fa215e4bf943ebdf084bffcfe8467d80046765 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 20 Aug 2025 17:22:10 +0200 Subject: [PATCH 26/31] shader-rt: correct RT format, working invert gpu node --- .../src/shader_runtime/per_pixel_adjust_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index 746e1a900e..119d346956 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -117,7 +117,7 @@ impl PerPixelAdjustGraphicsPipeline { entry_point: Some(&fragment_name), compilation_options: Default::default(), targets: &[Some(ColorTargetState { - format: TextureFormat::Rgba32Float, + format: TextureFormat::Rgba8UnormSrgb, blend: None, write_mask: Default::default(), })], From 766d7f118fc044127ab2be44ea090d1ede3aeab9 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 21 Aug 2025 11:53:53 +0200 Subject: [PATCH 27/31] shader-rt: cleanup codegen with common sym struct --- .../src/shader_nodes/per_pixel_adjust.rs | 162 +++++++++++------- 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 3203e4328d..1096d88daa 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -20,59 +20,90 @@ impl Parse for PerPixelAdjust { impl ShaderCodegen for PerPixelAdjust { fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { - let (shader_entry_point, entry_point_name) = self.codegen_shader_entry_point(parsed)?; - let gpu_node = self.codegen_gpu_node(parsed, node_cfg, &entry_point_name)?; - Ok(ShaderTokens { shader_entry_point, gpu_node }) - } -} - -impl PerPixelAdjust { - fn codegen_shader_entry_point(&self, parsed: &ParsedNodeFn) -> syn::Result<(TokenStream, TokenStream)> { let fn_name = &parsed.fn_name; - let gpu_mod = format_ident!("{}_gpu_entry_point", fn_name); - let spirv_image_ty = quote!(Image2d); + // categorize params and assign image bindings // bindings for images start at 1 - let mut binding_cnt = 0; - let params = parsed - .fields - .iter() - .map(|f| { - let ident = &f.pat_ident; - match &f.ty { - ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), - ParsedFieldType::Regular(RegularParsedField { gpu_image: false, ty, .. }) => Ok(Param { - ident: Cow::Borrowed(&ident.ident), - ty: Cow::Owned(ty.to_token_stream()), - param_type: ParamType::Uniform, - }), - ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => { - binding_cnt += 1; - Ok(Param { - ident: Cow::Owned(format_ident!("image_{}", &ident.ident)), - ty: Cow::Borrowed(&spirv_image_ty), - param_type: ParamType::Image { binding: binding_cnt }, - }) + let params = { + let mut binding_cnt = 0; + parsed + .fields + .iter() + .map(|f| { + let ident = &f.pat_ident; + match &f.ty { + ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), + ParsedFieldType::Regular(RegularParsedField { gpu_image: false, ty, .. }) => Ok(Param { + ident: Cow::Borrowed(&ident.ident), + ty: ty.to_token_stream(), + param_type: ParamType::Uniform, + }), + ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => { + binding_cnt += 1; + Ok(Param { + ident: Cow::Owned(format_ident!("image_{}", &ident.ident)), + ty: quote!(Image2d), + param_type: ParamType::Image { binding: binding_cnt }, + }) + } } - } - }) - .collect::>>()?; + }) + .collect::>>()? + }; + + let entry_point_mod = format_ident!("{}_gpu_entry_point", fn_name); + let entry_point_name_ident = format_ident!("ENTRY_POINT_NAME"); + let entry_point_name = quote!(#entry_point_mod::#entry_point_name_ident); + let gpu_node_mod = format_ident!("{}_gpu", fn_name); + + let codegen = PerPixelAdjustCodegen { + parsed, + node_cfg, + params, + entry_point_mod, + entry_point_name_ident, + entry_point_name, + gpu_node_mod, + }; - let uniform_members = params + Ok(ShaderTokens { + shader_entry_point: codegen.codegen_shader_entry_point()?, + gpu_node: codegen.codegen_gpu_node()?, + }) + } +} + +pub struct PerPixelAdjustCodegen<'a> { + parsed: &'a ParsedNodeFn, + node_cfg: &'a TokenStream, + params: Vec>, + entry_point_mod: Ident, + entry_point_name_ident: Ident, + entry_point_name: TokenStream, + gpu_node_mod: Ident, +} + +impl PerPixelAdjustCodegen<'_> { + fn codegen_shader_entry_point(&self) -> syn::Result { + let fn_name = &self.parsed.fn_name; + let uniform_members = self + .params .iter() .filter_map(|Param { ident, ty, param_type }| match param_type { ParamType::Image { .. } => None, ParamType::Uniform => Some(quote! {#ident: #ty}), }) .collect::>(); - let image_params = params + let image_params = self + .params .iter() .filter_map(|Param { ident, ty, param_type }| match param_type { ParamType::Image { binding } => Some(quote! {#[spirv(descriptor_set = 0, binding = #binding)] #ident: &#ty}), ParamType::Uniform => None, }) .collect::>(); - let call_args = params + let call_args = self + .params .iter() .map(|Param { ident, param_type, .. }| match param_type { ParamType::Image { .. } => quote!(Color::from_vec4(#ident.fetch_with(texel_coord, lod(0)))), @@ -81,11 +112,10 @@ impl PerPixelAdjust { .collect::>(); let context = quote!(()); - let entry_point_name = format_ident!("ENTRY_POINT_NAME"); - let entry_point_sym = quote!(#gpu_mod::#entry_point_name); - - let shader_entry_point = quote! { - pub mod #gpu_mod { + let entry_point_mod = &self.entry_point_mod; + let entry_point_name = &self.entry_point_name_ident; + Ok(quote! { + pub mod #entry_point_mod { use super::*; use graphene_core_shaders::color::Color; use spirv_std::spirv; @@ -111,23 +141,19 @@ impl PerPixelAdjust { *color_out = color.to_vec4(); } } - }; - Ok((shader_entry_point, entry_point_sym)) + }) } - fn codegen_gpu_node(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream, entry_point_name: &TokenStream) -> syn::Result { - let fn_name = format_ident!("{}_gpu", parsed.fn_name); - let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); - let mod_name = fn_name.clone(); - - let gcore = match &parsed.crate_name { + fn codegen_gpu_node(&self) -> syn::Result { + let gcore = match &self.parsed.crate_name { FoundCrate::Itself => format_ident!("crate"), FoundCrate::Name(name) => format_ident!("{name}"), }; - let raster_gpu: Type = parse_quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>); // adapt fields for gpu node - let mut fields = parsed + let raster_gpu: Type = parse_quote!(#gcore::table::Table<#gcore::raster_types::Raster<#gcore::raster_types::GPU>>); + let mut fields = self + .parsed .fields .iter() .map(|f| match &f.ty { @@ -144,7 +170,7 @@ impl PerPixelAdjust { }) .collect::>>()?; - // wgpu_executor field + // insert wgpu_executor field let wgpu_executor = format_ident!("__wgpu_executor"); fields.push(ParsedField { pat_ident: PatIdent { @@ -174,17 +200,19 @@ impl PerPixelAdjust { unit: None, }); - // exactly one gpu_image field, may be expanded later + // find exactly one gpu_image field, runtime doesn't support more than 1 atm let gpu_image_field = { let mut iter = fields.iter().filter(|f| matches!(f.ty, ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }))); match (iter.next(), iter.next()) { (Some(v), None) => Ok(v), (Some(_), Some(more)) => Err(syn::Error::new_spanned(&more.pat_ident, "No more than one parameter must be annotated with `#[gpu_image]`")), - (None, _) => Err(syn::Error::new_spanned(&parsed.fn_name, "At least one parameter must be annotated with `#[gpu_image]`")), + (None, _) => Err(syn::Error::new_spanned(&self.parsed.fn_name, "At least one parameter must be annotated with `#[gpu_image]`")), }? }; let gpu_image = &gpu_image_field.pat_ident.ident; + // node function body + let entry_point_name = &self.entry_point_name; let body = quote! { { #wgpu_executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::Shaders { @@ -194,19 +222,20 @@ impl PerPixelAdjust { } }; + // call node codegen let mut parsed_node_fn = ParsedNodeFn { - vis: parsed.vis.clone(), + vis: self.parsed.vis.clone(), attributes: NodeFnAttributes { shader_node: Some(ShaderNodeType::GpuNode), - ..parsed.attributes.clone() + ..self.parsed.attributes.clone() }, - fn_name, - struct_name, - mod_name: mod_name.clone(), + fn_name: self.gpu_node_mod.clone(), + struct_name: format_ident!("{}", self.gpu_node_mod.to_string().to_case(Case::Pascal)), + mod_name: self.gpu_node_mod.clone(), fn_generics: vec![parse_quote!('a: 'n)], where_clause: None, input: Input { - pat_ident: parsed.input.pat_ident.clone(), + pat_ident: self.parsed.input.pat_ident.clone(), ty: parse_quote!(impl #gcore::context::Ctx), implementations: Default::default(), }, @@ -214,19 +243,22 @@ impl PerPixelAdjust { is_async: true, fields, body, - crate_name: parsed.crate_name.clone(), + crate_name: self.parsed.crate_name.clone(), description: "".to_string(), }; parsed_node_fn.replace_impl_trait_in_input(); - let gpu_node = crate::codegen::generate_node_code(&parsed_node_fn)?; + let gpu_node_impl = crate::codegen::generate_node_code(&parsed_node_fn)?; + // wrap node in `mod #gpu_node_mod` + let node_cfg = self.node_cfg; + let gpu_node_mod = &self.gpu_node_mod; Ok(quote! { #node_cfg - mod #mod_name { + mod #gpu_node_mod { use super::*; use wgpu_executor::WgpuExecutor; - #gpu_node + #gpu_node_impl } }) } @@ -234,7 +266,7 @@ impl PerPixelAdjust { struct Param<'a> { ident: Cow<'a, Ident>, - ty: Cow<'a, TokenStream>, + ty: TokenStream, param_type: ParamType, } From a8ba74e757ae55293814a36a42f434e12ad6341a Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 21 Aug 2025 12:38:23 +0200 Subject: [PATCH 28/31] shader-rt: correct arg buffer handling --- node-graph/gcore-shaders/src/blending.rs | 2 +- node-graph/graster-nodes/src/adjustments.rs | 4 +- .../src/shader_nodes/per_pixel_adjust.rs | 91 +++++++++++--- .../wgpu-executor/src/shader_runtime/mod.rs | 5 - .../per_pixel_adjust_runtime.rs | 112 ++++++++++++------ 5 files changed, 152 insertions(+), 62 deletions(-) diff --git a/node-graph/gcore-shaders/src/blending.rs b/node-graph/gcore-shaders/src/blending.rs index c3701e2cc0..b305dd0910 100644 --- a/node-graph/gcore-shaders/src/blending.rs +++ b/node-graph/gcore-shaders/src/blending.rs @@ -66,7 +66,7 @@ impl AlphaBlending { } #[repr(i32)] -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, bytemuck::NoUninit)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] pub enum BlendMode { // Basic group diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 35de34c76b..dc3bd15b82 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -30,7 +30,7 @@ use num_traits::float::Float; // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27clrL%27%20%3D%20Color%20Lookup // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Color%20Lookup%20(Photoshop%20CS6 -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, node_macro::ChoiceType, bytemuck::NoUninit)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] #[repr(u32)] @@ -560,7 +560,7 @@ pub enum RedGreenBlue { } /// Color Channel -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType, bytemuck::NoUninit)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Radio)] #[repr(u32)] diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 1096d88daa..57e4fcefa9 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -22,11 +22,11 @@ impl ShaderCodegen for PerPixelAdjust { fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { let fn_name = &parsed.fn_name; - // categorize params and assign image bindings - // bindings for images start at 1 - let params = { - let mut binding_cnt = 0; - parsed + let mut params; + let has_uniform; + { + // categorize params + params = parsed .fields .iter() .map(|f| { @@ -39,30 +39,50 @@ impl ShaderCodegen for PerPixelAdjust { param_type: ParamType::Uniform, }), ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => { - binding_cnt += 1; - Ok(Param { + let param = Param { ident: Cow::Owned(format_ident!("image_{}", &ident.ident)), ty: quote!(Image2d), - param_type: ParamType::Image { binding: binding_cnt }, - }) + param_type: ParamType::Image { binding: 0 }, + }; + Ok(param) } } }) - .collect::>>()? - }; + .collect::>>()?; + + has_uniform = params.iter().any(|p| matches!(p.param_type, ParamType::Uniform)); + + // assign image bindings + // if an arg_buffer exists, bindings for images start at 1 to leave 0 for arg buffer + let mut binding_cnt = if has_uniform { 1 } else { 0 }; + for p in params.iter_mut() { + match &mut p.param_type { + ParamType::Image { binding } => { + *binding = binding_cnt; + binding_cnt += 1; + } + ParamType::Uniform => {} + } + } + } let entry_point_mod = format_ident!("{}_gpu_entry_point", fn_name); let entry_point_name_ident = format_ident!("ENTRY_POINT_NAME"); let entry_point_name = quote!(#entry_point_mod::#entry_point_name_ident); + let uniform_struct_ident = format_ident!("Uniform"); + let uniform_struct = quote!(#entry_point_mod::#uniform_struct_ident); let gpu_node_mod = format_ident!("{}_gpu", fn_name); let codegen = PerPixelAdjustCodegen { parsed, node_cfg, params, + has_uniform, entry_point_mod, entry_point_name_ident, entry_point_name, + uniform_struct_ident, + uniform_struct, gpu_node_mod, }; @@ -77,9 +97,12 @@ pub struct PerPixelAdjustCodegen<'a> { parsed: &'a ParsedNodeFn, node_cfg: &'a TokenStream, params: Vec>, + has_uniform: bool, entry_point_mod: Ident, entry_point_name_ident: Ident, entry_point_name: TokenStream, + uniform_struct_ident: Ident, + uniform_struct: TokenStream, gpu_node_mod: Ident, } @@ -114,6 +137,7 @@ impl PerPixelAdjustCodegen<'_> { let entry_point_mod = &self.entry_point_mod; let entry_point_name = &self.entry_point_name_ident; + let uniform_struct_ident = &self.uniform_struct_ident; Ok(quote! { pub mod #entry_point_mod { use super::*; @@ -125,8 +149,10 @@ impl PerPixelAdjustCodegen<'_> { pub const #entry_point_name: &str = core::concat!(core::module_path!(), "::entry_point"); - pub struct Uniform { - #(#uniform_members),* + #[repr(C)] + #[derive(Copy, Clone, bytemuck::NoUninit)] + pub struct #uniform_struct_ident { + #(pub #uniform_members),* } #[spirv(fragment)] @@ -158,6 +184,11 @@ impl PerPixelAdjustCodegen<'_> { .iter() .map(|f| match &f.ty { ParsedFieldType::Regular(reg @ RegularParsedField { gpu_image: true, .. }) => Ok(ParsedField { + pat_ident: PatIdent { + mutability: None, + by_ref: None, + ..f.pat_ident.clone() + }, ty: ParsedFieldType::Regular(RegularParsedField { ty: raster_gpu.clone(), implementations: Punctuated::default(), @@ -165,7 +196,14 @@ impl PerPixelAdjustCodegen<'_> { }), ..f.clone() }), - ParsedFieldType::Regular(RegularParsedField { gpu_image: false, .. }) => Ok(f.clone()), + ParsedFieldType::Regular(RegularParsedField { gpu_image: false, .. }) => Ok(ParsedField { + pat_ident: PatIdent { + mutability: None, + by_ref: None, + ..f.pat_ident.clone() + }, + ..f.clone() + }), ParsedFieldType::Node { .. } => Err(syn::Error::new_spanned(&f.pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), }) .collect::>>()?; @@ -211,14 +249,35 @@ impl PerPixelAdjustCodegen<'_> { }; let gpu_image = &gpu_image_field.pat_ident.ident; + // uniform buffer struct construction + let has_uniform = self.has_uniform; + let uniform_buffer = if has_uniform { + let uniform_struct = &self.uniform_struct; + let uniform_members = self + .params + .iter() + .filter_map(|p| match p.param_type { + ParamType::Image { .. } => None, + ParamType::Uniform => Some(p.ident.as_ref()), + }) + .collect::>(); + quote!(Some(&super::#uniform_struct { + #(#uniform_members),* + })) + } else { + // explicit generics placed here cause it's easier than explicitly writing `run_per_pixel_adjust::<()>` + quote!(Option::<&()>::None) + }; + // node function body let entry_point_name = &self.entry_point_name; let body = quote! { { - #wgpu_executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::Shaders { + #wgpu_executor.shader_runtime.run_per_pixel_adjust(&::wgpu_executor::shader_runtime::per_pixel_adjust_runtime::Shaders { wgsl_shader: crate::WGSL_SHADER, fragment_shader_name: super::#entry_point_name, - }, #gpu_image, &1u32).await + has_uniform: #has_uniform, + }, #gpu_image, #uniform_buffer).await } }; diff --git a/node-graph/wgpu-executor/src/shader_runtime/mod.rs b/node-graph/wgpu-executor/src/shader_runtime/mod.rs index 2745d5bda8..e7e0df8d94 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/mod.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/mod.rs @@ -18,8 +18,3 @@ impl ShaderRuntime { } } } - -pub struct Shaders<'a> { - pub wgsl_shader: &'a str, - pub fragment_shader_name: &'a str, -} diff --git a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs index 119d346956..d958e0650d 100644 --- a/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs +++ b/node-graph/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs @@ -1,5 +1,5 @@ use crate::Context; -use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime, Shaders}; +use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; use bytemuck::NoUninit; use futures::lock::Mutex; use graphene_core::raster_types::{GPU, Raster}; @@ -27,24 +27,33 @@ impl PerPixelAdjustShaderRuntime { } impl ShaderRuntime { - pub async fn run_per_pixel_adjust(&self, shaders: &Shaders<'_>, textures: Table>, args: &T) -> Table> { + pub async fn run_per_pixel_adjust(&self, shaders: &Shaders<'_>, textures: Table>, args: Option<&T>) -> Table> { let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; let pipeline = cache .entry(shaders.fragment_shader_name.to_owned()) .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, &shaders)); - let device = &self.context.device; - let arg_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some(&format!("{} arg buffer", pipeline.name.as_str())), - usage: BufferUsages::STORAGE, - contents: bytemuck::bytes_of(args), + let arg_buffer = args.map(|args| { + let device = &self.context.device; + device.create_buffer_init(&BufferInitDescriptor { + label: Some(&format!("{} arg buffer", pipeline.name.as_str())), + usage: BufferUsages::STORAGE, + contents: bytemuck::bytes_of(args), + }) }); - pipeline.dispatch(&self.context, textures, &arg_buffer) + pipeline.dispatch(&self.context, textures, arg_buffer) } } +pub struct Shaders<'a> { + pub wgsl_shader: &'a str, + pub fragment_shader_name: &'a str, + pub has_uniform: bool, +} + pub struct PerPixelAdjustGraphicsPipeline { name: String, + has_uniform: bool, pipeline: wgpu::RenderPipeline, } @@ -62,32 +71,46 @@ impl PerPixelAdjustGraphicsPipeline { source: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)), }); + let entries: &[_] = if info.has_uniform { + &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ] + } else { + &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }] + }; let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some(&format!("PerPixelAdjust {} PipelineLayout", name)), bind_group_layouts: &[&device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some(&format!("PerPixelAdjust {} BindGroupLayout 0", name)), - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - sample_type: TextureSampleType::Float { filterable: false }, - view_dimension: TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - ], + entries, })], push_constant_ranges: &[], }); @@ -125,10 +148,15 @@ impl PerPixelAdjustGraphicsPipeline { multiview: None, cache: None, }); - Self { pipeline, name } + Self { + pipeline, + name, + has_uniform: info.has_uniform, + } } - pub fn dispatch(&self, context: &Context, textures: Table>, arg_buffer: &Buffer) -> Table> { + pub fn dispatch(&self, context: &Context, textures: Table>, arg_buffer: Option) -> Table> { + assert_eq!(self.has_uniform, arg_buffer.is_some()); let device = &context.device; let name = self.name.as_str(); @@ -140,11 +168,8 @@ impl PerPixelAdjustGraphicsPipeline { let view_in = tex_in.create_view(&TextureViewDescriptor::default()); let format = tex_in.format(); - let bind_group = device.create_bind_group(&BindGroupDescriptor { - label: Some(&format!("{name} bind group")), - // `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that - layout: &self.pipeline.get_bind_group_layout(0), - entries: &[ + let entries: &[_] = if let Some(arg_buffer) = arg_buffer.as_ref() { + &[ BindGroupEntry { binding: 0, resource: BindingResource::Buffer(BufferBinding { @@ -157,7 +182,18 @@ impl PerPixelAdjustGraphicsPipeline { binding: 1, resource: BindingResource::TextureView(&view_in), }, - ], + ] + } else { + &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view_in), + }] + }; + let bind_group = device.create_bind_group(&BindGroupDescriptor { + label: Some(&format!("{name} bind group")), + // `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that + layout: &self.pipeline.get_bind_group_layout(0), + entries, }); let tex_out = device.create_texture(&TextureDescriptor { From cb246556c7b934b523957b5bd287195a783286bb Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 27 Aug 2025 15:02:31 +0200 Subject: [PATCH 29/31] shader-node feature: put shader nodes behind `shader-node` feature --- node-graph/graster-nodes/Cargo.toml | 9 +++-- node-graph/graster-nodes/src/lib.rs | 2 +- node-graph/node-macro/src/codegen.rs | 2 +- node-graph/node-macro/src/shader_nodes/mod.rs | 37 ++++++++++++------- .../src/shader_nodes/per_pixel_adjust.rs | 11 ++---- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index 75eb666202..e1fc7a521b 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -14,10 +14,13 @@ workspace = true [features] default = ["std"] +shader-node = [ + "std", + "dep:graphene-raster-nodes-shaders", + "dep:wgpu-executor" +] std = [ "dep:graphene-core", - "dep:graphene-raster-nodes-shaders", - "dep:wgpu-executor", "dep:dyn-any", "dep:image", "dep:ndarray", @@ -27,8 +30,6 @@ std = [ "dep:serde", "dep:specta", "dep:kurbo", - "glam/debug-glam-assert", - "glam/serde", ] [dependencies] diff --git a/node-graph/graster-nodes/src/lib.rs b/node-graph/graster-nodes/src/lib.rs index d5383df034..f65ecf30c9 100644 --- a/node-graph/graster-nodes/src/lib.rs +++ b/node-graph/graster-nodes/src/lib.rs @@ -7,7 +7,7 @@ pub mod cubic_spline; pub mod fullscreen_vertex; /// required by shader macro -#[cfg(feature = "std")] +#[cfg(feature = "shader-node")] pub use graphene_raster_nodes_shaders::WGSL_SHADER; #[cfg(feature = "std")] diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index b3a1b28be3..98bb5588a9 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -295,7 +295,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result TokenStream { - match (&attributes.cfg, &attributes.shader_node) { - (Some(cfg), Some(_)) => quote!(#[cfg(all(#cfg, feature = #STD_FEATURE_GATE))]), - (Some(cfg), None) => quote!(#[cfg(#cfg)]), - (None, Some(_)) => quote!(#[cfg(feature = #STD_FEATURE_GATE)]), - (None, None) => quote!(), - } + let feature_gate = match &attributes.shader_node { + // shader node cfg is done on the mod + Some(ShaderNodeType::ShaderNode) => quote!(), + Some(_) => quote!(feature = #STD_FEATURE_GATE), + None => quote!(), + }; + let cfgs: Punctuated<_, Token![,]> = match &attributes.cfg { + None => [&feature_gate].into_iter().collect(), + Some(cfg) => [cfg, &feature_gate].into_iter().collect(), + }; + quote!(#[cfg(all(#cfgs))]) } #[derive(Debug, Clone, VariantNames)] pub(crate) enum ShaderNodeType { + /// Marker for this node being in a gpu node crate, but not having a gpu implementation. This is distinct from not + /// declaring `shader_node` at all, as it will wrap the CPU node with a `#[cfg(feature = "std")]` feature gate. + None, /// Marker for this node being a generated gpu node implementation, that should not emit anything to prevent /// recursively generating more gpu nodes. But it still counts as a gpu node and will get the /// `#[cfg(feature = "std")]` feature gate around it's impl. - GpuNode, + ShaderNode, PerPixelAdjust(PerPixelAdjust), } @@ -32,6 +42,7 @@ impl Parse for ShaderNodeType { fn parse(input: ParseStream) -> syn::Result { let ident: Ident = input.parse()?; Ok(match ident.to_string().as_str() { + "None" => ShaderNodeType::None, "PerPixelAdjust" => ShaderNodeType::PerPixelAdjust(PerPixelAdjust::parse(input)?), _ => return Err(Error::new_spanned(&ident, format!("attr 'shader_node' must be one of {:?}", Self::VARIANTS))), }) @@ -39,13 +50,13 @@ impl Parse for ShaderNodeType { } pub trait ShaderCodegen { - fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result; + fn codegen(&self, parsed: &ParsedNodeFn) -> syn::Result; } impl ShaderCodegen for ShaderNodeType { - fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { + fn codegen(&self, parsed: &ParsedNodeFn) -> syn::Result { match self { - ShaderNodeType::GpuNode => (), + ShaderNodeType::None | ShaderNodeType::ShaderNode => (), _ => { if parsed.is_async { return Err(Error::new_spanned(&parsed.fn_name, "Shader nodes must not be async")); @@ -54,8 +65,8 @@ impl ShaderCodegen for ShaderNodeType { } match self { - ShaderNodeType::GpuNode => Ok(ShaderTokens::default()), - ShaderNodeType::PerPixelAdjust(x) => x.codegen(parsed, node_cfg), + ShaderNodeType::None | ShaderNodeType::ShaderNode => Ok(ShaderTokens::default()), + ShaderNodeType::PerPixelAdjust(x) => x.codegen(parsed), } } } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 57e4fcefa9..cd0d911694 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -1,5 +1,5 @@ use crate::parsing::{Input, NodeFnAttributes, ParsedField, ParsedFieldType, ParsedNodeFn, RegularParsedField}; -use crate::shader_nodes::{ShaderCodegen, ShaderNodeType, ShaderTokens}; +use crate::shader_nodes::{SHADER_NODES_FEATURE_GATE, ShaderCodegen, ShaderNodeType, ShaderTokens}; use convert_case::{Case, Casing}; use proc_macro_crate::FoundCrate; use proc_macro2::{Ident, TokenStream}; @@ -19,7 +19,7 @@ impl Parse for PerPixelAdjust { } impl ShaderCodegen for PerPixelAdjust { - fn codegen(&self, parsed: &ParsedNodeFn, node_cfg: &TokenStream) -> syn::Result { + fn codegen(&self, parsed: &ParsedNodeFn) -> syn::Result { let fn_name = &parsed.fn_name; let mut params; @@ -75,7 +75,6 @@ impl ShaderCodegen for PerPixelAdjust { let codegen = PerPixelAdjustCodegen { parsed, - node_cfg, params, has_uniform, entry_point_mod, @@ -95,7 +94,6 @@ impl ShaderCodegen for PerPixelAdjust { pub struct PerPixelAdjustCodegen<'a> { parsed: &'a ParsedNodeFn, - node_cfg: &'a TokenStream, params: Vec>, has_uniform: bool, entry_point_mod: Ident, @@ -285,7 +283,7 @@ impl PerPixelAdjustCodegen<'_> { let mut parsed_node_fn = ParsedNodeFn { vis: self.parsed.vis.clone(), attributes: NodeFnAttributes { - shader_node: Some(ShaderNodeType::GpuNode), + shader_node: Some(ShaderNodeType::ShaderNode), ..self.parsed.attributes.clone() }, fn_name: self.gpu_node_mod.clone(), @@ -309,10 +307,9 @@ impl PerPixelAdjustCodegen<'_> { let gpu_node_impl = crate::codegen::generate_node_code(&parsed_node_fn)?; // wrap node in `mod #gpu_node_mod` - let node_cfg = self.node_cfg; let gpu_node_mod = &self.gpu_node_mod; Ok(quote! { - #node_cfg + #[cfg(feature = #SHADER_NODES_FEATURE_GATE)] mod #gpu_node_mod { use super::*; use wgpu_executor::WgpuExecutor; From 904de628abaa2b9039b986ccc30cc9595a89e81a Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 27 Aug 2025 15:03:53 +0200 Subject: [PATCH 30/31] shader-node feature: rename any `gpu_node` to `shader-node` --- .../src/shader_nodes/per_pixel_adjust.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index cd0d911694..f01c364223 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -71,7 +71,7 @@ impl ShaderCodegen for PerPixelAdjust { let entry_point_name = quote!(#entry_point_mod::#entry_point_name_ident); let uniform_struct_ident = format_ident!("Uniform"); let uniform_struct = quote!(#entry_point_mod::#uniform_struct_ident); - let gpu_node_mod = format_ident!("{}_gpu", fn_name); + let shader_node_mod = format_ident!("{}_shader_node", fn_name); let codegen = PerPixelAdjustCodegen { parsed, @@ -82,7 +82,7 @@ impl ShaderCodegen for PerPixelAdjust { entry_point_name, uniform_struct_ident, uniform_struct, - gpu_node_mod, + shader_node_mod, }; Ok(ShaderTokens { @@ -101,7 +101,7 @@ pub struct PerPixelAdjustCodegen<'a> { entry_point_name: TokenStream, uniform_struct_ident: Ident, uniform_struct: TokenStream, - gpu_node_mod: Ident, + shader_node_mod: Ident, } impl PerPixelAdjustCodegen<'_> { @@ -286,9 +286,9 @@ impl PerPixelAdjustCodegen<'_> { shader_node: Some(ShaderNodeType::ShaderNode), ..self.parsed.attributes.clone() }, - fn_name: self.gpu_node_mod.clone(), - struct_name: format_ident!("{}", self.gpu_node_mod.to_string().to_case(Case::Pascal)), - mod_name: self.gpu_node_mod.clone(), + fn_name: self.shader_node_mod.clone(), + struct_name: format_ident!("{}", self.shader_node_mod.to_string().to_case(Case::Pascal)), + mod_name: self.shader_node_mod.clone(), fn_generics: vec![parse_quote!('a: 'n)], where_clause: None, input: Input { @@ -307,10 +307,10 @@ impl PerPixelAdjustCodegen<'_> { let gpu_node_impl = crate::codegen::generate_node_code(&parsed_node_fn)?; // wrap node in `mod #gpu_node_mod` - let gpu_node_mod = &self.gpu_node_mod; + let shader_node_mod = &self.shader_node_mod; Ok(quote! { #[cfg(feature = #SHADER_NODES_FEATURE_GATE)] - mod #gpu_node_mod { + mod #shader_node_mod { use super::*; use wgpu_executor::WgpuExecutor; From babf85dfe950b012cb22c34bb57288a7b36d4a79 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 27 Aug 2025 15:16:36 +0200 Subject: [PATCH 31/31] shader-node feature: forward feature to `wasm` crate --- frontend/wasm/Cargo.toml | 1 + node-graph/gstd/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index 90b8ebe6ba..912bf8ed60 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -14,6 +14,7 @@ license = "Apache-2.0" default = ["gpu"] gpu = ["editor/gpu"] native = [] +shader-node = ["graphene-std/shader-node"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 6eb7e8a075..d8ad793c7e 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -21,6 +21,7 @@ image-compare = [] vello = ["dep:vello", "gpu"] resvg = [] wayland = ["graph-craft/wayland"] +shader-node = ["graphene-raster-nodes/shader-node"] [dependencies] # Local dependencies