diff --git a/Cargo.lock b/Cargo.lock index 1293fbf..abb1ff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-activity" version = "0.5.2" @@ -73,18 +79,72 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arboard" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" +dependencies = [ + "clipboard-win", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot", + "thiserror", + "x11rb", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.4" @@ -124,6 +184,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec 0.7.4", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec 0.7.4", +] + [[package]] name = "backtrace" version = "0.3.70" @@ -139,6 +222,21 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -151,6 +249,27 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bitstream-io" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-sys" version = "0.2.1" @@ -170,6 +289,12 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d17f4d6e4dc36d1a02fbedc2753a096848e7c1b0772f7654eab8e2c927dd53" + [[package]] name = "bumpalo" version = "3.15.4" @@ -196,6 +321,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -244,6 +375,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -256,6 +397,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "chrono" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.4", +] + +[[package]] +name = "clipboard-win" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.50" @@ -265,6 +427,31 @@ dependencies = [ "cc", ] +[[package]] +name = "codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d" +dependencies = [ + "indexmap 1.9.3", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.6" @@ -324,6 +511,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -379,6 +575,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -400,12 +630,74 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "ecolor" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb152797942f72b84496eb2ebeff0060240e0bf55096c4525ffa22dd54722d86" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "egui" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1b8cc14b0b260aa6bd124ef12c8a94f57ffe8e40aa970f3db710c21bb945f3" +dependencies = [ + "ahash", + "epaint", + "log", + "nohash-hasher", +] + +[[package]] +name = "egui-winit" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3733435d6788c760bb98ce4cb1b8b7a2d953a3a7b421656ba8b3e014019be3d0" +dependencies = [ + "arboard", + "egui", + "log", + "raw-window-handle 0.6.0", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "emath" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555a7cbfcc52c81eb5f8f898190c840fa1c435f67f30b7ef77ce7cf6b7dcd987" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd63c37156e949bda80f7e39cc11508bc34840aecf52180567e67cdb2bf1a5fe" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -431,6 +723,37 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "filetime" version = "0.2.23" @@ -443,6 +766,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flax" version = "0.6.2" @@ -455,7 +788,7 @@ dependencies = [ "erased-serde", "flax-derive", "flume", - "itertools", + "itertools 0.11.0", "once_cell", "rayon", "serde", @@ -471,7 +804,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d141b9bb793ce817c1ec513e93b72e4e2a6d9494b18d5c1e454fa08905586d6" dependencies = [ - "itertools", + "itertools 0.11.0", "proc-macro-crate 2.0.0", "proc-macro2", "quote", @@ -517,6 +850,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -532,12 +874,63 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -561,6 +954,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -578,6 +981,22 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", + "serde", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -590,12 +1009,50 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icrate" version = "0.0.4" @@ -607,6 +1064,88 @@ dependencies = [ "objc2", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "im-lists" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e7233fb8b1ffc0b1d6033fd311a74a0443164628c62abbc871185ee95098b63" +dependencies = [ + "smallvec", +] + +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "image" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c" +dependencies = [ + "byteorder", + "thiserror", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.5" @@ -614,7 +1153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] @@ -637,6 +1176,17 @@ dependencies = [ "libc", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itertools" version = "0.11.0" @@ -646,6 +1196,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -683,6 +1242,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -696,16 +1261,21 @@ dependencies = [ name = "khors" version = "0.1.0" dependencies = [ + "ahash", "anyhow", "downcast-rs", + "egui", + "egui-winit", "flax", "flume", + "image", "notify", "notify-debouncer-mini", "parking_lot", "serde", "serde-lexpr", - "shrev", + "steel-core", + "steel-derive", "tokio", "vulkano", "vulkano-shaders", @@ -733,6 +1303,23 @@ dependencies = [ "libc", ] +[[package]] +name = "lasso" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" +dependencies = [ + "dashmap", + "hashbrown 0.13.2", + "serde", +] + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lexpr" version = "0.2.7" @@ -760,6 +1347,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -780,6 +1378,17 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "libredox" version = "0.0.2" @@ -813,6 +1422,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -822,6 +1440,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.1" @@ -850,6 +1478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -904,6 +1533,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -914,6 +1555,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify" version = "6.1.1" @@ -944,6 +1591,95 @@ dependencies = [ "notify", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -984,6 +1720,17 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc-sys" version = "0.3.2" @@ -1006,6 +1753,15 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.32.2" @@ -1021,13 +1777,19 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox", + "libredox 0.0.2", ] [[package]] @@ -1062,6 +1824,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1074,12 +1842,31 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.5.0" @@ -1094,6 +1881,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +dependencies = [ + "arrayvec 0.5.2", + "typed-arena", + "unicode-width", +] + [[package]] name = "proc-macro-crate" version = "2.0.0" @@ -1121,6 +1925,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.31.0" @@ -1130,6 +1968,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "quickscope" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d47bcfc3e13850589cf9338a02b6dfb5aebb3748a0f93a392e8df91d6193b6b" +dependencies = [ + "indexmap 1.9.3", + "smallvec", +] + [[package]] name = "quote" version = "1.0.35" @@ -1139,6 +1987,101 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec 0.7.4", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -1189,6 +2132,26 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox 0.0.1", + "thiserror", +] + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "roxmltree" version = "0.14.1" @@ -1298,6 +2261,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "shaderc" version = "0.8.3" @@ -1319,12 +2291,6 @@ dependencies = [ "roxmltree", ] -[[package]] -name = "shrev" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ea33232fdcf1bf691ca33450e5a94dde13e1a8cbb8caabc5e4f9d761e10b1a" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1334,6 +2300,31 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.9" @@ -1380,6 +2371,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.1" @@ -1408,12 +2410,88 @@ dependencies = [ "lock_api", ] +[[package]] +name = "steel-core" +version = "0.6.0" +source = "git+https://github.com/mattwparas/steel.git?branch=master#08c3a14a540027575f3a37f9f6dba3b4c1b48b7d" +dependencies = [ + "bincode", + "chrono", + "codespan-reporting", + "dirs", + "futures-executor", + "futures-task", + "futures-util", + "fxhash", + "im-lists", + "im-rc", + "lasso", + "log", + "num", + "once_cell", + "pretty", + "quickscope", + "radix_fmt", + "rand", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "steel-derive", + "steel-gen", + "steel-parser", + "strsim", + "weak-table", + "which", +] + +[[package]] +name = "steel-derive" +version = "0.5.0" +source = "git+https://github.com/mattwparas/steel.git?branch=master#08c3a14a540027575f3a37f9f6dba3b4c1b48b7d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "steel-gen" +version = "0.2.0" +source = "git+https://github.com/mattwparas/steel.git?branch=master#08c3a14a540027575f3a37f9f6dba3b4c1b48b7d" +dependencies = [ + "codegen", + "serde", + "serde_derive", +] + +[[package]] +name = "steel-parser" +version = "0.6.0" +source = "git+https://github.com/mattwparas/steel.git?branch=master#08c3a14a540027575f3a37f9f6dba3b4c1b48b7d" +dependencies = [ + "fxhash", + "lasso", + "num", + "once_cell", + "pretty", + "serde", + "serde_derive", + "smallvec", +] + [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "syn" version = "2.0.53" @@ -1425,6 +2503,34 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -1455,6 +2561,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1462,7 +2579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.4", "bytemuck", "cfg-if", "log", @@ -1480,6 +2597,21 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.36.0" @@ -1510,11 +2642,26 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.9", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -1522,9 +2669,9 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap", + "indexmap 2.2.5", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -1533,9 +2680,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap", + "indexmap 2.2.5", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +dependencies = [ + "indexmap 2.2.5", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.5", ] [[package]] @@ -1584,18 +2744,79 @@ dependencies = [ "nom", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -1622,8 +2843,8 @@ dependencies = [ "core-graphics-types", "crossbeam-queue", "half", - "heck", - "indexmap", + "heck 0.4.1", + "indexmap 2.2.5", "libloading 0.8.3", "nom", "objc", @@ -1658,7 +2879,7 @@ version = "0.34.0" source = "git+https://github.com/vulkano-rs/vulkano.git?branch=master#7cbf3a7f2694f75e44c778878b5520a45c6a2d1c" dependencies = [ "ahash", - "heck", + "heck 0.4.1", "proc-macro2", "quote", "shaderc", @@ -1867,6 +3088,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + [[package]] name = "web-sys" version = "0.3.69" @@ -1887,6 +3114,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b04c569c83a9bb971dd47ec6fd48753315f4bf989b9b04a2e7ca4d7f0dc950" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle 0.5.2", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1918,6 +3180,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -2174,6 +3445,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +dependencies = [ + "memchr", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -2262,3 +3542,27 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 0c9b5e1..84aea6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] anyhow = "1.0.80" -shrev = "1.1.3" winit = { version = "0.29.15",features = ["rwh_05"] } vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } @@ -21,3 +20,9 @@ serde-lexpr = "0.1.3" tokio = { version = "1.36.0", features = ["full"] } notify = "6.1.1" notify-debouncer-mini = "0.4.1" +steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" } +steel-derive = { git="https://github.com/mattwparas/steel.git", branch = "master" } +egui = "0.27.1" +image = "0.25.0" +ahash = "0.8.11" +egui-winit = "0.27.1" diff --git a/src/app.rs b/src/app.rs index 1be7555..a1cc560 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,22 +1,37 @@ #![warn(dead_code)] -use crate::core::{ - events::Events, - module::{Module, ModulesStack}, +use std::collections::HashMap; + +use crate::{ + core::{ + events::Events, + module::{Module, ModulesStack, RenderModule as ThreadLocalModule, RenderModulesStack}, + }, + modules::graphics::egui::{Gui, GuiConfig}, }; use anyhow::Result; use flax::{Schedule, World}; +use vulkano::device::DeviceFeatures; +use vulkano_util::{ + context::{VulkanoConfig, VulkanoContext}, + window::VulkanoWindows, +}; +use winit::{event::{Event, WindowEvent}, window::WindowId}; #[allow(dead_code)] pub struct App { name: String, modules: ModulesStack, + thread_local_modules: RenderModulesStack, world: World, schedule: Schedule, events: Events, rx: flume::Receiver, running: bool, event_cleanup_time: std::time::Duration, + vk_context: VulkanoContext, + vk_windows: VulkanoWindows, + guis: HashMap, } impl App { @@ -28,15 +43,29 @@ impl App { let schedule = Schedule::builder().build(); + let vk_config = VulkanoConfig { + device_features: DeviceFeatures { + dynamic_rendering: true, + ..Default::default() + }, + ..Default::default() + }; + let vk_context = VulkanoContext::new(vk_config); + let vk_windows = VulkanoWindows::default(); + Self { name: "Khors".into(), modules: ModulesStack::new(), + thread_local_modules: RenderModulesStack::new(), world: World::new(), schedule, events, rx, running: false, event_cleanup_time: std::time::Duration::from_secs(60), + vk_context, + vk_windows, + guis: HashMap::new(), } } @@ -45,19 +74,125 @@ impl App { self.schedule.execute_par(&mut self.world).unwrap(); + let vk_context = &mut self.vk_context; + let vk_windows = &mut self.vk_windows; let world = &mut self.world; let events = &mut self.events; let frame_time = std::time::Duration::from_millis(16); + let guis = &mut self.guis; for module in self.modules.iter_mut() { module.on_update(world, events, frame_time)?; } + for module in self.thread_local_modules.iter_mut() { + module.on_update(guis, vk_context, vk_windows, world, events, frame_time)?; + } + self.handle_events(); Ok(()) } + pub fn create_window(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget) + where + T: Clone + Send + Sync, + { + let window = self.vk_windows.create_window( + event_loop, + &self.vk_context, + &vulkano_util::window::WindowDescriptor { + title: self.name.clone(), + present_mode: vulkano::swapchain::PresentMode::Fifo, + ..Default::default() + }, + |_| {}, + ); + + let renderer = self.vk_windows.get_renderer(window).unwrap(); + let gui = Gui::new( + event_loop, + renderer.surface().clone(), + renderer.graphics_queue().clone(), + renderer.swapchain_format(), + GuiConfig { is_overlay: true, allow_srgb_render_target: false, ..Default::default() }, + ); + + self.guis.insert(window, gui); + } + + pub fn process_event_loop( + &mut self, + event: winit::event::Event, + _elwt: &winit::event_loop::EventLoopWindowTarget, + ) -> Result + where + T: Clone + Send + Sync, + { + match &event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + return Ok(true); + } + Event::WindowEvent { + event: WindowEvent::Focused(_), + .. + } => self.events().send(event.clone()), + Event::WindowEvent { + event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, + window_id, + } => self + .vk_windows + .get_renderer_mut(*window_id) + .unwrap() + .resize(), + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + window_id, + } => 'redraw: { + // Tasks for redrawing: + // 1. Update state based on events + // 2. Compute & Render + // 3. Reset input state + // 4. Update time & title + + // The rendering part goes here: + match self + .vk_windows + .get_renderer(*window_id) + .unwrap() + .window_size() + { + [w, h] => { + // Skip this frame when minimized. + if w == 0.0 || h == 0.0 { + break 'redraw; + } + } + } + + self.run()?; + } + Event::WindowEvent { window_id, event } => { + let window = self.vk_windows.get_window(*window_id).unwrap(); + let gui = self.guis.get_mut(window_id).unwrap(); + gui.update(window, event); + } + Event::AboutToWait => { + for (window_id, _) in self.vk_windows.iter() { + self.vk_windows + .get_window(*window_id) + .unwrap() + .request_redraw() + } + } + _ => (), + } + Ok(false) + } + pub fn handle_events(&mut self) { for event in self.rx.try_iter() { match event { @@ -66,14 +201,17 @@ impl App { } } + #[allow(dead_code)] pub fn set_schedule(&mut self, schedule: Schedule) { self.schedule = schedule; } + #[allow(dead_code)] pub fn world(&self) -> &World { &self.world } + #[allow(dead_code)] pub fn world_mut(&mut self) -> &mut World { &mut self.world } @@ -82,10 +220,34 @@ impl App { &self.events } + #[allow(dead_code)] pub fn events_mut(&mut self) -> &mut Events { &mut self.events } + /// Pushes a module from the provided init closure to to the top of the layer stack. The provided + /// closure to construct the layer takes in the world and events. + pub fn push_render_module(&mut self, func: F) + where + F: FnOnce( + &mut VulkanoContext, + &mut VulkanoWindows, + &mut Schedule, + &mut World, + &mut Events, + ) -> T, + T: 'static + ThreadLocalModule, + { + let module = func( + &mut self.vk_context, + &mut self.vk_windows, + &mut self.schedule, + &mut self.world, + &mut self.events, + ); + self.thread_local_modules.push(module); + } + /// Pushes a layer from the provided init closure to to the top of the layer stack. The provided /// closure to construct the layer takes in the world and events. pub fn push_module(&mut self, func: F) @@ -100,6 +262,7 @@ impl App { /// Pushes a module from the provided init closure to to the top of the module stack. The provided /// closure to construct the module takes in the world and events, and may return an error which /// is propagated to the callee. + #[allow(dead_code)] pub fn try_push_module(&mut self, func: F) -> Result<(), E> where F: FnOnce(&mut World, &mut Events) -> Result, @@ -112,6 +275,7 @@ impl App { /// Inserts a module from the provided init closure to to the top of the module stack. The provided /// closure to construct the module takes in the world and events. + #[allow(dead_code)] pub fn insert_module(&mut self, index: usize, func: F) where F: FnOnce(&mut World, &mut Events) -> T, @@ -124,6 +288,7 @@ impl App { /// Pushes a module from the provided init closure to to the top of the module stack. The provided /// closure to construct the module takes in the world and events, and may return an error which /// is propagated to the callee. + #[allow(dead_code)] pub fn try_insert_module(&mut self, index: usize, func: F) -> Result<(), E> where F: FnOnce(&mut World, &mut Events) -> Result, diff --git a/src/comp.rs b/src/comp.rs deleted file mode 100644 index 8b2efaf..0000000 --- a/src/comp.rs +++ /dev/null @@ -1,42 +0,0 @@ -use flax::{component, BoxedSystem, EntityBorrow, Query, System}; -use winit::window::Window; - -component! { - pub window_width: f32, - pub window: Window, - pub counter: i32, - - pub resources, -} - -pub fn update_distance_system() -> BoxedSystem { - System::builder() - .with_name("update_distance") - .with_query( - Query::new((window_width().as_mut(), window(), counter().as_mut())).entity(resources()), - ) - .build(|mut query: EntityBorrow<_>| { - if let Ok((window_width, _window, counter)) = query.get() { - // println!("Win width: {window_width}"); - *(window_width as &mut f32) = *(counter as &mut i32) as f32; - *(counter as &mut i32) += 1; - } - }) - .boxed() -} - -pub fn log_window_system() -> BoxedSystem { - let query = Query::new((window_width(), window())).entity(resources()); - - System::builder() - .with_query(query) - .build(|mut q: EntityBorrow<_>| { - if let Ok((width, wind)) = q.get() { - println!("window id: {:?}", (wind as &Window).id()); - println!("Config changed width: {width}"); - } else { - println!("No config change"); - } - }) - .boxed() -} diff --git a/src/core/mod.rs b/src/core/mod.rs index ec24779..bd43ed8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,2 @@ pub mod events; pub mod module; -// pub mod render; diff --git a/src/core/module.rs b/src/core/module.rs index 230d662..670b666 100644 --- a/src/core/module.rs +++ b/src/core/module.rs @@ -1,9 +1,10 @@ -use std::time::Duration; +use std::{collections::HashMap, time::Duration}; use anyhow::Result; use flax::World; +use winit::window::WindowId; -use crate::core::events::Events; +use crate::{core::events::Events, modules::graphics::egui::Gui}; pub trait Module { fn on_update(&mut self, world: &mut World, events: &mut Events, frame_time: Duration) -> Result<()>; @@ -62,3 +63,63 @@ impl<'a> IntoIterator for &'a mut ModulesStack { self.iter_mut() } } + +// THREAD LOCAL STUFF + +pub trait RenderModule { + fn on_update(&mut self, gui: &mut HashMap, vk_context: &mut vulkano_util::context::VulkanoContext, vk_windows: &mut vulkano_util::window::VulkanoWindows, world: &mut World, events: &mut Events, frame_time: Duration) -> Result<()>; +} + +pub struct RenderModulesStack { + modules: Vec>, +} + +impl RenderModulesStack { + pub fn new() -> Self { + Self { modules: Vec::new() } + } + + pub fn iter(&self) -> std::slice::Iter> { + self.modules.iter() + } + + pub fn iter_mut(&mut self) -> std::slice::IterMut> { + self.modules.iter_mut() + } + + pub fn push(&mut self, layer: T) { + let layer = Box::new(layer); + self.modules.push(layer); + } + + pub fn insert(&mut self, index: usize, layer: T) { + let layer = Box::new(layer); + self.modules.insert(index, layer); + } +} + +impl Default for RenderModulesStack { + fn default() -> Self { + Self::new() + } +} + +impl<'a> IntoIterator for &'a RenderModulesStack { + type Item = &'a Box; + + type IntoIter = std::slice::Iter<'a, Box>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut RenderModulesStack { + type Item = &'a mut Box; + + type IntoIter = std::slice::IterMut<'a, Box>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/core/render/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main.rs b/src/main.rs index b585bbf..d24e21b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,24 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use app::App; -use modules::{config::ConfigModule, graphics::RenderModule}; +use modules::{config::ConfigModule, graphics::RenderModule, window::WindowModule}; use tokio::runtime::Builder; -use vulkano_util::{ - context::{VulkanoConfig, VulkanoContext}, - renderer::VulkanoWindowRenderer, - window::{VulkanoWindows, WindowDescriptor}, -}; -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoopBuilder}, -}; +use winit::event_loop::{ControlFlow, EventLoopBuilder}; mod app; -mod modules; mod core; +mod modules; fn main() -> Result<()> { let event_loop = EventLoopBuilder::new().build()?; - - let context = VulkanoContext::new(VulkanoConfig::default()); - let mut windows = VulkanoWindows::default(); let runtime = Builder::new_multi_thread().enable_all().build()?; - let (event_tx, event_rx) = flume::unbounded(); + // let (event_tx, event_rx) = flume::unbounded(); runtime.block_on(async { runtime.spawn(async move { loop { - let _event = event_rx.recv_async().await.unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); + // let _event = event_rx.recv_async().await.unwrap(); // println!( // "Tokio got event: {:?} on thread: {:?}", // event, @@ -38,78 +28,24 @@ fn main() -> Result<()> { }); }); - let _id = windows.create_window( - &event_loop, - &context, - &WindowDescriptor { - title: "Khors".into(), - present_mode: vulkano::swapchain::PresentMode::Fifo, - ..Default::default() - }, - |_| {}, - ); - - let primary_window_renderer = windows.get_primary_renderer_mut().context("Failed to create primary window renderer")?; - let _gfx_queue = context.graphics_queue(); - - let mut app = App::new(); + let mut app = App::new(); // TODO: Move renderer into App + app.create_window(&event_loop); app.push_module(ConfigModule::new); - app.push_module(RenderModule::new); + app.push_module(WindowModule::new); + app.push_render_module(RenderModule::new); - event_loop - .run(move |event, elwt| { - elwt.set_control_flow(ControlFlow::Poll); + event_loop.run(move |event, elwt| { + elwt.set_control_flow(ControlFlow::Poll); - if process_event(primary_window_renderer, &event, &mut app).expect("App execution failed") { - elwt.exit(); - } + if app + .process_event_loop(event, elwt) + .expect("Execution failed") + { + elwt.exit(); + } - event_tx.send(event.clone()).unwrap(); - })?; + // event_tx.send(event.clone()).unwrap(); + })?; Ok(()) } - -pub fn process_event( - renderer: &mut VulkanoWindowRenderer, - event: &Event<()>, - app: &mut App, -) -> Result { - match &event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - return Ok(true); - } - Event::WindowEvent { - event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, - .. - } => renderer.resize(), - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => 'redraw: { - // Tasks for redrawing: - // 1. Update state based on events - // 2. Compute & Render - // 3. Reset input state - // 4. Update time & title - - // The rendering part goes here: - match renderer.window_size() { - [w, h] => { - // Skip this frame when minimized. - if w == 0.0 || h == 0.0 { - break 'redraw; - } - } - } - - app.run()?; - } - Event::AboutToWait => renderer.window().request_redraw(), - _ => (), - } - Ok(false) -} diff --git a/src/modules/graphics/egui/integration.rs b/src/modules/graphics/egui/integration.rs new file mode 100644 index 0000000..f642371 --- /dev/null +++ b/src/modules/graphics/egui/integration.rs @@ -0,0 +1,321 @@ +// Copyright (c) 2021 Okko Hakola, 2024 Klink +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. +use std::sync::Arc; + +use egui::{ClippedPrimitive, TexturesDelta}; +use egui_winit::winit::event_loop::EventLoopWindowTarget; +use vulkano::{ + command_buffer::CommandBuffer, device::Queue, format::{Format, NumericFormat}, image::{sampler::SamplerCreateInfo, view::ImageView, SampleCount}, render_pass::Subpass, swapchain::Surface, sync::GpuFuture +}; +use winit::window::Window; + +use super::{ + renderer::{RenderResources, Renderer}, + utils::{immutable_texture_from_bytes, immutable_texture_from_file}, +}; + +pub struct GuiConfig { + /// Allows supplying sRGB ImageViews as render targets instead of just UNORM ImageViews, defaults to false. + /// **Using sRGB will cause minor discoloration of UI elements** due to blending in linear color space and not + /// sRGB as Egui expects. + /// + /// If you would like to visually compare between UNORM and sRGB render targets, run the `demo_app` example of + /// this crate. + pub allow_srgb_render_target: bool, + /// Whether to render gui as overlay. Only relevant in the case of `Gui::new`, not when using + /// subpass. Determines whether the pipeline should clear the target image. + pub is_overlay: bool, + /// Multisample count. Defaults to 1. If you use more than 1, you'll have to ensure your + /// pipeline and target image matches that. + pub samples: SampleCount, +} + +impl Default for GuiConfig { + fn default() -> Self { + GuiConfig { + allow_srgb_render_target: false, + is_overlay: false, + samples: SampleCount::Sample1, + } + } +} + +impl GuiConfig { + pub fn validate(&self, output_format: Format) { + if output_format.numeric_format_color().unwrap() == NumericFormat::SRGB { + assert!( + self.allow_srgb_render_target, + "Using an output format with sRGB requires `GuiConfig::allow_srgb_render_target` \ + to be set! Egui prefers UNORM render targets. Using sRGB will cause minor \ + discoloration of UI elements due to blending in linear color space and not sRGB \ + as Egui expects." + ); + } + } +} + +pub struct Gui { + pub egui_ctx: egui::Context, + pub egui_winit: egui_winit::State, + renderer: Renderer, + surface: Arc, + + shapes: Vec, + textures_delta: egui::TexturesDelta, +} + +impl Gui { + /// Creates new Egui to Vulkano integration by setting the necessary parameters + /// This is to be called once we have access to vulkano_win's winit window surface + /// and gfx queue. Created with this, the renderer will own a render pass which is useful to e.g. place your render pass' images + /// onto egui windows + pub fn new( + event_loop: &EventLoopWindowTarget, + surface: Arc, + gfx_queue: Arc, + output_format: Format, + config: GuiConfig, + ) -> Gui { + config.validate(output_format); + let renderer = Renderer::new_with_render_pass( + gfx_queue, + output_format, + config.is_overlay, + config.samples, + ); + Self::new_internal(event_loop, surface, renderer) + } + + /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass + pub fn new_with_subpass( + event_loop: &EventLoopWindowTarget, + surface: Arc, + gfx_queue: Arc, + subpass: Subpass, + output_format: Format, + config: GuiConfig, + ) -> Gui { + config.validate(output_format); + let renderer = Renderer::new_with_subpass(gfx_queue, output_format, subpass); + Self::new_internal(event_loop, surface, renderer) + } + + /// Same as `new` but instead of integration owning a render pass, egui renders on your subpass + fn new_internal( + event_loop: &EventLoopWindowTarget, + surface: Arc, + renderer: Renderer, + ) -> Gui { + let max_texture_side = renderer + .queue() + .device() + .physical_device() + .properties() + .max_image_dimension2_d as usize; + let egui_ctx: egui::Context = Default::default(); + let egui_winit = egui_winit::State::new( + egui_ctx.clone(), + egui_ctx.viewport_id(), + event_loop, + Some(surface_window(&surface).scale_factor() as f32), + Some(max_texture_side), + ); + Gui { + egui_ctx, + egui_winit, + renderer, + surface, + shapes: vec![], + textures_delta: Default::default(), + } + } + + /// Returns the pixels per point of the window of this gui. + fn pixels_per_point(&self) -> f32 { + egui_winit::pixels_per_point(&self.egui_ctx, surface_window(&self.surface)) + } + + /// Returns a set of resources used to construct the render pipeline. These can be reused + /// to create additional pipelines and buffers to be rendered in a `PaintCallback`. + pub fn render_resources(&self) -> RenderResources { + self.renderer.render_resources() + } + + /// Updates context state by winit window event. + /// Returns `true` if egui wants exclusive use of this event + /// (e.g. a mouse click on an egui window, or entering text into a text field). + /// For instance, if you use egui for a game, you want to first call this + /// and only when this returns `false` pass on the events to your game. + /// + /// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs. + pub fn update(&mut self, window: &Window, winit_event: &winit::event::WindowEvent) -> bool { + self.egui_winit + .on_window_event(window, winit_event) + .consumed + } + + /// Begins Egui frame & determines what will be drawn later. This must be called before draw, and after `update` (winit event). + pub fn immediate_ui(&mut self, layout_function: impl FnOnce(&mut Self)) { + let raw_input = self + .egui_winit + .take_egui_input(surface_window(&self.surface)); + self.egui_ctx.begin_frame(raw_input); + // Render Egui + layout_function(self); + } + + /// If you wish to better control when to begin frame, do so by calling this function + /// (Finish by drawing) + pub fn begin_frame(&mut self) { + let raw_input = self + .egui_winit + .take_egui_input(surface_window(&self.surface)); + self.egui_ctx.begin_frame(raw_input); + } + + /// Renders ui on `final_image` & Updates cursor icon + /// Finishes Egui frame + /// - `before_future` = Vulkano's GpuFuture + /// - `final_image` = Vulkano's image (render target) + pub fn draw_on_image( + &mut self, + before_future: F, + final_image: Arc, + ) -> Box + where + F: GpuFuture + 'static, + { + if !self.renderer.has_renderpass() { + panic!( + "Gui integration has been created with subpass, use `draw_on_subpass_image` \ + instead" + ) + } + + let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); + + self.renderer.draw_on_image( + &clipped_meshes, + &textures_delta, + self.pixels_per_point(), + before_future, + final_image, + ) + } + + /// Creates commands for rendering ui on subpass' image and returns the command buffer for execution on your side + /// - Finishes Egui frame + /// - You must execute the secondary command buffer yourself + pub fn draw_on_subpass_image( + &mut self, + image_dimensions: [u32; 2], + ) -> Arc { + if self.renderer.has_renderpass() { + panic!( + "Gui integration has been created with its own render pass, use `draw_on_image` \ + instead" + ) + } + + let (clipped_meshes, textures_delta) = self.extract_draw_data_at_frame_end(); + + self.renderer.draw_on_subpass_image( + &clipped_meshes, + &textures_delta, + self.pixels_per_point(), + image_dimensions, + ) + } + + fn extract_draw_data_at_frame_end(&mut self) -> (Vec, TexturesDelta) { + self.end_frame(); + let shapes = std::mem::take(&mut self.shapes); + let textures_delta = std::mem::take(&mut self.textures_delta); + let clipped_meshes = self.egui_ctx.tessellate(shapes, self.pixels_per_point()); + (clipped_meshes, textures_delta) + } + + fn end_frame(&mut self) { + let egui::FullOutput { + platform_output, + textures_delta, + shapes, + pixels_per_point: _, + viewport_output: _, + } = self.egui_ctx.end_frame(); + + self.egui_winit.handle_platform_output( + surface_window(&self.surface), + platform_output, + ); + self.shapes = shapes; + self.textures_delta = textures_delta; + } + + /// Registers a user image from Vulkano image view to be used by egui + pub fn register_user_image_view( + &mut self, + image: Arc, + sampler_create_info: SamplerCreateInfo, + ) -> egui::TextureId { + self.renderer.register_image(image, sampler_create_info) + } + + /// Registers a user image to be used by egui + /// - `image_file_bytes`: e.g. include_bytes!("./assets/tree.png") + /// - `format`: e.g. vulkano::format::Format::R8G8B8A8Unorm + pub fn register_user_image( + &mut self, + image_file_bytes: &[u8], + format: vulkano::format::Format, + sampler_create_info: SamplerCreateInfo, + ) -> egui::TextureId { + let image = immutable_texture_from_file( + self.renderer.allocators(), + self.renderer.queue(), + image_file_bytes, + format, + ) + .expect("Failed to create image"); + self.renderer.register_image(image, sampler_create_info) + } + + pub fn register_user_image_from_bytes( + &mut self, + image_byte_data: &[u8], + dimensions: [u32; 2], + format: vulkano::format::Format, + sampler_create_info: SamplerCreateInfo, + ) -> egui::TextureId { + let image = immutable_texture_from_bytes( + self.renderer.allocators(), + self.renderer.queue(), + image_byte_data, + dimensions, + format, + ) + .expect("Failed to create image"); + self.renderer.register_image(image, sampler_create_info) + } + + /// Unregisters a user image + pub fn unregister_user_image(&mut self, texture_id: egui::TextureId) { + self.renderer.unregister_image(texture_id); + } + + /// Access egui's context (which can be used to e.g. set fonts, visuals etc) + pub fn context(&self) -> egui::Context { + self.egui_ctx.clone() + } +} + +// Helper to retrieve Window from surface object +fn surface_window(surface: &Surface) -> &Window { + surface.object().unwrap().downcast_ref::().unwrap() +} diff --git a/src/modules/graphics/egui/mod.rs b/src/modules/graphics/egui/mod.rs new file mode 100644 index 0000000..ec672e0 --- /dev/null +++ b/src/modules/graphics/egui/mod.rs @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Okko Hakola, 2024 Klink +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +mod integration; +mod renderer; +mod utils; + +pub use egui; +pub use integration::*; +#[allow(unused_imports)] +pub use renderer::{CallbackContext, CallbackFn, RenderResources}; +#[allow(unused_imports)] +pub use utils::{immutable_texture_from_bytes, immutable_texture_from_file}; diff --git a/src/modules/graphics/egui/renderer.rs b/src/modules/graphics/egui/renderer.rs new file mode 100644 index 0000000..ab61b19 --- /dev/null +++ b/src/modules/graphics/egui/renderer.rs @@ -0,0 +1,1208 @@ +// Copyright (c) 2021 Okko Hakola, 2024 Klink +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::sync::Arc; + +use ahash::AHashMap; +use egui::{epaint::Primitive, ClippedPrimitive, PaintCallbackInfo, Rect, TexturesDelta}; +use vulkano::{ + buffer::{ + allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, + Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, + }, + command_buffer::{ + allocator::StandardCommandBufferAllocator, auto::RecordingCommandBuffer, BufferImageCopy, + CommandBuffer, CommandBufferBeginInfo, CommandBufferInheritanceInfo, CommandBufferLevel, + CommandBufferUsage, CopyBufferToImageInfo, RenderPassBeginInfo, SubpassBeginInfo, + SubpassContents, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, layout::DescriptorSetLayout, DescriptorSet, + WriteDescriptorSet, + }, + device::Queue, + format::{Format, NumericFormat}, + image::{ + sampler::{ + ComponentMapping, ComponentSwizzle, Filter, Sampler, SamplerAddressMode, + SamplerCreateInfo, SamplerMipmapMode, + }, + view::{ImageView, ImageViewCreateInfo}, + Image, ImageAspects, ImageCreateInfo, ImageLayout, ImageSubresourceLayers, ImageType, + ImageUsage, SampleCount, + }, + memory::{ + allocator::{ + AllocationCreateInfo, DeviceLayout, MemoryTypeFilter, StandardMemoryAllocator, + }, + DeviceAlignment, + }, + pipeline::{ + graphics::{ + color_blend::{ + AttachmentBlend, BlendFactor, ColorBlendAttachmentState, ColorBlendState, + }, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, + vertex_input::{Vertex, VertexDefinition}, + viewport::{Scissor, Viewport, ViewportState}, + GraphicsPipelineCreateInfo, + }, + layout::PipelineDescriptorSetLayoutCreateInfo, + DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, + PipelineShaderStageCreateInfo, + }, + render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, + sync::GpuFuture, + DeviceSize, NonZeroDeviceSize, +}; + +use super::utils::Allocators; + +const VERTICES_PER_QUAD: DeviceSize = 4; +const VERTEX_BUFFER_SIZE: DeviceSize = 1024 * 1024 * VERTICES_PER_QUAD; +const INDEX_BUFFER_SIZE: DeviceSize = 1024 * 1024 * 2; + +type VertexBuffer = Subbuffer<[egui::epaint::Vertex]>; +type IndexBuffer = Subbuffer<[u32]>; + +/// Should match vertex definition of egui +#[repr(C)] +#[derive(BufferContents, Vertex)] +pub struct EguiVertex { + #[format(R32G32_SFLOAT)] + pub position: [f32; 2], + #[format(R32G32_SFLOAT)] + pub tex_coords: [f32; 2], + #[format(R8G8B8A8_UNORM)] + pub color: [u8; 4], +} + +pub struct Renderer { + gfx_queue: Arc, + render_pass: Option>, + is_overlay: bool, + output_in_linear_colorspace: bool, + + #[allow(unused)] + format: vulkano::format::Format, + font_sampler: Arc, + // May be R8G8_UNORM or R8G8B8A8_SRGB + font_format: Format, + + allocators: Allocators, + vertex_index_buffer_pool: SubbufferAllocator, + pipeline: Arc, + subpass: Subpass, + + texture_desc_sets: AHashMap>, + texture_images: AHashMap>, + next_native_tex_id: u64, +} + +impl Renderer { + pub fn new_with_subpass( + gfx_queue: Arc, + final_output_format: Format, + subpass: Subpass, + ) -> Renderer { + Self::new_internal(gfx_queue, final_output_format, subpass, None, false) + } + + /// Creates a new [Renderer] which is responsible for rendering egui with its own renderpass + /// See examples + pub fn new_with_render_pass( + gfx_queue: Arc, + final_output_format: Format, + is_overlay: bool, + samples: SampleCount, + ) -> Renderer { + // Create Gui render pass with just depth and final color + let render_pass = if is_overlay { + vulkano::single_pass_renderpass!(gfx_queue.device().clone(), + attachments: { + final_color: { + format: final_output_format, + samples: samples, + load_op: Load, + store_op: Store, + } + }, + pass: { + color: [final_color], + depth_stencil: {} + } + ) + .unwrap() + } else { + vulkano::single_pass_renderpass!(gfx_queue.device().clone(), + attachments: { + final_color: { + format: final_output_format, + samples: samples, + load_op: Clear, + store_op: Store, + } + }, + pass: { + color: [final_color], + depth_stencil: {} + } + ) + .unwrap() + }; + let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); + Self::new_internal( + gfx_queue, + final_output_format, + subpass, + Some(render_pass), + is_overlay, + ) + } + + fn new_internal( + gfx_queue: Arc, + final_output_format: Format, + subpass: Subpass, + render_pass: Option>, + is_overlay: bool, + ) -> Renderer { + let output_in_linear_colorspace = + // final_output_format.type_color().unwrap() == NumericType::SRGB; + final_output_format.numeric_format_color().unwrap() == NumericFormat::SRGB; + let allocators = Allocators::new_default(gfx_queue.device()); + let vertex_index_buffer_pool = SubbufferAllocator::new( + allocators.memory.clone(), + SubbufferAllocatorCreateInfo { + arena_size: INDEX_BUFFER_SIZE + VERTEX_BUFFER_SIZE, + buffer_usage: BufferUsage::INDEX_BUFFER | BufferUsage::VERTEX_BUFFER, + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + ); + let pipeline = Self::create_pipeline(gfx_queue.clone(), subpass.clone()); + let font_sampler = Sampler::new( + gfx_queue.device().clone(), + SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + address_mode: [SamplerAddressMode::ClampToEdge; 3], + mipmap_mode: SamplerMipmapMode::Linear, + ..Default::default() + }, + ) + .unwrap(); + let font_format = Self::choose_font_format(gfx_queue.device()); + Renderer { + gfx_queue, + format: final_output_format, + render_pass, + vertex_index_buffer_pool, + pipeline, + subpass, + texture_desc_sets: AHashMap::default(), + texture_images: AHashMap::default(), + next_native_tex_id: 0, + is_overlay, + output_in_linear_colorspace, + font_sampler, + font_format, + allocators, + } + } + + pub fn has_renderpass(&self) -> bool { + self.render_pass.is_some() + } + + fn create_pipeline(gfx_queue: Arc, subpass: Subpass) -> Arc { + let vs = vs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + let fs = fs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + + let mut blend = AttachmentBlend::alpha(); + blend.src_color_blend_factor = BlendFactor::One; + blend.src_alpha_blend_factor = BlendFactor::OneMinusDstAlpha; + blend.dst_alpha_blend_factor = BlendFactor::One; + let blend_state = ColorBlendState { + attachments: vec![ColorBlendAttachmentState { + blend: Some(blend), + ..Default::default() + }], + ..ColorBlendState::default() + }; + + let vertex_input_state = Some( + EguiVertex::per_vertex() + .definition(&vs) + .unwrap(), + ); + + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + + let layout = PipelineLayout::new( + gfx_queue.device().clone(), + PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) + .into_pipeline_layout_create_info(gfx_queue.device().clone()) + .unwrap(), + ) + .unwrap(); + + GraphicsPipeline::new( + gfx_queue.device().clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state, + input_assembly_state: Some(InputAssemblyState::default()), + viewport_state: Some(ViewportState::default()), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState { + rasterization_samples: subpass.num_samples().unwrap_or(SampleCount::Sample1), + ..Default::default() + }), + color_blend_state: Some(blend_state), + dynamic_state: [DynamicState::Viewport, DynamicState::Scissor] + .into_iter() + .collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + ) + .unwrap() + } + + /// Creates a descriptor set for images + fn sampled_image_desc_set( + &self, + layout: &Arc, + image: Arc, + sampler: Arc, + ) -> Arc { + DescriptorSet::new( + self.allocators.descriptor_set.clone(), + layout.clone(), + [WriteDescriptorSet::image_view_sampler(0, image, sampler)], + [], + ) + .unwrap() + } + + /// Registers a user texture. User texture needs to be unregistered when it is no longer needed + pub fn register_image( + &mut self, + image: Arc, + sampler_create_info: SamplerCreateInfo, + ) -> egui::TextureId { + let layout = self.pipeline.layout().set_layouts().first().unwrap(); + let sampler = Sampler::new(self.gfx_queue.device().clone(), sampler_create_info).unwrap(); + let desc_set = self.sampled_image_desc_set(layout, image.clone(), sampler); + let id = egui::TextureId::User(self.next_native_tex_id); + self.next_native_tex_id += 1; + self.texture_desc_sets.insert(id, desc_set); + self.texture_images.insert(id, image); + id + } + + /// Unregister user texture. + pub fn unregister_image(&mut self, texture_id: egui::TextureId) { + self.texture_desc_sets.remove(&texture_id); + self.texture_images.remove(&texture_id); + } + /// Choose a font format, attempt to minimize memory footprint and CPU unpacking time + /// by choosing a swizzled linear format. + fn choose_font_format(device: &vulkano::device::Device) -> Format { + // Some portability subset devices are unable to swizzle views. + let supports_swizzle = !device + .physical_device() + .supported_extensions() + .khr_portability_subset + || device + .physical_device() + .supported_features() + .image_view_format_swizzle; + // Check that this format is supported for all our uses: + let is_supported = |device: &vulkano::device::Device, format: Format| { + device + .physical_device() + .image_format_properties(vulkano::image::ImageFormatInfo { + format, + usage: ImageUsage::SAMPLED + | ImageUsage::TRANSFER_DST + | ImageUsage::TRANSFER_SRC, + ..Default::default() + }) + // Ok(Some(..)) is supported format for this usage. + .is_ok_and(|properties| properties.is_some()) + }; + if supports_swizzle && is_supported(device, Format::R8G8_UNORM) { + // We can save mem by swizzling in hardware! + Format::R8G8_UNORM + } else { + // Rest of implementation assumes R8G8B8A8_SRGB anyway! + Format::R8G8B8A8_SRGB + } + } + /// Based on self.font_format, extract into bytes. + fn pack_font_data_into(&self, data: &egui::FontImage, into: &mut [u8]) { + match self.font_format { + Format::R8G8_UNORM => { + // Egui expects RGB to be linear in shader, but alpha to be *nonlinear.* + // Thus, we use R channel for linear coverage, G for the same coverage converted to nonlinear. + // Then gets swizzled up to RRRG to match expected values. + let linear = data + .pixels + .iter() + .map(|f| (f.clamp(0.0, 1.0 - f32::EPSILON) * 256.0) as u8); + let bytes = linear + .zip(data.srgba_pixels(None)) + .flat_map(|(linear, srgb)| [linear, srgb.a()]); + + into.iter_mut() + .zip(bytes) + .for_each(|(into, from)| *into = from); + } + Format::R8G8B8A8_SRGB => { + // No special tricks, pack them directly. + let bytes = data.srgba_pixels(None).flat_map(|color| color.to_array()); + into.iter_mut() + .zip(bytes) + .for_each(|(into, from)| *into = from); + } + // This is the exhaustive list of choosable font formats. + _ => unreachable!(), + } + } + fn image_size_bytes(&self, delta: &egui::epaint::ImageDelta) -> usize { + match &delta.image { + egui::ImageData::Color(c) => { + // Always four bytes per pixel for sRGBA + c.width() * c.height() * 4 + } + egui::ImageData::Font(f) => { + f.width() + * f.height() + * match self.font_format { + Format::R8G8_UNORM => 2, + Format::R8G8B8A8_SRGB => 4, + // Exhaustive list of valid font formats + _ => unreachable!(), + } + } + } + } + /// Write a single texture delta using the provided staging region and commandbuffer + fn update_texture_within( + &mut self, + id: egui::TextureId, + delta: &egui::epaint::ImageDelta, + stage: Subbuffer<[u8]>, + mapped_stage: &mut [u8], + cbb: &mut RecordingCommandBuffer, + ) { + // Extract pixel data from egui, writing into our region of the stage buffer. + let format = match &delta.image { + egui::ImageData::Color(image) => { + assert_eq!( + image.width() * image.height(), + image.pixels.len(), + "Mismatch between texture size and texel count" + ); + let bytes = image.pixels.iter().flat_map(|color| color.to_array()); + mapped_stage + .iter_mut() + .zip(bytes) + .for_each(|(into, from)| *into = from); + Format::R8G8B8A8_SRGB + } + egui::ImageData::Font(image) => { + // Dynamically pack based on chosen format + self.pack_font_data_into(image, mapped_stage); + self.font_format + } + }; + + // Copy texture data to existing image if delta pos exists (e.g. font changed) + if let Some(pos) = delta.pos { + let Some(existing_image) = self.texture_images.get(&id) else { + // Egui wants us to update this texture but we don't have it to begin with! + panic!("attempt to write into non-existing image"); + }; + // Make sure delta image type and destination image type match. + assert_eq!(existing_image.format(), format); + + // Defer upload of data + cbb.copy_buffer_to_image(CopyBufferToImageInfo { + regions: [BufferImageCopy { + // Buffer offsets are derived + image_offset: [pos[0] as u32, pos[1] as u32, 0], + image_extent: [delta.image.width() as u32, delta.image.height() as u32, 1], + // Always use the whole image (no arrays or mips are performed) + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::COLOR, + mip_level: 0, + array_layers: 0..1, + }, + ..Default::default() + }] + .into(), + ..CopyBufferToImageInfo::buffer_image(stage, existing_image.image().clone()) + }) + .unwrap(); + } else { + // Otherwise save the newly created image + let img = { + let extent = [delta.image.width() as u32, delta.image.height() as u32, 1]; + Image::new( + self.allocators.memory.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format, + extent, + usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, + initial_layout: ImageLayout::Undefined, + ..Default::default() + }, + AllocationCreateInfo::default(), + ) + .unwrap() + }; + // Defer upload of data + cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(stage, img.clone())) + .unwrap(); + // Swizzle packed font images up to a full premul white. + let component_mapping = match format { + Format::R8G8_UNORM => ComponentMapping { + r: ComponentSwizzle::Red, + g: ComponentSwizzle::Red, + b: ComponentSwizzle::Red, + a: ComponentSwizzle::Green, + }, + _ => ComponentMapping::identity(), + }; + let view = ImageView::new( + img.clone(), + ImageViewCreateInfo { + component_mapping, + ..ImageViewCreateInfo::from_image(&img) + }, + ) + .unwrap(); + // Create a descriptor for it + let layout = self.pipeline.layout().set_layouts().first().unwrap(); + let desc_set = + self.sampled_image_desc_set(layout, view.clone(), self.font_sampler.clone()); + // Save! + self.texture_desc_sets.insert(id, desc_set); + self.texture_images.insert(id, view); + }; + } + /// Write the entire texture delta for this frame. + fn update_textures(&mut self, sets: &[(egui::TextureId, egui::epaint::ImageDelta)]) { + // Allocate enough memory to upload every delta at once. + let total_size_bytes = sets + .iter() + .map(|(_, set)| self.image_size_bytes(set)) + .sum::() + * 4; + // Infallible - unless we're on a 128 bit machine? :P + let total_size_bytes = u64::try_from(total_size_bytes).unwrap(); + let Ok(total_size_bytes) = vulkano::NonZeroDeviceSize::try_from(total_size_bytes) else { + // Nothing to upload! + return; + }; + let buffer = Buffer::new( + self.allocators.memory.clone(), + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + // Bytes, align of one, infallible. + DeviceLayout::new(total_size_bytes, DeviceAlignment::MIN).unwrap(), + ) + .unwrap(); + let buffer = Subbuffer::new(buffer); + + // Shared command buffer for every upload in this batch. + // prim + let mut cbb = RecordingCommandBuffer::new( + self.allocators.command_buffer.clone(), + self.gfx_queue.queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); + + { + // Scoped to keep writer lock bounded + // Should be infallible - Just made the buffer so it's exclusive, and we have host access to it. + let mut writer = buffer.write().unwrap(); + + // Keep track of where to write the next image to into the staging buffer. + let mut past_buffer_end = 0usize; + + for (id, delta) in sets { + let image_size_bytes = self.image_size_bytes(delta); + let range = past_buffer_end..(image_size_bytes + past_buffer_end); + + // Bump for next loop + past_buffer_end += image_size_bytes; + + // Represents the same memory in two ways. Writable memmap, and gpu-side description. + let stage = buffer.clone().slice(range.start as u64..range.end as u64); + let mapped_stage = &mut writer[range]; + + self.update_texture_within(*id, delta, stage, mapped_stage, &mut cbb); + } + } + + // Execute every upload at once and await: + let command_buffer = cbb.end().unwrap(); + // Executing on the graphics queue not only since it's what we have, but + // we must guarantee a transfer granularity of [1,1,x] which graphics queue is required to have. + command_buffer + .execute(self.gfx_queue.clone()) + .unwrap() + .then_signal_fence_and_flush() + .unwrap() + .wait(None) + .unwrap(); + } + + fn get_rect_scissor( + &self, + scale_factor: f32, + framebuffer_dimensions: [u32; 2], + rect: Rect, + ) -> Scissor { + let min = rect.min; + let min = egui::Pos2 { + x: min.x * scale_factor, + y: min.y * scale_factor, + }; + let min = egui::Pos2 { + x: min.x.clamp(0.0, framebuffer_dimensions[0] as f32), + y: min.y.clamp(0.0, framebuffer_dimensions[1] as f32), + }; + let max = rect.max; + let max = egui::Pos2 { + x: max.x * scale_factor, + y: max.y * scale_factor, + }; + let max = egui::Pos2 { + x: max.x.clamp(min.x, framebuffer_dimensions[0] as f32), + y: max.y.clamp(min.y, framebuffer_dimensions[1] as f32), + }; + Scissor { + offset: [min.x.round() as u32, min.y.round() as u32], + extent: [ + (max.x.round() - min.x) as u32, + (max.y.round() - min.y) as u32, + ], + } + } + + fn create_secondary_command_buffer_builder(&self) -> RecordingCommandBuffer { + RecordingCommandBuffer::new( + self.allocators.command_buffer.clone(), + self.gfx_queue.queue_family_index(), + CommandBufferLevel::Secondary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::MultipleSubmit, + inheritance_info: Some(CommandBufferInheritanceInfo { + render_pass: Some(self.subpass.clone().into()), + ..Default::default() + }), + ..Default::default() + }, + ) + .unwrap() + } + + // Starts the rendering pipeline and returns [`RecordingCommandBuffer`] for drawing + fn start(&mut self, final_image: Arc) -> (RecordingCommandBuffer, [u32; 2]) { + // Get dimensions + let img_dims = final_image.image().extent(); + // Create framebuffer (must be in same order as render pass description in `new` + let framebuffer = Framebuffer::new( + self.render_pass + .as_ref() + .expect( + "No renderpass on this renderer (created with subpass), use 'draw_subpass' \ + instead", + ) + .clone(), + FramebufferCreateInfo { + attachments: vec![final_image], + ..Default::default() + }, + ) + .unwrap(); + //prim + let mut command_buffer_builder = RecordingCommandBuffer::new( + self.allocators.command_buffer.clone(), + self.gfx_queue.queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); + // Add clear values here for attachments and begin render pass + command_buffer_builder + .begin_render_pass( + RenderPassBeginInfo { + clear_values: vec![if !self.is_overlay { + Some([0.0; 4].into()) + } else { + None + }], + ..RenderPassBeginInfo::framebuffer(framebuffer) + }, + SubpassBeginInfo { + contents: SubpassContents::SecondaryCommandBuffers, + ..SubpassBeginInfo::default() + }, + ) + .unwrap(); + (command_buffer_builder, [img_dims[0], img_dims[1]]) + } + + /// Executes our draw commands on the final image and returns a `GpuFuture` to wait on + pub fn draw_on_image( + &mut self, + clipped_meshes: &[ClippedPrimitive], + textures_delta: &TexturesDelta, + scale_factor: f32, + before_future: F, + final_image: Arc, + ) -> Box + where + F: GpuFuture + 'static, + { + self.update_textures(&textures_delta.set); + + let (mut command_buffer_builder, framebuffer_dimensions) = self.start(final_image); + let mut builder = self.create_secondary_command_buffer_builder(); + self.draw_egui( + scale_factor, + clipped_meshes, + framebuffer_dimensions, + &mut builder, + ); + // Execute draw commands + let command_buffer = builder.end().unwrap(); + command_buffer_builder + .execute_commands(command_buffer) + .unwrap(); + let done_future = self.finish(command_buffer_builder, Box::new(before_future)); + + for &id in &textures_delta.free { + self.unregister_image(id); + } + + done_future + } + + // Finishes the rendering pipeline + fn finish( + &self, + mut command_buffer_builder: RecordingCommandBuffer, + before_main_cb_future: Box, + ) -> Box { + // We end render pass + command_buffer_builder + .end_render_pass(Default::default()) + .unwrap(); + // Then execute our whole command buffer + let command_buffer = command_buffer_builder.end().unwrap(); + let after_main_cb = before_main_cb_future + .then_execute(self.gfx_queue.clone(), command_buffer) + .unwrap(); + // Return our future + Box::new(after_main_cb) + } + + pub fn draw_on_subpass_image( + &mut self, + clipped_meshes: &[ClippedPrimitive], + textures_delta: &TexturesDelta, + scale_factor: f32, + framebuffer_dimensions: [u32; 2], + ) -> Arc { + self.update_textures(&textures_delta.set); + let mut builder = self.create_secondary_command_buffer_builder(); + self.draw_egui( + scale_factor, + clipped_meshes, + framebuffer_dimensions, + &mut builder, + ); + let buffer = builder.end().unwrap(); + for &id in &textures_delta.free { + self.unregister_image(id); + } + buffer + } + /// Uploads all meshes in bulk. They will be available in the same order, packed. + /// None if no vertices or no indices. + fn upload_meshes( + &mut self, + clipped_meshes: &[ClippedPrimitive], + ) -> Option<(VertexBuffer, IndexBuffer)> { + use egui::epaint::Vertex; + type Index = u32; + const VERTEX_ALIGN: DeviceAlignment = DeviceAlignment::of::(); + const INDEX_ALIGN: DeviceAlignment = DeviceAlignment::of::(); + + // Iterator over only the meshes, no user callbacks. + let meshes = clipped_meshes + .iter() + .filter_map(|mesh| match &mesh.primitive { + Primitive::Mesh(m) => Some(m), + _ => None, + }); + + // Calculate counts of each mesh, and total bytes for combined data + let (total_vertices, total_size_bytes) = { + let mut total_vertices = 0; + let mut total_indices = 0; + + for mesh in meshes.clone() { + total_vertices += mesh.vertices.len(); + total_indices += mesh.indices.len(); + } + if total_indices == 0 || total_vertices == 0 { + return None; + } + + let total_size_bytes = total_vertices * std::mem::size_of::() + + total_indices * std::mem::size_of::(); + ( + total_vertices, + // Infallible! Checked above. + NonZeroDeviceSize::new(u64::try_from(total_size_bytes).unwrap()).unwrap(), + ) + }; + + // Allocate a buffer which can hold both packed arrays: + let layout = DeviceLayout::new(total_size_bytes, VERTEX_ALIGN.max(INDEX_ALIGN)).unwrap(); + let buffer = self.vertex_index_buffer_pool.allocate(layout).unwrap(); + + // We must put the items with stricter align *first* in the packed buffer. + // Correct at time of writing, but assert in case that changes. + assert!(VERTEX_ALIGN >= INDEX_ALIGN); + let (vertices, indices) = { + let partition_bytes = total_vertices as u64 * std::mem::size_of::() as u64; + ( + // Slice the start as vertices + buffer + .clone() + .slice(..partition_bytes) + .reinterpret::<[Vertex]>(), + // Take the rest, reinterpret as indices. + buffer.slice(partition_bytes..).reinterpret::<[Index]>(), + ) + }; + + // We have to upload in two mapping steps to avoid trivial but ugly unsafe. + { + let mut vertex_write = vertices.write().unwrap(); + vertex_write + .iter_mut() + .zip(meshes.clone().flat_map(|m| &m.vertices).copied()) + .for_each(|(into, from)| *into = from); + } + { + let mut index_write = indices.write().unwrap(); + index_write + .iter_mut() + .zip(meshes.flat_map(|m| &m.indices).copied()) + .for_each(|(into, from)| *into = from); + } + + Some((vertices, indices)) + } + + fn draw_egui( + &mut self, + scale_factor: f32, + clipped_meshes: &[ClippedPrimitive], + framebuffer_dimensions: [u32; 2], + builder: &mut RecordingCommandBuffer, + ) { + let push_constants = vs::PushConstants { + screen_size: [ + framebuffer_dimensions[0] as f32 / scale_factor, + framebuffer_dimensions[1] as f32 / scale_factor, + ], + output_in_linear_colorspace: self.output_in_linear_colorspace.into(), + }; + + let mesh_buffers = self.upload_meshes(clipped_meshes); + + // Current position of renderbuffers, advances as meshes are consumed. + let mut vertex_cursor = 0; + let mut index_cursor = 0; + // Some of our state is immutable and only changes + // if a user callback thrashes it, rebind all when this is set: + let mut needs_full_rebind = true; + // Track resources that change from call-to-call. + // egui already makes the optimization that draws with identical resources are merged into one, + // so every mesh changes usually one or possibly both of these. + let mut current_rect = None; + let mut current_texture = None; + + for ClippedPrimitive { + clip_rect, + primitive, + } in clipped_meshes + { + match primitive { + Primitive::Mesh(mesh) => { + // Nothing to draw if we don't have vertices & indices + if mesh.vertices.is_empty() || mesh.indices.is_empty() { + // Consume the mesh and skip it. + index_cursor += mesh.indices.len() as u32; + vertex_cursor += mesh.vertices.len() as u32; + continue; + } + // Reset overall state, if needed. + // Only happens on first mesh, and after a user callback which does unknowable + // things to the command buffer's state. + if needs_full_rebind { + needs_full_rebind = false; + + // Bind combined meshes. + let Some((vertices, indices)) = mesh_buffers.clone() else { + // Only None if there are no mesh calls, but here we are in a mesh call! + unreachable!() + }; + + builder + .bind_pipeline_graphics(self.pipeline.clone()) + .unwrap() + .bind_index_buffer(indices) + .unwrap() + .bind_vertex_buffers(0, [vertices]) + .unwrap() + .set_viewport( + 0, + [Viewport { + offset: [0.0, 0.0], + extent: [ + framebuffer_dimensions[0] as f32, + framebuffer_dimensions[1] as f32, + ], + depth_range: 0.0..=1.0, + }] + .into_iter() + .collect(), + ) + .unwrap() + .push_constants(self.pipeline.layout().clone(), 0, push_constants) + .unwrap(); + } + // Find and bind image, if different. + if current_texture != Some(mesh.texture_id) { + if self.texture_desc_sets.get(&mesh.texture_id).is_none() { + eprintln!("This texture no longer exists {:?}", mesh.texture_id); + continue; + } + current_texture = Some(mesh.texture_id); + + let desc_set = self.texture_desc_sets.get(&mesh.texture_id).unwrap(); + + builder + .bind_descriptor_sets( + PipelineBindPoint::Graphics, + self.pipeline.layout().clone(), + 0, + desc_set.clone(), + ) + .unwrap(); + }; + // Calculate and set scissor, if different + if current_rect != Some(*clip_rect) { + current_rect = Some(*clip_rect); + let new_scissor = + self.get_rect_scissor(scale_factor, framebuffer_dimensions, *clip_rect); + + builder + .set_scissor(0, [new_scissor].into_iter().collect()) + .unwrap(); + } + + // All set up to draw! + unsafe { + builder + .draw_indexed( + mesh.indices.len() as u32, + 1, + index_cursor, + vertex_cursor as i32, + 0, + ) + .unwrap(); + } + + // Consume this mesh for next iteration + index_cursor += mesh.indices.len() as u32; + vertex_cursor += mesh.vertices.len() as u32; + } + Primitive::Callback(callback) => { + if callback.rect.is_positive() { + let Some(callback_fn) = callback.callback.downcast_ref::() + else { + println!( + "Warning: Unsupported render callback. Expected \ + egui_winit_vulkano::CallbackFn" + ); + continue; + }; + + let rect_min_x = scale_factor * callback.rect.min.x; + let rect_min_y = scale_factor * callback.rect.min.y; + let rect_max_x = scale_factor * callback.rect.max.x; + let rect_max_y = scale_factor * callback.rect.max.y; + + let rect_min_x = rect_min_x.round(); + let rect_min_y = rect_min_y.round(); + let rect_max_x = rect_max_x.round(); + let rect_max_y = rect_max_y.round(); + + builder + .set_viewport( + 0, + [Viewport { + offset: [rect_min_x, rect_min_y], + extent: [rect_max_x - rect_min_x, rect_max_y - rect_min_y], + depth_range: 0.0..=1.0, + }] + .into_iter() + .collect(), + ) + .unwrap() + .set_scissor( + 0, + [self.get_rect_scissor( + scale_factor, + framebuffer_dimensions, + *clip_rect, + )] + .into_iter() + .collect(), + ) + .unwrap(); + + let info = egui::PaintCallbackInfo { + viewport: callback.rect, + clip_rect: *clip_rect, + pixels_per_point: scale_factor, + screen_size_px: framebuffer_dimensions, + }; + (callback_fn.f)( + info, + &mut CallbackContext { + builder, + resources: self.render_resources(), + }, + ); + + // The user could have done much here - rebind pipes, set views, bind things, etc. + // Mark all state as lost so that next mesh rebinds everything to a known state. + needs_full_rebind = true; + current_rect = None; + current_texture = None; + } + } + } + } + } + + pub fn render_resources(&self) -> RenderResources { + RenderResources { + queue: self.queue(), + subpass: self.subpass.clone(), + memory_allocator: self.allocators.memory.clone(), + descriptor_set_allocator: &self.allocators.descriptor_set, + command_buffer_allocator: &self.allocators.command_buffer, + } + } + + pub fn queue(&self) -> Arc { + self.gfx_queue.clone() + } + + pub fn allocators(&self) -> &Allocators { + &self.allocators + } +} + +/// A set of objects used to perform custom rendering in a `PaintCallback`. It +/// includes [`RenderResources`] for constructing a subpass pipeline and a secondary +/// command buffer for pushing render commands onto it. +/// +/// # Example +/// +/// See the `triangle` demo source for a detailed usage example. +pub struct CallbackContext<'a> { + pub builder: &'a mut RecordingCommandBuffer, + pub resources: RenderResources<'a>, +} + +/// A set of resources used to construct the render pipeline. These can be reused +/// to create additional pipelines and buffers to be rendered in a `PaintCallback`. +/// +/// # Example +/// +/// See the `triangle` demo source for a detailed usage example. +#[derive(Clone)] +pub struct RenderResources<'a> { + pub memory_allocator: Arc, + pub descriptor_set_allocator: &'a StandardDescriptorSetAllocator, + pub command_buffer_allocator: &'a StandardCommandBufferAllocator, + pub queue: Arc, + pub subpass: Subpass, +} + +pub type CallbackFnDef = dyn Fn(PaintCallbackInfo, &mut CallbackContext) + Sync + Send; + +/// A callback function that can be used to compose an [`epaint::PaintCallback`] for +/// custom rendering with [`vulkano`]. +/// +/// The callback is passed an [`egui::PaintCallbackInfo`] and a [`CallbackContext`] which +/// can be used to construct Vulkano graphics pipelines and buffers. +/// +/// # Example +/// +/// See the `triangle` demo source for a detailed usage example. +pub struct CallbackFn { + pub(crate) f: Box, +} + +#[allow(dead_code)] +impl CallbackFn { + pub fn new( + callback: F, + ) -> Self { + let f = Box::new(callback); + CallbackFn { f } + } +} + +mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: " +#version 450 + +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 tex_coords; +layout(location = 2) in vec4 color; + +layout(location = 0) out vec4 v_color; +layout(location = 1) out vec2 v_tex_coords; + +layout(push_constant) uniform PushConstants { + vec2 screen_size; + int output_in_linear_colorspace; +} push_constants; + +void main() { + gl_Position = vec4( + 2.0 * position.x / push_constants.screen_size.x - 1.0, + 2.0 * position.y / push_constants.screen_size.y - 1.0, + 0.0, 1.0 + ); + v_color = color; + v_tex_coords = tex_coords; +}" + } +} + +// Similar to https://github.com/ArjunNair/egui_sdl2_gl/blob/main/src/painter.rs +mod fs { + vulkano_shaders::shader! { + ty: "fragment", + src: " +#version 450 + +layout(location = 0) in vec4 v_color; +layout(location = 1) in vec2 v_tex_coords; + +layout(location = 0) out vec4 f_color; + +layout(binding = 0, set = 0) uniform sampler2D font_texture; + +layout(push_constant) uniform PushConstants { + vec2 screen_size; + int output_in_linear_colorspace; +} push_constants; + +// 0-1 sRGB from 0-1 linear +vec3 srgb_from_linear(vec3 linear) { + bvec3 cutoff = lessThan(linear, vec3(0.0031308)); + vec3 lower = linear * vec3(12.92); + vec3 higher = vec3(1.055) * pow(linear, vec3(1./2.4)) - vec3(0.055); + return mix(higher, lower, vec3(cutoff)); +} + +// 0-1 sRGBA from 0-1 linear +vec4 srgba_from_linear(vec4 linear) { + return vec4(srgb_from_linear(linear.rgb), linear.a); +} + +// 0-1 linear from 0-1 sRGB +vec3 linear_from_srgb(vec3 srgb) { + bvec3 cutoff = lessThan(srgb, vec3(0.04045)); + vec3 lower = srgb / vec3(12.92); + vec3 higher = pow((srgb + vec3(0.055) / vec3(1.055)), vec3(2.4)); + return mix(higher, lower, vec3(cutoff)); +} + +// 0-1 linear from 0-1 sRGB +vec4 linear_from_srgba(vec4 srgb) { + return vec4(linear_from_srgb(srgb.rgb), srgb.a); +} + +void main() { + // ALL calculations should be done in gamma space, this includes texture * color and blending + vec4 texture_color = srgba_from_linear(texture(font_texture, v_tex_coords)); + vec4 color = v_color * texture_color; + + // If output_in_linear_colorspace is true, we are rendering into an sRGB image, for which we'll convert to linear color space. + // **This will break blending** as it will be performed in linear color space instead of sRGB like egui expects. + if (push_constants.output_in_linear_colorspace == 1) { + color = linear_from_srgba(color); + } + f_color = color; +}" + } +} diff --git a/src/modules/graphics/egui/utils.rs b/src/modules/graphics/egui/utils.rs new file mode 100644 index 0000000..2436580 --- /dev/null +++ b/src/modules/graphics/egui/utils.rs @@ -0,0 +1,144 @@ +// Copyright (c) 2021 Okko Hakola, 2024 Klink +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::sync::Arc; + +use image::RgbaImage; +use vulkano::{ + buffer::{AllocateBufferError, Buffer, BufferCreateInfo, BufferUsage}, + command_buffer::{ + allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, CopyBufferToImageInfo, RecordingCommandBuffer + }, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + image::{view::ImageView, AllocateImageError, Image, ImageCreateInfo, ImageType, ImageUsage}, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + Validated, ValidationError, VulkanError, +}; + +#[derive(Debug)] +pub enum ImageCreationError { + Vulkan(Validated), + AllocateImage(Validated), + AllocateBuffer(Validated), + Validation(Box), +} + +pub fn immutable_texture_from_bytes( + allocators: &Allocators, + queue: Arc, + byte_data: &[u8], + dimensions: [u32; 2], + format: vulkano::format::Format, +) -> Result, ImageCreationError> { + let mut cbb = RecordingCommandBuffer::new( + allocators.command_buffer.clone(), + queue.queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + } + ) + .map_err(ImageCreationError::Vulkan)?; + + let texture_data_buffer = Buffer::from_iter( + allocators.memory.clone(), + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_HOST + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + byte_data.iter().cloned(), + ) + .map_err(ImageCreationError::AllocateBuffer)?; + + let texture = Image::new( + allocators.memory.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format, + extent: [dimensions[0], dimensions[1], 1], + usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, + ..Default::default() + }, + AllocationCreateInfo::default(), + ) + .map_err(ImageCreationError::AllocateImage)?; + + cbb.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image( + texture_data_buffer, + texture.clone(), + )) + .map_err(ImageCreationError::Validation)?; + + let _fut = cbb.end().unwrap().execute(queue).unwrap(); + + Ok(ImageView::new_default(texture).unwrap()) +} + +pub fn immutable_texture_from_file( + allocators: &Allocators, + queue: Arc, + file_bytes: &[u8], + format: vulkano::format::Format, +) -> Result, ImageCreationError> { + use image::GenericImageView; + + let img = image::load_from_memory(file_bytes).expect("Failed to load image from bytes"); + let rgba = if let Some(rgba) = img.as_rgba8() { + rgba.to_owned().to_vec() + } else { + // Convert rgb to rgba + let rgb = img.as_rgb8().unwrap().to_owned(); + let mut raw_data = vec![]; + for val in rgb.chunks(3) { + raw_data.push(val[0]); + raw_data.push(val[1]); + raw_data.push(val[2]); + raw_data.push(255); + } + let new_rgba = RgbaImage::from_raw(rgb.width(), rgb.height(), raw_data).unwrap(); + new_rgba.to_vec() + }; + let dimensions = img.dimensions(); + immutable_texture_from_bytes( + allocators, + queue, + &rgba, + [dimensions.0, dimensions.1], + format, + ) +} + +pub struct Allocators { + pub memory: Arc, + pub descriptor_set: Arc, + pub command_buffer: Arc, +} + +impl Allocators { + pub fn new_default(device: &Arc) -> Self { + Self { + memory: Arc::new(StandardMemoryAllocator::new_default(device.clone())), + descriptor_set: Arc::new(StandardDescriptorSetAllocator::new(device.clone(), Default::default())), + command_buffer: Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + StandardCommandBufferAllocatorCreateInfo { + secondary_buffer_count: 32, + ..Default::default() + }, + )), + } + } +} diff --git a/src/modules/graphics/mod.rs b/src/modules/graphics/mod.rs index bd8eef2..2cdcca9 100644 --- a/src/modules/graphics/mod.rs +++ b/src/modules/graphics/mod.rs @@ -1,38 +1,91 @@ +use std::{collections::HashMap, sync::Arc}; + use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World}; +use vulkano::{ + command_buffer::{ + allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}, + CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer, + RenderingAttachmentInfo, RenderingInfo, + }, + image::view::ImageView, + pipeline::graphics::viewport::Viewport, + render_pass::{AttachmentLoadOp, AttachmentStoreOp}, + sync::GpuFuture, +}; +use vulkano_util::{ + context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows, +}; +use winit::window::WindowId; -use crate::core::module::Module; +use crate::core::module::RenderModule as ThreadLocalModule; +use self::{egui::Gui, test_pipeline::test_pipeline}; + +pub mod egui; pub mod events; +mod test_pipeline; pub struct RenderModule { + schedule: Schedule, + command_buffer_allocator: Arc, + viewport: Viewport, } impl RenderModule { pub fn new( - schedule: &mut Schedule, + vk_context: &mut VulkanoContext, + _vk_windows: &mut VulkanoWindows, + _schedule: &mut Schedule, _world: &mut World, _events: &mut crate::core::events::Events, ) -> Self { - let schedule_r = Schedule::builder() + let schedule = Schedule::builder() .with_system(add_distance_system()) .build(); - schedule.append(schedule_r); - Self { + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + vk_context.device().clone(), + Default::default(), + )); + + let viewport = Viewport { + offset: [0.0, 0.0], + extent: [0.0, 0.0], + depth_range: 0.0..=1.0, + }; + + Self { + schedule, + command_buffer_allocator, + viewport, } } } -impl Module for RenderModule { +impl ThreadLocalModule for RenderModule { fn on_update( &mut self, - _world: &mut World, + guis: &mut HashMap, + vk_context: &mut VulkanoContext, + vk_windows: &mut vulkano_util::window::VulkanoWindows, + world: &mut World, _events: &mut crate::core::events::Events, _frame_time: std::time::Duration, ) -> anyhow::Result<()> { - // println!("RenderModule on_update"); + self.schedule.execute_seq(world).unwrap(); - + let viewport = &mut self.viewport; + + for (window_id, renderer) in vk_windows.iter_mut() { + let gui = guis.get_mut(window_id).unwrap(); + draw( + self.command_buffer_allocator.clone(), + viewport, + vk_context, + renderer, + gui, + ); + } Ok(()) } } @@ -44,8 +97,191 @@ pub fn add_distance_system() -> BoxedSystem { .with_query(query) .build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| { for _id in &mut query { - // println!("entity id: {}", id.index()); + // println!("----------: {}", _id.index()); } }) .boxed() } + +fn draw( + command_buffer_allocator: Arc, + viewport: &mut Viewport, + context: &mut VulkanoContext, + renderer: &mut VulkanoWindowRenderer, + gui: &mut Gui, +) { + let (vertex_buffer, pipeline) = test_pipeline( + context.device().clone(), + context.memory_allocator().clone(), + renderer.swapchain_format(), + ); + + // Do not draw the frame when the screen size is zero. On Windows, this can + // occur when minimizing the application. + let image_extent: [u32; 2] = renderer.window().inner_size().into(); + + if image_extent.contains(&0) { + return; + } + + // Begin rendering by acquiring the gpu future from the window renderer. + let previous_frame_end = renderer + .acquire(|swapchain_images| { + // Whenever the window resizes we need to recreate everything dependent + // on the window size. In this example that + // includes the swapchain, the framebuffers + // and the dynamic state viewport. + window_size_dependent_setup(swapchain_images, viewport); + }) + .unwrap(); + + let mut builder = RecordingCommandBuffer::new( + command_buffer_allocator.clone(), + context.graphics_queue().queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); + + builder + // Before we can draw, we have to *enter a render pass*. We specify which + // attachments we are going to use for rendering here, which needs to match + // what was previously specified when creating the pipeline. + .begin_rendering(RenderingInfo { + // As before, we specify one color attachment, but now we specify the image + // view to use as well as how it should be used. + color_attachments: vec![Some(RenderingAttachmentInfo { + // `Clear` means that we ask the GPU to clear the content of this + // attachment at the start of rendering. + load_op: AttachmentLoadOp::Clear, + // `Store` means that we ask the GPU to store the rendered output in + // the attachment image. We could also ask it to discard the result. + store_op: AttachmentStoreOp::Store, + // The value to clear the attachment with. Here we clear it with a blue + // color. + // + // Only attachments that have `AttachmentLoadOp::Clear` are provided + // with clear values, any others should use `None` as the clear value. + clear_value: Some([0.0, 0.0, 1.0, 1.0].into()), + ..RenderingAttachmentInfo::image_view( + // We specify image view corresponding to the currently acquired + // swapchain image, to use for this attachment. + // attachment_image_views[image_index as usize].clone(), + renderer.swapchain_image_view().clone(), + ) + })], + ..Default::default() + }) + .unwrap() + // We are now inside the first subpass of the render pass. + // + // TODO: Document state setting and how it affects subsequent draw commands. + .set_viewport(0, [viewport.clone()].into_iter().collect()) + .unwrap() + .bind_pipeline_graphics(pipeline.clone()) + .unwrap() + .bind_vertex_buffers(0, vertex_buffer.clone()) + .unwrap(); + + unsafe { + builder + // We add a draw command. + .draw(vertex_buffer.len() as u32, 1, 0, 0) + .unwrap(); + } + + builder + // We leave the render pass. + .end_rendering() + .unwrap(); + + // Finish recording the command buffer by calling `end`. + let command_buffer = builder.end().unwrap(); + + draw_gui(gui); + + let before_future = previous_frame_end + .then_execute(context.graphics_queue().clone(), command_buffer) + .unwrap() + .boxed(); + + let after_future = gui + .draw_on_image(before_future, renderer.swapchain_image_view()) + .boxed(); + + // The color output is now expected to contain our triangle. But in order to + // show it on the screen, we have to *present* the image by calling + // `present` on the window renderer. + // + // This function does not actually present the image immediately. Instead it + // submits a present command at the end of the queue. This means that it will + // only be presented once the GPU has finished executing the command buffer + // that draws the triangle. + renderer.present(after_future, true); +} + +fn draw_gui(gui: &mut Gui) { + let mut code = CODE.to_owned(); + gui.immediate_ui(|gui| { + let ctx = gui.context(); + egui::egui::Window::new("Colors") + .vscroll(true) + .show(&ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add(egui::egui::widgets::Label::new("Hi there!")); + sized_text(ui, "Rich Text", 32.0); + }); + ui.separator(); + ui.columns(2, |columns| { + egui::egui::ScrollArea::vertical().id_source("source").show( + &mut columns[0], + |ui| { + ui.add( + egui::egui::TextEdit::multiline(&mut code) + .font(egui::egui::TextStyle::Monospace), + ); + }, + ); + egui::egui::ScrollArea::vertical() + .id_source("rendered") + .show(&mut columns[1], |ui| { + ui.add(egui::egui::widgets::Label::new("Good day!")); + }); + }); + }); + }); +} + +fn sized_text(ui: &mut egui::egui::Ui, text: impl Into, size: f32) { + ui.label( + egui::egui::RichText::new(text) + .size(size) + .family(::egui::FontFamily::Monospace), + ); +} + +const CODE: &str = r" +# Some markup +``` +let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1); +``` +"; + +fn window_size_dependent_setup( + image_views: &[Arc], + viewport: &mut Viewport, +) -> Vec> { + let extent = image_views[0].image().extent(); + viewport.extent = [extent[0] as f32, extent[1] as f32]; + + image_views + .iter() + .map(|image_view| { + let image = image_view.image().clone(); + ImageView::new_default(image).unwrap() + }) + .collect::>() +} diff --git a/src/modules/graphics/test_pipeline.rs b/src/modules/graphics/test_pipeline.rs new file mode 100644 index 0000000..d710779 --- /dev/null +++ b/src/modules/graphics/test_pipeline.rs @@ -0,0 +1,190 @@ +use std::sync::Arc; + +use vulkano::{ + buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, + device::Device, + format::Format, + memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, + pipeline::{ + graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, + subpass::PipelineRenderingCreateInfo, + vertex_input::{Vertex, VertexDefinition}, + viewport::ViewportState, + GraphicsPipelineCreateInfo, + }, + layout::PipelineDescriptorSetLayoutCreateInfo, + DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, + }, +}; + +pub fn test_pipeline( + device: Arc, + memory_allocator: Arc, + image_format: Format, +) -> (Subbuffer<[MyVertex]>, Arc) { + let vertices = [ + MyVertex { + position: [-0.5, -0.25], + }, + MyVertex { + position: [0.0, 0.5], + }, + MyVertex { + position: [0.25, -0.1], + }, + ]; + let vertex_buffer = Buffer::from_iter( + memory_allocator, + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + vertices, + ) + .unwrap(); + + + + let pipeline = { + // First, we load the shaders that the pipeline will use: + // the vertex shader and the fragment shader. + // + // A Vulkan shader can in theory contain multiple entry points, so we have to specify which + // one. + let vs = vs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let fs = fs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + // Automatically generate a vertex input state from the vertex shader's input interface, + // that takes a single vertex buffer containing `Vertex` structs. + let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); + + // Make a list of the shader stages that the pipeline will have. + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + + // We must now create a **pipeline layout** object, which describes the locations and types + // of descriptor sets and push constants used by the shaders in the pipeline. + // + // Multiple pipelines can share a common layout object, which is more efficient. + // The shaders in a pipeline must use a subset of the resources described in its pipeline + // layout, but the pipeline layout is allowed to contain resources that are not present in + // the shaders; they can be used by shaders in other pipelines that share the same + // layout. Thus, it is a good idea to design shaders so that many pipelines have + // common resource locations, which allows them to share pipeline layouts. + let layout = PipelineLayout::new( + device.clone(), + // Since we only have one pipeline in this example, and thus one pipeline layout, + // we automatically generate the creation info for it from the resources used in the + // shaders. In a real application, you would specify this information manually so that + // you can re-use one layout in multiple pipelines. + PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) + .into_pipeline_layout_create_info(device.clone()) + .unwrap(), + ) + .unwrap(); + + // We describe the formats of attachment images where the colors, depth and/or stencil + // information will be written. The pipeline will only be usable with this particular + // configuration of the attachment images. + let subpass = PipelineRenderingCreateInfo { + // We specify a single color attachment that will be rendered to. When we begin + // rendering, we will specify a swapchain image to be used as this attachment, so here + // we set its format to be the same format as the swapchain. + color_attachment_formats: vec![Some(image_format)], + ..Default::default() + }; + + // Finally, create the pipeline. + GraphicsPipeline::new( + device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + // How vertex data is read from the vertex buffers into the vertex shader. + vertex_input_state: Some(vertex_input_state), + // How vertices are arranged into primitive shapes. + // The default primitive shape is a triangle. + input_assembly_state: Some(InputAssemblyState::default()), + // How primitives are transformed and clipped to fit the framebuffer. + // We use a resizable viewport, set to draw over the entire window. + viewport_state: Some(ViewportState::default()), + // How polygons are culled and converted into a raster of pixels. + // The default value does not perform any culling. + rasterization_state: Some(RasterizationState::default()), + // How multiple fragment shader samples are converted to a single pixel value. + // The default value does not perform any multisampling. + multisample_state: Some(MultisampleState::default()), + // How pixel values are combined with the values already present in the framebuffer. + // The default value overwrites the old value with the new one, without any + // blending. + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.color_attachment_formats.len() as u32, + ColorBlendAttachmentState::default(), + )), + // Dynamic states allows us to specify parts of the pipeline settings when + // recording the command buffer, before we perform drawing. + // Here, we specify that the viewport should be dynamic. + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + ) + .unwrap() + }; + + (vertex_buffer, pipeline) +} + +#[derive(BufferContents, Vertex)] +#[repr(C)] +pub struct MyVertex { + #[format(R32G32_SFLOAT)] + position: [f32; 2], +} + +mod vs { + vulkano_shaders::shader! { + ty: "vertex", + src: r" + #version 450 + + layout(location = 0) in vec2 position; + + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", + } +} + +mod fs { + vulkano_shaders::shader! { + ty: "fragment", + src: r" + #version 450 + + layout(location = 0) out vec4 f_color; + + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index aa88809..5b071f9 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,2 +1,4 @@ pub mod config; pub mod graphics; +pub mod window; +// pub mod steel; diff --git a/src/modules/steel/mod.rs b/src/modules/steel/mod.rs new file mode 100644 index 0000000..55fbbd9 --- /dev/null +++ b/src/modules/steel/mod.rs @@ -0,0 +1,104 @@ +use std::sync::Arc; + +use flax::{component, BoxedSystem, EntityBorrow, Query, QueryBorrow, Schedule, System, World}; +use steel::steel_vm::engine::Engine; +use steel::steel_vm::register_fn::RegisterFn; +use steel_derive::Steel; + +use crate::core::module::Module; + +component! { + steel_script: String, + steel_event_tx: flume::Sender, + + resources, +} + +pub fn execute_script_system() -> BoxedSystem { + let tx_query = Query::new(steel_event_tx()).entity(resources()); + let script_query = Query::new(steel_script()); + + System::builder() + .with_query(tx_query) + .with_query(script_query) + .build(|mut tx_query: EntityBorrow<'_, flax::Component>>, mut script_query: QueryBorrow>| { + if let Ok(tx) = tx_query.get() { + for script in &mut script_query { + println!("Got script and tx"); + tx.send(SteelEvent::Execute(script.into())).unwrap(); + } + } + }) + .boxed() +} + +#[derive(Debug, Steel)] +enum SteelEvent { + Execute(String), +} + +#[allow(dead_code)] +#[derive(Steel, Clone)] +pub struct SteelModule { + engine: Engine, + // schedule: Schedule, + rx: flume::Receiver, +} + +impl SteelModule { + pub fn new( + schedule: &mut Schedule, + world: &mut World, + _events: &mut crate::core::events::Events, + ) -> Self { + let mut engine = Engine::new(); + + let (tx, rx) = flume::unbounded::(); + + let schedule_r = Schedule::builder() + .with_system(execute_script_system()) + .build(); + schedule.append(schedule_r); + + world.set(resources(), steel_event_tx(), tx).unwrap(); + + // Some testing + let entity = world.spawn(); + world.set(entity, steel_script(), r#" +(require-builtin steel/time) +(display "Hello ") +(time/sleep-ms 5000) +(display "World!")"#.into()).unwrap(); + + Self { + engine, + // schedule, + rx, + } + } +} + +impl Module for SteelModule { + fn on_update( + &mut self, + world: &mut World, + _events: &mut crate::core::events::Events, + _frame_time: std::time::Duration, + ) -> anyhow::Result<()> { + // self.schedule.execute_par(world).unwrap(); + + if let Ok(event) = self.rx.recv() { + match event { + SteelEvent::Execute(script) => { + let handle = std::thread::spawn(|| { + let mut engine = Engine::new(); + let val = engine.run(script).unwrap(); + println!("Steel val: {:?}", val); + }); + } + } + } + + Ok(()) + } +} diff --git a/src/modules/window/mod.rs b/src/modules/window/mod.rs new file mode 100644 index 0000000..0d5aded --- /dev/null +++ b/src/modules/window/mod.rs @@ -0,0 +1,34 @@ +use flax::{Schedule, World}; + +use crate::core::module::Module; + +pub struct WindowModule { +} + +impl WindowModule { + pub fn new( + schedule: &mut Schedule, + _world: &mut World, + _events: &mut crate::core::events::Events, + ) -> Self { + let schedule_r = Schedule::builder() + .build(); + schedule.append(schedule_r); + Self { + + } + } +} + +impl Module for WindowModule { + fn on_update( + &mut self, + _world: &mut World, + _events: &mut crate::core::events::Events, + _frame_time: std::time::Duration, + ) -> anyhow::Result<()> { + // println!("WindowModule on_update"); + + Ok(()) + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs deleted file mode 100644 index c590599..0000000 --- a/src/render/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use vulkano::device::DeviceFeatures; -use vulkano_util::context::{VulkanoConfig, VulkanoContext}; - -pub fn make_render_config() -> VulkanoConfig { - let device_features: DeviceFeatures = DeviceFeatures { - dynamic_rendering: true, - ..DeviceFeatures::empty() - }; - - VulkanoConfig { - device_features, - print_device_name: true, - ..Default::default() - } -} - -pub fn make_render_context() -> VulkanoContext { - VulkanoContext::new(make_render_config()) -}