gpu invert node demo

This commit is contained in:
Firestar99
2025-06-05 16:00:36 +02:00
parent eb8605a0b3
commit bf2673428a
12 changed files with 1531 additions and 26 deletions

409
Cargo.lock generated
View File

@@ -82,6 +82,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-activity"
version = "0.5.2"
@@ -637,6 +643,27 @@ dependencies = [
"serde",
]
[[package]]
name = "cargo-gpu"
version = "0.1.0"
source = "git+https://github.com/rust-gpu/cargo-gpu?rev=7f5358bf3bac7363c8017409942464365ca61fd8#7f5358bf3bac7363c8017409942464365ca61fd8"
dependencies = [
"anyhow",
"cargo_metadata",
"clap",
"crossterm",
"directories",
"env_logger 0.10.2",
"log",
"naga 25.0.1",
"relative-path",
"rustc_codegen_spirv-target-specs",
"semver",
"serde",
"serde_json",
"spirv-builder",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
@@ -776,9 +803,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.31"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
"clap_derive",
@@ -786,9 +813,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.31"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [
"anstream",
"anstyle",
@@ -798,9 +825,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.28"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -824,6 +851,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "codespan-reporting"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
dependencies = [
"serde",
"termcolor",
"unicode-width",
]
[[package]]
name = "color"
version = "0.1.0"
@@ -1119,6 +1157,31 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags 2.9.0",
"crossterm_winapi",
"mio",
"parking_lot",
"rustix 0.38.44",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.3"
@@ -1269,13 +1332,34 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
"dirs-sys 0.5.0",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users 0.4.6",
"windows-sys 0.48.0",
]
[[package]]
@@ -1286,7 +1370,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"redox_users 0.5.0",
"windows-sys 0.59.0",
]
@@ -1457,14 +1541,27 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.6"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"jiff",
"log",
]
@@ -1568,6 +1665,18 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@@ -1691,6 +1800,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futf"
version = "0.1.5"
@@ -2298,6 +2416,15 @@ dependencies = [
"wgpu",
]
[[package]]
name = "graphene-core-shader"
version = "0.1.0"
dependencies = [
"bytemuck",
"glam",
"spirv-std",
]
[[package]]
name = "graphene-std"
version = "0.1.0"
@@ -2371,7 +2498,7 @@ dependencies = [
"convert_case 0.7.1",
"derivative",
"dyn-any",
"env_logger",
"env_logger 0.11.8",
"futures",
"glam",
"gpu-executor",
@@ -2516,13 +2643,14 @@ dependencies = [
[[package]]
name = "half"
version = "2.4.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
dependencies = [
"bytemuck",
"cfg-if",
"crunchy",
"num-traits",
"serde",
]
@@ -2538,6 +2666,8 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
@@ -3044,6 +3174,35 @@ dependencies = [
"cfb",
]
[[package]]
name = "inotify"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
@@ -3172,6 +3331,30 @@ dependencies = [
"system-deps",
]
[[package]]
name = "jiff"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.99",
]
[[package]]
name = "jni"
version = "0.21.1"
@@ -3269,6 +3452,26 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kqueue"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "kuchikiki"
version = "0.8.2"
@@ -3362,7 +3565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -3573,6 +3776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
@@ -3608,7 +3812,7 @@ dependencies = [
"bit-set",
"bitflags 2.9.0",
"cfg_aliases 0.1.1",
"codespan-reporting",
"codespan-reporting 0.11.1",
"hexf-parse",
"indexmap 2.7.1",
"log",
@@ -3620,6 +3824,29 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "naga"
version = "25.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.9.0",
"cfg_aliases 0.2.1",
"codespan-reporting 0.12.0",
"half",
"hashbrown 0.15.2",
"indexmap 2.7.1",
"log",
"num-traits",
"once_cell",
"petgraph 0.8.1",
"rustc-hash 1.1.0",
"spirv",
"thiserror 2.0.12",
]
[[package]]
name = "native-tls"
version = "0.2.14"
@@ -3748,6 +3975,34 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "notify"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
dependencies = [
"bitflags 2.9.0",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.52.0",
]
[[package]]
name = "notify-types"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
dependencies = [
"instant",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -4092,9 +4347,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.20.3"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "oorandom"
@@ -4347,6 +4602,18 @@ dependencies = [
"indexmap 2.7.1",
]
[[package]]
name = "petgraph"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06"
dependencies = [
"fixedbitset 0.5.7",
"hashbrown 0.15.2",
"indexmap 2.7.1",
"serde",
]
[[package]]
name = "phf"
version = "0.8.0"
@@ -5011,6 +5278,12 @@ dependencies = [
"rgb",
]
[[package]]
name = "raw-string"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2"
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -5071,6 +5344,17 @@ dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 1.0.69",
]
[[package]]
name = "redox_users"
version = "0.5.0"
@@ -5111,6 +5395,12 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "renderdoc-sys"
version = "1.1.0"
@@ -5229,6 +5519,16 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rspirv"
version = "0.12.0+sdk-1.3.268.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cf3a93856b6e5946537278df0d3075596371b1950ccff012f02b0f7eafec8d"
dependencies = [
"rustc-hash 1.1.0",
"spirv",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -5247,6 +5547,23 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_codegen_spirv-target-specs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c89eaf493b3dfc730cda42a77014aad65e03213992c7afe0dff60a9f7d3dd94"
[[package]]
name = "rustc_codegen_spirv-types"
version = "0.9.0"
source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919"
dependencies = [
"rspirv",
"serde",
"serde_json",
"spirv",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -5691,6 +6008,27 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@@ -5904,15 +6242,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
dependencies = [
"bitflags 2.9.0",
"serde",
]
[[package]]
name = "spirv-builder"
version = "0.9.0"
source = "git+https://github.com/Rust-GPU/rust-gpu?rev=8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919#8cb17db18d8a44e1de7c9b3ea2b65d5aaf24b919"
dependencies = [
"clap",
"memchr",
"notify",
"raw-string",
"rustc_codegen_spirv-types",
"semver",
"serde",
"serde_json",
"thiserror 2.0.12",
]
[[package]]
name = "spirv-std"
version = "0.9.0"
source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421"
source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d"
dependencies = [
"bitflags 1.3.2",
"glam",
"libm",
"num-traits",
"spirv-std-macros",
"spirv-std-types",
@@ -5921,7 +6277,7 @@ dependencies = [
[[package]]
name = "spirv-std-macros"
version = "0.9.0"
source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421"
source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d"
dependencies = [
"proc-macro2",
"quote",
@@ -5932,7 +6288,7 @@ dependencies = [
[[package]]
name = "spirv-std-types"
version = "0.9.0"
source = "git+https://github.com/Rust-GPU/rust-gpu.git#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421"
source = "git+https://github.com/Rust-GPU/rust-gpu?rev=9a357691334b9cdd13c82a740ced97c5d857bf4d#9a357691334b9cdd13c82a740ced97c5d857bf4d"
[[package]]
name = "stable_deref_trait"
@@ -7134,7 +7490,7 @@ version = "0.3.0"
source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d831180be81820de06cca29a97a757f5"
dependencies = [
"bytemuck",
"naga",
"naga 23.1.0",
"thiserror 2.0.12",
"vello_encoding",
]
@@ -7540,7 +7896,7 @@ dependencies = [
"document-features",
"js-sys",
"log",
"naga",
"naga 23.1.0",
"parking_lot",
"profiling",
"raw-window-handle",
@@ -7568,7 +7924,7 @@ dependencies = [
"document-features",
"indexmap 2.7.1",
"log",
"naga",
"naga 23.1.0",
"once_cell",
"parking_lot",
"profiling",
@@ -7587,12 +7943,15 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"bytemuck",
"cargo-gpu",
"dyn-any",
"env_logger 0.11.8",
"futures",
"futures-intrusive",
"glam",
"gpu-executor",
"graphene-core",
"graphene-core-shader",
"half",
"log",
"node-macro",
@@ -7632,7 +7991,7 @@ dependencies = [
"libloading 0.8.6",
"log",
"metal",
"naga",
"naga 23.1.0",
"ndk-sys 0.5.0+25.2.9519653",
"objc",
"once_cell",

View File

@@ -5,6 +5,7 @@ members = [
"frontend/wasm",
"frontend/src-tauri",
"node-graph/gcore",
"node-graph/gcore-shader",
"node-graph/gstd",
"node-graph/graph-craft",
"node-graph/graphene-cli",
@@ -69,7 +70,7 @@ axum = "0.8"
chrono = "0.4"
ron = "0.8"
fastnoise-lite = "1.1"
spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "9a357691334b9cdd13c82a740ced97c5d857bf4d" }
wgpu-types = "23"
wgpu = "23"
once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
@@ -108,6 +109,7 @@ kurbo = { version = "0.11.0", features = ["serde"] }
petgraph = { version = "0.7.1", default-features = false, features = [
"graphmap",
] }
cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "7f5358bf3bac7363c8017409942464365ca61fd8", features = ["wgsl-out"] }
[profile.dev]
opt-level = 1

View File

@@ -14,6 +14,7 @@ use graph_craft::ProtoNodeIdentifier;
use graph_craft::concrete;
use graph_craft::document::value::*;
use graph_craft::document::*;
use graphene_core::application_io::TextureFrameTable;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::ImageFrameTable;
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
@@ -1850,6 +1851,91 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "GPU Invert",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(TextureFrameTable), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::gcore_shader_nodes::GpuInvertNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(generic!(T)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
// experimentally determined to work
inputs: vec![NodeInput::value(TaggedValue::None, true)],
// inputs: vec![NodeInput::value(TaggedValue::TextureFrame(TextureFrameTable::default()), true)],
// inputs: vec![],
// inputs: vec![NodeInput::network(concrete!(TextureFrameTable), 0)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Upload Texture".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Extract",
category: "Debug",

View File

@@ -0,0 +1,27 @@
[package]
name = "graphene-core-shader"
version = "0.1.0"
edition = "2024"
description = "Graphene nodes compiled to shaders"
authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
[lib]
crate-type = ["rlib", "cdylib"]
[features]
gpu = ["glam/libm"]
[dependencies]
# Workspace dependencies
spirv-std = { workspace = true }
bytemuck = { workspace = true }
glam = { workspace = true, features = [
"scalar-math", "bytemuck"
] }
[lints.rust]
# the spirv target is not in the list of common cfgs so must be added manually
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(target_arch, values("spirv"))',
] }

View File

@@ -0,0 +1,782 @@
use bytemuck::{Pod, Zeroable};
use core::hash::Hash;
use glam::Vec4;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::Euclid;
#[cfg(target_arch = "spirv")]
use spirv_std::num_traits::float::Float;
// -----------------------------------------------------
// custom color start
// -----------------------------------------------------
impl From<Vec4> for Color {
fn from(value: Vec4) -> Self {
Color {
red: value.x,
green: value.y,
blue: value.z,
alpha: value.w,
}
}
}
impl From<Color> for Vec4 {
fn from(value: Color) -> Self {
Vec4::new(value.red, value.green, value.blue, value.alpha)
}
}
// -----------------------------------------------------
// custom color end
// -----------------------------------------------------
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
pub struct SRGBA8 {
red: u8,
green: u8,
blue: u8,
alpha: u8,
}
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
pub struct Luma(pub f32);
/// Structure that represents a color.
/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque).
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
pub struct Color {
red: f32,
green: f32,
blue: f32,
alpha: f32,
}
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for Color {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.red.to_bits().hash(state);
self.green.to_bits().hash(state);
self.blue.to_bits().hash(state);
self.alpha.to_bits().hash(state);
}
}
impl Color {
pub const BLACK: Color = Color::from_rgbf32_unchecked(0., 0., 0.);
pub const WHITE: Color = Color::from_rgbf32_unchecked(1., 1., 1.);
pub const RED: Color = Color::from_rgbf32_unchecked(1., 0., 0.);
pub const GREEN: Color = Color::from_rgbf32_unchecked(0., 1., 0.);
pub const BLUE: Color = Color::from_rgbf32_unchecked(0., 0., 1.);
pub const YELLOW: Color = Color::from_rgbf32_unchecked(1., 1., 0.);
pub const CYAN: Color = Color::from_rgbf32_unchecked(0., 1., 1.);
pub const MAGENTA: Color = Color::from_rgbf32_unchecked(1., 0., 1.);
pub const TRANSPARENT: Color = Self {
red: 0.,
green: 0.,
blue: 0.,
alpha: 0.,
};
/// Returns `Some(Color)` if `red`, `green`, `blue` and `alpha` have a valid value. Negative numbers (including `-0.0`), NaN, and infinity are not valid values and return `None`.
/// Alpha values greater than `1.0` are not valid.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.3, 0.14, 0.15, 0.92).unwrap();
/// assert!(color.components() == (0.3, 0.14, 0.15, 0.92));
///
/// let color = Color::from_rgbaf32(1., 1., 1., f32::NAN);
/// assert!(color == None);
/// ```
#[inline(always)]
pub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Option<Color> {
if alpha > 1. || [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) {
return None;
}
let color = Color { red, green, blue, alpha };
Some(color)
}
/// Return an opaque `Color` from given `f32` RGB channels.
#[inline(always)]
pub const fn from_rgbf32_unchecked(red: f32, green: f32, blue: f32) -> Color {
Color { red, green, blue, alpha: 1. }
}
/// Return an opaque `Color` from given `f32` RGB channels.
#[inline(always)]
pub const fn from_rgbaf32_unchecked(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
Color { red, green, blue, alpha }
}
/// Return an opaque `Color` from given `f32` RGB channels.
#[inline(always)]
pub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
Color::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha)
}
/// Return an opaque SDR `Color` given RGB channels from `0` to `255`, premultiplied by alpha.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgb8_srgb(0x72, 0x67, 0x62);
/// let color2 = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0xFF);
/// assert_eq!(color, color2)
/// ```
#[inline(always)]
pub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color {
Color::from_rgba8_srgb(red, green, blue, 255)
}
// TODO: Should this be premult?
/// Return an SDR `Color` given RGBA channels from `0` to `255`, premultiplied by alpha.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0x61);
/// ```
#[inline(always)]
pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
let map_range = |int_color| int_color as f32 / 255.;
let red = map_range(red);
let green = map_range(green);
let blue = map_range(blue);
let alpha = map_range(alpha);
Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha)
}
/// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1)
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.);
/// ```
pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {
let temp1 = if lightness < 0.5 {
lightness * (saturation + 1.)
} else {
lightness + saturation - lightness * saturation
};
let temp2 = 2. * lightness - temp1;
#[cfg(not(target_arch = "spirv"))]
let rem = |x: f32| x.rem_euclid(1.);
#[cfg(target_arch = "spirv")]
let rem = |x: f32| x.rem_euclid(&1.);
let mut red = rem(hue + 1. / 3.);
let mut green = rem(hue);
let mut blue = rem(hue - 1. / 3.);
fn map_channel(channel: &mut f32, temp2: f32, temp1: f32) {
*channel = if *channel * 6. < 1. {
temp2 + (temp1 - temp2) * 6. * *channel
} else if *channel * 2. < 1. {
temp1
} else if *channel * 3. < 2. {
temp2 + (temp1 - temp2) * (2. / 3. - *channel) * 6.
} else {
temp2
}
.clamp(0., 1.);
}
map_channel(&mut red, temp2, temp1);
map_channel(&mut green, temp2, temp1);
map_channel(&mut blue, temp2, temp1);
Color { red, green, blue, alpha }
}
/// Return the `red` component.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert!(color.r() == 0.114);
/// ```
#[inline(always)]
pub fn r(&self) -> f32 {
self.red
}
/// Return the `green` component.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert!(color.g() == 0.103);
/// ```
#[inline(always)]
pub fn g(&self) -> f32 {
self.green
}
/// Return the `blue` component.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert!(color.b() == 0.98);
/// ```
#[inline(always)]
pub fn b(&self) -> f32 {
self.blue
}
/// Return the `alpha` component without checking its expected `0.0` to `1.0` range.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert!(color.a() == 0.97);
/// ```
#[inline(always)]
pub fn a(&self) -> f32 {
self.alpha
}
#[inline(always)]
pub fn average_rgb_channels(&self) -> f32 {
(self.red + self.green + self.blue) / 3.
}
#[inline(always)]
pub fn minimum_rgb_channels(&self) -> f32 {
self.red.min(self.green).min(self.blue)
}
#[inline(always)]
pub fn maximum_rgb_channels(&self) -> f32 {
self.red.max(self.green).max(self.blue)
}
// From https://stackoverflow.com/a/56678483/775283
#[inline(always)]
pub fn luminance_srgb(&self) -> f32 {
0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue
}
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
#[inline(always)]
pub fn luminance_rec_601(&self) -> f32 {
0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
}
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
#[inline(always)]
pub fn luminance_rec_601_rounded(&self) -> f32 {
0.3 * self.red + 0.59 * self.green + 0.11 * self.blue
}
// From https://stackoverflow.com/a/56678483/775283
#[inline(always)]
pub fn luminance_perceptual(&self) -> f32 {
let luminance = self.luminance_srgb();
if luminance <= 0.008856 {
(luminance * 903.3) / 100.
} else {
(luminance.cbrt() * 116. - 16.) / 100.
}
}
#[inline(always)]
pub fn from_luminance(luminance: f32) -> Color {
Color {
red: luminance,
green: luminance,
blue: luminance,
alpha: 1.,
}
}
#[inline(always)]
pub fn with_luminance(&self, luminance: f32) -> Color {
let delta = luminance - self.luminance_rec_601_rounded();
self.map_rgb(|c| (c + delta).clamp(0., 1.))
}
#[inline(always)]
pub fn saturation(&self) -> f32 {
let max = (self.red).max(self.green).max(self.blue);
let min = (self.red).min(self.green).min(self.blue);
max - min
}
#[inline(always)]
pub fn with_saturation(&self, saturation: f32) -> Color {
let [hue, _, lightness, alpha] = self.to_hsla();
Color::from_hsla(hue, saturation, lightness, alpha)
}
pub fn with_alpha(&self, alpha: f32) -> Color {
Color {
red: self.red,
green: self.green,
blue: self.blue,
alpha,
}
}
pub fn with_red(&self, red: f32) -> Color {
Color {
red,
green: self.green,
blue: self.blue,
alpha: self.alpha,
}
}
pub fn with_green(&self, green: f32) -> Color {
Color {
red: self.red,
green,
blue: self.blue,
alpha: self.alpha,
}
}
pub fn with_blue(&self, blue: f32) -> Color {
Color {
red: self.red,
green: self.green,
blue,
alpha: self.alpha,
}
}
#[inline(always)]
pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {
c_s
}
#[inline(always)]
pub fn blend_multiply(c_b: f32, c_s: f32) -> f32 {
c_s * c_b
}
#[inline(always)]
pub fn blend_darken(c_b: f32, c_s: f32) -> f32 {
c_s.min(c_b)
}
#[inline(always)]
pub fn blend_color_burn(c_b: f32, c_s: f32) -> f32 {
if c_b == 1. {
1.
} else if c_s == 0. {
0.
} else {
1. - ((1. - c_b) / c_s).min(1.)
}
}
#[inline(always)]
pub fn blend_linear_burn(c_b: f32, c_s: f32) -> f32 {
c_b + c_s - 1.
}
#[inline(always)]
pub fn blend_darker_color(&self, other: Color) -> Color {
if self.average_rgb_channels() <= other.average_rgb_channels() { *self } else { other }
}
#[inline(always)]
pub fn blend_screen(c_b: f32, c_s: f32) -> f32 {
1. - (1. - c_s) * (1. - c_b)
}
#[inline(always)]
pub fn blend_lighten(c_b: f32, c_s: f32) -> f32 {
c_s.max(c_b)
}
#[inline(always)]
pub fn blend_color_dodge(c_b: f32, c_s: f32) -> f32 {
if c_s == 1. { 1. } else { (c_b / (1. - c_s)).min(1.) }
}
#[inline(always)]
pub fn blend_linear_dodge(c_b: f32, c_s: f32) -> f32 {
c_b + c_s
}
#[inline(always)]
pub fn blend_lighter_color(&self, other: Color) -> Color {
if self.average_rgb_channels() >= other.average_rgb_channels() { *self } else { other }
}
pub fn blend_softlight(c_b: f32, c_s: f32) -> f32 {
if c_s <= 0.5 {
c_b - (1. - 2. * c_s) * c_b * (1. - c_b)
} else {
let d: fn(f32) -> f32 = |x| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() };
c_b + (2. * c_s - 1.) * (d(c_b) - c_b)
}
}
pub fn blend_hardlight(c_b: f32, c_s: f32) -> f32 {
if c_s <= 0.5 {
Color::blend_multiply(2. * c_s, c_b)
} else {
Color::blend_screen(2. * c_s - 1., c_b)
}
}
pub fn blend_vivid_light(c_b: f32, c_s: f32) -> f32 {
if c_s <= 0.5 {
Color::blend_color_burn(2. * c_s, c_b)
} else {
Color::blend_color_dodge(2. * c_s - 1., c_b)
}
}
pub fn blend_linear_light(c_b: f32, c_s: f32) -> f32 {
if c_s <= 0.5 {
Color::blend_linear_burn(2. * c_s, c_b)
} else {
Color::blend_linear_dodge(2. * c_s - 1., c_b)
}
}
pub fn blend_pin_light(c_b: f32, c_s: f32) -> f32 {
if c_s <= 0.5 {
Color::blend_darken(2. * c_s, c_b)
} else {
Color::blend_lighten(2. * c_s - 1., c_b)
}
}
pub fn blend_hard_mix(c_b: f32, c_s: f32) -> f32 {
if Color::blend_linear_light(c_b, c_s) < 0.5 { 0. } else { 1. }
}
pub fn blend_difference(c_b: f32, c_s: f32) -> f32 {
(c_b - c_s).abs()
}
pub fn blend_exclusion(c_b: f32, c_s: f32) -> f32 {
c_b + c_s - 2. * c_b * c_s
}
pub fn blend_subtract(c_b: f32, c_s: f32) -> f32 {
c_b - c_s
}
pub fn blend_divide(c_b: f32, c_s: f32) -> f32 {
if c_b == 0. { 1. } else { c_b / c_s }
}
pub fn blend_hue(&self, c_s: Color) -> Color {
let sat_b = self.saturation();
let lum_b = self.luminance_rec_601();
c_s.with_saturation(sat_b).with_luminance(lum_b)
}
pub fn blend_saturation(&self, c_s: Color) -> Color {
let sat_s = c_s.saturation();
let lum_b = self.luminance_rec_601();
self.with_saturation(sat_s).with_luminance(lum_b)
}
pub fn blend_color(&self, c_s: Color) -> Color {
let lum_b = self.luminance_rec_601();
c_s.with_luminance(lum_b)
}
pub fn blend_luminosity(&self, c_s: Color) -> Color {
let lum_s = c_s.luminance_rec_601();
self.with_luminance(lum_s)
}
/// Return the all components as a tuple, first component is red, followed by green, followed by blue, followed by alpha.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// assert_eq!(color.components(), (0.114, 0.103, 0.98, 0.97));
/// ```
#[inline(always)]
pub fn components(&self) -> (f32, f32, f32, f32) {
(self.red, self.green, self.blue, self.alpha)
}
/// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in linear space.
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// // TODO: Add test
/// ```
#[inline(always)]
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
let gamma = self.to_gamma_srgb();
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
}
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
/// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1)
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla();
/// ```
pub fn to_hsla(&self) -> [f32; 4] {
let min_channel = self.red.min(self.green).min(self.blue);
let max_channel = self.red.max(self.green).max(self.blue);
let lightness = (min_channel + max_channel) / 2.;
let saturation = if min_channel == max_channel {
0.
} else if lightness <= 0.5 {
(max_channel - min_channel) / (max_channel + min_channel)
} else {
(max_channel - min_channel) / (2. - max_channel - min_channel)
};
let hue = if self.red >= self.green && self.red >= self.blue {
(self.green - self.blue) / (max_channel - min_channel)
} else if self.green >= self.red && self.green >= self.blue {
2. + (self.blue - self.red) / (max_channel - min_channel)
} else {
4. + (self.red - self.green) / (max_channel - min_channel)
} / 6.;
#[cfg(not(target_arch = "spirv"))]
let hue = hue.rem_euclid(1.);
#[cfg(target_arch = "spirv")]
let hue = hue.rem_euclid(&1.);
[hue, saturation, lightness, self.alpha]
}
// TODO: Readd formatting
/// Creates a color from a 8-character RGBA hex string (without a # prefix).
///
/// # Examples
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgba_str("7C67FA61").unwrap();
/// ```
pub fn from_rgba_str(color_str: &str) -> Option<Color> {
if color_str.len() != 8 {
return None;
}
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
let a = u8::from_str_radix(&color_str[6..8], 16).ok()?;
Some(Color::from_rgba8_srgb(r, g, b, a))
}
/// Creates a color from a 6-character RGB hex string (without a # prefix).
///
/// ```
/// use graphene_core::raster::color::Color;
/// let color = Color::from_rgb_str("7C67FA").unwrap();
/// ```
pub fn from_rgb_str(color_str: &str) -> Option<Color> {
if color_str.len() != 6 {
return None;
}
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
Some(Color::from_rgb8_srgb(r, g, b))
}
/// Linearly interpolates between two colors based on t.
///
/// T must be between 0 and 1.
#[inline(always)]
pub fn lerp(&self, other: &Color, t: f32) -> Self {
assert!((0. ..=1.).contains(&t));
Color::from_rgbaf32_unchecked(
self.red + ((other.red - self.red) * t),
self.green + ((other.green - self.green) * t),
self.blue + ((other.blue - self.blue) * t),
self.alpha + ((other.alpha - self.alpha) * t),
)
}
#[inline(always)]
pub fn gamma(&self, gamma: f32) -> Color {
let gamma = gamma.max(0.0001);
// From https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/
let inverse_gamma = 1. / gamma;
self.map_rgb(|c: f32| c.powf(inverse_gamma))
}
#[inline(always)]
pub fn to_linear_srgb(&self) -> Self {
Self {
red: Self::srgb_to_linear(self.red),
green: Self::srgb_to_linear(self.green),
blue: Self::srgb_to_linear(self.blue),
alpha: self.alpha,
}
}
#[inline(always)]
pub fn to_gamma_srgb(&self) -> Self {
Self {
red: Self::linear_to_srgb(self.red),
green: Self::linear_to_srgb(self.green),
blue: Self::linear_to_srgb(self.blue),
alpha: self.alpha,
}
}
#[inline(always)]
pub fn srgb_to_linear(channel: f32) -> f32 {
if channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) }
}
#[inline(always)]
pub fn linear_to_srgb(channel: f32) -> f32 {
if channel <= 0.0031308 { channel * 12.92 } else { 1.055 * channel.powf(1. / 2.4) - 0.055 }
}
#[inline(always)]
pub fn map_rgba<F: Fn(f32) -> f32>(&self, f: F) -> Self {
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), f(self.a()))
}
#[inline(always)]
pub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())
}
#[inline(always)]
pub fn apply_opacity(&self, opacity: f32) -> Self {
Self::from_rgbaf32_unchecked(self.r() * opacity, self.g() * opacity, self.b() * opacity, self.a() * opacity)
}
#[inline(always)]
pub fn to_associated_alpha(&self, alpha: f32) -> Self {
Self {
red: self.red * alpha,
green: self.green * alpha,
blue: self.blue * alpha,
alpha: self.alpha * alpha,
}
}
#[inline(always)]
pub fn to_unassociated_alpha(&self) -> Self {
if self.alpha == 0. {
return *self;
}
let unmultiply = 1. / self.alpha;
Self {
red: self.red * unmultiply,
green: self.green * unmultiply,
blue: self.blue * unmultiply,
alpha: self.alpha,
}
}
#[inline(always)]
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
let background = self.to_unassociated_alpha();
Color {
red: f(background.red, other.red).clamp(0., 1.),
green: f(background.green, other.green).clamp(0., 1.),
blue: f(background.blue, other.blue).clamp(0., 1.),
alpha: other.alpha,
}
}
#[inline(always)]
pub fn alpha_blend(&self, other: Color) -> Self {
let inv_alpha = 1. - other.alpha;
Self {
red: self.red * inv_alpha + other.red,
green: self.green * inv_alpha + other.green,
blue: self.blue * inv_alpha + other.blue,
alpha: self.alpha * inv_alpha + other.alpha,
}
}
#[inline(always)]
pub fn alpha_add(&self, other: Color) -> Self {
Self {
alpha: (self.alpha + other.alpha).clamp(0., 1.),
..*self
}
}
#[inline(always)]
pub fn alpha_subtract(&self, other: Color) -> Self {
Self {
alpha: (self.alpha - other.alpha).clamp(0., 1.),
..*self
}
}
#[inline(always)]
pub fn alpha_multiply(&self, other: Color) -> Self {
Self {
alpha: (self.alpha * other.alpha).clamp(0., 1.),
..*self
}
}
}
#[test]
fn hsl_roundtrip() {
for (red, green, blue) in [
(24, 98, 118),
(69, 11, 89),
(54, 82, 38),
(47, 76, 50),
(25, 15, 73),
(62, 57, 33),
(55, 2, 18),
(12, 3, 82),
(91, 16, 98),
(91, 39, 82),
(97, 53, 32),
(76, 8, 91),
(54, 87, 19),
(56, 24, 88),
(14, 82, 34),
(61, 86, 31),
(73, 60, 75),
(95, 79, 88),
(13, 34, 4),
(82, 84, 84),
(255, 255, 178),
] {
let col = Color::from_rgb8_srgb(red, green, blue);
let [hue, saturation, lightness, alpha] = col.to_hsla();
let result = Color::from_hsla(hue, saturation, lightness, alpha);
assert!((col.r() - result.r()) < f32::EPSILON * 100.);
assert!((col.g() - result.g()) < f32::EPSILON * 100.);
assert!((col.b() - result.b()) < f32::EPSILON * 100.);
assert!((col.a() - result.a()) < f32::EPSILON * 100.);
}
}

View File

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

View File

@@ -0,0 +1,39 @@
use crate::color::Color;
// exact copy of the invert node
// #[node_macro::node(category("Raster: Adjustment"))]
fn invert_copy(
// _: impl Ctx,
// #[implementations(
// Color,
// ImageFrameTable<Color>,
// GradientStops,
// )]
// mut input: T,
color: Color,
) -> Color {
// input.adjust(|color| {
let color = color.to_gamma_srgb();
let color = color.map_rgb(|c| color.a() - c);
color.to_linear_srgb()
// });
// input
}
pub mod gpu_invert_shader {
use crate::color::Color;
use crate::gpu_invert::invert_copy;
use glam::{Vec4, Vec4Swizzles};
use spirv_std::image::sample_with::lod;
use spirv_std::image::{Image2d, ImageWithMethods};
use spirv_std::spirv;
#[spirv(fragment)]
pub fn gpu_invert_fragment(#[spirv(frag_coord)] frag_coord: Vec4, #[spirv(descriptor_set = 0, binding = 0)] texture: &Image2d, color_out: &mut Vec4) {
let color = Color::from(texture.fetch_with(frag_coord.xy().as_uvec2(), lod(0)));
let color = invert_copy(color);
*color_out = Vec4::from(color);
}
}

View File

@@ -0,0 +1,5 @@
#![no_std]
pub mod color;
pub mod fullscreen_vertex;
pub mod gpu_invert;

View File

@@ -15,6 +15,8 @@ gpu-executor = { path = "../gpu-executor" }
# Workspace dependencies
graphene-core = { workspace = true, features = ["std", "alloc", "gpu", "wgpu"] }
# required for cargo watch to pick up changes
graphene-core-shader = { path = "../gcore-shader" }
dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] }
node-macro = { workspace = true }
num-traits = { workspace = true }
@@ -40,3 +42,7 @@ half = "2.4.1"
# Optional dependencies
nvtx = { version = "1.3", optional = true }
[build-dependencies]
cargo-gpu = { workspace = true }
env_logger = { workspace = true }

View File

@@ -0,0 +1,34 @@
use cargo_gpu::CompileResultNagaExt;
use cargo_gpu::naga::back::wgsl::WriterFlags;
use cargo_gpu::naga::valid::Capabilities;
use cargo_gpu::spirv_builder::{MetadataPrintout, SpirvMetadata};
use std::path::PathBuf;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::builder().init();
let shader_crate = PathBuf::from("../gcore-shader");
// install the toolchain and build the `rustc_codegen_spirv` codegen backend with it
let backend = cargo_gpu::Install::from_shader_crate(shader_crate.clone()).run()?;
// build the shader crate
let mut builder = backend.to_spirv_builder(shader_crate, "spirv-unknown-vulkan1.2");
builder.print_metadata = MetadataPrintout::DependencyOnly;
builder.spirv_metadata = SpirvMetadata::Full;
builder.shader_crate_features.default_features = false;
builder.shader_crate_features.features = vec![String::from("gpu")];
let spv_result = builder.build()?;
// transpile the spv binaries to wgsl
let wgsl_result = spv_result.naga_transpile(Capabilities::empty())?.to_wgsl(WriterFlags::empty())?;
let path_to_wgsl = wgsl_result.module.unwrap_single();
// emit path to wgsl into env var, used in `quad.rs` like this:
// > include_str!(env!("WGSL_SHADER_PATH"))
println!("cargo::rustc-env=WGSL_SHADER_PATH={}", path_to_wgsl.display());
// you could also generate some rust source code into the `std::env::var("OUT_DIR")` dir
// and use `include!(concat!(env!("OUT_DIR"), "/shader_symbols.rs"));` to include it
Ok(())
}

View File

@@ -0,0 +1,150 @@
use crate::{Context, WgpuExecutor};
use graphene_core::Ctx;
use graphene_core::application_io::{ImageTexture, TextureFrameTable};
use graphene_core::instances::Instance;
use std::borrow::Cow;
use std::sync::Arc;
use wgpu::{
BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Device, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, Queue,
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat,
TextureViewDescriptor, VertexState,
};
const WGSL_SHADER: &str = include_str!(env!("WGSL_SHADER_PATH"));
#[node_macro::node(category(""))]
async fn gpu_invert<'a: 'n>(_: impl Ctx, input: TextureFrameTable, executor: &'a WgpuExecutor) -> TextureFrameTable {
let Context { device, queue, .. } = &executor.context;
// this should be cached
let graphics_pipeline = GraphitePerPixelGraphicsPipeline::new(device, "gpu_invertgpu_invert_shadergpu_invert_fragment");
graphics_pipeline.run(input, queue)
}
pub struct GraphitePerPixelGraphicsPipeline {
device: Arc<Device>,
render_pipeline_f32: wgpu::RenderPipeline,
render_pipeline_f16: wgpu::RenderPipeline,
render_pipeline_srgb8: wgpu::RenderPipeline,
}
impl GraphitePerPixelGraphicsPipeline {
pub fn new(device: &Arc<Device>, fragment_shader_name: &str) -> Self {
let shader_module = device.create_shader_module(ShaderModuleDescriptor {
label: Some("graphite wgsl shader"),
source: ShaderSource::Wgsl(Cow::Borrowed(WGSL_SHADER)),
});
let create_render_pipeline = |format| {
device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("gpu_invert"),
layout: None,
vertex: VertexState {
module: &shader_module,
entry_point: Some("fullscreen_vertexfullscreen_vertex"),
compilation_options: Default::default(),
buffers: &[],
},
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: Default::default(),
fragment: Some(FragmentState {
module: &shader_module,
entry_point: Some(fragment_shader_name),
compilation_options: Default::default(),
targets: &[Some(ColorTargetState {
format,
blend: None,
write_mask: Default::default(),
})],
}),
multiview: None,
cache: None,
})
};
Self {
device: device.clone(),
render_pipeline_f32: create_render_pipeline(TextureFormat::Rgba32Float),
render_pipeline_f16: create_render_pipeline(TextureFormat::Rgba16Float),
render_pipeline_srgb8: create_render_pipeline(TextureFormat::Rgba8UnormSrgb),
}
}
pub fn get(&self, format: TextureFormat) -> &wgpu::RenderPipeline {
match format {
TextureFormat::Rgba32Float => &self.render_pipeline_f32,
TextureFormat::Rgba16Float => &self.render_pipeline_f16,
TextureFormat::Rgba8UnormSrgb => &self.render_pipeline_srgb8,
_ => panic!("unsupported"),
}
}
pub fn run(&self, input: TextureFrameTable, queue: &Arc<Queue>) -> TextureFrameTable {
let device = &self.device;
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") });
let out = input
.instance_ref_iter()
.map(|instance| {
let view_in = instance.instance.texture.create_view(&TextureViewDescriptor::default());
let format = instance.instance.texture.format();
let pipeline = self.get(format);
let bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some("gpu_invert bind group"),
// `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that
layout: &pipeline.get_bind_group_layout(0),
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view_in),
}],
});
let tex_out = device.create_texture(&TextureDescriptor {
label: Some("gpu_invert_out"),
size: instance.instance.texture.size(),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[format],
});
let view_out = tex_out.create_view(&TextureViewDescriptor::default());
let mut rp = cmd.begin_render_pass(&RenderPassDescriptor {
label: Some("gpu_invert rp"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &view_out,
resolve_target: None,
ops: Operations {
// should be dont_care but wgpu doesn't expose that
load: LoadOp::Clear(wgpu::Color::BLACK),
store: StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rp.set_pipeline(&pipeline);
rp.set_bind_group(0, Some(&bind_group), &[]);
rp.draw(0..3, 0..1);
Instance {
instance: ImageTexture { texture: Arc::new(tex_out) },
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *instance.source_node_id,
}
})
.collect::<TextureFrameTable>();
queue.submit([cmd.finish()]);
out
}
}

View File

@@ -1,5 +1,6 @@
mod context;
mod executor;
mod gcore_shader_nodes;
use anyhow::{Result, bail};
pub use context::Context;