Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3fd9417d4 | ||
|
|
3ab9c922fd | ||
|
|
32b10383d8 | ||
|
|
71ba3723ff | ||
|
|
0a14df2cc3 | ||
|
|
0e7076a01c | ||
|
|
00d2230b84 | ||
|
|
f62131eecf | ||
|
|
b234497d21 | ||
|
|
028521b419 | ||
|
|
0f6a28b4dd |
No files matched your search
Generated
+203
-229
@@ -234,18 +234,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-vec"
|
name = "bit-vec"
|
||||||
version = "0.8.0"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
@@ -274,12 +274,6 @@ dependencies = [
|
|||||||
"core2",
|
"core2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -289,6 +283,15 @@ dependencies = [
|
|||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block2"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "built"
|
name = "built"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -391,9 +394,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
@@ -406,9 +409,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.12.0"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
@@ -450,16 +453,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -473,8 +466,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation",
|
||||||
"core-graphics-types 0.1.3",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -486,18 +479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation",
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-graphics-types"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -739,15 +721,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "font-types"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -771,7 +744,7 @@ dependencies = [
|
|||||||
"objc2-core-text",
|
"objc2-core-text",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"parlance",
|
"parlance",
|
||||||
"read-fonts 0.41.0",
|
"read-fonts",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows",
|
"windows",
|
||||||
@@ -806,6 +779,30 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fxhash"
|
name = "fxhash"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -860,9 +857,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
|
checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
@@ -893,26 +890,6 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gpu-descriptor"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"gpu-descriptor-types",
|
|
||||||
"hashbrown 0.15.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gpu-descriptor-types"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
@@ -933,7 +910,7 @@ checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"read-fonts 0.41.0",
|
"read-fonts",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -972,12 +949,6 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hexf-parse"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -1108,9 +1079,9 @@ checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.9"
|
version = "0.25.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
@@ -1126,8 +1097,8 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
"rgb",
|
"rgb",
|
||||||
"tiff",
|
"tiff",
|
||||||
"zune-core 0.5.0",
|
"zune-core",
|
||||||
"zune-jpeg 0.5.8",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1247,11 +1218,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.105"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1280,9 +1252,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.179"
|
version = "0.2.189"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -1375,15 +1347,6 @@ dependencies = [
|
|||||||
"imgref",
|
"imgref",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "malloc_buf"
|
|
||||||
version = "0.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maybe-rayon"
|
name = "maybe-rayon"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1409,21 +1372,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "metal"
|
|
||||||
version = "0.33.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"block",
|
|
||||||
"core-graphics-types 0.2.0",
|
|
||||||
"foreign-types",
|
|
||||||
"log",
|
|
||||||
"objc",
|
|
||||||
"paste",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -1436,9 +1384,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.7.11"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pxfm",
|
"pxfm",
|
||||||
@@ -1446,9 +1394,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135"
|
checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
@@ -1457,11 +1405,11 @@ dependencies = [
|
|||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"half",
|
"half",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
"hexf-parse",
|
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"libm",
|
"libm",
|
||||||
"log",
|
"log",
|
||||||
|
"naga-types",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
@@ -1470,6 +1418,18 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "naga-types"
|
||||||
|
version = "30.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
"indexmap",
|
||||||
|
"rustc-hash",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1594,15 +1554,6 @@ dependencies = [
|
|||||||
"syn 2.0.113",
|
"syn 2.0.113",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
|
||||||
dependencies = [
|
|
||||||
"malloc_buf",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc-sys"
|
name = "objc-sys"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -1635,13 +1586,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-image",
|
"objc2-core-image",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1663,7 +1614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1675,7 +1626,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1687,7 +1638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1722,10 +1673,10 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1734,7 +1685,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-contacts",
|
"objc2-contacts",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1763,7 +1714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
@@ -1797,7 +1748,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit 0.2.2",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1810,11 +1761,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-metal"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"block2 0.6.2",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -1822,10 +1785,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-quartz-core"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"objc2-metal 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1845,7 +1822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
@@ -1853,7 +1830,7 @@ dependencies = [
|
|||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-link-presentation",
|
"objc2-link-presentation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core 0.2.2",
|
||||||
"objc2-symbols",
|
"objc2-symbols",
|
||||||
"objc2-uniform-type-identifiers",
|
"objc2-uniform-type-identifiers",
|
||||||
"objc2-user-notifications",
|
"objc2-user-notifications",
|
||||||
@@ -1865,7 +1842,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1877,7 +1854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1971,7 +1948,7 @@ dependencies = [
|
|||||||
"linebender_resource_handle",
|
"linebender_resource_handle",
|
||||||
"parlance",
|
"parlance",
|
||||||
"parley_data",
|
"parley_data",
|
||||||
"skrifa 0.44.0",
|
"skrifa",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2275,9 +2252,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ravif"
|
name = "ravif"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
|
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avif-serialize",
|
"avif-serialize",
|
||||||
"imgref",
|
"imgref",
|
||||||
@@ -2294,6 +2271,18 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "raw-window-metal"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"objc2-quartz-core 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -2314,16 +2303,6 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "read-fonts"
|
|
||||||
version = "0.36.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"font-types 0.10.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "read-fonts"
|
name = "read-fonts"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
@@ -2331,7 +2310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
|
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"font-types 0.12.4",
|
"font-types",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2506,16 +2485,6 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "skrifa"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"read-fonts 0.36.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "skrifa"
|
name = "skrifa"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
@@ -2523,7 +2492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
|
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"read-fonts 0.41.0",
|
"read-fonts",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2583,9 +2552,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spirv"
|
name = "spirv"
|
||||||
version = "0.3.0+sdk-1.3.268.0"
|
version = "0.4.0+sdk-1.4.341.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
|
checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
@@ -2614,7 +2583,7 @@ version = "0.2.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
|
checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"skrifa 0.39.0",
|
"skrifa",
|
||||||
"yazi",
|
"yazi",
|
||||||
"zeno",
|
"zeno",
|
||||||
]
|
]
|
||||||
@@ -2703,16 +2672,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.3"
|
version = "0.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fax",
|
"fax",
|
||||||
"flate2",
|
"flate2",
|
||||||
"half",
|
"half",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"weezl",
|
"weezl",
|
||||||
"zune-jpeg 0.4.21",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2885,9 +2854,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.106"
|
version = "0.2.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -2898,22 +2867,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.56"
|
version = "0.4.78"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
|
checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.106"
|
version = "0.2.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -2921,22 +2887,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.106"
|
version = "0.2.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.113",
|
"syn 3.0.5",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.106"
|
version = "0.2.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -3052,9 +3018,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.105"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
|
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3078,9 +3044,9 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu"
|
name = "wgpu"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f"
|
checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
@@ -3088,7 +3054,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"document-features",
|
"document-features",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"naga",
|
"naga",
|
||||||
@@ -3108,9 +3074,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core"
|
name = "wgpu-core"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9"
|
checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
@@ -3119,10 +3085,11 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"document-features",
|
"document-features",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"naga",
|
"naga",
|
||||||
|
"naga-types",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
@@ -3135,66 +3102,70 @@ dependencies = [
|
|||||||
"wgpu-core-deps-emscripten",
|
"wgpu-core-deps-emscripten",
|
||||||
"wgpu-core-deps-windows-linux-android",
|
"wgpu-core-deps-windows-linux-android",
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
|
"wgpu-naga-bridge",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-apple"
|
name = "wgpu-core-deps-apple"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da"
|
checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-emscripten"
|
name = "wgpu-core-deps-emscripten"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79"
|
checksum = "d98b86cf4abf524a902dd35f18ca6a3f08fc2ae9847c8f10b48e30491b1f0b86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-windows-linux-android"
|
name = "wgpu-core-deps-windows-linux-android"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae"
|
checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-hal"
|
name = "wgpu-hal"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef"
|
checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"ash",
|
"ash",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block",
|
"block2 0.6.2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"core-graphics-types 0.2.0",
|
|
||||||
"glow",
|
"glow",
|
||||||
"glutin_wgl_sys",
|
"glutin_wgl_sys",
|
||||||
"gpu-allocator",
|
"gpu-allocator",
|
||||||
"gpu-descriptor",
|
"hashbrown 0.17.1",
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"khronos-egl",
|
"khronos-egl",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading",
|
"libloading",
|
||||||
"log",
|
"log",
|
||||||
"metal",
|
|
||||||
"naga",
|
"naga",
|
||||||
|
"naga-types",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"objc",
|
"objc2 0.6.4",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"objc2-metal 0.3.2",
|
||||||
|
"objc2-quartz-core 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@@ -3203,26 +3174,44 @@ dependencies = [
|
|||||||
"profiling",
|
"profiling",
|
||||||
"range-alloc",
|
"range-alloc",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
"raw-window-metal",
|
||||||
"renderdoc-sys",
|
"renderdoc-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"static_assertions",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
"wayland-sys",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
"wgpu-naga-bridge",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
|
"windows-result",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wgpu-naga-bridge"
|
||||||
|
version = "30.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081"
|
||||||
|
dependencies = [
|
||||||
|
"naga",
|
||||||
|
"wgpu-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-types"
|
name = "wgpu-types"
|
||||||
version = "28.0.0"
|
version = "30.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c"
|
checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
|
"naga-types",
|
||||||
|
"raw-window-handle",
|
||||||
|
"static_assertions",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3586,12 +3575,12 @@ dependencies = [
|
|||||||
"android-activity",
|
"android-activity",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"calloop",
|
"calloop",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"cursor-icon",
|
"cursor-icon",
|
||||||
"dpi",
|
"dpi",
|
||||||
@@ -3858,12 +3847,6 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zune-core"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -3879,20 +3862,11 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zune-jpeg"
|
|
||||||
version = "0.4.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
|
||||||
dependencies = [
|
|
||||||
"zune-core 0.4.12",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
|
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core 0.5.0",
|
"zune-core",
|
||||||
]
|
]
|
||||||
+2
-2
@@ -29,9 +29,9 @@ edition = "2024"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
wgpu = "28.0.0"
|
wgpu = "30.0.1"
|
||||||
bytemuck = "1.23.1"
|
bytemuck = "1.23.1"
|
||||||
image = "0.25.6"
|
image = "0.25.10"
|
||||||
parley = "0.11.1"
|
parley = "0.11.1"
|
||||||
swash = "0.2.10"
|
swash = "0.2.10"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What this widget registered, without running any of it.
|
||||||
|
pub fn registered(&self, id: WidgetId) -> impl Iterator<Item = &E> {
|
||||||
|
self.map.get(&id).into_iter().flatten().map(|(e, _)| e)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_fn<'a>(
|
pub fn run_fn<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||||
util::to_mut,
|
util::to_mut,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ struct Child {
|
|||||||
tail: usize,
|
tail: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PrimitiveLayers = Layers<Primitives>;
|
pub type DrawLayers = Layers<LayerDraws>;
|
||||||
|
|
||||||
impl<T: Default> Layers<T> {
|
impl<T: Default> Layers<T> {
|
||||||
pub fn new() -> Layers<T> {
|
pub fn new() -> Layers<T> {
|
||||||
@@ -119,7 +119,7 @@ impl<T: Default> Layers<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveLayers {
|
impl DrawLayers {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor,
|
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, UiColor, util::Vec2,
|
||||||
util::Vec2,
|
|
||||||
};
|
};
|
||||||
use parley::{
|
use parley::{
|
||||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
||||||
@@ -159,7 +158,7 @@ impl TextBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextData {
|
impl TextData {
|
||||||
pub fn place(&mut self, buffer: &TextBuffer, textures: &mut Textures) -> Vec<PlacedGlyph> {
|
pub fn place(&mut self, buffer: &TextBuffer) -> Vec<PlacedGlyph> {
|
||||||
let mut placed = Vec::new();
|
let mut placed = Vec::new();
|
||||||
for line in buffer.layout.lines() {
|
for line in buffer.layout.lines() {
|
||||||
for item in line.items() {
|
for item in line.items() {
|
||||||
@@ -185,17 +184,14 @@ impl TextData {
|
|||||||
subpixel,
|
subpixel,
|
||||||
coords: coords_hash,
|
coords: coords_hash,
|
||||||
};
|
};
|
||||||
let Some(entry) = self.glyph_entry(
|
let Some(entry) = self.glyph_entry(GlyphRaster {
|
||||||
GlyphRaster {
|
|
||||||
key,
|
key,
|
||||||
font: font_ref,
|
font: font_ref,
|
||||||
font_size,
|
font_size,
|
||||||
coords,
|
coords,
|
||||||
subpixel,
|
subpixel,
|
||||||
glyph_id: glyph.id,
|
glyph_id: glyph.id,
|
||||||
},
|
}) else {
|
||||||
textures,
|
|
||||||
) else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
placed.push(PlacedGlyph {
|
placed.push(PlacedGlyph {
|
||||||
@@ -211,11 +207,7 @@ impl TextData {
|
|||||||
placed
|
placed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glyph_entry(
|
fn glyph_entry(&mut self, glyph: GlyphRaster<'_>) -> Option<GlyphEntry> {
|
||||||
&mut self,
|
|
||||||
glyph: GlyphRaster<'_>,
|
|
||||||
textures: &mut Textures,
|
|
||||||
) -> Option<GlyphEntry> {
|
|
||||||
if let Some(entry) = self.atlas.get(&glyph.key) {
|
if let Some(entry) = self.atlas.get(&glyph.key) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -237,7 +229,7 @@ impl TextData {
|
|||||||
.render(&mut scaler, glyph.glyph_id as u16);
|
.render(&mut scaler, glyph.glyph_id as u16);
|
||||||
|
|
||||||
if let Some(image) = image {
|
if let Some(image) = image {
|
||||||
self.atlas.insert(glyph.key, &image, textures)
|
self.atlas.insert(glyph.key, &image)
|
||||||
} else {
|
} else {
|
||||||
self.atlas.insert_empty(glyph.key);
|
self.atlas.insert_empty(glyph.key);
|
||||||
None
|
None
|
||||||
@@ -278,10 +270,9 @@ impl TextData {
|
|||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
textures: &mut Textures,
|
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
buffer.shape(self, attrs, width);
|
buffer.shape(self, attrs, width);
|
||||||
let glyphs = self.place(buffer, textures);
|
let glyphs = self.place(buffer);
|
||||||
RenderedText {
|
RenderedText {
|
||||||
glyphs,
|
glyphs,
|
||||||
size: buffer.size(),
|
size: buffer.size(),
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::util::{RefCounter, Vec2};
|
||||||
render::TexturePrimitive,
|
|
||||||
util::{RefCounter, Vec2},
|
|
||||||
};
|
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView};
|
||||||
use std::{
|
use std::{
|
||||||
ops::Index,
|
ops::Index,
|
||||||
@@ -10,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextureHandle {
|
pub struct TextureHandle {
|
||||||
inner: TexturePrimitive,
|
slot: u32,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
counter: RefCounter,
|
counter: RefCounter,
|
||||||
send: Sender<u32>,
|
send: Sender<u32>,
|
||||||
@@ -31,6 +28,8 @@ pub enum TextureUpdate<'a> {
|
|||||||
Set(u32, &'a DynamicImage),
|
Set(u32, &'a DynamicImage),
|
||||||
Patch(u32, PatchRect, &'a DynamicImage),
|
Patch(u32, PatchRect, &'a DynamicImage),
|
||||||
Free(u32),
|
Free(u32),
|
||||||
|
/// Added and freed before the renderer drained either update. It still has
|
||||||
|
/// to push a slot to stay lined up with `images`; `Free` then empties it.
|
||||||
PushFree,
|
PushFree,
|
||||||
SetFree,
|
SetFree,
|
||||||
}
|
}
|
||||||
@@ -64,14 +63,8 @@ impl Textures {
|
|||||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||||
let image = image.into();
|
let image = image.into();
|
||||||
let size = image.dimensions().into();
|
let size = image.dimensions().into();
|
||||||
let view_idx = self.push(image);
|
|
||||||
// 0 == default in renderer; TODO: actually create samplers here
|
|
||||||
let sampler_idx = 0;
|
|
||||||
TextureHandle {
|
TextureHandle {
|
||||||
inner: TexturePrimitive {
|
slot: self.push(image),
|
||||||
view_idx,
|
|
||||||
sampler_idx,
|
|
||||||
},
|
|
||||||
size,
|
size,
|
||||||
counter: RefCounter::new(),
|
counter: RefCounter::new(),
|
||||||
send: self.send.clone(),
|
send: self.send.clone(),
|
||||||
@@ -92,15 +85,20 @@ impl Textures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
|
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
|
||||||
self.images[handle.inner.view_idx as usize]
|
self.images[handle.slot as usize]
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("texture was freed while still held")
|
.expect("texture was freed while still held")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue an upload of just `rect`, after writing it with `image_mut`.
|
/// Queue an upload of just `rect`, after writing it with `image_mut`.
|
||||||
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
|
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
|
||||||
self.updates
|
self.updates.push(Update::Patch(handle.slot, rect));
|
||||||
.push(Update::Patch(handle.inner.view_idx, rect));
|
}
|
||||||
|
|
||||||
|
/// How many textures are live, which is what a ui can ask; the renderer's
|
||||||
|
/// copies follow from the updates it drains.
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.images.iter().flatten().count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self) {
|
pub fn free(&mut self) {
|
||||||
@@ -131,8 +129,9 @@ impl Textures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextureHandle {
|
impl TextureHandle {
|
||||||
pub fn primitive(&self) -> TexturePrimitive {
|
/// Index into `Textures`, and into the renderer's parallel slots.
|
||||||
self.inner
|
pub fn slot(&self) -> u32 {
|
||||||
|
self.slot
|
||||||
}
|
}
|
||||||
pub fn size(&self) -> Vec2 {
|
pub fn size(&self) -> Vec2 {
|
||||||
self.size
|
self.size
|
||||||
@@ -142,7 +141,7 @@ impl TextureHandle {
|
|||||||
impl Drop for TextureHandle {
|
impl Drop for TextureHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.counter.drop() {
|
if self.counter.drop() {
|
||||||
let _ = self.send.send(self.inner.view_idx);
|
let _ = self.send.send(self.slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +150,7 @@ impl Index<&TextureHandle> for Textures {
|
|||||||
type Output = DynamicImage;
|
type Output = DynamicImage;
|
||||||
|
|
||||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
||||||
self.images[index.inner.view_idx as usize].as_ref().unwrap()
|
self.images[index.slot as usize].as_ref().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-36
@@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
PatchRect, TextureHandle, Textures,
|
PatchRect,
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
use swash::scale::image::{Content, Image};
|
use swash::scale::image::{Content, Image};
|
||||||
|
|
||||||
const PAGE: u32 = 1024;
|
/// Side of one page, and so of every layer of `render::page`'s array texture.
|
||||||
|
pub(crate) const PAGE: u32 = 1024;
|
||||||
|
|
||||||
/// Transparent margin kept around every glyph, so that sampling one cannot
|
/// Transparent margin kept around every glyph, so that sampling one cannot
|
||||||
/// pick up its neighbour along a shared edge.
|
/// pick up its neighbour along a shared edge.
|
||||||
@@ -35,8 +36,8 @@ pub struct GlyphEntry {
|
|||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub is_colored: bool,
|
pub is_colored: bool,
|
||||||
pub view_idx: u32,
|
/// Which atlas array layer this glyph is on.
|
||||||
pub sampler_idx: u32,
|
pub layer: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphEntry {
|
impl GlyphEntry {
|
||||||
@@ -48,18 +49,26 @@ impl GlyphEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Page {
|
struct Page {
|
||||||
handle: TextureHandle,
|
image: RgbaImage,
|
||||||
x: u32,
|
x: u32,
|
||||||
y: u32,
|
y: u32,
|
||||||
shelf_height: u32,
|
shelf_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A rectangle of one page the renderer has not uploaded yet.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PageUpload {
|
||||||
|
pub layer: u32,
|
||||||
|
pub rect: PatchRect,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GlyphAtlas {
|
pub struct GlyphAtlas {
|
||||||
pages: Vec<Page>,
|
pages: Vec<Page>,
|
||||||
/// `None` for a glyph that rasterised to nothing -- a space, say. Cached
|
/// `None` for a glyph that rasterised to nothing -- a space, say. Cached
|
||||||
/// too, so it is not re-rasterised on every layout.
|
/// too, so it is not re-rasterised on every layout.
|
||||||
entries: HashMap<GlyphKey, Option<GlyphEntry>>,
|
entries: HashMap<GlyphKey, Option<GlyphEntry>>,
|
||||||
|
uploads: Vec<PageUpload>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphAtlas {
|
impl GlyphAtlas {
|
||||||
@@ -67,12 +76,7 @@ impl GlyphAtlas {
|
|||||||
self.entries.get(key).copied()
|
self.entries.get(key).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(
|
pub fn insert(&mut self, key: GlyphKey, image: &Image) -> Option<GlyphEntry> {
|
||||||
&mut self,
|
|
||||||
key: GlyphKey,
|
|
||||||
image: &Image,
|
|
||||||
textures: &mut Textures,
|
|
||||||
) -> Option<GlyphEntry> {
|
|
||||||
let w = image.placement.width;
|
let w = image.placement.width;
|
||||||
let h = image.placement.height;
|
let h = image.placement.height;
|
||||||
if w == 0 || h == 0 {
|
if w == 0 || h == 0 {
|
||||||
@@ -94,23 +98,11 @@ impl GlyphAtlas {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (page_idx, x, y) = self.allocate(w, h, textures);
|
let upload = self.allocate(w, h);
|
||||||
let page = &self.pages[page_idx];
|
let PatchRect { x, y, .. } = upload.rect;
|
||||||
|
write_glyph(&mut self.pages[upload.layer as usize].image, image, x, y);
|
||||||
|
self.uploads.push(upload);
|
||||||
|
|
||||||
let img = textures.image_mut(&page.handle);
|
|
||||||
let rgba = img.as_mut_rgba8().expect("atlas page is rgba8");
|
|
||||||
write_glyph(rgba, image, x, y);
|
|
||||||
|
|
||||||
let handle = page.handle.clone();
|
|
||||||
let rect = PatchRect {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
};
|
|
||||||
textures.patch(&handle, rect);
|
|
||||||
|
|
||||||
let page = &self.pages[page_idx];
|
|
||||||
let scale = 1.0 / PAGE as f32;
|
let scale = 1.0 / PAGE as f32;
|
||||||
let entry = GlyphEntry {
|
let entry = GlyphEntry {
|
||||||
uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
|
uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
|
||||||
@@ -120,39 +112,60 @@ impl GlyphAtlas {
|
|||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
is_colored: matches!(image.content, Content::Color),
|
is_colored: matches!(image.content, Content::Color),
|
||||||
view_idx: page.handle.primitive().view_idx,
|
layer: upload.layer,
|
||||||
sampler_idx: page.handle.primitive().sampler_idx,
|
|
||||||
};
|
};
|
||||||
self.entries.insert(key, Some(entry));
|
self.entries.insert(key, Some(entry));
|
||||||
Some(entry)
|
Some(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocate(&mut self, w: u32, h: u32, textures: &mut Textures) -> (usize, u32, u32) {
|
/// Reserves room for a `w` by `h` glyph, adding a page if none has it.
|
||||||
|
fn allocate(&mut self, w: u32, h: u32) -> PageUpload {
|
||||||
|
let rect = |x, y| PatchRect {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
};
|
||||||
if let Some((i, (x, y))) = self
|
if let Some((i, (x, y))) = self
|
||||||
.pages
|
.pages
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
||||||
{
|
{
|
||||||
return (i, x, y);
|
return PageUpload {
|
||||||
|
layer: i as u32,
|
||||||
|
rect: rect(x, y),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = textures.add(RgbaImage::new(PAGE, PAGE));
|
|
||||||
self.pages.push(Page {
|
self.pages.push(Page {
|
||||||
handle,
|
image: RgbaImage::new(PAGE, PAGE),
|
||||||
x: PAD + w + PAD,
|
x: PAD + w + PAD,
|
||||||
y: PAD,
|
y: PAD,
|
||||||
shelf_height: h + PAD,
|
shelf_height: h + PAD,
|
||||||
});
|
});
|
||||||
(self.pages.len() - 1, PAD, PAD)
|
PageUpload {
|
||||||
|
layer: self.pages.len() as u32 - 1,
|
||||||
|
rect: rect(PAD, PAD),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drains what has been written since the last call, for the renderer to
|
||||||
|
/// upload. A new page needs nothing more: wgpu leaves the rest of a fresh
|
||||||
|
/// layer transparent, which is what an atlas wants.
|
||||||
|
pub fn uploads(&mut self) -> impl Iterator<Item = (PageUpload, &RgbaImage)> {
|
||||||
|
let pages = &self.pages;
|
||||||
|
self.uploads
|
||||||
|
.drain(..)
|
||||||
|
.map(|upload| (upload, &pages[upload.layer as usize].image))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_empty(&mut self, key: GlyphKey) {
|
pub fn insert_empty(&mut self, key: GlyphKey) {
|
||||||
self.entries.insert(key, None);
|
self.entries.insert(key, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page_count(&self) -> usize {
|
pub fn page_count(&self) -> u32 {
|
||||||
self.pages.len()
|
self.pages.len() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyph_count(&self) -> usize {
|
pub fn glyph_count(&self) -> usize {
|
||||||
|
|||||||
@@ -12,20 +12,16 @@ pub struct WindowUniform {
|
|||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct PrimitiveInstance {
|
pub struct PrimitiveInstance {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
pub binding: u32,
|
|
||||||
pub idx: u32,
|
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
5 => Uint32,
|
|
||||||
6 => Uint32,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> VertexBufferLayout<'static> {
|
||||||
|
|||||||
+200
-191
@@ -1,8 +1,6 @@
|
|||||||
use std::num::NonZero;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
UiData, UiRenderState,
|
UiData, UiRenderState,
|
||||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
render::{data::PrimitiveInstance, util::ArrBuf},
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use data::WindowUniform;
|
use data::WindowUniform;
|
||||||
@@ -13,6 +11,7 @@ use wgpu::{
|
|||||||
|
|
||||||
mod atlas;
|
mod atlas;
|
||||||
mod data;
|
mod data;
|
||||||
|
mod page;
|
||||||
mod primitive;
|
mod primitive;
|
||||||
mod texture;
|
mod texture;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -21,42 +20,63 @@ pub use atlas::*;
|
|||||||
pub use data::{Mask, MaskIdx};
|
pub use data::{Mask, MaskIdx};
|
||||||
pub use primitive::*;
|
pub use primitive::*;
|
||||||
|
|
||||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
uniform_group: BindGroup,
|
shared_layout: BindGroupLayout,
|
||||||
primitive_layout: BindGroupLayout,
|
shared_group: BindGroup,
|
||||||
rsc_layout: BindGroupLayout,
|
format: TextureFormat,
|
||||||
rsc_group: BindGroup,
|
|
||||||
|
|
||||||
pipeline: RenderPipeline,
|
/// One per registered primitive, in id order.
|
||||||
|
primitives: Vec<PrimitivePipeline>,
|
||||||
|
|
||||||
layers: HashMap<usize, RenderLayer>,
|
layers: HashMap<usize, RenderLayer>,
|
||||||
active: Vec<usize>,
|
active: Vec<usize>,
|
||||||
window_buffer: Buffer,
|
window_buffer: Buffer,
|
||||||
textures: GpuTextures,
|
|
||||||
masks: ArrBuf<Mask>,
|
masks: ArrBuf<Mask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
struct RenderLayer {
|
||||||
|
/// One per registered primitive, `None` where this layer draws none.
|
||||||
|
primitives: Vec<Option<ListBuffers>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What draws one registered primitive.
|
||||||
|
struct PrimitivePipeline {
|
||||||
|
data_layout: BindGroupLayout,
|
||||||
|
pipeline: RenderPipeline,
|
||||||
|
render: Box<dyn PrimitiveRender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One list's vertex buffer and the data its shader reads.
|
||||||
|
struct ListBuffers {
|
||||||
instance: ArrBuf<PrimitiveInstance>,
|
instance: ArrBuf<PrimitiveInstance>,
|
||||||
primitives: PrimitiveBuffers,
|
data: ArrBuf<u8>,
|
||||||
primitive_group: BindGroup,
|
group: Option<BindGroup>,
|
||||||
|
/// What the primitive asked to keep per instance, if anything.
|
||||||
|
bindings: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderNode {
|
impl UiRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_bind_group(0, &self.shared_group, &[]);
|
||||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
|
||||||
pass.set_bind_group(2, &self.rsc_group, &[]);
|
|
||||||
for i in &self.active {
|
for i in &self.active {
|
||||||
let layer = &self.layers[i];
|
let layer = &self.layers[i];
|
||||||
if layer.instance.len() == 0 {
|
for (id, list) in layer.primitives.iter().enumerate() {
|
||||||
continue;
|
let Some(list) = list else { continue };
|
||||||
|
let Some(group) = &list.group else { continue };
|
||||||
|
let primitive = &self.primitives[id];
|
||||||
|
pass.set_pipeline(&primitive.pipeline);
|
||||||
|
pass.set_bind_group(1, group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
|
||||||
|
primitive.render.draw(
|
||||||
|
pass,
|
||||||
|
ListDraw {
|
||||||
|
instances: list.instance.len() as u32,
|
||||||
|
bindings: &list.bindings,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
|
||||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
|
||||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,55 +87,56 @@ impl UiRenderNode {
|
|||||||
ui: &mut UiData,
|
ui: &mut UiData,
|
||||||
ui_render: &mut UiRenderState,
|
ui_render: &mut UiRenderState,
|
||||||
) {
|
) {
|
||||||
|
// Before the layers: each list is given its pipeline's data layout.
|
||||||
|
self.build_pipelines(device, queue, &ui.primitives);
|
||||||
self.active.clear();
|
self.active.clear();
|
||||||
for (i, primitives) in ui_render.layers.iter_mut() {
|
for (i, draws) in ui_render.layers.iter_mut() {
|
||||||
self.active.push(i);
|
self.active.push(i);
|
||||||
for change in primitives.apply_free() {
|
for change in draws.apply_free() {
|
||||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
||||||
for h in &mut inst.primitives {
|
for h in &mut inst.primitives {
|
||||||
if h.layer == i && h.inst_idx == change.old {
|
if h.layer == i && h.kind == change.kind && h.inst_idx == change.old {
|
||||||
h.inst_idx = change.new;
|
h.inst_idx = change.new;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
|
||||||
let primitives = PrimitiveBuffers::new(device);
|
if draws.updated {
|
||||||
let primitive_group =
|
let lists = draws.primitives();
|
||||||
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
// The zip would otherwise skip a list with no pipeline.
|
||||||
RenderLayer {
|
assert!(lists.len() <= self.primitives.len());
|
||||||
instance: ArrBuf::new(
|
rlayer.primitives.resize_with(lists.len(), || None);
|
||||||
device,
|
for ((buffers, list), primitive) in rlayer
|
||||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
.primitives
|
||||||
"instance",
|
.iter_mut()
|
||||||
),
|
.zip(lists)
|
||||||
primitives,
|
.zip(&self.primitives)
|
||||||
primitive_group,
|
{
|
||||||
|
let Some(list) = list else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
buffers
|
||||||
|
.get_or_insert_with(|| ListBuffers::new(device))
|
||||||
|
.update(device, queue, primitive, list);
|
||||||
}
|
}
|
||||||
});
|
draws.updated = false;
|
||||||
if primitives.updated {
|
|
||||||
rlayer
|
|
||||||
.instance
|
|
||||||
.update(device, queue, primitives.instances());
|
|
||||||
rlayer.primitives.update(device, queue, primitives.data());
|
|
||||||
rlayer.primitive_group = Self::primitive_group(
|
|
||||||
device,
|
|
||||||
&self.primitive_layout,
|
|
||||||
rlayer.primitives.buffers(),
|
|
||||||
);
|
|
||||||
primitives.updated = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut changed = false;
|
for primitive in &mut self.primitives {
|
||||||
changed |= self.textures.update(&mut ui.textures);
|
primitive.render.update(ui);
|
||||||
|
}
|
||||||
if ui.masks.changed {
|
if ui.masks.changed {
|
||||||
ui.masks.changed = false;
|
ui.masks.changed = false;
|
||||||
self.masks.update(device, queue, &ui.masks[..]);
|
if self.masks.update(device, queue, &ui.masks[..]) {
|
||||||
changed = true;
|
self.shared_group = Self::shared_group(
|
||||||
|
device,
|
||||||
|
&self.shared_layout,
|
||||||
|
&self.window_buffer,
|
||||||
|
&self.masks,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if changed {
|
|
||||||
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,17 +149,7 @@ impl UiRenderNode {
|
|||||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
||||||
device: &Device,
|
|
||||||
queue: &Queue,
|
|
||||||
config: &SurfaceConfiguration,
|
|
||||||
limits: UiLimits,
|
|
||||||
) -> Self {
|
|
||||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
|
||||||
label: Some("UI Shape Shader"),
|
|
||||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let window_uniform = WindowUniform {
|
let window_uniform = WindowUniform {
|
||||||
width: config.width as f32,
|
width: config.width as f32,
|
||||||
height: config.height as f32,
|
height: config.height as f32,
|
||||||
@@ -149,67 +160,73 @@ impl UiRenderNode {
|
|||||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let shared_layout = Self::shared_layout(device);
|
||||||
entries: &[BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Uniform,
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("window"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
|
||||||
|
|
||||||
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: i as u32,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
label: Some("primitive"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let tex_manager = GpuTextures::new(device, queue);
|
|
||||||
let masks = ArrBuf::new(
|
let masks = ArrBuf::new(
|
||||||
device,
|
device,
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
"ui masks",
|
"ui masks",
|
||||||
);
|
);
|
||||||
|
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
|
||||||
|
|
||||||
let rsc_layout = Self::rsc_layout(device, &limits);
|
Self {
|
||||||
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
shared_layout,
|
||||||
|
shared_group,
|
||||||
|
format: config.format,
|
||||||
|
primitives: Vec::new(),
|
||||||
|
window_buffer,
|
||||||
|
layers: HashMap::default(),
|
||||||
|
active: Vec::new(),
|
||||||
|
masks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
/// Compiles a pipeline for every primitive registered since the last call.
|
||||||
label: Some("UI Shape Pipeline Layout"),
|
/// Sources only ever arrive at the end, so an id keeps its pipeline.
|
||||||
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
|
fn build_pipelines(&mut self, device: &Device, queue: &Queue, registry: &PrimitiveRegistry) {
|
||||||
|
for source in ®istry.sources()[self.primitives.len()..] {
|
||||||
|
let render = (source.render)(device, queue);
|
||||||
|
let data_layout = Self::data_layout(device, source.stride);
|
||||||
|
let mut groups = vec![Some(&self.shared_layout), Some(&data_layout)];
|
||||||
|
groups.extend(render.layout().map(Some));
|
||||||
|
let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
|
label: Some(source.label),
|
||||||
|
bind_group_layouts: &groups,
|
||||||
immediate_size: 0,
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
let pipeline = Self::pipeline(device, &layout, self.format, source.wgsl, source.label);
|
||||||
label: Some("UI Shape Pipeline"),
|
self.primitives.push(PrimitivePipeline {
|
||||||
layout: Some(&pipeline_layout),
|
data_layout,
|
||||||
|
pipeline,
|
||||||
|
render,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipeline(
|
||||||
|
device: &Device,
|
||||||
|
layout: &PipelineLayout,
|
||||||
|
format: TextureFormat,
|
||||||
|
wgsl: &str,
|
||||||
|
label: &str,
|
||||||
|
) -> RenderPipeline {
|
||||||
|
let module = device.create_shader_module(ShaderModuleDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
source: ShaderSource::Wgsl(format!("{PRELUDE}\n{wgsl}").into()),
|
||||||
|
});
|
||||||
|
device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
layout: Some(layout),
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: &shader,
|
module: &module,
|
||||||
entry_point: Some("vs_main"),
|
entry_point: Some("vs_main"),
|
||||||
buffers: &[PrimitiveInstance::desc()],
|
buffers: &[Some(PrimitiveInstance::desc())],
|
||||||
compilation_options: Default::default(),
|
compilation_options: Default::default(),
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
module: &shader,
|
module: &module,
|
||||||
entry_point: Some("fs_main"),
|
entry_point: Some("fs_main"),
|
||||||
targets: &[Some(ColorTargetState {
|
targets: &[Some(ColorTargetState {
|
||||||
format: config.format,
|
format,
|
||||||
blend: Some(BlendState::ALPHA_BLENDING),
|
blend: Some(BlendState::ALPHA_BLENDING),
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -232,90 +249,42 @@ impl UiRenderNode {
|
|||||||
},
|
},
|
||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
uniform_group,
|
|
||||||
primitive_layout,
|
|
||||||
rsc_layout,
|
|
||||||
rsc_group,
|
|
||||||
pipeline,
|
|
||||||
window_buffer,
|
|
||||||
layers: HashMap::default(),
|
|
||||||
active: Vec::new(),
|
|
||||||
textures: tex_manager,
|
|
||||||
masks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind_group_0(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
window_buffer: &Buffer,
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: window_buffer.as_entire_binding(),
|
|
||||||
}],
|
|
||||||
label: Some("ui window"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn primitive_group(
|
/// What every draw in the ui is given: the window and the masks.
|
||||||
device: &Device,
|
fn shared_layout(device: &Device) -> BindGroupLayout {
|
||||||
layout: &BindGroupLayout,
|
|
||||||
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
|
||||||
binding,
|
|
||||||
resource: buf.as_entire_binding(),
|
|
||||||
}),
|
|
||||||
label: Some("ui primitives"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
|
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Texture {
|
ty: BindingType::Buffer {
|
||||||
sample_type: TextureSampleType::Float { filterable: false },
|
ty: BufferBindingType::Uniform,
|
||||||
view_dimension: TextureViewDimension::D2,
|
has_dynamic_offset: false,
|
||||||
multisampled: false,
|
min_binding_size: BufferSize::new(size_of::<WindowUniform>() as u64),
|
||||||
},
|
},
|
||||||
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
count: None,
|
||||||
},
|
},
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
|
||||||
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
|
||||||
},
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 2,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: None,
|
min_binding_size: BufferSize::new(size_of::<Mask>() as u64),
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui rsc"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rsc_group(
|
fn shared_group(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
tex_manager: &GpuTextures,
|
window: &Buffer,
|
||||||
masks: &ArrBuf<Mask>,
|
masks: &ArrBuf<Mask>,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
@@ -323,45 +292,85 @@ impl UiRenderNode {
|
|||||||
entries: &[
|
entries: &[
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
resource: window.as_entire_binding(),
|
||||||
},
|
},
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
|
||||||
},
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 2,
|
|
||||||
resource: masks.buffer.as_entire_binding(),
|
resource: masks.buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui rsc"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_count(&self) -> usize {
|
/// Layout for a list of one primitive's data. Every size in the ui is
|
||||||
self.textures.view_count()
|
/// stated, so "is the buffer big enough for one entry?" is answered when
|
||||||
|
/// the bind group is made; a `None` size is wgpu's to check on every draw.
|
||||||
|
fn data_layout(device: &Device, stride: u64) -> BindGroupLayout {
|
||||||
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: BufferSize::new(stride),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: Some("ui primitive data"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiLimits {
|
impl RenderLayer {
|
||||||
max_textures: u32,
|
fn new() -> Self {
|
||||||
max_samplers: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UiLimits {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
max_textures: 100000,
|
primitives: Vec::new(),
|
||||||
max_samplers: 1000,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiLimits {
|
impl ListBuffers {
|
||||||
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
fn new(device: &Device) -> Self {
|
||||||
self.max_textures + self.max_samplers
|
Self {
|
||||||
|
instance: ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||||
|
"instance",
|
||||||
|
),
|
||||||
|
data: ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
"primitive data",
|
||||||
|
),
|
||||||
|
group: None,
|
||||||
|
bindings: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
device: &Device,
|
||||||
|
queue: &Queue,
|
||||||
|
primitive: &PrimitivePipeline,
|
||||||
|
list: &InstanceList,
|
||||||
|
) {
|
||||||
|
self.bindings.clear();
|
||||||
|
primitive.render.instance_bindings(list, &mut self.bindings);
|
||||||
|
self.instance.update(device, queue, list.instances());
|
||||||
|
let resized = self.data.update(device, queue, list.data());
|
||||||
|
if list.instances().is_empty() {
|
||||||
|
self.group = None;
|
||||||
|
} else if resized || self.group.is_none() {
|
||||||
|
self.group = Some(device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout: &primitive.data_layout,
|
||||||
|
entries: &[BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: self.data.buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("ui primitive data"),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
|
|
||||||
self.max_samplers
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
use wgpu::*;
|
||||||
|
|
||||||
|
use crate::{GlyphAtlas, UiData};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
atlas::PAGE,
|
||||||
|
primitive::{ListDraw, PrimitiveRender},
|
||||||
|
texture::{default_sampler, sampled_group, sampled_layout, write_region},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Draws glyphs from the atlas, which it owns: one array texture bound once
|
||||||
|
/// for a whole list, since every glyph in it reads the same pages.
|
||||||
|
pub struct GlyphRender {
|
||||||
|
pages: GpuPages,
|
||||||
|
layout: BindGroupLayout,
|
||||||
|
sampler: Sampler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlyphRender {
|
||||||
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
|
let layout = sampled_layout(device, TextureViewDimension::D2Array, "ui atlas");
|
||||||
|
let sampler = default_sampler(device);
|
||||||
|
Self {
|
||||||
|
pages: GpuPages::new(device, queue, &layout, &sampler),
|
||||||
|
layout,
|
||||||
|
sampler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveRender for GlyphRender {
|
||||||
|
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||||
|
Some(&self.layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ui: &mut UiData) {
|
||||||
|
self.pages
|
||||||
|
.update(&mut ui.text.atlas, &self.layout, &self.sampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||||
|
pass.set_bind_group(2, self.pages.group(), &[]);
|
||||||
|
pass.draw(0..4, 0..list.instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
||||||
|
/// `GlyphAtlas` packs.
|
||||||
|
///
|
||||||
|
/// One array rather than a texture per page because a layer index is ordinary
|
||||||
|
/// Vulkan 1.0 / GLES sampling, where a `binding_array` would need
|
||||||
|
/// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack.
|
||||||
|
pub struct GpuPages {
|
||||||
|
device: Device,
|
||||||
|
queue: Queue,
|
||||||
|
texture: Texture,
|
||||||
|
group: BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpuPages {
|
||||||
|
pub fn new(
|
||||||
|
device: &Device,
|
||||||
|
queue: &Queue,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
sampler: &Sampler,
|
||||||
|
) -> Self {
|
||||||
|
let texture = create_array(device, 1);
|
||||||
|
Self {
|
||||||
|
device: device.clone(),
|
||||||
|
queue: queue.clone(),
|
||||||
|
group: atlas_group(device, layout, &texture, sampler),
|
||||||
|
texture,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||||
|
if atlas.page_count() > self.texture.depth_or_array_layers() {
|
||||||
|
self.grow(atlas.page_count(), layout, sampler);
|
||||||
|
}
|
||||||
|
for (upload, page) in atlas.uploads() {
|
||||||
|
let dst = TexelCopyTextureInfo {
|
||||||
|
texture: &self.texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Origin3d {
|
||||||
|
x: upload.rect.x,
|
||||||
|
y: upload.rect.y,
|
||||||
|
z: upload.layer,
|
||||||
|
},
|
||||||
|
aspect: TextureAspect::All,
|
||||||
|
};
|
||||||
|
write_region(&self.queue, dst, page, upload.rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group(&self) -> &BindGroup {
|
||||||
|
&self.group
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
||||||
|
/// The new texture stales the group, so that is rebuilt here.
|
||||||
|
fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||||
|
let old = self.texture.depth_or_array_layers();
|
||||||
|
let mut layers = old;
|
||||||
|
while layers < needed {
|
||||||
|
layers *= 2;
|
||||||
|
}
|
||||||
|
let texture = create_array(&self.device, layers);
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&CommandEncoderDescriptor {
|
||||||
|
label: Some("atlas grow"),
|
||||||
|
});
|
||||||
|
encoder.copy_texture_to_texture(
|
||||||
|
self.texture.as_image_copy(),
|
||||||
|
texture.as_image_copy(),
|
||||||
|
Extent3d {
|
||||||
|
width: PAGE,
|
||||||
|
height: PAGE,
|
||||||
|
depth_or_array_layers: old,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
self.group = atlas_group(&self.device, layout, &texture, sampler);
|
||||||
|
self.texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn atlas_group(
|
||||||
|
device: &Device,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
texture: &Texture,
|
||||||
|
sampler: &Sampler,
|
||||||
|
) -> BindGroup {
|
||||||
|
let view = texture.create_view(&TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::D2Array),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
sampled_group(device, layout, &view, sampler, "ui atlas")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_array(device: &Device, layers: u32) -> Texture {
|
||||||
|
device.create_texture(&TextureDescriptor {
|
||||||
|
label: Some("glyph atlas"),
|
||||||
|
size: Extent3d {
|
||||||
|
width: PAGE,
|
||||||
|
height: PAGE,
|
||||||
|
depth_or_array_layers: layers,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rgba8Unorm,
|
||||||
|
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
|
||||||
|
view_formats: &[],
|
||||||
|
})
|
||||||
|
}
|
||||||
+304
-204
@@ -1,115 +1,247 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::{any::TypeId, marker::PhantomData};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, UiRegion, WidgetId,
|
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
ArrBuf,
|
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
data::{MaskIdx, PrimitiveInstance},
|
||||||
|
page::GlyphRender,
|
||||||
|
texture::ImageRender,
|
||||||
},
|
},
|
||||||
util::Vec2,
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
use wgpu::*;
|
use wgpu::{BindGroupLayout, Device, Queue, RenderPass};
|
||||||
|
|
||||||
pub struct Primitives {
|
/// One instance of a primitive, laid out as the struct its shader reads.
|
||||||
|
///
|
||||||
|
/// The type carries its own shader, so drawing one is all the wiring it needs:
|
||||||
|
/// its list, free list, buffers and pipeline follow from being registered.
|
||||||
|
pub trait Primitive: Pod + 'static {
|
||||||
|
/// Compiled after `prelude.wgsl`, which states what it declares and what
|
||||||
|
/// it is given.
|
||||||
|
const WGSL: &'static str;
|
||||||
|
|
||||||
|
/// Made once, the first time the renderer sees this primitive. It owns
|
||||||
|
/// whatever the shader samples and records the primitive's own draws; the
|
||||||
|
/// default owns nothing and draws every instance in one call.
|
||||||
|
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let _ = (device, queue);
|
||||||
|
Box::new(Instanced)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The renderer's half of a primitive: what it samples, what it uploads, and
|
||||||
|
/// what draws it records.
|
||||||
|
///
|
||||||
|
/// Everything a draw shares -- the pipeline, the window and masks, the list's
|
||||||
|
/// own data and instance buffer -- is set before this is called. What is left
|
||||||
|
/// is what only this primitive knows: its group 2, and how many draws its
|
||||||
|
/// instances are.
|
||||||
|
pub trait PrimitiveRender {
|
||||||
|
/// The layout its shader reads at group 2. `None` for a primitive whose
|
||||||
|
/// shader samples nothing, whose pipeline then has no group 2 at all.
|
||||||
|
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uploads whatever this primitive owns, once a frame, before any draw.
|
||||||
|
fn update(&mut self, ui: &mut UiData) {
|
||||||
|
let _ = ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps what the primitive needs per instance at draw time, read from
|
||||||
|
/// the list's own data. A primitive that binds nothing per instance --
|
||||||
|
/// most of them -- leaves this empty and draws in one call.
|
||||||
|
fn instance_bindings(&self, list: &InstanceList, out: &mut Vec<u32>) {
|
||||||
|
let _ = (list, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a `PrimitiveRender` draws: this list's instances, and whatever
|
||||||
|
/// `instance_bindings` kept for them.
|
||||||
|
pub struct ListDraw<'a> {
|
||||||
|
pub instances: u32,
|
||||||
|
pub bindings: &'a [u32],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default: nothing sampled, every instance in one call.
|
||||||
|
pub struct Instanced;
|
||||||
|
|
||||||
|
impl PrimitiveRender for Instanced {
|
||||||
|
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||||
|
pass.draw(0..4, 0..list.instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which registered primitive an instance is.
|
||||||
|
pub struct PrimitiveKind<P> {
|
||||||
|
id: u32,
|
||||||
|
_p: PhantomData<fn(P)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> PrimitiveKind<P> {
|
||||||
|
fn new(id: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
_p: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Clone for PrimitiveKind<P> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Copy for PrimitiveKind<P> {}
|
||||||
|
|
||||||
|
/// Every primitive a ui can draw, in the order they were first drawn.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PrimitiveRegistry {
|
||||||
|
kinds: Vec<PrimitiveSource>,
|
||||||
|
ids: HashMap<TypeId, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveSource {
|
||||||
|
pub wgsl: &'static str,
|
||||||
|
pub label: &'static str,
|
||||||
|
/// Size of one instance's entry, stated as the data binding's minimum.
|
||||||
|
pub stride: u64,
|
||||||
|
pub render: fn(&Device, &Queue) -> Box<dyn PrimitiveRender>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveRegistry {
|
||||||
|
/// Registers `P` if this is the first time it has been drawn.
|
||||||
|
pub fn kind<P: Primitive>(&mut self) -> PrimitiveKind<P> {
|
||||||
|
let Self { kinds, ids } = self;
|
||||||
|
let id = *ids.entry(TypeId::of::<P>()).or_insert_with(|| {
|
||||||
|
kinds.push(PrimitiveSource {
|
||||||
|
wgsl: P::WGSL,
|
||||||
|
label: std::any::type_name::<P>(),
|
||||||
|
stride: size_of::<P>() as u64,
|
||||||
|
render: P::render,
|
||||||
|
});
|
||||||
|
kinds.len() as u32 - 1
|
||||||
|
});
|
||||||
|
PrimitiveKind::new(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sources(&self) -> &[PrimitiveSource] {
|
||||||
|
&self.kinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One registered primitive's instances in one layer. Everything per-instance
|
||||||
|
/// rides here, so it stays in step through a `swap_remove`.
|
||||||
|
pub struct InstanceList {
|
||||||
instances: Vec<PrimitiveInstance>,
|
instances: Vec<PrimitiveInstance>,
|
||||||
|
/// The widget each instance belongs to, for renumbering its handles.
|
||||||
assoc: Vec<WidgetId>,
|
assoc: Vec<WidgetId>,
|
||||||
data: PrimitiveData,
|
|
||||||
free: Vec<usize>,
|
free: Vec<usize>,
|
||||||
|
/// `stride` bytes of the primitive's own data per instance.
|
||||||
|
data: Vec<u8>,
|
||||||
|
/// From the type the list was made for, so a write is never checked.
|
||||||
|
stride: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceList {
|
||||||
|
fn new<P: Primitive>() -> Self {
|
||||||
|
Self {
|
||||||
|
instances: Vec::new(),
|
||||||
|
assoc: Vec::new(),
|
||||||
|
free: Vec::new(),
|
||||||
|
data: Vec::new(),
|
||||||
|
stride: size_of::<P>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instances(&self) -> &[PrimitiveInstance] {
|
||||||
|
&self.instances
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stride(&self) -> usize {
|
||||||
|
self.stride
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize {
|
||||||
|
if let Some(i) = self.free.pop() {
|
||||||
|
self.instances[i] = inst;
|
||||||
|
self.assoc[i] = id;
|
||||||
|
self.data[i * self.stride..][..self.stride].copy_from_slice(data);
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
let i = self.instances.len();
|
||||||
|
self.instances.push(inst);
|
||||||
|
self.assoc.push(id);
|
||||||
|
self.data.extend_from_slice(data);
|
||||||
|
i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&mut self, i: usize) -> MaskIdx {
|
||||||
|
self.free.push(i);
|
||||||
|
self.instances[i].mask_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_free(&mut self, kind: u32) -> impl Iterator<Item = PrimitiveChange> {
|
||||||
|
self.free.sort_by(|a, b| b.cmp(a));
|
||||||
|
let instances = &mut self.instances;
|
||||||
|
let assoc = &mut self.assoc;
|
||||||
|
let data = &mut self.data;
|
||||||
|
let stride = self.stride;
|
||||||
|
self.free.drain(..).filter_map(move |i| {
|
||||||
|
instances.swap_remove(i);
|
||||||
|
assoc.swap_remove(i);
|
||||||
|
let last = instances.len();
|
||||||
|
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
||||||
|
data.truncate(last * stride);
|
||||||
|
if i == last {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let id = assoc[i];
|
||||||
|
Some(PrimitiveChange {
|
||||||
|
id,
|
||||||
|
kind,
|
||||||
|
old: last,
|
||||||
|
new: i,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Everything one layer draws, one list per registered primitive.
|
||||||
|
pub struct LayerDraws {
|
||||||
|
/// `None` until this layer draws that primitive, because only the write
|
||||||
|
/// knows the type the list is for.
|
||||||
|
primitives: Vec<Option<InstanceList>>,
|
||||||
pub updated: bool,
|
pub updated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Primitives {
|
impl Default for LayerDraws {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
instances: Default::default(),
|
primitives: Vec::new(),
|
||||||
assoc: Default::default(),
|
|
||||||
data: Default::default(),
|
|
||||||
free: Vec::new(),
|
|
||||||
updated: true,
|
updated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Primitive: Pod {
|
impl LayerDraws {
|
||||||
const BINDING: u32;
|
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! primitives {
|
|
||||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PrimitiveData {
|
|
||||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveBuffers {
|
|
||||||
$($name: ArrBuf<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
|
||||||
$(self.$name.update(device, queue, &data.$name);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub const LEN: usize = primitives!(@count $($name)*);
|
|
||||||
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
|
||||||
[
|
|
||||||
$((<$ty>::BINDING, &self.$name.buffer),)*
|
|
||||||
]
|
|
||||||
}
|
|
||||||
pub fn new(device: &Device) -> Self {
|
|
||||||
Self {
|
|
||||||
$($name: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
|
||||||
stringify!($name),
|
|
||||||
),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveData {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
$(self.$name.clear();)*
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, binding: u32, idx: usize) {
|
|
||||||
match binding {
|
|
||||||
$(<$ty>::BINDING => self.$name.free(idx),)*
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(
|
|
||||||
unsafe impl bytemuck::Pod for $ty {}
|
|
||||||
unsafe impl bytemuck::Zeroable for $ty {}
|
|
||||||
impl Primitive for $ty {
|
|
||||||
const BINDING: u32 = $binding;
|
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
|
||||||
&mut data.$name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t)+) };
|
|
||||||
(@count $t:tt) => { 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveInst<P> {
|
|
||||||
pub id: WidgetId,
|
|
||||||
pub primitive: P,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Primitives {
|
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: usize,
|
layer: usize,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
|
kind,
|
||||||
id,
|
id,
|
||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
@@ -117,65 +249,67 @@ impl Primitives {
|
|||||||
}: PrimitiveInst<P>,
|
}: PrimitiveInst<P>,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
let vec = P::vec(&mut self.data);
|
// Grown on first use rather than sized from the registry, which a
|
||||||
let i = vec.add(primitive);
|
// layer cannot see.
|
||||||
let inst = PrimitiveInstance {
|
if self.primitives.len() <= kind.id as usize {
|
||||||
region,
|
self.primitives.resize_with(kind.id as usize + 1, || None);
|
||||||
idx: i as u32,
|
}
|
||||||
mask_idx,
|
let inst_idx = self.primitives[kind.id as usize]
|
||||||
binding: P::BINDING,
|
.get_or_insert_with(InstanceList::new::<P>)
|
||||||
};
|
.push(
|
||||||
let inst_i = if let Some(i) = self.free.pop() {
|
id,
|
||||||
self.instances[i] = inst;
|
PrimitiveInstance { region, mask_idx },
|
||||||
self.assoc[i] = id;
|
bytemuck::bytes_of(&primitive),
|
||||||
i
|
);
|
||||||
} else {
|
PrimitiveHandle {
|
||||||
let i = self.instances.len();
|
layer,
|
||||||
self.instances.push(inst);
|
kind: kind.id,
|
||||||
self.assoc.push(id);
|
inst_idx,
|
||||||
i
|
}
|
||||||
};
|
|
||||||
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns (old index, new index)
|
pub fn primitives(&self) -> &[Option<InstanceList>] {
|
||||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
&self.primitives
|
||||||
self.free.sort_by(|a, b| b.cmp(a));
|
|
||||||
self.free.drain(..).filter_map(|i| {
|
|
||||||
self.instances.swap_remove(i);
|
|
||||||
self.assoc.swap_remove(i);
|
|
||||||
if i == self.instances.len() {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
let id = self.assoc[i];
|
|
||||||
let old = self.instances.len();
|
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||||
Some(PrimitiveChange { id, old, new: i })
|
self.primitives
|
||||||
})
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
|
||||||
|
.flat_map(|(kind, list)| list.apply_free(kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
self.data.free(h.binding, h.data_idx);
|
self.list(h).free(h.inst_idx)
|
||||||
self.free.push(h.inst_idx);
|
|
||||||
self.instances[h.inst_idx].mask_idx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> &PrimitiveData {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
|
||||||
&self.instances
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
&mut self.instances[h.inst_idx].region
|
&mut self.list(h).instances[h.inst_idx].region
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A handle is only ever made by `write`, which is what created the list.
|
||||||
|
fn list(&mut self, h: &PrimitiveHandle) -> &mut InstanceList {
|
||||||
|
self.primitives[h.kind as usize]
|
||||||
|
.as_mut()
|
||||||
|
.expect("handle names a primitive this layer never drew")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveInst<P> {
|
||||||
|
pub kind: PrimitiveKind<P>,
|
||||||
|
pub id: WidgetId,
|
||||||
|
pub primitive: P,
|
||||||
|
pub region: UiRegion,
|
||||||
|
pub mask_idx: MaskIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveChange {
|
pub struct PrimitiveChange {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
|
/// Which registered primitive's list moved, since they index separately.
|
||||||
|
pub kind: u32,
|
||||||
pub old: usize,
|
pub old: usize,
|
||||||
pub new: usize,
|
pub new: usize,
|
||||||
}
|
}
|
||||||
@@ -183,30 +317,12 @@ pub struct PrimitiveChange {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PrimitiveHandle {
|
pub struct PrimitiveHandle {
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
|
pub kind: u32,
|
||||||
pub inst_idx: usize,
|
pub inst_idx: usize,
|
||||||
pub data_idx: usize,
|
|
||||||
pub binding: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveHandle {
|
|
||||||
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
layer,
|
|
||||||
inst_idx,
|
|
||||||
data_idx,
|
|
||||||
binding: P::BINDING,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primitives!(
|
|
||||||
rects: RectPrimitive => 0,
|
|
||||||
textures: TexturePrimitive => 1,
|
|
||||||
glyphs: GlyphPrimitive => 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct RectPrimitive {
|
pub struct RectPrimitive {
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
@@ -214,6 +330,10 @@ pub struct RectPrimitive {
|
|||||||
pub inner_radius: f32,
|
pub inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Primitive for RectPrimitive {
|
||||||
|
const WGSL: &'static str = include_str!("shader/rect.wgsl");
|
||||||
|
}
|
||||||
|
|
||||||
impl RectPrimitive {
|
impl RectPrimitive {
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
pub fn color(color: Color<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -225,71 +345,51 @@ impl RectPrimitive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
||||||
#[derive(Debug, Copy, Clone)]
|
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
||||||
pub struct TexturePrimitive {
|
#[repr(C, align(8))]
|
||||||
pub view_idx: u32,
|
|
||||||
pub sampler_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct GlyphPrimitive {
|
pub struct GlyphPrimitive {
|
||||||
pub uv_min: Vec2,
|
pub uv_min: Vec2,
|
||||||
pub uv_max: Vec2,
|
pub uv_max: Vec2,
|
||||||
pub view_idx: u32,
|
/// Which atlas array layer this glyph is on.
|
||||||
pub sampler_idx: u32,
|
pub layer: u32,
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveVec<T> {
|
// Manual rather than derived: the align(8) leaves four bytes of padding, which
|
||||||
vec: Vec<T>,
|
// is how WGSL lays the struct out.
|
||||||
free: Vec<usize>,
|
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
||||||
|
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
||||||
|
impl Primitive for GlyphPrimitive {
|
||||||
|
const WGSL: &'static str = include_str!("shader/glyph.wgsl");
|
||||||
|
|
||||||
|
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
||||||
|
Box::new(GlyphRender::new(device, queue))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PrimitiveVec<T> {
|
/// One drawn image. Its shader reads nothing per instance; the slot names the
|
||||||
pub fn new() -> Self {
|
/// texture to bind for it.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct TexturePrimitive {
|
||||||
|
pub slot: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitive for TexturePrimitive {
|
||||||
|
const WGSL: &'static str = include_str!("shader/texture.wgsl");
|
||||||
|
|
||||||
|
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
||||||
|
Box::new(ImageRender::new(device, queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TextureHandle> for TexturePrimitive {
|
||||||
|
fn from(handle: &TextureHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vec: Vec::new(),
|
slot: handle.slot(),
|
||||||
free: Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add(&mut self, t: T) -> usize {
|
|
||||||
if let Some(i) = self.free.pop() {
|
|
||||||
self.vec[i] = t;
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.vec.len();
|
|
||||||
self.vec.push(t);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, i: usize) {
|
|
||||||
self.free.push(i);
|
|
||||||
}
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.free.clear();
|
|
||||||
self.vec.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for PrimitiveVec<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for PrimitiveVec<T> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for PrimitiveVec<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.vec
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
const RECT: u32 = 0u;
|
|
||||||
const TEXTURE: u32 = 1u;
|
|
||||||
const GLYPH: u32 = 2u;
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> window: WindowUniform;
|
|
||||||
@group(1) @binding(RECT)
|
|
||||||
var<storage> rects: array<Rect>;
|
|
||||||
@group(1) @binding(TEXTURE)
|
|
||||||
var<storage> textures: array<TextureInfo>;
|
|
||||||
@group(1) @binding(GLYPH)
|
|
||||||
var<storage> glyphs: array<GlyphInfo>;
|
|
||||||
|
|
||||||
struct Rect {
|
|
||||||
color: u32,
|
|
||||||
radius: f32,
|
|
||||||
thickness: f32,
|
|
||||||
inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextureInfo {
|
|
||||||
view_idx: u32,
|
|
||||||
sampler_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlyphInfo {
|
|
||||||
uv_min: vec2<f32>,
|
|
||||||
uv_max: vec2<f32>,
|
|
||||||
view_idx: u32,
|
|
||||||
sampler_idx: u32,
|
|
||||||
color: u32,
|
|
||||||
flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Mask {
|
|
||||||
x: UiSpan,
|
|
||||||
y: UiSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiSpan {
|
|
||||||
start: UiScalar,
|
|
||||||
end: UiScalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiScalar {
|
|
||||||
rel: f32,
|
|
||||||
abs: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiVec2 {
|
|
||||||
rel: vec2<f32>,
|
|
||||||
abs: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var views: binding_array<texture_2d<f32>>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samplers: binding_array<sampler>;
|
|
||||||
@group(2) @binding(2)
|
|
||||||
var<storage> masks: array<Mask>;
|
|
||||||
|
|
||||||
struct WindowUniform {
|
|
||||||
dim: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InstanceInput {
|
|
||||||
@location(0) x_start: vec2<f32>,
|
|
||||||
@location(1) x_end: vec2<f32>,
|
|
||||||
@location(2) y_start: vec2<f32>,
|
|
||||||
@location(3) y_end: vec2<f32>,
|
|
||||||
@location(4) binding: u32,
|
|
||||||
@location(5) idx: u32,
|
|
||||||
@location(6) mask_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@location(0) top_left: vec2<f32>,
|
|
||||||
@location(1) bot_right: vec2<f32>,
|
|
||||||
@location(2) uv: vec2<f32>,
|
|
||||||
@location(3) binding: u32,
|
|
||||||
@location(4) idx: u32,
|
|
||||||
@location(5) mask_idx: u32,
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Region {
|
|
||||||
pos: vec2<f32>,
|
|
||||||
uv: vec2<f32>,
|
|
||||||
top_left: vec2<f32>,
|
|
||||||
bot_right: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@builtin(vertex_index) vi: u32,
|
|
||||||
in: InstanceInput,
|
|
||||||
) -> VertexOutput {
|
|
||||||
var out: VertexOutput;
|
|
||||||
|
|
||||||
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
|
||||||
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
|
||||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
|
||||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
|
||||||
|
|
||||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
|
||||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
|
||||||
let size = bot_right - top_left;
|
|
||||||
|
|
||||||
let uv = vec2<f32>(
|
|
||||||
f32(vi % 2u),
|
|
||||||
f32(vi / 2u)
|
|
||||||
);
|
|
||||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
|
||||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
|
||||||
out.uv = uv;
|
|
||||||
out.binding = in.binding;
|
|
||||||
out.idx = in.idx;
|
|
||||||
out.top_left = top_left;
|
|
||||||
out.bot_right = bot_right;
|
|
||||||
out.mask_idx = in.mask_idx;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(
|
|
||||||
in: VertexOutput
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
let pos = in.clip_position.xy;
|
|
||||||
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
|
||||||
let i = in.idx;
|
|
||||||
var color: vec4<f32>;
|
|
||||||
switch in.binding {
|
|
||||||
case RECT: {
|
|
||||||
color = draw_rounded_rect(region, rects[i]);
|
|
||||||
}
|
|
||||||
case TEXTURE: {
|
|
||||||
color = draw_texture(region, textures[i]);
|
|
||||||
}
|
|
||||||
case GLYPH: {
|
|
||||||
color = draw_glyph(region, glyphs[i]);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
color = vec4(1.0, 0.0, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.mask_idx != 4294967295u {
|
|
||||||
let mask = masks[in.mask_idx];
|
|
||||||
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
|
|
||||||
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
|
|
||||||
|
|
||||||
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
|
|
||||||
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
|
|
||||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
|
||||||
color *= 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this seems really inefficient (per frag indexing)?
|
|
||||||
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
|
|
||||||
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_glyph(region: Region, g: GlyphInfo) -> vec4<f32> {
|
|
||||||
let uv = mix(g.uv_min, g.uv_max, region.uv);
|
|
||||||
let texel = textureSample(views[g.view_idx], samplers[g.sampler_idx], uv);
|
|
||||||
if (g.flags & 1u) != 0u {
|
|
||||||
return texel;
|
|
||||||
}
|
|
||||||
var color = unpack4x8unorm(g.color);
|
|
||||||
color.a *= texel.a;
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
|
|
||||||
var color = unpack4x8unorm(rect.color);
|
|
||||||
|
|
||||||
let edge = 0.5;
|
|
||||||
|
|
||||||
let size = region.bot_right - region.top_left;
|
|
||||||
let corner = size / 2.0;
|
|
||||||
let center = region.top_left + corner;
|
|
||||||
|
|
||||||
let dist = distance_from_rect(region.pos, center, corner, rect.radius);
|
|
||||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
|
||||||
|
|
||||||
if rect.thickness > 0.0 {
|
|
||||||
let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius);
|
|
||||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
|
||||||
// vec from center to pixel
|
|
||||||
let p = pixel_pos - rect_center;
|
|
||||||
// vec from inner rect corner to pixel
|
|
||||||
let q = abs(p) - (rect_corner - radius);
|
|
||||||
return length(max(q, vec2(0.0))) - radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Matches `GlyphEntry::IS_COLORED`.
|
||||||
|
const COLORED: u32 = 1u;
|
||||||
|
|
||||||
|
// The glyph atlas, whose array layers are its pages.
|
||||||
|
@group(2) @binding(0)
|
||||||
|
var atlas: texture_2d_array<f32>;
|
||||||
|
@group(2) @binding(1)
|
||||||
|
var samp: sampler;
|
||||||
|
|
||||||
|
struct GlyphInfo {
|
||||||
|
uv_min: vec2<f32>,
|
||||||
|
uv_max: vec2<f32>,
|
||||||
|
// Which layer of the atlas array this glyph's page is.
|
||||||
|
layer: u32,
|
||||||
|
color: u32,
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<storage> glyphs: array<GlyphInfo>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let g = glyphs[in.idx];
|
||||||
|
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
||||||
|
let texel = textureSample(atlas, samp, uv, i32(g.layer));
|
||||||
|
if (g.flags & COLORED) != 0u {
|
||||||
|
return masked(in, texel);
|
||||||
|
}
|
||||||
|
var color = unpack4x8unorm(g.color);
|
||||||
|
color.a *= texel.a;
|
||||||
|
return masked(in, color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Prepended to every primitive's shader, which declares its own instance data
|
||||||
|
// as `var<storage> <name>: array<T>` at group 1 binding 0, and an `fs_main`
|
||||||
|
// shading one instance of it. What it samples, if anything, is bound at group
|
||||||
|
// 2: the texture at binding 0 and the sampler at binding 1.
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> window: WindowUniform;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var<storage> masks: array<Mask>;
|
||||||
|
|
||||||
|
struct WindowUniform {
|
||||||
|
dim: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mask {
|
||||||
|
x: UiSpan,
|
||||||
|
y: UiSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiSpan {
|
||||||
|
start: UiScalar,
|
||||||
|
end: UiScalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiScalar {
|
||||||
|
rel: f32,
|
||||||
|
abs: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstanceInput {
|
||||||
|
@location(0) x_start: vec2<f32>,
|
||||||
|
@location(1) x_end: vec2<f32>,
|
||||||
|
@location(2) y_start: vec2<f32>,
|
||||||
|
@location(3) y_end: vec2<f32>,
|
||||||
|
@location(4) mask_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@location(0) top_left: vec2<f32>,
|
||||||
|
@location(1) bot_right: vec2<f32>,
|
||||||
|
@location(2) uv: vec2<f32>,
|
||||||
|
@location(3) @interpolate(flat) mask_idx: u32,
|
||||||
|
@location(4) @interpolate(flat) idx: u32,
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
@builtin(vertex_index) vi: u32,
|
||||||
|
@builtin(instance_index) ii: u32,
|
||||||
|
in: InstanceInput,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
||||||
|
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
||||||
|
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||||
|
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||||
|
|
||||||
|
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
||||||
|
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
||||||
|
let size = bot_right - top_left;
|
||||||
|
|
||||||
|
let uv = vec2<f32>(
|
||||||
|
f32(vi % 2u),
|
||||||
|
f32(vi / 2u)
|
||||||
|
);
|
||||||
|
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||||
|
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||||
|
out.uv = uv;
|
||||||
|
out.top_left = top_left;
|
||||||
|
out.bot_right = bot_right;
|
||||||
|
out.mask_idx = in.mask_idx;
|
||||||
|
out.idx = ii;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
||||||
|
if in.mask_idx == 4294967295u {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
let mask = masks[in.mask_idx];
|
||||||
|
let tl = vec2(mask.x.start.rel, mask.y.start.rel);
|
||||||
|
let tl_abs = vec2(mask.x.start.abs, mask.y.start.abs);
|
||||||
|
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
||||||
|
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
||||||
|
|
||||||
|
let top_left = floor(tl * window.dim) + floor(tl_abs);
|
||||||
|
let bot_right = floor(br * window.dim) + floor(br_abs);
|
||||||
|
let pos = in.clip_position.xy;
|
||||||
|
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||||
|
return color * 0.0;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
struct Rect {
|
||||||
|
color: u32,
|
||||||
|
radius: f32,
|
||||||
|
thickness: f32,
|
||||||
|
inner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<storage> rects: array<Rect>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let rect = rects[in.idx];
|
||||||
|
var color = unpack4x8unorm(rect.color);
|
||||||
|
|
||||||
|
let edge = 0.5;
|
||||||
|
let size = in.bot_right - in.top_left;
|
||||||
|
let corner = size / 2.0;
|
||||||
|
let center = in.top_left + corner;
|
||||||
|
let pos = in.clip_position.xy;
|
||||||
|
|
||||||
|
let dist = distance_from_rect(pos, center, corner, rect.radius);
|
||||||
|
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||||
|
|
||||||
|
if rect.thickness > 0.0 {
|
||||||
|
let dist2 = distance_from_rect(pos, center, corner - rect.thickness, rect.inner_radius);
|
||||||
|
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked(in, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||||
|
// vec from center to pixel
|
||||||
|
let p = pixel_pos - rect_center;
|
||||||
|
// vec from inner rect corner to pixel
|
||||||
|
let q = abs(p) - (rect_corner - radius);
|
||||||
|
return length(max(q, vec2(0.0))) - radius;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// The image this instance draws, bound for it alone.
|
||||||
|
@group(2) @binding(0)
|
||||||
|
var image: texture_2d<f32>;
|
||||||
|
@group(2) @binding(1)
|
||||||
|
var samp: sampler;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return masked(in, textureSample(image, samp, in.uv));
|
||||||
|
}
|
||||||
+190
-138
@@ -1,119 +1,119 @@
|
|||||||
use image::{DynamicImage, EncodableLayout, GenericImageView};
|
use image::{DynamicImage, EncodableLayout, GenericImageView, RgbaImage};
|
||||||
use wgpu::{util::DeviceExt, *};
|
use wgpu::{util::DeviceExt, *};
|
||||||
|
|
||||||
use crate::{PatchRect, TextureUpdate, Textures};
|
use crate::{
|
||||||
|
PatchRect, TextureUpdate, Textures, UiData,
|
||||||
|
render::{
|
||||||
|
TexturePrimitive,
|
||||||
|
primitive::{ListDraw, PrimitiveRender},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Draws standalone images, which it owns. Each is its own texture, so each
|
||||||
|
/// instance binds its own and is a draw of its own.
|
||||||
|
pub struct ImageRender {
|
||||||
|
textures: GpuTextures,
|
||||||
|
layout: BindGroupLayout,
|
||||||
|
sampler: Sampler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageRender {
|
||||||
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
|
Self {
|
||||||
|
textures: GpuTextures::new(device, queue),
|
||||||
|
layout: sampled_layout(device, TextureViewDimension::D2, "ui image"),
|
||||||
|
sampler: default_sampler(device),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveRender for ImageRender {
|
||||||
|
fn layout(&self) -> Option<&BindGroupLayout> {
|
||||||
|
Some(&self.layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ui: &mut UiData) {
|
||||||
|
self.textures
|
||||||
|
.update(&mut ui.textures, &self.layout, &self.sampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_bindings(&self, list: &super::InstanceList, out: &mut Vec<u32>) {
|
||||||
|
let slots = list
|
||||||
|
.data()
|
||||||
|
.chunks_exact(list.stride())
|
||||||
|
.map(|data| bytemuck::pod_read_unaligned::<TexturePrimitive>(data).slot);
|
||||||
|
out.extend(slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
||||||
|
for (i, &slot) in list.bindings.iter().enumerate() {
|
||||||
|
let Some(image) = self.textures.group(slot) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
pass.set_bind_group(2, image, &[]);
|
||||||
|
pass.draw(0..4, i as u32..i as u32 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The standalone images a ui draws, each its own texture and bind group --
|
||||||
|
/// unlike the glyph atlas in `super::page`, which is one array they share.
|
||||||
pub struct GpuTextures {
|
pub struct GpuTextures {
|
||||||
device: Device,
|
device: Device,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
/// Parallel to `views`; patches require textures rather than views.
|
slots: Vec<Option<ImageGpu>>,
|
||||||
textures: Vec<Option<Texture>>,
|
}
|
||||||
views: Vec<TextureView>,
|
|
||||||
view_count: usize,
|
struct ImageGpu {
|
||||||
samplers: Vec<Sampler>,
|
/// Kept for `patch`, which needs the texture rather than the view.
|
||||||
null_view: TextureView,
|
texture: Texture,
|
||||||
no_views: Vec<TextureView>,
|
group: BindGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuTextures {
|
impl GpuTextures {
|
||||||
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
let mut bindings_changed = false;
|
Self {
|
||||||
|
device: device.clone(),
|
||||||
|
queue: queue.clone(),
|
||||||
|
slots: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout, sampler: &Sampler) {
|
||||||
for update in textures.updates() {
|
for update in textures.updates() {
|
||||||
bindings_changed |= match update {
|
match update {
|
||||||
TextureUpdate::Push(image) => {
|
TextureUpdate::Push(image) => {
|
||||||
self.push(image);
|
let image = self.create(image, layout, sampler);
|
||||||
true
|
self.slots.push(Some(image));
|
||||||
}
|
}
|
||||||
TextureUpdate::Set(i, image) => {
|
TextureUpdate::Set(i, image) => {
|
||||||
self.set(i, image);
|
let image = self.create(image, layout, sampler);
|
||||||
true
|
self.slots[i as usize] = Some(image);
|
||||||
}
|
}
|
||||||
TextureUpdate::Patch(i, rect, image) => {
|
TextureUpdate::Patch(i, rect, image) => self.patch(i, rect, image),
|
||||||
self.patch(i, rect, image);
|
TextureUpdate::PushFree => self.slots.push(None),
|
||||||
false
|
TextureUpdate::SetFree => {}
|
||||||
|
TextureUpdate::Free(i) => self.slots[i as usize] = None,
|
||||||
}
|
}
|
||||||
TextureUpdate::SetFree => {
|
|
||||||
self.view_count += 1;
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
TextureUpdate::Free(i) => {
|
|
||||||
self.free(i);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
TextureUpdate::PushFree => {
|
|
||||||
self.push_free();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
bindings_changed
|
|
||||||
}
|
|
||||||
fn set(&mut self, i: u32, image: &DynamicImage) {
|
|
||||||
self.view_count += 1;
|
|
||||||
let (texture, view) = self.create(image);
|
|
||||||
self.textures[i as usize] = Some(texture);
|
|
||||||
self.views[i as usize] = view;
|
|
||||||
}
|
|
||||||
fn free(&mut self, i: u32) {
|
|
||||||
self.view_count -= 1;
|
|
||||||
self.textures[i as usize] = None;
|
|
||||||
self.views[i as usize] = self.null_view.clone();
|
|
||||||
}
|
|
||||||
fn push(&mut self, image: &DynamicImage) {
|
|
||||||
self.view_count += 1;
|
|
||||||
let (texture, view) = self.create(image);
|
|
||||||
self.textures.push(Some(texture));
|
|
||||||
self.views.push(view);
|
|
||||||
}
|
|
||||||
fn push_free(&mut self) {
|
|
||||||
self.view_count += 1;
|
|
||||||
self.textures.push(None);
|
|
||||||
self.views.push(self.null_view.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
pub fn group(&self, slot: u32) -> Option<&BindGroup> {
|
||||||
let Some(texture) = &self.textures[i as usize] else {
|
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
||||||
return;
|
|
||||||
};
|
|
||||||
if rect.width == 0 || rect.height == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// `write_texture` requires tightly packed rows, unlike the atlas image.
|
|
||||||
let sub = image
|
|
||||||
.view(rect.x, rect.y, rect.width, rect.height)
|
|
||||||
.to_image();
|
|
||||||
self.queue.write_texture(
|
|
||||||
TexelCopyTextureInfo {
|
|
||||||
texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Origin3d {
|
|
||||||
x: rect.x,
|
|
||||||
y: rect.y,
|
|
||||||
z: 0,
|
|
||||||
},
|
|
||||||
aspect: TextureAspect::All,
|
|
||||||
},
|
|
||||||
sub.as_bytes(),
|
|
||||||
TexelCopyBufferLayout {
|
|
||||||
offset: 0,
|
|
||||||
bytes_per_row: Some(rect.width * 4),
|
|
||||||
rows_per_image: Some(rect.height),
|
|
||||||
},
|
|
||||||
Extent3d {
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self, image: &DynamicImage) -> (Texture, TextureView) {
|
fn create(
|
||||||
let image = image.to_rgba8();
|
&self,
|
||||||
let (width, height) = image.dimensions();
|
image: &DynamicImage,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
sampler: &Sampler,
|
||||||
|
) -> ImageGpu {
|
||||||
|
let rgba = image.to_rgba8();
|
||||||
|
let (width, height) = rgba.dimensions();
|
||||||
let texture = self.device.create_texture_with_data(
|
let texture = self.device.create_texture_with_data(
|
||||||
&self.queue,
|
&self.queue,
|
||||||
&TextureDescriptor {
|
&TextureDescriptor {
|
||||||
label: None,
|
label: Some("image"),
|
||||||
size: Extent3d {
|
size: Extent3d {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -127,63 +127,115 @@ impl GpuTextures {
|
|||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
},
|
},
|
||||||
wgt::TextureDataOrder::MipMajor,
|
wgt::TextureDataOrder::MipMajor,
|
||||||
image.as_bytes(),
|
rgba.as_bytes(),
|
||||||
);
|
);
|
||||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
let view = texture.create_view(&TextureViewDescriptor::default());
|
||||||
(texture, view)
|
let group = sampled_group(&self.device, layout, &view, sampler, "ui image");
|
||||||
|
ImageGpu { texture, group }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
||||||
let null_view = null_texture_view(device);
|
let Some(Some(slot)) = self.slots.get(i as usize) else {
|
||||||
Self {
|
return;
|
||||||
device: device.clone(),
|
};
|
||||||
queue: queue.clone(),
|
let dst = TexelCopyTextureInfo {
|
||||||
textures: Vec::new(),
|
texture: &slot.texture,
|
||||||
views: Vec::new(),
|
mip_level: 0,
|
||||||
samplers: vec![default_sampler(device)],
|
origin: Origin3d {
|
||||||
no_views: vec![null_view.clone()],
|
x: rect.x,
|
||||||
null_view,
|
y: rect.y,
|
||||||
view_count: 0,
|
z: 0,
|
||||||
|
},
|
||||||
|
aspect: TextureAspect::All,
|
||||||
|
};
|
||||||
|
match image.as_rgba8() {
|
||||||
|
Some(rgba) => write_region(&self.queue, dst, rgba, rect),
|
||||||
|
// The texture is rgba8, so any other layout has to be converted --
|
||||||
|
// and converting the rectangle is cheaper than the whole image.
|
||||||
|
None => {
|
||||||
|
let sub = image
|
||||||
|
.view(rect.x, rect.y, rect.width, rect.height)
|
||||||
|
.to_image();
|
||||||
|
write_region(&self.queue, dst, &sub, PatchRect { x: 0, y: 0, ..rect });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn views(&self) -> Vec<&TextureView> {
|
|
||||||
if self.views.is_empty() {
|
|
||||||
&self.no_views
|
|
||||||
} else {
|
|
||||||
&self.views
|
|
||||||
}
|
|
||||||
.iter()
|
|
||||||
.by_ref()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn samplers(&self) -> Vec<&Sampler> {
|
|
||||||
self.samplers.iter().by_ref().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_count(&self) -> usize {
|
|
||||||
self.view_count
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn null_texture_view(device: &Device) -> TextureView {
|
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
||||||
device
|
if rect.width == 0 || rect.height == 0 {
|
||||||
.create_texture(&TextureDescriptor {
|
return;
|
||||||
label: Some("null"),
|
}
|
||||||
size: Extent3d {
|
let stride = src.width() * 4;
|
||||||
width: 1,
|
queue.write_texture(
|
||||||
height: 1,
|
dst,
|
||||||
|
src.as_bytes(),
|
||||||
|
TexelCopyBufferLayout {
|
||||||
|
offset: (rect.y * stride + rect.x * 4) as u64,
|
||||||
|
bytes_per_row: Some(stride),
|
||||||
|
rows_per_image: Some(rect.height),
|
||||||
|
},
|
||||||
|
Extent3d {
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
mip_level_count: 1,
|
);
|
||||||
sample_count: 1,
|
}
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format: TextureFormat::Rgba8Unorm,
|
/// What a primitive that samples binds: a texture, and the sampler that reads
|
||||||
usage: TextureUsages::TEXTURE_BINDING,
|
/// it.
|
||||||
view_formats: &[],
|
pub fn sampled_group(
|
||||||
|
device: &Device,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
view: &TextureView,
|
||||||
|
sampler: &Sampler,
|
||||||
|
label: &'static str,
|
||||||
|
) -> BindGroup {
|
||||||
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::TextureView(view),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: BindingResource::Sampler(sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some(label),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The layout for one of those. The dimension differs -- the atlas is an
|
||||||
|
/// array of pages and an image is not -- and nothing else does.
|
||||||
|
pub fn sampled_layout(
|
||||||
|
device: &Device,
|
||||||
|
dimension: TextureViewDimension,
|
||||||
|
label: &'static str,
|
||||||
|
) -> BindGroupLayout {
|
||||||
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Texture {
|
||||||
|
sample_type: TextureSampleType::Float { filterable: false },
|
||||||
|
view_dimension: dimension,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some(label),
|
||||||
})
|
})
|
||||||
.create_view(&TextureViewDescriptor::default())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_sampler(device: &Device) -> Sampler {
|
pub fn default_sampler(device: &Device) -> Sampler {
|
||||||
|
|||||||
@@ -21,17 +21,25 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
|
/// Returns whether the `Buffer` was recreated, which stales any cached
|
||||||
if self.len != data.len() {
|
/// `BindGroup` holding it.
|
||||||
|
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) -> bool {
|
||||||
|
let resized = self.len != data.len();
|
||||||
|
if resized {
|
||||||
self.len = data.len();
|
self.len = data.len();
|
||||||
self.buffer =
|
self.buffer =
|
||||||
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
||||||
}
|
}
|
||||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
||||||
|
resized
|
||||||
|
}
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
}
|
}
|
||||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||||
let mut size = size as u64;
|
let mut size = size as u64;
|
||||||
if usage.contains(BufferUsages::STORAGE) {
|
if usage.contains(BufferUsages::STORAGE) {
|
||||||
|
// A binding cannot be empty or under the layout's minimum.
|
||||||
size = size.max(std::mem::size_of::<T>() as u64);
|
size = size.max(std::mem::size_of::<T>() as u64);
|
||||||
}
|
}
|
||||||
device.create_buffer(&BufferDescriptor {
|
device.create_buffer(&BufferDescriptor {
|
||||||
@@ -41,8 +49,4 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
usage,
|
usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#[allow(clippy::len_without_is_empty)]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.len
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+6
-2
@@ -1,4 +1,6 @@
|
|||||||
use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
|
use crate::{
|
||||||
|
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
|
||||||
|
};
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
mod cache;
|
mod cache;
|
||||||
@@ -7,13 +9,15 @@ mod render_state;
|
|||||||
mod size;
|
mod size;
|
||||||
|
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
pub use painter::Painter;
|
pub use painter::{Painter, PrimitiveLike};
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
pub use size::*;
|
pub use size::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UiData {
|
pub struct UiData {
|
||||||
pub widgets: Widgets,
|
pub widgets: Widgets,
|
||||||
|
/// Every primitive this ui can draw.
|
||||||
|
pub primitives: PrimitiveRegistry,
|
||||||
pub textures: Textures,
|
pub textures: Textures,
|
||||||
pub text: TextData,
|
pub text: TextData,
|
||||||
pub masks: TrackedArena<Mask, u32>,
|
pub masks: TrackedArena<Mask, u32>,
|
||||||
|
|||||||
+50
-22
@@ -1,7 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||||
render::{GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
render::{
|
||||||
|
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
||||||
|
TexturePrimitive,
|
||||||
|
},
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,15 +24,26 @@ pub struct Painter<'a> {
|
|||||||
|
|
||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
|
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
||||||
|
self.write(kind, primitive, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the kind, for a caller writing many of one primitive.
|
||||||
|
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||||
let h = self.state.layers.write(
|
let h = self.state.layers.write(
|
||||||
self.layer,
|
self.layer,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
|
kind,
|
||||||
id: self.id,
|
id: self.id,
|
||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
mask_idx: self.mask,
|
mask_idx: self.mask,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
self.push_primitive(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_primitive(&mut self, h: PrimitiveHandle) {
|
||||||
if self.mask != MaskIdx::NONE {
|
if self.mask != MaskIdx::NONE {
|
||||||
// TODO: I have no clue if this works at all :joy:
|
// TODO: I have no clue if this works at all :joy:
|
||||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
||||||
@@ -38,11 +52,13 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
/// Writes a primitive to be rendered
|
||||||
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
||||||
|
let primitive = primitive.into_primitive(self);
|
||||||
self.primitive_at(primitive, self.region)
|
self.primitive_at(primitive, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
||||||
|
let primitive = primitive.into_primitive(self);
|
||||||
self.primitive_at(primitive, region.within(&self.region));
|
self.primitive_at(primitive, region.within(&self.region));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,21 +91,6 @@ impl<'a> Painter<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive_at(handle.primitive(), region.within(&self.region));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture(&mut self, handle: &TextureHandle) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive(handle.primitive());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
|
||||||
self.textures.push(handle.clone());
|
|
||||||
self.primitive_at(handle.primitive(), region);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_text(
|
pub fn render_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
@@ -97,10 +98,12 @@ impl<'a> Painter<'a> {
|
|||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
let ui = self.rsc.ui_mut();
|
let ui = self.rsc.ui_mut();
|
||||||
ui.text.render(buffer, attrs, width, &mut ui.textures)
|
ui.text.render(buffer, attrs, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: merge the text methods into the primitive ones.
|
||||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||||
|
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
let mut region = origin;
|
let mut region = origin;
|
||||||
region.x.end = region.x.start;
|
region.x.end = region.x.start;
|
||||||
@@ -108,12 +111,12 @@ impl<'a> Painter<'a> {
|
|||||||
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
||||||
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
||||||
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
||||||
self.primitive_at(
|
self.write(
|
||||||
|
kind,
|
||||||
GlyphPrimitive {
|
GlyphPrimitive {
|
||||||
uv_min: glyph.entry.uv_min,
|
uv_min: glyph.entry.uv_min,
|
||||||
uv_max: glyph.entry.uv_max,
|
uv_max: glyph.entry.uv_max,
|
||||||
view_idx: glyph.entry.view_idx,
|
layer: glyph.entry.layer,
|
||||||
sampler_idx: glyph.entry.sampler_idx,
|
|
||||||
color: text.color,
|
color: text.color,
|
||||||
flags: glyph.entry.flags(),
|
flags: glyph.entry.flags(),
|
||||||
},
|
},
|
||||||
@@ -169,3 +172,28 @@ impl<'a> Painter<'a> {
|
|||||||
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What `Painter::primitive` takes: a primitive, or something that yields one
|
||||||
|
/// and does whatever else drawing it needs.
|
||||||
|
pub trait PrimitiveLike {
|
||||||
|
type Primitive: Primitive;
|
||||||
|
fn into_primitive(self, painter: &mut Painter) -> Self::Primitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Primitive> PrimitiveLike for P {
|
||||||
|
type Primitive = P;
|
||||||
|
fn into_primitive(self, _: &mut Painter) -> P {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveLike for &TextureHandle {
|
||||||
|
type Primitive = TexturePrimitive;
|
||||||
|
|
||||||
|
/// Retains a share of the handle, so the slot the primitive names cannot
|
||||||
|
/// be freed and reused while it is still drawn.
|
||||||
|
fn into_primitive(self, painter: &mut Painter) -> TexturePrimitive {
|
||||||
|
painter.textures.push(self.clone());
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-11
@@ -1,13 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, IdLike, MaskIdx, Painter, PixelRegion, PrimitiveLayers, SizeCtx,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
||||||
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||||
ui::cache::Cache,
|
ui::cache::Cache,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UiRenderState {
|
pub struct UiRenderState {
|
||||||
pub active: HashMap<WidgetId, ActiveData>,
|
pub active: HashMap<WidgetId, ActiveData>,
|
||||||
pub layers: PrimitiveLayers,
|
pub layers: DrawLayers,
|
||||||
pub(super) output_size: Vec2,
|
pub(super) output_size: Vec2,
|
||||||
pub cache: Cache,
|
pub cache: Cache,
|
||||||
|
|
||||||
@@ -243,14 +243,14 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_layers(&self) {
|
pub fn debug_layers(&self) {
|
||||||
for ((idx, depth), primitives) in self.layers.iter_depth() {
|
for ((idx, depth), draws) in self.layers.iter_depth() {
|
||||||
let indent = " ".repeat(depth * 2);
|
let indent = " ".repeat(depth * 2);
|
||||||
let len = primitives.instances().len();
|
let counts: Vec<String> = draws
|
||||||
print!("{indent}{idx}: {len} primitives");
|
.primitives()
|
||||||
if len >= 1 {
|
.iter()
|
||||||
print!(" ({})", primitives.instances()[0].binding);
|
.map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
|
||||||
}
|
.collect();
|
||||||
println!();
|
println!("{indent}{idx}: [{}]", counts.join(", "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +308,6 @@ impl UiRenderState {
|
|||||||
source,
|
source,
|
||||||
cache: &mut self.cache,
|
cache: &mut self.cache,
|
||||||
text: &mut ui.text,
|
text: &mut ui.text,
|
||||||
textures: &mut ui.textures,
|
|
||||||
widgets: &ui.widgets,
|
widgets: &ui.widgets,
|
||||||
outer,
|
outer,
|
||||||
output_size: self.output_size,
|
output_size: self.output_size,
|
||||||
|
|||||||
+3
-5
@@ -1,11 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
|
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, UiVec2,
|
||||||
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SizeCtx<'a> {
|
pub struct SizeCtx<'a> {
|
||||||
pub text: &'a mut TextData,
|
pub text: &'a mut TextData,
|
||||||
pub textures: &'a mut Textures,
|
|
||||||
pub(super) source: WidgetId,
|
pub(super) source: WidgetId,
|
||||||
pub(super) widgets: &'a Widgets,
|
pub(super) widgets: &'a Widgets,
|
||||||
pub(super) cache: &'a mut Cache,
|
pub(super) cache: &'a mut Cache,
|
||||||
@@ -33,7 +32,6 @@ impl SizeCtx<'_> {
|
|||||||
.get_dyn_dynamic(id)
|
.get_dyn_dynamic(id)
|
||||||
.desired_len::<A>(&mut SizeCtx {
|
.desired_len::<A>(&mut SizeCtx {
|
||||||
text: self.text,
|
text: self.text,
|
||||||
textures: self.textures,
|
|
||||||
source: self.source,
|
source: self.source,
|
||||||
widgets: self.widgets,
|
widgets: self.widgets,
|
||||||
cache: self.cache,
|
cache: self.cache,
|
||||||
@@ -82,7 +80,7 @@ impl SizeCtx<'_> {
|
|||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
self.text.render(buffer, attrs, width, self.textures)
|
self.text.render(buffer, attrs, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self, id: WidgetId) -> &String {
|
pub fn label(&self, id: WidgetId) -> &String {
|
||||||
|
|||||||
@@ -212,10 +212,10 @@ impl DefaultAppState for Client {
|
|||||||
render: &mut UiRenderState,
|
render: &mut UiRenderState,
|
||||||
) {
|
) {
|
||||||
let new = format!(
|
let new = format!(
|
||||||
"widgets: {}\nactive: {}\nviews: {}",
|
"widgets: {}\nactive: {}\ntextures: {}",
|
||||||
rsc.widgets().len(),
|
rsc.widgets().len(),
|
||||||
render.active_widgets(),
|
render.active_widgets(),
|
||||||
self.ui_state.renderer.ui.view_count(),
|
rsc.ui().textures.count(),
|
||||||
);
|
);
|
||||||
if new != *rsc.widgets()[self.info].content {
|
if new != *rsc.widgets()[self.info].content {
|
||||||
*rsc.widgets_mut()[self.info].content = new;
|
*rsc.widgets_mut()[self.info].content = new;
|
||||||
|
|||||||
+1
-5
@@ -1,10 +1,6 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use arboard::Clipboard;
|
use arboard::Clipboard;
|
||||||
use std::{
|
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
||||||
marker::{PhantomData, Sized},
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Ime, WindowEvent},
|
event::{Ime, WindowEvent},
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||||
|
|||||||
+22
-15
@@ -1,4 +1,4 @@
|
|||||||
use iris_core::{UiData, UiLimits, UiRenderNode, UiRenderState};
|
use iris_core::{UiData, UiRenderNode, UiRenderState};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::*;
|
use wgpu::*;
|
||||||
@@ -22,7 +22,20 @@ impl UiRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
let output = self.surface.get_current_texture().unwrap();
|
let output = match self.surface.get_current_texture() {
|
||||||
|
CurrentSurfaceTexture::Success(texture) => texture,
|
||||||
|
CurrentSurfaceTexture::Suboptimal(texture) => {
|
||||||
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
texture
|
||||||
|
}
|
||||||
|
CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Lost => {
|
||||||
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CurrentSurfaceTexture::Timeout
|
||||||
|
| CurrentSurfaceTexture::Occluded
|
||||||
|
| CurrentSurfaceTexture::Validation => return,
|
||||||
|
};
|
||||||
let view = output
|
let view = output
|
||||||
.texture
|
.texture
|
||||||
.create_view(&TextureViewDescriptor::default());
|
.create_view(&TextureViewDescriptor::default());
|
||||||
@@ -46,7 +59,7 @@ impl UiRenderer {
|
|||||||
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
self.window.pre_present_notify();
|
self.window.pre_present_notify();
|
||||||
output.present();
|
self.queue.present(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||||
@@ -65,9 +78,10 @@ impl UiRenderer {
|
|||||||
pub fn new(window: Arc<Window>) -> Self {
|
pub fn new(window: Arc<Window>) -> Self {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
|
||||||
let instance = Instance::new(&InstanceDescriptor {
|
let instance = Instance::new(InstanceDescriptor {
|
||||||
backends: Backends::PRIMARY,
|
backends: Backends::PRIMARY,
|
||||||
..Default::default()
|
display: Some(Box::new(window.clone())),
|
||||||
|
..InstanceDescriptor::new_without_display_handle()
|
||||||
});
|
});
|
||||||
|
|
||||||
let surface = instance
|
let surface = instance
|
||||||
@@ -79,22 +93,14 @@ impl UiRenderer {
|
|||||||
power_preference: PowerPreference::default(),
|
power_preference: PowerPreference::default(),
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
|
apply_limit_buckets: false,
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
.expect("Could not get adapter!");
|
.expect("Could not get adapter!");
|
||||||
|
|
||||||
let ui_limits = UiLimits::default();
|
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.request_device(&DeviceDescriptor {
|
||||||
required_features: Features::TEXTURE_BINDING_ARRAY
|
|
||||||
| Features::PARTIALLY_BOUND_BINDING_ARRAY
|
|
||||||
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
|
||||||
required_limits: Limits {
|
required_limits: Limits {
|
||||||
max_binding_array_elements_per_shader_stage: ui_limits
|
|
||||||
.max_binding_array_elements_per_shader_stage(),
|
|
||||||
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
|
||||||
.max_binding_array_sampler_elements_per_shader_stage(),
|
|
||||||
max_buffer_size: 1 << 30,
|
max_buffer_size: 1 << 30,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -114,6 +120,7 @@ impl UiRenderer {
|
|||||||
let config = SurfaceConfiguration {
|
let config = SurfaceConfiguration {
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
|
color_space: SurfaceColorSpace::Auto,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: PresentMode::AutoVsync,
|
present_mode: PresentMode::AutoVsync,
|
||||||
@@ -126,7 +133,7 @@ impl UiRenderer {
|
|||||||
|
|
||||||
let encoder = Self::create_encoder(&device);
|
let encoder = Self::create_encoder(&device);
|
||||||
|
|
||||||
let ui = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
let ui = UiRenderNode::new(&device, &config);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
|
|||||||
+45
-7
@@ -4,14 +4,14 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum CursorButton {
|
pub enum CursorButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum CursorSense {
|
pub enum CursorSense {
|
||||||
PressStart(CursorButton),
|
PressStart(CursorButton),
|
||||||
Pressing(CursorButton),
|
Pressing(CursorButton),
|
||||||
@@ -39,6 +39,19 @@ impl Event for CursorSenses {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CursorSenses {
|
||||||
|
/// Whether a widget with these senses stops the input reaching the layer
|
||||||
|
/// below it. Where the cursor rests stops at the top layer that is under
|
||||||
|
/// it; something happening to the cursor stops only at a widget that
|
||||||
|
/// answers to that, so a click-only child does not swallow a scroll.
|
||||||
|
fn consumes(&self, cursor: &CursorState, hover: ActivationState, momentary: bool) -> bool {
|
||||||
|
if !momentary {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
should_run(self, cursor, hover).is_some_and(|sense| sense.is_momentary())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CursorSense {
|
impl CursorSense {
|
||||||
pub fn click() -> Self {
|
pub fn click() -> Self {
|
||||||
Self::PressStart(CursorButton::Left)
|
Self::PressStart(CursorButton::Left)
|
||||||
@@ -52,6 +65,12 @@ impl CursorSense {
|
|||||||
pub fn is_dragging(&self) -> bool {
|
pub fn is_dragging(&self) -> bool {
|
||||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this sense is about something happening to the cursor, rather
|
||||||
|
/// than about where it rests.
|
||||||
|
fn is_momentary(&self) -> bool {
|
||||||
|
!matches!(self, Self::HoverStart | Self::Hovering | Self::HoverEnd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -96,6 +115,11 @@ impl CursorButtons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CursorState {
|
impl CursorState {
|
||||||
|
/// Whether anything is happening to the cursor beyond where it rests.
|
||||||
|
pub fn has_momentary_input(&self) -> bool {
|
||||||
|
self.scroll_delta != Vec2::ZERO || self.buttons.iter().any(|(_, state)| !state.is_off())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn end_frame(&mut self) {
|
||||||
self.buttons.end_frame();
|
self.buttons.end_frame();
|
||||||
self.scroll_delta = Vec2::ZERO;
|
self.scroll_delta = Vec2::ZERO;
|
||||||
@@ -164,8 +188,9 @@ impl SensorUi for UiRenderState {
|
|||||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||||
// update it there?
|
// update it there?
|
||||||
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||||
|
let momentary = cursor.has_momentary_input();
|
||||||
for layer in self.layers.indices().rev() {
|
for layer in self.layers.indices().rev() {
|
||||||
let mut sensed = false;
|
let mut consumed = false;
|
||||||
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
||||||
let shape = self.active.get(id).unwrap().region;
|
let shape = self.active.get(id).unwrap().region;
|
||||||
let region = shape.to_px(window_size);
|
let region = shape.to_px(window_size);
|
||||||
@@ -174,9 +199,22 @@ impl SensorUi for UiRenderState {
|
|||||||
if sensor.hover == ActivationState::Off {
|
if sensor.hover == ActivationState::Off {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sensed = true;
|
// A widget the cursor has left still hears its hover ending,
|
||||||
|
// but a press or a scroll landing elsewhere is not its input.
|
||||||
let cursor = cursor.clone();
|
let cursor = match in_shape {
|
||||||
|
true => cursor.clone(),
|
||||||
|
false => CursorState {
|
||||||
|
pos: cursor.pos,
|
||||||
|
exists: cursor.exists,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
consumed = consumed
|
||||||
|
|| rsc
|
||||||
|
.events_mut()
|
||||||
|
.get_type::<CursorSense>()
|
||||||
|
.registered(*id)
|
||||||
|
.any(|senses| senses.consumes(&cursor, sensor.hover, momentary));
|
||||||
|
|
||||||
let data = CursorData {
|
let data = CursorData {
|
||||||
pos: cursor.pos - region.top_left,
|
pos: cursor.pos - region.top_left,
|
||||||
@@ -191,7 +229,7 @@ impl SensorUi for UiRenderState {
|
|||||||
};
|
};
|
||||||
rsc.run_event::<CursorSense>(*id, data, state);
|
rsc.run_event::<CursorSense>(*id, data, state);
|
||||||
}
|
}
|
||||||
if sensed {
|
if consumed {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ pub struct Image {
|
|||||||
|
|
||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.texture(&self.handle);
|
painter.primitive(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod max_size;
|
|||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod sized;
|
mod set_size;
|
||||||
mod span;
|
mod span;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
@@ -14,6 +14,6 @@ pub use max_size::*;
|
|||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
pub use sized::*;
|
pub use set_size::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Sized {
|
pub struct SetSize {
|
||||||
pub inner: StrongWidget,
|
pub inner: StrongWidget,
|
||||||
pub x: Option<Len>,
|
pub x: Option<Len>,
|
||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sized {
|
impl SetSize {
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||||
if let Some(x) = self.x {
|
if let Some(x) = self.x {
|
||||||
ctx.outer.x.select_len(x.apply_rest());
|
ctx.outer.x.select_len(x.apply_rest());
|
||||||
@@ -17,7 +17,7 @@ impl Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Sized {
|
impl Widget for SetSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget(&self.inner);
|
painter.widget(&self.inner);
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::{Sized, Unsize};
|
use std::marker::Unsize;
|
||||||
|
|
||||||
pub struct WidgetPtr {
|
pub struct WidgetPtr {
|
||||||
pub inner: Option<StrongWidget>,
|
pub inner: Option<StrongWidget>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::{PhantomData, Sized};
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
|
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(size.x),
|
x: Some(size.x),
|
||||||
y: Some(size.y),
|
y: Some(size.y),
|
||||||
@@ -58,18 +58,18 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(len),
|
x: Some(len),
|
||||||
y: None,
|
y: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: None,
|
x: None,
|
||||||
y: Some(len),
|
y: Some(len),
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
//! What one frame of `UiRenderNode::draw` costs on the CPU, against the number
|
||||||
|
//! of layers it walks. Recording only: the pass is built and dropped without
|
||||||
|
//! being submitted, so this is the loop's cost and not the GPU's.
|
||||||
|
//!
|
||||||
|
//! cargo test --release --test draw_cost -- --ignored --nocapture
|
||||||
|
//!
|
||||||
|
//! Wall time is the wrong number to read for anything under a few percent --
|
||||||
|
//! it varied by 2x between runs of one unchanged binary where instructions
|
||||||
|
//! retired varied by 0.1%. Count those instead:
|
||||||
|
//!
|
||||||
|
//! perf stat -e instructions:u target/release/.../draw_cost-* --ignored
|
||||||
|
//!
|
||||||
|
//! That is how `PrimitiveRender` was measured against a match in the renderer:
|
||||||
|
//! 6 instructions per list drawn, against the ~5,400 wgpu spends recording
|
||||||
|
//! one.
|
||||||
|
//!
|
||||||
|
//! The instance is leaked deliberately. A Vulkan loader may unload the driver
|
||||||
|
//! when the last one drops, which can fault as a thread that used it exits --
|
||||||
|
//! and every test runs on a spawned thread.
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use iris::prelude::*;
|
||||||
|
use iris_core::{
|
||||||
|
GlyphPrimitive, MaskIdx, PrimitiveInst, RectPrimitive, TextureHandle, TexturePrimitive, UiData,
|
||||||
|
UiRegion, UiRenderNode, UiRenderState,
|
||||||
|
};
|
||||||
|
use wgpu::{Color as GpuColor, *};
|
||||||
|
|
||||||
|
const SIZE: u32 = 1024;
|
||||||
|
const FRAMES: u32 = 200;
|
||||||
|
/// Reported as the best of this many batches, since the mean moves by more
|
||||||
|
/// than the thing being measured.
|
||||||
|
const BATCHES: u32 = 8;
|
||||||
|
|
||||||
|
fn gpu() -> Option<(Device, Queue)> {
|
||||||
|
// Probed rather than assumed: there may be no Vulkan adapter, and GL is
|
||||||
|
// what is left when there is not.
|
||||||
|
let all = Instance::new(InstanceDescriptor::new_without_display_handle());
|
||||||
|
let instance = match pollster::block_on(all.request_adapter(&RequestAdapterOptions::default()))
|
||||||
|
{
|
||||||
|
Ok(_) => all,
|
||||||
|
Err(_) => Instance::new(InstanceDescriptor {
|
||||||
|
backends: Backends::GL,
|
||||||
|
..InstanceDescriptor::new_without_display_handle()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
// Leaked rather than dropped: see the note at the top of the file.
|
||||||
|
let instance: &'static Instance = Box::leak(Box::new(instance));
|
||||||
|
let adapter =
|
||||||
|
pollster::block_on(instance.request_adapter(&RequestAdapterOptions::default())).ok()?;
|
||||||
|
println!("adapter: {:?}", adapter.get_info());
|
||||||
|
pollster::block_on(adapter.request_device(&DeviceDescriptor::default())).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config(format: TextureFormat) -> SurfaceConfiguration {
|
||||||
|
SurfaceConfiguration {
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format,
|
||||||
|
color_space: SurfaceColorSpace::Auto,
|
||||||
|
width: SIZE,
|
||||||
|
height: SIZE,
|
||||||
|
present_mode: PresentMode::Fifo,
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
alpha_mode: CompositeAlphaMode::Auto,
|
||||||
|
view_formats: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every layer draws all three primitives, so the renderer takes a different
|
||||||
|
/// path for each list it walks -- which is the case a single-primitive layer
|
||||||
|
/// would never exercise. Images are bound per instance, so there are few.
|
||||||
|
fn fill(
|
||||||
|
ui: &mut UiData,
|
||||||
|
render: &mut UiRenderState,
|
||||||
|
layers: usize,
|
||||||
|
per_layer: usize,
|
||||||
|
) -> Vec<TextureHandle> {
|
||||||
|
let rect = ui.primitives.kind::<RectPrimitive>();
|
||||||
|
let glyph = ui.primitives.kind::<GlyphPrimitive>();
|
||||||
|
let texture = ui.primitives.kind::<TexturePrimitive>();
|
||||||
|
let id = ui.widgets.add_strong(Rect::new(UiColor::WHITE)).id();
|
||||||
|
let handles: Vec<_> = (0..4)
|
||||||
|
.map(|_| ui.textures.add(image::RgbaImage::new(4, 4)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut layer = 0;
|
||||||
|
for _ in 0..layers {
|
||||||
|
for _ in 0..per_layer {
|
||||||
|
render.layers.write(
|
||||||
|
layer,
|
||||||
|
PrimitiveInst {
|
||||||
|
kind: rect,
|
||||||
|
id,
|
||||||
|
primitive: RectPrimitive::color(UiColor::WHITE),
|
||||||
|
region: UiRegion::FULL,
|
||||||
|
mask_idx: MaskIdx::NONE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
render.layers.write(
|
||||||
|
layer,
|
||||||
|
PrimitiveInst {
|
||||||
|
kind: glyph,
|
||||||
|
id,
|
||||||
|
primitive: GlyphPrimitive {
|
||||||
|
uv_min: vec2(0.0, 0.0),
|
||||||
|
uv_max: vec2(1.0, 1.0),
|
||||||
|
layer: 0,
|
||||||
|
color: UiColor::WHITE,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
region: UiRegion::FULL,
|
||||||
|
mask_idx: MaskIdx::NONE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for h in &handles[..2] {
|
||||||
|
render.layers.write(
|
||||||
|
layer,
|
||||||
|
PrimitiveInst {
|
||||||
|
kind: texture,
|
||||||
|
id,
|
||||||
|
primitive: TexturePrimitive::from(h),
|
||||||
|
region: UiRegion::FULL,
|
||||||
|
mask_idx: MaskIdx::NONE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
layer = render.layers.next(layer);
|
||||||
|
}
|
||||||
|
handles
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_cost(device: &Device, queue: &Queue, layers: usize, per_layer: usize) -> f64 {
|
||||||
|
let format = TextureFormat::Bgra8Unorm;
|
||||||
|
let mut node = UiRenderNode::new(device, &config(format));
|
||||||
|
let mut ui = UiData::default();
|
||||||
|
let mut render = UiRenderState::new();
|
||||||
|
let _handles = fill(&mut ui, &mut render, layers, per_layer);
|
||||||
|
node.update(device, queue, &mut ui, &mut render);
|
||||||
|
|
||||||
|
let target = device.create_texture(&TextureDescriptor {
|
||||||
|
label: Some("draw cost"),
|
||||||
|
size: Extent3d {
|
||||||
|
width: SIZE,
|
||||||
|
height: SIZE,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format,
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
let view = target.create_view(&TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let record = |frames: u32| {
|
||||||
|
let start = Instant::now();
|
||||||
|
for _ in 0..frames {
|
||||||
|
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
|
||||||
|
{
|
||||||
|
let pass = &mut encoder.begin_render_pass(&RenderPassDescriptor {
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations {
|
||||||
|
load: LoadOp::Clear(GpuColor::BLACK),
|
||||||
|
store: StoreOp::Store,
|
||||||
|
},
|
||||||
|
depth_slice: None,
|
||||||
|
})],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
node.draw(pass);
|
||||||
|
}
|
||||||
|
drop(encoder.finish());
|
||||||
|
}
|
||||||
|
start.elapsed().as_secs_f64() / frames as f64
|
||||||
|
};
|
||||||
|
record(FRAMES / 4);
|
||||||
|
(0..BATCHES)
|
||||||
|
.map(|_| record(FRAMES))
|
||||||
|
.fold(f64::MAX, f64::min)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "measurement, not a check"]
|
||||||
|
fn draw_cost_by_layer_count() {
|
||||||
|
let Some((device, queue)) = gpu() else {
|
||||||
|
panic!("no wgpu device; see the this-machine-graphics notes");
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"layers, each 8 rects + 8 glyphs + 2 images: us/frame (us per layer), best of {BATCHES}"
|
||||||
|
);
|
||||||
|
let base = frame_cost(&device, &queue, 1, 8) * 1e6;
|
||||||
|
for layers in [8, 64, 256, 1024] {
|
||||||
|
let per_frame = frame_cost(&device, &queue, layers, 8) * 1e6;
|
||||||
|
// Net of the empty pass, which is the same in any version of this.
|
||||||
|
println!(
|
||||||
|
"{layers:>5}: {per_frame:8.1} us ({:.3} us)",
|
||||||
|
(per_frame - base).max(0.0) / layers as f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
//! Input across layers: what stops at a layer, what passes through it, and
|
||||||
|
//! where hovering stops. These drive `run_sensors` directly, which needs no
|
||||||
|
//! GPU and no window.
|
||||||
|
|
||||||
|
use iris::prelude::*;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
struct SenseRsc {
|
||||||
|
ui: UiData,
|
||||||
|
events: EventManager<SenseRsc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiRsc for SenseRsc {
|
||||||
|
fn ui(&self) -> &UiData {
|
||||||
|
&self.ui
|
||||||
|
}
|
||||||
|
fn ui_mut(&mut self) -> &mut UiData {
|
||||||
|
&mut self.ui
|
||||||
|
}
|
||||||
|
fn on_draw(&mut self, active: &ActiveData) {
|
||||||
|
self.events.draw(active);
|
||||||
|
}
|
||||||
|
fn on_undraw(&mut self, active: &ActiveData) {
|
||||||
|
self.events.undraw(active);
|
||||||
|
}
|
||||||
|
fn on_remove(&mut self, id: WidgetId) {
|
||||||
|
self.events.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasState for SenseRsc {
|
||||||
|
type State = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasEvents for SenseRsc {
|
||||||
|
fn events(&self) -> &EventManager<Self> {
|
||||||
|
&self.events
|
||||||
|
}
|
||||||
|
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
||||||
|
&mut self.events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WINDOW: f32 = 100.0;
|
||||||
|
|
||||||
|
/// Every sense that has fired on one widget since it was last read.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct Fired(Rc<RefCell<Vec<CursorSense>>>);
|
||||||
|
|
||||||
|
impl Fired {
|
||||||
|
fn take(&self) -> Vec<CursorSense> {
|
||||||
|
std::mem::take(&mut self.0.borrow_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Ui {
|
||||||
|
rsc: SenseRsc,
|
||||||
|
render: UiRenderState,
|
||||||
|
state: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
rsc: SenseRsc {
|
||||||
|
ui: UiData::default(),
|
||||||
|
events: EventManager::default(),
|
||||||
|
},
|
||||||
|
render: UiRenderState::new(),
|
||||||
|
state: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn listen<W: Widget + ?core::marker::Sized + 'static>(
|
||||||
|
&mut self,
|
||||||
|
widget: &StrongWidget<W>,
|
||||||
|
senses: impl Into<CursorSenses>,
|
||||||
|
) -> Fired {
|
||||||
|
let fired = Fired::default();
|
||||||
|
let sink = fired.clone();
|
||||||
|
self.rsc
|
||||||
|
.register_event(widget.weak(), senses.into(), move |ctx, _rsc| {
|
||||||
|
sink.0.borrow_mut().push(ctx.data.sense)
|
||||||
|
});
|
||||||
|
fired
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stacks the widgets bottom first, each on its own layer, and lays them
|
||||||
|
/// out in a square window.
|
||||||
|
fn stack(&mut self, children: Vec<StrongWidget>) {
|
||||||
|
let root = self
|
||||||
|
.rsc
|
||||||
|
.ui
|
||||||
|
.widgets
|
||||||
|
.add_strong(Stack {
|
||||||
|
children,
|
||||||
|
size: StackSize::default(),
|
||||||
|
})
|
||||||
|
.any();
|
||||||
|
self.render.resize((WINDOW, WINDOW));
|
||||||
|
self.render.update(&root, &mut self.rsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor(&mut self, at: (f32, f32)) -> CursorState {
|
||||||
|
CursorState {
|
||||||
|
pos: at.into(),
|
||||||
|
exists: true,
|
||||||
|
buttons: Default::default(),
|
||||||
|
scroll_delta: Vec2::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, cursor: CursorState) {
|
||||||
|
self.render.run_sensors(
|
||||||
|
&mut self.rsc,
|
||||||
|
&mut self.state,
|
||||||
|
cursor,
|
||||||
|
(WINDOW, WINDOW).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
||||||
|
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_stops_at_the_topmost_widget() {
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let (bottom, middle, top) = (full(&mut ui), full(&mut ui), full(&mut ui));
|
||||||
|
let bottom_hover = ui.listen(&bottom, CursorSense::HoverStart);
|
||||||
|
let middle_hover = ui.listen(&middle, CursorSense::HoverStart);
|
||||||
|
let top_hover = ui.listen(&top, CursorSense::HoverStart);
|
||||||
|
ui.stack(vec![bottom.any(), middle.any(), top.any()]);
|
||||||
|
|
||||||
|
let cursor = ui.cursor((50.0, 50.0));
|
||||||
|
ui.run(cursor);
|
||||||
|
|
||||||
|
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
||||||
|
assert_eq!(
|
||||||
|
middle_hover.take(),
|
||||||
|
[],
|
||||||
|
"hover is not shared with a layer below"
|
||||||
|
);
|
||||||
|
assert_eq!(bottom_hover.take(), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let (list, button, overlay) = (full(&mut ui), full(&mut ui), full(&mut ui));
|
||||||
|
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||||
|
let clicked = ui.listen(&button, CursorSense::click());
|
||||||
|
let overlay_clicked = ui.listen(&overlay, CursorSense::click());
|
||||||
|
ui.stack(vec![list.any(), button.any(), overlay.any()]);
|
||||||
|
|
||||||
|
let mut cursor = ui.cursor((50.0, 50.0));
|
||||||
|
cursor.scroll_delta = (0.0, 10.0).into();
|
||||||
|
ui.run(cursor);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
scrolled.take(),
|
||||||
|
[CursorSense::Scroll],
|
||||||
|
"two layers of click-only widgets do not stop a scroll"
|
||||||
|
);
|
||||||
|
assert_eq!(clicked.take(), []);
|
||||||
|
assert_eq!(overlay_clicked.take(), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_the_topmost_listener_takes_a_press() {
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let (below, above) = (full(&mut ui), full(&mut ui));
|
||||||
|
let below_clicked = ui.listen(&below, CursorSense::click());
|
||||||
|
let above_clicked = ui.listen(&above, CursorSense::click());
|
||||||
|
ui.stack(vec![below.any(), above.any()]);
|
||||||
|
|
||||||
|
let mut cursor = ui.cursor((50.0, 50.0));
|
||||||
|
cursor.buttons.left = ActivationState::Start;
|
||||||
|
ui.run(cursor);
|
||||||
|
|
||||||
|
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
||||||
|
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let list = full(&mut ui);
|
||||||
|
// The row above the list covers it, but only its left half is the button.
|
||||||
|
let button = full(&mut ui);
|
||||||
|
let gap = full(&mut ui);
|
||||||
|
let list_clicked = ui.listen(&list, CursorSense::click());
|
||||||
|
let button_clicked = ui.listen(&button, CursorSense::click());
|
||||||
|
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||||
|
children: vec![button.any(), gap.any()],
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: 0.0,
|
||||||
|
});
|
||||||
|
ui.stack(vec![list.any(), row.any()]);
|
||||||
|
|
||||||
|
let mut on_button = ui.cursor((20.0, 50.0));
|
||||||
|
on_button.buttons.left = ActivationState::Start;
|
||||||
|
ui.run(on_button);
|
||||||
|
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
||||||
|
assert_eq!(list_clicked.take(), []);
|
||||||
|
|
||||||
|
let mut beside_it = ui.cursor((80.0, 50.0));
|
||||||
|
beside_it.buttons.left = ActivationState::Start;
|
||||||
|
ui.run(beside_it);
|
||||||
|
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
||||||
|
assert_eq!(
|
||||||
|
list_clicked.take(),
|
||||||
|
[CursorSense::click()],
|
||||||
|
"a press beside the button belongs to what is under it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leaving_a_widget_still_ends_its_hover() {
|
||||||
|
let mut ui = Ui::new();
|
||||||
|
let widget = full(&mut ui);
|
||||||
|
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||||
|
ui.stack(vec![widget.any()]);
|
||||||
|
|
||||||
|
let cursor = ui.cursor((50.0, 50.0));
|
||||||
|
ui.run(cursor);
|
||||||
|
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||||
|
|
||||||
|
let mut gone = ui.cursor((50.0, 50.0));
|
||||||
|
gone.exists = false;
|
||||||
|
ui.run(gone);
|
||||||
|
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user