diff --git a/.nix/flake.lock b/.nix/flake.lock index dc9b853784..8e6c993b66 100644 --- a/.nix/flake.lock +++ b/.nix/flake.lock @@ -63,11 +63,11 @@ ] }, "locked": { - "lastModified": 1753238793, - "narHash": "sha256-jmQeEpgX+++MEgrcikcwoSiI7vDZWLP0gci7XiWb9uQ=", + "lastModified": 1754621349, + "narHash": "sha256-JkXUS/nBHyUqVTuL4EDCvUWauTHV78EYfk+WqiTAMQ4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "0ad7ab4ca8e83febf147197e65c006dff60623ab", + "rev": "c448ab42002ac39d3337da10420c414fccfb1088", "type": "github" }, "original": { diff --git a/.nix/flake.nix b/.nix/flake.nix index d8fb88ed54..45164a8353 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -33,12 +33,57 @@ pkgs = import nixpkgs { 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; }; + rustGPUToolchainPkg = pkgs.rust-bin.nightly."2025-06-23".default.override { + extensions = rustExtensions ++ [ "rustc-dev" "llvm-tools" ]; + }; + + rustPlatformRustGPUToolchain = pkgs.makeRustPlatform { + cargo = rustGPUToolchainPkg; + rustc = rustGPUToolchainPkg; + }; + + rustc_codegen_spirv = rustPlatformRustGPUToolchain.buildRustPackage (finalAttrs: { + pname = "rustc_codegen_spirv"; + version = "0-unstable-2025-08-04"; + src = pkgs.fetchFromGitHub { + owner = "Rust-GPU"; + repo = "rust-gpu"; + rev = "3f05f5482824e3b1fbb44c9ef90a8795a0204c7c"; + hash = "sha256-ygNxjkzuvcO2jLYhayNuIthhH6/seCbTq3M0IkbsDrY="; + }; + cargoHash = "sha256-SzTvKUG/da//pHb7hN230wRsQ6BYAkP8HoXqJO30/dU="; + + cargoBuildFlags = [ "-p" "rustc_codegen_spirv" "--features=use-installed-tools" "--no-default-features" ]; + + doCheck = false; + }); + + + cargoRustGPUPkg = pkgs.writeShellScriptBin "cargo" '' + #!${pkgs.bash}/bin/bash + + filtered_args=() + for arg in "$@"; do + case "$arg" in + +nightly|+nightly-*) ;; + *) filtered_args+=("$arg") ;; + esac + done + + echo "running cargo with rust-gpu toolchain" + echo "cargo ${"\${filtered_args[@]}"}" + exec ${rustGPUToolchainPkg}/bin/cargo ${"\${filtered_args[@]}"} + ''; + + libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { version = "138.0.26"; gitRevision = "84f2d27"; @@ -79,7 +124,7 @@ # Development tools that don't need to be in LD_LIBRARY_PATH buildTools = [ - rustc-wasm + # rust pkgs.nodejs pkgs.nodePackages.npm pkgs.binaryen @@ -91,6 +136,10 @@ # Linker pkgs.mold + + pkgs.spirv-tools + cargoRustGPUPkg + rustGPUToolchainPkg ]; # Development tools that don't need to be in LD_LIBRARY_PATH devTools = with pkgs; [ @@ -113,6 +162,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_PATH="${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so"; + shellHook = '' alias cargo='mold --run cargo' ''; diff --git a/Cargo.lock b/Cargo.lock index b9a131eba5..985dc51f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,6 +664,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" @@ -1058,6 +1127,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" @@ -1119,6 +1215,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" @@ -1135,6 +1252,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" @@ -1353,6 +1479,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" @@ -1604,6 +1740,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" @@ -2041,16 +2186,27 @@ dependencies = [ "glam", "graphene-core", "graphene-core-shaders", + "graphene-raster-nodes-shaders", "image", "ndarray", "node-macro", + "num-traits", "rand 0.9.1", "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" @@ -2670,6 +2826,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" @@ -2876,6 +3052,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" @@ -2933,9 +3129,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" @@ -3127,6 +3323,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", ] @@ -3152,7 +3349,7 @@ dependencies = [ "petgraph 0.8.2", "rustc-hash 1.1.0", "spirv", - "strum", + "strum 0.26.3", "thiserror 2.0.12", "unicode-ident", ] @@ -3258,7 +3455,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "strum", + "strum 0.26.3", "syn 2.0.104", ] @@ -3278,6 +3475,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" @@ -3738,6 +3959,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" @@ -4396,6 +4626,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" @@ -4521,6 +4757,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" @@ -4655,6 +4900,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" @@ -4673,6 +4928,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" @@ -4845,6 +5127,9 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -4855,6 +5140,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" @@ -4879,9 +5185,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", @@ -4953,6 +5259,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" @@ -5130,8 +5457,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" @@ -5165,7 +5541,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]] @@ -5181,6 +5566,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" @@ -5589,9 +5986,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" @@ -5719,6 +6123,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" @@ -5796,6 +6206,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" @@ -6377,7 +6793,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 f6dc8df0fc..681e79c691 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "node-graph/graph-craft", "node-graph/graphene-cli", "node-graph/graster-nodes", + "node-graph/graster-nodes/shaders", "node-graph/gsvg-renderer", "node-graph/interpreted-executor", "node-graph/node-macro", @@ -165,6 +166,11 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing = "0.1.41" rfd = "0.15.4" open = "5.3.2" +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/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 07739e46df..e780b2c6cd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -146,9 +146,9 @@ pub(crate) fn property_from_type( Type::Concrete(concrete_type) => { match concrete_type.alias.as_ref().map(|x| x.as_ref()) { // Aliased types (ambiguous values) - Some("Percentage") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(), - Some("SignedPercentage") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(), - Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(), + Some("Percentage") | Some("PercentageF32") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(), + Some("SignedPercentage") | Some("SignedPercentageF32") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(), + Some("Angle") | Some("AngleF32") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(), Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(), Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(), Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(), diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index 114f2b4489..b597570675 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -137,7 +137,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster { } pub fn blend_with_mode(background: TableRow>, foreground: TableRow>, blend_mode: BlendMode, opacity: f64) -> TableRow> { - let opacity = opacity / 100.; + let opacity = opacity as f32 / 100.; match std::hint::black_box(blend_mode) { // Normal group BlendMode::Normal => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Normal, opacity)), diff --git a/node-graph/gcore-shaders/src/color/color.rs b/node-graph/gcore-shaders/src/color/color.rs index b745d56547..b955db1d65 100644 --- a/node-graph/gcore-shaders/src/color/color.rs +++ b/node-graph/gcore-shaders/src/color/color.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(target_arch = "spirv")] use num_traits::Euclid; @@ -700,7 +701,7 @@ impl Color { if c_s <= 0.5 { c_b - (1. - 2. * c_s) * c_b * (1. - c_b) } else { - let d: fn(f32) -> f32 = |x| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() }; + let d = |x: f32| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() }; c_b + (2. * c_s - 1.) * (d(c_b) - c_b) } } @@ -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/gcore-shaders/src/registry.rs b/node-graph/gcore-shaders/src/registry.rs index 69ef59753c..d1957c8d29 100644 --- a/node-graph/gcore-shaders/src/registry.rs +++ b/node-graph/gcore-shaders/src/registry.rs @@ -1,10 +1,16 @@ pub mod types { /// 0% - 100% pub type Percentage = f64; + /// 0% - 100% + pub type PercentageF32 = f32; /// -100% - 100% pub type SignedPercentage = f64; + /// -100% - 100% + pub type SignedPercentageF32 = f32; /// -180° - 180° pub type Angle = f64; + /// -180° - 180° + pub type AngleF32 = f32; /// Ends in the unit of x pub type Multiplier = f64; /// Non-negative integer with px unit diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index 9ef3f33a13..11c17e6da2 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -6,6 +6,12 @@ description = "graphene raster data format" authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" +[lib] +crate-type = ["rlib", "dylib"] + +[lints] +workspace = true + [features] default = ["std"] std = [ @@ -19,7 +25,8 @@ std = [ "dep:fastnoise-lite", "dep:serde", "dep:specta", - "dep:glam" + "dep:glam", + "dep:graphene-raster-nodes-shaders", ] [dependencies] @@ -30,9 +37,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 } +spirv-std = { workspace = true } +num-traits = { workspace = true } # glam is reexported from gcore-shaders in no_std mode glam = { workspace = true, optional = true } 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..50061aefcd --- /dev/null +++ b/node-graph/graster-nodes/shaders/build.rs @@ -0,0 +1,36 @@ +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"), "/..")); + + 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 + 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/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" +} 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")); diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index c0df2f879f..a1cdea30dc 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -7,10 +7,13 @@ use core::fmt::Debug; use graphene_core::gradient::GradientStops; #[cfg(feature = "std")] use graphene_core::raster_types::{CPU, Raster}; +#[cfg(feature = "std")] use graphene_core::table::Table; use graphene_core_shaders::color::Color; use graphene_core_shaders::context::Ctx; -use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage}; +use graphene_core_shaders::registry::types::{AngleF32, PercentageF32, SignedPercentageF32}; +#[cfg(not(feature = "std"))] +use num_traits::float::Float; // TODO: Implement the following: // Color Balance @@ -30,6 +33,7 @@ use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage #[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")] @@ -48,6 +52,7 @@ fn luminance>( Table>, GradientStops, )] + #[gpu_image] mut input: T, luminance_calc: LuminanceCalculation, ) -> T { @@ -64,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( @@ -72,6 +77,7 @@ fn gamma_correction>( Table>, GradientStops, )] + #[gpu_image] mut input: T, #[default(2.2)] #[range((0.01, 10.))] @@ -92,6 +98,7 @@ fn extract_channel>( Table>, GradientStops, )] + #[gpu_image] mut input: T, channel: RedGreenBlueAlpha, ) -> T { @@ -115,13 +122,14 @@ fn make_opaque>( Table>, GradientStops, )] + #[gpu_image] mut input: T, ) -> T { input.adjust(|color| { if color.a() == 0. { return color.with_alpha(1.); } - Color::from_rgbaf32(color.r() / color.a(), color.g() / color.a(), color.b() / color.a(), 1.).unwrap() + Color::from_rgbaf32_unchecked(color.r() / color.a(), color.g() / color.a(), color.b() / color.a(), 1.) }); input } @@ -132,7 +140,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( @@ -140,9 +148,10 @@ fn brightness_contrast>( Table>, GradientStops, )] + #[gpu_image] mut input: T, - brightness: SignedPercentage, - contrast: SignedPercentage, + brightness: SignedPercentageF32, + contrast: SignedPercentageF32, use_classic: bool, ) -> T { if use_classic { @@ -229,12 +238,13 @@ fn levels>( Table>, GradientStops, )] + #[gpu_image] mut image: T, - #[default(0.)] shadows: Percentage, - #[default(50.)] midtones: Percentage, - #[default(100.)] highlights: Percentage, - #[default(0.)] output_minimums: Percentage, - #[default(100.)] output_maximums: Percentage, + #[default(0.)] shadows: PercentageF32, + #[default(50.)] midtones: PercentageF32, + #[default(100.)] highlights: PercentageF32, + #[default(0.)] output_minimums: PercentageF32, + #[default(100.)] output_maximums: PercentageF32, ) -> T { image.adjust(|color| { let color = color.to_gamma_srgb(); @@ -289,33 +299,34 @@ fn levels>( // https://stackoverflow.com/a/55233732/775283 // Works the same for gamma and linear color #[node_macro::node(name("Black & White"), category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn black_and_white>( +fn black_and_white>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut image: T, #[default(Color::BLACK)] tint: Color, #[default(40.)] #[range((-200., 300.))] - reds: Percentage, + reds: PercentageF32, #[default(60.)] #[range((-200., 300.))] - yellows: Percentage, + yellows: PercentageF32, #[default(40.)] #[range((-200., 300.))] - greens: Percentage, + greens: PercentageF32, #[default(60.)] #[range((-200., 300.))] - cyans: Percentage, + cyans: PercentageF32, #[default(20.)] #[range((-200., 300.))] - blues: Percentage, + blues: PercentageF32, #[default(80.)] #[range((-200., 300.))] - magentas: Percentage, + magentas: PercentageF32, ) -> T { image.adjust(|color| { let color = color.to_gamma_srgb(); @@ -350,7 +361,7 @@ async fn black_and_white>( // TODO: Fix "Color" blend mode implementation so it matches the expected behavior perfectly (it's currently close) let color = tint.with_luminance(luminance); - let color = Color::from_rgbaf32(color.r(), color.g(), color.b(), alpha_part).unwrap(); + let color = Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), alpha_part); color.to_linear_srgb() }); @@ -361,17 +372,18 @@ async fn black_and_white>( // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn hue_saturation>( +fn hue_saturation>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut input: T, - hue_shift: Angle, - saturation_shift: SignedPercentage, - lightness_shift: SignedPercentage, + hue_shift: AngleF32, + saturation_shift: SignedPercentageF32, + lightness_shift: SignedPercentageF32, ) -> T { input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -395,13 +407,14 @@ async fn hue_saturation>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn invert>( +fn invert>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut input: T, ) -> T { input.adjust(|color| { @@ -417,16 +430,17 @@ async fn invert>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn threshold>( +fn threshold>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut image: T, - #[default(50.)] min_luminance: Percentage, - #[default(100.)] max_luminance: Percentage, + #[default(50.)] min_luminance: PercentageF32, + #[default(100.)] max_luminance: PercentageF32, luminance_calc: LuminanceCalculation, ) -> T { image.adjust(|color| { @@ -462,15 +476,16 @@ async fn threshold>( // When both parameters are set, it is equivalent to running this adjustment twice, with only vibrance set and then only saturation set. // (Except for some noise probably due to rounding error.) #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn vibrance>( +fn vibrance>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut image: T, - vibrance: SignedPercentage, + vibrance: SignedPercentageF32, ) -> T { image.adjust(|color| { let vibrance = vibrance as f32 / 100.; @@ -537,6 +552,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, @@ -626,14 +642,15 @@ 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))] -async fn channel_mixer>( +#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"), cfg(feature = "std"))] +fn channel_mixer>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut image: T, monochrome: bool, @@ -754,14 +771,15 @@ 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))] -async fn selective_color>( +#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"), cfg(feature = "std"))] +fn selective_color>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut image: T, mode: RelativeAbsolute, @@ -897,13 +915,14 @@ async fn selective_color>( // https://www.axiomx.com/posterize.htm // This algorithm produces fully accurate output in relation to the industry standard. #[node_macro::node(category("Raster: Adjustment"), shader_node(PerPixelAdjust))] -async fn posterize>( +fn posterize>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut input: T, #[default(4)] #[hard_min(2.)] @@ -930,29 +949,30 @@ async fn posterize>( // Algorithm based on: // https://geraldbakker.nl/psnumbers/exposure.html #[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"), shader_node(PerPixelAdjust))] -async fn exposure>( +fn exposure>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] mut input: T, - exposure: f64, - offset: f64, + exposure: f32, + offset: f32, #[default(1.)] #[range((0.01, 10.))] #[hard_min(0.0001)] - gamma_correction: f64, + gamma_correction: f32, ) -> T { input.adjust(|color| { let adjusted = color // Exposure - .map_rgb(|c: f32| c * 2_f32.powf(exposure as f32)) + .map_rgb(|c: f32| c * 2_f32.powf(exposure)) // Offset - .map_rgb(|c: f32| c + offset as f32) + .map_rgb(|c: f32| c + offset) // Gamma correction - .gamma(gamma_correction as f32); + .gamma(gamma_correction); adjusted.map_rgb(|c: f32| c.clamp(0., 1.)) }); diff --git a/node-graph/graster-nodes/src/blending_nodes.rs b/node-graph/graster-nodes/src/blending_nodes.rs index 78dc027019..d624587def 100644 --- a/node-graph/graster-nodes/src/blending_nodes.rs +++ b/node-graph/graster-nodes/src/blending_nodes.rs @@ -3,11 +3,12 @@ use crate::adjust::Adjust; use graphene_core::gradient::GradientStops; #[cfg(feature = "std")] use graphene_core::raster_types::{CPU, Raster}; +#[cfg(feature = "std")] use graphene_core::table::Table; use graphene_core_shaders::Ctx; use graphene_core_shaders::blending::BlendMode; use graphene_core_shaders::color::{Color, Pixel}; -use graphene_core_shaders::registry::types::Percentage; +use graphene_core_shaders::registry::types::PercentageF32; pub trait Blend { fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; @@ -71,7 +72,7 @@ mod blend_std { } #[inline(always)] -pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f64) -> Color { +pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color { let target_color = match blend_mode { // Other utility blend modes (hidden from the normal list) - do not have alpha blend BlendMode::Erase => return background.alpha_subtract(foreground), @@ -123,13 +124,14 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM } #[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))] -async fn blend + Send>( +fn blend + Send>( _: impl Ctx, #[implementations( Color, Table>, GradientStops, )] + #[gpu_image] over: T, #[expose] #[implementations( @@ -137,9 +139,10 @@ async fn blend + Send>( Table>, GradientStops, )] + #[gpu_image] under: T, blend_mode: BlendMode, - #[default(100.)] opacity: Percentage, + #[default(100.)] opacity: PercentageF32, ) -> T { over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } @@ -152,10 +155,11 @@ fn color_overlay>( Table>, GradientStops, )] + #[gpu_image] mut image: T, #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, - #[default(100.)] opacity: Percentage, + #[default(100.)] opacity: PercentageF32, ) -> T { let opacity = (opacity as f32 / 100.).clamp(0., 1.); @@ -176,11 +180,11 @@ fn color_overlay>( fn blend_color_pair(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color where BlendModeNode: graphene_core::Node<'n, (), Output = BlendMode> + 'n, - OpacityNode: graphene_core::Node<'n, (), Output = Percentage> + 'n, + OpacityNode: graphene_core::Node<'n, (), Output = graphene_core_shaders::registry::types::Percentage> + 'n, { let blend_mode = blend_mode.eval(()); let opacity = opacity.eval(()); - blend_colors(input.0, input.1, blend_mode, opacity / 100.) + blend_colors(input.0, input.1, blend_mode, opacity as f32 / 100.) } #[cfg(all(feature = "std", test))] @@ -192,7 +196,7 @@ mod test { use graphene_core::table::Table; #[tokio::test] - async fn color_overlay_multiply() { + fn color_overlay_multiply() { let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4); let image = Image::new(1, 1, image_color); @@ -200,7 +204,7 @@ mod test { let overlay_color = Color::GREEN; // 100% of the output should come from the multiplied value - let opacity = 100_f64; + let opacity = 100.; let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); let result = result.iter().next().unwrap().element; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 8039fdf273..9b1e0976f2 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; @@ -347,6 +347,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result, @@ -121,7 +123,9 @@ pub(crate) enum ParsedField { number_step: Option, implementations: Punctuated, unit: Option, + gpu_image: bool, }, + /// a param of `impl Node` with `#[implementation(in -> out)]` Node { pat_ident: PatIdent, name: Option, @@ -533,6 +537,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 '{}': {}\nUSAGE EXAMPLE: #[step(2.)]", ident, e))) }) .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 @@ -597,6 +602,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul value_source, implementations, unit, + gpu_image, }) } } 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..38fddb4942 --- /dev/null +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -0,0 +1,107 @@ +use crate::parsing::{ParsedField, ParsedNodeFn}; +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| match f { + ParsedField::Node { pat_ident, .. } => Err(syn::Error::new_spanned(pat_ident, "PerPixelAdjust shader nodes cannot accept other nodes as generics")), + ParsedField::Regular { gpu_image: false, pat_ident, ty, .. } => Ok(Param { + ident: Cow::Borrowed(&pat_ident.ident), + ty: Cow::Owned(ty.to_token_stream()), + param_type: ParamType::Uniform, + }), + ParsedField::Regular { gpu_image: true, pat_ident, .. } => { + binding_cnt += 1; + Ok(Param { + ident: Cow::Owned(format_ident!("image_{}", &pat_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, +}