diff --git a/Cargo.lock b/Cargo.lock index 5797783a..2d3aefc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + [[package]] name = "addr2line" version = "0.25.1" @@ -100,9 +116,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arrayref" @@ -148,9 +164,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake3" @@ -178,9 +194,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" @@ -196,9 +212,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -223,9 +239,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -236,9 +252,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -246,9 +262,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -259,9 +275,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.65" +version = "4.5.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" dependencies = [ "clap", ] @@ -275,14 +291,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" @@ -337,9 +353,9 @@ checksum = "24efe21bd9a78102d1225f10f0a41d9d5b43f4df7ae8235f39a9c79e4d476c1e" [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -365,6 +381,54 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ecolor" +version = "0.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e" +dependencies = [ + "emath", +] + +[[package]] +name = "egui" +version = "0.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3" +dependencies = [ + "ahash", + "bitflags", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "emath" +version = "0.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32" + +[[package]] +name = "epaint" +version = "0.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62" +dependencies = [ + "ab_glyph", + "ahash", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -426,38 +490,38 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", @@ -465,7 +529,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -488,19 +551,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.0", "wasip2", "wasip3", @@ -658,6 +721,7 @@ dependencies = [ "clap", "clap_complete", "dirs", + "egui", "futures-util", "gpu-alloc", "gpu-alloc-types", @@ -734,9 +798,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -773,7 +837,7 @@ checksum = "28067e7361c0069c3753795d131653f9ea5333aeb35a3855fb2de66447c48ac8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -790,9 +854,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -806,19 +870,18 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags", "libc", ] [[package]] name = "linearize" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d5b35550da9461fb8d3acf71c9925afae570bfc45a11857b55138d25c8604d" +checksum = "f6e1430c89633736996fd763822abd252e395dbccaaee33be601b4d59678a93e" dependencies = [ "cfg-if", "linearize-derive", @@ -835,14 +898,14 @@ checksum = "f657db73fbcad5341c5991ddee6c464d4bfd521575c0dc1a47913e0f434defeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" @@ -875,6 +938,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num-conv" version = "0.2.0" @@ -889,7 +958,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -943,6 +1012,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -997,7 +1075,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1011,35 +1089,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -1049,9 +1121,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ "bitflags", "crc32fast", @@ -1073,7 +1145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1086,19 +1158,25 @@ dependencies = [ ] [[package]] -name = "quick-xml" -version = "0.39.0" +name = "profiling" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e3bf4aa9d243beeb01a7b3bc30b77cfe2c44e24ec02d751a7104a53c2c49a1" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1109,6 +1187,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -1134,7 +1218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom 0.4.1", + "getrandom 0.4.2", "rand_core 0.10.0", ] @@ -1201,9 +1285,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "repc" @@ -1234,9 +1318,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -1314,7 +1398,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1410,9 +1494,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1455,7 +1539,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1538,6 +1622,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "uapi" version = "0.2.13" @@ -1566,9 +1656,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" @@ -1636,9 +1732,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -1649,9 +1745,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1659,22 +1755,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -1743,7 +1839,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1754,7 +1850,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1967,7 +2063,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1983,7 +2079,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2055,26 +2151,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 2a7f6bf5..74602f7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ opera = "1.0.1" with_builtin_macros = "0.1.0" blake3 = "1.8.2" run-on-drop = "1.0.0" +egui = { version = "0.33.3", default-features = false } [build-dependencies] repc = "0.1.1" @@ -87,6 +88,38 @@ opt-level = 3 [profile.dev.package."smallvec"] opt-level = 3 +[profile.dev.package."egui"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."emath"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."epaint"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."ab_glyph"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."ab_glyph_rasterizer"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."owned_ttf_parser"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."ttf-parser"] +opt-level = 3 +debug = "line-tables-only" + +[profile.dev.package."ecolor"] +opt-level = 3 +debug = "line-tables-only" + [features] rc_tracking = [] it = [] diff --git a/build/vulkan/hash.rs b/build/vulkan/hash.rs index de6e0692..7a0cb297 100644 --- a/build/vulkan/hash.rs +++ b/build/vulkan/hash.rs @@ -8,23 +8,31 @@ pub struct Tree { pub shaders: &'static [&'static str], } -pub const TREES: &[Tree] = &[Tree { - root: "src/gfx_apis/vulkan/shaders", - hash: "src/gfx_apis/vulkan/shaders_hash.txt", - bin: "src/gfx_apis/vulkan/shaders_bin", - shaders: &[ - "fill.frag", - "fill.vert", - "tex.vert", - "tex.frag", - "out.vert", - "out.frag", - "legacy/fill.frag", - "legacy/fill.vert", - "legacy/tex.vert", - "legacy/tex.frag", - ], -}]; +pub const TREES: &[Tree] = &[ + Tree { + root: "src/gfx_apis/vulkan/shaders", + hash: "src/gfx_apis/vulkan/shaders_hash.txt", + bin: "src/gfx_apis/vulkan/shaders_bin", + shaders: &[ + "fill.frag", + "fill.vert", + "tex.vert", + "tex.frag", + "out.vert", + "out.frag", + "legacy/fill.frag", + "legacy/fill.vert", + "legacy/tex.vert", + "legacy/tex.frag", + ], + }, + Tree { + root: "src/egui_adapter/shaders", + hash: "src/egui_adapter/shaders_hash.txt", + bin: "src/egui_adapter/shaders_bin", + shaders: &["shader.vert", "shader.frag"], + }, +]; fn calculate_hash(tree: &Tree) -> anyhow::Result { let dir = WalkDir::new(tree.root); diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 7c71461e..9e24c86f 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1035,6 +1035,13 @@ impl ConfigClient { position } + pub fn set_egui_fonts(&self, proportional: Option>, monospace: Option>) { + self.send(&ClientMessage::SetEguiFonts { + proportional, + monospace, + }); + } + pub fn set_middle_click_paste_enabled(&self, enabled: bool) { self.send(&ClientMessage::SetMiddleClickPasteEnabled { enabled }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index ab1de845..fc61dd85 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -841,6 +841,10 @@ pub enum ClientMessage<'a> { fds: Vec<(i32, i32)>, tag: Option<&'a str>, }, + SetEguiFonts { + proportional: Option>, + monospace: Option>, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index 64883b09..d3e3fbb0 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -197,6 +197,20 @@ pub fn get_bar_position() -> BarPosition { get!(BarPosition::Top).get_bar_position() } +/// Sets the proportional fonts used by egui windows. +/// +/// The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`. +pub fn set_egui_proportional_fonts<'a>(fonts: impl IntoIterator) { + get!().set_egui_fonts(Some(fonts.into_iter().collect()), None); +} + +/// Sets the monospace fonts used by egui windows. +/// +/// The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`. +pub fn set_egui_monospace_fonts<'a>(fonts: impl IntoIterator) { + get!().set_egui_fonts(None, Some(fonts.into_iter().collect())); +} + /// Elements of the compositor whose color can be changed. pub mod colors { use { diff --git a/src/compositor.rs b/src/compositor.rs index fa6777b5..0482f6d3 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -392,6 +392,7 @@ fn start_compositor2( eventfd_cache, lazy_event_sources: Default::default(), bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)), + egg_state: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index 75a1c336..86f8bbb3 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1807,6 +1807,10 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_egui_fonts(&self, proportional: Option>, monospace: Option>) { + self.state.set_egui_fonts(proportional, monospace); + } + fn handle_set_log_level(&self, level: ConfigLogLevel) { self.state.set_log_level(level.into()); } @@ -3311,6 +3315,10 @@ impl ConfigProxyHandler { fds, tag, } => self.handle_run(prog, args, env, fds, tag).wrn("run")?, + ClientMessage::SetEguiFonts { + proportional, + monospace, + } => self.handle_set_egui_fonts(proportional, monospace), } Ok(()) } diff --git a/src/egui_adapter.rs b/src/egui_adapter.rs new file mode 100644 index 00000000..c9939c56 --- /dev/null +++ b/src/egui_adapter.rs @@ -0,0 +1,3 @@ +pub mod egui_oklch; +pub mod egui_platform; +mod egui_vulkan; diff --git a/src/egui_adapter/egui_oklch.rs b/src/egui_adapter/egui_oklch.rs new file mode 100644 index 00000000..6710ca0a --- /dev/null +++ b/src/egui_adapter/egui_oklch.rs @@ -0,0 +1,37 @@ +use { + crate::{ + cmm::cmm_eotf::Eotf, + theme::{Color, Oklab, Oklch}, + }, + egui::{Color32, Rgba}, +}; + +#[expect(dead_code)] +pub trait Color32Ext { + fn to_oklab(self) -> Oklab; + fn to_oklch(self) -> Oklch; +} + +impl Color32Ext for Color32 { + fn to_oklab(self) -> Oklab { + let [r, g, b, a] = self.to_array(); + Color::from_srgba_premultiplied(r, g, b, a).srgb_to_oklab() + } + + fn to_oklch(self) -> Oklch { + self.to_oklab().to_oklch() + } +} + +impl Into for Oklch { + fn into(self) -> Color32 { + self.to_oklab().into() + } +} + +impl Into for Oklab { + fn into(self) -> Color32 { + let [r, g, b, a] = self.to_srgb().to_array(Eotf::Linear); + Rgba::from_rgba_premultiplied(r, g, b, a).into() + } +} diff --git a/src/egui_adapter/egui_platform.rs b/src/egui_adapter/egui_platform.rs new file mode 100644 index 00000000..6c063855 --- /dev/null +++ b/src/egui_adapter/egui_platform.rs @@ -0,0 +1,1456 @@ +use { + crate::{ + allocator::{Allocator, AllocatorError, BO_USE_RENDERING, BufferObject}, + async_engine::SpawnedFuture, + client::{Client, ClientCaps, ClientError}, + cursor::KnownCursor, + egui_adapter::egui_vulkan::{ + EGV_FORMAT, EgvContext, EgvError, EgvFramebuffer, EgvRenderer, + }, + fixed::Fixed, + fontconfig::match_font, + gfx_api::SyncFile, + globals::{GlobalName, Singleton}, + ifs::wl_seat::{ + BTN_EXTRA, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, BTN_SIDE, + wl_pointer::{self, HORIZONTAL_SCROLL, PendingScroll, VERTICAL_SCROLL}, + }, + object::Version, + scale::Scale, + security_context_acceptor::AcceptorMetadata, + state::State, + utils::{ + asyncevent::AsyncEvent, + buf::Buf, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + double_buffered::DoubleBuffered, + errorfmt::ErrorFmt, + object_drop_queue::ObjectDropQueue, + oserror::OsError, + pipe::{Pipe, pipe}, + rc_eq::rc_eq, + }, + video::{dmabuf::DMA_BUF_SYNC_WRITE, drm::DrmError}, + wire::{ + WlSurfaceId, + wl_pointer::{Button, Enter, Leave, Motion}, + wp_fractional_scale_v1::PreferredScale, + }, + wl_usr::{ + UsrCon, UsrConOwner, + usr_ifs::{ + usr_jay_compositor::UsrJayCompositor, + usr_jay_sync_file_release::UsrJaySyncFileReleaseOwner, + usr_jay_sync_file_surface::UsrJaySyncFileSurface, + usr_wl_buffer::UsrWlBuffer, + usr_wl_callback::UsrWlCallbackOwner, + usr_wl_compositor::UsrWlCompositor, + usr_wl_data_device::UsrWlDataDevice, + usr_wl_data_device_manager::UsrWlDataDeviceManager, + usr_wl_data_source::{UsrWlDataSource, UsrWlDataSourceOwner}, + usr_wl_keyboard::{UsrWlKeyboard, UsrWlKeyboardOwner}, + usr_wl_pointer::{UsrWlPointer, UsrWlPointerOwner}, + usr_wl_registry::UsrWlRegistry, + usr_wl_seat::UsrWlSeat, + usr_wl_surface::UsrWlSurface, + usr_wp_cursor_shape_device_v1::UsrWpCursorShapeDeviceV1, + usr_wp_cursor_shape_manager_v1::UsrWpCursorShapeManagerV1, + usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner}, + usr_wp_fractional_scale_manager::UsrWpFractionalScaleManager, + usr_wp_viewport::UsrWpViewport, + usr_wp_viewporter::UsrWpViewporter, + usr_xdg_surface::{UsrXdgSurface, UsrXdgSurfaceOwner}, + usr_xdg_toplevel::{UsrXdgToplevel, UsrXdgToplevelOwner}, + usr_xdg_wm_base::UsrXdgWmBase, + usr_zwp_linux_dmabuf_v1::UsrZwpLinuxDmabufV1, + usr_zwp_primary_selection_device_manager::UsrZwpPrimarySelectionDeviceManagerV1, + }, + }, + }, + egui::{ + CursorIcon, Event, FontData, FontDefinitions, FontFamily, FullOutput, Key, Modifiers, + MouseWheelUnit, OutputCommand, PlatformOutput, PointerButton, Pos2, RawInput, Vec2, + ViewportCommand, ViewportEvent, ViewportId, ViewportInfo, pos2, vec2, + }, + futures_util::{FutureExt, select}, + isnt::std_1::primitive::{IsntCharExt, IsntSliceExt, IsntStrExt}, + kbvm::{Keysym, ModifierMask, lookup::Lookup}, + std::{ + cell::{Cell, RefCell}, + collections::btree_map::Entry, + fs, mem, + rc::{Rc, Weak}, + sync::Arc, + }, + thiserror::Error, + uapi::{OwnedFd, c}, +}; + +#[derive(Debug, Error)] +pub enum EggError { + #[error("Could not create a socket pair")] + CreateSocketPair(#[source] OsError), + #[error("Could not spawn a client")] + SpawnClient(#[source] ClientError), + #[error("Could not create a renderer")] + CreateRenderer(#[source] EgvError), + #[error("There is no render context")] + NoRenderContext, + #[error("Could not allocate a buffer")] + AllocateBuffer(#[source] AllocatorError), + #[error("Could not import a framebuffer")] + ImportFramebuffer(#[source] EgvError), + #[error("Could not render")] + Render(#[source] EgvError), + #[error("No viewport output")] + NoViewportOutput, + #[error("Could not export initial dmabuf sync file")] + ExportBoSyncFile(#[source] DrmError), +} + +pub mod icons { + #[expect(dead_code)] + pub const ICON_ADD: &str = "\u{e145}"; + #[expect(dead_code)] + pub const ICON_CLOSE: &str = "\u{e5cd}"; + #[expect(dead_code)] + pub const ICON_DRAG_INDICATOR: &str = "\u{e945}"; + #[expect(dead_code)] + pub const ICON_INFO: &str = "\u{e88e}"; + #[expect(dead_code)] + pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}"; + #[expect(dead_code)] + pub const ICON_PENDING: &str = "\u{ef64}"; + #[expect(dead_code)] + pub const ICON_REMOVE: &str = "\u{e15b}"; +} + +linear_ids!(EggContextIds, EggContextId, u64); + +pub struct EggState { + fonts: RefCell, + ctx: CloneCell>>, + context_ids: EggContextIds, + cxts: CopyHashMap>, +} + +#[derive(Default)] +struct EggFonts { + definitions: Option, + proportional: Vec, + monospace: Vec, +} + +pub struct EggContext { + inner: Rc, +} + +struct EggContextInner { + id: EggContextId, + renderer: Rc, + allocator: Rc, + state: Rc, + _client: Rc, + con: Rc, + jay_compositor: Rc, + wl_compositor: Rc, + xdg_wm_base: Rc, + wl_data_device_manager: Rc, + _zwp_primary_selection_device_manager_v1: Rc, + wp_viewporter: Rc, + wp_cursor_shape_manager_v1: Rc, + wp_fractional_scale_manager: Rc, + zwp_linux_dmabuf_v1: Rc, + registry: Rc, + windows: CopyHashMap>, + seats: CopyHashMap, +} + +struct EggSeat { + inner: Rc, +} + +pub struct EggSeatInner { + ctx: Rc, + global_name: GlobalName, + wl_seat: Rc, + wl_pointer: Rc, + wl_data_device: Rc, + pointer_window: CloneCell>>, + pointer_enter_serial: Cell, + pointer_serial: Cell, + pointer_pos: Cell, + kb_modifiers: Cell, + wp_cursor_shape_device_v1: Rc, + wl_keyboard: Rc, + kb_window: CloneCell>>, + kb_serial: Cell, + serial: Cell, + wl_data_source: CloneCell>>, + copy_text: RefCell>, + copy_task: Cell>>, + paste_task: Cell>>, +} + +pub struct EggWindow { + _ctx: Rc, + inner: Rc, + _render_task: SpawnedFuture<()>, + _timer_task: SpawnedFuture<()>, +} + +pub trait EggWindowOwner { + fn close(&self); + fn render(self: Rc, ctx: &egui::Context); +} + +struct EggWindowInner { + ctx: Rc, + egv: Rc, + egui: egui::Context, + wl_surface: Rc, + wp_viewport: Rc, + wp_fractional_scale: Rc, + xdg_surface: Rc, + xdg_toplevel: Rc, + jay_sync_file_surface: Rc, + frame_task: AsyncEvent, + want_frame: Cell, + have_frame: Cell, + initial_commit_pending: Cell, + owner: CloneCell>>, + active_seat: CloneCell>>, + raw_input: RefCell>, + close: Cell, + repaint_timeout: Cell, + repaint_timeout_changed: AsyncEvent, + fonts_changed: Cell, + + buffers: DoubleBuffered>>>, + + surface_pending: RefCell, + logical_size: Cell<[i32; 2]>, + physical_size: Cell<[i32; 2]>, + scale: Cell, +} + +struct EggFramebuffer { + client_acquire_fence: CloneCell>>, + size: Cell<[i32; 2]>, + bo: Rc, + egv: Rc, + wl_buffer: Rc, + window: Weak, + drop_queue: Rc>>, +} + +#[derive(Default)] +struct PendingWindowState { + size: Option<(i32, i32)>, +} + +const PROPORTIONAL_FONTS: &[&str] = &["sans-serif", "Noto Sans", "Noto Color Emoji"]; + +const MONOSPACE_FONTS: &[&str] = &["monospace", "Noto Sans Mono", "Noto Color Emoji"]; + +impl Default for EggState { + fn default() -> Self { + let slf = Self { + fonts: Default::default(), + ctx: Default::default(), + context_ids: Default::default(), + cxts: Default::default(), + }; + slf.reset_fonts(); + slf + } +} + +impl EggState { + pub fn reset_fonts(&self) { + self.set_proportional_fonts(PROPORTIONAL_FONTS); + self.set_monospace_fonts(MONOSPACE_FONTS); + } + + pub fn set_proportional_fonts(&self, fonts: &[&str]) { + self.change_fonts(fonts, |f| &mut f.proportional) + } + + pub fn set_monospace_fonts(&self, fonts: &[&str]) { + self.change_fonts(fonts, |f| &mut f.monospace) + } + + fn change_fonts(&self, fonts: &[&str], field: impl Fn(&mut EggFonts) -> &mut Vec) { + let f = &mut *self.fonts.borrow_mut(); + let field = field(f); + if *field == fonts { + return; + } + *field = fonts.iter().map(|s| s.to_string()).collect(); + f.definitions.take(); + for ctx in self.cxts.lock().values() { + for window in ctx.windows.lock().values() { + window.fonts_changed.set(true); + window.want_frame(); + } + } + } + + pub fn clear(&self) { + self.ctx.take(); + } + + fn font_definitions(&self) -> FontDefinitions { + let f = &mut self.fonts.borrow_mut(); + if let Some(d) = &f.definitions { + return d.clone(); + } + let mut d = FontDefinitions::empty(); + for (ff, list) in [ + (FontFamily::Proportional, &f.proportional), + (FontFamily::Monospace, &f.monospace), + ] { + for family in list { + let font = match match_font(family) { + Ok(f) => f, + Err(e) => { + log::warn!("Could not find font family {family}: {}", ErrorFmt(e)); + continue; + } + }; + if let Entry::Vacant(e) = d.font_data.entry(font.fullname.clone()) { + let data = match fs::read(&font.file) { + Ok(f) => f, + Err(e) => { + log::error!("Could not read {}: {}", font.file.display(), ErrorFmt(e)); + continue; + } + }; + let data = Arc::new(FontData { + font: data.into(), + index: font.index.unwrap_or(0) as u32, + tweak: Default::default(), + }); + e.insert(data); + } + let list = d.families.entry(ff.clone()).or_default(); + if list.not_contains(&font.fullname) { + list.push(font.fullname); + } + } + } + { + let name = "material-icons"; + let list = d.families.entry(FontFamily::Proportional).or_default(); + if list.iter().all(|n| n != name) { + if let Entry::Vacant(e) = d.font_data.entry(name.to_string()) { + let data = Arc::new(FontData { + font: include_bytes!("icons.ttf").into(), + index: 0, + tweak: Default::default(), + }); + e.insert(data); + } + list.push(name.to_string()); + } + } + f.definitions = Some(d.clone()); + d + } +} + +impl State { + #[expect(dead_code)] + pub fn get_egg_context(self: &Rc) -> Result, EggError> { + if let Some(ctx) = self.egg_state.ctx.get() { + return Ok(ctx); + } + let Some(ctx) = self.render_ctx.get() else { + return Err(EggError::NoRenderContext); + }; + let (client1, client2) = uapi::socketpair(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) + .map_err(Into::into) + .map_err(EggError::CreateSocketPair)?; + let allocator = ctx.allocator(); + let dev = allocator.drm().map(|d| d.dev()); + let renderer = EgvRenderer::new(&self.eng, &self.ring, &self.eventfd_cache, dev) + .map_err(EggError::CreateRenderer)?; + let con = UsrCon::from_socket( + &self.ring, + &self.wheel, + &self.eng, + &self.dma_buf_ids, + &Rc::new(client1), + 0, + ); + let client = self + .clients + .spawn2( + self.clients.id(), + self, + Rc::new(client2), + uapi::getuid(), + uapi::getpid(), + ClientCaps::all(), + true, + false, + &Rc::new(AcceptorMetadata::secure()), + ) + .map_err(EggError::SpawnClient)?; + let registry = con.get_registry(); + let jay_compositor = { + let obj = Rc::new(UsrJayCompositor { + id: con.id(), + con: con.clone(), + owner: Default::default(), + caps: Default::default(), + version: Version(27), + }); + registry.bind(self.globals.singletons[Singleton::JayCompositor], &*obj); + con.add_object(obj.clone()); + obj + }; + macro_rules! add_singletons { + ($($name:ident, $global:ident, $ty:ident, $version:expr;)*) => { + $( + let $name = Rc::new($ty { + id: con.id(), + con: con.clone(), + version: Version($version), + }); + registry.bind(self.globals.singletons[Singleton::$global], &*$name); + con.add_object($name.clone()); + )* + }; + } + add_singletons! { + wl_compositor, WlCompositor, UsrWlCompositor, 6; + xdg_wm_base, XdgWmBase, UsrXdgWmBase, 7; + wl_data_device_manager, WlDataDeviceManager, UsrWlDataDeviceManager, 3; + zwp_primary_selection_device_manager_v1, ZwpPrimarySelectionDeviceManagerV1, UsrZwpPrimarySelectionDeviceManagerV1, 1; + wp_viewporter, WpViewporter, UsrWpViewporter, 1; + wp_cursor_shape_manager_v1, WpCursorShapeManagerV1, UsrWpCursorShapeManagerV1, 2; + wp_fractional_scale_manager, WpFractionalScaleManagerV1, UsrWpFractionalScaleManager, 1; + zwp_linux_dmabuf_v1, ZwpLinuxDmabufV1, UsrZwpLinuxDmabufV1, 5; + } + let ctx = Rc::new(EggContext { + inner: Rc::new(EggContextInner { + id: self.egg_state.context_ids.next(), + renderer, + allocator, + state: self.clone(), + _client: client.clone(), + con, + jay_compositor, + wl_compositor, + xdg_wm_base, + wl_data_device_manager, + _zwp_primary_selection_device_manager_v1: zwp_primary_selection_device_manager_v1, + wp_viewporter, + wp_cursor_shape_manager_v1, + wp_fractional_scale_manager, + zwp_linux_dmabuf_v1, + registry, + windows: Default::default(), + seats: Default::default(), + }), + }); + ctx.inner.con.owner.set(Some(ctx.inner.clone())); + self.egg_state.cxts.set(ctx.inner.id, ctx.inner.clone()); + for &global_name in self.globals.seats.lock().keys() { + ctx.inner.add_seat(global_name); + } + self.egg_state.ctx.set(Some(ctx.clone())); + Ok(ctx) + } +} + +impl EggContext { + #[expect(dead_code)] + pub fn create_window(self: &Rc, title: &str) -> Rc { + let i = &self.inner; + let wl_surface = i.wl_compositor.create_surface(); + let jay_sync_file_surface = i.jay_compositor.get_sync_file_surface(&wl_surface); + let xdg_surface = i.xdg_wm_base.get_xdg_surface(&wl_surface); + let xdg_toplevel = xdg_surface.get_toplevel(); + xdg_toplevel.set_title(title); + let wp_fractional_scale = i + .wp_fractional_scale_manager + .get_fractional_scale(&wl_surface); + let wp_viewport = i.wp_viewporter.get_viewport(&wl_surface); + wl_surface.commit(); + let window = Rc::new(EggWindowInner { + ctx: self.inner.clone(), + egv: i.renderer.create_context(), + egui: egui::Context::default(), + wl_surface, + wp_viewport, + wp_fractional_scale, + xdg_surface, + xdg_toplevel, + jay_sync_file_surface, + frame_task: Default::default(), + want_frame: Default::default(), + have_frame: Cell::new(true), + initial_commit_pending: Cell::new(true), + owner: Default::default(), + active_seat: Default::default(), + raw_input: RefCell::new(None), + close: Default::default(), + repaint_timeout: Cell::new(u64::MAX), + repaint_timeout_changed: Default::default(), + fonts_changed: Cell::new(true), + buffers: Default::default(), + surface_pending: Default::default(), + logical_size: Cell::new([800, 600]), + physical_size: Cell::new([800, 600]), + scale: Default::default(), + }); + window + .egui + .all_styles_mut(|s| s.spacing.item_spacing.y = 5.0); + window.xdg_surface.owner.set(Some(window.clone())); + window.xdg_toplevel.owner.set(Some(window.clone())); + window.wp_fractional_scale.owner.set(Some(window.clone())); + i.windows.set(window.wl_surface.id, window.clone()); + let eng = &i.state.eng; + let render_task = eng.spawn("egui-render", window.clone().render_frames()); + let timer_task = eng.spawn("egui-timer", window.clone().handle_timer()); + let window = EggWindow { + _ctx: self.clone(), + inner: window, + _render_task: render_task, + _timer_task: timer_task, + }; + Rc::new(window) + } +} + +impl EggContextInner { + pub fn add_seat(self: &Rc, global_name: GlobalName) { + let wl_seat = Rc::new(UsrWlSeat { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: Version(10), + }); + self.registry.bind(global_name, &*wl_seat); + self.con.add_object(wl_seat.clone()); + let wl_pointer = wl_seat.get_pointer(); + let wl_keyboard = wl_seat.get_keyboard(); + let wp_cursor_shape_device_v1 = self.wp_cursor_shape_manager_v1.get_pointer(&wl_pointer); + let wl_data_device = self.wl_data_device_manager.get_data_device(&wl_seat); + let seat = Rc::new(EggSeatInner { + ctx: self.clone(), + global_name, + wl_seat, + wl_pointer, + wl_data_device, + pointer_window: Default::default(), + pointer_enter_serial: Default::default(), + pointer_serial: Default::default(), + pointer_pos: Default::default(), + kb_modifiers: Default::default(), + wp_cursor_shape_device_v1, + wl_keyboard, + kb_window: Default::default(), + kb_serial: Default::default(), + serial: Default::default(), + wl_data_source: Default::default(), + copy_text: Default::default(), + copy_task: Default::default(), + paste_task: Default::default(), + }); + seat.wl_pointer.owner.set(Some(seat.clone())); + seat.wl_keyboard.owner.set(Some(seat.clone())); + let seat = EggSeat { inner: seat }; + self.seats.set(global_name, seat); + } +} + +const TEXT_PLAIN: &str = "text/plain;charset=utf-8"; + +impl EggSeatInner { + pub fn request_paste(self: &Rc) { + let Some(offer) = self.wl_data_device.selection.get() else { + return; + }; + if !offer.mime_types.borrow().contains(TEXT_PLAIN) { + return; + } + let Some(window) = self.kb_window.get() else { + return; + }; + let Pipe { read, write } = match pipe() { + Ok(p) => p.map_read(Rc::new).map_write(Rc::new), + Err(e) => { + log::error!("Could not create pipe: {}", ErrorFmt(e)); + return; + } + }; + offer.receive(TEXT_PLAIN, &write); + let window = Rc::downgrade(&window); + let ring = self.ctx.state.ring.clone(); + let mut buf = Buf::new(1024); + let mut out = Vec::new(); + let task = self.ctx.state.eng.spawn("egui-paste", async move { + loop { + let n = match ring.read(&read, buf.clone()).await { + Ok(n) => n, + Err(e) => { + log::error!("Could not read from peer: {}", ErrorFmt(e)); + return; + } + }; + if n == 0 { + if let Some(window) = window.upgrade() { + let s = match String::from_utf8(out) { + Ok(s) => s, + Err(e) => { + log::error!("Peer did not send UTF-8: {}", ErrorFmt(e)); + return; + } + }; + window.event(Event::Paste(s)); + } + return; + } + out.extend_from_slice(&buf[..n]); + if out.len() >= 1024 * buf.len() { + log::error!("Paste buffer is too large"); + return; + } + } + }); + self.paste_task.set(Some(task)); + } +} + +impl EggWindow { + #[expect(dead_code)] + pub fn request_redraw(&self) { + self.inner.want_frame(); + } + + #[expect(dead_code)] + pub fn set_owner(&self, owner: Option>) { + self.inner.owner.set(owner); + } +} + +impl EggWindowInner { + fn update_physical_size(&self) { + let size = self.logical_size.get(); + let scale = self.scale.get(); + let physical_size = scale.pixel_size(size); + if self.physical_size.replace(physical_size) != physical_size { + self.want_frame(); + } + } + + fn want_frame(&self) { + self.want_frame.set(true); + self.maybe_trigger_frame(); + } + + fn maybe_trigger_frame(&self) { + if self.want_frame.get() && self.have_frame.get() { + self.frame_task.trigger(); + } + } + + async fn handle_timer(self: Rc) { + loop { + let timeout = self.ctx.state.ring.timeout(self.repaint_timeout.get()); + let triggered = || self.repaint_timeout_changed.triggered(); + let timeout = select! { + _ = timeout.fuse() => true, + _ = triggered().fuse() => false, + }; + if timeout { + self.want_frame(); + triggered().await; + } + } + } + + async fn render_frames(self: Rc) { + loop { + self.frame_task.triggered().await; + if let Err(e) = self.render_frame() { + log::error!("Could not render frame: {}", ErrorFmt(e)); + break; + } + } + } + + fn render_frame(self: &Rc) -> Result<(), EggError> { + if self.fonts_changed.take() { + self.egui + .set_fonts(self.ctx.state.egg_state.font_definitions()); + } + if self.initial_commit_pending.get() { + return Ok(()); + } + if !self.have_frame.get() { + return Ok(()); + } + if !self.want_frame.get() { + return Ok(()); + } + let Some(owner) = self.owner.get() else { + return Ok(()); + }; + let Some(render_ctx) = self.ctx.state.render_ctx.get() else { + return Ok(()); + }; + let Some(format) = render_ctx.formats().get(&EGV_FORMAT.drm) else { + return Ok(()); + }; + let logical_size = self.logical_size.get(); + let physical_size = self.physical_size.get(); + let mut fb_opt = self.buffers.back().get(); + 'check: { + if let Some(fb) = &fb_opt { + if fb.size.get() != physical_size { + fb_opt = None; + break 'check; + } + if !format.read_modifiers.contains(&fb.bo.dmabuf().modifier) { + fb_opt = None; + break 'check; + } + } + } + let fb = match fb_opt { + Some(fb) => fb, + _ => { + let modifiers: Vec<_> = self + .ctx + .renderer + .support() + .iter() + .filter(|s| { + s.max_width >= physical_size[0] as u32 + && s.max_height >= physical_size[1] as u32 + && format.read_modifiers.contains(&s.modifier) + }) + .map(|s| s.modifier) + .collect(); + let bo = self + .ctx + .allocator + .create_bo( + &self.ctx.state.dma_buf_ids, + physical_size[0], + physical_size[1], + EGV_FORMAT, + &modifiers, + BO_USE_RENDERING, + ) + .map_err(EggError::AllocateBuffer)?; + let egv = self + .egv + .import_framebuffer(&bo) + .map_err(EggError::ImportFramebuffer)?; + let dmabuf = bo.dmabuf(); + let sync_file = dmabuf + .export_sync_file(DMA_BUF_SYNC_WRITE) + .map_err(EggError::ExportBoSyncFile)?; + let wl_buffer = self.ctx.zwp_linux_dmabuf_v1.create_buffer(dmabuf); + let fb = Rc::new(EggFramebuffer { + client_acquire_fence: CloneCell::new(Some(sync_file)), + size: Cell::new(physical_size), + bo, + egv, + wl_buffer, + window: Rc::downgrade(self), + drop_queue: self.ctx.state.bo_drop_queue.clone(), + }); + self.buffers.back().set(Some(fb.clone())); + fb + } + }; + let Some(sync_file) = fb.client_acquire_fence.get() else { + return Ok(()); + }; + let raw_input = self + .raw_input + .take() + .unwrap_or_else(|| self.default_raw_input()); + let full_output = self.egui.run(raw_input, |ctx| { + owner.clone().render(ctx); + }); + let FullOutput { + platform_output, + textures_delta, + shapes, + pixels_per_point, + viewport_output, + } = full_output; + let primitives = self.egui.tessellate(shapes, pixels_per_point); + let sync = fb + .egv + .render( + textures_delta, + pixels_per_point, + &primitives, + (0.0, 0.0), + sync_file.as_ref(), + ) + .map_err(EggError::Render)?; + let PlatformOutput { + commands, + cursor_icon, + .. + } = platform_output; + if let Some(seat) = self.active_seat.get() { + 'set_icon: { + let cursor = match cursor_icon { + CursorIcon::None => { + seat.wl_pointer + .set_cursor(seat.pointer_serial.get(), None, 0, 0); + break 'set_icon; + } + CursorIcon::Default => KnownCursor::Default, + CursorIcon::ContextMenu => KnownCursor::ContextMenu, + CursorIcon::Help => KnownCursor::Help, + CursorIcon::PointingHand => KnownCursor::Pointer, + CursorIcon::Progress => KnownCursor::Progress, + CursorIcon::Wait => KnownCursor::Wait, + CursorIcon::Cell => KnownCursor::Cell, + CursorIcon::Crosshair => KnownCursor::Crosshair, + CursorIcon::Text => KnownCursor::Text, + CursorIcon::VerticalText => KnownCursor::VerticalText, + CursorIcon::Alias => KnownCursor::Alias, + CursorIcon::Copy => KnownCursor::Copy, + CursorIcon::Move => KnownCursor::Move, + CursorIcon::NoDrop => KnownCursor::NoDrop, + CursorIcon::NotAllowed => KnownCursor::NotAllowed, + CursorIcon::Grab => KnownCursor::Grab, + CursorIcon::Grabbing => KnownCursor::Grabbing, + CursorIcon::AllScroll => KnownCursor::AllScroll, + CursorIcon::ResizeHorizontal => KnownCursor::EwResize, + CursorIcon::ResizeNeSw => KnownCursor::NeswResize, + CursorIcon::ResizeNwSe => KnownCursor::NwseResize, + CursorIcon::ResizeVertical => KnownCursor::NsResize, + CursorIcon::ResizeEast => KnownCursor::EResize, + CursorIcon::ResizeSouthEast => KnownCursor::SeResize, + CursorIcon::ResizeSouth => KnownCursor::SResize, + CursorIcon::ResizeSouthWest => KnownCursor::SwResize, + CursorIcon::ResizeWest => KnownCursor::WResize, + CursorIcon::ResizeNorthWest => KnownCursor::NwResize, + CursorIcon::ResizeNorth => KnownCursor::NResize, + CursorIcon::ResizeNorthEast => KnownCursor::NeResize, + CursorIcon::ResizeColumn => KnownCursor::ColResize, + CursorIcon::ResizeRow => KnownCursor::RowResize, + CursorIcon::ZoomIn => KnownCursor::ZoomIn, + CursorIcon::ZoomOut => KnownCursor::ZoomOut, + }; + seat.wp_cursor_shape_device_v1 + .set_shape(seat.pointer_serial.get(), cursor); + } + } + for command in commands { + match command { + OutputCommand::CopyText(t) => { + if let Some(seat) = self.active_seat.get() { + let data_src = self.ctx.wl_data_device_manager.create_data_source(); + data_src.offer(TEXT_PLAIN); + data_src.owner.set(Some(seat.clone())); + seat.wl_data_device + .set_selection(seat.serial.get(), &data_src); + if let Some(old) = seat.wl_data_source.set(Some(data_src)) { + old.con.remove_obj(&*old); + } + seat.copy_text.replace(Some(Buf::from_slice(t.as_bytes()))); + } + } + OutputCommand::CopyImage(_) => {} + OutputCommand::OpenUrl(url) => { + if let Some(forker) = self.ctx.state.forker.get() { + forker.spawn("xdg-open".to_string(), vec![url.url], vec![], vec![]); + } + } + } + } + let Some(viewport) = viewport_output.get(&ViewportId::ROOT) else { + return Err(EggError::NoViewportOutput); + }; + for command in &viewport.commands { + match command { + ViewportCommand::Close => self.close.set(true), + ViewportCommand::CancelClose => self.close.set(false), + ViewportCommand::Title(s) => self.xdg_toplevel.set_title(s), + ViewportCommand::Fullscreen(b) => self.xdg_toplevel.set_fullscreen(*b), + ViewportCommand::RequestPaste => { + if let Some(seat) = self.active_seat.get() { + seat.request_paste(); + } + } + _ => {} + } + } + let repaint_delay = u64::try_from(viewport.repaint_delay.as_nanos()).unwrap_or(u64::MAX); + let repaint_timeout = self.ctx.state.now_nsec().saturating_add(repaint_delay); + self.repaint_timeout.set(repaint_timeout); + if repaint_timeout != u64::MAX { + self.repaint_timeout_changed.trigger(); + } + self.wl_surface.attach(&fb.wl_buffer); + self.wl_surface.damage(); + self.jay_sync_file_surface.set_acquire(sync.as_ref()); + self.jay_sync_file_surface + .get_release() + .owner + .set(Some(fb.clone())); + self.wl_surface.frame().owner.set(Some(self.clone())); + self.wp_viewport + .set_destination(logical_size[0], logical_size[1]); + self.wl_surface.commit(); + fb.client_acquire_fence.take(); + self.buffers.flip(); + self.have_frame.set(false); + self.want_frame.set(false); + if self.close.get() { + owner.close(); + } + Ok(()) + } +} + +impl UsrXdgSurfaceOwner for EggWindowInner { + fn configure(&self) { + let pending = mem::take(&mut *self.surface_pending.borrow_mut()); + if let Some((mut w, mut h)) = pending.size { + let [old_w, old_h] = self.logical_size.get(); + w = if w > 0 { w } else { old_w }; + h = if h > 0 { h } else { old_h }; + let size = [w, h]; + if self.logical_size.replace(size) != size { + self.update_physical_size(); + } + } + if self.initial_commit_pending.take() { + self.want_frame(); + } + } +} + +impl UsrXdgToplevelOwner for EggWindowInner { + fn configure(&self, width: i32, height: i32) { + self.surface_pending.borrow_mut().size = Some((width, height)); + } + + fn close(&self) { + let raw_input = &mut *self.raw_input.borrow_mut(); + let raw_input = raw_input.get_or_insert_with(|| self.default_raw_input()); + raw_input + .viewports + .get_mut(&ViewportId::ROOT) + .unwrap() + .events + .push(ViewportEvent::Close); + self.close.set(true); + self.want_frame(); + } +} + +impl UsrWpFractionalScaleOwner for EggWindowInner { + fn preferred_scale(self: Rc, ev: &PreferredScale) { + let scale = Scale::from_wl(ev.scale); + if self.scale.replace(scale) != scale { + self.update_physical_size(); + } + } +} + +impl EggWindowInner { + fn event(&self, event: Event) { + let raw_input = &mut *self.raw_input.borrow_mut(); + let raw_input = raw_input.get_or_insert_with(|| self.default_raw_input()); + raw_input.events.push(event); + self.want_frame(); + } + + fn default_raw_input(&self) -> RawInput { + let viewport_info = ViewportInfo { + native_pixels_per_point: Some(self.scale.get().to_f64() as _), + ..Default::default() + }; + let size = self.logical_size.get(); + let size = + egui::Rect::from_min_size(Pos2::default(), Vec2::new(size[0] as f32, size[1] as f32)); + let mut modifiers = Modifiers::default(); + if let Some(seat) = self.active_seat.get() { + modifiers = seat.kb_modifiers.get(); + } + RawInput { + viewport_id: ViewportId::ROOT, + viewports: std::iter::once((ViewportId::ROOT, viewport_info)).collect(), + screen_rect: Some(size), + max_texture_side: Some(self.ctx.renderer.max_texture_side()), + time: Some(self.ctx.state.now_nsec() as f64 / 1_000_000_000.0), + modifiers, + ..Default::default() + } + } +} + +impl EggSeatInner { + fn activate_pointer_window(self: &Rc) -> Option> { + let window = self.pointer_window.get()?; + window.active_seat.set(Some(self.clone())); + Some(window) + } + + fn activate_kb_window(self: &Rc) -> Option> { + let window = self.kb_window.get()?; + window.active_seat.set(Some(self.clone())); + Some(window) + } + + fn leave(self: &Rc) { + if let Some(window) = self.pointer_window.take() + && let Some(active_seat) = window.active_seat.get() + && rc_eq(&active_seat, &self) + { + window.active_seat.take(); + } + } + + fn unfocus(self: &Rc) { + if let Some(window) = self.kb_window.take() + && let Some(active_seat) = window.active_seat.get() + && rc_eq(&active_seat, &self) + { + window.active_seat.take(); + } + } + + fn motion(self: &Rc, surface_x: Fixed, surface_y: Fixed) { + let Some(window) = self.activate_pointer_window() else { + return; + }; + let pos = pos2(surface_x.to_f32(), surface_y.to_f32()); + self.pointer_pos.set(pos); + window.event(Event::PointerMoved(pos)); + } +} + +impl UsrWlPointerOwner for EggSeatInner { + fn enter(self: Rc, ev: &Enter) { + let Some(window) = self.ctx.windows.get(&ev.surface) else { + return; + }; + self.pointer_window.set(Some(window.clone())); + self.pointer_enter_serial.set(ev.serial); + self.pointer_serial.set(ev.serial); + self.serial.set(ev.serial); + (&self).motion(ev.surface_x, ev.surface_y); + } + + fn leave(self: Rc, _ev: &Leave) { + (&self).leave(); + } + + fn motion(self: Rc, ev: &Motion) { + (&self).motion(ev.surface_x, ev.surface_y); + } + + fn button(self: Rc, ev: &Button) { + let Some(window) = self.activate_pointer_window() else { + return; + }; + self.pointer_serial.set(ev.serial); + self.serial.set(ev.serial); + let button = match ev.button { + BTN_LEFT => PointerButton::Primary, + BTN_RIGHT => PointerButton::Secondary, + BTN_MIDDLE => PointerButton::Middle, + BTN_SIDE => PointerButton::Extra1, + BTN_EXTRA => PointerButton::Extra2, + _ => return, + }; + window.event(Event::PointerButton { + pos: self.pointer_pos.get(), + button, + pressed: ev.state == wl_pointer::PRESSED, + modifiers: self.kb_modifiers.get(), + }); + } + + fn scroll(self: Rc, ps: &PendingScroll) { + let Some(window) = self.activate_pointer_window() else { + return; + }; + let v120_x = ps.v120[HORIZONTAL_SCROLL].get(); + let v120_y = ps.v120[VERTICAL_SCROLL].get(); + let px_x = ps.px[HORIZONTAL_SCROLL].get(); + let px_y = ps.px[VERTICAL_SCROLL].get(); + let unit; + let delta; + if v120_x.is_some() || v120_y.is_some() { + unit = MouseWheelUnit::Line; + delta = vec2( + -v120_x.unwrap_or_default() as f32 / 120.0, + -v120_y.unwrap_or_default() as f32 / 120.0, + ); + } else if px_x.is_some() || px_y.is_some() { + unit = MouseWheelUnit::Point; + delta = vec2( + -px_x.unwrap_or_default().to_f32(), + -px_y.unwrap_or_default().to_f32(), + ); + } else { + return; + } + window.event(Event::MouseWheel { + unit, + delta, + modifiers: self.kb_modifiers.get(), + }); + } +} + +impl EggSeatInner { + fn handle_key(self: &Rc, lookup: Lookup<'_>, serial: u32, down: bool) { + let Some(window) = self.activate_kb_window() else { + return; + }; + self.kb_serial.set(serial); + self.serial.set(serial); + if down { + let mut text = String::new(); + for key in lookup { + if let Some(c) = key.char() + && c.is_not_control() + { + text.push(c); + } + } + if text.is_not_empty() { + window.event(Event::Text(text)); + } + } + for key in lookup { + let mut modifiers = map_mods(lookup.remaining_mods()); + let Some(key) = map_key(key.keysym(), &mut modifiers) else { + continue; + }; + if down && modifiers.ctrl { + match key { + Key::V => self.request_paste(), + Key::C => window.event(Event::Copy), + Key::X => window.event(Event::Cut), + _ => {} + } + } + window.event(Event::Key { + key, + physical_key: None, + pressed: down, + repeat: false, + modifiers, + }); + } + } +} + +impl UsrWlKeyboardOwner for EggSeatInner { + fn focus(self: Rc, surface: WlSurfaceId, serial: u32) { + let Some(window) = self.ctx.windows.get(&surface) else { + return; + }; + self.kb_window.set(Some(window.clone())); + self.kb_serial.set(serial); + self.serial.set(serial); + window.active_seat.set(Some(self.clone())); + } + + fn unfocus(self: Rc) { + (&self).unfocus(); + } + + fn modifiers(self: Rc, mods: ModifierMask) { + self.kb_modifiers.set(map_mods(mods)); + } + + fn down(self: Rc, lookup: Lookup<'_>, serial: u32) { + self.handle_key(lookup, serial, true); + } + + fn repeat(self: Rc, lookup: Lookup<'_>, serial: u32) { + self.handle_key(lookup, serial, true); + } + + fn up(self: Rc, lookup: Lookup<'_>, serial: u32) { + self.handle_key(lookup, serial, false); + } +} + +fn map_mods(mods: ModifierMask) -> Modifiers { + Modifiers { + alt: mods.contains(ModifierMask::ALT), + ctrl: mods.contains(ModifierMask::CONTROL), + shift: mods.contains(ModifierMask::SHIFT), + mac_cmd: false, + command: mods.contains(ModifierMask::CONTROL), + } +} + +impl UsrJaySyncFileReleaseOwner for EggFramebuffer { + fn release(&self, sync_file: Option) { + self.client_acquire_fence.set(Some(sync_file)); + if let Some(window) = self.window.upgrade() { + window.maybe_trigger_frame(); + } + } +} + +impl UsrWlCallbackOwner for EggWindowInner { + fn done(self: Rc) { + self.have_frame.set(true); + self.maybe_trigger_frame(); + } +} + +fn map_key(kc: Keysym, mods: &mut Modifiers) -> Option { + use {Key as K, kbvm::syms as s}; + let mut with_shift = |k| { + mods.shift = true; + k + }; + let key = match kc { + s::Down | s::KP_Down => K::ArrowDown, + s::Left | s::KP_Left => K::ArrowLeft, + s::Right | s::KP_Right => K::ArrowRight, + s::Up | s::KP_Up => K::ArrowUp, + s::Escape => K::Escape, + s::Tab | s::KP_Tab => K::Tab, + s::ISO_Left_Tab => with_shift(K::Tab), + s::BackSpace => K::Backspace, + s::Return | s::KP_Enter => K::Enter, + s::space | s::KP_Space => K::Space, + s::Insert | s::KP_Insert => K::Insert, + s::Delete | s::KP_Delete => K::Delete, + s::Home | s::KP_Home | s::KP_Begin => K::Home, + s::End | s::KP_End => K::End, + s::Page_Up | s::KP_Page_Up => K::PageUp, + s::Page_Down | s::KP_Page_Down => K::PageDown, + s::XF86Copy => K::Copy, + s::XF86Cut => K::Cut, + s::XF86Paste => K::Paste, + s::colon => K::Colon, + s::comma => K::Comma, + s::backslash => K::Backslash, + s::slash | s::KP_Divide => K::Slash, + s::bar => K::Pipe, + s::question => K::Questionmark, + s::exclam => K::Exclamationmark, + s::bracketleft => K::OpenBracket, + s::bracketright => K::CloseBracket, + s::braceleft => K::OpenCurlyBracket, + s::braceright => K::CloseCurlyBracket, + s::grave => K::Backtick, + s::minus | s::KP_Subtract => K::Minus, + s::period | s::KP_Decimal => K::Period, + s::plus | s::KP_Add => K::Plus, + s::equal | s::KP_Equal => K::Equals, + s::semicolon => K::Semicolon, + s::quotedbl => K::Quote, + s::KP_0 | s::_0 => K::Num0, + s::KP_1 | s::_1 => K::Num1, + s::KP_2 | s::_2 => K::Num2, + s::KP_3 | s::_3 => K::Num3, + s::KP_4 | s::_4 => K::Num4, + s::KP_5 | s::_5 => K::Num5, + s::KP_6 | s::_6 => K::Num6, + s::KP_7 | s::_7 => K::Num7, + s::KP_8 | s::_8 => K::Num8, + s::KP_9 | s::_9 => K::Num9, + s::a => K::A, + s::b => K::B, + s::c => K::C, + s::d => K::D, + s::e => K::E, + s::f => K::F, + s::g => K::G, + s::h => K::H, + s::i => K::I, + s::j => K::J, + s::k => K::K, + s::l => K::L, + s::m => K::M, + s::n => K::N, + s::o => K::O, + s::p => K::P, + s::q => K::Q, + s::r => K::R, + s::s => K::S, + s::t => K::T, + s::u => K::U, + s::v => K::V, + s::w => K::W, + s::x => K::X, + s::y => K::Y, + s::z => K::Z, + s::A => with_shift(K::A), + s::B => with_shift(K::B), + s::C => with_shift(K::C), + s::D => with_shift(K::D), + s::E => with_shift(K::E), + s::F => with_shift(K::F), + s::G => with_shift(K::G), + s::H => with_shift(K::H), + s::I => with_shift(K::I), + s::J => with_shift(K::J), + s::K => with_shift(K::K), + s::L => with_shift(K::L), + s::M => with_shift(K::M), + s::N => with_shift(K::N), + s::O => with_shift(K::O), + s::P => with_shift(K::P), + s::Q => with_shift(K::Q), + s::R => with_shift(K::R), + s::S => with_shift(K::S), + s::T => with_shift(K::T), + s::U => with_shift(K::U), + s::V => with_shift(K::V), + s::W => with_shift(K::W), + s::X => with_shift(K::X), + s::Y => with_shift(K::Y), + s::Z => with_shift(K::Z), + s::F1 | s::KP_F1 => K::F1, + s::F2 | s::KP_F2 => K::F2, + s::F3 | s::KP_F3 => K::F3, + s::F4 | s::KP_F4 => K::F4, + s::F5 => K::F5, + s::F6 => K::F6, + s::F7 => K::F7, + s::F8 => K::F8, + s::F9 => K::F9, + s::F10 => K::F10, + s::F11 => K::F11, + s::F12 => K::F12, + s::F13 => K::F13, + s::F14 => K::F14, + s::F15 => K::F15, + s::F16 => K::F16, + s::F17 => K::F17, + s::F18 => K::F18, + s::F19 => K::F19, + s::F20 => K::F20, + s::F21 => K::F21, + s::F22 => K::F22, + s::F23 => K::F23, + s::F24 => K::F24, + s::F25 => K::F25, + s::F26 => K::F26, + s::F27 => K::F27, + s::F28 => K::F28, + s::F29 => K::F29, + s::F30 => K::F30, + s::F31 => K::F31, + s::F32 => K::F32, + s::F33 => K::F33, + s::F34 => K::F34, + s::F35 => K::F35, + s::XF86Back => K::BrowserBack, + _ => return None, + }; + Some(key) +} + +impl Drop for EggSeat { + fn drop(&mut self) { + let s = &self.inner; + s.copy_task.take(); + s.paste_task.take(); + s.leave(); + s.unfocus(); + if let Some(v) = s.wl_data_source.take() { + s.ctx.con.remove_obj(&*v); + } + s.ctx.seats.remove(&s.global_name); + s.ctx.con.remove_obj(&*s.wl_data_device); + s.ctx.con.remove_obj(&*s.wl_keyboard); + s.ctx.con.remove_obj(&*s.wp_cursor_shape_device_v1); + s.ctx.con.remove_obj(&*s.wl_pointer); + s.ctx.con.remove_obj(&*s.wl_seat); + } +} + +impl Drop for EggContext { + fn drop(&mut self) { + let i = &self.inner; + i.state.egg_state.cxts.remove(&self.inner.id); + i.seats.clear(); + i.windows.clear(); + i.con.owner.take(); + i.con.kill(); + } +} + +impl Drop for EggWindow { + fn drop(&mut self) { + let i = &self.inner; + i.ctx.windows.remove(&i.wl_surface.id); + if let Some(seat) = i.active_seat.take() { + for field in [&seat.kb_window, &seat.pointer_window] { + if let Some(w) = field.get() + && rc_eq(&w, i) + { + field.take(); + } + } + } + i.owner.take(); + i.ctx.con.remove_obj(&*i.jay_sync_file_surface); + i.ctx.con.remove_obj(&*i.xdg_toplevel); + i.ctx.con.remove_obj(&*i.xdg_surface); + i.ctx.con.remove_obj(&*i.wp_fractional_scale); + i.ctx.con.remove_obj(&*i.wp_viewport); + i.ctx.con.remove_obj(&*i.wl_surface); + } +} + +impl UsrWlDataSourceOwner for EggSeatInner { + fn send(&self, _mime_type: &str, fd: Rc) { + let Some(buf) = self.copy_text.borrow_mut().as_mut().map(|b| b.clone()) else { + return; + }; + let ring = self.ctx.state.ring.clone(); + let task = self.ctx.state.eng.spawn("egg-copy-text", async move { + if let Err(e) = ring.write(&fd, buf, None).await { + log::error!("Could not send text to client: {}", e); + } + }); + self.copy_task.set(Some(task)); + } +} + +impl UsrConOwner for EggContextInner { + fn killed(&self) { + if let Some(ctx) = self.state.egg_state.ctx.get() + && ctx.inner.id == self.id + { + self.state.egg_state.ctx.take(); + } + for window in self.windows.clear().values() { + if let Some(owner) = window.owner.take() { + owner.close(); + } + } + } +} + +impl Drop for EggFramebuffer { + fn drop(&mut self) { + if let Some(Some(fence)) = self.client_acquire_fence.take() { + self.drop_queue.push(&fence.0, self.bo.clone()); + } + self.wl_buffer.con.remove_obj(&*self.wl_buffer); + } +} diff --git a/src/egui_adapter/egui_vulkan.rs b/src/egui_adapter/egui_vulkan.rs new file mode 100644 index 00000000..aae9f29d --- /dev/null +++ b/src/egui_adapter/egui_vulkan.rs @@ -0,0 +1,2055 @@ +use { + crate::{ + allocator::BufferObject, + async_engine::{AsyncEngine, SpawnedFuture}, + eventfd_cache::EventfdCache, + format::XRGB8888, + gfx_api::{FdSync, SyncFile}, + io_uring::IoUring, + utils::{errorfmt::ErrorFmt, queue::AsyncQueue}, + video::{Modifier, dmabuf::PlaneVec, drm::syncobj::SyncobjCtx}, + vulkan_core::{ + VULKAN_API_VERSION, VulkanCoreError, VulkanCoreInstance, VulkanDeviceFeatures, + device::VulkanDeviceInf, + gpu_alloc_ash::AshMemoryDevice, + map_extension_properties, + sync::{VulkanDeviceSyncExt, VulkanSync}, + timeline_semaphore::{VulkanDeviceTimelineSemaphoreExt, VulkanTimelineSemaphore}, + }, + }, + ahash::AHashMap, + arrayvec::ArrayVec, + ash::{ + Device, + ext::{ + external_memory_dma_buf, image_drm_format_modifier, physical_device_drm, + queue_family_foreign, + }, + khr::{external_fence_fd, external_memory_fd, external_semaphore_fd, push_descriptor}, + util::read_spv, + vk::{ + self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BindImageMemoryInfo, + BindImagePlaneMemoryInfo, BlendFactor, BlendOp, BorderColor, Buffer, BufferCreateInfo, + BufferImageCopy2, BufferMemoryBarrier2, BufferUsageFlags, ColorComponentFlags, + CommandBuffer, CommandBufferAllocateInfo, CommandBufferBeginInfo, CommandBufferLevel, + CommandBufferSubmitInfo, CommandBufferUsageFlags, CommandPool, CommandPoolCreateFlags, + CommandPoolCreateInfo, ComponentMapping, ComponentSwizzle, CopyBufferToImageInfo2, + CullModeFlags, DependencyInfo, DescriptorImageInfo, DescriptorSetLayout, + DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, + DescriptorSetLayoutCreateInfo, DescriptorType, DeviceCreateInfo, DeviceMemory, + DeviceQueueCreateInfo, DrmFormatModifierPropertiesEXT, + DrmFormatModifierPropertiesListEXT, DynamicState, Extent2D, Extent3D, + ExternalFenceFeatureFlags, ExternalFenceHandleTypeFlags, ExternalFenceProperties, + ExternalImageFormatPropertiesKHR, ExternalMemoryFeatureFlags, + ExternalMemoryHandleTypeFlags, ExternalMemoryImageCreateInfo, + ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags, + ExternalSemaphoreProperties, Filter, Format, FormatFeatureFlags, FormatProperties2, + FrontFace, GraphicsPipelineCreateInfo, Image, ImageAspectFlags, ImageCreateFlags, + ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, ImageFormatProperties2, + ImageLayout, ImageMemoryBarrier2, ImageMemoryRequirementsInfo2, + ImagePlaneMemoryRequirementsInfo, ImageSubresourceLayers, ImageSubresourceRange, + ImageTiling, ImageType, ImageUsageFlags, ImageView, ImageViewCreateInfo, ImageViewType, + ImportMemoryFdInfoKHR, ImportSemaphoreFdInfoKHR, IndexType, MappedMemoryRange, + MemoryAllocateInfo, MemoryDedicatedAllocateInfo, MemoryFdPropertiesKHR, + MemoryRequirements, MemoryRequirements2, Offset2D, Offset3D, + PhysicalDeviceDrmPropertiesEXT, PhysicalDeviceDynamicRenderingFeatures, + PhysicalDeviceExternalFenceInfo, PhysicalDeviceExternalImageFormatInfoKHR, + PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceFeatures2, + PhysicalDeviceImageDrmFormatModifierInfoEXT, PhysicalDeviceImageFormatInfo2, + PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, + PhysicalDeviceTimelineSemaphoreFeatures, PhysicalDeviceType, + PhysicalDeviceVulkan13Properties, Pipeline, PipelineBindPoint, PipelineCache, + PipelineColorBlendAttachmentState, PipelineColorBlendStateCreateInfo, + PipelineDynamicStateCreateInfo, PipelineInputAssemblyStateCreateInfo, PipelineLayout, + PipelineLayoutCreateInfo, PipelineMultisampleStateCreateInfo, + PipelineRasterizationStateCreateInfo, PipelineRenderingCreateInfo, + PipelineShaderStageCreateInfo, PipelineStageFlags2, PipelineVertexInputStateCreateInfo, + PipelineViewportStateCreateInfo, PolygonMode, PrimitiveTopology, + QUEUE_FAMILY_FOREIGN_EXT, Queue, QueueFlags, Rect2D, RenderingAttachmentInfo, + RenderingInfoKHR, SampleCountFlags, Sampler, SamplerAddressMode, SamplerCreateInfo, + SamplerMipmapMode, Semaphore, SemaphoreCreateInfo, SemaphoreImportFlags, + SemaphoreSubmitInfo, ShaderModule, ShaderModuleCreateInfo, ShaderStageFlags, + SharingMode, SubmitInfo2, SubresourceLayout, VertexInputAttributeDescription, + VertexInputBindingDescription, VertexInputRate, Viewport, WHOLE_SIZE, + WriteDescriptorSet, + }, + }, + bstr::ByteSlice, + egui::epaint::{ + ClippedPrimitive, ImageData, ImageDelta, Primitive, TextureId, Vertex, + textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, + }, + gpu_alloc::{ + AllocationError, Config, GpuAllocator, MapError, MemoryBlock, Request, UsageFlags, + }, + gpu_alloc_types::MemoryPropertyFlags, + isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, + log::Level, + run_on_drop::on_drop, + std::{ + cell::{Cell, RefCell}, + collections::hash_map::Entry, + ffi::CStr, + io::{self, Cursor}, + mem::{ManuallyDrop, offset_of}, + ptr, + rc::Rc, + slice, + }, + thiserror::Error, + uapi::{AsUstr, AssertPacked, Packed, Pod, c}, +}; + +#[derive(Debug, Error)] +pub enum EgvError { + #[error(transparent)] + Core(#[from] VulkanCoreError), + #[error("could not read spv source")] + ReadSpv(#[source] io::Error), + #[error("could not create a shader module")] + CreateShaderModule(#[source] vk::Result), + #[error("could not create a sampler")] + CreateSampler(#[source] vk::Result), + #[error("could not allocate GPU memory")] + AllocateMemory(#[source] AllocationError), + #[error("could not map GPU memory")] + MapMemory(#[source] MapError), + #[error("could not create a buffer")] + CreateBuffer(#[source] vk::Result), + #[error("could not bind memory to buffer")] + BindBufferMemory(#[source] vk::Result), + #[error("could not bind memory to image")] + BindImageMemory(#[source] vk::Result), + #[error("could not flush GPU memory")] + FlushMemory(#[source] vk::Result), + #[error("could not create an image")] + CreateImage(#[source] vk::Result), + #[error("could not create an image view")] + CreateImageView(#[source] vk::Result), + #[error("tried to render an unknown texture {0:?}")] + UnknownTexture(TextureId), + #[error("could not create a descriptor set layout")] + CreateDescriptorSetLayout(#[source] vk::Result), + #[error("could not create a pipeline layout")] + CreatePipelineLayout(#[source] vk::Result), + #[error("could not create a pipeline")] + CreatePipeline(#[source] vk::Result), + #[error("cannot perform a partial update of unknown texture {0:?}")] + PartialTextureUpdateForUnknownTexture(TextureId), + #[error("cannot perform out-of-bounds texture update for {0:?}")] + TextureUpdateOutOfBounds(TextureId), + #[error("could not allocate a command buffer")] + AllocateCommandBuffer(#[source] vk::Result), + #[error("could not begin a command buffer")] + BeginCommandBuffer(#[source] vk::Result), + #[error("could not end a command buffer")] + EndCommandBuffer(#[source] vk::Result), + #[error("could not create a semaphore")] + CreateSemaphore(#[source] vk::Result), + #[error("could not submit a command buffer")] + Submit(#[source] vk::Result), + #[error("could not get device properties")] + GetDeviceProperties(#[source] vk::Result), + #[error("could not create a command pool")] + CreateCommandPool(#[source] vk::Result), + #[error("driver does not support all required format features")] + MissingFormatFeatures, + #[error("could not get image format properties")] + GetImageFormatProperties(#[source] vk::Result), + #[error("texture is empty")] + EmptyImage, + #[error("texture is too large")] + TexTooLarge, + #[error("driver does not support sufficiently-large buffers")] + BufferTooLarge, + #[error("Could not enumerate the physical devices")] + EnumeratePhysicalDevice(#[source] vk::Result), + #[error("Could not find a corresponding vulkan device")] + NoVulkanDevice, + #[error("Device does not support vulkan 1.3")] + NoVulkan13, + #[error("Device does not support the synchronization2 feature")] + NoSynchronization2, + #[error("Device does not support the dynamic rendering feature")] + NoDynamicRendering, + #[error("Device does not support the device extension {}", .0.as_ustr().as_bytes().as_bstr())] + MissingDeviceExtensions(&'static CStr), + #[error("Device does not support importing sync files")] + NoSyncFileImport, + #[error("Device does not support exporting sync files")] + NoSyncFileExport, + #[error("Device does not have a graphics queue family")] + NoGfxQueueFamily, + #[error("Could not create the device")] + CreateDevice(#[source] vk::Result), + #[error("Only XRGB8888 is supported as the framebuffer format")] + WrongFbFormat, + #[error("The size of FB must be > 0")] + NonPositiveFbSize, + #[error("The modifier is not supported")] + UnsupportedModifier, + #[error("The number of planes is incorrect")] + WrongPlaneCount, + #[error("The FB is too large")] + TooLarge, + #[error("Could not query memory fd properties")] + GetMemoryFdProperties(#[source] vk::Result), + #[error("Could not find a memory type for import")] + NoMemoryTypeForImport, + #[error("Could not dup a dma buf")] + DupDmaBuf(#[source] io::Error), + #[error("Could not import memory")] + ImportMemory(#[source] vk::Result), + #[error("Could not dup a sync file")] + DupSyncFile(#[source] io::Error), + #[error("Could not import a sync file")] + ImportSyncFile(#[source] vk::Result), +} + +pub struct EgvRenderer { + ri: Rc, + timeline_semaphore: Option>>, + _task: SpawnedFuture<()>, +} + +linear_ids!(EgvContextIds, EgvContextId, u64); + +pub struct EgvContext { + renderer: Rc, + id: EgvContextId, +} + +pub struct EgvFramebuffer { + renderer: Rc, + ctx: Rc, + image: Rc>, +} + +pub struct Support { + pub modifier: Modifier, + pub planes: usize, + pub max_width: u32, + pub max_height: u32, +} + +struct EgvRendererInner { + instance: VulkanCoreInstance, + sync_ctx: Option>, + eventfd_cache: Rc, + supports_timeline_opaque_export: bool, + device: Device, + queue: Queue, + queue_family: u32, + external_fence_fd: external_fence_fd::Device, + external_semaphore_fd: external_semaphore_fd::Device, + external_memory_fd: external_memory_fd::Device, + push_descriptor: push_descriptor::Device, + vert: ShaderModule, + frag: ShaderModule, + non_coherent_atom_size: u64, + descriptor_set_layout: DescriptorSetLayout, + pipeline_layout: PipelineLayout, + max_tex_width: u32, + max_tex_height: u32, + max_buffer_size: u64, + allocator: RefCell>, + pool: CommandPool, + cache: RefCell, + submissions: Rc, + pipeline: Pipeline, + dmabuf_support: Vec, + context_ids: EgvContextIds, +} + +#[derive(Default)] +struct EgvRendererCache { + device_local_buffers: Vec, + samplers: AHashMap>, + images: AHashMap<(EgvContextId, TextureId), EgvSampledImage>, + upload_todos: Vec<(Rc>, EgvBuffer, ImageDelta)>, + buffer_memory_barriers: Vec>, + initial_image_memory_barriers: Vec>, + final_image_memory_barriers: Vec>, + semaphores: Vec, +} + +struct EgvBuffer { + ri: Rc, + memory: EgvAllocatedMemory, + buffer: Buffer, + size: u64, + usage: BufferUsageFlags, + mapping: *mut [u8], + host_coherent: bool, +} + +struct EgvImportedMemory { + ri: Rc, + _bo: Rc, + memories: PlaneVec, +} + +struct EgvAllocatedMemory { + ri: Rc, + block: ManuallyDrop>, + mapping: Option<*mut [u8]>, +} + +struct EgvCommandBuffer { + ri: Rc, + buf: CommandBuffer, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct VkVertex { + pos: [f32; 2], + uv: [f32; 2], + color: [u8; 4], +} + +unsafe impl Pod for VkVertex {} +unsafe impl Packed for VkVertex {} + +struct VkSampler { + ri: Rc, + options: TextureOptions, + sampler: Sampler, +} + +#[derive(Clone)] +struct EgvSampledImage { + image: Rc>, + sampler: Rc, +} + +struct EgvImage { + ri: Rc, + width: u32, + height: u32, + image: Image, + image_view: ImageView, + _memory: M, + layout: Cell, +} + +#[derive(Default)] +struct PendingSubmissions { + task_has_pending: Cell, + pending: AsyncQueue, +} + +struct Pending { + ri: Rc, + sync: Option, + semaphore: Option, + vulkan_sync: VulkanSync, + _cmd: EgvCommandBuffer, + _uploads: Vec<(Rc>, EgvBuffer)>, + _sampled: Vec, + _fb: Rc>, + index_buffer: Option, + vertex_buffer: Option, +} + +struct EgvSemaphore { + ri: Rc, + semaphore: Semaphore, +} + +const SRGB_FORMAT: Format = Format::R8G8B8A8_SRGB; +const SRGB_FORMAT_BPP: u64 = 4; +pub const EGV_FORMAT: &crate::format::Format = XRGB8888; +const VK_FB_FORMAT: Format = Format::B8G8R8A8_SRGB; + +const DEVICE_EXTENSIONS: [&CStr; 7] = [ + external_fence_fd::NAME, + external_semaphore_fd::NAME, + external_memory_fd::NAME, + external_memory_dma_buf::NAME, + image_drm_format_modifier::NAME, + queue_family_foreign::NAME, + push_descriptor::NAME, +]; + +const VERT: &[u8] = include_bytes!("shaders_bin/shader.vert.spv"); +const FRAG: &[u8] = include_bytes!("shaders_bin/shader.frag.spv"); + +const IMAGE_SUBRESOURCE_RANGE: ImageSubresourceRange = ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, +}; + +const IMAGE_SUBRESOURCE_LAYERS: ImageSubresourceLayers = ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, +}; + +impl EgvRenderer { + pub fn new( + eng: &Rc, + ring: &Rc, + eventfd_cache: &Rc, + dev: Option, + ) -> Result, EgvError> { + let core_instance = VulkanCoreInstance::new(Level::Debug)?; + let instance = &core_instance.instance; + let mut physical_device; + let mut device_extensions; + let mut device_properties; + 'find_device: { + let devices = unsafe { + instance + .enumerate_physical_devices() + .map_err(EgvError::EnumeratePhysicalDevice)? + }; + 'outer: for phy in devices { + let res = unsafe { instance.enumerate_device_extension_properties(phy) }; + let exts = match res { + Ok(res) => map_extension_properties(res), + Err(e) => { + log::error!( + "Could not enumerate extensions of physical device: {}", + ErrorFmt(e), + ); + continue; + } + }; + let mut drm_props = PhysicalDeviceDrmPropertiesEXT::default(); + let mut props = PhysicalDeviceProperties2::default().push_next(&mut drm_props); + unsafe { + instance.get_physical_device_properties2(phy, &mut props); + } + let props = props.properties; + physical_device = phy; + device_extensions = exts; + device_properties = props; + if let Some(dev) = dev { + if device_extensions.not_contains_key(physical_device_drm::NAME) { + continue 'outer; + } + let major = uapi::major(dev) as i64; + let minor = uapi::minor(dev) as i64; + let matches = (drm_props.has_primary == vk::TRUE + && drm_props.primary_major == major + && drm_props.primary_minor == minor) + || (drm_props.has_render == vk::TRUE + && drm_props.render_major == major + && drm_props.render_minor == minor); + if matches { + break 'find_device; + } + } else { + if device_properties.device_type == PhysicalDeviceType::CPU { + break 'find_device; + } + } + } + return Err(EgvError::NoVulkanDevice); + } + if device_properties.api_version < VULKAN_API_VERSION { + return Err(EgvError::NoVulkan13); + } + for ext in DEVICE_EXTENSIONS { + if device_extensions.not_contains_key(ext) { + return Err(EgvError::MissingDeviceExtensions(ext)); + } + } + let features = { + let mut synchronization2_features = PhysicalDeviceSynchronization2Features::default(); + let mut dynamic_rendering_features = PhysicalDeviceDynamicRenderingFeatures::default(); + let mut timeline_semaphore_features = + PhysicalDeviceTimelineSemaphoreFeatures::default(); + let mut physical_device_features = PhysicalDeviceFeatures2::default() + .push_next(&mut synchronization2_features) + .push_next(&mut dynamic_rendering_features) + .push_next(&mut timeline_semaphore_features); + unsafe { + instance + .get_physical_device_features2(physical_device, &mut physical_device_features); + } + let features = physical_device_features.features; + if synchronization2_features.synchronization2 != vk::TRUE { + return Err(EgvError::NoSynchronization2); + } + if dynamic_rendering_features.dynamic_rendering != vk::TRUE { + return Err(EgvError::NoDynamicRendering); + } + VulkanDeviceFeatures { + features, + semaphore_features: timeline_semaphore_features, + } + }; + { + let info = PhysicalDeviceExternalSemaphoreInfo::default() + .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD); + let mut props = ExternalSemaphoreProperties::default(); + unsafe { + instance.get_physical_device_external_semaphore_properties( + physical_device, + &info, + &mut props, + ); + } + let supported = props + .external_semaphore_features + .contains(ExternalSemaphoreFeatureFlags::IMPORTABLE); + if !supported { + return Err(EgvError::NoSyncFileImport); + } + } + { + let info = PhysicalDeviceExternalFenceInfo::default() + .handle_type(ExternalFenceHandleTypeFlags::SYNC_FD); + let mut props = ExternalFenceProperties::default(); + unsafe { + instance.get_physical_device_external_fence_properties( + physical_device, + &info, + &mut props, + ); + } + let supported = props + .external_fence_features + .contains(ExternalFenceFeatureFlags::EXPORTABLE); + if !supported { + return Err(EgvError::NoSyncFileExport); + } + } + let queue_family = 'queue_family: { + let families = + unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; + for (idx, family) in families.iter().enumerate() { + if family.queue_count > 0 && family.queue_flags.contains(QueueFlags::GRAPHICS) { + break 'queue_family idx as u32; + } + } + return Err(EgvError::NoGfxQueueFamily); + }; + let dmabuf_support = { + let mut list = vec![]; + for attach in [false, true] { + let mut modifiers = DrmFormatModifierPropertiesListEXT::default(); + if attach { + modifiers = modifiers.drm_format_modifier_properties(&mut list); + } + let mut out = FormatProperties2::default().push_next(&mut modifiers); + unsafe { + instance.get_physical_device_format_properties2( + physical_device, + VK_FB_FORMAT, + &mut out, + ); + } + if !attach { + list = vec![ + DrmFormatModifierPropertiesEXT::default(); + modifiers.drm_format_modifier_count as usize + ]; + } + } + let mut support = vec![]; + for modifier in list { + let image_features = modifier.drm_format_modifier_tiling_features; + if !image_features.contains( + FormatFeatureFlags::COLOR_ATTACHMENT + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND, + ) { + continue; + } + let mut modifier_info = PhysicalDeviceImageDrmFormatModifierInfoEXT::default() + .drm_format_modifier(modifier.drm_format_modifier); + let mut external_memory_info = PhysicalDeviceExternalImageFormatInfoKHR::default() + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); + let info = PhysicalDeviceImageFormatInfo2::default() + .format(VK_FB_FORMAT) + .ty(ImageType::TYPE_2D) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .usage(ImageUsageFlags::COLOR_ATTACHMENT) + .push_next(&mut external_memory_info) + .push_next(&mut modifier_info); + let mut external_memory_prop = ExternalImageFormatPropertiesKHR::default(); + let mut prop = + ImageFormatProperties2::default().push_next(&mut external_memory_prop); + let res = unsafe { + instance.get_physical_device_image_format_properties2( + physical_device, + &info, + &mut prop, + ) + }; + if res.is_err() { + continue; + } + let prop = prop.image_format_properties; + let memory_features = external_memory_prop + .external_memory_properties + .external_memory_features; + if !memory_features.contains(ExternalMemoryFeatureFlags::IMPORTABLE) { + continue; + } + let me = prop.max_extent; + if me.width > 0 && me.height > 0 && me.depth > 0 { + support.push(Support { + modifier: modifier.drm_format_modifier, + planes: modifier.drm_format_modifier_plane_count as usize, + max_width: me.width, + max_height: me.height, + }); + } + } + support + }; + let supports_timeline_opaque_export = + core_instance.supports_timeline_opaque_export(physical_device, &features); + let format_properties = + unsafe { instance.get_physical_device_format_properties(physical_device, SRGB_FORMAT) }; + let required_features = FormatFeatureFlags::SAMPLED_IMAGE + | FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR + | FormatFeatureFlags::TRANSFER_DST; + if !format_properties + .optimal_tiling_features + .contains(required_features) + { + return Err(EgvError::MissingFormatFeatures); + } + let format_properties = unsafe { + instance + .get_physical_device_image_format_properties( + physical_device, + SRGB_FORMAT, + ImageType::TYPE_2D, + ImageTiling::OPTIMAL, + ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + ImageCreateFlags::empty(), + ) + .map_err(EgvError::GetImageFormatProperties)? + }; + let max_buffer_size; + { + let mut prop13 = PhysicalDeviceVulkan13Properties::default(); + let mut prop = PhysicalDeviceProperties2::default().push_next(&mut prop13); + unsafe { + instance.get_physical_device_properties2(physical_device, &mut prop); + } + max_buffer_size = prop13.max_buffer_size; + } + let device = { + let queue_create_info = DeviceQueueCreateInfo::default() + .queue_family_index(queue_family) + .queue_priorities(&[1.0]); + let extensions = DEVICE_EXTENSIONS.map(|e| e.as_ptr()); + let mut dynamic_rendering_features = + PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true); + let mut synchronization2_features = + PhysicalDeviceSynchronization2Features::default().synchronization2(true); + let mut timeline_semaphore_features = + PhysicalDeviceTimelineSemaphoreFeatures::default() + .timeline_semaphore(supports_timeline_opaque_export); + let info = DeviceCreateInfo::default() + .queue_create_infos(slice::from_ref(&queue_create_info)) + .enabled_extension_names(&extensions) + .push_next(&mut synchronization2_features) + .push_next(&mut dynamic_rendering_features) + .push_next(&mut timeline_semaphore_features); + unsafe { + instance + .create_device(physical_device, &info, None) + .map_err(EgvError::CreateDevice)? + } + }; + let destroy_device = on_drop(|| unsafe { device.destroy_device(None) }); + let external_fence_fd = external_fence_fd::Device::new(instance, &device); + let external_semaphore_fd = external_semaphore_fd::Device::new(instance, &device); + let external_memory_fd = external_memory_fd::Device::new(instance, &device); + let push_descriptor = push_descriptor::Device::new(instance, &device); + let queue = unsafe { device.get_device_queue(queue_family, 0) }; + let pool = { + let info = CommandPoolCreateInfo::default() + .queue_family_index(queue_family) + .flags(CommandPoolCreateFlags::RESET_COMMAND_BUFFER); + unsafe { + device + .create_command_pool(&info, None) + .map_err(EgvError::CreateCommandPool)? + } + }; + let destroy_pool = on_drop(|| unsafe { device.destroy_command_pool(pool, None) }); + let create_shader = |src: &[u8]| { + let mut cursor = Cursor::new(src); + let spv = read_spv(&mut cursor).map_err(EgvError::ReadSpv)?; + let create_info = ShaderModuleCreateInfo::default().code(&spv); + unsafe { + device + .create_shader_module(&create_info, None) + .map_err(EgvError::CreateShaderModule) + } + }; + let vert = create_shader(VERT)?; + let destroy_vert = on_drop(|| unsafe { device.destroy_shader_module(vert, None) }); + let frag = create_shader(FRAG)?; + let destroy_frag = on_drop(|| unsafe { device.destroy_shader_module(frag, None) }); + let descriptor_set_layout = { + let binding = DescriptorSetLayoutBinding::default() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(ShaderStageFlags::FRAGMENT); + let create_info = DescriptorSetLayoutCreateInfo::default() + .flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR) + .bindings(slice::from_ref(&binding)); + unsafe { + device + .create_descriptor_set_layout(&create_info, None) + .map_err(EgvError::CreateDescriptorSetLayout)? + } + }; + let destroy_descriptor_set_layout = on_drop(|| unsafe { + device.destroy_descriptor_set_layout(descriptor_set_layout, None) + }); + let pipeline_layout = { + let create_info = PipelineLayoutCreateInfo::default() + .set_layouts(slice::from_ref(&descriptor_set_layout)); + unsafe { + device + .create_pipeline_layout(&create_info, None) + .map_err(EgvError::CreatePipelineLayout)? + } + }; + let destroy_pipeline_layout = + on_drop(|| unsafe { device.destroy_pipeline_layout(pipeline_layout, None) }); + let mut device_properties = unsafe { + crate::vulkan_core::gpu_alloc_ash::device_properties(instance, physical_device) + .map_err(EgvError::GetDeviceProperties)? + }; + device_properties.buffer_device_address = false; + let non_coherent_atom_size = device_properties.non_coherent_atom_size; + let allocator = GpuAllocator::new(Config::i_am_potato(), device_properties); + let pipeline = { + let stages = [ + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::VERTEX) + .module(vert) + .name(c"main"), + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::FRAGMENT) + .module(frag) + .name(c"main"), + ]; + let vertex_input_binding_description = VertexInputBindingDescription { + binding: 0, + stride: size_of::() as _, + input_rate: VertexInputRate::VERTEX, + }; + let vertex_attribute_descriptions = [ + VertexInputAttributeDescription::default() + .location(0) + .format(Format::R32G32_SFLOAT) + .offset(offset_of!(Vertex, pos) as u32), + VertexInputAttributeDescription::default() + .location(1) + .format(Format::R32G32_SFLOAT) + .offset(offset_of!(Vertex, uv) as u32), + VertexInputAttributeDescription::default() + .location(2) + .format(Format::R8G8B8A8_UNORM) + .offset(offset_of!(Vertex, color) as u32), + ]; + let vertex_input_state = PipelineVertexInputStateCreateInfo::default() + .vertex_binding_descriptions(slice::from_ref(&vertex_input_binding_description)) + .vertex_attribute_descriptions(&vertex_attribute_descriptions); + let input_assembly_info = PipelineInputAssemblyStateCreateInfo::default() + .topology(PrimitiveTopology::TRIANGLE_LIST); + let viewport_state = PipelineViewportStateCreateInfo::default() + .viewport_count(1) + .scissor_count(1); + let rasterization_state = PipelineRasterizationStateCreateInfo::default() + .polygon_mode(PolygonMode::FILL) + .cull_mode(CullModeFlags::NONE) + .front_face(FrontFace::CLOCKWISE) + .line_width(1.0); + let multisampling_state = PipelineMultisampleStateCreateInfo::default() + .rasterization_samples(SampleCountFlags::TYPE_1) + .min_sample_shading(1.0); + let color_blend_attachment_state = PipelineColorBlendAttachmentState::default() + .blend_enable(true) + .src_color_blend_factor(BlendFactor::ONE) + .dst_color_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .color_blend_op(BlendOp::ADD) + .src_alpha_blend_factor(BlendFactor::ONE) + .dst_alpha_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .alpha_blend_op(BlendOp::ADD) + .color_write_mask(ColorComponentFlags::RGBA); + let color_blend_state = PipelineColorBlendStateCreateInfo::default() + .attachments(slice::from_ref(&color_blend_attachment_state)); + let dynamic_state = PipelineDynamicStateCreateInfo::default() + .dynamic_states(&[DynamicState::VIEWPORT, DynamicState::SCISSOR]); + let mut rendering_create_info = PipelineRenderingCreateInfo::default() + .color_attachment_formats(slice::from_ref(&VK_FB_FORMAT)); + let create_info = GraphicsPipelineCreateInfo::default() + .stages(&stages) + .vertex_input_state(&vertex_input_state) + .input_assembly_state(&input_assembly_info) + .viewport_state(&viewport_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisampling_state) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) + .layout(pipeline_layout) + .push_next(&mut rendering_create_info); + let mut pipelines = unsafe { + device + .create_graphics_pipelines( + PipelineCache::null(), + slice::from_ref(&create_info), + None, + ) + .map_err(|e| EgvError::CreatePipeline(e.1))? + }; + pipelines.pop().unwrap() + }; + let destroy_pipeline = on_drop(|| unsafe { device.destroy_pipeline(pipeline, None) }); + let submissions = Rc::new(PendingSubmissions::default()); + let sync_ctx = dev + .and_then(|d| { + SyncobjCtx::from_dev_t(d) + .inspect_err(|e| log::warn!("Could not create a syncobj ctx: {}", ErrorFmt(e))) + .ok() + }) + .map(Rc::new); + destroy_pipeline.forget(); + destroy_pool.forget(); + destroy_pipeline_layout.forget(); + destroy_descriptor_set_layout.forget(); + destroy_frag.forget(); + destroy_vert.forget(); + destroy_device.forget(); + let renderer = Rc::new(EgvRendererInner { + push_descriptor, + sync_ctx, + eventfd_cache: eventfd_cache.clone(), + supports_timeline_opaque_export, + device, + queue, + queue_family, + external_fence_fd, + external_semaphore_fd, + vert, + frag, + non_coherent_atom_size, + descriptor_set_layout, + pipeline_layout, + max_tex_width: format_properties.max_extent.width, + max_tex_height: format_properties.max_extent.height, + max_buffer_size, + allocator: RefCell::new(allocator), + pool, + cache: Default::default(), + submissions: submissions.clone(), + pipeline, + instance: core_instance, + external_memory_fd, + dmabuf_support, + context_ids: Default::default(), + }); + let task = { + let future = wait_for_submissions(submissions, renderer.clone(), ring.clone()); + eng.spawn("egui-vulkan-await-pending", future) + }; + let renderer = Self { + timeline_semaphore: renderer.create_timeline_semaphore_or_log(), + ri: renderer, + _task: task, + }; + Ok(Rc::new(renderer)) + } + + fn create_semaphore(&self) -> Result { + let ri = &self.ri; + let create_info = SemaphoreCreateInfo::default(); + let semaphore = unsafe { + ri.device + .create_semaphore(&create_info, None) + .map_err(EgvError::CreateSemaphore)? + }; + Ok(EgvSemaphore { + ri: ri.clone(), + semaphore, + }) + } + + fn allocate_command_buffer(&self) -> Result { + let ri = &self.ri; + let allocate_info = CommandBufferAllocateInfo::default() + .command_pool(ri.pool) + .command_buffer_count(1) + .level(CommandBufferLevel::PRIMARY); + let mut cmd = unsafe { + ri.device + .allocate_command_buffers(&allocate_info) + .map_err(EgvError::AllocateCommandBuffer)? + }; + Ok(EgvCommandBuffer { + ri: ri.clone(), + buf: cmd.pop().unwrap(), + }) + } + + fn create_image( + self: &Rc, + data: &ImageData, + ) -> Result>, EgvError> { + let extent = Extent3D { + width: data.width() as _, + height: data.height() as _, + depth: 1, + }; + if extent.width == 0 || extent.height == 0 { + return Err(EgvError::EmptyImage); + } + let ri = &self.ri; + if extent.width > ri.max_tex_width || extent.height > ri.max_tex_height { + return Err(EgvError::TexTooLarge); + } + let dev = &ri.device; + let image = { + let create_info = ImageCreateInfo::default() + .image_type(ImageType::TYPE_2D) + .format(SRGB_FORMAT) + .extent(extent) + .mip_levels(1) + .array_layers(1) + .samples(SampleCountFlags::TYPE_1) + .tiling(ImageTiling::OPTIMAL) + .usage(ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST) + .sharing_mode(SharingMode::EXCLUSIVE); + unsafe { + dev.create_image(&create_info, None) + .map_err(EgvError::CreateImage)? + } + }; + let destroy_image = on_drop(|| unsafe { dev.destroy_image(image, None) }); + let memory = { + let req = unsafe { dev.get_image_memory_requirements(image) }; + self.allocate_memory(req, UsageFlags::FAST_DEVICE_ACCESS, false)? + }; + unsafe { + dev.bind_image_memory(image, *memory.block.memory(), memory.block.offset()) + .map_err(EgvError::BindImageMemory)?; + } + let view = { + let create_info = ImageViewCreateInfo::default() + .image(image) + .view_type(ImageViewType::TYPE_2D) + .format(SRGB_FORMAT) + .components(ComponentMapping { + r: ComponentSwizzle::R, + g: ComponentSwizzle::G, + b: ComponentSwizzle::B, + a: ComponentSwizzle::A, + }) + .subresource_range(IMAGE_SUBRESOURCE_RANGE); + unsafe { + dev.create_image_view(&create_info, None) + .map_err(EgvError::CreateImageView)? + } + }; + let destroy_image_view = on_drop(|| unsafe { dev.destroy_image_view(view, None) }); + destroy_image_view.forget(); + destroy_image.forget(); + Ok(Rc::new(EgvImage { + ri: ri.clone(), + width: extent.width, + height: extent.height, + image, + _memory: memory, + image_view: view, + layout: Cell::new(ImageLayout::UNDEFINED), + })) + } + + fn get_device_local_buffer( + &self, + sync: &mut EgvRendererCache, + size: u64, + usage: BufferUsageFlags, + ) -> Result { + { + let mut best = None; + let mut best_size = u64::MAX; + for (i, buf) in sync.device_local_buffers.iter().enumerate() { + if buf.size < size { + continue; + } + if buf.usage != usage { + continue; + } + if buf.size < best_size { + best = Some(i); + best_size = buf.size; + } + } + if let Some(best) = best { + return Ok(sync.device_local_buffers.swap_remove(best)); + } + } + self.create_device_local_buffer(size, usage) + } + + fn create_device_local_buffer( + &self, + size: u64, + usage: BufferUsageFlags, + ) -> Result { + self.create_buffer( + size, + usage, + UsageFlags::FAST_DEVICE_ACCESS | UsageFlags::UPLOAD, + ) + } + + fn create_staging_buffer(&self, size: u64) -> Result { + self.create_buffer( + size, + BufferUsageFlags::TRANSFER_SRC, + UsageFlags::TRANSIENT | UsageFlags::UPLOAD, + ) + } + + fn create_buffer( + &self, + mut size: u64, + usage: BufferUsageFlags, + usage_flags: UsageFlags, + ) -> Result { + const MIN_SIZE: u64 = 1024; + size = size.max(MIN_SIZE); + let ri = &self.ri; + if size > ri.max_buffer_size { + return Err(EgvError::BufferTooLarge); + } + let dev = &ri.device; + let buffer = { + let create_info = BufferCreateInfo::default().size(size).usage(usage); + unsafe { + dev.create_buffer(&create_info, None) + .map_err(EgvError::CreateBuffer)? + } + }; + let destroy_buffer = on_drop(|| unsafe { dev.destroy_buffer(buffer, None) }); + let memory_requirements = unsafe { dev.get_buffer_memory_requirements(buffer) }; + let memory = self.allocate_memory(memory_requirements, usage_flags, true)?; + unsafe { + dev.bind_buffer_memory(buffer, *memory.block.memory(), memory.block.offset()) + .map_err(EgvError::BindBufferMemory)?; + } + destroy_buffer.forget(); + Ok(EgvBuffer { + ri: ri.clone(), + mapping: memory.mapping.unwrap(), + host_coherent: memory + .block + .props() + .contains(MemoryPropertyFlags::HOST_COHERENT), + memory, + buffer, + size, + usage, + }) + } + + fn allocate_memory( + &self, + req: MemoryRequirements, + usage: UsageFlags, + map: bool, + ) -> Result { + let request = Request { + size: req.size, + align_mask: req.alignment - 1, + usage, + memory_types: req.memory_type_bits, + }; + let ri = &self.ri; + let block = unsafe { + ri.allocator + .borrow_mut() + .alloc(AshMemoryDevice::wrap(&ri.device), request) + .map_err(EgvError::AllocateMemory)? + }; + let block = RefCell::new(ManuallyDrop::new(block)); + let deallocate = on_drop(|| unsafe { + ri.allocator.borrow_mut().dealloc( + AshMemoryDevice::wrap(&ri.device), + ManuallyDrop::take(&mut block.borrow_mut()), + ); + }); + let mut block_mut = block.borrow_mut(); + let mut mapping = None; + if map { + let size = block_mut.size() as usize; + let ptr = unsafe { + block_mut + .map(AshMemoryDevice::wrap(&ri.device), 0, size) + .map_err(EgvError::MapMemory)? + }; + let slice = unsafe { slice::from_raw_parts_mut(ptr.as_ptr(), size) }; + mapping = Some(slice as *mut [u8]); + } + drop(block_mut); + deallocate.forget(); + Ok(EgvAllocatedMemory { + ri: ri.clone(), + block: block.into_inner(), + mapping, + }) + } + + fn fill_index_buffer( + &self, + sync: &mut EgvRendererCache, + primitives: &[ClippedPrimitive], + ) -> Result { + let indices: Vec<_> = primitives + .iter() + .filter_map(|c| match &c.primitive { + Primitive::Mesh(m) => Some(&m.indices), + Primitive::Callback(_) => None, + }) + .flat_map(|i| i.iter().copied()) + .collect(); + let indices: &[u8] = uapi::as_bytes(&*indices); + let buffer = self.get_device_local_buffer( + sync, + indices.len() as u64, + BufferUsageFlags::INDEX_BUFFER, + )?; + buffer.upload(indices)?; + Ok(buffer) + } + + fn get_sampler( + self: &Rc, + samplers: &mut AHashMap>, + options: &TextureOptions, + ) -> Result, EgvError> { + let sampler = match samplers.entry(*options) { + Entry::Occupied(o) => o.get().clone(), + Entry::Vacant(v) => { + let s = self.create_sampler(options)?; + v.insert(s).clone() + } + }; + Ok(sampler) + } + + fn create_sampler( + self: &Rc, + options: &TextureOptions, + ) -> Result, EgvError> { + let address_mode = match options.wrap_mode { + TextureWrapMode::ClampToEdge => SamplerAddressMode::CLAMP_TO_EDGE, + TextureWrapMode::Repeat => SamplerAddressMode::REPEAT, + TextureWrapMode::MirroredRepeat => SamplerAddressMode::MIRRORED_REPEAT, + }; + let map_filter = |f: TextureFilter| match f { + TextureFilter::Nearest => Filter::NEAREST, + TextureFilter::Linear => Filter::LINEAR, + }; + let create_info = SamplerCreateInfo::default() + .mag_filter(map_filter(options.magnification)) + .min_filter(map_filter(options.minification)) + .address_mode_u(address_mode) + .address_mode_v(address_mode) + .address_mode_w(address_mode) + .mipmap_mode(SamplerMipmapMode::NEAREST) + .max_anisotropy(1.0) + .min_lod(0.0) + .max_lod(0.25) + .border_color(BorderColor::FLOAT_TRANSPARENT_BLACK); + let ri = &self.ri; + let sampler = unsafe { + ri.device + .create_sampler(&create_info, None) + .map_err(EgvError::CreateSampler)? + }; + Ok(Rc::new(VkSampler { + ri: ri.clone(), + options: *options, + sampler, + })) + } + + pub fn support(&self) -> &[Support] { + &self.ri.dmabuf_support + } + + pub fn max_texture_side(&self) -> usize { + self.ri.max_tex_width.min(self.ri.max_tex_height) as usize + } + + pub fn create_context(self: &Rc) -> Rc { + Rc::new(EgvContext { + renderer: self.clone(), + id: self.ri.context_ids.next(), + }) + } +} + +async fn wait_for_submissions( + submissions: Rc, + dev: Rc, + ring: Rc, +) { + loop { + submissions.task_has_pending.set(false); + let pending = submissions.pending.pop().await; + submissions.task_has_pending.set(true); + if let Some(sync) = &pending.sync + && let Err(e) = sync.try_signaled(&ring).await + { + log::warn!( + "Could not wait for sync file to become readable: {}", + ErrorFmt(e), + ); + dev.wait_idle(); + } + pending.vulkan_sync.handle_validation(); + } +} + +impl EgvRendererInner { + fn wait_idle(&self) { + log::warn!("Blocking"); + let res = unsafe { self.device.device_wait_idle() }; + if let Err(e) = res { + log::error!("Could not wait for device idle: {}", ErrorFmt(e)); + log::error!("This is unsound."); + } + self.submissions.pending.clear(); + } +} + +impl EgvContext { + pub fn import_framebuffer( + self: &Rc, + bo: &Rc, + ) -> Result, EgvError> { + let ri = &self.renderer.ri; + let buf = bo.dmabuf(); + if buf.format != EGV_FORMAT { + return Err(EgvError::WrongFbFormat); + } + if buf.width <= 0 || buf.height <= 0 { + return Err(EgvError::NonPositiveFbSize); + } + let Some(support) = ri + .dmabuf_support + .iter() + .find(|s| s.modifier == buf.modifier) + else { + return Err(EgvError::UnsupportedModifier); + }; + if buf.planes.len() != support.planes { + return Err(EgvError::WrongPlaneCount); + } + let width = buf.width as u32; + let height = buf.height as u32; + if width > support.max_width || height > support.max_height { + return Err(EgvError::TooLarge); + } + let dev = &ri.device; + let disjoint = buf.is_disjoint(); + let image = { + let image_create_flags = match disjoint { + true => ImageCreateFlags::DISJOINT, + false => ImageCreateFlags::empty(), + }; + let plane_layouts: PlaneVec<_> = buf + .planes + .iter() + .map(|p| SubresourceLayout { + offset: p.offset as _, + row_pitch: p.stride as _, + size: 0, + array_pitch: 0, + depth_pitch: 0, + }) + .collect(); + let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::default() + .drm_format_modifier(buf.modifier) + .plane_layouts(&plane_layouts); + let mut memory_image_create_info = ExternalMemoryImageCreateInfo::default() + .handle_types(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); + let info = ImageCreateInfo::default() + .flags(image_create_flags) + .image_type(ImageType::TYPE_2D) + .format(VK_FB_FORMAT) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(SampleCountFlags::TYPE_1) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .usage(ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .push_next(&mut mod_info) + .push_next(&mut memory_image_create_info); + unsafe { + dev.create_image(&info, None) + .map_err(EgvError::CreateImage)? + } + }; + let destroy_image = on_drop(|| unsafe { dev.destroy_image(image, None) }); + let mut memories = PlaneVec::new(); + let mut free_memories = PlaneVec::new(); + { + let num_device_memories = match disjoint { + true => buf.planes.len(), + false => 1, + }; + let mut bind_image_plane_memory_infos = PlaneVec::new(); + for plane_idx in 0..num_device_memories { + let dma_buf_plane = &buf.planes[plane_idx]; + let mut image_memory_requirements_info = + ImageMemoryRequirementsInfo2::default().image(image); + let mut image_plane_memory_requirements_info; + if disjoint { + let plane_aspect = match plane_idx { + 0 => ImageAspectFlags::MEMORY_PLANE_0_EXT, + 1 => ImageAspectFlags::MEMORY_PLANE_1_EXT, + 2 => ImageAspectFlags::MEMORY_PLANE_2_EXT, + 3 => ImageAspectFlags::MEMORY_PLANE_3_EXT, + _ => unreachable!(), + }; + image_plane_memory_requirements_info = + ImagePlaneMemoryRequirementsInfo::default().plane_aspect(plane_aspect); + image_memory_requirements_info = image_memory_requirements_info + .push_next(&mut image_plane_memory_requirements_info); + bind_image_plane_memory_infos + .push(BindImagePlaneMemoryInfo::default().plane_aspect(plane_aspect)); + } + let mut memory_requirements = MemoryRequirements2::default(); + unsafe { + dev.get_image_memory_requirements2( + &image_memory_requirements_info, + &mut memory_requirements, + ); + } + let mut fd_props = MemoryFdPropertiesKHR::default(); + unsafe { + ri.external_memory_fd + .get_memory_fd_properties( + ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, + dma_buf_plane.fd.raw(), + &mut fd_props, + ) + .map_err(EgvError::GetMemoryFdProperties)?; + } + let memory_type_bits = memory_requirements.memory_requirements.memory_type_bits + & fd_props.memory_type_bits; + if memory_type_bits == 0 { + return Err(EgvError::NoMemoryTypeForImport); + } + let fd = uapi::fcntl_dupfd_cloexec(dma_buf_plane.fd.raw(), 0) + .map_err(Into::into) + .map_err(EgvError::DupDmaBuf)?; + let mut memory_dedicated_allocate_info = + MemoryDedicatedAllocateInfo::default().image(image); + let mut import_memory_fd_info = ImportMemoryFdInfoKHR::default() + .fd(fd.raw()) + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); + let memory_allocate_info = MemoryAllocateInfo::default() + .allocation_size(memory_requirements.memory_requirements.size) + .memory_type_index(memory_type_bits.trailing_zeros() as _) + .push_next(&mut import_memory_fd_info) + .push_next(&mut memory_dedicated_allocate_info); + let device_memory = unsafe { + dev.allocate_memory(&memory_allocate_info, None) + .map_err(EgvError::ImportMemory)? + }; + let _ = fd.unwrap(); + memories.push(device_memory); + free_memories.push(on_drop(move || unsafe { + dev.free_memory(device_memory, None) + })); + } + let mut bind_image_memory_infos = PlaneVec::new(); + let mut bind_image_plane_memory_infos = bind_image_plane_memory_infos.iter_mut(); + for mem in memories.iter().copied() { + let mut info = BindImageMemoryInfo::default().image(image).memory(mem); + if disjoint { + info = info.push_next(bind_image_plane_memory_infos.next().unwrap()); + } + bind_image_memory_infos.push(info); + } + unsafe { + dev.bind_image_memory2(&bind_image_memory_infos) + .map_err(EgvError::BindImageMemory)?; + } + } + let image_view = { + let info = ImageViewCreateInfo::default() + .image(image) + .view_type(ImageViewType::TYPE_2D) + .format(VK_FB_FORMAT) + .components(ComponentMapping { + r: ComponentSwizzle::IDENTITY, + g: ComponentSwizzle::IDENTITY, + b: ComponentSwizzle::IDENTITY, + a: ComponentSwizzle::IDENTITY, + }) + .subresource_range(IMAGE_SUBRESOURCE_RANGE); + unsafe { + dev.create_image_view(&info, None) + .map_err(EgvError::CreateImageView)? + } + }; + let destroy_image_view = on_drop(|| unsafe { dev.destroy_image_view(image_view, None) }); + destroy_image_view.forget(); + free_memories.into_iter().for_each(|f| f.forget()); + destroy_image.forget(); + let image = Rc::new(EgvImage { + ri: ri.clone(), + width, + height, + image, + image_view, + _memory: EgvImportedMemory { + ri: ri.clone(), + _bo: bo.clone(), + memories, + }, + layout: Cell::new(ImageLayout::UNDEFINED), + }); + let fb = Rc::new(EgvFramebuffer { + renderer: self.renderer.clone(), + ctx: self.clone(), + image, + }); + Ok(fb) + } +} + +impl EgvFramebuffer { + fn create_vertex_buffer( + &self, + sync: &mut EgvRendererCache, + pixels_per_point: f32, + primitives: &[ClippedPrimitive], + offset: (f32, f32), + ) -> Result { + let width = self.image.width as f32 / pixels_per_point; + let height = self.image.height as f32 / pixels_per_point; + let vertices: Vec<_> = primitives + .iter() + .filter_map(|c| match &c.primitive { + Primitive::Mesh(m) => Some(&m.vertices), + Primitive::Callback(_) => None, + }) + .flat_map(|i| i.iter().copied()) + .map(|mut v| { + v.pos.x = 2.0 * (v.pos.x + offset.0) / width - 1.0; + v.pos.y = 2.0 * (v.pos.y + offset.1) / height - 1.0; + VkVertex { + pos: [v.pos.x, v.pos.y], + uv: [v.uv.x, v.uv.y], + color: [v.color.r(), v.color.g(), v.color.b(), v.color.a()], + } + }) + .collect(); + let vertices: &[u8] = uapi::as_bytes(&*vertices); + let buffer = self.renderer.get_device_local_buffer( + sync, + vertices.len() as u64, + BufferUsageFlags::VERTEX_BUFFER, + )?; + buffer.upload(vertices)?; + Ok(buffer) + } + + pub fn render( + &self, + delta: TexturesDelta, + pixels_per_point: f32, + primitives: &[ClippedPrimitive], + offset: (f32, f32), + sync_file: Option<&SyncFile>, + ) -> Result, EgvError> { + let renderer = &self.renderer; + let ri = &renderer.ri; + let dev = &ri.device; + let cache = &mut *ri.cache.borrow_mut(); + let index_buffer = self.renderer.fill_index_buffer(cache, primitives)?; + let vertex_buffer = + self.create_vertex_buffer(cache, pixels_per_point, primitives, offset)?; + let uploads = &mut cache.upload_todos; + uploads.clear(); + for (id, delta) in delta.set { + let id = (self.ctx.id, id); + let mut options = delta.options; + options.mipmap_mode = None; + let mut create_sampled_image = || -> Result<_, EgvError> { + let sampler = renderer.get_sampler(&mut cache.samplers, &options)?; + let image = renderer.create_image(&delta.image)?; + let sampled = EgvSampledImage { + image: image.clone(), + sampler, + }; + Ok((image, sampled)) + }; + let image = match cache.images.entry(id) { + Entry::Occupied(mut o) => { + let t = o.get(); + if delta.pos.is_none() + && [t.image.width as usize, t.image.height as usize] != delta.image.size() + { + let (image, sampled) = create_sampled_image()?; + *o.get_mut() = sampled; + image + } else if t.sampler.options != options { + let sampler = self.renderer.get_sampler(&mut cache.samplers, &options)?; + let image = t.image.clone(); + *o.get_mut() = EgvSampledImage { + image: image.clone(), + sampler, + }; + image + } else { + t.image.clone() + } + } + Entry::Vacant(v) => { + if delta.pos.is_some() { + return Err(EgvError::PartialTextureUpdateForUnknownTexture(id.1)); + } + let (image, sampled) = create_sampled_image()?; + v.insert(sampled); + image + } + }; + if let Some(pos) = delta.pos { + let x2 = pos[0].saturating_add(delta.image.width()); + let y2 = pos[1].saturating_add(delta.image.height()); + if x2 > image.width as usize || y2 > image.height as usize { + return Err(EgvError::TextureUpdateOutOfBounds(id.1)); + } + } + let size = delta.image.width() as u64 * delta.image.height() as u64 * SRGB_FORMAT_BPP; + uploads.push((image.clone(), renderer.create_staging_buffer(size)?, delta)); + } + let buffer_memory_barriers = &mut cache.buffer_memory_barriers; + buffer_memory_barriers.clear(); + let initial_image_barriers = &mut cache.initial_image_memory_barriers; + initial_image_barriers.clear(); + let final_image_barriers = &mut cache.final_image_memory_barriers; + final_image_barriers.clear(); + for (image, buf, delta) in &*uploads { + match &delta.image { + ImageData::Color(c) => { + let pixels = unsafe { AssertPacked::new(&c.pixels as &[_]) }; + buf.upload(uapi::as_bytes(pixels))?; + } + } + buffer_memory_barriers.push( + BufferMemoryBarrier2::default() + .src_access_mask(AccessFlags2::HOST_WRITE) + .src_stage_mask(PipelineStageFlags2::HOST) + .dst_access_mask(AccessFlags2::TRANSFER_READ) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .buffer(buf.buffer) + .size(WHOLE_SIZE), + ); + initial_image_barriers.push( + ImageMemoryBarrier2::default() + .src_access_mask(AccessFlags2::SHADER_READ) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .dst_access_mask(AccessFlags2::TRANSFER_WRITE) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .old_layout(image.layout.get()) + .new_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .image(image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ); + final_image_barriers.push( + ImageMemoryBarrier2::default() + .src_access_mask(AccessFlags2::TRANSFER_WRITE) + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask(AccessFlags2::SHADER_READ) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .old_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .image(image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ); + } + let cmd = renderer.allocate_command_buffer()?; + let buf = cmd.buf; + { + let begin_info = + CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + dev.begin_command_buffer(buf, &begin_info) + .map_err(EgvError::BeginCommandBuffer)?; + } + } + unsafe { + let info = DependencyInfo::default() + .buffer_memory_barriers(&buffer_memory_barriers) + .image_memory_barriers(&initial_image_barriers); + dev.cmd_pipeline_barrier2(buf, &info); + } + for (image, staging, delta) in &*uploads { + let x = delta.pos.unwrap_or_default()[0] as i32; + let y = delta.pos.unwrap_or_default()[1] as i32; + let region = BufferImageCopy2::default() + .image_subresource(IMAGE_SUBRESOURCE_LAYERS) + .image_offset(Offset3D { x, y, z: 0 }) + .image_extent(Extent3D { + width: delta.image.width() as u32, + height: delta.image.height() as u32, + depth: 1, + }); + let info = CopyBufferToImageInfo2::default() + .src_buffer(staging.buffer) + .dst_image(image.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .regions(slice::from_ref(®ion)); + unsafe { + dev.cmd_copy_buffer_to_image2(buf, &info); + } + } + { + final_image_barriers.push( + ImageMemoryBarrier2::default() + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(ri.queue_family) + .image(self.image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ); + final_image_barriers.push( + ImageMemoryBarrier2::default() + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .src_queue_family_index(ri.queue_family) + .dst_queue_family_index(ri.queue_family) + .image(self.image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ) + } + unsafe { + let info = DependencyInfo::default().image_memory_barriers(&final_image_barriers); + dev.cmd_pipeline_barrier2(buf, &info); + } + for primitive in primitives { + match &primitive.primitive { + Primitive::Mesh(m) => { + if cache.images.not_contains_key(&(self.ctx.id, m.texture_id)) { + return Err(EgvError::UnknownTexture(m.texture_id)); + } + } + Primitive::Callback(_) => { + unreachable!() + } + } + } + { + let rendering_attachment_info = RenderingAttachmentInfo::default() + .image_view(self.image.image_view) + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(AttachmentLoadOp::DONT_CARE) + .store_op(AttachmentStoreOp::STORE); + let rendering_info = RenderingInfoKHR::default() + .render_area(Rect2D { + offset: Default::default(), + extent: Extent2D { + width: self.image.width, + height: self.image.height, + }, + }) + .layer_count(1) + .color_attachments(slice::from_ref(&rendering_attachment_info)); + unsafe { + dev.cmd_begin_rendering(buf, &rendering_info); + } + } + if primitives.is_not_empty() { + unsafe { + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, ri.pipeline); + dev.cmd_bind_index_buffer(buf, index_buffer.buffer, 0, IndexType::UINT32); + dev.cmd_bind_vertex_buffers(buf, 0, &[vertex_buffer.buffer], &[0]); + dev.cmd_set_viewport( + buf, + 0, + &[Viewport { + x: 0.0, + y: 0.0, + width: self.image.width as f32, + height: self.image.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }], + ); + } + } + let mut first_index = 0; + let mut vertex_offset = 0; + let mut sampled_images = Vec::with_capacity(primitives.len()); + for primitive in primitives { + let mesh = match &primitive.primitive { + Primitive::Mesh(m) => m, + Primitive::Callback(_) => unreachable!(), + }; + let sampled = cache.images.get(&(self.ctx.id, mesh.texture_id)).unwrap(); + sampled_images.push(sampled.clone()); + let image_info = DescriptorImageInfo::default() + .sampler(sampled.sampler.sampler) + .image_view(sampled.image.image_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let write_descriptor_set = WriteDescriptorSet::default() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&image_info)); + unsafe { + ri.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + ri.pipeline_layout, + 0, + slice::from_ref(&write_descriptor_set), + ); + } + { + let c = primitive.clip_rect; + let x1 = ((c.min.x + offset.0) * pixels_per_point).floor().max(0.0) as i32; + let y1 = ((c.min.y + offset.1) * pixels_per_point).floor().max(0.0) as i32; + let x2 = ((c.max.x + offset.0) * pixels_per_point).ceil().max(0.0) as i32; + let y2 = ((c.max.y + offset.1) * pixels_per_point).ceil().max(0.0) as i32; + unsafe { + dev.cmd_set_scissor( + buf, + 0, + &[Rect2D { + offset: Offset2D { x: x1, y: y1 }, + extent: Extent2D { + width: x2.wrapping_sub(x1) as u32, + height: y2.wrapping_sub(y1) as u32, + }, + }], + ); + } + } + let index_count = mesh.indices.len() as u32; + unsafe { + dev.cmd_draw_indexed(buf, index_count, 1, first_index, vertex_offset, 0); + } + first_index += index_count; + vertex_offset += mesh.vertices.len() as i32; + } + unsafe { + dev.cmd_end_rendering(buf); + } + { + final_image_barriers.clear(); + final_image_barriers.push( + ImageMemoryBarrier2::default() + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(ri.queue_family) + .dst_queue_family_index(ri.queue_family) + .image(self.image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ); + final_image_barriers.push( + ImageMemoryBarrier2::default() + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(ri.queue_family) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image(self.image.image) + .subresource_range(IMAGE_SUBRESOURCE_RANGE), + ); + unsafe { + let info = DependencyInfo::default().image_memory_barriers(&final_image_barriers); + dev.cmd_pipeline_barrier2(buf, &info); + } + } + unsafe { + dev.end_command_buffer(buf) + .map_err(EgvError::EndCommandBuffer)?; + } + let mut semaphore = None; + let mut vk_semaphores = ArrayVec::<_, 1>::new(); + if let Some(sync_file) = sync_file { + let s = match cache.semaphores.pop() { + Some(f) => f, + None => renderer.create_semaphore()?, + }; + s.import(sync_file)?; + let info = SemaphoreSubmitInfo::default() + .semaphore(s.semaphore) + .stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT); + vk_semaphores.push(info); + semaphore = Some(s); + } + let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(buf); + let mut submit_info = SubmitInfo2::default() + .command_buffer_infos(slice::from_ref(&command_buffer_info)) + .wait_semaphore_infos(&vk_semaphores); + let mut semaphore_submit_info = SemaphoreSubmitInfo::default(); + let vulkan_sync = ri.create_sync( + renderer.timeline_semaphore.as_ref(), + &mut semaphore_submit_info, + &mut submit_info, + )?; + unsafe { + dev.queue_submit2(ri.queue, slice::from_ref(&submit_info), vulkan_sync.fence()) + .map_err(EgvError::Submit)?; + } + for id in delta.free { + cache.images.remove(&(self.ctx.id, id)); + } + let mut used_uploads = Vec::with_capacity(uploads.len()); + for (image, staging, _) in uploads.drain(..) { + image.layout.set(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + used_uploads.push((image, staging)); + } + let sync = vulkan_sync.to_sync(|| ri.wait_idle()); + let pending = Pending { + ri: ri.clone(), + sync: sync.clone(), + semaphore, + vulkan_sync, + _cmd: cmd, + _uploads: used_uploads, + _sampled: sampled_images, + _fb: self.image.clone(), + index_buffer: Some(index_buffer), + vertex_buffer: Some(vertex_buffer), + }; + ri.submissions.pending.push(pending); + Ok(sync) + } +} + +impl EgvBuffer { + fn upload(&self, data: &[u8]) -> Result<(), EgvError> { + assert!(self.mapping.len() >= data.len()); + unsafe { + ptr::copy_nonoverlapping(data.as_ptr(), self.mapping.cast(), data.len()); + } + if !self.host_coherent { + let m = &self.memory; + let mask = m.ri.non_coherent_atom_size - 1; + let lo = m.block.offset() & !mask; + let hi = (m.block.offset() + data.len() as u64 + mask) & !mask; + let range = MappedMemoryRange::default() + .memory(*m.block.memory()) + .offset(lo) + .size(hi - lo); + unsafe { + m.ri.device + .flush_mapped_memory_ranges(slice::from_ref(&range)) + .map_err(EgvError::FlushMemory)?; + } + } + Ok(()) + } +} + +impl EgvSemaphore { + fn import(&self, sync_file: &SyncFile) -> Result<(), EgvError> { + let fd = uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) + .map_err(Into::into) + .map_err(EgvError::DupSyncFile)?; + let info = ImportSemaphoreFdInfoKHR::default() + .flags(SemaphoreImportFlags::TEMPORARY) + .semaphore(self.semaphore) + .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD) + .fd(fd.raw()); + unsafe { + self.ri + .external_semaphore_fd + .import_semaphore_fd(&info) + .map_err(EgvError::ImportSyncFile)?; + } + let _ = fd.unwrap(); + Ok(()) + } +} + +impl Drop for EgvBuffer { + fn drop(&mut self) { + unsafe { + self.ri.device.destroy_buffer(self.buffer, None); + } + } +} + +impl Drop for EgvRendererInner { + fn drop(&mut self) { + let dev = &self.device; + unsafe { + self.allocator + .borrow_mut() + .cleanup(AshMemoryDevice::wrap(dev)); + dev.destroy_pipeline(self.pipeline, None); + dev.destroy_command_pool(self.pool, None); + dev.destroy_pipeline_layout(self.pipeline_layout, None); + dev.destroy_descriptor_set_layout(self.descriptor_set_layout, None); + dev.destroy_shader_module(self.vert, None); + dev.destroy_shader_module(self.frag, None); + dev.destroy_device(None); + } + } +} + +impl Drop for EgvImportedMemory { + fn drop(&mut self) { + unsafe { + for &memory in &self.memories { + self.ri.device.free_memory(memory, None); + } + } + } +} + +impl Drop for EgvAllocatedMemory { + fn drop(&mut self) { + if self.mapping.is_some() { + unsafe { + self.block.unmap(AshMemoryDevice::wrap(&self.ri.device)); + } + } + unsafe { + self.ri.allocator.borrow_mut().dealloc( + AshMemoryDevice::wrap(&self.ri.device), + ManuallyDrop::take(&mut self.block), + ); + } + } +} + +impl Drop for EgvCommandBuffer { + fn drop(&mut self) { + let ri = &self.ri; + unsafe { + ri.device + .free_command_buffers(ri.pool, slice::from_ref(&self.buf)); + } + } +} + +impl Drop for VkSampler { + fn drop(&mut self) { + unsafe { + self.ri.device.destroy_sampler(self.sampler, None); + } + } +} + +impl Drop for EgvImage { + fn drop(&mut self) { + let dev = &self.ri.device; + unsafe { + dev.destroy_image_view(self.image_view, None); + dev.destroy_image(self.image, None); + } + } +} + +impl Drop for Pending { + fn drop(&mut self) { + let cache = &mut *self.ri.cache.borrow_mut(); + if let Some(v) = self.semaphore.take() { + cache.semaphores.push(v); + } + if let Some(v) = self.index_buffer.take() { + cache.device_local_buffers.push(v); + } + if let Some(v) = self.vertex_buffer.take() { + cache.device_local_buffers.push(v); + } + } +} + +impl Drop for EgvSemaphore { + fn drop(&mut self) { + let dev = &self.ri.device; + unsafe { + dev.destroy_semaphore(self.semaphore, None); + } + } +} + +impl Drop for EgvRenderer { + fn drop(&mut self) { + let ri = &self.ri; + if ri.submissions.pending.is_not_empty() || ri.submissions.task_has_pending.get() { + ri.wait_idle(); + } + ri.cache.take(); + } +} + +impl Drop for EgvContext { + fn drop(&mut self) { + self.renderer + .ri + .cache + .borrow_mut() + .images + .retain(|&(id, _), _| id != self.id); + } +} + +impl VulkanDeviceInf for EgvRendererInner { + fn instance(&self) -> &VulkanCoreInstance { + &self.instance + } + + fn device(&self) -> &Device { + &self.device + } + + fn external_fence_fd(&self) -> &external_fence_fd::Device { + &self.external_fence_fd + } + + fn external_semaphore_fd(&self) -> &external_semaphore_fd::Device { + &self.external_semaphore_fd + } + + fn supports_timeline_opaque_export(&self) -> bool { + self.supports_timeline_opaque_export + } + + fn sync_ctx(&self) -> Option<&Rc> { + self.sync_ctx.as_ref() + } + + fn eventfd_cache(&self) -> &Rc { + &self.eventfd_cache + } +} diff --git a/src/egui_adapter/icons.ttf b/src/egui_adapter/icons.ttf new file mode 100644 index 00000000..0abd161b Binary files /dev/null and b/src/egui_adapter/icons.ttf differ diff --git a/src/egui_adapter/shaders/shader.frag b/src/egui_adapter/shaders/shader.frag new file mode 100644 index 00000000..56defac2 --- /dev/null +++ b/src/egui_adapter/shaders/shader.frag @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) in vec4 color; +layout(location = 1) in vec2 pos; + +layout(binding = 0, set = 0) uniform sampler2D tex; + +layout(location = 0) out vec4 res; + +void main() { + vec4 src = texture(tex, pos); + res = color * src; +} diff --git a/src/egui_adapter/shaders/shader.vert b/src/egui_adapter/shaders/shader.vert new file mode 100644 index 00000000..73584e1a --- /dev/null +++ b/src/egui_adapter/shaders/shader.vert @@ -0,0 +1,19 @@ +#version 450 + +layout(location = 0) in vec2 if_pos; +layout(location = 1) in vec2 it_pos; +layout(location = 2) in vec4 i_color; + +layout(location = 0) out vec4 o_color; +layout(location = 1) out vec2 ot_pos; + +void main() { + o_color = i_color; + o_color.rgb = mix( + o_color.rgb / vec3(12.92), + pow((o_color.rgb + vec3(0.055)) / vec3(1.055), vec3(2.4)), + greaterThan(o_color.rgb, vec3(0.04045)) + ); + ot_pos = it_pos; + gl_Position = vec4(if_pos.x, if_pos.y, 0.0, 1.0); +} diff --git a/src/egui_adapter/shaders_bin/shader.frag.spv b/src/egui_adapter/shaders_bin/shader.frag.spv new file mode 100644 index 00000000..28f8c6f5 Binary files /dev/null and b/src/egui_adapter/shaders_bin/shader.frag.spv differ diff --git a/src/egui_adapter/shaders_bin/shader.vert.spv b/src/egui_adapter/shaders_bin/shader.vert.spv new file mode 100644 index 00000000..20e61987 Binary files /dev/null and b/src/egui_adapter/shaders_bin/shader.vert.spv differ diff --git a/src/egui_adapter/shaders_hash.txt b/src/egui_adapter/shaders_hash.txt new file mode 100644 index 00000000..fab5b02f --- /dev/null +++ b/src/egui_adapter/shaders_hash.txt @@ -0,0 +1,2 @@ +7eb8fae39ae513bc4f6973c12227aa4aa43734bdf34c90e1b3b69294ad98db87 src/egui_adapter/shaders/shader.frag +501f4d0c5c5f10a371659b89f12d87abb03e5b57a31dbae5f3c6ca5726e4db01 src/egui_adapter/shaders/shader.vert diff --git a/src/fontconfig.rs b/src/fontconfig.rs index 413557a0..76275bee 100644 --- a/src/fontconfig.rs +++ b/src/fontconfig.rs @@ -34,14 +34,12 @@ pub enum FontconfigError { } #[derive(Debug)] -#[expect(dead_code)] pub struct Font { pub fullname: String, pub file: PathBuf, pub index: Option, } -#[expect(dead_code)] pub fn match_font(family: &str) -> Result { thread_local! { static CONFIG: *mut FcConfig = FcConfigGetCurrent(); diff --git a/src/globals.rs b/src/globals.rs index 07c18464..ad4b64be 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -256,7 +256,7 @@ pub struct Globals { removed: CopyHashMap>, pub outputs: CopyHashMap>, pub seats: CopyHashMap>, - singletons: StaticMap, + pub singletons: StaticMap, exposed: StaticMap>, } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 7816843d..1fa99506 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -138,6 +138,9 @@ const MISSING_CAPABILITY: u32 = 0; pub const BTN_LEFT: u32 = 0x110; pub const BTN_RIGHT: u32 = 0x111; +pub const BTN_MIDDLE: u32 = 0x112; +pub const BTN_SIDE: u32 = 0x113; +pub const BTN_EXTRA: u32 = 0x114; pub const SEAT_NAME_SINCE: Version = Version(2); diff --git a/src/main.rs b/src/main.rs index ca0a0d56..73440c61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,6 +67,7 @@ mod damage; mod dbus; mod drm_feedback; mod edid; +mod egui_adapter; mod ei; mod eventfd_cache; mod fixed; diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs index 0a5f9bb0..bf164643 100644 --- a/src/security_context_acceptor.rs +++ b/src/security_context_acceptor.rs @@ -42,6 +42,15 @@ pub struct AcceptorMetadata { pub tag: Option, } +impl AcceptorMetadata { + pub fn secure() -> Self { + Self { + secure: true, + ..Default::default() + } + } +} + impl SecurityContextAcceptors { pub fn clear(&self) { for acceptor in self.acceptors.lock().drain_values() { diff --git a/src/state.rs b/src/state.rs index 5788a95f..e1b22f7b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,6 +24,7 @@ use { damage::DamageVisualizer, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, + egui_adapter::egui_platform::EggState, ei::{ ei_acceptor::EiAcceptor, ei_client::{EiClient, EiClients}, @@ -226,7 +227,7 @@ pub struct State { pub activation_tokens: CopyHashMap, pub toplevel_lists: CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc>, - pub dma_buf_ids: DmaBufIds, + pub dma_buf_ids: Rc, pub drm_feedback_ids: DrmFeedbackIds, pub direct_scanout_enabled: Cell, pub persistent_output_states: CopyHashMap, Rc>, @@ -295,6 +296,7 @@ pub struct State { pub eventfd_cache: Rc, pub lazy_event_sources: Rc, pub bo_drop_queue: Rc>>, + pub egg_state: EggState, } // impl Drop for State { @@ -645,6 +647,7 @@ impl State { } pub fn set_render_ctx(&self, ctx: Option>) { + self.egg_state.clear(); self.explicit_sync_supported.set(false); self.render_ctx.set(ctx.clone()); self.render_ctx_version.fetch_add(1); @@ -1158,6 +1161,7 @@ impl State { self.xdg_surface_configure_events.clear(); self.lazy_event_sources.clear(); self.bo_drop_queue.kill(); + self.egg_state.clear(); } pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) { @@ -1876,6 +1880,7 @@ impl State { theme.font.set(self.theme.default_font.clone()); theme.bar_font.set(None); theme.title_font.set(None); + self.egg_state.reset_fonts(); self.fonts_changed(); } @@ -1896,6 +1901,16 @@ impl State { self.fonts_changed(); } + pub fn set_egui_fonts(&self, proportional: Option>, monospace: Option>) { + if let Some(fonts) = &proportional { + self.egg_state.set_proportional_fonts(fonts); + } + if let Some(fonts) = &monospace { + self.egg_state.set_monospace_fonts(fonts); + } + self.fonts_changed(); + } + pub fn set_bar_position(&self, p: BarPosition) { self.theme.bar_position.set(p); self.spaces_changed(); diff --git a/src/utils/object_drop_queue.rs b/src/utils/object_drop_queue.rs index 2ce1ea45..f14eabbb 100644 --- a/src/utils/object_drop_queue.rs +++ b/src/utils/object_drop_queue.rs @@ -32,7 +32,6 @@ impl ObjectDropQueue { } } - #[expect(dead_code)] pub fn push(self: &Rc, fd: &Rc, t: T) where T: 'static, diff --git a/src/utils/pipe.rs b/src/utils/pipe.rs index ed720856..2d645bc9 100644 --- a/src/utils/pipe.rs +++ b/src/utils/pipe.rs @@ -14,7 +14,6 @@ pub fn pipe() -> Result, OsError> { } impl Pipe { - #[expect(dead_code)] pub fn map_read(self, map: impl FnOnce(L) -> Lprime) -> Pipe { Pipe { read: map(self.read), @@ -22,7 +21,6 @@ impl Pipe { } } - #[expect(dead_code)] pub fn map_write(self, map: impl FnOnce(R) -> Rprime) -> Pipe { Pipe { read: self.read, diff --git a/src/video/dmabuf.rs b/src/video/dmabuf.rs index e19def66..9ff8b1b8 100644 --- a/src/video/dmabuf.rs +++ b/src/video/dmabuf.rs @@ -1,8 +1,12 @@ use { crate::{ format::Format, + gfx_api::SyncFile, utils::{compat::IoctlNumber, oserror::OsError}, - video::{LINEAR_MODIFIER, Modifier}, + video::{ + LINEAR_MODIFIER, Modifier, + drm::{DrmError, syncobj::merge_sync_files}, + }, }, arrayvec::ArrayVec, std::{cell::OnceCell, rc::Rc, sync::OnceLock}, @@ -113,6 +117,22 @@ impl DmaBuf { } Ok(()) } + + pub fn export_sync_file(&self, flags: u32) -> Result, DrmError> { + let mut sf = PlaneVec::new(); + for plane in &self.planes { + sf.push( + dma_buf_export_sync_file(&plane.fd, flags) + .map(Rc::new) + .map(SyncFile) + .map_err(DrmError::ExportSyncFile)?, + ); + if self.is_one_file() { + break; + } + } + merge_sync_files(sf.iter()) + } } const DMA_BUF_BASE: u64 = b'b' as _; diff --git a/src/wl_usr/usr_ifs/usr_jay_compositor.rs b/src/wl_usr/usr_ifs/usr_jay_compositor.rs index 3d0a7287..63102b64 100644 --- a/src/wl_usr/usr_ifs/usr_jay_compositor.rs +++ b/src/wl_usr/usr_ifs/usr_jay_compositor.rs @@ -189,7 +189,6 @@ impl UsrJayCompositor { obj } - #[expect(dead_code)] pub fn get_sync_file_surface(&self, surface: &UsrWlSurface) -> Rc { let obj = Rc::new(UsrJaySyncFileSurface { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs b/src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs index 8aa184f4..bd07ef76 100644 --- a/src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs +++ b/src/wl_usr/usr_ifs/usr_jay_sync_file_surface.rs @@ -18,7 +18,6 @@ pub struct UsrJaySyncFileSurface { } impl UsrJaySyncFileSurface { - #[expect(dead_code)] pub fn set_acquire(&self, sf: Option<&FdSync>) { match sf.and_then(|s| s.get_sync_file()) { None => { @@ -33,7 +32,6 @@ impl UsrJaySyncFileSurface { } } - #[expect(dead_code)] pub fn get_release(&self) -> Rc { let obj = Rc::new(UsrJaySyncFileRelease { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_wl_data_device.rs b/src/wl_usr/usr_ifs/usr_wl_data_device.rs index 7b3cb57e..6309e86f 100644 --- a/src/wl_usr/usr_ifs/usr_wl_data_device.rs +++ b/src/wl_usr/usr_ifs/usr_wl_data_device.rs @@ -21,7 +21,6 @@ pub struct UsrWlDataDevice { } impl UsrWlDataDevice { - #[expect(dead_code)] pub fn set_selection(&self, serial: u32, source: &UsrWlDataSource) { self.con.request(SetSelection { self_id: self.id, diff --git a/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs b/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs index 0957a93a..58847c9f 100644 --- a/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs +++ b/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs @@ -21,7 +21,6 @@ pub struct UsrWlDataDeviceManager { } impl UsrWlDataDeviceManager { - #[expect(dead_code)] pub fn create_data_source(&self) -> Rc { let obj = Rc::new(UsrWlDataSource { id: self.con.id(), @@ -37,7 +36,6 @@ impl UsrWlDataDeviceManager { obj } - #[expect(dead_code)] pub fn get_data_device(&self, seat: &UsrWlSeat) -> Rc { let obj = Rc::new(UsrWlDataDevice { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_wl_data_offer.rs b/src/wl_usr/usr_ifs/usr_wl_data_offer.rs index 4c500e36..e757180a 100644 --- a/src/wl_usr/usr_ifs/usr_wl_data_offer.rs +++ b/src/wl_usr/usr_ifs/usr_wl_data_offer.rs @@ -17,7 +17,6 @@ pub struct UsrWlDataOffer { } impl UsrWlDataOffer { - #[expect(dead_code)] pub fn receive(&self, mime_type: &str, fd: &Rc) { self.con.request(Receive { self_id: self.id, diff --git a/src/wl_usr/usr_ifs/usr_wl_data_source.rs b/src/wl_usr/usr_ifs/usr_wl_data_source.rs index e7109346..99c07f67 100644 --- a/src/wl_usr/usr_ifs/usr_wl_data_source.rs +++ b/src/wl_usr/usr_ifs/usr_wl_data_source.rs @@ -21,7 +21,6 @@ pub trait UsrWlDataSourceOwner { } impl UsrWlDataSource { - #[expect(dead_code)] pub fn offer(&self, mime_type: &str) { self.con.request(Offer { self_id: self.id, diff --git a/src/wl_usr/usr_ifs/usr_wl_pointer.rs b/src/wl_usr/usr_ifs/usr_wl_pointer.rs index c8dcf486..661d9db9 100644 --- a/src/wl_usr/usr_ifs/usr_wl_pointer.rs +++ b/src/wl_usr/usr_ifs/usr_wl_pointer.rs @@ -41,7 +41,6 @@ pub trait UsrWlPointerOwner { } impl UsrWlPointer { - #[expect(dead_code)] pub fn set_cursor(&self, serial: u32, cursor: Option<&UsrWlSurface>, hot_x: i32, hot_y: i32) { self.con.request(SetCursor { self_id: self.id, diff --git a/src/wl_usr/usr_ifs/usr_wl_seat.rs b/src/wl_usr/usr_ifs/usr_wl_seat.rs index d1f4ba6f..76cfcab6 100644 --- a/src/wl_usr/usr_ifs/usr_wl_seat.rs +++ b/src/wl_usr/usr_ifs/usr_wl_seat.rs @@ -47,7 +47,6 @@ impl UsrWlSeat { ptr } - #[expect(dead_code)] pub fn get_keyboard(&self) -> Rc { let kb = Rc::new(UsrWlKeyboard { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs index 49e8c816..623f085f 100644 --- a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs +++ b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs @@ -15,7 +15,6 @@ pub struct UsrWpCursorShapeDeviceV1 { } impl UsrWpCursorShapeDeviceV1 { - #[expect(dead_code)] pub fn set_shape(&self, serial: u32, cursor: KnownCursor) { self.con.request(SetShape { self_id: self.id, diff --git a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs index 69c9cd55..49549cb9 100644 --- a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs +++ b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs @@ -21,7 +21,6 @@ pub struct UsrWpCursorShapeManagerV1 { } impl UsrWpCursorShapeManagerV1 { - #[expect(dead_code)] pub fn get_pointer(&self, pointer: &UsrWlPointer) -> Rc { let obj = Rc::new(UsrWpCursorShapeDeviceV1 { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_xdg_surface.rs b/src/wl_usr/usr_ifs/usr_xdg_surface.rs index e706c5c2..04eebaf3 100644 --- a/src/wl_usr/usr_ifs/usr_xdg_surface.rs +++ b/src/wl_usr/usr_ifs/usr_xdg_surface.rs @@ -22,7 +22,6 @@ pub trait UsrXdgSurfaceOwner { } impl UsrXdgSurface { - #[expect(dead_code)] pub fn get_toplevel(&self) -> Rc { let obj = Rc::new(UsrXdgToplevel { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs b/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs index 4e4d7491..3705abc6 100644 --- a/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs +++ b/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs @@ -16,7 +16,6 @@ pub struct UsrXdgToplevel { } impl UsrXdgToplevel { - #[expect(dead_code)] pub fn set_title(&self, title: &str) { self.con.request(SetTitle { self_id: self.id, @@ -24,7 +23,6 @@ impl UsrXdgToplevel { }); } - #[expect(dead_code)] pub fn set_fullscreen(&self, fullscreen: bool) { match fullscreen { true => { diff --git a/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs b/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs index b9d7faf0..95b5f16a 100644 --- a/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs +++ b/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs @@ -18,7 +18,6 @@ pub struct UsrXdgWmBase { } impl UsrXdgWmBase { - #[expect(dead_code)] pub fn get_xdg_surface(&self, surface: &UsrWlSurface) -> Rc { let obj = Rc::new(UsrXdgSurface { id: self.con.id(), diff --git a/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs b/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs index f2017a51..f39e85b4 100644 --- a/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs +++ b/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs @@ -22,7 +22,6 @@ pub struct UsrZwpLinuxDmabufV1 { } impl UsrZwpLinuxDmabufV1 { - #[expect(dead_code)] pub fn create_buffer(&self, buffer: &DmaBuf) -> Rc { let params = Rc::new(UsrZwpLinuxBufferParamsV1 { id: self.con.id(), diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 45a62ab6..8f51e717 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -211,6 +211,12 @@ pub struct Theme { pub bar_separator_width: Option, } +#[derive(Debug, Clone, Default)] +pub struct Egui { + pub proportional_fonts: Option>, + pub monospace_fonts: Option>, +} + #[derive(Debug, Clone)] pub struct Status { pub format: MessageFormat, @@ -510,6 +516,7 @@ pub struct Config { pub auto_reload: Option, pub log_level: Option, pub theme: Theme, + pub egui: Egui, pub gfx_api: Option, pub direct_scanout_enabled: Option, pub drm_devices: Vec, diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index 1a4319c3..27a4ea8a 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -19,6 +19,7 @@ mod connector_match; mod content_type; mod drm_device; mod drm_device_match; +mod egui; mod env; pub mod exec; mod fallback_output_mode; diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 10e12fca..1f0651b2 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -1,7 +1,7 @@ use { crate::{ config::{ - Action, Config, Libei, Theme, UiDrag, + Action, Config, Egui, Libei, Theme, UiDrag, context::Context, extractor::{Extractor, ExtractorError, arr, bol, int, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, @@ -13,6 +13,7 @@ use { connector::ConnectorsParser, drm_device::DrmDevicesParser, drm_device_match::DrmDeviceMatchParser, + egui::EguiParser, env::EnvParser, fallback_output_mode::FallbackOutputModeParser, float::FloatParser, @@ -150,6 +151,7 @@ impl Parser for ConfigParser<'_> { simple_im_val, show_titles, fallback_output_mode_val, + egui_val, ), ) = ext.extract(( ( @@ -208,6 +210,7 @@ impl Parser for ConfigParser<'_> { opt(val("simple-im")), recover(opt(bol("show-titles"))), opt(val("fallback-output-mode")), + opt(val("egui")), ), ))?; let mut keymap = None; @@ -313,6 +316,15 @@ impl Parser for ConfigParser<'_> { } } } + let mut egui = Egui::default(); + if let Some(value) = egui_val { + match value.parse(&mut EguiParser(self.0)) { + Ok(v) => egui = v, + Err(e) => { + log::warn!("Could not parse the egui settings: {}", self.0.error(e)); + } + } + } let mut gfx_api = None; if let Some(value) = gfx_api_val { match value.parse(&mut GfxApiParser) { @@ -556,6 +568,7 @@ impl Parser for ConfigParser<'_> { auto_reload: auto_reload.despan(), log_level, theme, + egui, gfx_api, drm_devices, direct_scanout_enabled: direct_scanout.despan(), diff --git a/toml-config/src/config/parsers/egui.rs b/toml-config/src/config/parsers/egui.rs new file mode 100644 index 00000000..6c77606e --- /dev/null +++ b/toml-config/src/config/parsers/egui.rs @@ -0,0 +1,63 @@ +use { + crate::{ + config::{ + Egui, + context::Context, + extractor::{Extractor, ExtractorError, arr, opt}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + toml::{ + toml_span::{Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +pub struct EguiParser<'a>(pub &'a Context<'a>); + +#[derive(Debug, Error)] +pub enum EguiParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extractor(#[from] ExtractorError), +} + +impl Parser for EguiParser<'_> { + type Value = Egui; + type Error = EguiParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (proportional_fonts_arr, monospace_fonts_arr) = + ext.extract((opt(arr("proportional-fonts")), opt(arr("monospace-fonts"))))?; + let mut proportional_fonts = None; + let mut monospace_fonts = None; + for (out, f) in [ + (&mut proportional_fonts, proportional_fonts_arr), + (&mut monospace_fonts, monospace_fonts_arr), + ] { + if let Some(f) = f { + let fonts = out.insert(vec![]); + for f in f.value { + let Value::String(s) = &f.value else { + log::error!("Expected a string: {}", self.0.error3(f.span)); + continue; + }; + fonts.push(s.clone()); + } + } + } + Ok(Egui { + proportional_fonts, + monospace_fonts, + }) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index db9058e6..9d2b5dfb 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -44,8 +44,8 @@ use { switch_to_vt, tasks::{self, JoinHandle}, theme::{ - reset_colors, reset_font, reset_sizes, set_bar_font, set_bar_position, set_font, - set_title_font, + reset_colors, reset_font, reset_sizes, set_bar_font, set_bar_position, + set_egui_monospace_fonts, set_egui_proportional_fonts, set_font, set_title_font, }, toggle_float_above_fullscreen, toggle_show_bar, toggle_show_titles, video::{ @@ -1633,6 +1633,12 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 54a26aea..ad6aeb6c 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1081,6 +1081,10 @@ "fallback-output-mode": { "description": "Sets the fallback output mode.\n\nThe default is `cursor`.\n\n- Example:\n\n ```toml\n fallback-output-mode = \"focus\"\n ```\n", "$ref": "#/$defs/FallbackOutputMode" + }, + "egui": { + "description": "Sets the egui settings of the compositor.\n", + "$ref": "#/$defs/Egui" } }, "required": [] @@ -1237,6 +1241,29 @@ } ] }, + "Egui": { + "description": "The egui settings.\n", + "type": "object", + "properties": { + "proportional-fonts": { + "type": "array", + "description": "The list of proportional fonts.\n\nThe default is `[\"sans-serif\", \"Noto Sans\", \"Noto Color Emoji\"]`.\n", + "items": { + "type": "string", + "description": "" + } + }, + "monospace-fonts": { + "type": "array", + "description": "The list of monospace fonts.\n\nThe default is `[\"monospace\", \"Noto Sans Mono\", \"Noto Color Emoji\"]`.\n", + "items": { + "type": "string", + "description": "" + } + } + }, + "required": [] + }, "Eotf": { "type": "string", "description": "The EOTF of an output.\n", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 1c4b1c94..fb1ef758 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -2248,6 +2248,12 @@ The table has the following fields: The value of this field should be a [FallbackOutputMode](#types-FallbackOutputMode). +- `egui` (optional): + + Sets the egui settings of the compositor. + + The value of this field should be a [Egui](#types-Egui). + ### `Connector` @@ -2588,6 +2594,32 @@ The table has the following fields: The numbers should be integers. + +### `Egui` + +The egui settings. + +Values of this type should be tables. + +The table has the following fields: + +- `proportional-fonts` (optional): + + The list of proportional fonts. + + The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`. + + The value of this field should be an array of strings. + +- `monospace-fonts` (optional): + + The list of monospace fonts. + + The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`. + + The value of this field should be an array of strings. + + ### `Eotf` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index cdb0767a..81442597 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3004,6 +3004,11 @@ Config: ```toml fallback-output-mode = "focus" ``` + egui: + ref: Egui + required: false + description: | + Sets the egui settings of the compositor. Idle: @@ -4426,3 +4431,28 @@ FallbackOutputMode: description: Use the output the cursor is on. - value: focus description: Use the output the focus is on (highlighted window). + + +Egui: + kind: table + description: | + The egui settings. + fields: + proportional-fonts: + kind: array + items: + kind: string + required: false + description: | + The list of proportional fonts. + + The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`. + monospace-fonts: + kind: array + items: + kind: string + required: false + description: | + The list of monospace fonts. + + The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`.