mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Integrate Vello for vector rendering (#1802)
* Start integrating vello into render pipeline Cache vello render creation Implement viewport navigation Close vello path Add transform parameter to vello render pass * Fix render node types * Fix a bunch of bugs in the path translation * Avoid panic on empty document * Fix rendering of holes * Implement image rendering * Implement graph recompilation afer editor api change * Implement preferences toggle for using vello as the renderer * Make surface creation optional * Feature gate vello usages * Implement skeleton for radial gradient * Rename vello preference * Fix some gradients * Only update monitor nodes on graph recompile * Fix warnings + remove dead code * Update everything except for thumbnails after a node graph evaluation * Fix missing click targets for Image frames * Improve perfamance by removing unecessary widget updates * Fix node graph paning * Fix thumbnail loading * Implement proper hash for vector modification * Fix test and warnings * Code review * Fix dep * Remove warning --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
8e774efe9d
commit
ab71d26d84
41 changed files with 807 additions and 760 deletions
348
Cargo.lock
generated
348
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
] }
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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),
|
||||
];
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
|
|
|
@ -118,6 +118,7 @@ pub struct DocumentMessageHandler {
|
|||
#[serde(skip)]
|
||||
node_graph_ptz: HashMap<Vec<NodeId>, 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<Vec<NodeId>, DAffine2>,
|
||||
}
|
||||
|
@ -1141,9 +1142,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> 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<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::ZoomCanvasTo100Percent => {
|
||||
responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. });
|
||||
|
|
|
@ -569,7 +569,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> 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<GraphOperationMessage, GraphOperationMessageData<'_>> 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<usvg::Stroke>, 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<usvg::Stroke>, modify_inputs: &mut ModifyIn
|
|||
}
|
||||
}
|
||||
|
||||
fn apply_usvg_fill(fill: &Option<usvg::Fill>, 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<usvg::Fill>, 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 {
|
||||
|
|
|
@ -1311,15 +1311,16 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
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<WasmEdito
|
|||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Create Canvas".to_string(),
|
||||
inputs: vec![NodeInput::network(concrete!(&WasmEditorApi), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode<_>")),
|
||||
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<WasmEdito
|
|||
name: "RenderNode".to_string(),
|
||||
manual_composition: Some(concrete!(RenderConfig)),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
],
|
||||
|
|
|
@ -52,7 +52,7 @@ pub enum PortfolioMessage {
|
|||
},
|
||||
ImaginateCheckServerStatus,
|
||||
ImaginatePollServerStatus,
|
||||
ImaginatePreferences,
|
||||
EditorPreferences,
|
||||
ImaginateServerHostname,
|
||||
Import,
|
||||
LoadDocumentResources {
|
||||
|
@ -103,4 +103,5 @@ pub enum PortfolioMessage {
|
|||
ToggleRulers,
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdateVelloPreference,
|
||||
}
|
||||
|
|
|
@ -324,7 +324,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> 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<PortfolioMessage, PortfolioMessageData<'_>> 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<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
.collect::<Vec<_>>();
|
||||
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
||||
}
|
||||
PortfolioMessage::UpdateVelloPreference => {
|
||||
self.persistent_data.use_vello = preferences.use_vello;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -7,6 +7,7 @@ pub enum PreferencesMessage {
|
|||
ResetToDefaults,
|
||||
|
||||
ImaginateRefreshFrequency { seconds: f64 },
|
||||
UseVello { use_vello: bool },
|
||||
ImaginateServerHostname { hostname: String },
|
||||
ModifyLayout { zoom_with_scroll: bool },
|
||||
}
|
||||
|
|
|
@ -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<PreferencesMessage, ()> 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<PreferencesMessage, ()> 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<PreferencesMessage, ()> 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;
|
||||
|
|
|
@ -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<NodeRuntimeMessage>,
|
||||
sender: InternalNodeGraphUpdateSender,
|
||||
imaginate_preferences: ImaginatePreferences,
|
||||
recompile_graph: bool,
|
||||
editor_preferences: EditorPreferences,
|
||||
old_graph: Option<NodeNetwork>,
|
||||
update_thumbnails: bool,
|
||||
|
||||
editor_api: Arc<WasmEditorApi>,
|
||||
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<FrontendMessage>) {
|
||||
pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<FrontendMessage>, 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::<IORecord<Footprint, graphene_core::GraphicElement>>() {
|
||||
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::<IORecord<Footprint, graphene_core::Artboard>>() {
|
||||
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::<IORecord<Footprint, VectorData>>() {
|
||||
// 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<FrontendMessage>,
|
||||
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<T: std::any::Any + core::fmt::Debug, U, F1: FnOnce(&NodeNetwork) -> Option<NodeId>, 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<Message>) -> 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#"
|
||||
<svg><foreignObject width="{}" height="{}" transform="matrix({})"><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>
|
||||
"#,
|
||||
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),
|
||||
|
|
|
@ -22,6 +22,8 @@ crate-type = ["cdylib", "rlib"]
|
|||
# Local dependencies
|
||||
editor = { path = "../../editor", package = "graphite-editor", features = [
|
||||
"gpu",
|
||||
"resvg",
|
||||
"vello",
|
||||
] }
|
||||
|
||||
# Workspace dependencies
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -343,6 +343,27 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
|||
|
||||
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<DVec2> {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<S> From<SurfaceHandleFrame<S>> 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<S: Size> From<SurfaceHandleFrame<S>> for SurfaceFrame {
|
||||
fn from(x: SurfaceHandleFrame<S>) -> 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<Surface> {
|
|||
// #[cfg(target_arch = "wasm32")]
|
||||
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
|
||||
|
||||
impl<S: Size> Size for SurfaceHandle<S> {
|
||||
fn size(&self) -> UVec2 {
|
||||
self.surface.size()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
|
||||
type Static = SurfaceHandle<T>;
|
||||
}
|
||||
|
@ -162,8 +181,9 @@ impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
|
|||
}
|
||||
}
|
||||
|
||||
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<Io> {
|
||||
|
@ -208,8 +232,8 @@ pub struct EditorApi<Io> {
|
|||
/// 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<Arc<Io>>,
|
||||
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
|
||||
/// Imaginate preferences made available to the graph through the [`WasmEditorApi`].
|
||||
pub imaginate_preferences: Box<dyn GetImaginatePreferences + Send + Sync>,
|
||||
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
|
||||
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<Io> Eq for EditorApi<Io> {}
|
||||
|
@ -220,7 +244,7 @@ impl<Io: Default> Default for EditorApi<Io> {
|
|||
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<Io> Hash for EditorApi<Io> {
|
|||
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<Io> PartialEq for EditorApi<Io> {
|
|||
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 _)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Data: Into<Artboard> + Send>(footprint: Footprint, artboar
|
|||
}
|
||||
|
||||
impl From<ImageFrame<Color>> for GraphicElement {
|
||||
fn from(mut image_frame: ImageFrame<Color>) -> 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<Color>) -> Self {
|
||||
GraphicElement::ImageFrame(image_frame)
|
||||
}
|
||||
}
|
||||
|
@ -237,14 +224,28 @@ impl From<alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>> for GraphicEl
|
|||
fn from(surface: alloc::sync::Arc<SurfaceHandleFrame<HtmlCanvasElement>>) -> 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<SurfaceHandleFrame<HtmlCanvasElement>> for GraphicElement {
|
||||
fn from(surface: SurfaceHandleFrame<HtmlCanvasElement>) -> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ClickTarget>);
|
||||
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<ClickTarget>) {
|
||||
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<ClickTarget>) {
|
||||
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<Color> {
|
|||
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<T: Primitive> GraphicElementRendered for T {
|
|||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
|
||||
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<Color> {
|
||||
|
|
|
@ -233,6 +233,37 @@ impl core::fmt::Display for BlendMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
impl From<BlendMode> 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<LuminanceCalculation> {
|
||||
luminance_calc: LuminanceCalculation,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,6 +16,20 @@ pub struct PointModification {
|
|||
delta: HashMap<PointId, DVec2>,
|
||||
}
|
||||
|
||||
impl Hash for PointModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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<SegmentId, StrokeId>,
|
||||
}
|
||||
|
||||
impl Hash for SegmentModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut start_point = self.start_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
start_point.sort_unstable();
|
||||
start_point.hash(state);
|
||||
|
||||
let mut end_point = self.end_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
handle_end.sort_unstable();
|
||||
handle_end.hash(state);
|
||||
|
||||
let mut stroke = self.stroke.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
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<RegionId, FillId>,
|
||||
}
|
||||
|
||||
impl Hash for RegionModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut segment_range = self.segment_range.iter().map(|(&a, b)| (a, (*b.start(), *b.end()))).collect::<Vec<_>>();
|
||||
segment_range.sort_unstable();
|
||||
segment_range.hash(state);
|
||||
|
||||
let mut fill = self.fill.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
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<H: core::hash::Hasher>(&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::<Vec<_>>();
|
||||
add_g1_continuous.sort_unstable();
|
||||
add_g1_continuous.hash(state);
|
||||
|
||||
let mut remove_g1_continuous = self.remove_g1_continuous.iter().copied().collect::<Vec<_>>();
|
||||
remove_g1_continuous.sort_unstable();
|
||||
remove_g1_continuous.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,10 +76,6 @@ macro_rules! tagged_value {
|
|||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
||||
|
||||
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
|
||||
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
|
||||
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
||||
}
|
||||
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -220,3 +220,32 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
|
||||
pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
|
||||
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<wgpu_executor::Window>;
|
||||
|
||||
#[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;
|
||||
}
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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<WasmEdito
|
|||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
|
||||
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<WasmEdito
|
|||
// font_cache: &FontCache::default(),
|
||||
// application_io: &block_on(WasmApplicationIo::new()),
|
||||
// node_graph_message_sender: &UpdateLogger {},
|
||||
// imaginate_preferences: &ImaginatePreferences::default(),
|
||||
// editor_preferences: &EditorPreferences::default(),
|
||||
// render_config: graphene_core::application_io::RenderConfig::default(),
|
||||
// };
|
||||
// let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
|
@ -191,7 +191,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
|||
// font_cache: &FontCache::default(),
|
||||
// application_io: &block_on(WasmApplicationIo::new()),
|
||||
// node_graph_message_sender: &UpdateLogger {},
|
||||
// imaginate_preferences: &ImaginatePreferences::default(),
|
||||
// editor_preferences: &EditorPreferences::default(),
|
||||
// render_config: graphene_core::application_io::RenderConfig::default(),
|
||||
// };
|
||||
// let result = (&executor).execute(editor_api.clone()).await.unwrap();
|
||||
|
|
|
@ -290,7 +290,6 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
|
|||
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();
|
||||
|
|
|
@ -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<P> {
|
||||
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,
|
||||
|
|
|
@ -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 xmlns="http://www.w3.org/2000/svg"><path d="{}"></path></svg>"#, 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<Subpath<PointId>> {
|
|||
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));
|
||||
|
|
|
@ -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<Color> {
|
|||
};
|
||||
image
|
||||
}
|
||||
pub use graph_craft::document::value::RenderOutput;
|
||||
pub struct RenderNode<Data, Surface, Parameter> {
|
||||
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<Surface>,
|
||||
parameter: PhantomData<Parameter>,
|
||||
}
|
||||
|
||||
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::<CanvasRenderingContext2d>().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<Data, Surface, Footprint>
|
||||
where
|
||||
for<'a> Data: Node<'a, Footprint, Output: Future<Output = T> + WasmNotSend>,
|
||||
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + 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<EditorApi, Data, Surface> {
|
||||
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<Data, Surface, ()>
|
||||
where
|
||||
for<'a> Data: Node<'a, (), Output: Future<Output = T> + WasmNotSend>,
|
||||
for<'a> Surface: Node<'a, (), Output: Future<Output = wgpu_executor::WindowHandle> + 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<Footprint, Output = T>,
|
||||
_surface_handle: impl Node<Footprint, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
) -> 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<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
|
||||
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:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
#[cfg(feature = "gpu")]
|
||||
async_node!(wgpu_executor::ReadOutputBufferNode<_, _>, input: Arc<WgpuShaderInput>, output: Vec<u8>, 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<wgpu_executor::WgpuSurface>, 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<wgpu_executor::WgpuSurface>, () =>&WgpuExecutor]),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(
|
||||
wgpu_executor::UploadTextureNode<_>,
|
||||
|
@ -612,6 +612,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
async_node!(graphene_core::memo::MemoNode<_, _>, 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<wgpu_executor::WgpuSurface>, params: [Option<wgpu_executor::WgpuSurface>]),
|
||||
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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, 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<WgpuSurface>, fn_params: [Footprint => Option<WgpuSurface>]),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
||||
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, 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<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
register_node!(graphene_core::quantization::QuantizeNode<_>, 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<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ImageFrame<Color>, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => VectorData, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => GraphicGroup, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Artboard, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => ArtboardGroup, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Option<Color>, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => Vec<Color>, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => bool, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f32, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => f64, Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [() => &WasmEditorApi, Footprint => String, Footprint => Option<WgpuSurface>]),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<vello::Renderer>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WgpuExecutor {
|
||||
|
@ -47,6 +50,12 @@ impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
|
|||
pub type WgpuSurface = Arc<SurfaceHandle<Surface>>;
|
||||
pub type WgpuWindow = Arc<SurfaceHandle<WindowHandle>>;
|
||||
|
||||
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<ShaderHandle> {
|
||||
#[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<SurfaceHandle<Surface>> {
|
||||
pub fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle, resolution: Option<UVec2>) -> Result<SurfaceHandle<Surface>> {
|
||||
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<Window>) -> Result<SurfaceHandle<Surface>> {
|
||||
pub fn create_surface(&self, window: SurfaceHandle<Window>, resolution: Option<UVec2>) -> Result<SurfaceHandle<Surface>> {
|
||||
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<WgpuShaderInput>, execu
|
|||
executor.read_output_buffer(buffer).await.unwrap()
|
||||
}
|
||||
|
||||
pub struct CreateGpuSurfaceNode {}
|
||||
pub struct CreateGpuSurfaceNode<EditorApi> {
|
||||
editor_api: EditorApi,
|
||||
}
|
||||
|
||||
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
||||
|
||||
#[node_macro::node_fn(CreateGpuSurfaceNode)]
|
||||
async fn create_gpu_surface<'a: 'input, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + Send + Sync>(editor_api: &'a EditorApi<Io>) -> 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<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(footprint: Footprint, editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
|
||||
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<Image, Surface, EditorApi> {
|
||||
|
@ -885,14 +955,19 @@ pub struct ShaderInputFrame {
|
|||
}
|
||||
|
||||
#[node_macro::node_fn(RenderTextureNode)]
|
||||
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: WgpuSurface, executor: &'a WgpuExecutor) -> SurfaceFrame {
|
||||
async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<Footprint, Output = ShaderInputFrame>, surface: Option<WgpuSurface>, 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<Executor> {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue