diff --git a/Cargo.lock b/Cargo.lock index 5d463a1f0..05f2d740f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,6 +570,7 @@ version = "0.4.0" dependencies = [ "dyn-any", "glam", + "kurbo", "log", "serde", ] @@ -1232,17 +1233,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" -[[package]] -name = "d3d12" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" -dependencies = [ - "bitflags 2.6.0", - "libloading 0.8.4", - "winapi", -] - [[package]] name = "d3d12" version = "0.20.0" @@ -1683,9 +1673,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-types" -version = "0.4.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7f6040d337bd44434ab21fc6509154edf2cece88b23758d9d64654c4e7730b" +checksum = "34fd7136aca682873d859ef34494ab1a7d3f57ecd485ed40eb6437ee8c85aa29" +dependencies = [ + "bytemuck", +] [[package]] name = "fontconfig-parser" @@ -2036,9 +2029,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", @@ -2243,17 +2236,6 @@ dependencies = [ "wgpu-executor", ] -[[package]] -name = "gpu-descriptor" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" -dependencies = [ - "bitflags 2.6.0", - "gpu-descriptor-types 0.1.2", - "hashbrown 0.14.5", -] - [[package]] name = "gpu-descriptor" version = "0.3.0" @@ -2261,19 +2243,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ "bitflags 2.6.0", - "gpu-descriptor-types 0.2.0", + "gpu-descriptor-types", "hashbrown 0.14.5", ] -[[package]] -name = "gpu-descriptor-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "gpu-descriptor-types" version = "0.2.0" @@ -2348,7 +2321,7 @@ dependencies = [ "serde_json", "tokio", "wasm-bindgen", - "wgpu 0.20.1", + "wgpu", "wgpu-executor", ] @@ -2364,7 +2337,7 @@ dependencies = [ "half", "image 0.25.1", "js-sys", - "kurbo 0.11.0 (git+https://github.com/linebender/kurbo.git)", + "kurbo", "log", "node-macro", "num-derive", @@ -2377,6 +2350,7 @@ dependencies = [ "spirv-std", "tokio", "usvg", + "vello", "wasm-bindgen", "web-sys", ] @@ -2417,7 +2391,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu 0.20.1", + "wgpu", "wgpu-executor", "wgpu-types 0.17.0", "winit", @@ -2505,7 +2479,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu 0.20.1", + "wgpu", ] [[package]] @@ -3090,7 +3064,7 @@ dependencies = [ "num-traits", "once_cell", "serde", - "wgpu 0.20.1", + "wgpu", "wgpu-executor", ] @@ -3262,39 +3236,11 @@ dependencies = [ "selectors", ] -[[package]] -name = "kurbo" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "kurbo" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" -dependencies = [ - "arrayvec", - "smallvec", -] - [[package]] name = "kurbo" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" -dependencies = [ - "arrayvec", - "smallvec", -] - -[[package]] -name = "kurbo" -version = "0.11.0" -source = "git+https://github.com/linebender/kurbo.git#c9843e8d4a6cd376922c827055eeb43e653f14d0" dependencies = [ "arrayvec", "serde", @@ -3528,21 +3474,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" -dependencies = [ - "bitflags 2.6.0", - "block", - "core-graphics-types", - "foreign-types 0.5.0", - "log", - "objc", - "paste", -] - [[package]] name = "metal" version = "0.28.0" @@ -3601,26 +3532,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "naga" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" -dependencies = [ - "bit-set", - "bitflags 2.6.0", - "codespan-reporting", - "hexf-parse", - "indexmap 2.2.6", - "log", - "num-traits", - "rustc-hash 1.1.0", - "spirv", - "termcolor", - "thiserror", - "unicode-xid", -] - [[package]] name = "naga" version = "0.20.0" @@ -4187,9 +4098,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -4219,9 +4130,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -4356,7 +4267,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c28d7294093837856bb80ad191cc46a2fcec8a30b43b7a3b0285325f0a917a9" dependencies = [ - "kurbo 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kurbo", "smallvec", ] @@ -4489,7 +4400,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -4498,7 +4409,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -4507,7 +4418,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -4928,10 +4839,11 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.15.6" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea23eedb4d938031b6d4343222444608727a6aa68ec355e13588d9947ffe92" +checksum = "e8b8af39d1f23869711ad4cea5e7835a20daa987f80232f7f2a2374d648ca64d" dependencies = [ + "bytemuck", "font-types", ] @@ -5116,9 +5028,9 @@ dependencies = [ [[package]] name = "resvg" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a15c715c5a88eedff8cd54e2821f39f6ee4345964fd48986c0615d5a24cbe5" +checksum = "c2327ced609dadeed3e9702fec3e6b2ddd208758a9268d13e06566c6101ba533" dependencies = [ "gif", "jpeg-decoder", @@ -5295,16 +5207,16 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustybuzz" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +checksum = "88117946aa1bfb53c2ae0643ceac6506337f44887f8c9fbfb43587b1cc52ba49" dependencies = [ "bitflags 2.6.0", "bytemuck", "smallvec", "ttf-parser 0.20.0", - "unicode-bidi-mirroring 0.1.0", - "unicode-ccc 0.1.2", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", "unicode-properties", "unicode-script", ] @@ -5668,11 +5580,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] -name = "skrifa" -version = "0.15.5" +name = "siphasher" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff28ee3b66d43060ef9a327e0f18e4c1813f194120156b4d4524fac3ba8ce22" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab45fb68b53576a43d4fc0e9ec8ea64e29a4d2cc7f44506964cb75f288222e9" dependencies = [ + "bytemuck", "read-fonts", ] @@ -5959,12 +5878,12 @@ checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "svgtypes" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d7618f12b51be8171a7cfdda1e7a93f79cbc57c4e7adf89a749cf671125241" +checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" dependencies = [ - "kurbo 0.10.4", - "siphasher", + "kurbo", + "siphasher 1.0.1", ] [[package]] @@ -6832,9 +6751,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-bidi-mirroring" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" [[package]] name = "unicode-bidi-mirroring" @@ -6844,9 +6763,9 @@ checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" [[package]] name = "unicode-ccc" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ccc" @@ -6925,22 +6844,22 @@ dependencies = [ [[package]] name = "usvg" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e753216e7c0e49048a0c986ed9ad7284451844a21107374392aaa107ec805c9c" +checksum = "5c704361d822337cfc00387672c7b59eaa72a1f0744f62b2a68aa228a0c6927d" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "data-url", "flate2", "fontdb", "imagesize", - "kurbo 0.9.5", + "kurbo", "log", "pico-args", "roxmltree", - "rustybuzz 0.12.1", + "rustybuzz 0.13.0", "simplecss", - "siphasher", + "siphasher 1.0.1", "strict-num", "svgtypes", "tiny-skia-path", @@ -6985,29 +6904,46 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vello" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a4b96a2d6d6effa67868b4436560e3a767f71f0e043df007587c5d6b2e8b7a" +checksum = "d08e6dd8a058d02acc1dff78487a0fa34c79bd9b85c01123d97b9134bfd48b24" dependencies = [ "bytemuck", "futures-intrusive", + "log", "peniko", "raw-window-handle 0.6.2", "skrifa", + "static_assertions", + "thiserror", "vello_encoding", - "wgpu 0.19.4", + "vello_shaders", + "wgpu", ] [[package]] name = "vello_encoding" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c5b6c6ec113c9b6ee1e1894ccef1b5559373aead718b7442811f2fefff7d423" +checksum = "77306d1ae01e2ef6a2e5dfd6f81696556ddee3e15f6a7422f360759672a0fd90" dependencies = [ "bytemuck", "guillotiere", "peniko", "skrifa", + "smallvec", +] + +[[package]] +name = "vello_shaders" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3f31e3763e09febb449533238551bca5203aff4513324c8558b4c0b1c546fb" +dependencies = [ + "bytemuck", + "naga", + "thiserror", + "vello_encoding", ] [[package]] @@ -7387,31 +7323,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "wgpu" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" -dependencies = [ - "arrayvec", - "cfg-if", - "cfg_aliases 0.1.1", - "js-sys", - "log", - "naga 0.19.2", - "parking_lot", - "profiling", - "raw-window-handle 0.6.2", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core 0.19.4", - "wgpu-hal 0.19.4", - "wgpu-types 0.19.2", -] - [[package]] name = "wgpu" version = "0.20.1" @@ -7424,7 +7335,7 @@ dependencies = [ "document-features", "js-sys", "log", - "naga 0.20.0", + "naga", "parking_lot", "profiling", "raw-window-handle 0.6.2", @@ -7433,37 +7344,11 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core 0.21.1", - "wgpu-hal 0.21.1", + "wgpu-core", + "wgpu-hal", "wgpu-types 0.20.0", ] -[[package]] -name = "wgpu-core" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" -dependencies = [ - "arrayvec", - "bit-vec", - "bitflags 2.6.0", - "cfg_aliases 0.1.1", - "codespan-reporting", - "indexmap 2.2.6", - "log", - "naga 0.19.2", - "once_cell", - "parking_lot", - "profiling", - "raw-window-handle 0.6.2", - "rustc-hash 1.1.0", - "smallvec", - "thiserror", - "web-sys", - "wgpu-hal 0.19.4", - "wgpu-types 0.19.2", -] - [[package]] name = "wgpu-core" version = "0.21.1" @@ -7479,7 +7364,7 @@ dependencies = [ "document-features", "indexmap 2.2.6", "log", - "naga 0.20.0", + "naga", "once_cell", "parking_lot", "profiling", @@ -7488,7 +7373,7 @@ dependencies = [ "smallvec", "thiserror", "web-sys", - "wgpu-hal 0.21.1", + "wgpu-hal", "wgpu-types 0.20.0", ] @@ -7512,56 +7397,12 @@ dependencies = [ "nvtx", "serde", "spirv", + "vello", "web-sys", - "wgpu 0.20.1", + "wgpu", "winit", ] -[[package]] -name = "wgpu-hal" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", - "bitflags 2.6.0", - "block", - "cfg_aliases 0.1.1", - "core-graphics-types", - "d3d12 0.19.0", - "glow", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-allocator", - "gpu-descriptor 0.2.4", - "hassle-rs", - "js-sys", - "khronos-egl", - "libc", - "libloading 0.8.4", - "log", - "metal 0.27.0", - "naga 0.19.2", - "ndk-sys 0.5.0+25.2.9519653", - "objc", - "once_cell", - "parking_lot", - "profiling", - "range-alloc", - "raw-window-handle 0.6.2", - "renderdoc-sys", - "rustc-hash 1.1.0", - "smallvec", - "thiserror", - "wasm-bindgen", - "web-sys", - "wgpu-types 0.19.2", - "winapi", -] - [[package]] name = "wgpu-hal" version = "0.21.1" @@ -7576,20 +7417,20 @@ dependencies = [ "block", "cfg_aliases 0.1.1", "core-graphics-types", - "d3d12 0.20.0", + "d3d12", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", - "gpu-descriptor 0.3.0", + "gpu-descriptor", "hassle-rs", "js-sys", "khronos-egl", "libc", "libloading 0.8.4", "log", - "metal 0.28.0", - "naga 0.20.0", + "metal", + "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", @@ -7618,17 +7459,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wgpu-types" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" -dependencies = [ - "bitflags 2.6.0", - "js-sys", - "web-sys", -] - [[package]] name = "wgpu-types" version = "0.20.0" diff --git a/Cargo.toml b/Cargo.toml index cc8b0d309..6321baa7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,9 +66,9 @@ web-sys = "=0.3.69" winit = "0.29" url = "2.5" tokio = { version = "1.29", features = ["fs", "io-std"] } -vello = "0.1" -resvg = "0.39" -usvg = "0.39" +vello = "0.2" +resvg = "0.41" +usvg = "0.41" rand = { version = "0.8", default-features = false } rand_chacha = "0.3" glam = { version = "0.25", default-features = false, features = ["serde"] } @@ -89,7 +89,7 @@ syn = { version = "2.0", default-features = false, features = [ "full", "derive", ] } -kurbo = { git = "https://github.com/linebender/kurbo.git", features = [ +kurbo = { version = "0.11.0", features = [ "serde", ] } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 4f27ab0fe..6a07fe385 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -20,6 +20,8 @@ gpu = [ "wgpu-executor", "gpu-executor", ] +resvg = ["graphene-std/resvg"] +vello = ["graphene-std/vello", "resvg", "graphene-core/vello"] quantization = [ "graphene-std/quantization", "interpreted-executor/quantization", diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index db4d1a208..62696157a 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -37,6 +37,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderRulers)), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), ]; diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index fd56d2c75..fa6ab03bc 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -45,6 +45,15 @@ impl PreferencesDialogMessageHandler { }) .widget_holder(), ]; + let use_vello = vec![ + TextLabel::new("Renderer").min_width(60).italic(true).widget_holder(), + TextLabel::new("Vello (Experimental)").table_align(true).widget_holder(), + Separator::new(SeparatorType::Unrelated).widget_holder(), + CheckboxInput::new(preferences.use_vello) + .tooltip("Use the experimental Vello renderer (your browser must support WebGPU)") + .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into()) + .widget_holder(), + ]; let imaginate_server_hostname = vec![ TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(), @@ -71,6 +80,7 @@ impl PreferencesDialogMessageHandler { Layout::WidgetLayout(WidgetLayout::new(vec![ LayoutGroup::Row { widgets: zoom_with_scroll }, + LayoutGroup::Row { widgets: use_vello }, LayoutGroup::Row { widgets: imaginate_server_hostname }, LayoutGroup::Row { widgets: imaginate_refresh_frequency }, ])) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index e5ce171f4..7e3d7b2b7 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -118,6 +118,7 @@ pub struct DocumentMessageHandler { #[serde(skip)] node_graph_ptz: HashMap, PTZ>, /// Transform from node graph space to viewport space. + // TODO: Remove this and replace its usages with a derived value from the PTZ stored above #[serde(skip)] node_graph_to_viewport: HashMap, DAffine2>, } @@ -1141,9 +1142,6 @@ impl MessageHandler> for DocumentMessag responses.add(DocumentMessage::UpdateDocumentTransform { transform }); } DocumentMessage::UpdateDocumentTransform { transform } => { - responses.add(DocumentMessage::RenderRulers); - responses.add(DocumentMessage::RenderScrollbars); - if !self.graph_view_overlay_open { self.metadata.document_to_viewport = transform; @@ -1159,8 +1157,6 @@ impl MessageHandler> for DocumentMessag }, }) } - - responses.add(PortfolioMessage::UpdateDocumentWidgets); } DocumentMessage::ZoomCanvasTo100Percent => { responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. }); diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 3d87101d7..eed028280 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -569,7 +569,8 @@ impl MessageHandler> for Gr parent, insert_index, } => { - let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) { + let database = usvg::fontdb::Database::new(); + let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default(), &database) { Ok(t) => t, Err(e) => { responses.add(DocumentMessage::DocumentHistoryBackward); @@ -582,7 +583,7 @@ impl MessageHandler> for Gr }; let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); - import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root)), transform, id, parent, insert_index); + import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root().clone())), transform, id, parent, insert_index); load_network_structure(document_network, document_metadata, collapsed); } GraphOperationMessage::SetNodePosition { node_id, position } => { @@ -715,7 +716,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, modify_inputs.layer_node = Some(layer); match node { usvg::Node::Group(group) => { - for child in &group.children { + for child in group.children() { import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), LayerNodeIdentifier::new_unchecked(layer), -1); } modify_inputs.layer_node = Some(layer); @@ -723,58 +724,46 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, usvg::Node::Path(path) => { let subpaths = convert_usvg_path(path); let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default(); - let transformed_bounds = subpaths - .iter() - .filter_map(|subpath| subpath.bounding_box_with_transform(transform * usvg_transform(node.abs_transform()))) - .reduce(Quad::combine_bounds) - .unwrap_or_default(); modify_inputs.insert_vector_data(subpaths, layer); modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| { transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform())); }); let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); - let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]); - apply_usvg_fill( - &path.fill, - modify_inputs, - transform * usvg_transform(node.abs_transform()), - bounds_transform, - transformed_bound_transform, - ); - apply_usvg_stroke(&path.stroke, modify_inputs); + apply_usvg_fill(path.fill(), modify_inputs, transform * usvg_transform(node.abs_transform()), bounds_transform); + apply_usvg_stroke(path.stroke(), modify_inputs); } usvg::Node::Image(_image) => { warn!("Skip image") } usvg::Node::Text(text) => { let font = Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_core::consts::DEFAULT_FONT_STYLE.to_string()); - modify_inputs.insert_text(text.chunks.iter().map(|chunk| chunk.text.clone()).collect(), font, 24., layer); + modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, 24., layer); modify_inputs.fill_set(Fill::Solid(Color::BLACK)); } } } -fn apply_usvg_stroke(stroke: &Option, modify_inputs: &mut ModifyInputsContext) { +fn apply_usvg_stroke(stroke: Option<&usvg::Stroke>, modify_inputs: &mut ModifyInputsContext) { if let Some(stroke) = stroke { - if let usvg::Paint::Color(color) = &stroke.paint { + if let usvg::Paint::Color(color) = &stroke.paint() { modify_inputs.stroke_set(Stroke { - color: Some(usvg_color(*color, stroke.opacity.get())), - weight: stroke.width.get() as f64, - dash_lengths: stroke.dasharray.as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(), - dash_offset: stroke.dashoffset as f64, - line_cap: match stroke.linecap { + color: Some(usvg_color(*color, stroke.opacity().get())), + weight: stroke.width().get() as f64, + dash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(), + dash_offset: stroke.dashoffset() as f64, + line_cap: match stroke.linecap() { usvg::LineCap::Butt => LineCap::Butt, usvg::LineCap::Round => LineCap::Round, usvg::LineCap::Square => LineCap::Square, }, - line_join: match stroke.linejoin { + line_join: match stroke.linejoin() { usvg::LineJoin::Miter => LineJoin::Miter, usvg::LineJoin::MiterClip => LineJoin::Miter, usvg::LineJoin::Round => LineJoin::Round, usvg::LineJoin::Bevel => LineJoin::Bevel, }, - line_join_miter_limit: stroke.miterlimit.get() as f64, + line_join_miter_limit: stroke.miterlimit().get() as f64, }) } else { warn!("Skip non-solid stroke") @@ -782,25 +771,27 @@ fn apply_usvg_stroke(stroke: &Option, modify_inputs: &mut ModifyIn } } -fn apply_usvg_fill(fill: &Option, modify_inputs: &mut ModifyInputsContext, transform: DAffine2, bounds_transform: DAffine2, transformed_bound_transform: DAffine2) { +fn apply_usvg_fill(fill: Option<&usvg::Fill>, modify_inputs: &mut ModifyInputsContext, transform: DAffine2, bounds_transform: DAffine2) { if let Some(fill) = &fill { - modify_inputs.fill_set(match &fill.paint { - usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity.get())), + modify_inputs.fill_set(match &fill.paint() { + usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())), usvg::Paint::LinearGradient(linear) => { - let local = [DVec2::new(linear.x1 as f64, linear.y1 as f64), DVec2::new(linear.x2 as f64, linear.y2 as f64)]; + let local = [DVec2::new(linear.x1() as f64, linear.y1() as f64), DVec2::new(linear.x2() as f64, linear.y2() as f64)]; - let to_doc_transform = if linear.base.units == usvg::Units::UserSpaceOnUse { - transform - } else { - transformed_bound_transform - }; - let to_doc = to_doc_transform * usvg_transform(linear.transform); + // TODO: fix this + // let to_doc_transform = if linear.base.units() == usvg::Units::UserSpaceOnUse { + // transform + // } else { + // transformed_bound_transform + // }; + let to_doc_transform = transform; + let to_doc = to_doc_transform * usvg_transform(linear.transform()); let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])]; let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])]; let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])]; - let stops = linear.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(); + let stops = linear.stops().iter().map(|stop| (stop.offset().get() as f64, usvg_color(stop.color(), stop.opacity().get()))).collect(); let stops = GradientStops(stops); Fill::Gradient(Gradient { @@ -812,20 +803,22 @@ fn apply_usvg_fill(fill: &Option, modify_inputs: &mut ModifyInputsCo }) } usvg::Paint::RadialGradient(radial) => { - let local = [DVec2::new(radial.cx as f64, radial.cy as f64), DVec2::new(radial.fx as f64, radial.fy as f64)]; + let local = [DVec2::new(radial.cx() as f64, radial.cy() as f64), DVec2::new(radial.fx() as f64, radial.fy() as f64)]; - let to_doc_transform = if radial.base.units == usvg::Units::UserSpaceOnUse { - transform - } else { - transformed_bound_transform - }; - let to_doc = to_doc_transform * usvg_transform(radial.transform); + // TODO: fix this + // let to_doc_transform = if radial.base.units == usvg::Units::UserSpaceOnUse { + // transform + // } else { + // transformed_bound_transform + // }; + let to_doc_transform = transform; + let to_doc = to_doc_transform * usvg_transform(radial.transform()); let document = [to_doc.transform_point2(local[0]), to_doc.transform_point2(local[1])]; let layer = [transform.inverse().transform_point2(document[0]), transform.inverse().transform_point2(document[1])]; let [start, end] = [bounds_transform.inverse().transform_point2(layer[0]), bounds_transform.inverse().transform_point2(layer[1])]; - let stops = radial.stops.iter().map(|stop| (stop.offset.get() as f64, usvg_color(stop.color, stop.opacity.get()))).collect(); + let stops = radial.stops().iter().map(|stop| (stop.offset().get() as f64, usvg_color(stop.color(), stop.opacity().get()))).collect(); let stops = GradientStops(stops); Fill::Gradient(Gradient { diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs index 07ea75a79..8a0091db0 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs @@ -1311,15 +1311,16 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Create Gpu Surface".to_string(), + manual_composition: Some(concrete!(Footprint)), inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode<_>")), ..Default::default() }, DocumentNode { name: "Cache".to_string(), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Footprint)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")), ..Default::default() }, ] @@ -2789,16 +2790,17 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc")), skip_deduplication: true, ..Default::default() }, DocumentNode { name: "Cache".to_string(), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Footprint)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")), ..Default::default() }, // TODO: Add conversion step @@ -2806,6 +2808,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc> for PortfolioMes self.persistent_data.imaginate.poll_server_check(); responses.add(PropertiesPanelMessage::Refresh); } - PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()), + PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()), PortfolioMessage::ImaginateServerHostname => { self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname); } @@ -496,6 +496,7 @@ impl MessageHandler> for PortfolioMes document.set_auto_save_state(document_is_auto_saved); document.set_save_state(document_is_saved); + self.load_document(document, document_id, responses); } PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => { @@ -640,6 +641,9 @@ impl MessageHandler> for PortfolioMes .collect::>(); responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents }); } + PortfolioMessage::UpdateVelloPreference => { + self.persistent_data.use_vello = preferences.use_vello; + } } } diff --git a/editor/src/messages/portfolio/utility_types.rs b/editor/src/messages/portfolio/utility_types.rs index f428d419d..14f0db076 100644 --- a/editor/src/messages/portfolio/utility_types.rs +++ b/editor/src/messages/portfolio/utility_types.rs @@ -4,6 +4,7 @@ use graphene_std::{imaginate::ImaginatePersistentData, text::FontCache}; pub struct PersistentData { pub font_cache: FontCache, pub imaginate: ImaginatePersistentData, + pub use_vello: bool, } #[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] diff --git a/editor/src/messages/preferences/preferences_message.rs b/editor/src/messages/preferences/preferences_message.rs index e3702b975..44f1c6861 100644 --- a/editor/src/messages/preferences/preferences_message.rs +++ b/editor/src/messages/preferences/preferences_message.rs @@ -7,6 +7,7 @@ pub enum PreferencesMessage { ResetToDefaults, ImaginateRefreshFrequency { seconds: f64 }, + UseVello { use_vello: bool }, ImaginateServerHostname { hostname: String }, ModifyLayout { zoom_with_scroll: bool }, } diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 24364bfd2..0031943f2 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -1,29 +1,35 @@ use crate::messages::input_mapper::key_mapping::MappingVariant; use crate::messages::prelude::*; -use graph_craft::imaginate_input::ImaginatePreferences; +use graph_craft::wasm_application_io::EditorPreferences; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)] pub struct PreferencesMessageHandler { pub imaginate_server_hostname: String, pub imaginate_refresh_frequency: f64, pub zoom_with_scroll: bool, + pub use_vello: bool, } impl PreferencesMessageHandler { - pub fn get_imaginate_preferences(&self) -> ImaginatePreferences { - ImaginatePreferences { - host_name: self.imaginate_server_hostname.clone(), + pub fn editor_preferences(&self) -> EditorPreferences { + EditorPreferences { + imaginate_hostname: self.imaginate_server_hostname.clone(), + use_vello: self.use_vello, } } } impl Default for PreferencesMessageHandler { fn default() -> Self { - let ImaginatePreferences { host_name } = Default::default(); + let EditorPreferences { + imaginate_hostname: host_name, + use_vello, + } = Default::default(); Self { imaginate_server_hostname: host_name, imaginate_refresh_frequency: 1., zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll), + use_vello, } } } @@ -37,7 +43,8 @@ impl MessageHandler for PreferencesMessageHandler { responses.add(PortfolioMessage::ImaginateServerHostname); responses.add(PortfolioMessage::ImaginateCheckServerStatus); - responses.add(PortfolioMessage::ImaginatePreferences); + responses.add(PortfolioMessage::EditorPreferences); + responses.add(PortfolioMessage::UpdateVelloPreference); responses.add(PreferencesMessage::ModifyLayout { zoom_with_scroll: self.zoom_with_scroll, }); @@ -53,7 +60,12 @@ impl MessageHandler for PreferencesMessageHandler { PreferencesMessage::ImaginateRefreshFrequency { seconds } => { self.imaginate_refresh_frequency = seconds; responses.add(PortfolioMessage::ImaginateCheckServerStatus); - responses.add(PortfolioMessage::ImaginatePreferences); + responses.add(PortfolioMessage::EditorPreferences); + } + PreferencesMessage::UseVello { use_vello } => { + self.use_vello = use_vello; + responses.add(PortfolioMessage::UpdateVelloPreference); + responses.add(PortfolioMessage::EditorPreferences); } PreferencesMessage::ImaginateServerHostname { hostname } => { let initial = hostname.clone(); @@ -68,7 +80,7 @@ impl MessageHandler for PreferencesMessageHandler { self.imaginate_server_hostname = hostname; responses.add(PortfolioMessage::ImaginateServerHostname); responses.add(PortfolioMessage::ImaginateCheckServerStatus); - responses.add(PortfolioMessage::ImaginatePreferences); + responses.add(PortfolioMessage::EditorPreferences); } 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 cbfab3802..e6895d775 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -9,8 +9,8 @@ use graph_craft::concrete; 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::proto::GraphErrors; +use graph_craft::wasm_application_io::EditorPreferences; use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_core::memo::IORecord; use graphene_core::raster::ImageFrame; @@ -36,8 +36,9 @@ pub struct NodeRuntime { executor: DynamicExecutor, receiver: Receiver, sender: InternalNodeGraphUpdateSender, - imaginate_preferences: ImaginatePreferences, - recompile_graph: bool, + editor_preferences: EditorPreferences, + old_graph: Option, + update_thumbnails: bool, editor_api: Arc, node_graph_errors: GraphErrors, @@ -60,7 +61,7 @@ pub enum NodeRuntimeMessage { GraphUpdate(NodeNetwork), ExecutionRequest(ExecutionRequest), FontCacheUpdate(FontCache), - ImaginatePreferencesUpdate(ImaginatePreferences), + EditorPreferencesUpdate(EditorPreferences), } #[derive(Default, Debug, Clone)] @@ -127,12 +128,13 @@ impl NodeRuntime { executor: DynamicExecutor::default(), receiver, sender: InternalNodeGraphUpdateSender(sender.clone()), - imaginate_preferences: ImaginatePreferences::default(), - recompile_graph: true, + editor_preferences: EditorPreferences::default(), + old_graph: None, + update_thumbnails: true, editor_api: WasmEditorApi { font_cache: FontCache::default(), - imaginate_preferences: Box::new(ImaginatePreferences::default()), + editor_preferences: Box::new(EditorPreferences::default()), node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)), application_io: None, @@ -154,7 +156,7 @@ impl NodeRuntime { // TODO: Currently we still render the document after we submit the node graph execution request. This should be avoided in the future. let mut font = None; - let mut imaginate = None; + let mut preferences = None; let mut graph = None; let mut execution = None; for request in self.receiver.try_iter() { @@ -162,10 +164,10 @@ impl NodeRuntime { NodeRuntimeMessage::GraphUpdate(_) => graph = Some(request), NodeRuntimeMessage::ExecutionRequest(_) => execution = Some(request), NodeRuntimeMessage::FontCacheUpdate(_) => font = Some(request), - NodeRuntimeMessage::ImaginatePreferencesUpdate(_) => imaginate = Some(request), + NodeRuntimeMessage::EditorPreferencesUpdate(_) => preferences = Some(request), } } - let requests = [font, imaginate, graph, execution].into_iter().flatten(); + let requests = [font, preferences, graph, execution].into_iter().flatten(); for request in requests { match request { @@ -174,38 +176,46 @@ impl NodeRuntime { font_cache, application_io: self.editor_api.application_io.clone(), node_graph_message_sender: Box::new(self.sender.clone()), - imaginate_preferences: Box::new(self.imaginate_preferences.clone()), + editor_preferences: Box::new(self.editor_preferences.clone()), } .into(); - self.recompile_graph = true; + if let Some(graph) = self.old_graph.clone() { + // We ignore this result as compilation errors should have been reported in an earlier iteration + let _ = self.update_network(graph).await; + } } - NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => { + NodeRuntimeMessage::EditorPreferencesUpdate(preferences) => { + self.editor_preferences = preferences.clone(); self.editor_api = WasmEditorApi { font_cache: self.editor_api.font_cache.clone(), application_io: self.editor_api.application_io.clone(), node_graph_message_sender: Box::new(self.sender.clone()), - imaginate_preferences: Box::new(preferences), + editor_preferences: Box::new(preferences), } .into(); - self.recompile_graph = true; + if let Some(graph) = self.old_graph.clone() { + // We ignore this result as compilation errors should have been reported in an earlier iteration + let _ = self.update_network(graph).await; + } } NodeRuntimeMessage::GraphUpdate(graph) => { + self.old_graph = Some(graph.clone()); self.node_graph_errors.clear(); let result = self.update_network(graph).await; + self.update_thumbnails = true; self.sender.send_generation_response(CompilationResponse { result, resolved_types: self.resolved_types.clone(), node_graph_errors: self.node_graph_errors.clone(), }); - self.recompile_graph = true; } NodeRuntimeMessage::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => { let transform = render_config.viewport.transform; let result = self.execute_network(render_config).await; - let mut responses = VecDeque::new(); - self.process_monitor_nodes(&mut responses); + self.process_monitor_nodes(&mut responses, self.update_thumbnails); + self.update_thumbnails = false; self.sender.send_execution_response(ExecutionResponse { execution_id, @@ -227,7 +237,7 @@ impl NodeRuntime { application_io: Some(WasmApplicationIo::new().await.into()), font_cache: self.editor_api.font_cache.clone(), node_graph_message_sender: Box::new(self.sender.clone()), - imaginate_preferences: Box::new(ImaginatePreferences::default()), + editor_preferences: Box::new(self.editor_preferences.clone()), } .into(); } @@ -274,7 +284,7 @@ impl NodeRuntime { } /// Updates state data - pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque) { + pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque, update_thumbnails: bool) { // TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n)) self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); @@ -290,15 +300,15 @@ impl NodeRuntime { let Some(introspected_data) = self.executor.introspect(monitor_node_path).flatten() else { // TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds) #[cfg(debug_assertions)] - warn!("Failed to introspect monitor node"); + warn!("Failed to introspect monitor node {:?}", self.executor.introspect(monitor_node_path)); continue; }; if let Some(io) = introspected_data.downcast_ref::>() { - Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses) + Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses, update_thumbnails) } else if let Some(io) = introspected_data.downcast_ref::>() { - Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses) + Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses, update_thumbnails) } else if let Some(record) = introspected_data.downcast_ref::>() { // Insert the vector modify if we are dealing with vector data self.vector_modify.insert(parent_network_node_id, record.output.clone()); @@ -330,6 +340,7 @@ impl NodeRuntime { parent_network_node_id: NodeId, graphic_element: &impl GraphicElementRendered, responses: &mut VecDeque, + update_thumbnails: bool, ) { let click_targets = click_targets.entry(parent_network_node_id).or_default(); click_targets.clear(); @@ -337,6 +348,10 @@ impl NodeRuntime { // RENDER THUMBNAIL + if !update_thumbnails { + return; + } + let bounds = graphic_element.bounding_box(DAffine2::IDENTITY); // Render the thumbnail from a `GraphicElement` into an SVG string @@ -429,10 +444,10 @@ 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) { + pub fn update_editor_preferences(&self, editor_preferences: EditorPreferences) { self.sender - .send(NodeRuntimeMessage::ImaginatePreferencesUpdate(imaginate_preferences)) - .expect("Failed to send imaginate preferences"); + .send(NodeRuntimeMessage::EditorPreferencesUpdate(editor_preferences)) + .expect("Failed to send editor preferences"); } pub fn introspect_node_in_network Option, F2: FnOnce(&T) -> U>( @@ -560,15 +575,13 @@ impl NodeGraphExecutor { let ExecutionResponse { execution_id, result, - responses: existing_responses, new_click_targets, + responses: existing_responses, new_vector_modify, new_upstream_transforms, transform, } = execution_response; - responses.extend(existing_responses.into_iter().map(Into::into)); - responses.add(NodeGraphMessage::SendGraph); responses.add(OverlaysMessage::Draw); let node_graph_output = match result { @@ -581,6 +594,7 @@ impl NodeGraphExecutor { } }; + responses.extend(existing_responses.into_iter().map(Into::into)); document.metadata.update_transforms(new_upstream_transforms); document.metadata.update_from_monitor(new_click_targets, new_vector_modify); @@ -606,6 +620,7 @@ impl NodeGraphExecutor { return Err("Node graph evaluation failed".to_string()); }; + responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors }); } NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => { @@ -634,17 +649,17 @@ impl NodeGraphExecutor { fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { match node_graph_output { - TaggedValue::SurfaceFrame(SurfaceFrame { surface_id: _, transform: _ }) => { + TaggedValue::SurfaceFrame(SurfaceFrame { .. }) => { // TODO: Reimplement this now that document-legacy is gone } TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => { // Send to frontend responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); responses.add(DocumentMessage::RenderScrollbars); + responses.add(DocumentMessage::RenderRulers); } TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => { // Send to frontend - responses.add(DocumentMessage::RenderScrollbars); let matrix = frame .transform .to_cols_array() @@ -655,9 +670,11 @@ impl NodeGraphExecutor { r#"
"#, - 1920, 1080, matrix, frame.surface_id.0 + frame.resolution.x, frame.resolution.y, matrix, frame.surface_id.0 ); responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); + responses.add(DocumentMessage::RenderScrollbars); + responses.add(DocumentMessage::RenderRulers); } TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses), TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses), diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index c12ea5637..a87530862 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -22,6 +22,8 @@ crate-type = ["cdylib", "rlib"] # Local dependencies editor = { path = "../../editor", package = "graphite-editor", features = [ "gpu", + "resvg", + "vello", ] } # Workspace dependencies diff --git a/libraries/bezier-rs/Cargo.toml b/libraries/bezier-rs/Cargo.toml index 176561ca1..b792acda3 100644 --- a/libraries/bezier-rs/Cargo.toml +++ b/libraries/bezier-rs/Cargo.toml @@ -21,5 +21,6 @@ glam = { version = "0.25", features = ["serde"] } dyn-any = { version = "0.3.0", path = "../dyn-any", optional = true } # Optional workspace dependencies +kurbo = { workspace = true, optional = true } serde = { workspace = true, optional = true } log = { workspace = true, optional = true } diff --git a/libraries/bezier-rs/src/bezier/mod.rs b/libraries/bezier-rs/src/bezier/mod.rs index b1202b814..c6eeb6ccd 100644 --- a/libraries/bezier-rs/src/bezier/mod.rs +++ b/libraries/bezier-rs/src/bezier/mod.rs @@ -124,7 +124,7 @@ unsafe impl dyn_any::StaticType for BezierHandles { pub struct Bezier { /// Start point of the bezier curve. pub start: DVec2, - /// Start point of the bezier curve. + /// End point of the bezier curve. pub end: DVec2, /// Handles of the bezier curve. pub handles: BezierHandles, diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index cd83a792d..69e3669eb 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -343,6 +343,27 @@ impl Subpath { subpath } + + #[cfg(feature = "kurbo")] + pub fn to_vello_path(&self, transform: glam::DAffine2, path: &mut kurbo::BezPath) { + use crate::BezierHandles; + + let to_point = |p: DVec2| { + let p = transform.transform_point2(p); + kurbo::Point::new(p.x, p.y) + }; + path.move_to(to_point(self.iter().next().unwrap().start)); + for segment in self.iter() { + match segment.handles { + BezierHandles::Linear => path.line_to(to_point(segment.end)), + BezierHandles::Quadratic { handle } => path.quad_to(to_point(handle), to_point(segment.end)), + BezierHandles::Cubic { handle_start, handle_end } => path.curve_to(to_point(handle_start), to_point(handle_end), to_point(segment.end)), + } + } + if self.closed { + path.close_path(); + } + } } pub fn solve_spline_first_handle(points: &[DVec2]) -> Vec { diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index 4b10809a4..c1a0372db 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -14,6 +14,7 @@ nightly = [] alloc = ["dyn-any", "bezier-rs"] type_id_logging = [] wasm = ["web-sys"] +vello = ["dep:vello", "bezier-rs/kurbo"] std = [ "dyn-any", "dyn-any/std", @@ -57,6 +58,7 @@ rand_chacha = { workspace = true, optional = true } bezier-rs = { workspace = true, optional = true } kurbo = { workspace = true, optional = true } base64 = { workspace = true, optional = true } +vello = { workspace = true, optional = true } specta = { workspace = true, optional = true } rustybuzz = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index f78277f6f..0ff4d5ff1 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -10,7 +10,7 @@ use core::future::Future; use core::hash::{Hash, Hasher}; use core::pin::Pin; use core::ptr::addr_of; -use glam::DAffine2; +use glam::{DAffine2, UVec2}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -26,6 +26,7 @@ impl core::fmt::Display for SurfaceId { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SurfaceFrame { pub surface_id: SurfaceId, + pub resolution: UVec2, pub transform: DAffine2, } @@ -51,11 +52,23 @@ unsafe impl StaticType for SurfaceFrame { type Static = SurfaceFrame; } -impl From> for SurfaceFrame { +pub trait Size { + fn size(&self) -> UVec2; +} + +#[cfg(target_arch = "wasm32")] +impl Size for web_sys::HtmlCanvasElement { + fn size(&self) -> UVec2 { + UVec2::new(self.width(), self.height()) + } +} + +impl From> for SurfaceFrame { fn from(x: SurfaceHandleFrame) -> Self { Self { surface_id: x.surface_handle.surface_id, transform: x.transform, + resolution: x.surface_handle.surface.size(), } } } @@ -70,6 +83,12 @@ pub struct SurfaceHandle { // #[cfg(target_arch = "wasm32")] // unsafe impl Sync for SurfaceHandle {} +impl Size for SurfaceHandle { + fn size(&self) -> UVec2 { + self.surface.size() + } +} + unsafe impl StaticType for SurfaceHandle { type Static = SurfaceHandle; } @@ -162,8 +181,9 @@ impl NodeGraphUpdateSender for std::sync::Mutex { } } -pub trait GetImaginatePreferences { - fn get_host_name(&self) -> &str; +pub trait GetEditorPreferences { + fn hostname(&self) -> &str; + fn use_vello(&self) -> bool; } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -196,10 +216,14 @@ impl NodeGraphUpdateSender for Logger { struct DummyPreferences; -impl GetImaginatePreferences for DummyPreferences { - fn get_host_name(&self) -> &str { +impl GetEditorPreferences for DummyPreferences { + fn hostname(&self) -> &str { "dummy_endpoint" } + + fn use_vello(&self) -> bool { + false + } } pub struct EditorApi { @@ -208,8 +232,8 @@ pub struct EditorApi { /// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web). pub application_io: Option>, pub node_graph_message_sender: Box, - /// Imaginate preferences made available to the graph through the [`WasmEditorApi`]. - pub imaginate_preferences: Box, + /// Editor preferences made available to the graph through the [`WasmEditorApi`]. + pub editor_preferences: Box, } impl Eq for EditorApi {} @@ -220,7 +244,7 @@ impl Default for EditorApi { font_cache: FontCache::default(), application_io: None, node_graph_message_sender: Box::new(Logger), - imaginate_preferences: Box::new(DummyPreferences), + editor_preferences: Box::new(DummyPreferences), } } } @@ -230,7 +254,7 @@ impl Hash for EditorApi { self.font_cache.hash(state); self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state); (self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state); - (self.imaginate_preferences.as_ref() as *const dyn GetImaginatePreferences).hash(state); + (self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state); } } @@ -239,7 +263,7 @@ impl PartialEq for EditorApi { self.font_cache == other.font_cache && self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) && std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _) - && std::ptr::eq(self.imaginate_preferences.as_ref() as *const _, other.imaginate_preferences.as_ref() as *const _) + && std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _) } } diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 149b3902d..6b714d503 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -1,6 +1,5 @@ use crate::application_io::SurfaceHandleFrame; use crate::raster::{BlendMode, ImageFrame}; -use crate::renderer::GraphicElementRendered; use crate::transform::Footprint; use crate::vector::VectorData; use crate::{Color, Node, SurfaceFrame}; @@ -9,7 +8,7 @@ use dyn_any::{DynAny, StaticType}; use node_macro::node_fn; use core::ops::{Deref, DerefMut}; -use glam::{DAffine2, DVec2, IVec2, UVec2}; +use glam::{DAffine2, IVec2, UVec2}; use web_sys::HtmlCanvasElement; pub mod renderer; @@ -202,19 +201,7 @@ async fn add_artboard + Send>(footprint: Footprint, artboar } impl From> for GraphicElement { - fn from(mut image_frame: ImageFrame) -> Self { - use base64::Engine; - - let image = &image_frame.image; - if !image.data.is_empty() { - let output = image.to_png(); - let preamble = "data:image/png;base64,"; - let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4); - base64_string.push_str(preamble); - base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string); - image_frame.image.base64_string = Some(base64_string); - } - + fn from(image_frame: ImageFrame) -> Self { GraphicElement::ImageFrame(image_frame) } } @@ -237,14 +224,28 @@ impl From>> for GraphicEl fn from(surface: alloc::sync::Arc>) -> Self { let surface_id = surface.surface_handle.surface_id; let transform = surface.transform; - GraphicElement::Surface(SurfaceFrame { surface_id, transform }) + GraphicElement::Surface(SurfaceFrame { + surface_id, + transform, + resolution: UVec2 { + x: surface.surface_handle.surface.width(), + y: surface.surface_handle.surface.height(), + }, + }) } } impl From> for GraphicElement { fn from(surface: SurfaceHandleFrame) -> Self { let surface_id = surface.surface_handle.surface_id; let transform = surface.transform; - GraphicElement::Surface(SurfaceFrame { surface_id, transform }) + GraphicElement::Surface(SurfaceFrame { + surface_id, + transform, + resolution: UVec2 { + x: surface.surface_handle.surface.width(), + y: surface.surface_handle.surface.height(), + }, + }) } } @@ -287,21 +288,4 @@ impl GraphicGroup { transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::new(), }; - - pub fn to_usvg_tree(&self, resolution: UVec2, viewbox: [DVec2; 2]) -> usvg::Tree { - let mut root_node = usvg::Group::default(); - let tree = usvg::Tree { - size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(), - view_box: usvg::ViewBox { - rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(), - aspect: usvg::AspectRatio::default(), - }, - root: root_node.clone(), - }; - - for element in self.iter() { - root_node.children.push(element.to_usvg_node()); - } - tree - } } diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 6f6d11fe7..f3532036e 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -4,6 +4,7 @@ use crate::raster::bbox::Bbox; use crate::raster::{BlendMode, Image, ImageFrame}; use crate::transform::Transform; use crate::uuid::generate_uuid; +use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::PointId; use crate::SurfaceFrame; use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup}; @@ -13,6 +14,8 @@ use bezier_rs::Subpath; use base64::Engine; use glam::{DAffine2, DVec2}; +#[cfg(feature = "vello")] +use vello::*; /// Represents a clickable target for the layer #[derive(Clone, Debug)] @@ -166,7 +169,7 @@ pub enum ImageRenderMode { /// Static state used whilst rendering #[derive(Default)] pub struct RenderParams { - pub view_mode: crate::vector::style::ViewMode, + pub view_mode: ViewMode, pub image_render_mode: ImageRenderMode, pub culling_bounds: Option<[DVec2; 2]>, pub thumbnail: bool, @@ -177,7 +180,7 @@ pub struct RenderParams { } impl RenderParams { - pub fn new(view_mode: crate::vector::style::ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self { + pub fn new(view_mode: ViewMode, image_render_mode: ImageRenderMode, culling_bounds: Option<[DVec2; 2]>, thumbnail: bool, hide_artboards: bool, for_export: bool) -> Self { Self { view_mode, image_render_mode, @@ -203,7 +206,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String { result } -fn to_transform(transform: DAffine2) -> usvg::Transform { +pub fn to_transform(transform: DAffine2) -> usvg::Transform { let cols = transform.to_cols_array(); usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32) } @@ -212,33 +215,14 @@ pub trait GraphicElementRendered { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>; fn add_click_targets(&self, click_targets: &mut Vec); - fn to_usvg_node(&self) -> usvg::Node { - let mut render = SvgRender::new(); - let render_params = RenderParams::new(crate::vector::style::ViewMode::Normal, ImageRenderMode::Base64, None, false, false, false); - self.render_svg(&mut render, &render_params); - render.format_svg(DVec2::ZERO, DVec2::ONE); - let svg = render.svg.to_svg_string(); - - let opt = usvg::Options::default(); - - let tree = usvg::Tree::from_str(&svg, &opt).expect("Failed to parse SVG"); - usvg::Node::Group(Box::new(tree.root.clone())) - } - - fn to_usvg_tree(&self, resolution: glam::UVec2, viewbox: [DVec2; 2]) -> usvg::Tree { - let root = match self.to_usvg_node() { - usvg::Node::Group(root_node) => *root_node, - _ => usvg::Group::default(), - }; - usvg::Tree { - size: usvg::Size::from_wh(resolution.x as f32, resolution.y as f32).unwrap(), - view_box: usvg::ViewBox { - rect: usvg::NonZeroRect::from_ltrb(viewbox[0].x as f32, viewbox[0].y as f32, viewbox[1].x as f32, viewbox[1].y as f32).unwrap(), - aspect: usvg::AspectRatio::default(), - }, - root, - } + #[cfg(feature = "vello")] + fn to_vello_scene(&self, transform: DAffine2) -> Scene { + let mut scene = vello::Scene::new(); + self.render_to_vello(&mut scene, transform); + scene } + #[cfg(feature = "vello")] + fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) {} fn contains_artboard(&self) -> bool { false @@ -283,12 +267,22 @@ impl GraphicElementRendered for GraphicGroup { } } - fn to_usvg_node(&self) -> usvg::Node { - let mut root_node = usvg::Group::default(); + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { + let kurbo_transform = kurbo::Affine::new((transform * self.transform).to_cols_array()); + // TODO: We make the bounding box bigger to accommodate for the stroke width. This should be done in a better way. + let Some(bounds) = self.bounding_box(DAffine2::from_scale(DVec2::splat(1.05))) else { return }; + let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver); + scene.push_layer( + blending, + self.alpha_blending.opacity, + kurbo_transform, + &vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y), + ); for element in self.iter() { - root_node.children.push(element.to_usvg_node()); + element.render_to_vello(scene, transform * self.transform); } - usvg::Node::Group(Box::new(root_node)) + scene.pop_layer(); } fn contains_artboard(&self) -> bool { @@ -335,8 +329,8 @@ impl GraphicElementRendered for VectorData { } fn add_click_targets(&self, click_targets: &mut Vec) { - let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight); - let filled = self.style.fill() != &crate::vector::style::Fill::None; + let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); + let filled = self.style.fill() != &Fill::None; let fill = |mut subpath: bezier_rs::Subpath<_>| { if filled { subpath.set_closed(true); @@ -346,38 +340,81 @@ impl GraphicElementRendered for VectorData { click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath })); } - fn to_usvg_node(&self) -> usvg::Node { - use bezier_rs::BezierHandles; - use usvg::tiny_skia_path::PathBuilder; - let mut builder = PathBuilder::new(); - let vector_data = self; + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { + use crate::vector::style::GradientType; + use vello::peniko; - let transform = to_transform(vector_data.transform); - for subpath in vector_data.stroke_bezier_paths() { - let start = vector_data.transform.transform_point2(subpath[0].anchor); - builder.move_to(start.x as f32, start.y as f32); - for bezier in subpath.iter() { - bezier.apply_transformation(|pos| vector_data.transform.transform_point2(pos)); - let end = bezier.end; - match bezier.handles { - BezierHandles::Linear => builder.line_to(end.x as f32, end.y as f32), - BezierHandles::Quadratic { handle } => builder.quad_to(handle.x as f32, handle.y as f32, end.x as f32, end.y as f32), - BezierHandles::Cubic { handle_start, handle_end } => { - builder.cubic_to(handle_start.x as f32, handle_start.y as f32, handle_end.x as f32, handle_end.y as f32, end.x as f32, end.y as f32) - } - } - } - if subpath.closed { - builder.close() - } + let kurbo_transform = kurbo::Affine::new(transform.to_cols_array()); + let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); + let mut path = kurbo::BezPath::new(); + for (_, subpath) in self.region_bezier_paths() { + subpath.to_vello_path(self.transform, &mut path); + } + + match self.style.fill() { + Fill::Solid(color) => { + let fill = peniko::Brush::Solid(peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64)); + scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path); + } + Fill::Gradient(gradient) => { + let mut stops = peniko::ColorStops::new(); + for &(offset, color) in &gradient.stops.0 { + stops.push(peniko::ColorStop { + offset: offset as f32, + color: peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64), + }); + } + // Compute bounding box of the shape to determine the gradient start and end points + let bounds = self.bounding_box().unwrap_or_default(); + let lerp_bounds = |p: DVec2| bounds[0] + (bounds[1] - bounds[0]) * p; + let start = lerp_bounds(gradient.start); + let end = lerp_bounds(gradient.end); + + let transform = self.transform * gradient.transform; + let start = transform.transform_point2(start); + let end = transform.transform_point2(end); + let fill = peniko::Brush::Gradient(peniko::Gradient { + kind: match gradient.gradient_type { + GradientType::Linear => peniko::GradientKind::Linear { + start: to_point(start), + end: to_point(end), + }, + GradientType::Radial => { + let radius = start.distance(end); + peniko::GradientKind::Radial { + start_center: to_point(start), + start_radius: 0., + end_center: to_point(end), + end_radius: radius as f32, + } + } + }, + stops, + ..Default::default() + }); + scene.fill(peniko::Fill::NonZero, kurbo_transform, &fill, None, &path); + } + Fill::None => (), + }; + + let mut path = kurbo::BezPath::new(); + for (_, subpath) in self.region_bezier_paths() { + subpath.to_vello_path(self.transform, &mut path); + } + + if let Some(stroke) = self.style.stroke() { + let color = match stroke.color { + Some(color) => peniko::Color::rgba(color.r() as f64, color.g() as f64, color.b() as f64, color.a() as f64), + None => peniko::Color::TRANSPARENT, + }; + let stroke = kurbo::Stroke { + width: stroke.weight, + miter_limit: stroke.line_join_miter_limit, + ..Default::default() + }; + scene.stroke(&stroke, kurbo_transform, color, None, &path); } - let path = builder.finish().unwrap(); - let mut path = usvg::Path::new(path.into()); - path.abs_transform = transform; - // TODO: use proper style - path.fill = None; - path.stroke = Some(usvg::Stroke::default()); - usvg::Node::Path(Box::new(path)) } } @@ -458,6 +495,28 @@ impl GraphicElementRendered for Artboard { } } + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { + use vello::peniko; + + // Render background + let color = peniko::Color::rgba(self.background.r() as f64, self.background.g() as f64, self.background.b() as f64, self.background.a() as f64); + let rect = kurbo::Rect::new(self.location.x as f64, self.location.y as f64, self.dimensions.x as f64, self.dimensions.y as f64); + let blend_mode = peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcOver); + + scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::new(transform.to_cols_array()), &rect); + scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), color, None, &rect); + scene.pop_layer(); + + if self.clip { + scene.push_layer(blend_mode, 1., kurbo::Affine::new(transform.to_cols_array()), &rect); + } + self.graphic_group.render_to_vello(scene, transform * self.transform()); + if self.clip { + scene.pop_layer(); + } + } + fn add_click_targets(&self, click_targets: &mut Vec) { let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); subpath.apply_transform(self.graphic_group.transform.inverse()); @@ -486,6 +545,13 @@ impl GraphicElementRendered for crate::ArtboardGroup { } } + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { + for artboard in &self.artboards { + artboard.render_to_vello(scene, transform) + } + } + fn contains_artboard(&self) -> bool { !self.artboards.is_empty() } @@ -511,6 +577,11 @@ impl GraphicElementRendered for SurfaceFrame { render.svg.push(canvas.into()) } + #[cfg(feature = "vello")] + fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2) { + todo!() + } + fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { let bbox = Bbox::from_transform(transform); let aabb = bbox.to_axis_aligned_bbox(); @@ -567,24 +638,24 @@ impl GraphicElementRendered for ImageFrame { click_targets.push(ClickTarget { subpath, stroke_width: 0. }); } - fn to_usvg_node(&self) -> usvg::Node { - let image_frame = self; - if image_frame.image.width * image_frame.image.height == 0 { - return usvg::Node::Group(Box::default()); + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { + use vello::peniko; + + let image = &self.image; + if image.data.is_empty() { + return; } - let png = image_frame.image.to_png(); - usvg::Node::Image(Box::new(usvg::Image { - id: String::new(), - abs_transform: to_transform(image_frame.transform), - visibility: usvg::Visibility::Visible, - view_box: usvg::ViewBox { - rect: usvg::NonZeroRect::from_xywh(0., 0., 1., 1.).unwrap(), - aspect: usvg::AspectRatio::default(), - }, - rendering_mode: usvg::ImageRendering::OptimizeSpeed, - kind: usvg::ImageKind::PNG(png.into()), - bounding_box: None, - })) + let image = vello::peniko::Image { + data: image.to_flat_u8().0.into(), + width: image.width, + height: image.height, + format: peniko::Format::Rgba8, + extend: peniko::Extend::Repeat, + }; + let transform = transform * self.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); + + scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array())); } } @@ -616,12 +687,13 @@ impl GraphicElementRendered for GraphicElement { } } - fn to_usvg_node(&self) -> usvg::Node { + #[cfg(feature = "vello")] + fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2) { match self { - GraphicElement::VectorData(vector_data) => vector_data.to_usvg_node(), - GraphicElement::ImageFrame(image_frame) => image_frame.to_usvg_node(), - GraphicElement::GraphicGroup(graphic_group) => graphic_group.to_usvg_node(), - GraphicElement::Surface(surface) => surface.to_usvg_node(), + GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform), + GraphicElement::ImageFrame(image_frame) => image_frame.render_to_vello(scene, transform), + GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform), + GraphicElement::Surface(surface) => surface.render_to_vello(scene, transform), } } @@ -659,32 +731,6 @@ impl GraphicElementRendered for T { } fn add_click_targets(&self, _click_targets: &mut Vec) {} - - fn to_usvg_node(&self) -> usvg::Node { - let text = self; - usvg::Node::Text(Box::new(usvg::Text { - id: String::new(), - abs_transform: usvg::Transform::identity(), - rendering_mode: usvg::TextRendering::OptimizeSpeed, - writing_mode: usvg::WritingMode::LeftToRight, - chunks: vec![usvg::TextChunk { - text: text.to_string(), - x: None, - y: None, - anchor: usvg::TextAnchor::Start, - spans: vec![], - text_flow: usvg::TextFlow::Linear, - }], - dx: Vec::new(), - dy: Vec::new(), - rotate: Vec::new(), - bounding_box: None, - abs_bounding_box: None, - stroke_bounding_box: None, - abs_stroke_bounding_box: None, - flattened: None, - })) - } } impl GraphicElementRendered for Option { diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 70523174e..4b511d3dd 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -233,6 +233,37 @@ impl core::fmt::Display for BlendMode { } } +#[cfg(feature = "vello")] +impl From for vello::peniko::Mix { + fn from(val: BlendMode) -> Self { + match val { + // Normal group + BlendMode::Normal => vello::peniko::Mix::Normal, + // Darken group + BlendMode::Darken => vello::peniko::Mix::Darken, + BlendMode::Multiply => vello::peniko::Mix::Multiply, + BlendMode::ColorBurn => vello::peniko::Mix::ColorBurn, + // Lighten group + BlendMode::Lighten => vello::peniko::Mix::Lighten, + BlendMode::Screen => vello::peniko::Mix::Screen, + BlendMode::ColorDodge => vello::peniko::Mix::ColorDodge, + // Contrast group + BlendMode::Overlay => vello::peniko::Mix::Overlay, + BlendMode::SoftLight => vello::peniko::Mix::SoftLight, + BlendMode::HardLight => vello::peniko::Mix::HardLight, + // Inversion group + BlendMode::Difference => vello::peniko::Mix::Difference, + BlendMode::Exclusion => vello::peniko::Mix::Exclusion, + // Component group + BlendMode::Hue => vello::peniko::Mix::Hue, + BlendMode::Saturation => vello::peniko::Mix::Saturation, + BlendMode::Color => vello::peniko::Mix::Color, + BlendMode::Luminosity => vello::peniko::Mix::Luminosity, + _ => todo!(), + } + } +} + #[derive(Debug, Clone, Copy, Default)] pub struct LuminanceNode { luminance_calc: LuminanceCalculation, diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index ff5d30bca..ef04c1bf1 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -277,7 +277,7 @@ impl ManipulatorPointId { } /// The type of handle found on a bézier curve. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum HandleType { /// The first handle on a cubic bézier or the only handle on a quadratic bézier. @@ -287,7 +287,7 @@ pub enum HandleType { } /// Represents a primary or end handle found in a particular segment. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct HandleId { pub ty: HandleType, diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index bc30d9c2e..1746c1e5e 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; macro_rules! create_ids { ($($id:ident),*) => { $( - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DynAny)] + #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// A strongly typed ID pub struct $id(u64); diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 5694f0629..f8e3daa07 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -16,6 +16,20 @@ pub struct PointModification { delta: HashMap, } +impl Hash for PointModification { + fn hash(&self, state: &mut H) { + self.add.hash(state); + + let mut remove = self.remove.iter().collect::>(); + remove.sort_unstable(); + remove.hash(state); + + let mut delta = self.delta.iter().map(|(&a, &b)| (a, [b.x.to_bits(), b.y.to_bits()])).collect::>(); + delta.sort_unstable(); + delta.hash(state); + } +} + impl PointModification { /// Apply this modification to the specified [`PointDomain`]. pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) { @@ -90,6 +104,36 @@ pub struct SegmentModification { stroke: HashMap, } +impl Hash for SegmentModification { + fn hash(&self, state: &mut H) { + self.add.hash(state); + + let mut remove = self.remove.iter().collect::>(); + remove.sort_unstable(); + remove.hash(state); + + let mut start_point = self.start_point.iter().map(|(&a, &b)| (a, b)).collect::>(); + start_point.sort_unstable(); + start_point.hash(state); + + let mut end_point = self.end_point.iter().map(|(&a, &b)| (a, b)).collect::>(); + end_point.sort_unstable(); + end_point.hash(state); + + let mut handle_primary = self.handle_primary.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::>(); + handle_primary.sort_unstable(); + handle_primary.hash(state); + + let mut handle_end = self.handle_end.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::>(); + handle_end.sort_unstable(); + handle_end.hash(state); + + let mut stroke = self.stroke.iter().map(|(&a, &b)| (a, b)).collect::>(); + stroke.sort_unstable(); + stroke.hash(state); + } +} + impl SegmentModification { /// Apply this modification to the specified [`SegmentDomain`]. pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) { @@ -245,6 +289,24 @@ pub struct RegionModification { fill: HashMap, } +impl Hash for RegionModification { + fn hash(&self, state: &mut H) { + self.add.hash(state); + + let mut remove = self.remove.iter().collect::>(); + remove.sort_unstable(); + remove.hash(state); + + let mut segment_range = self.segment_range.iter().map(|(&a, b)| (a, (*b.start(), *b.end()))).collect::>(); + segment_range.sort_unstable(); + segment_range.hash(state); + + let mut fill = self.fill.iter().map(|(&a, &b)| (a, b)).collect::>(); + fill.sort_unstable(); + fill.hash(state); + } +} + impl RegionModification { /// Apply this modification to the specified [`RegionDomain`]. pub fn apply(&self, region_domain: &mut RegionDomain) { @@ -398,8 +460,19 @@ impl VectorModification { impl core::hash::Hash for VectorModification { fn hash(&self, state: &mut H) { - // TODO: properly implement (hashing a hashset is difficult because ordering is unstable) - PointId::generate().hash(state); + self.points.hash(state); + + self.segments.hash(state); + + self.regions.hash(state); + + let mut add_g1_continuous = self.add_g1_continuous.iter().copied().collect::>(); + add_g1_continuous.sort_unstable(); + add_g1_continuous.hash(state); + + let mut remove_g1_continuous = self.remove_g1_continuous.iter().copied().collect::>(); + remove_g1_continuous.sort_unstable(); + remove_g1_continuous.hash(state); } } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 16e64ed35..2e5898e53 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -248,9 +248,6 @@ pub struct DocumentNode { /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. #[serde(default)] pub skip_deduplication: bool, - /// Used as a hash of the graph input where applicable. This ensures that proto nodes that depend on the graph's input are always regenerated. - #[serde(default)] - pub world_state_hash: u64, /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. #[serde(skip)] pub original_location: OriginalLocation, @@ -293,7 +290,6 @@ impl Default for DocumentNode { locked: Default::default(), metadata: DocumentNodeMetadata::default(), skip_deduplication: Default::default(), - world_state_hash: Default::default(), original_location: OriginalLocation::default(), } } @@ -392,7 +388,6 @@ impl DocumentNode { construction_args: args, original_location: self.original_location, skip_deduplication: self.skip_deduplication, - world_state_hash: self.world_state_hash, } } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index dfd7fe6d5..2fc2ed4b5 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -76,10 +76,6 @@ macro_rules! tagged_value { x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())), - x if x == TypeId::of::() => { - let frame = *downcast::(input).unwrap(); - Ok(TaggedValue::SurfaceFrame(frame.into())) - } _ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))), } diff --git a/node-graph/graph-craft/src/imaginate_input.rs b/node-graph/graph-craft/src/imaginate_input.rs index f4bf99058..eea05414d 100644 --- a/node-graph/graph-craft/src/imaginate_input.rs +++ b/node-graph/graph-craft/src/imaginate_input.rs @@ -286,27 +286,3 @@ impl std::fmt::Display for ImaginateSamplingMethod { } } } - -#[derive(Clone, Debug, PartialEq, Hash, specta::Type)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -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/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index fa0cdcd06..9a0285693 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -224,9 +224,6 @@ pub struct ProtoNode { pub identifier: ProtoNodeIdentifier, pub original_location: OriginalLocation, pub skip_deduplication: bool, - // TODO: This is a hack, figure out a proper solution - /// Represents a global state on which the node depends. - pub world_state_hash: u64, } impl Default for ProtoNode { @@ -237,7 +234,6 @@ impl Default for ProtoNode { input: ProtoNodeInput::None, original_location: OriginalLocation::default(), skip_deduplication: false, - world_state_hash: 0, } } } @@ -288,7 +284,6 @@ impl ProtoNode { if self.skip_deduplication { self.original_location.path.hash(&mut hasher); } - self.world_state_hash.hash(&mut hasher); std::mem::discriminant(&self.input).hash(&mut hasher); match self.input { ProtoNodeInput::None => (), @@ -317,7 +312,6 @@ impl ProtoNode { ..Default::default() }, skip_deduplication: false, - world_state_hash: 0, } } @@ -451,7 +445,6 @@ impl ProtoNetwork { input, original_location: OriginalLocation { path, ..Default::default() }, skip_deduplication: false, - world_state_hash: 0, }, )); @@ -947,12 +940,12 @@ mod test { assert_eq!( ids, vec![ - NodeId(8751908307531981068), - NodeId(3279077344149194814), - NodeId(532186116905587629), - NodeId(10764326338085309082), - NodeId(18015434340620913446), - NodeId(11801333199647382191) + NodeId(5686040524603683634), + NodeId(13787140740513543798), + NodeId(1280393769237740322), + NodeId(3100442468152897091), + NodeId(14834729712909816752), + NodeId(8678825113056010444) ] ); } diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index faea8e5f9..3c4a46515 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -220,3 +220,32 @@ impl ApplicationIo for WasmApplicationIo { pub type WasmSurfaceHandle = SurfaceHandle; pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; + +#[derive(Clone, Debug, PartialEq, Hash, specta::Type)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EditorPreferences { + pub imaginate_hostname: String, + pub use_vello: bool, +} + +impl graphene_core::application_io::GetEditorPreferences for EditorPreferences { + fn hostname(&self) -> &str { + &self.imaginate_hostname + } + fn use_vello(&self) -> bool { + self.use_vello + } +} + +impl Default for EditorPreferences { + fn default() -> Self { + Self { + imaginate_hostname: "http://localhost:7860/".into(), + use_vello: false, + } + } +} + +unsafe impl dyn_any::StaticType for EditorPreferences { + type Static = EditorPreferences; +} diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 1d6692b64..d73b5983a 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -1,6 +1,6 @@ use graph_craft::document::value::TaggedValue; use graph_craft::graphene_compiler::{Compiler, Executor}; -use graph_craft::imaginate_input::ImaginatePreferences; +use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::{concrete, ProtoNodeIdentifier}; use graph_craft::{document::*, generic}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateSender}; @@ -47,7 +47,7 @@ async fn main() -> Result<(), Box> { font_cache: FontCache::default(), application_io: Some(application_io.into()), node_graph_message_sender: Box::new(UpdateLogger {}), - imaginate_preferences: Box::new(ImaginatePreferences::default()), + editor_preferences: Box::new(EditorPreferences::default()), }); let executor = create_executor(document_string, editor_api)?; let render_config = graphene_core::application_io::RenderConfig::default(); @@ -126,7 +126,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc")), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _, _>")), ..Default::default() }, ] @@ -174,7 +174,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc( let canvas = editor_api.application_io.create_surface(); let surface = unsafe { executor.create_surface(canvas) }.unwrap(); - // log::debug!("id: {surface:?}"); let surface_id = surface.surface_id; let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap(); diff --git a/node-graph/gstd/src/imaginate.rs b/node-graph/gstd/src/imaginate.rs index ae60d21bd..3873adb42 100644 --- a/node-graph/gstd/src/imaginate.rs +++ b/node-graph/gstd/src/imaginate.rs @@ -3,7 +3,8 @@ use core::any::TypeId; use core::future::Future; use futures::{future::Either, TryFutureExt}; use glam::{DVec2, U64Vec2}; -use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginatePreferences, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle}; +use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus, ImaginateTerminationHandle}; +use graph_craft::wasm_application_io::EditorPreferences; use graphene_core::application_io::NodeGraphUpdateMessage; use graphene_core::raster::{Color, Image, Luma, Pixel}; use image::{DynamicImage, ImageBuffer, ImageFormat}; @@ -50,17 +51,19 @@ impl core::fmt::Debug for ImaginatePersistentData { impl Default for ImaginatePersistentData { fn default() -> Self { - let mut status = ImaginateServerStatus::default(); + let server_status = ImaginateServerStatus::default(); #[cfg(not(miri))] - let client = new_client().map_err(|err| status = ImaginateServerStatus::Failed(err.to_string())).ok(); + let mut server_status = server_status; + #[cfg(not(miri))] + let client = new_client().map_err(|err| server_status = ImaginateServerStatus::Failed(err.to_string())).ok(); #[cfg(miri)] let client = None; - let ImaginatePreferences { host_name } = Default::default(); + let EditorPreferences { imaginate_hostname: host_name, .. } = Default::default(); Self { pending_server_check: None, host_name: parse_url(&host_name).unwrap(), client, - server_status: status, + server_status, } } } @@ -285,14 +288,14 @@ pub async fn imaginate<'a, P: Pixel>( ) -> Image

{ let WasmEditorApi { node_graph_message_sender, - imaginate_preferences, + editor_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(); + let host_name = editor_preferences.hostname(); imaginate_maybe_fail( image, host_name, diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 107fab4c5..9486fd800 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -225,10 +225,11 @@ fn to_svg_string(vector: &VectorData, transform: DAffine2) -> String { fn from_svg_string(svg_string: &str) -> VectorData { let svg = format!(r#""#, svg_string); - let Some(tree) = usvg::Tree::from_str(&svg, &Default::default()).ok() else { + let fontdb = usvg::fontdb::Database::new(); + let Some(tree) = usvg::Tree::from_str(&svg, &Default::default(), &fontdb).ok() else { return VectorData::empty(); }; - let Some(usvg::Node::Path(path)) = tree.root.children.first() else { + let Some(usvg::Node::Path(path)) = tree.root().children().first() else { return VectorData::empty(); }; @@ -239,10 +240,10 @@ pub fn convert_usvg_path(path: &usvg::Path) -> Vec> { let mut subpaths = Vec::new(); let mut groups = Vec::new(); - let mut points = path.data.points().iter(); + let mut points = path.data().points().iter(); let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64); - for verb in path.data.verbs() { + for verb in path.data().verbs() { match verb { usvg::tiny_skia_path::PathVerb::Move => { subpaths.push(Subpath::new(std::mem::take(&mut groups), false)); diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 837182beb..069cd24ba 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,4 +1,4 @@ -use dyn_any::DynFuture; +pub use graph_craft::document::value::RenderOutput; pub use graph_craft::wasm_application_io::*; #[cfg(target_arch = "wasm32")] use graphene_core::application_io::SurfaceHandle; @@ -14,10 +14,8 @@ use graphene_core::{Color, WasmNotSend}; #[cfg(target_arch = "wasm32")] use base64::Engine; -use core::future::Future; #[cfg(target_arch = "wasm32")] use glam::DAffine2; -use std::marker::PhantomData; use std::sync::Arc; #[cfg(target_arch = "wasm32")] use wasm_bindgen::Clamped; @@ -87,15 +85,6 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame { }; image } -pub use graph_craft::document::value::RenderOutput; -pub struct RenderNode { - data: Data, - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - surface_handle: Surface, - #[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))] - surface_handle: PhantomData, - parameter: PhantomData, -} fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput { if !data.contains_artboard() && !render_params.hide_artboards { @@ -115,50 +104,28 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p RenderOutput::Svg(render.svg.to_svg_string()) } -#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] -fn render_canvas( - data: impl GraphicElementRendered, - mut render: SvgRender, - render_params: RenderParams, - footprint: Footprint, - editor: &'_ WasmEditorApi, - surface_handle: wgpu_executor::WindowHandle, -) -> RenderOutput { - let resolution = footprint.resolution; - data.render_svg(&mut render, &render_params); - // TODO: reenable once we switch to full node graph - let min = footprint.transform.inverse().transform_point2((0., 0.).into()); - let max = footprint.transform.inverse().transform_point2(resolution.as_dvec2()); - render.format_svg(min, max); - let string = render.svg.to_svg_string(); - let _array = string.as_bytes(); - let canvas = &surface_handle.surface; - canvas.set_width(resolution.x); - canvas.set_height(resolution.y); - let usvg_tree = data.to_usvg_tree(resolution, [min, max]); +#[cfg(feature = "vello")] +#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] +async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutput { + if let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() { + use vello::*; - if let Some(_exec) = editor.application_io.as_ref().unwrap().gpu_executor() { - todo!() + let footprint = render_config.viewport; + + let mut scene = Scene::new(); + let mut child = Scene::new(); + + data.render_to_vello(&mut child, glam::DAffine2::IDENTITY); + + // TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost + scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array()))); + + exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y) + .await + .expect("Failed to render Vello scene"); } else { - let pixmap_size = usvg_tree.size.to_int_size(); - let mut pixmap = resvg::tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap(); - resvg::render(&usvg_tree, resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut()); - let array: Clamped<&[u8]> = Clamped(pixmap.data()); - let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); - let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, pixmap_size.width(), pixmap_size.height()).expect("Failed to construct ImageData"); - context.put_image_data(&image_data, 0.0, 0.0).unwrap(); + unreachable!("Attempted to render with Vello when no GPU executor is available"); } - /* - let preamble = "data:image/svg+xml;base64,"; - let mut base64_string = String::with_capacity(preamble.len() + array.len() * 4); - base64_string.push_str(preamble); - base64::engine::general_purpose::STANDARD.encode_string(array, &mut base64_string); - - let image_data = web_sys::HtmlImageElement::new().unwrap(); - image_data.set_src(base64_string.as_str()); - wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap(); - context.draw_image_with_html_image_element(&image_data, 0.0, 0.0).unwrap(); - */ let frame = graphene_core::application_io::SurfaceHandleFrame { surface_handle, transform: glam::DAffine2::IDENTITY, @@ -226,90 +193,44 @@ async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::Transf } } -// Render with the data node taking in Footprint. -impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode -where - for<'a> Data: Node<'a, Footprint, Output: Future + WasmNotSend>, - for<'a> Surface: Node<'a, (), Output: Future + WasmNotSend> + 'input, -{ - type Output = DynFuture<'input, RenderOutput>; - - #[inline] - fn eval(&'input self, render_config: RenderConfig) -> Self::Output { - let footprint = render_config.viewport; - - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let RenderConfig { hide_artboards, for_export, .. } = render_config; - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - - let data_fut = self.data.eval(footprint); - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let surface_fut = self.surface_handle.eval(()); - Box::pin(async move { - let data = data_fut.await; - let footprint = render_config.viewport; - - let RenderConfig { hide_artboards, for_export, .. } = render_config; - let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - - let output_format = render_config.export_format; - match output_format { - ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await), - _ => todo!("Non-SVG render output for {output_format:?}"), - } - }) - } +pub struct RenderNode { + editor_api: EditorApi, + data: Data, + _surface_handle: Surface, } -// Render with the data node taking in (). -impl<'input, T: 'input + GraphicElementRendered, Data: 'input, Surface: 'input> Node<'input, RenderConfig> for RenderNode -where - for<'a> Data: Node<'a, (), Output: Future + WasmNotSend>, - for<'a> Surface: Node<'a, (), Output: Future + WasmNotSend> + 'input, -{ - type Output = DynFuture<'input, RenderOutput>; +#[node_macro::node_fn(RenderNode)] +async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSend>( + render_config: RenderConfig, + editor_api: &'a WasmEditorApi, + data: impl Node, + _surface_handle: impl Node>, +) -> RenderOutput { + let footprint = render_config.viewport; - #[inline] - fn eval(&'input self, render_config: RenderConfig) -> Self::Output { - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let RenderConfig { hide_artboards, for_export, .. } = render_config; - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); + let RenderConfig { hide_artboards, for_export, .. } = render_config; + let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - let data_fut = self.data.eval(()); - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - let surface_fut = self.surface_handle.eval(()); - Box::pin(async move { - let data = data_fut.await; - let footprint = render_config.viewport; + let data = self.data.eval(footprint).await; + #[cfg(all(feature = "vello", target_arch = "wasm32"))] + let surface_handle = self._surface_handle.eval(footprint).await; + let use_vello = editor_api.editor_preferences.use_vello(); + #[cfg(all(feature = "vello", target_arch = "wasm32"))] + let use_vello = use_vello && surface_handle.is_some(); - let RenderConfig { hide_artboards, for_export, .. } = render_config; - let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - - let output_format = render_config.export_format; - match output_format { - ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - ExportFormat::Canvas => render_canvas(data, SvgRender::new(), render_params, footprint, editor, surface_fut.await), - _ => todo!("Non-SVG render output for {output_format:?}"), + let output_format = render_config.export_format; + match output_format { + ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), + ExportFormat::Canvas => { + if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() { + #[cfg(all(feature = "vello", target_arch = "wasm32"))] + return render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await; + #[cfg(not(all(feature = "vello", target_arch = "wasm32")))] + render_svg(data, SvgRender::new(), render_params, footprint) + } else { + render_svg(data, SvgRender::new(), render_params, footprint) } - }) - } -} - -#[automatically_derived] -impl RenderNode { - pub fn new(data: Data, _surface_handle: Surface) -> Self { - Self { - data, - #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - surface_handle: _surface_handle, - #[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))] - surface_handle: PhantomData, - parameter: PhantomData, } + _ => todo!("Non-SVG render output for {output_format:?}"), } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 8fcc1d869..47e2af087 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -20,9 +20,9 @@ use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureW use graphene_std::application_io::RenderConfig; use graphene_std::raster::*; use graphene_std::wasm_application_io::*; -use wgpu_executor::WindowHandle; #[cfg(feature = "gpu")] use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput}; +use wgpu_executor::{WgpuSurface, WindowHandle}; use dyn_any::StaticType; use glam::{DAffine2, DVec2, UVec2}; @@ -381,9 +381,9 @@ fn node_registry() -> HashMap, input: Arc, output: Vec, params: [&WgpuExecutor, ()]), #[cfg(feature = "gpu")] - async_node!(wgpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: wgpu_executor::WgpuSurface, params: []), + async_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: Footprint, output: Option, params: [&WasmEditorApi]), #[cfg(feature = "gpu")] - async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => wgpu_executor::WgpuSurface, () =>&WgpuExecutor]), + async_node!(wgpu_executor::RenderTextureNode<_, _, _>, input: Footprint, output: graphene_std::SurfaceFrame, fn_params: [Footprint => ShaderInputFrame, () => Option, () =>&WgpuExecutor]), #[cfg(feature = "gpu")] async_node!( wgpu_executor::UploadTextureNode<_>, @@ -612,6 +612,7 @@ fn node_registry() -> HashMap, input: (), output: ShaderInputFrame, params: [ShaderInputFrame]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WgpuSurface, params: [wgpu_executor::WgpuSurface]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Option, params: [Option]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: wgpu_executor::WindowHandle, params: [wgpu_executor::WindowHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: graphene_std::SurfaceFrame, params: [graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: RenderOutput, params: [RenderOutput]), @@ -619,6 +620,8 @@ fn node_registry() -> HashMap, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: ShaderInputFrame, fn_params: [Footprint => ShaderInputFrame]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: WgpuSurface, fn_params: [Footprint => WgpuSurface]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, output: Option, fn_params: [Footprint => Option]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: (), params: [UVec2, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]), @@ -627,23 +630,17 @@ fn node_registry() -> HashMap, input: Color, params: [QuantizationChannels]), register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec, () => Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option, Arc]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec, Arc]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ImageFrame, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => VectorData, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => GraphicGroup, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Artboard, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ArtboardGroup, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Option, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Vec, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => bool, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f32, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f64, Footprint => Option]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => String, Footprint => Option]), #[cfg(target_arch = "wasm32")] async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame, params: [Footprint, Arc]), #[cfg(target_arch = "wasm32")] diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index cdfea1bd1..4f29b5a7a 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -33,6 +33,7 @@ futures = { workspace = true } web-sys = { workspace = true, features = ["HtmlCanvasElement"] } winit = { workspace = true } serde = { workspace = true } +vello = { workspace = true } # Required dependencies futures-intrusive = { version = "0.5.0", features = ["alloc"] } diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index 06ac3bdfc..ddbb07a73 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -18,8 +18,13 @@ impl Context { }; let instance = wgpu::Instance::new(instance_descriptor); + let adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + }; // `request_adapter` instantiates the general connection to the GPU - let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?; + let adapter = instance.request_adapter(&adapter_options).await?; let required_limits = adapter.limits(); // `request_device` instantiates the feature specific connection to the GPU, defining some parameters, diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 6fccc5473..092225acd 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -1,5 +1,6 @@ mod context; mod executor; + pub use context::Context; pub use executor::GpuExecutor; @@ -14,9 +15,10 @@ use graphene_core::{Color, Cow, Node, SurfaceFrame}; use anyhow::{bail, Result}; use futures::Future; -use glam::DAffine2; +use glam::{DAffine2, UVec2}; use std::pin::Pin; use std::sync::Arc; +use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; use wgpu::util::DeviceExt; use wgpu::{Buffer, BufferDescriptor, ShaderModule, SurfaceConfiguration, SurfaceError, Texture, TextureView}; @@ -27,6 +29,7 @@ use web_sys::HtmlCanvasElement; pub struct WgpuExecutor { pub context: Context, render_configuration: RenderConfiguration, + vello_renderer: std::sync::Mutex, } impl std::fmt::Debug for WgpuExecutor { @@ -47,6 +50,12 @@ impl<'a, T: ApplicationIo> From<&'a EditorApi> for & pub type WgpuSurface = Arc>; pub type WgpuWindow = Arc>; +impl graphene_core::application_io::Size for Surface { + fn size(&self) -> UVec2 { + self.resolution + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { @@ -107,7 +116,10 @@ pub struct ShaderModuleWrapper(ShaderModule); pub type ShaderHandle = ShaderModuleWrapper; pub type BufferHandle = Buffer; pub type TextureHandle = Texture; -pub struct Surface(wgpu::Surface<'static>); +pub struct Surface { + pub inner: wgpu::Surface<'static>, + resolution: UVec2, +} #[cfg(target_arch = "wasm32")] pub type Window = HtmlCanvasElement; #[cfg(not(target_arch = "wasm32"))] @@ -122,6 +134,44 @@ unsafe impl StaticType for Surface { // } impl WgpuExecutor { + pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, width: u32, height: u32) -> Result<()> { + let surface = &surface.surface.inner; + let surface_caps = surface.get_capabilities(&self.context.adapter); + surface.configure( + &self.context.device, + &SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING, + format: wgpu::TextureFormat::Rgba8Unorm, + width, + height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + let surface_texture = surface.get_current_texture()?; + + let render_params = RenderParams { + base_color: vello::peniko::Color::TRANSPARENT, + width, + height, + antialiasing_method: AaConfig::Area, + }; + + { + let mut renderer = self.vello_renderer.lock().unwrap(); + renderer + .render_to_surface_async(&self.context.device, &self.context.queue, scene, &surface_texture, &render_params) + .await + .unwrap(); + } + + surface_texture.present(); + + Ok(()) + } + pub fn load_shader(&self, shader: Shader) -> Result { #[cfg(not(feature = "passthrough"))] let shader_module = self.context.device.create_shader_module(wgpu::ShaderModuleDescriptor { @@ -300,11 +350,11 @@ impl WgpuExecutor { ..Default::default() }); - let surface = &canvas.as_ref().surface.0; + let surface = &canvas.as_ref().surface.inner; let surface_caps = surface.get_capabilities(&self.context.adapter); if surface_caps.formats.is_empty() { log::warn!("No surface formats available"); - // return Ok(()); + return Ok(()); } // TODO: let resolution = transform.decompose_scale().as_uvec2(); @@ -455,8 +505,9 @@ impl WgpuExecutor { } #[cfg(target_arch = "wasm32")] - fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result> { + pub fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle, resolution: Option) -> Result> { let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?; + let resolution = resolution.unwrap_or(UVec2::new(1920, 1080)); // let surface_caps = surface.get_capabilities(&self.context.adapter); // let surface_format = wgpu::TextureFormat::Rgba16Float; @@ -474,12 +525,13 @@ impl WgpuExecutor { // self.surface_config.set(Some(config)); Ok(SurfaceHandle { surface_id: canvas.surface_id, - surface: Surface(surface), + surface: Surface { inner: surface, resolution }, }) } #[cfg(not(target_arch = "wasm32"))] - fn create_surface(&self, window: SurfaceHandle) -> Result> { + pub fn create_surface(&self, window: SurfaceHandle, resolution: Option) -> Result> { let size = window.surface.inner_size(); + let resolution = resolution.unwrap_or(UVec2 { x: size.width, y: size.height }); let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?; let surface_caps = surface.get_capabilities(&self.context.adapter); @@ -488,8 +540,8 @@ impl WgpuExecutor { let _config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, - width: size.width, - height: size.height, + width: resolution.x, + height: resolution.y, present_mode: surface_caps.present_modes[0], alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], @@ -500,7 +552,7 @@ impl WgpuExecutor { let surface_id = window.surface_id; Ok(SurfaceHandle { surface_id, - surface: Surface(surface), + surface: Surface { inner: surface, resolution }, }) } } @@ -619,7 +671,23 @@ impl WgpuExecutor { sampler, }; - Some(Self { context, render_configuration }) + let vello_renderer = Renderer::new( + &context.device, + RendererOptions { + surface_format: Some(wgpu::TextureFormat::Rgba8Unorm), + use_cpu: false, + antialiasing_support: AaSupport::all(), + num_init_threads: std::num::NonZeroUsize::new(1), + }, + ) + .map_err(|e| anyhow::anyhow!("Failed to create Vello renderer: {:?}", e)) + .ok()?; + + Some(Self { + context, + render_configuration, + vello_renderer: vello_renderer.into(), + }) } } @@ -861,15 +929,17 @@ async fn read_output_buffer_node<'a: 'input>(buffer: Arc, execu executor.read_output_buffer(buffer).await.unwrap() } -pub struct CreateGpuSurfaceNode {} +pub struct CreateGpuSurfaceNode { + editor_api: EditorApi, +} pub type WindowHandle = Arc>; #[node_macro::node_fn(CreateGpuSurfaceNode)] -async fn create_gpu_surface<'a: 'input, Io: ApplicationIo + Send + Sync>(editor_api: &'a EditorApi) -> WgpuSurface { - let canvas = editor_api.application_io.as_ref().unwrap().create_surface(); - let executor = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); - Arc::new(executor.create_surface(canvas).unwrap()) +async fn create_gpu_surface<'a: 'input, Io: ApplicationIo + 'a + Send + Sync>(footprint: Footprint, editor_api: &'a EditorApi) -> Option { + let canvas = editor_api.application_io.as_ref()?.create_surface(); + let executor = editor_api.application_io.as_ref()?.gpu_executor()?; + Some(Arc::new(executor.create_surface(canvas, Some(footprint.resolution)).ok()?)) } pub struct RenderTextureNode { @@ -885,14 +955,19 @@ pub struct ShaderInputFrame { } #[node_macro::node_fn(RenderTextureNode)] -async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node, surface: WgpuSurface, executor: &'a WgpuExecutor) -> SurfaceFrame { +async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node, surface: Option, executor: &'a WgpuExecutor) -> SurfaceFrame { + let surface = surface.unwrap(); let surface_id = surface.surface_id; let image = self.image.eval(footprint).await; let transform = image.transform; executor.create_render_pass(footprint, image, surface).unwrap(); - SurfaceFrame { surface_id, transform } + SurfaceFrame { + surface_id, + transform, + resolution: footprint.resolution, + } } pub struct UploadTextureNode { diff --git a/shell.nix b/shell.nix index afe362c7a..f128b2aff 100644 --- a/shell.nix +++ b/shell.nix @@ -46,6 +46,7 @@ in pkgs.cargo-nextest pkgs.cargo-expand pkgs.wasm-pack + pkgs.binaryen pkgs.wasm-bindgen-cli pkgs.vulkan-loader pkgs.libxkbcommon