From f76b850b9c671ae06ba4ea2bf5207d3eed8ddd08 Mon Sep 17 00:00:00 2001 From: nat-rix Date: Fri, 9 Jun 2023 09:03:15 +0200 Subject: [PATCH] Fix Imaginate by porting its JS roundtrip code to graph-based async execution in Rust (#1250) * Create asynchronous rust imaginate node * Make a first imaginate request via rust * Implement parsing of imaginate API result image * Stop refresh timer from affecting imaginate progress requests * Add cargo-about clarification for rustls-webpki * Delete imaginate.ts and all uses of its functions * Add imaginate img2img feature * Fix imaginate random seed button * Fix imaginate ui inferring non-custom resolutions * Fix the imaginate progress indicator * Remove ImaginatePreferences from being compiled into node graph * Regenerate imaginate only when hitting button * Add ability to terminate imaginate requests * Add imaginate server check feature * Do not compile wasm_bindgen bindings in graphite_editor for tests * Address some review suggestions - move wasm futures dependency in editor to the future-executor crate - guard wasm-bindgen in editor behind a `wasm` feature flag - dont make seed number input a slider - remove poll_server_check from process_message function beginning - guard wasm related code behind `cfg(target_arch = "wasm32")` instead of `cfg(test)` - Call the imaginate idle states "Ready" and "Done" instead of "Nothing to do" - Call the imaginate uploading state "Uploading Image" instead of "Uploading Input Image" - Remove the EvalSyncNode * Fix imaginate host name being restored between graphite instances also change the progress status texts a bit. --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 710 +++++++++--------- about.toml | 9 +- document-legacy/src/document.rs | 20 +- editor/Cargo.toml | 7 +- .../src/messages/frontend/frontend_message.rs | 35 - .../portfolio/document/document_message.rs | 6 - .../document/document_message_handler.rs | 28 +- .../node_graph/node_graph_message_handler.rs | 14 +- .../document_node_types.rs | 60 +- .../node_properties.rs | 141 ++-- .../messages/portfolio/portfolio_message.rs | 23 +- .../portfolio/portfolio_message_handler.rs | 87 +-- .../src/messages/portfolio/utility_types.rs | 24 +- .../preferences_message_handler.rs | 21 +- editor/src/node_graph_executor.rs | 119 ++- frontend/src/state-providers/portfolio.ts | 34 - frontend/src/utility-functions/imaginate.ts | 367 --------- frontend/src/wasm-communication/editor.ts | 4 + frontend/src/wasm-communication/messages.ts | 86 +-- frontend/wasm/Cargo.toml | 6 +- frontend/wasm/src/editor_api.rs | 64 +- node-graph/future-executor/Cargo.toml | 7 +- node-graph/future-executor/src/lib.rs | 5 + node-graph/gcore/src/application_io.rs | 17 + node-graph/graph-craft/src/document.rs | 9 +- node-graph/graph-craft/src/document/value.rs | 22 +- node-graph/graph-craft/src/imaginate_input.rs | 197 +++-- node-graph/gstd/Cargo.toml | 18 +- node-graph/gstd/src/any.rs | 5 + node-graph/gstd/src/http.rs | 15 - node-graph/gstd/src/imaginate.rs | 517 +++++++++++++ node-graph/gstd/src/lib.rs | 2 + node-graph/gstd/src/raster.rs | 80 +- node-graph/interpreted-executor/src/lib.rs | 1 - .../interpreted-executor/src/node_registry.rs | 66 +- 35 files changed, 1500 insertions(+), 1326 deletions(-) delete mode 100644 frontend/src/utility-functions/imaginate.ts create mode 100644 node-graph/gstd/src/imaginate.rs diff --git a/Cargo.lock b/Cargo.lock index 43950867..0ae7da2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -73,9 +73,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -119,6 +119,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -130,9 +136,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "approx" @@ -196,9 +202,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ash" -version = "0.37.2+1.3.238" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ "libloading 0.7.4", ] @@ -211,7 +217,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -235,7 +241,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -287,9 +293,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70caf9f1b0c045f7da350636435b775a9733adf2df56e8aa2a29210fbc335d4" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", @@ -357,9 +363,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bezier-rs" @@ -379,7 +385,7 @@ dependencies = [ "js-sys", "log", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.4.5", "serde_json", "wasm-bindgen", "wasm-bindgen-test", @@ -478,9 +484,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "serde", @@ -488,9 +494,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" @@ -509,7 +515,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -545,15 +551,16 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] name = "calloop" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ + "bitflags 1.3.2", "log", "nix 0.25.1", "slotmap", @@ -563,12 +570,12 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.13.3" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "toml 0.5.11", + "toml 0.7.3", ] [[package]] @@ -588,12 +595,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.6.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", - "uuid 0.8.2", + "fnv", + "uuid", ] [[package]] @@ -607,9 +615,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a" dependencies = [ "smallvec", "target-lexicon", @@ -629,14 +637,15 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", + "serde", "time 0.1.45", "wasm-bindgen", "winapi", @@ -886,12 +895,12 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -904,50 +913,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - [[package]] name = "d3d12" version = "0.6.0" @@ -961,9 +926,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ "darling_core", "darling_macro", @@ -971,27 +936,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1031,9 +996,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -1068,11 +1033,11 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.0", ] [[package]] @@ -1092,15 +1057,15 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] @@ -1136,6 +1101,19 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +[[package]] +name = "embed-resource" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" +dependencies = [ + "cc", + "rustc_version", + "toml 0.7.3", + "vswhom", + "winreg 0.11.0", +] + [[package]] name = "embed_plist" version = "1.2.2" @@ -1215,11 +1193,11 @@ dependencies = [ [[package]] name = "field-offset" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset 0.8.0", + "memoffset 0.9.0", "rustc_version", ] @@ -1274,9 +1252,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -1297,6 +1275,7 @@ version = "0.1.0" dependencies = [ "futures", "log", + "wasm-bindgen-futures", "wasm-rs-async-executor", ] @@ -1367,7 +1346,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -1448,7 +1427,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -1465,7 +1444,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -1477,7 +1456,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", "x11", ] @@ -1517,9 +1496,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -1558,7 +1537,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", "winapi", ] @@ -1615,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -1639,9 +1618,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "807edf58b70c0b5b2181dd39fe1839dbdb3ba02645630dc5f753e23da307f762" dependencies = [ "js-sys", "slotmap", @@ -1657,14 +1636,14 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] name = "gpu-alloc" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" dependencies = [ "bitflags 1.3.2", "gpu-alloc-types", @@ -1796,6 +1775,7 @@ name = "graphene-std" version = "0.1.0" dependencies = [ "autoquant", + "base64 0.21.2", "bezier-rs", "bytemuck", "compilation-client", @@ -1818,6 +1798,7 @@ dependencies = [ "tempfile", "vulkan-executor", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "wgpu", "wgpu-executor", @@ -1870,6 +1851,7 @@ dependencies = [ "derivative", "dyn-any", "env_logger", + "future-executor", "futures", "glam", "gpu-executor", @@ -1890,6 +1872,7 @@ dependencies = [ "specta", "test-case", "thiserror", + "wasm-bindgen", "wgpu-executor", ] @@ -1917,7 +1900,7 @@ dependencies = [ "log", "ron", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.5.0", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -1962,7 +1945,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -1981,9 +1964,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -2070,6 +2053,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -2168,9 +2157,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", @@ -2194,9 +2183,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2208,19 +2197,18 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "ico" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" dependencies = [ "byteorder", "png", @@ -2234,9 +2222,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2282,13 +2270,14 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] name = "infer" -version = "0.7.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" dependencies = [ "cfb", ] @@ -2327,9 +2316,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -2408,21 +2397,22 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" dependencies = [ "serde", "serde_json", + "thiserror", "treediff", ] @@ -2451,8 +2441,8 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.9.4" -source = "git+https://github.com/linebender/kurbo.git#01b52cd85c3a9b1be2c6e39ab97fcf401a8911c4" +version = "0.9.5" +source = "git+https://github.com/linebender/kurbo.git#8c07a06d5e04daf0d65abb5a59c63c8003ac59b1" dependencies = [ "arrayvec", "serde", @@ -2478,9 +2468,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.142" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libdbus-sys" @@ -2513,9 +2503,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "line-wrap" @@ -2526,20 +2516,11 @@ dependencies = [ "safemem", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "litrs" @@ -2549,9 +2530,9 @@ checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -2559,12 +2540,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "loom" @@ -2597,7 +2575,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -2646,10 +2624,11 @@ checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "matrixmultiply" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb99c395ae250e1bf9133673f03ca9f97b7e71b705436bf8f089453445d1e9fe" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ + "autocfg", "rawpointer", ] @@ -2679,9 +2658,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -2733,21 +2712,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "naga" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d3edd593521f4a1dfd9b25193ed0224764572905f013d30ca5fbb85e010876" +checksum = "80cd00bd6180a8790f1c020ed258a46b8d73dd5bd6af104a238c9d71f806938e" dependencies = [ "bit-set", "bitflags 1.3.2", @@ -3093,7 +3072,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -3172,18 +3151,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "open" @@ -3197,9 +3176,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -3218,7 +3197,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -3229,9 +3208,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", @@ -3306,7 +3285,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -3321,15 +3300,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] @@ -3346,9 +3325,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" @@ -3460,22 +3439,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -3492,9 +3471,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" @@ -3502,12 +3481,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "indexmap", "line-wrap", "quick-xml 0.28.2", "serde", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -3577,9 +3556,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -3610,9 +3589,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -3678,7 +3657,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "serde", ] @@ -3752,20 +3731,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick 1.0.2", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -3785,9 +3764,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "remain" @@ -3797,7 +3776,7 @@ checksum = "e13cca257d068dd3a390d04b2c3009a3fad2ee5048dfa8f239d048372810470c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -3808,11 +3787,11 @@ checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "reqwest" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -3845,7 +3824,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -3921,9 +3900,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.15" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags 1.3.2", "errno", @@ -3935,14 +3914,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] @@ -3951,7 +3930,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -3984,9 +3973,9 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe_arch" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +checksum = "62a7484307bd40f8f7ccbacccac730108f2cae119a3b11c74485b48aa9ea650f" dependencies = [ "bytemuck", ] @@ -4027,12 +4016,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "sct" version = "0.7.0" @@ -4058,9 +4041,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -4071,9 +4054,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -4110,9 +4093,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -4129,14 +4112,25 @@ dependencies = [ ] [[package]] -name = "serde_derive" -version = "1.0.160" +name = "serde-wasm-bindgen" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -4167,14 +4161,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" dependencies = [ "serde", ] @@ -4193,24 +4187,30 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap", "serde", + "serde_json", "serde_with_macros", + "time 0.3.15", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -4345,7 +4345,7 @@ dependencies = [ "serde", "serde_json", "slog", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -4358,7 +4358,7 @@ dependencies = [ "slog", "term", "thread_local", - "time 0.3.20", + "time 0.3.15", ] [[package]] @@ -4608,9 +4608,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -4638,11 +4638,11 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.5" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fe581ad25d11420b873cf9aedaca0419c2b411487b134d4d21065f3d092055" +checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" dependencies = [ - "cfg-expr 0.15.1", + "cfg-expr 0.15.2", "heck 0.4.1", "pkg-config", "toml 0.7.3", @@ -4695,7 +4695,7 @@ dependencies = [ "scopeguard", "serde", "unicode-segmentation", - "uuid 1.3.1", + "uuid", "windows 0.39.0", "windows-implement", "x11-dl", @@ -4720,9 +4720,9 @@ checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tauri" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449" +checksum = "e3a1fe72365a6d860fddf3403934649a5157b2bbb6f0b50dd3a8858cd1a22412" dependencies = [ "anyhow", "attohttpc", @@ -4765,7 +4765,7 @@ dependencies = [ "thiserror", "tokio", "url", - "uuid 1.3.1", + "uuid", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -4773,27 +4773,29 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" +checksum = "929b3bd1248afc07b63e33a6a53c3f82c32d0b0a5e216e4530e94c467e019389" dependencies = [ "anyhow", "cargo_toml", "heck 0.4.1", "json-patch", "semver", + "serde", "serde_json", "tauri-utils", - "winres", + "tauri-winres", + "winnow", ] [[package]] name = "tauri-codegen" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" +checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "brotli", "ico", "json-patch", @@ -4808,16 +4810,16 @@ dependencies = [ "sha2", "tauri-utils", "thiserror", - "time 0.3.20", - "uuid 1.3.1", + "time 0.3.15", + "uuid", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" +checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -4829,9 +4831,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +checksum = "dc36898ad4acb6c381878acf903c320a36cf29b68b74f6e791d6045b6557128c" dependencies = [ "gtk", "http", @@ -4842,16 +4844,17 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.3.1", + "url", + "uuid", "webview2-com", "windows 0.39.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +checksum = "e2ebc22bc5566ba33310744fadd86709fa591ed163491b165855474523ac1aab" dependencies = [ "cocoa", "gtk", @@ -4860,7 +4863,8 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.3.1", + "url", + "uuid", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -4869,9 +4873,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864" dependencies = [ "brotli", "ctor", @@ -4895,6 +4899,16 @@ dependencies = [ "windows 0.39.0", ] +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.3", +] + [[package]] name = "tauri-winrt-notification" version = "0.1.0" @@ -4908,15 +4922,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4995,7 +5010,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -5021,32 +5036,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa 1.0.6", "libc", "num_threads", "serde", - "time-core", "time-macros", ] -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", -] +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tiny-skia" @@ -5090,9 +5095,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -5115,7 +5120,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -5130,13 +5135,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] @@ -5176,9 +5180,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" dependencies = [ "serde", ] @@ -5244,10 +5248,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.38" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -5262,14 +5267,14 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -5306,9 +5311,9 @@ dependencies = [ [[package]] name = "treediff" -version = "3.0.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" dependencies = [ "serde_json", ] @@ -5369,9 +5374,9 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -5414,9 +5419,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -5432,17 +5437,11 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "0.8.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "uuid" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -5502,6 +5501,26 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "vulkan-executor" version = "0.1.0" @@ -5604,15 +5623,15 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -5638,7 +5657,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5651,9 +5670,9 @@ checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wasm-bindgen-test" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" dependencies = [ "console_error_panic_hook", "js-sys", @@ -5665,9 +5684,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" dependencies = [ "proc-macro2", "quote", @@ -5761,9 +5780,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -5813,7 +5832,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.0.5", + "system-deps 6.1.0", ] [[package]] @@ -5946,9 +5965,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41af2ea7d87bd41ad0a37146252d5f7c26490209f47f544b2ee3b3ff34c7732e" +checksum = "74851c2c8e5d97652e74c241d41b0656b31c924a45dcdecde83975717362cfa4" dependencies = [ "android_system_properties", "arrayvec", @@ -5999,9 +6018,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b689b6c49d6549434bf944e6b0f39238cf63693cb7a147e9d887507fffa3b223" +checksum = "40018623e2dba2602a9790faba8d33f2ebdebf4b86561b83928db735f8784728" dependencies = [ "bytemuck", "safe_arch", @@ -6382,12 +6401,13 @@ dependencies = [ ] [[package]] -name = "winres" -version = "0.1.12" +name = "winreg" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" dependencies = [ - "toml 0.5.11", + "cfg-if", + "winapi", ] [[package]] @@ -6469,9 +6489,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.4" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xxhash-rust" diff --git a/about.toml b/about.toml index 597d7532..f72a8926 100644 --- a/about.toml +++ b/about.toml @@ -21,7 +21,12 @@ workarounds = ["ring"] # is the ISC license but test code within the repo is BSD-3-Clause, but is not compiled into the crate when we use it [webpki.clarify] license = "ISC" - [[webpki.clarify.files]] -path = 'LICENSE' +path = "LICENSE" +checksum = "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a" + +[rustls-webpki.clarify] +license = "ISC" +[[rustls-webpki.clarify.files]] +path = "LICENSE" checksum = "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a" diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 8a14936b..a3720552 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -983,23 +983,5 @@ pub fn pick_layer_safe_imaginate_resolution(layer: &Layer, render_data: &RenderD let layer_bounds = layer.bounding_transform(render_data); let layer_bounds_size = (layer_bounds.transform_vector2((1., 0.).into()).length(), layer_bounds.transform_vector2((0., 1.).into()).length()); - pick_safe_imaginate_resolution(layer_bounds_size) -} - -pub fn pick_safe_imaginate_resolution((width, height): (f64, f64)) -> (u64, u64) { - const MAX_RESOLUTION: u64 = 1000 * 1000; - - let mut scale_factor = 1.; - - let round_to_increment = |size: f64| (size / 64.).round() as u64 * 64; - - loop { - let possible_solution = (round_to_increment(width * scale_factor), round_to_increment(height * scale_factor)); - - if possible_solution.0 * possible_solution.1 <= MAX_RESOLUTION { - return possible_solution; - } - - scale_factor -= 0.1; - } + graphene_std::imaginate::pick_safe_imaginate_resolution(layer_bounds_size) } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 99ea74d9..b74874d5 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -11,11 +11,13 @@ repository = "https://github.com/GraphiteEditor/Graphite" license = "Apache-2.0" [features] +default = ["wasm"] gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "graphene-core/gpu", "wgpu-executor", "gpu-executor"] quantization = [ "graphene-std/quantization", "interpreted-executor/quantization", ] +wasm = ["wasm-bindgen", "future-executor", "graphene-std/wasm"] [dependencies] log = "0.4" @@ -45,9 +47,12 @@ gpu-executor = { path = "../node-graph/gpu-executor", optional = true } interpreted-executor = { path = "../node-graph/interpreted-executor" } dyn-any = { path = "../libraries/dyn-any" } graphene-core = { path = "../node-graph/gcore" } -graphene-std = { path = "../node-graph/gstd", features = ["wasm"] } +graphene-std = { path = "../node-graph/gstd" } +future-executor = { path = "../node-graph/future-executor", optional = true } num_enum = "0.6.1" +wasm-bindgen = { version = "0.2.86", optional = true } + [dependencies.document-legacy] path = "../document-legacy" package = "graphite-document-legacy" diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 1ef3cf1f..9e542848 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -9,7 +9,6 @@ use crate::messages::tool::utility_types::HintData; use document_legacy::LayerId; use graph_craft::document::NodeId; -use graph_craft::imaginate_input::*; use graphene_core::raster::color::Color; use graphene_core::text::Font; @@ -75,40 +74,6 @@ pub enum FrontendMessage { #[serde(rename = "isDefault")] is_default: bool, }, - TriggerImaginateCheckServerStatus { - hostname: String, - }, - TriggerImaginateGenerate { - parameters: Box, - #[serde(rename = "baseImage")] - base_image: Option>, - #[serde(rename = "maskImage")] - mask_image: Option>, - #[serde(rename = "maskPaintMode")] - mask_paint_mode: ImaginateMaskPaintMode, - #[serde(rename = "maskBlurPx")] - mask_blur_px: u32, - #[serde(rename = "maskFillContent")] - imaginate_mask_starting_fill: ImaginateMaskStartingFill, - hostname: String, - #[serde(rename = "refreshFrequency")] - refresh_frequency: f64, - #[serde(rename = "documentId")] - document_id: u64, - #[serde(rename = "layerPath")] - layer_path: Vec, - #[serde(rename = "nodePath")] - node_path: Vec, - }, - TriggerImaginateTerminate { - #[serde(rename = "documentId")] - document_id: u64, - #[serde(rename = "layerPath")] - layer_path: Vec, - #[serde(rename = "nodePath")] - node_path: Vec, - hostname: String, - }, TriggerImport, TriggerIndexedDbRemoveDocument { #[serde(rename = "documentId")] diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index e1161bc4..35ac0051 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -93,8 +93,6 @@ pub enum DocumentMessage { GroupSelectedLayers, ImaginateClear { layer_path: Vec, - node_id: NodeId, - cached_index: usize, }, ImaginateGenerate { layer_path: Vec, @@ -105,10 +103,6 @@ pub enum DocumentMessage { imaginate_node: Vec, then_generate: bool, }, - ImaginateTerminate { - layer_path: Vec, - node_path: Vec, - }, InputFrameRasterizeRegionBelowLayer { layer_path: Vec, }, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index a1498579..82dff903 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -465,15 +465,7 @@ impl MessageHandler { - let value = graph_craft::document::value::TaggedValue::RcImage(None); - responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value }); - responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }); - } + ImaginateClear { layer_path } => responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }), ImaginateGenerate { layer_path, imaginate_node } => { if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data, Some(imaginate_node)) { responses.add(message); @@ -484,12 +476,16 @@ impl MessageHandler { + // Generate a random seed. We only want values between -2^53 and 2^53, because integer values + // outside of this range can get rounded in f64 + let random_bits = generate_uuid(); + let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63))); // Set a random seed input responses.add(NodeGraphMessage::SetInputValue { node_id: *imaginate_node.last().unwrap(), // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeType` in `document_node_type.rs` - input_index: 1, - value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64), + input_index: 3, + value: graph_craft::document::value::TaggedValue::F64(random_value), }); // Generate the image @@ -497,14 +493,6 @@ impl MessageHandler { - responses.add(FrontendMessage::TriggerImaginateTerminate { - document_id, - layer_path, - node_path, - hostname: preferences.imaginate_server_hostname.clone(), - }); - } InputFrameRasterizeRegionBelowLayer { layer_path } => { if layer_path.is_empty() { responses.add(NodeGraphMessage::RunDocumentGraph); @@ -996,7 +984,7 @@ impl DocumentMessageHandler { // Check if we use the "Input Frame" node. // TODO: Remove once rasterization is moved into a node. let input_frame_node_id = node_network.nodes.iter().find(|(_, node)| node.name == "Input Frame").map(|(&id, _)| id); - let input_frame_connected_to_graph_output = input_frame_node_id.map_or(false, |target_node_id| node_network.connected_to_output(target_node_id, imaginate_node_path.is_none())); + let input_frame_connected_to_graph_output = input_frame_node_id.map_or(false, |target_node_id| node_network.connected_to_output(target_node_id)); // If the Input Frame node is connected upstream, rasterize the artwork below this layer by calling into JS let response = if input_frame_connected_to_graph_output { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index a0bbb565..6000d5ac 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -459,7 +459,7 @@ impl MessageHandler let input = NodeInput::node(output_node, output_node_connector_index); responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); - let should_rerender = network.connected_to_output(node_id, true); + let should_rerender = network.connected_to_output(node_id); responses.add(NodeGraphMessage::SendGraph { should_rerender }); } NodeGraphMessage::Copy => { @@ -517,7 +517,7 @@ impl MessageHandler if let Some(network) = self.get_active_network(document) { // Only generate node graph if one of the selected nodes is connected to the output - if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) { + if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } @@ -549,7 +549,7 @@ impl MessageHandler } responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); - let should_rerender = network.connected_to_output(node_id, true); + let should_rerender = network.connected_to_output(node_id); responses.add(NodeGraphMessage::SendGraph { should_rerender }); } NodeGraphMessage::DoubleClickNode { node } => { @@ -626,7 +626,7 @@ impl MessageHandler } responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); - let should_rerender = network.connected_to_output(node_id, true); + let should_rerender = network.connected_to_output(node_id); responses.add(NodeGraphMessage::SendGraph { should_rerender }); responses.add(PropertiesPanelMessage::ResendActiveProperties); } @@ -743,7 +743,7 @@ impl MessageHandler let input = NodeInput::Value { tagged_value: value, exposed: false }; responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); responses.add(PropertiesPanelMessage::ResendActiveProperties); - if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id, true) { + if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } else { @@ -780,7 +780,7 @@ impl MessageHandler node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network(generic!(T)))); } node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false }; - if network.connected_to_output(*node_id, true) { + if network.connected_to_output(*node_id) { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } } @@ -854,7 +854,7 @@ impl MessageHandler Self::send_graph(network, executor, &self.layer_path, responses); // Only generate node graph if one of the selected nodes is connected to the output - if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) { + if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 25a84e05..91923e82 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -1673,12 +1673,63 @@ fn static_nodes() -> Vec { pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeType { name: "Imaginate", category: "Image Synthesis", - identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>"), + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + outputs: vec![NodeOutput::new(1, 0)], + nodes: [ + ( + 0, + DocumentNode { + name: "Frame Monitor".into(), + inputs: vec![NodeInput::Network(concrete!(ImageFrame))], + implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"), + ..Default::default() + }, + ), + ( + 1, + DocumentNode { + name: "Imaginate".into(), + inputs: vec![ + NodeInput::node(0, 0), + NodeInput::Network(concrete!(WasmEditorApi)), + NodeInput::Network(concrete!(ImaginateController)), + NodeInput::Network(concrete!(f64)), + NodeInput::Network(concrete!(Option)), + NodeInput::Network(concrete!(u32)), + NodeInput::Network(concrete!(ImaginateSamplingMethod)), + NodeInput::Network(concrete!(f64)), + NodeInput::Network(concrete!(String)), + NodeInput::Network(concrete!(String)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(f64)), + NodeInput::Network(concrete!(Option>)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(f64)), + NodeInput::Network(concrete!(ImaginateMaskStartingFill)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(ImaginateCache)), + ], + implementation: DocumentNodeImplementation::proto("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"), + ..Default::default() + }, + ), + ] + .into(), + ..Default::default() + }), inputs: vec![ DocumentInputType::value("Input Image", TaggedValue::ImageFrame(ImageFrame::empty()), true), + DocumentInputType { + name: "Editor Api", + data_type: FrontendGraphDataType::General, + default: NodeInput::Network(concrete!(WasmEditorApi)), + }, + DocumentInputType::value("Controller", TaggedValue::ImaginateController(Default::default()), false), DocumentInputType::value("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index DocumentInputType::value("Resolution", TaggedValue::OptionalDVec2(None), false), - DocumentInputType::value("Samples", TaggedValue::F64(30.), false), + DocumentInputType::value("Samples", TaggedValue::U32(30), false), DocumentInputType::value("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false), DocumentInputType::value("Prompt Guidance", TaggedValue::F64(7.5), false), DocumentInputType::value("Prompt", TaggedValue::String(String::new()), false), @@ -1691,10 +1742,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeTyp DocumentInputType::value("Mask Starting Fill", TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false), DocumentInputType::value("Improve Faces", TaggedValue::Bool(false), false), DocumentInputType::value("Tiling", TaggedValue::Bool(false), false), - // Non-user status (is document input the right way to do this?) - DocumentInputType::value("Cached Data", TaggedValue::RcImage(None), false), - DocumentInputType::value("Percent Complete", TaggedValue::F64(0.), false), - DocumentInputType::value("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false), + DocumentInputType::value("Cache", TaggedValue::ImaginateCache(Default::default()), false), ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], properties: node_properties::imaginate_properties, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 138e64ff..20ca218f 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -5,9 +5,11 @@ use super::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; +use document_legacy::{layers::layer_info::LayerDataTypeDiscriminant, Operation}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; +use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus}; use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice}; use graphene_core::text::Font; use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin}; @@ -980,12 +982,17 @@ pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context result } -pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - /* +pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { let imaginate_node = [context.nested_path, &[node_id]].concat(); let layer_path = context.layer_path.to_vec(); - let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found")); + let resolve_input = |name: &str| { + super::IMAGINATE_NODE + .inputs + .iter() + .position(|input| input.name == name) + .unwrap_or_else(|| panic!("Input {name} not found")) + }; let seed_index = resolve_input("Seed"); let resolution_index = resolve_input("Resolution"); let samples_index = resolve_input("Samples"); @@ -1001,22 +1008,12 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co let mask_fill_index = resolve_input("Mask Starting Fill"); let faces_index = resolve_input("Improve Faces"); let tiling_index = resolve_input("Tiling"); - let cached_index = resolve_input("Cached Data"); - let cached_value = &document_node.inputs[cached_index]; - let complete_value = &document_node.inputs[resolve_input("Percent Complete")]; - let status_value = &document_node.inputs[resolve_input("Status")]; + let controller = &document_node.inputs[resolve_input("Controller")]; let server_status = { - let status = match &context.persistent_data.imaginate_server_status { - ImaginateServerStatus::Unknown => { - context.responses.add(PortfolioMessage::ImaginateCheckServerStatus); - "Checking..." - } - ImaginateServerStatus::Checking => "Checking...", - ImaginateServerStatus::Unavailable => "Unavailable", - ImaginateServerStatus::Connected => "Connected", - }; + let server_status = context.persistent_data.imaginate.server_status(); + let status_text = server_status.to_text(); let mut widgets = vec![ WidgetHolder::text_widget("Server"), WidgetHolder::unrelated_separator(), @@ -1025,14 +1022,14 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .on_update(|_| DialogMessage::RequestPreferencesDialog.into()) .widget_holder(), WidgetHolder::unrelated_separator(), - WidgetHolder::bold_text(status), + WidgetHolder::bold_text(status_text), WidgetHolder::related_separator(), IconButton::new("Reload", 24) .tooltip("Refresh connection status") .on_update(|_| PortfolioMessage::ImaginateCheckServerStatus.into()) .widget_holder(), ]; - if context.persistent_data.imaginate_server_status == ImaginateServerStatus::Unavailable { + if let ImaginateServerStatus::Unavailable | ImaginateServerStatus::Failed(_) = server_status { widgets.extend([ WidgetHolder::unrelated_separator(), TextButton::new("Server Help") @@ -1049,15 +1046,11 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co LayoutGroup::Row { widgets }.with_tooltip("Connection status to the server that computes generated images") }; - let &NodeInput::Value {tagged_value: TaggedValue::ImaginateStatus( imaginate_status),..} = status_value else { - panic!("Invalid status input") - }; - let NodeInput::Value {tagged_value: TaggedValue::RcImage( cached_data),..} = cached_value else { - panic!("Invalid cached image input, received {:?}, index: {}", cached_value, cached_index) - }; - let &NodeInput::Value {tagged_value: TaggedValue::F64( percent_complete),..} = complete_value else { - panic!("Invalid percent complete input") + let &NodeInput::Value {tagged_value: TaggedValue::ImaginateController(ref controller),..} = controller else { + panic!("Invalid output status input") }; + let imaginate_status = controller.get_status(); + let use_base_image = if let &NodeInput::Value { tagged_value: TaggedValue::Bool(use_base_image), .. @@ -1071,23 +1064,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co let transform_not_connected = false; let progress = { - // Since we don't serialize the status, we need to derive from other state whether the Idle state is actually supposed to be the Terminated state - let mut interpreted_status = imaginate_status; - if imaginate_status == ImaginateStatus::Idle && cached_data.is_some() && percent_complete > 0. && percent_complete < 100. { - interpreted_status = ImaginateStatus::Terminated; - } - - let status = match interpreted_status { - ImaginateStatus::Idle => match cached_data { - Some(_) => "Done".into(), - None => "Ready".into(), - }, - ImaginateStatus::Beginning => "Beginning...".into(), - ImaginateStatus::Uploading(percent) => format!("Uploading Input Image: {percent:.0}%"), - ImaginateStatus::Generating => format!("Generating: {percent_complete:.0}%"), - ImaginateStatus::Terminating => "Terminating...".into(), - ImaginateStatus::Terminated => format!("{percent_complete:.0}% (Terminated)"), - }; + let status = imaginate_status.to_text(); let widgets = vec![ WidgetHolder::text_widget("Progress"), WidgetHolder::unrelated_separator(), @@ -1095,38 +1072,38 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area. WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. WidgetHolder::unrelated_separator(), - WidgetHolder::bold_text(status), + WidgetHolder::bold_text(status.as_ref()), ]; - LayoutGroup::Row { widgets }.with_tooltip("When generating, the percentage represents how many sampling steps have so far been processed out of the target number") + LayoutGroup::Row { widgets }.with_tooltip(match imaginate_status { + ImaginateStatus::Failed(_) => status.as_ref(), + _ => "When generating, the percentage represents how many sampling steps have so far been processed out of the target number", + }) }; - let image_controls = { + let image_controls: _ = { let mut widgets = vec![WidgetHolder::text_widget("Image"), WidgetHolder::unrelated_separator()]; - let assist_separators = vec![ + let assist_separators = [ WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px, WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area. WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. WidgetHolder::unrelated_separator(), ]; - match imaginate_status { - ImaginateStatus::Beginning | ImaginateStatus::Uploading(_) => { + match &imaginate_status { + ImaginateStatus::Beginning | ImaginateStatus::Uploading => { widgets.extend_from_slice(&assist_separators); widgets.push(TextButton::new("Beginning...").tooltip("Sending image generation request to the server").disabled(true).widget_holder()); } - ImaginateStatus::Generating => { + ImaginateStatus::Generating(_) => { widgets.extend_from_slice(&assist_separators); widgets.push( TextButton::new("Terminate") .tooltip("Cancel the in-progress image generation and keep the latest progress") .on_update({ - let imaginate_node = imaginate_node.clone(); + let controller = controller.clone(); move |_| { - DocumentMessage::ImaginateTerminate { - layer_path: layer_path.clone(), - node_path: imaginate_node.clone(), - } - .into() + controller.request_termination(); + Message::NoOp } }) .widget_holder(), @@ -1141,13 +1118,15 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .widget_holder(), ); } - ImaginateStatus::Idle | ImaginateStatus::Terminated => widgets.extend_from_slice(&[ + ImaginateStatus::Ready | ImaginateStatus::ReadyDone | ImaginateStatus::Terminated | ImaginateStatus::Failed(_) => widgets.extend_from_slice(&[ IconButton::new("Random", 24) .tooltip("Generate with a new random seed") .on_update({ let imaginate_node = imaginate_node.clone(); let layer_path = context.layer_path.to_vec(); + let controller = controller.clone(); move |_| { + controller.trigger_regenerate(); DocumentMessage::ImaginateRandom { layer_path: layer_path.clone(), imaginate_node: imaginate_node.clone(), @@ -1163,7 +1142,9 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .on_update({ let imaginate_node = imaginate_node.clone(); let layer_path = context.layer_path.to_vec(); + let controller = controller.clone(); move |_| { + controller.trigger_regenerate(); DocumentMessage::ImaginateGenerate { layer_path: layer_path.clone(), imaginate_node: imaginate_node.clone(), @@ -1175,16 +1156,13 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co WidgetHolder::related_separator(), TextButton::new("Clear") .tooltip("Remove generated image from the layer frame") - .disabled(cached_data.is_none()) + .disabled(!matches!(imaginate_status, ImaginateStatus::ReadyDone)) .on_update({ let layer_path = context.layer_path.to_vec(); + let controller = controller.clone(); move |_| { - DocumentMessage::ImaginateClear { - node_id, - layer_path: layer_path.clone(), - cached_index, - } - .into() + controller.set_status(ImaginateStatus::Ready); + DocumentMessage::ImaginateClear { layer_path: layer_path.clone() }.into() } }) .widget_holder(), @@ -1221,9 +1199,11 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .widget_holder(), WidgetHolder::unrelated_separator(), NumberInput::new(Some(seed)) - .min(0.) .int() + .min(-((1u64 << f64::MANTISSA_DIGITS) as f64)) + .max((1u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap()), node_id, seed_index)) + .mode(NumberInputMode::Increment) .widget_holder(), ]) } @@ -1231,23 +1211,19 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations") }; - // Create the input to the graph using an empty image - let editor_api = std::borrow::Cow::Owned(EditorApi { - image_frame: None, - font_cache: Some(&context.persistent_data.font_cache), - }); - // Compute the transform input to the image frame - let image_frame: ImageFrame = context.executor.compute_input(context.network, &imaginate_node, 0, editor_api).unwrap_or_default(); - let transform = image_frame.transform; + let transform = context + .executor + .introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &ImageFrame| frame.transform) + .unwrap_or_default(); let resolution = { - use document_legacy::document::pick_safe_imaginate_resolution; + use graphene_std::imaginate::pick_safe_imaginate_resolution; let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Vector, false); let round = |x: DVec2| { let (x, y) = pick_safe_imaginate_resolution(x.into()); - Some(DVec2::new(x as f64, y as f64)) + DVec2::new(x as f64, y as f64) }; if let &NodeInput::Value { @@ -1256,14 +1232,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co } = &document_node.inputs[resolution_index] { let dimensions_is_auto = vec2.is_none(); - let vec2 = vec2.unwrap_or_else(|| { - let w = transform.transform_vector2(DVec2::new(1., 0.)).length(); - let h = transform.transform_vector2(DVec2::new(0., 1.)).length(); - - let (x, y) = pick_safe_imaginate_resolution((w, h)); - - DVec2::new(x as f64, y as f64) - }); + let vec2 = vec2.unwrap_or_else(|| round([transform.matrix2.x_axis, transform.matrix2.y_axis].map(DVec2::length).into())); let layer_path = context.layer_path.to_vec(); widgets.extend_from_slice(&[ @@ -1308,7 +1277,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .unit(" px") .disabled(dimensions_is_auto && !transform_not_connected) .on_update(update_value( - move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(number_input.value.unwrap(), vec2.y))), + move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(number_input.value.unwrap(), vec2.y)))), node_id, resolution_index, )) @@ -1321,7 +1290,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co .unit(" px") .disabled(dimensions_is_auto && !transform_not_connected) .on_update(update_value( - move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(vec2.x, number_input.value.unwrap()))), + move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(vec2.x, number_input.value.unwrap())))), node_id, resolution_index, )) @@ -1538,8 +1507,6 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co layout.extend_from_slice(&[improve_faces, tiling]); layout - */ - todo!() } fn unknown_node_properties(document_node: &DocumentNode) -> Vec { diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 753bc231..ee6bd753 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,10 +1,8 @@ -use super::utility_types::ImaginateServerStatus; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; use document_legacy::LayerId; use graph_craft::document::NodeId; -use graph_craft::imaginate_input::ImaginateStatus; use graphene_core::text::Font; use serde::{Deserialize, Serialize}; @@ -57,24 +55,9 @@ pub enum PortfolioMessage { is_default: bool, }, ImaginateCheckServerStatus, - ImaginateSetGeneratingStatus { - document_id: u64, - layer_path: Vec, - node_path: Vec, - percent: Option, - status: ImaginateStatus, - }, - ImaginateSetImageData { - document_id: u64, - layer_path: Vec, - node_path: Vec, - image_data: Vec, - width: u32, - height: u32, - }, - ImaginateSetServerStatus { - status: ImaginateServerStatus, - }, + ImaginatePollServerStatus, + ImaginatePreferences, + ImaginateServerHostname, Import, LoadDocumentResources { document_id: u64, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 7325b24e..b90f2584 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -7,9 +7,7 @@ use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::layout::utility_types::misc::LayoutTarget; -use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; -use crate::messages::portfolio::utility_types::ImaginateServerStatus; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup}; use crate::node_graph_executor::NodeGraphExecutor; @@ -19,7 +17,6 @@ use document_legacy::layers::style::RenderData; use document_legacy::Operation as DocumentOperation; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; -use graphene_core::raster::Image; use graphene_core::text::Font; #[derive(Debug, Default)] @@ -218,70 +215,35 @@ impl MessageHandler { - self.persistent_data.imaginate_server_status = ImaginateServerStatus::Checking; - responses.add(FrontendMessage::TriggerImaginateCheckServerStatus { - hostname: preferences.imaginate_server_hostname.clone(), - }); - responses.add(PropertiesPanelMessage::ResendActiveProperties); - } - PortfolioMessage::ImaginateSetGeneratingStatus { - document_id, - layer_path, - node_path, - percent, - status, - } => { - let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found")); - if let Some(percentage) = percent { - responses.add(PortfolioMessage::DocumentPassMessage { - document_id, - message: NodeGraphMessage::SetQualifiedInputValue { - layer_path: layer_path.clone(), - node_path: node_path.clone(), - input_index: get("Percent Complete"), - value: TaggedValue::F64(percentage), + let server_status = self.persistent_data.imaginate.server_status().clone(); + self.persistent_data.imaginate.poll_server_check(); + #[cfg(target_arch = "wasm32")] + if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() { + future_executor::spawn(async move { + let () = fut.await; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "/../frontend/src/wasm-communication/editor.ts")] + extern "C" { + #[wasm_bindgen(js_name = injectImaginatePollServerStatus)] + fn inject(); } - .into(), - }); + inject(); + }) + } + if &server_status != self.persistent_data.imaginate.server_status() { + responses.add(PropertiesPanelMessage::ResendActiveProperties); } - - responses.add(PortfolioMessage::DocumentPassMessage { - document_id, - message: NodeGraphMessage::SetQualifiedInputValue { - layer_path, - node_path, - input_index: get("Status"), - value: TaggedValue::ImaginateStatus(status), - } - .into(), - }); } - PortfolioMessage::ImaginateSetImageData { - document_id, - layer_path, - node_path, - image_data, - width, - height, - } => { - let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found")); - - let image = Image::from_image_data(&image_data, width, height); - responses.add(PortfolioMessage::DocumentPassMessage { - document_id, - message: NodeGraphMessage::SetQualifiedInputValue { - layer_path, - node_path, - input_index: get("Cached Data"), - value: TaggedValue::RcImage(Some(std::sync::Arc::new(image))), - } - .into(), - }); - } - PortfolioMessage::ImaginateSetServerStatus { status } => { - self.persistent_data.imaginate_server_status = status; + PortfolioMessage::ImaginatePollServerStatus => { + self.persistent_data.imaginate.poll_server_check(); responses.add(PropertiesPanelMessage::ResendActiveProperties); } + PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()), + PortfolioMessage::ImaginateServerHostname => { + info!("setting imaginate persistent data"); + self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname); + } PortfolioMessage::Import => { // This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages if self.active_document().is_some() { @@ -461,7 +423,6 @@ impl MessageHandler Self { - Self { - font_cache: Default::default(), - imaginate_server_status: ImaginateServerStatus::Unknown, - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize, specta::Type)] -pub enum ImaginateServerStatus { - #[default] - Unknown, - Checking, - Unavailable, - Connected, + pub imaginate: ImaginatePersistentData, } #[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)] diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 97bbfc99..7bef513c 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -1,5 +1,6 @@ use crate::messages::input_mapper::key_mapping::MappingVariant; use crate::messages::prelude::*; +use graph_craft::imaginate_input::ImaginatePreferences; use serde::{Deserialize, Serialize}; @@ -10,10 +11,19 @@ pub struct PreferencesMessageHandler { pub zoom_with_scroll: bool, } +impl PreferencesMessageHandler { + pub fn get_imaginate_preferences(&self) -> ImaginatePreferences { + ImaginatePreferences { + host_name: self.imaginate_server_hostname.clone(), + } + } +} + impl Default for PreferencesMessageHandler { fn default() -> Self { + let ImaginatePreferences { host_name } = Default::default(); Self { - imaginate_server_hostname: "http://localhost:7860/".into(), + imaginate_server_hostname: host_name, imaginate_refresh_frequency: 1., zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll), } @@ -28,9 +38,9 @@ impl MessageHandler for PreferencesMessageHandler { if let Ok(deserialized_preferences) = serde_json::from_str::(&preferences) { *self = deserialized_preferences; - if self.imaginate_server_hostname != Self::default().imaginate_server_hostname { - responses.add(PortfolioMessage::ImaginateCheckServerStatus); - } + responses.add(PortfolioMessage::ImaginateServerHostname); + responses.add(PortfolioMessage::ImaginateCheckServerStatus); + responses.add(PortfolioMessage::ImaginatePreferences); } } PreferencesMessage::ResetToDefaults => { @@ -43,6 +53,7 @@ impl MessageHandler for PreferencesMessageHandler { PreferencesMessage::ImaginateRefreshFrequency { seconds } => { self.imaginate_refresh_frequency = seconds; responses.add(PortfolioMessage::ImaginateCheckServerStatus); + responses.add(PortfolioMessage::ImaginatePreferences); } PreferencesMessage::ImaginateServerHostname { hostname } => { let initial = hostname.clone(); @@ -55,7 +66,9 @@ impl MessageHandler for PreferencesMessageHandler { } self.imaginate_server_hostname = hostname; + responses.add(PortfolioMessage::ImaginateServerHostname); responses.add(PortfolioMessage::ImaginateCheckServerStatus); + responses.add(PortfolioMessage::ImaginatePreferences); } PreferencesMessage::ModifyLayout { zoom_with_scroll } => { self.zoom_with_scroll = zoom_with_scroll; diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index c0a0febc..87c5da59 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -10,16 +10,16 @@ use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; +use graph_craft::imaginate_input::ImaginatePreferences; use graph_craft::{concrete, Type, TypeDescriptor}; -use graphene_core::application_io::ApplicationIo; +use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender}; use graphene_core::raster::{Image, ImageFrame}; use graphene_core::renderer::{SvgSegment, SvgSegmentList}; use graphene_core::text::FontCache; use graphene_core::vector::style::ViewMode; use graphene_core::{Color, SurfaceFrame, SurfaceId}; -use graphene_std::wasm_application_io::WasmApplicationIo; -use graphene_std::wasm_application_io::WasmEditorApi; +use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::DynamicExecutor; use glam::{DAffine2, DVec2}; @@ -33,8 +33,9 @@ pub struct NodeRuntime { pub(crate) executor: DynamicExecutor, font_cache: FontCache, receiver: Receiver, - sender: Sender, + sender: InternalNodeGraphUpdateSender, wasm_io: Option, + imaginate_preferences: ImaginatePreferences, pub(crate) thumbnails: HashMap>, canvas_cache: HashMap, SurfaceId>, } @@ -42,6 +43,7 @@ pub struct NodeRuntime { enum NodeRuntimeMessage { GenerationRequest(GenerationRequest), FontCacheUpdate(FontCache), + ImaginatePreferencesUpdate(ImaginatePreferences), } pub(crate) struct GenerationRequest { @@ -50,6 +52,7 @@ pub(crate) struct GenerationRequest { path: Vec, image_frame: Option>, } + pub(crate) struct GenerationResponse { generation_id: u64, result: Result, @@ -57,18 +60,38 @@ pub(crate) struct GenerationResponse { new_thumbnails: HashMap>, } +enum NodeGraphUpdate { + GenerationResponse(GenerationResponse), + NodeGraphUpdateMessage(NodeGraphUpdateMessage), +} + +struct InternalNodeGraphUpdateSender(Sender); + +impl InternalNodeGraphUpdateSender { + fn send_generation_response(&self, response: GenerationResponse) { + self.0.send(NodeGraphUpdate::GenerationResponse(response)).expect("Failed to send response") + } +} + +impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender { + fn send(&self, message: NodeGraphUpdateMessage) { + self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response") + } +} + thread_local! { pub(crate) static NODE_RUNTIME: Rc>> = Rc::new(RefCell::new(None)); } impl NodeRuntime { - fn new(receiver: Receiver, sender: Sender) -> Self { + fn new(receiver: Receiver, sender: Sender) -> Self { let executor = DynamicExecutor::default(); Self { executor, receiver, - sender, + sender: InternalNodeGraphUpdateSender(sender), font_cache: FontCache::default(), + imaginate_preferences: Default::default(), thumbnails: Default::default(), wasm_io: None, canvas_cache: Default::default(), @@ -80,13 +103,14 @@ impl NodeRuntime { // This should be avoided in the future. requests.reverse(); requests.dedup_by_key(|x| match x { - NodeRuntimeMessage::FontCacheUpdate(_) => None, NodeRuntimeMessage::GenerationRequest(x) => Some(x.path.clone()), + _ => None, }); requests.reverse(); for request in requests { match request { NodeRuntimeMessage::FontCacheUpdate(font_cache) => self.font_cache = font_cache, + NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => self.imaginate_preferences = preferences, NodeRuntimeMessage::GenerationRequest(GenerationRequest { generation_id, graph, @@ -105,7 +129,7 @@ impl NodeRuntime { updates: responses, new_thumbnails: self.thumbnails.clone(), }; - self.sender.send(response).expect("Failed to send response"); + self.sender.send_generation_response(response); } } } @@ -134,6 +158,8 @@ impl NodeRuntime { font_cache: &self.font_cache, image_frame, application_io: &self.wasm_io.as_ref().unwrap(), + node_graph_message_sender: &self.sender, + imaginate_preferences: &self.imaginate_preferences, }; // We assume only one output @@ -240,7 +266,7 @@ pub async fn run_node_graph() { #[derive(Debug)] pub struct NodeGraphExecutor { sender: Sender, - receiver: Receiver, + receiver: Receiver, // TODO: This is a memory leak since layers are never removed pub(crate) last_output_type: HashMap, Option>, pub(crate) thumbnails: HashMap>, @@ -294,10 +320,31 @@ impl NodeGraphExecutor { self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); } + pub fn update_imaginate_preferences(&self, imaginate_preferences: ImaginatePreferences) { + self.sender + .send(NodeRuntimeMessage::ImaginatePreferencesUpdate(imaginate_preferences)) + .expect("Failed to send imaginate preferences"); + } + pub fn previous_output_type(&self, path: &[LayerId]) -> Option { self.last_output_type.get(path).cloned().flatten() } + pub fn introspect_node_in_network Option, F2: FnOnce(&T) -> U>( + &mut self, + network: &NodeNetwork, + node_path: &[NodeId], + find_node: F1, + extract_data: F2, + ) -> Option { + let wrapping_document_node = network.nodes.get(node_path.last()?)?; + let DocumentNodeImplementation::Network(wrapped_network) = &wrapping_document_node.implementation else { return None; }; + let introspection_node = find_node(&wrapped_network)?; + let introspection = self.introspect_node(&[node_path, &[introspection_node]].concat())?; + let downcasted: &T = ::downcast_ref(introspection.as_ref())?; + Some(extract_data(downcasted)) + } + /// Encodes an image into a format using the image crate fn encode_img(image: Image, resize: Option, format: image::ImageOutputFormat) -> Result<(Vec, (u32, u32)), String> { use image::{ImageBuffer, Rgba}; @@ -334,13 +381,12 @@ impl NodeGraphExecutor { }) } - /// Evaluates a node graph, computing either the Imaginate node or the entire graph + /// Evaluates a node graph, computing the entire graph pub fn submit_node_graph_evaluation( &mut self, (document_id, documents): (u64, &mut HashMap), layer_path: Vec, (input_image_data, (width, height)): (Vec, (u32, u32)), - _imaginate_node: Option>, _persistent_data: (&PreferencesMessageHandler, &PersistentData), _responses: &mut VecDeque, ) -> Result<(), String> { @@ -365,11 +411,6 @@ impl NodeGraphExecutor { let transform = DAffine2::IDENTITY; let image_frame = ImageFrame { image, transform }; - // Special execution path for generating Imaginate (as generation requires IO from outside node graph) - /*if let Some(imaginate_node) = imaginate_node { - responses.add(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, editor_api, persistent_data)?); - return Ok(()); - }*/ // Execute the node graph let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone()); @@ -381,26 +422,32 @@ impl NodeGraphExecutor { pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque) -> Result<(), String> { let results = self.receiver.try_iter().collect::>(); for response in results { - let GenerationResponse { - generation_id, - result, - updates, - new_thumbnails, - } = response; - self.thumbnails = new_thumbnails; - let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?; - let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; - responses.extend(updates); - self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?; - responses.add(DocumentMessage::LayerChanged { - affected_layer_path: execution_context.layer_path, - }); - responses.add(DocumentMessage::RenderDocument); - responses.add(ArtboardMessage::RenderArtboards); - responses.add(DocumentMessage::DocumentStructureChanged); - responses.add(BroadcastEvent::DocumentIsDirty); - responses.add(DocumentMessage::DirtyRenderDocument); - responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender)); + match response { + NodeGraphUpdate::GenerationResponse(GenerationResponse { + generation_id, + result, + updates, + new_thumbnails, + }) => { + self.thumbnails = new_thumbnails; + let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?; + let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; + responses.extend(updates); + self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?; + responses.add(DocumentMessage::LayerChanged { + affected_layer_path: execution_context.layer_path, + }); + responses.add(DocumentMessage::RenderDocument); + responses.add(ArtboardMessage::RenderArtboards); + responses.add(DocumentMessage::DocumentStructureChanged); + responses.add(BroadcastEvent::DocumentIsDirty); + responses.add(DocumentMessage::DirtyRenderDocument); + responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender)); + } + NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => { + responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties)) + } + } } Ok(()) } diff --git a/frontend/src/state-providers/portfolio.ts b/frontend/src/state-providers/portfolio.ts index 56b88fe4..bf947044 100644 --- a/frontend/src/state-providers/portfolio.ts +++ b/frontend/src/state-providers/portfolio.ts @@ -3,7 +3,6 @@ import {writable} from "svelte/store"; import { downloadFileText, downloadFileBlob, upload, downloadFileURL } from "@graphite/utility-functions/files"; -import { imaginateGenerate, imaginateCheckConnection, imaginateTerminate, updateBackendImage } from "@graphite/utility-functions/imaginate"; import { extractPixelData, imageToPNG, rasterizeSVG, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization"; import { type Editor } from "@graphite/wasm-communication/editor"; import { @@ -13,8 +12,6 @@ import { TriggerDownloadRaster, TriggerDownloadTextFile, TriggerImaginateCheckServerStatus, - TriggerImaginateGenerate, - TriggerImaginateTerminate, TriggerImport, TriggerOpenDocument, TriggerRasterizeRegionBelowLayer, @@ -85,37 +82,6 @@ export function createPortfolioState(editor: Editor) { // Have the browser download the file to the user's disk downloadFileBlob(name, blob); }); - editor.subscriptions.subscribeJsMessage(TriggerImaginateCheckServerStatus, async (triggerImaginateCheckServerStatus) => { - const { hostname } = triggerImaginateCheckServerStatus; - - imaginateCheckConnection(hostname, editor); - }); - editor.subscriptions.subscribeJsMessage(TriggerImaginateGenerate, async (triggerImaginateGenerate) => { - const { documentId, layerPath, nodePath, hostname, refreshFrequency, baseImage, maskImage, maskPaintMode, maskBlurPx, maskFillContent, parameters } = triggerImaginateGenerate; - - // Handle img2img mode - let image: Blob | undefined; - if (parameters.denoisingStrength !== undefined && baseImage !== undefined) { - const buffer = new Uint8Array(baseImage.imageData.values()).buffer; - - image = new Blob([buffer], { type: baseImage.mime }); - updateBackendImage(editor, image, documentId, layerPath, nodePath); - } - - // Handle layer mask - let mask: Blob | undefined; - if (maskImage !== undefined) { - // Rasterize the SVG to an image file - mask = await rasterizeSVG(maskImage.svg, maskImage.size[0], maskImage.size[1], "image/png"); - } - - imaginateGenerate(parameters, image, mask, maskPaintMode, maskBlurPx, maskFillContent, hostname, refreshFrequency, documentId, layerPath, nodePath, editor); - }); - editor.subscriptions.subscribeJsMessage(TriggerImaginateTerminate, async (triggerImaginateTerminate) => { - const { documentId, layerPath, nodePath, hostname } = triggerImaginateTerminate; - - imaginateTerminate(hostname, documentId, layerPath, nodePath, editor); - }); editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => { updateImageData.imageData.forEach(async (element) => { const buffer = new Uint8Array(element.imageData.values()).buffer; diff --git a/frontend/src/utility-functions/imaginate.ts b/frontend/src/utility-functions/imaginate.ts deleted file mode 100644 index fa6ca393..00000000 --- a/frontend/src/utility-functions/imaginate.ts +++ /dev/null @@ -1,367 +0,0 @@ -/* eslint-disable camelcase */ - -// import { escapeJSON } from "@graphite/utility-functions/escape"; -import { blobToBase64 } from "@graphite/utility-functions/files"; -import { type RequestResult, requestWithUploadDownloadProgress } from "@graphite/utility-functions/network"; -import { type Editor } from "@graphite/wasm-communication/editor"; -import type { XY } from "@graphite/wasm-communication/messages"; -import { type ImaginateGenerationParameters } from "@graphite/wasm-communication/messages"; - -const MAX_POLLING_RETRIES = 4; -const SERVER_STATUS_CHECK_TIMEOUT = 5000; -const PROGRESS_EVERY_N_STEPS = 5; - -let timer: NodeJS.Timeout | undefined; -let terminated = false; - -let generatingAbortRequest: XMLHttpRequest | undefined; -let pollingAbortController = new AbortController(); -let statusAbortController = new AbortController(); - -// PUBLICLY CALLABLE FUNCTIONS - -export async function imaginateGenerate( - parameters: ImaginateGenerationParameters, - image: Blob | undefined, - mask: Blob | undefined, - maskPaintMode: string, - maskBlurPx: number, - maskFillContent: string, - hostname: string, - refreshFrequency: number, - documentId: bigint, - layerPath: BigUint64Array, - nodePath: BigUint64Array, - editor: Editor -): Promise { - // Ignore a request to generate a new image while another is already being generated - if (generatingAbortRequest !== undefined) return; - - terminated = false; - - // Immediately set the progress to 0% so the backend knows to update its layout - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, 0, "Beginning"); - - // Initiate a request to the computation server - const discloseUploadingProgress = (progress: number): void => { - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, progress * 100, "Uploading"); - }; - const { uploaded, result, xhr } = await generate(discloseUploadingProgress, hostname, image, mask, maskPaintMode, maskBlurPx, maskFillContent, parameters); - generatingAbortRequest = xhr; - - try { - // Wait until the request is fully uploaded, which could be slow if the img2img source is large and the user is on a slow connection - await uploaded; - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, 0, "Generating"); - - // Begin polling for updates to the in-progress image generation at the specified interval - // Don't poll if the chosen interval is 0, or if the chosen sampling method does not support polling - if (refreshFrequency > 0) { - const interval = Math.max(refreshFrequency * 1000, 500); - scheduleNextPollingUpdate(interval, Date.now(), 0, editor, hostname, documentId, layerPath, nodePath, parameters.resolution); - } - - // Wait for the final image to be returned by the initial request containing either the full image or the last frame if it was terminated by the user - const { body, status } = await result; - if (status < 200 || status > 299) { - throw new Error(`Request to server failed to return a 200-level status code (${status})`); - } - - // Extract the final image from the response and convert it to a data blob - const base64Data = JSON.parse(body)?.images?.[0] as string | undefined; - const base64 = typeof base64Data === "string" && base64Data.length > 0 ? `data:image/png;base64,${base64Data}` : undefined; - if (!base64) throw new Error("Could not read final image result from server response"); - const blob = await (await fetch(base64)).blob(); - - // Send the backend an updated status - const percent = terminated ? undefined : 100; - const newStatus = terminated ? "Terminated" : "Idle"; - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, percent, newStatus); - - // Send the backend a blob URL for the final image - updateBackendImage(editor, blob, documentId, layerPath, nodePath); - } catch { - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, undefined, "Terminated"); - - await imaginateCheckConnection(hostname, editor); - } - - abortAndResetGenerating(); - abortAndResetPolling(); -} - -export async function imaginateTerminate(hostname: string, documentId: bigint, layerPath: BigUint64Array, nodePath: BigUint64Array, editor: Editor): Promise { - terminated = true; - abortAndResetPolling(); - - try { - await terminate(hostname); - - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, undefined, "Terminating"); - } catch { - abortAndResetGenerating(); - abortAndResetPolling(); - - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, undefined, "Terminated"); - - await imaginateCheckConnection(hostname, editor); - } -} - -export async function imaginateCheckConnection(hostname: string, editor: Editor): Promise { - const serverReached = await checkConnection(hostname); - editor.instance.setImaginateServerStatus(serverReached); -} - -// Converts the blob image into a list of pixels using an invisible canvas. -export async function updateBackendImage(editor: Editor, blob: Blob, documentId: bigint, layerPath: BigUint64Array, nodePath: BigUint64Array): Promise { - const image = await createImageBitmap(blob); - const canvas = document.createElement("canvas"); - canvas.width = image.width; - canvas.height = image.height; - const ctx = canvas.getContext("2d"); - if (!ctx) throw new Error("Could not create canvas context"); - ctx.drawImage(image, 0, 0); - - // Send the backend the blob data to be stored persistently in the layer - const imageData = ctx.getImageData(0, 0, image.width, image.height); - const u8Array = new Uint8Array(imageData.data); - - editor.instance.setImaginateImageData(documentId, layerPath, nodePath, u8Array, imageData.width, imageData.height); -} - -// ABORTING AND RESETTING HELPERS - -function abortAndResetGenerating(): void { - generatingAbortRequest?.abort(); - generatingAbortRequest = undefined; -} - -function abortAndResetPolling(): void { - pollingAbortController.abort(); - pollingAbortController = new AbortController(); - clearTimeout(timer); -} - -// POLLING IMPLEMENTATION DETAILS - -function scheduleNextPollingUpdate( - interval: number, - timeoutBegan: number, - pollingRetries: number, - editor: Editor, - hostname: string, - documentId: bigint, - layerPath: BigUint64Array, - nodePath: BigUint64Array, - resolution: XY -): void { - // Pick a future time that keeps to the user-requested interval if possible, but on slower connections will go as fast as possible without overlapping itself - const nextPollTimeGoal = timeoutBegan + interval; - const timeFromNow = Math.max(0, nextPollTimeGoal - Date.now()); - - timer = setTimeout(async () => { - const nextTimeoutBegan = Date.now(); - - try { - const [blob, percentComplete] = await pollImage(hostname); - - // After waiting for the polling result back from the server, if during that intervening time the user has terminated the generation, exit so we don't overwrite that terminated status - if (terminated) return; - - if (blob) updateBackendImage(editor, blob, documentId, layerPath, nodePath); - editor.instance.setImaginateGeneratingStatus(documentId, layerPath, nodePath, percentComplete, "Generating"); - - scheduleNextPollingUpdate(interval, nextTimeoutBegan, 0, editor, hostname, documentId, layerPath, nodePath, resolution); - } catch { - if (generatingAbortRequest === undefined) return; - - if (pollingRetries + 1 > MAX_POLLING_RETRIES) { - abortAndResetGenerating(); - abortAndResetPolling(); - - await imaginateCheckConnection(hostname, editor); - } else { - scheduleNextPollingUpdate(interval, nextTimeoutBegan, pollingRetries + 1, editor, hostname, documentId, layerPath, nodePath, resolution); - } - } - }, timeFromNow); -} - -// API COMMUNICATION FUNCTIONS - -async function pollImage(hostname: string): Promise<[Blob | undefined, number]> { - // Fetch the percent progress and in-progress image from the API - const result = await fetch(`${hostname}sdapi/v1/progress`, { signal: pollingAbortController.signal, method: "GET" }); - const { current_image, progress } = await result.json(); - - // Convert to a usable format - const progressPercent = progress * 100; - const base64 = typeof current_image === "string" && current_image.length > 0 ? `data:image/png;base64,${current_image}` : undefined; - - // Deal with a missing image - if (!base64) { - // The image is not ready yet (because it's only had a few samples since generation began), but we do have a progress percentage - if (!Number.isNaN(progressPercent) && progressPercent >= 0 && progressPercent <= 100) { - return [undefined, progressPercent]; - } - - // Something else is wrong and the image wasn't provided as expected - return Promise.reject(); - } - - // The image was provided so we turn it into a data blob - const blob = await (await fetch(base64)).blob(); - return [blob, progressPercent]; -} - -async function generate( - discloseUploadingProgress: (progress: number) => void, - hostname: string, - image: Blob | undefined, - mask: Blob | undefined, - maskPaintMode: string, - maskBlurPx: number, - maskFillContent: string, - parameters: ImaginateGenerationParameters -): Promise<{ - uploaded: Promise; - result: Promise; - xhr?: XMLHttpRequest; -}> { - let body; - let endpoint; - if (image === undefined || parameters.denoisingStrength === undefined) { - endpoint = `${hostname}sdapi/v1/txt2img`; - - body = { - // enable_hr: false, - // denoising_strength: 0, - // firstphase_width: 0, - // firstphase_height: 0, - prompt: parameters.prompt, - // styles: [], - seed: Number(parameters.seed), - // subseed: -1, - // subseed_strength: 0, - // seed_resize_from_h: -1, - // seed_resize_from_w: -1, - // batch_size: 1, - // n_iter: 1, - steps: parameters.samples, - cfg_scale: parameters.cfgScale, - width: parameters.resolution.x, - height: parameters.resolution.y, - restore_faces: parameters.restoreFaces, - tiling: parameters.tiling, - negative_prompt: parameters.negativePrompt, - // eta: 0, - // s_churn: 0, - // s_tmax: 0, - // s_tmin: 0, - // s_noise: 1, - override_settings: { - show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS, - }, - sampler_index: parameters.samplingMethod, - }; - } else { - const sourceImageBase64 = await blobToBase64(image); - const maskImageBase64 = mask ? await blobToBase64(mask) : ""; - - const maskFillContentIndexes = ["Fill", "Original", "LatentNoise", "LatentNothing"]; - const maskFillContentIndexFound = maskFillContentIndexes.indexOf(maskFillContent); - const maskFillContentIndex = maskFillContentIndexFound === -1 ? undefined : maskFillContentIndexFound; - - const maskInvert = maskPaintMode === "Inpaint" ? 1 : 0; - - endpoint = `${hostname}sdapi/v1/img2img`; - - body = { - init_images: [sourceImageBase64], - // resize_mode: 0, - denoising_strength: parameters.denoisingStrength, - mask: mask && maskImageBase64, - mask_blur: mask && maskBlurPx, - inpainting_fill: mask && maskFillContentIndex, - inpaint_full_res: mask && false, - // inpaint_full_res_padding: 0, - inpainting_mask_invert: mask && maskInvert, - prompt: parameters.prompt, - // styles: [], - seed: Number(parameters.seed), - // subseed: -1, - // subseed_strength: 0, - // seed_resize_from_h: -1, - // seed_resize_from_w: -1, - // batch_size: 1, - // n_iter: 1, - steps: parameters.samples, - cfg_scale: parameters.cfgScale, - width: parameters.resolution.x, - height: parameters.resolution.y, - restore_faces: parameters.restoreFaces, - tiling: parameters.tiling, - negative_prompt: parameters.negativePrompt, - // eta: 0, - // s_churn: 0, - // s_tmax: 0, - // s_tmin: 0, - // s_noise: 1, - override_settings: { - show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS, - img2img_fix_steps: true, - }, - sampler_index: parameters.samplingMethod, - // include_init_images: false, - }; - } - - // Prepare a promise that will resolve after the outbound request upload is complete - let uploadedResolve: () => void; - let uploadedReject: () => void; - const uploaded = new Promise((resolve, reject): void => { - uploadedResolve = resolve; - uploadedReject = reject; - }); - - // Fire off the request and, once the outbound request upload is complete, resolve the promise we defined above - const uploadProgress = (progress: number): void => { - if (progress < 1) { - discloseUploadingProgress(progress); - } else { - uploadedResolve(); - } - }; - const [result, xhr] = requestWithUploadDownloadProgress(endpoint, "POST", JSON.stringify(body), uploadProgress, abortAndResetPolling); - result.catch(() => uploadedReject()); - - // Return the promise that resolves when the request upload is complete, the promise that resolves when the response download is complete, and the XHR so it can be aborted - return { uploaded, result, xhr }; -} - -async function terminate(hostname: string): Promise { - await fetch(`${hostname}sdapi/v1/interrupt`, { method: "POST" }); -} - -async function checkConnection(hostname: string): Promise { - statusAbortController.abort(); - statusAbortController = new AbortController(); - - const timeout = setTimeout(() => statusAbortController.abort(), SERVER_STATUS_CHECK_TIMEOUT); - - try { - // Intentionally misuse this API endpoint by using it just to check for a code 200 response, regardless of what the result is - const { status } = await fetch(`${hostname}sdapi/v1/progress?skip_current_image=true`, { signal: statusAbortController.signal, method: "GET" }); - - // This code means the server has indeed responded and the endpoint exists (otherwise it would be 404) - if (status === 200) { - clearTimeout(timeout); - return true; - } - } catch { - // Do nothing here - } - - return false; -} diff --git a/frontend/src/wasm-communication/editor.ts b/frontend/src/wasm-communication/editor.ts index 6395bfa2..64109fad 100644 --- a/frontend/src/wasm-communication/editor.ts +++ b/frontend/src/wasm-communication/editor.ts @@ -95,3 +95,7 @@ export function createEditor() { subscriptions, }; } + +export function injectImaginatePollServerStatus() { + window["editorInstance"]?.injectImaginatePollServerStatus() +} diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 6dd27c6a..41928433 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -516,7 +516,7 @@ export class TriggerCopyToClipboardBlobUrl extends JsMessage { export class TriggerDownloadBlobUrl extends JsMessage { readonly layerName!: string; - + readonly blobUrl!: string; } @@ -537,85 +537,6 @@ export class TriggerDownloadTextFile extends JsMessage { readonly name!: string; } -export class TriggerImaginateCheckServerStatus extends JsMessage { - readonly hostname!: string; -} - -export class TriggerImaginateGenerate extends JsMessage { - @Type(() => ImaginateGenerationParameters) - readonly parameters!: ImaginateGenerationParameters; - - @Type(() => ImaginateBaseImage) - readonly baseImage!: ImaginateBaseImage | undefined; - - @Type(() => ImaginateMaskImage) - readonly maskImage: ImaginateMaskImage | undefined; - - readonly maskPaintMode!: string; - - readonly maskBlurPx!: number; - - readonly maskFillContent!: string; - - readonly hostname!: string; - - readonly refreshFrequency!: number; - - readonly documentId!: bigint; - - readonly layerPath!: BigUint64Array; - - readonly nodePath!: BigUint64Array; -} - -export class ImaginateMaskImage { - readonly svg!: string; - - readonly size!: [number, number]; -} - -export class ImaginateBaseImage { - readonly mime!: string; - - readonly imageData!: Uint8Array; - - @TupleToVec2 - readonly size!: [number, number]; -} - -export class ImaginateGenerationParameters { - readonly seed!: number; - - readonly samples!: number; - - readonly samplingMethod!: string; - - readonly denoisingStrength!: number | undefined; - - readonly cfgScale!: number; - - readonly prompt!: string; - - readonly negativePrompt!: string; - - @BigIntTupleToVec2 - readonly resolution!: XY; - - readonly restoreFaces!: boolean; - - readonly tiling!: boolean; -} - -export class TriggerImaginateTerminate extends JsMessage { - readonly documentId!: bigint; - - readonly layerPath!: BigUint64Array; - - readonly nodePath!: BigUint64Array; - - readonly hostname!: string; -} - export class TriggerRasterizeRegionBelowLayer extends JsMessage { readonly documentId!: bigint; @@ -778,7 +699,7 @@ export class ImaginateImageData { readonly mime!: string; readonly imageData!: Uint8Array; - + readonly transform!: Float64Array ; } @@ -1404,9 +1325,6 @@ export const messageMakers: Record = { TriggerDownloadRaster, TriggerDownloadTextFile, TriggerFontLoad, - TriggerImaginateCheckServerStatus, - TriggerImaginateGenerate, - TriggerImaginateTerminate, TriggerImport, TriggerIndexedDbRemoveDocument, TriggerIndexedDbWriteDocument, diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index fea6fb5e..ebadb5f5 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -30,9 +30,9 @@ graphene-core = { path = "../../node-graph/gcore", features = [ ] } serde = { version = "1.0", features = ["derive"] } wasm-bindgen = { version = "=0.2.86" } -serde-wasm-bindgen = "0.4.1" -js-sys = "0.3.55" -wasm-bindgen-futures = "0.4.33" +serde-wasm-bindgen = "0.5.0" +js-sys = "0.3.63" +wasm-bindgen-futures = "0.4.36" ron = { version = "0.8", optional = true } bezier-rs = { path = "../../libraries/bezier-rs" } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index a3d56ca6..be2d87a3 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -11,7 +11,7 @@ use editor::application::Editor; use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION}; use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; -use editor::messages::portfolio::utility_types::{ImaginateServerStatus, Platform}; +use editor::messages::portfolio::utility_types::Platform; use editor::messages::prelude::*; use graph_craft::document::NodeId; use graphene_core::raster::color::Color; @@ -586,63 +586,6 @@ impl JsEditorHandle { } } - /// Sends the blob URL generated by JS to the Imaginate layer in the respective document - #[wasm_bindgen(js_name = setImaginateImageData)] - pub fn set_imaginate_image_data(&self, document_id: u64, layer_path: Vec, node_path: Vec, image_data: Vec, width: u32, height: u32) { - let message = PortfolioMessage::ImaginateSetImageData { - document_id, - node_path, - layer_path, - image_data, - width, - height, - }; - self.dispatch(message); - } - - /// Notifies the Imaginate layer of a new percentage of completion and whether or not it's currently generating - #[wasm_bindgen(js_name = setImaginateGeneratingStatus)] - pub fn set_imaginate_generating_status(&self, document_id: u64, layer_path: Vec, node_path: Vec, percent: Option, status: String) { - use graph_craft::imaginate_input::ImaginateStatus; - - let status = match status.as_str() { - "Idle" => ImaginateStatus::Idle, - "Beginning" => ImaginateStatus::Beginning, - "Uploading" => ImaginateStatus::Uploading(percent.expect("Percent needs to be supplied to set ImaginateStatus::Uploading")), - "Generating" => ImaginateStatus::Generating, - "Terminating" => ImaginateStatus::Terminating, - "Terminated" => ImaginateStatus::Terminated, - _ => panic!("Invalid string from JS for ImaginateStatus, received: {}", status), - }; - - let percent = if matches!(status, ImaginateStatus::Uploading(_)) { None } else { percent }; - - let message = PortfolioMessage::ImaginateSetGeneratingStatus { - document_id, - layer_path, - node_path, - percent, - status, - }; - self.dispatch(message); - } - - /// Notifies the editor that the Imaginate server is available or unavailable - #[wasm_bindgen(js_name = setImaginateServerStatus)] - pub fn set_imaginate_server_status(&self, available: bool) { - let message: Message = match available { - true => PortfolioMessage::ImaginateSetServerStatus { - status: ImaginateServerStatus::Connected, - } - .into(), - false => PortfolioMessage::ImaginateSetServerStatus { - status: ImaginateServerStatus::Unavailable, - } - .into(), - }; - self.dispatch(message); - } - /// Sends the blob URL generated by JS to the Imaginate layer in the respective document #[wasm_bindgen(js_name = renderGraphUsingRasterizedRegionBelowLayer)] pub fn render_graph_using_rasterized_region_below_layer( @@ -793,6 +736,11 @@ impl JsEditorHandle { }); frontend_messages.unwrap().unwrap_or_default() } + + #[wasm_bindgen(js_name = injectImaginatePollServerStatus)] + pub fn inject_imaginate_poll_server_status(&self) { + self.dispatch(PortfolioMessage::ImaginatePollServerStatus); + } } // Needed to make JsEditorHandle functions pub to Rust. diff --git a/node-graph/future-executor/Cargo.toml b/node-graph/future-executor/Cargo.toml index 0fa9251f..91bc402e 100644 --- a/node-graph/future-executor/Cargo.toml +++ b/node-graph/future-executor/Cargo.toml @@ -12,4 +12,9 @@ futures = "0.3.25" log = "0.4" [target.wasm32-unknown-unknown.dependencies] -wasm-rs-async-executor = {version = "0.9.0", features = ["cooperative-browser", "debug", "requestIdleCallback"] } +wasm-rs-async-executor = { version = "0.9.0", features = [ + "cooperative-browser", + "debug", + "requestIdleCallback", +] } +wasm-bindgen-futures = "0.4.36" diff --git a/node-graph/future-executor/src/lib.rs b/node-graph/future-executor/src/lib.rs index 315668d7..1a7a0b86 100644 --- a/node-graph/future-executor/src/lib.rs +++ b/node-graph/future-executor/src/lib.rs @@ -22,3 +22,8 @@ pub fn block_on(future: F) -> F::Output { #[cfg(not(target_arch = "wasm32"))] futures::executor::block_on(future) } + +#[cfg(target_arch = "wasm32")] +pub fn spawn + 'static>(future: F) { + wasm_bindgen_futures::spawn_local(future); +} diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index a83c4b8b..5b23d02b 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -112,10 +112,25 @@ impl ApplicationIo for &T { } } +#[derive(Debug, Clone)] +pub enum NodeGraphUpdateMessage { + ImaginateStatusUpdate, +} + +pub trait NodeGraphUpdateSender { + fn send(&self, message: NodeGraphUpdateMessage); +} + +pub trait GetImaginatePreferences { + fn get_host_name(&self) -> &str; +} + pub struct EditorApi<'a, Io> { pub image_frame: Option>, pub font_cache: &'a FontCache, pub application_io: &'a Io, + pub node_graph_message_sender: &'a dyn NodeGraphUpdateSender, + pub imaginate_preferences: &'a dyn GetImaginatePreferences, } impl<'a, Io> Clone for EditorApi<'a, Io> { @@ -124,6 +139,8 @@ impl<'a, Io> Clone for EditorApi<'a, Io> { image_frame: self.image_frame.clone(), font_cache: self.font_cache, application_io: self.application_io, + node_graph_message_sender: self.node_graph_message_sender, + imaginate_preferences: self.imaginate_preferences, } } } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 7f5cab88..4a320a2e 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -441,7 +441,7 @@ impl NodeNetwork { } /// Check if the specified node id is connected to the output - pub fn connected_to_output(&self, target_node_id: NodeId, ignore_imaginate: bool) -> bool { + pub fn connected_to_output(&self, target_node_id: NodeId) -> bool { // If the node is the output then return true if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) { return true; @@ -454,11 +454,6 @@ impl NodeNetwork { already_visited.extend(self.outputs.iter().map(|output| output.node_id)); while let Some(node) = stack.pop() { - // Skip the imaginate node inputs - if ignore_imaginate && node.name == "Imaginate" { - continue; - } - for input in &node.inputs { if let &NodeInput::Node { node_id: ref_id, .. } = input { // Skip if already viewed @@ -680,7 +675,7 @@ impl NodeNetwork { let mut dummy_input = NodeInput::ShortCircut(concrete!(())); std::mem::swap(&mut dummy_input, input); - if let NodeInput::Value { tagged_value, exposed } = dummy_input { + if let NodeInput::Value { mut tagged_value, exposed } = dummy_input { let value_node_id = gen_id(); let merged_node_id = map_ids(id, value_node_id); let path = if let Some(mut new_path) = node.path.clone() { diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 5832ebac..c2578ea4 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -1,6 +1,6 @@ use super::DocumentNode; use crate::graphene_compiler::Any; -pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; +pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use crate::proto::{Any as DAny, FutureAny}; use graphene_core::raster::brush_cache::BrushCache; @@ -27,7 +27,7 @@ pub enum TaggedValue { OptionalDVec2(Option), DAffine2(DAffine2), Image(graphene_core::raster::Image), - RcImage(Option>>), + ImaginateCache(ImaginateCache), ImageFrame(graphene_core::raster::ImageFrame), Color(graphene_core::raster::color::Color), Subpaths(Vec>), @@ -36,7 +36,7 @@ pub enum TaggedValue { LuminanceCalculation(LuminanceCalculation), ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateMaskStartingFill(ImaginateMaskStartingFill), - ImaginateStatus(ImaginateStatus), + ImaginateController(ImaginateController), LayerPath(Option>), VectorData(graphene_core::vector::VectorData), Fill(graphene_core::vector::style::Fill), @@ -83,7 +83,7 @@ impl Hash for TaggedValue { } Self::DAffine2(m) => m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)), Self::Image(i) => i.hash(state), - Self::RcImage(i) => i.hash(state), + Self::ImaginateCache(i) => i.hash(state), Self::Color(c) => c.hash(state), Self::Subpaths(s) => s.iter().for_each(|subpath| subpath.hash(state)), Self::RcSubpath(s) => s.hash(state), @@ -91,7 +91,7 @@ impl Hash for TaggedValue { Self::LuminanceCalculation(l) => l.hash(state), Self::ImaginateSamplingMethod(m) => m.hash(state), Self::ImaginateMaskStartingFill(f) => f.hash(state), - Self::ImaginateStatus(s) => s.hash(state), + Self::ImaginateController(s) => s.hash(state), Self::LayerPath(p) => p.hash(state), Self::ImageFrame(i) => i.hash(state), Self::VectorData(vector_data) => vector_data.hash(state), @@ -146,7 +146,7 @@ impl<'a> TaggedValue { TaggedValue::OptionalDVec2(x) => Box::new(x), TaggedValue::DAffine2(x) => Box::new(x), TaggedValue::Image(x) => Box::new(x), - TaggedValue::RcImage(x) => Box::new(x), + TaggedValue::ImaginateCache(x) => Box::new(x), TaggedValue::ImageFrame(x) => Box::new(x), TaggedValue::Color(x) => Box::new(x), TaggedValue::Subpaths(x) => Box::new(x), @@ -155,7 +155,7 @@ impl<'a> TaggedValue { TaggedValue::LuminanceCalculation(x) => Box::new(x), TaggedValue::ImaginateSamplingMethod(x) => Box::new(x), TaggedValue::ImaginateMaskStartingFill(x) => Box::new(x), - TaggedValue::ImaginateStatus(x) => Box::new(x), + TaggedValue::ImaginateController(x) => Box::new(x), TaggedValue::LayerPath(x) => Box::new(x), TaggedValue::VectorData(x) => Box::new(x), TaggedValue::Fill(x) => Box::new(x), @@ -210,7 +210,7 @@ impl<'a> TaggedValue { TaggedValue::DVec2(_) => concrete!(DVec2), TaggedValue::OptionalDVec2(_) => concrete!(Option), TaggedValue::Image(_) => concrete!(graphene_core::raster::Image), - TaggedValue::RcImage(_) => concrete!(Option>>), + TaggedValue::ImaginateCache(_) => concrete!(ImaginateCache), TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame), TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), TaggedValue::Subpaths(_) => concrete!(Vec>), @@ -218,7 +218,7 @@ impl<'a> TaggedValue { TaggedValue::BlendMode(_) => concrete!(BlendMode), TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod), TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill), - TaggedValue::ImaginateStatus(_) => concrete!(ImaginateStatus), + TaggedValue::ImaginateController(_) => concrete!(ImaginateController), TaggedValue::LayerPath(_) => concrete!(Option>), TaggedValue::DAffine2(_) => concrete!(DAffine2), TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation), @@ -263,7 +263,7 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Ok(TaggedValue::DVec2(*downcast(input).unwrap())), x if x == TypeId::of::>() => Ok(TaggedValue::OptionalDVec2(*downcast(input).unwrap())), x if x == TypeId::of::>() => Ok(TaggedValue::Image(*downcast(input).unwrap())), - x if x == TypeId::of::>>>() => Ok(TaggedValue::RcImage(*downcast(input).unwrap())), + x if x == TypeId::of::() => Ok(TaggedValue::ImaginateCache(*downcast(input).unwrap())), x if x == TypeId::of::>() => Ok(TaggedValue::ImageFrame(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::Color(*downcast(input).unwrap())), x if x == TypeId::of::>>() => Ok(TaggedValue::Subpaths(*downcast(input).unwrap())), @@ -271,7 +271,7 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Ok(TaggedValue::BlendMode(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::ImaginateSamplingMethod(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::ImaginateMaskStartingFill(*downcast(input).unwrap())), - x if x == TypeId::of::() => Ok(TaggedValue::ImaginateStatus(*downcast(input).unwrap())), + x if x == TypeId::of::() => Ok(TaggedValue::ImaginateController(*downcast(input).unwrap())), x if x == TypeId::of::>>() => Ok(TaggedValue::LayerPath(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::DAffine2(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::LuminanceCalculation(*downcast(input).unwrap())), diff --git a/node-graph/graph-craft/src/imaginate_input.rs b/node-graph/graph-craft/src/imaginate_input.rs index 206bbc52..9569ae1d 100644 --- a/node-graph/graph-craft/src/imaginate_input.rs +++ b/node-graph/graph-craft/src/imaginate_input.rs @@ -1,50 +1,155 @@ use dyn_any::{DynAny, StaticType}; -use glam::DVec2; +use graphene_core::Color; +use std::borrow::Cow; use std::fmt::Debug; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; -#[derive(Default, Debug, Clone, Copy, PartialEq, DynAny, specta::Type)] +#[derive(Default, Debug, Clone, DynAny, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ImaginateCache(Arc>>); + +impl ImaginateCache { + pub fn into_inner(self) -> Arc>> { + self.0 + } +} + +impl std::cmp::PartialEq for ImaginateCache { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl core::hash::Hash for ImaginateCache { + fn hash(&self, state: &mut H) { + self.0.lock().unwrap().hash(state); + } +} + +pub trait ImaginateTerminationHandle: Debug + Send + Sync + 'static { + fn terminate(&self); +} + +#[derive(Default, Debug, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct InternalImaginateControl { + status: Mutex, + trigger_regenerate: AtomicBool, + #[serde(skip)] + termination_sender: Mutex>>, +} + +#[derive(Debug, Default, Clone, DynAny, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ImaginateController(Arc); + +impl ImaginateController { + pub fn get_status(&self) -> ImaginateStatus { + self.0.status.lock().as_deref().cloned().unwrap_or_default() + } + + pub fn set_status(&self, status: ImaginateStatus) { + if let Ok(mut lock) = self.0.status.lock() { + *lock = status + } + } + + pub fn take_regenerate_trigger(&self) -> bool { + self.0.trigger_regenerate.swap(false, Ordering::SeqCst) + } + + pub fn trigger_regenerate(&self) { + self.0.trigger_regenerate.store(true, Ordering::SeqCst) + } + + pub fn request_termination(&self) { + if let Some(handle) = self.0.termination_sender.lock().ok().and_then(|mut lock| lock.take()) { + handle.terminate() + } + } + + pub fn set_termination_handle(&self, handle: Box) { + if let Ok(mut lock) = self.0.termination_sender.lock() { + *lock = Some(handle) + } + } +} + +impl std::cmp::PartialEq for ImaginateController { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl core::hash::Hash for ImaginateController { + fn hash(&self, state: &mut H) { + core::ptr::hash(Arc::as_ptr(&self.0), state) + } +} + +#[derive(Default, Debug, Clone, PartialEq, DynAny, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ImaginateStatus { #[default] - Idle, + Ready, + ReadyDone, Beginning, - Uploading(f64), - Generating, + Uploading, + Generating(f64), Terminating, Terminated, + Failed(String), +} + +impl ImaginateStatus { + pub fn to_text(&self) -> Cow<'static, str> { + match self { + Self::Ready => Cow::Borrowed("Ready"), + Self::ReadyDone => Cow::Borrowed("Done"), + Self::Beginning => Cow::Borrowed("Beginning…"), + Self::Uploading => Cow::Borrowed("Downloading Image…"), + Self::Generating(percent) => Cow::Owned(format!("Generating {percent:.0}%")), + Self::Terminating => Cow::Owned(format!("Terminating…")), + Self::Terminated => Cow::Owned(format!("Terminated")), + Self::Failed(err) => Cow::Owned(format!("Failed: {err}")), + } + } } #[allow(clippy::derived_hash_with_manual_eq)] impl core::hash::Hash for ImaginateStatus { fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); match self { - Self::Idle => 0.hash(state), - Self::Beginning => 1.hash(state), - Self::Uploading(f) => { - 2.hash(state); - f.to_bits().hash(state); - } - Self::Generating => 3.hash(state), - Self::Terminating => 4.hash(state), - Self::Terminated => 5.hash(state), + Self::Ready | Self::ReadyDone | Self::Beginning | Self::Uploading | Self::Terminating | Self::Terminated => (), + Self::Generating(f) => f.to_bits().hash(state), + Self::Failed(err) => err.hash(state), } } } -#[derive(Debug, Clone, PartialEq, specta::Type)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ImaginateBaseImage { - pub mime: String, - #[cfg_attr(feature = "serde", serde(rename = "imageData"))] - pub image_data: Vec, - pub size: DVec2, +#[derive(PartialEq, Eq, Clone, Default, Debug)] +pub enum ImaginateServerStatus { + #[default] + Unknown, + Checking, + Connected, + Failed(String), + Unavailable, } -#[derive(Debug, Clone, PartialEq, specta::Type)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ImaginateMaskImage { - pub svg: String, - pub size: DVec2, +impl ImaginateServerStatus { + pub fn to_text(&self) -> Cow<'static, str> { + match self { + Self::Unknown | Self::Checking => Cow::Borrowed("Checking..."), + Self::Connected => Cow::Borrowed("Connected"), + Self::Failed(err) => Cow::Owned(err.clone()), + Self::Unavailable => Cow::Borrowed("Unavailable"), + } + } } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -180,24 +285,26 @@ impl std::fmt::Display for ImaginateSamplingMethod { } } -#[derive(Debug, Clone, PartialEq, specta::Type)] +#[derive(Clone, Debug, PartialEq, Hash, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ImaginateGenerationParameters { - pub seed: u64, - pub samples: u32, - /// Use `ImaginateSamplingMethod::api_value()` to generate this string - #[cfg_attr(feature = "serde", serde(rename = "samplingMethod"))] - pub sampling_method: String, - #[cfg_attr(feature = "serde", serde(rename = "denoisingStrength"))] - pub image_creativity: Option, - #[cfg_attr(feature = "serde", serde(rename = "cfgScale"))] - pub text_guidance: f64, - #[cfg_attr(feature = "serde", serde(rename = "prompt"))] - pub text_prompt: String, - #[cfg_attr(feature = "serde", serde(rename = "negativePrompt"))] - pub negative_prompt: String, - pub resolution: (u32, u32), - #[cfg_attr(feature = "serde", serde(rename = "restoreFaces"))] - pub restore_faces: bool, - pub tiling: bool, +pub struct ImaginatePreferences { + pub host_name: String, +} + +impl graphene_core::application_io::GetImaginatePreferences for ImaginatePreferences { + fn get_host_name(&self) -> &str { + &self.host_name + } +} + +impl Default for ImaginatePreferences { + fn default() -> Self { + Self { + host_name: "http://localhost:7860/".into(), + } + } +} + +unsafe impl dyn_any::StaticType for ImaginatePreferences { + type Static = ImaginatePreferences; } diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index b9690e4d..c92cf7b6 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -9,12 +9,18 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["wasm"] -gpu = ["graphene-core/gpu", "gpu-compiler-bin-wrapper", "compilation-client", "gpu-executor"] +default = ["wasm", "imaginate"] +gpu = [ + "graphene-core/gpu", + "gpu-compiler-bin-wrapper", + "compilation-client", + "gpu-executor", +] vulkan = ["gpu", "vulkan-executor"] wgpu = ["gpu", "wgpu-executor"] quantization = ["autoquant"] wasm = ["wasm-bindgen", "web-sys", "js-sys"] +imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"] [dependencies] @@ -37,6 +43,7 @@ compilation-client = { path = "../compilation-client", optional = true } bytemuck = { version = "1.8" } tempfile = "3" image = { version = "*", default-features = false } +base64 = { version = "0.21", optional = true } dyn-clone = "1.0" log = "0.4" @@ -48,12 +55,13 @@ glam = { version = "0.22", features = ["serde"] } node-macro = { path = "../node-macro" } xxhash-rust = { workspace = true } serde_json = "1.0.96" -reqwest = { version = "0.11.17", features = ["rustls", "rustls-tls"] } +reqwest = { version = "0.11.18", features = ["rustls", "rustls-tls", "json"] } futures = "0.3.28" wasm-bindgen = { version = "0.2.84", optional = true } -js-sys = { version = "0.3.55", optional = true } +js-sys = { version = "0.3.63", optional = true } wgpu-types = "0.16.0" wgpu = "0.16.1" +wasm-bindgen-futures = { version = "0.4.36", optional = true } [dependencies.serde] version = "1.0" @@ -62,7 +70,7 @@ features = ["derive"] [dependencies.web-sys] -version = "0.3.4" +version = "0.3.63" optional = true features = [ "Window", diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 823d6716..e0f0c9e6 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -76,6 +76,7 @@ impl<_I, _O, S0> DynAnyRefNode<_I, _O, S0> { Self { node, _i: core::marker::PhantomData } } } + pub struct DynAnyInRefNode { node: Node, _i: PhantomData<(I, O)>, @@ -115,6 +116,10 @@ where fn reset(&self) { self.node.reset(); } + + fn serialize(&self) -> Option> { + self.node.serialize() + } } impl FutureWrapperNode { diff --git a/node-graph/gstd/src/http.rs b/node-graph/gstd/src/http.rs index 3df77586..9bdfbb09 100644 --- a/node-graph/gstd/src/http.rs +++ b/node-graph/gstd/src/http.rs @@ -1,5 +1,3 @@ -use std::future::Future; - use crate::Node; pub struct GetNode; @@ -17,16 +15,3 @@ pub struct PostNode { async fn post_node(url: String, body: String) -> reqwest::Response { reqwest::Client::new().post(url).body(body).send().await.unwrap() } - -#[derive(Clone, Copy, Debug)] -pub struct EvalSyncNode {} - -#[node_macro::node_fn(EvalSyncNode)] -fn eval_sync(future: F) -> F::Output { - let future = futures::future::maybe_done(future); - futures::pin_mut!(future); - match future.as_mut().take_output() { - Some(value) => value, - _ => panic!("Node construction future returned pending"), - } -} diff --git a/node-graph/gstd/src/imaginate.rs b/node-graph/gstd/src/imaginate.rs new file mode 100644 index 00000000..34d82655 --- /dev/null +++ b/node-graph/gstd/src/imaginate.rs @@ -0,0 +1,517 @@ +use crate::wasm_application_io::WasmEditorApi; +use core::any::TypeId; +use core::future::Future; +use futures::{future::Either, TryFutureExt}; +use glam::DVec2; +use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginatePreferences, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle}; +use graphene_core::application_io::NodeGraphUpdateMessage; +use graphene_core::raster::{Color, Image, Luma, Pixel}; +use image::{DynamicImage, ImageBuffer, ImageOutputFormat}; +use reqwest::Url; + +const PROGRESS_EVERY_N_STEPS: u32 = 5; +const SDAPI_TEXT_TO_IMAGE: &str = "sdapi/v1/txt2img"; +const SDAPI_IMAGE_TO_IMAGE: &str = "sdapi/v1/img2img"; +const SDAPI_PROGRESS: &str = "sdapi/v1/progress?skip_current_image=true"; +const SDAPI_TERMINATE: &str = "sdapi/v1/interrupt"; + +fn new_client() -> Result { + reqwest::ClientBuilder::new().build().map_err(Error::ClientBuild) +} + +fn parse_url(url: &str) -> Result { + url.try_into().map_err(|err| Error::UrlParse { text: url.into(), err }) +} + +fn join_url(base_url: &Url, path: &str) -> Result { + base_url.join(path).map_err(|err| Error::UrlParse { text: base_url.to_string(), err }) +} + +fn new_get_request(client: &reqwest::Client, url: U) -> Result { + client.get(url).header("Accept", "*/*").build().map_err(Error::RequestBuild) +} + +pub struct ImaginatePersistentData { + pending_server_check: Option>>, + host_name: Url, + client: Option, + server_status: ImaginateServerStatus, +} + +impl core::fmt::Debug for ImaginatePersistentData { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct(core::any::type_name::()) + .field("pending_server_check", &self.pending_server_check.is_some()) + .field("host_name", &self.host_name) + .field("status", &self.server_status) + .finish() + } +} + +impl Default for ImaginatePersistentData { + fn default() -> Self { + let mut status = ImaginateServerStatus::default(); + let client = new_client().map_err(|err| status = ImaginateServerStatus::Failed(err.to_string())).ok(); + let ImaginatePreferences { host_name } = Default::default(); + Self { + pending_server_check: None, + host_name: parse_url(&host_name).unwrap(), + client, + server_status: status, + } + } +} + +impl ImaginatePersistentData { + pub fn set_host_name(&mut self, name: &str) { + match parse_url(name) { + Ok(url) => self.host_name = url, + Err(err) => self.server_status = ImaginateServerStatus::Failed(err.to_string()), + } + } + + fn initiate_server_check_maybe_fail(&mut self) -> Result + 'static>>>, Error> { + use futures::future::FutureExt; + let Some(client) = &self.client else { return Ok(None); }; + if self.pending_server_check.is_some() { + return Ok(None); + } + self.server_status = ImaginateServerStatus::Checking; + let url = join_url(&self.host_name, SDAPI_PROGRESS)?; + let request = new_get_request(client, url)?; + let (send, recv) = futures::channel::oneshot::channel(); + let response_future = client.execute(request).map(move |r| { + let _ = send.send(r); + }); + self.pending_server_check = Some(recv); + Ok(Some(Box::pin(response_future))) + } + + pub fn initiate_server_check(&mut self) -> Option + 'static>>> { + match self.initiate_server_check_maybe_fail() { + Ok(f) => f, + Err(err) => { + self.server_status = ImaginateServerStatus::Failed(err.to_string()); + None + } + } + } + + pub fn poll_server_check(&mut self) { + if let Some(mut check) = self.pending_server_check.take() { + self.server_status = match check.try_recv().map(|r| r.map(|r| r.and_then(reqwest::Response::error_for_status))) { + Ok(Some(Ok(_response))) => ImaginateServerStatus::Connected, + Ok(Some(Err(_))) | Err(_) => ImaginateServerStatus::Unavailable, + Ok(None) => { + self.pending_server_check = Some(check); + ImaginateServerStatus::Checking + } + } + } + } + + pub fn server_status(&self) -> &ImaginateServerStatus { + &self.server_status + } + + pub fn is_checking(&self) -> bool { + matches!(self.server_status, ImaginateServerStatus::Checking) + } +} + +#[derive(Debug)] +struct ImaginateFutureAbortHandle(futures::future::AbortHandle); + +impl ImaginateTerminationHandle for ImaginateFutureAbortHandle { + fn terminate(&self) { + self.0.abort() + } +} + +#[derive(Debug)] +enum Error { + UrlParse { text: String, err: <&'static str as TryInto>::Error }, + ClientBuild(reqwest::Error), + RequestBuild(reqwest::Error), + Request(reqwest::Error), + ResponseFormat(reqwest::Error), + NoImage, + Base64Decode(base64::DecodeError), + ImageDecode(image::error::ImageError), + ImageEncode(image::error::ImageError), + UnsupportedPixelType(&'static str), + InconsistentImageSize, + Terminated, + TerminationFailed(reqwest::Error), +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Self::UrlParse { text, err } => write!(f, "invalid url '{text}' ({err})"), + Self::ClientBuild(err) => write!(f, "failed to create a reqwest client ({err})"), + Self::RequestBuild(err) => write!(f, "failed to create a reqwest request ({err})"), + Self::Request(err) => write!(f, "request failed ({err})"), + Self::ResponseFormat(err) => write!(f, "got an invalid API response ({err})"), + Self::NoImage => write!(f, "got an empty API response"), + Self::Base64Decode(err) => write!(f, "failed to decode base64 encoded image ({err})"), + Self::ImageDecode(err) => write!(f, "failed to decode png image ({err})"), + Self::ImageEncode(err) => write!(f, "failed to encode png image ({err})"), + Self::UnsupportedPixelType(ty) => write!(f, "pixel type `{ty}` not supported for imaginate images"), + Self::InconsistentImageSize => write!(f, "image width and height do not match the image byte size"), + Self::Terminated => write!(f, "imaginate request was terminated by the user"), + Self::TerminationFailed(err) => write!(f, "termination failed ({err})"), + } + } +} + +impl std::error::Error for Error {} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +struct ImageResponse { + images: Vec, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +struct ProgressResponse { + progress: f64, +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateTextToImageRequestOverrideSettings { + show_progress_every_n_steps: u32, +} + +impl Default for ImaginateTextToImageRequestOverrideSettings { + fn default() -> Self { + Self { + show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS, + } + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateImageToImageRequestOverrideSettings { + show_progress_every_n_steps: u32, + img2img_fix_steps: bool, +} + +impl Default for ImaginateImageToImageRequestOverrideSettings { + fn default() -> Self { + Self { + show_progress_every_n_steps: PROGRESS_EVERY_N_STEPS, + img2img_fix_steps: true, + } + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateTextToImageRequest<'a> { + #[serde(flatten)] + common: ImaginateCommonImageRequest<'a>, + override_settings: ImaginateTextToImageRequestOverrideSettings, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateMask { + mask: String, + mask_blur: String, + inpainting_fill: u32, + inpaint_full_res: bool, + inpainting_mask_invert: u32, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateImageToImageRequest<'a> { + #[serde(flatten)] + common: ImaginateCommonImageRequest<'a>, + override_settings: ImaginateImageToImageRequestOverrideSettings, + + init_images: Vec, + denoising_strength: f64, + #[serde(flatten)] + mask: Option, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +struct ImaginateCommonImageRequest<'a> { + prompt: String, + seed: f64, + steps: u32, + cfg_scale: f64, + width: f64, + height: f64, + restore_faces: bool, + tiling: bool, + negative_prompt: String, + sampler_index: &'a str, +} + +#[cfg(feature = "imaginate")] +pub async fn imaginate<'a, P: Pixel>( + image: Image

, + editor_api: impl Future>, + controller: ImaginateController, + seed: impl Future, + res: impl Future>, + samples: impl Future, + sampling_method: impl Future, + prompt_guidance: impl Future, + prompt: impl Future, + negative_prompt: impl Future, + adapt_input_image: impl Future, + image_creativity: impl Future, + masking_layer: impl Future>>, + inpaint: impl Future, + mask_blur: impl Future, + mask_starting_fill: impl Future, + improve_faces: impl Future, + tiling: impl Future, +) -> Image

{ + let WasmEditorApi { + node_graph_message_sender, + imaginate_preferences, + .. + } = editor_api.await; + let set_progress = |progress: ImaginateStatus| { + controller.set_status(progress); + node_graph_message_sender.send(NodeGraphUpdateMessage::ImaginateStatusUpdate); + }; + let host_name = imaginate_preferences.get_host_name(); + imaginate_maybe_fail( + image, + host_name, + set_progress, + &controller, + seed, + res, + samples, + sampling_method, + prompt_guidance, + prompt, + negative_prompt, + adapt_input_image, + image_creativity, + masking_layer, + inpaint, + mask_blur, + mask_starting_fill, + improve_faces, + tiling, + ) + .await + .unwrap_or_else(|err| { + match err { + Error::Terminated => { + set_progress(ImaginateStatus::Terminated); + } + err => { + error!("{err}"); + set_progress(ImaginateStatus::Failed(err.to_string())); + } + }; + Image::empty() + }) +} + +#[cfg(feature = "imaginate")] +async fn imaginate_maybe_fail<'a, P: Pixel, F: Fn(ImaginateStatus)>( + image: Image

, + host_name: &str, + set_progress: F, + controller: &ImaginateController, + seed: impl Future, + res: impl Future>, + samples: impl Future, + sampling_method: impl Future, + prompt_guidance: impl Future, + prompt: impl Future, + negative_prompt: impl Future, + adapt_input_image: impl Future, + image_creativity: impl Future, + _masking_layer: impl Future>>, + _inpaint: impl Future, + _mask_blur: impl Future, + _mask_starting_fill: impl Future, + improve_faces: impl Future, + tiling: impl Future, +) -> Result, Error> { + set_progress(ImaginateStatus::Beginning); + + let base_url: Url = parse_url(host_name)?; + + let client = new_client()?; + + let sampler_index = sampling_method.await; + let sampler_index = sampler_index.api_value(); + + let res = res.await.unwrap_or_else(|| { + let (width, height) = pick_safe_imaginate_resolution((image.width as _, image.height as _)); + DVec2::new(width as _, height as _) + }); + let common_request_data = ImaginateCommonImageRequest { + prompt: prompt.await, + seed: seed.await, + steps: samples.await, + cfg_scale: prompt_guidance.await, + width: res.x, + height: res.y, + restore_faces: improve_faces.await, + tiling: tiling.await, + negative_prompt: negative_prompt.await, + sampler_index, + }; + let request_builder = if adapt_input_image.await { + let base64_data = image_to_base64(image)?; + let request_data = ImaginateImageToImageRequest { + common: common_request_data, + override_settings: Default::default(), + + init_images: vec![base64_data], + denoising_strength: image_creativity.await * 0.01, + mask: None, + }; + let url = join_url(&base_url, SDAPI_IMAGE_TO_IMAGE)?; + client.post(url).json(&request_data) + } else { + let request_data = ImaginateTextToImageRequest { + common: common_request_data, + override_settings: Default::default(), + }; + let url = join_url(&base_url, SDAPI_TEXT_TO_IMAGE)?; + client.post(url).json(&request_data) + }; + + let request = request_builder.header("Accept", "*/*").build().map_err(Error::RequestBuild)?; + + let (response_future, abort_handle) = futures::future::abortable(client.execute(request)); + controller.set_termination_handle(Box::new(ImaginateFutureAbortHandle(abort_handle))); + + let progress_url = join_url(&base_url, SDAPI_PROGRESS)?; + + futures::pin_mut!(response_future); + + let response = loop { + let progress_request = new_get_request(&client, progress_url.clone())?; + let progress_response_future = client.execute(progress_request).and_then(|response| response.json()); + + futures::pin_mut!(progress_response_future); + + response_future = match futures::future::select(response_future, progress_response_future).await { + Either::Left((response, _)) => break response, + Either::Right((progress, response_future)) => { + if let Ok(ProgressResponse { progress }) = progress { + set_progress(ImaginateStatus::Generating(progress * 100.)); + } + response_future + } + }; + }; + + let response = match response { + Ok(response) => response.and_then(reqwest::Response::error_for_status).map_err(Error::Request)?, + Err(_aborted) => { + set_progress(ImaginateStatus::Terminating); + let url = join_url(&base_url, SDAPI_TERMINATE)?; + let request = client.post(url).build().map_err(Error::RequestBuild)?; + // The user probably doesn't really care if the server side was really aborted or if there was an network error. + // So we fool them that the request was terminated if the termination request in reality failed. + let _ = client.execute(request).await.and_then(reqwest::Response::error_for_status).map_err(Error::TerminationFailed)?; + return Err(Error::Terminated); + } + }; + + set_progress(ImaginateStatus::Uploading); + + let ImageResponse { images } = response.json().await.map_err(Error::ResponseFormat)?; + + let result = images.into_iter().next().ok_or(Error::NoImage).and_then(base64_to_image)?; + + set_progress(ImaginateStatus::ReadyDone); + + Ok(result) +} + +fn image_to_base64(image: Image

) -> Result { + use base64::prelude::*; + + let Image { width, height, data } = image; + + fn cast_with_f32>(data: Vec, width: u32, height: u32) -> Result + where + DynamicImage: From>>, + { + ImageBuffer::>::from_raw(width, height, bytemuck::cast_vec(data)) + .ok_or(Error::InconsistentImageSize) + .map(Into::into) + } + + let image: DynamicImage = match TypeId::of::

() { + id if id == TypeId::of::() => cast_with_f32::<_, image::Rgba>(data, width, height)? + // we need to do this cast, because png does not support rgba32f + .to_rgba16().into(), + id if id == TypeId::of::() => cast_with_f32::<_, image::Luma>(data, width, height)? + // we need to do this cast, because png does not support luma32f + .to_luma16().into(), + _ => return Err(Error::UnsupportedPixelType(core::any::type_name::

())), + }; + + let mut png_data = std::io::Cursor::new(vec![]); + image.write_to(&mut png_data, ImageOutputFormat::Png).map_err(Error::ImageEncode)?; + Ok(BASE64_STANDARD.encode(png_data.into_inner())) +} + +fn base64_to_image, P: Pixel>(base64_data: D) -> Result, Error> { + use base64::prelude::*; + + let png_data = BASE64_STANDARD.decode(base64_data).map_err(Error::Base64Decode)?; + let dyn_image = image::load_from_memory_with_format(&png_data, image::ImageFormat::Png).map_err(Error::ImageDecode)?; + let (width, height) = (dyn_image.width(), dyn_image.height()); + + let result_data: Vec

= match TypeId::of::

() { + id if id == TypeId::of::() => bytemuck::cast_vec(dyn_image.into_rgba32f().into_raw()), + id if id == TypeId::of::() => bytemuck::cast_vec(dyn_image.to_luma32f().into_raw()), + _ => return Err(Error::UnsupportedPixelType(core::any::type_name::

())), + }; + + Ok(Image { data: result_data, width, height }) +} + +pub fn pick_safe_imaginate_resolution((width, height): (f64, f64)) -> (u64, u64) { + const MAX_RESOLUTION: u64 = 1000 * 1000; + + // this is the maximum width/height that can be obtained + const MAX_DIMENSION: u64 = (MAX_RESOLUTION / 64) & !63; + + // round the resolution to the nearest multiple of 64 + let [width, height] = [width, height].map(|c| (c.round().clamp(0., MAX_DIMENSION as _) as u64 + 32).max(64) & !63); + let resolution = width * height; + + if resolution > MAX_RESOLUTION { + // scale down the image, so it is smaller than MAX_RESOLUTION + let scale = (MAX_RESOLUTION as f64 / resolution as f64).sqrt(); + let [width, height] = [width, height].map(|c| c as f64 * scale); + + if width < 64.0 { + // the image is extremely wide + (64, MAX_DIMENSION) + } else if height < 64.0 { + // the image is extremely high + (MAX_DIMENSION, 64) + } else { + // round down to a multiple of 64, so that the resolution still is smaller than MAX_RESOLUTION + let [width, height] = [width, height].map(|c| c as u64 & !63); + (width, height) + } + } else { + (width, height) + } +} diff --git a/node-graph/gstd/src/lib.rs b/node-graph/gstd/src/lib.rs index 26830431..c20fab7d 100644 --- a/node-graph/gstd/src/lib.rs +++ b/node-graph/gstd/src/lib.rs @@ -25,3 +25,5 @@ pub mod brush; #[cfg(feature = "wasm")] pub mod wasm_application_io; + +pub mod imaginate; diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 5b260c20..7a9ac2b5 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,8 +1,11 @@ use dyn_any::{DynAny, StaticType}; use glam::{DAffine2, DVec2}; +use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; +use graph_craft::proto::DynFuture; use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample}; use graphene_core::transform::Transform; +use crate::wasm_application_io::WasmEditorApi; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::value::CopiedNode; use graphene_core::{Color, Node}; @@ -414,19 +417,74 @@ fn empty_image<_P: Pixel>(transform: DAffine2, color: _P) -> ImageFrame<_P> { ImageFrame { image, transform } } -#[derive(Debug, Clone, Copy)] -pub struct ImaginateNode { - cached: E, - _p: PhantomData

, +macro_rules! generate_imaginate_node { + ($($val:ident: $t:ident: $o:ty,)*) => { + pub struct ImaginateNode { + editor_api: E, + controller: C, + $($val: $t,)* + cache: std::sync::Arc>>, + } + + impl<'e, P: Pixel, E, C, $($t,)*> ImaginateNode + where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* + E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, WasmEditorApi<'e>>>, + C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, + { + pub fn new(editor_api: E, controller: C, $($val: $t,)* cache: std::sync::Arc>>) -> Self { + Self { editor_api, controller, $($val,)* cache } + } + } + + impl<'i, 'e: 'i, P: Pixel + 'i, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode + where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* + E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, WasmEditorApi<'e>>>, + C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, + { + type Output = DynFuture<'i, ImageFrame

>; + + fn eval(&'i self, frame: ImageFrame

) -> Self::Output { + let controller = self.controller.eval(()); + $(let $val = self.$val.eval(());)* + Box::pin(async move { + let controller: std::pin::Pin>> = controller; + let controller: ImaginateController = controller.await; + if controller.take_regenerate_trigger() { + let editor_api = self.editor_api.eval(()); + let image = super::imaginate::imaginate(frame.image, editor_api, controller, $($val,)*).await; + self.cache.lock().unwrap().clone_from(&image); + return ImageFrame { + image, + ..frame + } + } + let image = self.cache.lock().unwrap().clone(); + ImageFrame { + image, + ..frame + } + }) + } + } + } } -#[node_macro::node_fn(ImaginateNode<_P>)] -fn imaginate<_P: Pixel>(image_frame: ImageFrame<_P>, cached: Option>>) -> ImageFrame<_P> { - let cached_image = cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image_frame.image); - ImageFrame { - image: cached_image, - transform: image_frame.transform, - } +generate_imaginate_node! { + seed: Seed: f64, + res: Res: Option, + samples: Samples: u32, + sampling_method: SamplingMethod: ImaginateSamplingMethod, + prompt_guidance: PromptGuidance: f64, + prompt: Prompt: String, + negative_prompt: NegativePrompt: String, + adapt_input_image: AdaptInputImage: bool, + image_creativity: ImageCreativity: f64, + masking_layer: MaskingLayer: Option>, + inpaint: Inpaint: bool, + mask_blur: MaskBlur: f64, + mask_starting_fill: MaskStartingFill: ImaginateMaskStartingFill, + improve_faces: ImproveFaces: bool, + tiling: Tiling: bool, } #[derive(Debug, Clone, Copy)] diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 51dc646b..4c72e3de 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -3,7 +3,6 @@ pub mod node_registry; #[cfg(test)] mod tests { - use graph_craft::document::value::TaggedValue; use graphene_core::*; use std::borrow::Cow; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index c0e13f20..b39a6236 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,3 +1,4 @@ +use graph_craft::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::ops::IdNode; use graphene_core::quantization::QuantizationChannels; @@ -444,18 +445,59 @@ fn node_registry() -> HashMap, input: WasmEditorApi, output: SurfaceFrame, params: [SurfaceFrame]), - vec![( - NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"), - |args| { - Box::pin(async move { - let node: DowncastBothNode, WasmEditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone()); - let node = >::new(node); - let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - any.into_type_erased() - }) - }, - NodeIOTypes::new(concrete!(()), concrete!(WasmEditorApi), vec![fn_type!(Option, WasmEditorApi)]), - )], + vec![ + ( + NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"), + |args| { + Box::pin(async move { + let node: DowncastBothNode, WasmEditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone()); + let node = >::new(node); + let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); + any.into_type_erased() + }) + }, + NodeIOTypes::new(concrete!(()), concrete!(WasmEditorApi), vec![fn_type!(Option, WasmEditorApi)]), + ), + ( + NodeIdentifier::new("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"), + |args: Vec>| { + Box::pin(async move { + use graphene_std::raster::ImaginateNode; + let cache: ImaginateCache = graphene_std::any::input_node(args.last().unwrap().clone()).eval(()).await; + macro_rules! instanciate_imaginate_node { + ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* cache.into_inner()) }; + } + let node: ImaginateNode = instanciate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,); + let any = graphene_std::any::DynAnyNode::new(ValueNode::new(node)); + any.into_type_erased() + }) + }, + NodeIOTypes::new( + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![ + fn_type!(WasmEditorApi), + fn_type!(ImaginateController), + fn_type!(f64), + fn_type!(Option), + fn_type!(u32), + fn_type!(ImaginateSamplingMethod), + fn_type!(f64), + fn_type!(String), + fn_type!(String), + fn_type!(bool), + fn_type!(f64), + fn_type!(Option>), + fn_type!(bool), + fn_type!(f64), + fn_type!(ImaginateMaskStartingFill), + fn_type!(bool), + fn_type!(bool), + fn_type!(ImaginateCache), + ], + ), + ), + ], async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Image, params: [Image]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame, params: [ImageFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),