diff --git a/Cargo.lock b/Cargo.lock index 865d71a..85a0b93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ "bitflags 2.6.0", "cc", "cesu8", - "jni 0.21.1", + "jni", "jni-sys", "libc", "log", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -229,7 +229,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.4", + "libloading", ] [[package]] @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", @@ -367,14 +367,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ "async-io", "async-lock", @@ -402,7 +402,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -456,7 +456,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -468,7 +468,7 @@ dependencies = [ "autocxx-engine", "env_logger", "indexmap 1.9.3", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -494,7 +494,7 @@ dependencies = [ "regex", "rustversion", "serde_json", - "syn 2.0.70", + "syn 2.0.72", "tempfile", "thiserror", "version_check", @@ -510,7 +510,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -527,7 +527,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.70", + "syn 2.0.72", "thiserror", ] @@ -552,6 +552,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bcdec_rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9934c2b68e46448d814db20e34a840ef9b4e7b3b7c8b1da91161481230f6350" + [[package]] name = "bindgen" version = "0.69.4" @@ -570,7 +576,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -642,7 +648,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -653,23 +659,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - -[[package]] -name = "calloop" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" -dependencies = [ - "bitflags 2.6.0", - "log", - "polling", - "rustix", - "slab", - "thiserror", -] +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "calloop" @@ -685,25 +677,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "calloop-wayland-source" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" -dependencies = [ - "calloop 0.12.4", - "rustix", - "wayland-backend", - "wayland-client", -] - [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop 0.13.0", + "calloop", "rustix", "wayland-backend", "wayland-client", @@ -711,13 +691,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.0" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -807,14 +786,14 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.4", + "libloading", ] [[package]] name = "clap" -version = "4.5.9" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" dependencies = [ "clap_builder", "clap_derive", @@ -822,9 +801,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" dependencies = [ "anstream", "anstyle", @@ -841,7 +820,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1034,7 +1013,7 @@ dependencies = [ "core-foundation-sys", "coreaudio-rs", "dasp_sample", - "jni 0.21.1", + "jni", "js-sys", "libc", "mach2", @@ -1153,7 +1132,7 @@ dependencies = [ "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1170,7 +1149,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1193,7 +1172,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1204,7 +1183,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1224,6 +1203,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "ddsfile" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479dfe1e6737aa9e96c6ac7b69689dc4c32da8383f2c12744739d76afa8b66c4" +dependencies = [ + "bitflags 2.6.0", + "byteorder", + "enum-primitive-derive", + "num-traits", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1245,7 +1236,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1270,7 +1261,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.4", + "libloading", ] [[package]] @@ -1312,6 +1303,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-primitive-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" +dependencies = [ + "num-traits", + "quote", + "syn 1.0.109", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -1330,28 +1332,28 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] name = "enumset" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1466,7 +1468,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1575,7 +1577,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -1795,6 +1797,19 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image_dds" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb76fb263814aa9dcc4370dda5b3c129ec9b711f5076ae584035c87db3ddc1f" +dependencies = [ + "bcdec_rs", + "bytemuck", + "ddsfile", + "half", + "thiserror", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1834,9 +1849,9 @@ dependencies = [ [[package]] name = "input-linux-sys" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a687a25a4973027df9153753a5589f97fe1e958f694a34eea5606ae65299ab8" +checksum = "1c7ef95c35c8ef8d114f5e197a5ac9554dc4afdd19ae78ae1fd0fc0944cb1340" dependencies = [ "libc", "nix 0.26.4", @@ -1890,20 +1905,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jni" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - [[package]] name = "jni" version = "0.21.1" @@ -1928,9 +1929,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1990,19 +1991,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -2174,7 +2165,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2336,7 +2327,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2366,7 +2357,7 @@ dependencies = [ "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2596,7 +2587,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" dependencies = [ - "jni 0.21.1", + "jni", "ndk 0.8.0", "ndk-context", "num-derive", @@ -2621,23 +2612,20 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openxr" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d5c194407c4fb5d3bf08c34ae57f3ea6cc9d9cfbe0594ce066896c809d9215" +version = "0.18.0" +source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading", "ndk-context", "openxr-sys", ] [[package]] name = "openxr-sys" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8f022053ecd7989d86f867b4fb8c3520347612b9d637e217077a0d6b4a6634" +version = "0.10.0" +source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76" dependencies = [ - "jni 0.19.0", "libc", ] @@ -2729,7 +2717,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] @@ -2786,7 +2774,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2855,7 +2843,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -2943,7 +2931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3093,9 +3081,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -3259,14 +3247,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7555fcb4f753d095d734fdefebb0ad8c98478a21db500492d87c55913d3b0086" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", "memmap2 0.9.4", - "smithay-client-toolkit 0.18.1", + "smithay-client-toolkit", "tiny-skia", ] @@ -3293,7 +3281,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3326,7 +3314,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3444,39 +3432,14 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" -dependencies = [ - "bitflags 2.6.0", - "calloop 0.12.4", - "calloop-wayland-source 0.2.0", - "cursor-icon", - "libc", - "log", - "memmap2 0.9.4", - "rustix", - "thiserror", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols 0.31.2", - "wayland-protocols-wlr 0.2.0", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "837d3067369e24aeda699a5d9fc5aa14ca14a84dd70aeed7156bfa04a5605b32" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.6.0", "bytemuck", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", + "calloop", + "calloop-wayland-source", "cursor-icon", "libc", "log", @@ -3488,8 +3451,8 @@ dependencies = [ "wayland-client", "wayland-csd-frame", "wayland-cursor", - "wayland-protocols 0.32.2", - "wayland-protocols-wlr 0.3.2", + "wayland-protocols", + "wayland-protocols-wlr", "wayland-scanner", "xkbcommon", "xkeysym", @@ -3541,7 +3504,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3557,9 +3520,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.70" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -3623,22 +3586,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3764,7 +3727,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3896,7 +3859,7 @@ dependencies = [ "half", "heck 0.4.1", "indexmap 2.2.6", - "libloading 0.8.4", + "libloading", "nom", "objc", "once_cell", @@ -3921,7 +3884,7 @@ dependencies = [ "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] @@ -3934,7 +3897,7 @@ dependencies = [ "proc-macro2", "quote", "shaderc", - "syn 2.0.70", + "syn 2.0.72", "vulkano", ] @@ -3975,7 +3938,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -4009,7 +3972,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4022,9 +3985,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wayland-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269c04f203640d0da2092d1b8d89a2d081714ae3ac2f1b53e99f205740517198" +checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" dependencies = [ "cc", "downcast-rs", @@ -4036,9 +3999,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bd0f46c069d3382a36c8666c1b9ccef32b8b04f41667ca1fef06a1adcc2982" +checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" dependencies = [ "bitflags 2.6.0", "rustix", @@ -4059,9 +4022,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09414bcf0fd8d9577d73e9ac4659ebc45bcc9cff1980a350543ad8e50ee263b2" +checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" dependencies = [ "rustix", "wayland-client", @@ -4070,21 +4033,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1794d82d869f38439d15c24b26f06f6c8603d27d47b4f786d5197c99044de415" +checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -4094,48 +4045,35 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.31.2", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.31.2", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa43c961473aed713d44c1f616f775186249dfca657f256d8841ca0690366aba" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.2", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.31.3" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf466fc49a4feb65a511ca403fec3601494d0dee85dbf37fff6fa0dd4eec3b6" +checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" dependencies = [ "proc-macro2", "quick-xml 0.34.0", @@ -4144,9 +4082,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.3" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6754825230fa5b27bafaa28c30b3c9e72c55530581220cef401fa422c0fae7" +checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" dependencies = [ "dlib", "log", @@ -4460,9 +4398,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f45a7b7e2de6af35448d7718dab6d95acec466eb3bb7a56f4d31d1af754004" +checksum = "4225ddd8ab67b8b59a2fee4b34889ebf13c0460c1c3fa297c58e21eb87801b33" dependencies = [ "ahash", "android-activity", @@ -4470,7 +4408,7 @@ dependencies = [ "bitflags 2.6.0", "block2", "bytemuck", - "calloop 0.12.4", + "calloop", "cfg_aliases 0.2.1", "concurrent-queue", "core-foundation", @@ -4492,7 +4430,7 @@ dependencies = [ "redox_syscall 0.4.1", "rustix", "sctk-adwaita", - "smithay-client-toolkit 0.18.1", + "smithay-client-toolkit", "smol_str", "tracing", "unicode-segmentation", @@ -4500,7 +4438,7 @@ dependencies = [ "wasm-bindgen-futures", "wayland-backend", "wayland-client", - "wayland-protocols 0.31.2", + "wayland-protocols", "wayland-protocols-plasma", "web-sys", "web-time", @@ -4532,9 +4470,9 @@ dependencies = [ "once_cell", "pipewire", "rxscreen", - "smithay-client-toolkit 0.19.1", + "smithay-client-toolkit", "wayland-client", - "wayland-protocols 0.32.2", + "wayland-protocols", "xcb", ] @@ -4558,11 +4496,12 @@ dependencies = [ "glam", "idmap", "idmap-derive", + "image_dds", "input-linux", "json", "json5", "libc", - "libloading 0.8.4", + "libloading", "log", "log-panics", "once_cell", @@ -4608,7 +4547,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.4", + "libloading", "once_cell", "rustix", "x11rb-protocol", @@ -4634,9 +4573,9 @@ dependencies = [ [[package]] name = "xcursor" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" [[package]] name = "xdg" @@ -4811,7 +4750,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.70", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5ba4e61..4f55693 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,9 @@ libc = "0.2.155" libloading = "0.8.3" log = "0.4.21" once_cell = "1.19.0" -openxr = { version = "0.17.1", features = ["linked"], optional = true } +openxr = { git = "https://github.com/galister/openxrs", rev = "af4a55d", features = [ + "linked", +], optional = true } ovr_overlay = { features = [ "ovr_input", "ovr_system", @@ -62,6 +64,9 @@ xkbcommon = { version = "0.7.0" } xcb = { version = "1.4.0", optional = true, features = [ "as-raw-xcb-connection", ] } +image_dds = { version = "0.5.1", default-features = false, features = [ + "ddsfile", +] } [features] default = ["openvr", "openxr", "osc", "x11", "wayland"] diff --git a/src/backend/openvr/lines.rs b/src/backend/openvr/lines.rs index ae53a0a..cc5ad0b 100644 --- a/src/backend/openvr/lines.rs +++ b/src/backend/openvr/lines.rs @@ -32,7 +32,7 @@ impl LinePool { let buf = vec![255; 16]; - let texture = command_buffer.texture2d(2, 2, Format::R8G8B8A8_UNORM, &buf)?; + let texture = command_buffer.texture2d_raw(2, 2, Format::R8G8B8A8_UNORM, &buf)?; command_buffer.build_and_execute_now()?; graphics diff --git a/src/backend/openxr/helpers.rs b/src/backend/openxr/helpers.rs index 095c468..641d7ae 100644 --- a/src/backend/openxr/helpers.rs +++ b/src/backend/openxr/helpers.rs @@ -35,6 +35,11 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { } else { log::warn!("Missing EXT_hp_mixed_reality_controller extension."); } + if available_extensions.khr_composition_layer_equirect2 { + enabled_extensions.khr_composition_layer_equirect2 = true; + } else { + log::warn!("Missing EXT_composition_layer_equirect2 extension."); + } //#[cfg(not(debug_assertions))] let layers = []; diff --git a/src/backend/openxr/libmonado.rs b/src/backend/openxr/libmonado.rs new file mode 100644 index 0000000..893e96d --- /dev/null +++ b/src/backend/openxr/libmonado.rs @@ -0,0 +1,149 @@ +use std::{env, ffi::c_void, fs}; + +use anyhow::bail; +use libloading::{Library, Symbol}; +use serde::Deserialize; + +#[repr(C)] +#[derive(Default, Debug)] +struct MndPose { + orientation: [f32; 4], + position: [f32; 3], +} + +const MND_REFERENCE_TYPE_STAGE: i32 = 3; + +const MND_SUCCESS: i32 = 0; +const MND_ERROR_BAD_SPACE_TYPE: i32 = -7; + +type GetDeviceCount = extern "C" fn(*mut c_void, *mut u32) -> i32; +type GetDeviceInfo = extern "C" fn(*mut c_void, u32, *mut u32, *mut *const char) -> i32; +type GetDeviceFromRole = extern "C" fn(*mut c_void, *const std::os::raw::c_char, *mut i32) -> i32; +type GetDeviceBatteryStatus = + extern "C" fn(*mut c_void, u32, *mut bool, *mut bool, *mut f32) -> i32; + +type PlaySpaceMove = extern "C" fn(*mut c_void, f32, f32, f32) -> i32; +type ApplyStageOffset = extern "C" fn(*mut c_void, *const MndPose) -> i32; + +// New implementation +type GetReferenceSpaceOffset = extern "C" fn(*mut c_void, i32, *mut MndPose) -> i32; +type SetReferenceSpaceOffset = extern "C" fn(*mut c_void, i32, *const MndPose) -> i32; + +// TODO: Clean up after merge into upstream Monado +enum MoverImpl { + None, + PlaySpaceMove(PlaySpaceMove), + ApplyStageOffset(ApplyStageOffset), + SpaceOffsetApi { + get_reference: GetReferenceSpaceOffset, + set_reference: SetReferenceSpaceOffset, + }, +} + +pub struct LibMonado { + libmonado: Library, + mnd_root: *mut c_void, + mover: MoverImpl, +} + +impl Drop for LibMonado { + fn drop(&mut self) { + unsafe { + type RootDestroy = extern "C" fn(*mut *mut c_void) -> i32; + let Ok(root_destroy) = self.libmonado.get::(b"mnd_root_destroy\0") else { + return; + }; + root_destroy(&mut self.mnd_root); + } + } +} + +impl LibMonado { + pub fn new() -> anyhow::Result { + let lib_path = if let Ok(path) = env::var("LIBMONADO_PATH") { + path + } else if let Some(path) = xr_runtime_manifest() + .map(|manifest| manifest.runtime.mnd_libmonado_path) + .ok() + .flatten() + { + path + } else { + bail!("Monado: libmonado not found. Update your Monado/WiVRn or set LIBMONADO_PATH to point at your libmonado.so"); + }; + + let (libmonado, mnd_root) = unsafe { + let libmonado = libloading::Library::new(lib_path)?; + let root_create: Symbol i32> = + libmonado.get(b"mnd_root_create\0")?; + + let mut root: *mut c_void = std::ptr::null_mut(); + let ret = root_create(&mut root); + if ret != 0 { + anyhow::bail!("Failed to create libmonado root, code: {}", ret); + } + + (libmonado, root) + }; + + let space_api = unsafe { + if let (Ok(get_reference), Ok(set_reference)) = ( + libmonado.get(b"mnd_root_get_reference_space_offset\0"), + libmonado.get(b"mnd_root_set_reference_space_offset\0"), + ) { + log::info!("Monado: using space offset API"); + + let get_reference: GetReferenceSpaceOffset = *get_reference; + let set_reference: SetReferenceSpaceOffset = *set_reference; + + MoverImpl::SpaceOffsetApi { + get_reference, + set_reference, + } + } else if let Ok(playspace_move) = libmonado.get(b"mnd_root_playspace_move\0") { + log::warn!("Monado: using playspace_move, which is obsolete. Consider updating."); + MoverImpl::PlaySpaceMove(*playspace_move) + } else if let Ok(apply_stage_offset) = libmonado.get(b"mnd_root_apply_stage_offset\0") { + log::warn!( + "Monado: using apply_stage_offset, which is obsolete. Consider updating." + ); + MoverImpl::ApplyStageOffset(*apply_stage_offset) + } else { + MoverImpl::None + } + }; + + Ok(Self { + libmonado, + mnd_root, + mover: space_api, + }) + } + + pub fn mover_supported(&self) -> bool { + !matches!(self.mover, MoverImpl::None) + } +} + +#[derive(Deserialize)] +struct XrRuntimeManifestRuntime { + name: String, + library_path: String, + mnd_libmonado_path: Option, +} + +#[derive(Deserialize)] +struct XrRuntimeManifest { + file_format_version: String, + runtime: XrRuntimeManifestRuntime, +} + +fn xr_runtime_manifest() -> anyhow::Result { + let xdg_dirs = xdg::BaseDirectories::new()?; // only fails if $HOME unset + let mut file = xdg_dirs.get_config_home(); + file.push("openxr/1/active_runtime.json"); + + let json = fs::read_to_string(file)?; + let manifest = serde_json::from_str(&json)?; + Ok(manifest) +} diff --git a/src/backend/openxr/lines.rs b/src/backend/openxr/lines.rs index d4fcce4..ce982a6 100644 --- a/src/backend/openxr/lines.rs +++ b/src/backend/openxr/lines.rs @@ -17,7 +17,7 @@ use crate::{ }; use super::{ - swapchain::{create_swapchain_render_data, SwapchainRenderData}, + swapchain::{create_swapchain_render_data, SwapchainOpts, SwapchainRenderData}, CompositionLayer, XrState, }; @@ -46,7 +46,7 @@ impl LinePool { let views: anyhow::Result>> = colors .into_iter() .map( - |color| match command_buffer.texture2d(1, 1, Format::R8G8B8A8_UNORM, &color) { + |color| match command_buffer.texture2d_raw(1, 1, Format::R8G8B8A8_UNORM, &color) { Ok(tex) => ImageView::new_default(tex).map_err(|e| anyhow::anyhow!(e)), Err(e) => Err(e), }, @@ -68,7 +68,8 @@ impl LinePool { ) -> anyhow::Result { let id = AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed); - let srd = create_swapchain_render_data(xr, graphics, [1, 1, 1])?; + let srd = + create_swapchain_render_data(xr, graphics, [1, 1, 1], SwapchainOpts::new().srgb())?; self.lines.insert( id, LineContainer { diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index 0718297..1135504 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -10,6 +10,7 @@ use std::{ use glam::{Affine3A, Vec3}; use openxr as xr; +use skybox::create_skybox; use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject}; use crate::{ @@ -34,6 +35,7 @@ mod input; mod lines; mod overlay; mod playspace; +mod skybox; mod swapchain; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; @@ -122,6 +124,8 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), stage_offset: Affine3A::IDENTITY, }; + let mut skybox = create_skybox(&xr_state, &app_state); + let pointer_lines = [ lines.allocate(&xr_state, app_state.graphics.clone())?, lines.allocate(&xr_state, app_state.graphics.clone())?, @@ -136,6 +140,8 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), let mut due_tasks = VecDeque::with_capacity(4); + let mut main_session_visible = false; + 'main_loop: loop { let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed); @@ -179,6 +185,19 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), EventsLost(e) => { log::warn!("lost {} events", e.lost_event_count()); } + MainSessionVisibilityChangedEXTX(e) => { + if main_session_visible != e.visible() { + main_session_visible = e.visible(); + log::info!("Main session visible: {}", main_session_visible); + if main_session_visible { + log::debug!("Destroying skybox."); + skybox = None; + } else { + log::debug!("Allocating skybox."); + skybox = create_skybox(&xr_state, &app_state); + } + } + } _ => {} } } @@ -284,6 +303,18 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + if !main_session_visible { + if let Some(skybox) = skybox.as_mut() { + for (idx, layer) in skybox + .present_xr(&xr_state, app_state.input_state.hmd, &mut command_buffer)? + .into_iter() + .enumerate() + { + layers.push((200.0 - 50.0 * (idx as f32), layer)); + } + } + } + for o in overlays.iter_mut() { if !o.state.want_visible { continue; @@ -327,6 +358,7 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), .map(|f| match f.1 { CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase, CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase, + CompositionLayer::Equirect2(ref l) => l as &xr::CompositionLayerBase, CompositionLayer::None => unreachable!(), }) .collect::>(); @@ -413,4 +445,5 @@ pub(super) enum CompositionLayer<'a> { None, Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>), Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>), + Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>), } diff --git a/src/backend/openxr/overlay.rs b/src/backend/openxr/overlay.rs index 33889ae..095432a 100644 --- a/src/backend/openxr/overlay.rs +++ b/src/backend/openxr/overlay.rs @@ -1,11 +1,14 @@ use glam::Vec3A; -use openxr as xr; +use openxr::{self as xr, CompositionLayerFlags}; use std::{f32::consts::PI, sync::Arc}; use xr::EyeVisibility; use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState}; use crate::{ - backend::{openxr::swapchain::create_swapchain_render_data, overlay::OverlayData}, + backend::{ + openxr::swapchain::{create_swapchain_render_data, SwapchainOpts}, + overlay::OverlayData, + }, graphics::WlxCommandBuffer, state::AppState, }; @@ -40,8 +43,12 @@ impl OverlayData { let data = match self.data.swapchain { Some(ref mut data) => data, None => { - let srd = - create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent)?; + let srd = create_swapchain_render_data( + xr, + command_buffer.graphics.clone(), + extent, + SwapchainOpts::new(), + )?; log::debug!( "{}: Created swapchain {}x{}, {} images, {} MB", self.state.name, @@ -75,6 +82,7 @@ impl OverlayData { let angle = 2.0 * (scale_x / (2.0 * radius)); let cylinder = xr::CompositionLayerCylinderKHR::new() + .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .pose(posef) .sub_image(sub_image) .eye_visibility(EyeVisibility::BOTH) @@ -86,6 +94,7 @@ impl OverlayData { } else { let posef = helpers::transform_to_posef(&self.state.transform); let quad = xr::CompositionLayerQuad::new() + .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .pose(posef) .sub_image(sub_image) .eye_visibility(EyeVisibility::BOTH) diff --git a/src/backend/openxr/skybox.rs b/src/backend/openxr/skybox.rs new file mode 100644 index 0000000..1de439b --- /dev/null +++ b/src/backend/openxr/skybox.rs @@ -0,0 +1,168 @@ +use std::{f32::consts::PI, fs::File, sync::Arc}; + +use glam::{Affine3A, Quat, Vec3A}; +use once_cell::sync::Lazy; +use openxr::{self as xr, CompositionLayerFlags}; +use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView}; + +use crate::{ + backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts}, + config_io::CONFIG_ROOT_PATH, + graphics::{dds::WlxCommandBufferDds, format_is_srgb, WlxCommandBuffer}, + state::AppState, +}; + +use super::{ + swapchain::{create_swapchain_render_data, SwapchainRenderData}, + CompositionLayer, XrState, +}; + +pub(super) struct Skybox { + view: Arc, + srd: Option<(SwapchainRenderData, SwapchainRenderData)>, +} + +impl Skybox { + pub fn new(app: &AppState) -> anyhow::Result { + let mut command_buffer = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + + let mut maybe_image = None; + + 'custom_tex: { + if app.session.config.skybox_texture.is_empty() { + break 'custom_tex; + } + + let real_path = CONFIG_ROOT_PATH.join(&*app.session.config.skybox_texture); + let Ok(f) = File::open(real_path) else { + log::warn!( + "Could not open custom skybox texture at: {}", + app.session.config.skybox_texture + ); + break 'custom_tex; + }; + match command_buffer.texture2d_dds(f) { + Ok(image) => { + maybe_image = Some(image); + } + Err(e) => { + log::warn!( + "Could not use custom skybox texture at: {}", + app.session.config.skybox_texture + ); + log::warn!("{:?}", e); + } + } + } + + if maybe_image.is_none() { + let p = include_bytes!("../../res/table_mountain_2.dds"); + maybe_image = Some(command_buffer.texture2d_dds(p.as_slice())?); + } + + command_buffer.build_and_execute_now()?; + + let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap + + Ok(Self { view, srd: None }) + } + + pub(super) fn present_xr<'a>( + &'a mut self, + xr: &'a XrState, + hmd: Affine3A, + command_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result> { + let (sky_image, grid_image) = if let Some((ref mut srd_sky, ref mut srd_grid)) = self.srd { + (srd_sky.present_last()?, srd_grid.present_last()?) + } else { + log::debug!("Render skybox."); + + let mut opts = SwapchainOpts::new().immutable(); + opts.srgb = format_is_srgb(self.view.image().format()); + + let srd_sky = create_swapchain_render_data( + xr, + command_buffer.graphics.clone(), + self.view.image().extent(), + opts, + )?; + + let srd_grid = create_swapchain_render_data( + xr, + command_buffer.graphics.clone(), + [1024, 1024, 1], + SwapchainOpts::new().immutable().grid(), + )?; + + self.srd = Some((srd_sky, srd_grid)); + + let (srd_sky, srd_grid) = self.srd.as_mut().unwrap(); // safe unwrap + + ( + srd_sky.acquire_present_release(command_buffer, self.view.clone(), 1.0)?, + srd_grid.acquire_compute_release(command_buffer)?, + ) + }; + + let pose = xr::Posef { + orientation: xr::Quaternionf::IDENTITY, + position: xr::Vector3f { + x: hmd.translation.x, + y: hmd.translation.y, + z: hmd.translation.z, + }, + }; + + // cover the entire sphere + const HORIZ_ANGLE: f32 = 2.0 * PI; + const HI_VERT_ANGLE: f32 = 0.5 * PI; + const LO_VERT_ANGLE: f32 = -0.5 * PI; + + let mut layers = vec![]; + + let sky = xr::CompositionLayerEquirect2KHR::new() + .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) + .pose(pose) + .radius(10.0) + .sub_image(sky_image) + .eye_visibility(xr::EyeVisibility::BOTH) + .space(&xr.stage) + .central_horizontal_angle(HORIZ_ANGLE) + .upper_vertical_angle(HI_VERT_ANGLE) + .lower_vertical_angle(LO_VERT_ANGLE); + + layers.push(CompositionLayer::Equirect2(sky)); + + static GRID_POSE: Lazy = Lazy::new(|| { + translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)) + }); + + let grid = xr::CompositionLayerQuad::new() + .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) + .pose(*GRID_POSE) + .size(xr::Extent2Df { + width: 10.0, + height: 10.0, + }) + .sub_image(grid_image) + .eye_visibility(xr::EyeVisibility::BOTH) + .space(&xr.stage); + + layers.push(CompositionLayer::Quad(grid)); + + Ok(layers) + } +} + +pub(super) fn create_skybox(xr: &XrState, app: &AppState) -> Option { + if !app.session.config.use_skybox { + return None; + } + xr.instance + .exts() + .khr_composition_layer_equirect2 + .and_then(|_| Skybox::new(app).ok()) +} diff --git a/src/backend/openxr/swapchain.rs b/src/backend/openxr/swapchain.rs index b73abfa..4075553 100644 --- a/src/backend/openxr/swapchain.rs +++ b/src/backend/openxr/swapchain.rs @@ -16,13 +16,45 @@ use crate::graphics::{WlxCommandBuffer, WlxGraphics, WlxPipeline, WlxPipelineDyn use super::XrState; +#[derive(Default)] +pub(super) struct SwapchainOpts { + pub immutable: bool, + pub srgb: bool, + pub grid: bool, +} + +impl SwapchainOpts { + pub fn new() -> Self { + Default::default() + } + pub fn immutable(mut self) -> Self { + self.immutable = true; + self + } + pub fn srgb(mut self) -> Self { + self.srgb = true; + self + } + pub fn grid(mut self) -> Self { + self.grid = true; + self + } +} + pub(super) fn create_swapchain_render_data( xr: &XrState, graphics: Arc, extent: [u32; 3], + opts: SwapchainOpts, ) -> anyhow::Result { + let create_flags = if opts.immutable { + xr::SwapchainCreateFlags::STATIC_IMAGE + } else { + xr::SwapchainCreateFlags::EMPTY + }; + let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo { - create_flags: xr::SwapchainCreateFlags::EMPTY, + create_flags, usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED, format: Format::R8G8B8A8_SRGB as _, sample_count: 1, @@ -36,10 +68,22 @@ pub(super) fn create_swapchain_render_data( let Ok(shaders) = graphics.shared_shaders.read() else { bail!("Failed to lock shared shaders for reading"); }; + + let image_fmt = if opts.srgb { + Format::R8G8B8A8_SRGB + } else { + Format::R8G8B8A8_UNORM + }; + + let frag_shader = if opts.grid { + "frag_grid" + } else { + "frag_swapchain" + }; let pipeline = graphics.create_pipeline_dynamic( shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_swapchain").unwrap().clone(), // want panic - Format::R8G8B8A8_UNORM, + shaders.get(frag_shader).unwrap().clone(), // want panic + image_fmt, Some(AttachmentBlend::alpha()), )?; @@ -54,7 +98,7 @@ pub(super) fn create_swapchain_render_data( graphics.device.clone(), vk_image, ImageCreateInfo { - format: Format::R8G8B8A8_UNORM, // actually SRGB but we lie + format: image_fmt, // actually SRGB but we lie extent, usage: ImageUsage::COLOR_ATTACHMENT, ..Default::default() @@ -72,6 +116,7 @@ pub(super) fn create_swapchain_render_data( pipeline, images, extent, + target_extent: [0, 0, 0], }) } @@ -79,6 +124,7 @@ pub(super) struct SwapchainRenderData { pub(super) swapchain: xr::Swapchain, pub(super) pipeline: Arc>, pub(super) extent: [u32; 3], + pub(super) target_extent: [u32; 3], pub(super) images: SmallVec<[Arc; 4]>, } @@ -95,7 +141,7 @@ impl SwapchainRenderData { let render_target = &mut self.images[idx]; command_buffer.begin_rendering(render_target.clone())?; - let target_extent = render_target.image().extent(); + self.target_extent = render_target.image().extent(); let set0 = self.pipeline.uniform_sampler( 0, @@ -106,7 +152,7 @@ impl SwapchainRenderData { let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?; let pass = self.pipeline.create_pass( - [target_extent[0] as _, target_extent[1] as _], + [self.target_extent[0] as _, self.target_extent[1] as _], command_buffer.graphics.quad_verts.clone(), command_buffer.graphics.quad_indices.clone(), vec![set0, set1], @@ -121,8 +167,60 @@ impl SwapchainRenderData { .image_rect(xr::Rect2Di { offset: xr::Offset2Di { x: 0, y: 0 }, extent: xr::Extent2Di { - width: target_extent[0] as _, - height: target_extent[1] as _, + width: self.target_extent[0] as _, + height: self.target_extent[1] as _, + }, + }) + .image_array_index(0)) + } + + pub(super) fn acquire_compute_release( + &mut self, + command_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result> { + let idx = self.swapchain.acquire_image()? as usize; + self.swapchain.wait_image(xr::Duration::INFINITE)?; + + let render_target = &mut self.images[idx]; + command_buffer.begin_rendering(render_target.clone())?; + + self.target_extent = render_target.image().extent(); + + let pass = self.pipeline.create_pass( + [self.target_extent[0] as _, self.target_extent[1] as _], + command_buffer.graphics.quad_verts.clone(), + command_buffer.graphics.quad_indices.clone(), + vec![], + )?; + command_buffer.run_ref(&pass)?; + command_buffer.end_rendering()?; + + self.swapchain.release_image()?; + + Ok(xr::SwapchainSubImage::new() + .swapchain(&self.swapchain) + .image_rect(xr::Rect2Di { + offset: xr::Offset2Di { x: 0, y: 0 }, + extent: xr::Extent2Di { + width: self.target_extent[0] as _, + height: self.target_extent[1] as _, + }, + }) + .image_array_index(0)) + } + + pub(super) fn present_last(&self) -> anyhow::Result> { + debug_assert!( + self.target_extent[0] * self.target_extent[1] != 0, + "present_last: target_extent zero" + ); + Ok(xr::SwapchainSubImage::new() + .swapchain(&self.swapchain) + .image_rect(xr::Rect2Di { + offset: xr::Offset2Di { x: 0, y: 0 }, + extent: xr::Extent2Di { + width: self.target_extent[0] as _, + height: self.target_extent[1] as _, }, }) .image_array_index(0)) diff --git a/src/backend/uidev/mod.rs b/src/backend/uidev/mod.rs index 6c7dc8a..2583f9a 100644 --- a/src/backend/uidev/mod.rs +++ b/src/backend/uidev/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use vulkano::{ command_buffer::CommandBufferUsage, - image::{sampler::Filter, view::ImageView, ImageUsage}, + image::{view::ImageView, ImageUsage}, swapchain::{ acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, }, @@ -21,8 +21,8 @@ use crate::{ config_io, graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA}, gui::{ + canvas::Canvas, modular::{modular_canvas, ModularData}, - Canvas, }, hid::USE_UINPUT, state::{AppState, ScreenMeta}, diff --git a/src/config.rs b/src/config.rs index 96e4546..c219b58 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,6 +141,10 @@ fn def_auto() -> Arc { "auto".into() } +fn def_empty() -> Arc { + "".into() +} + fn def_toast_topics() -> IdMap { IdMap::new() } @@ -243,6 +247,12 @@ pub struct GeneralConfig { #[serde(default = "def_one")] pub space_drag_multiplier: f32, + + #[serde(default = "def_empty")] + pub skybox_texture: Arc, + + #[serde(default = "def_true")] + pub use_skybox: bool, } impl GeneralConfig { diff --git a/src/graphics/dds.rs b/src/graphics/dds.rs new file mode 100644 index 0000000..330015a --- /dev/null +++ b/src/graphics/dds.rs @@ -0,0 +1,102 @@ +use image_dds::{ImageFormat, Surface}; +use std::{io::Read, sync::Arc}; +use vulkano::{ + buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, + command_buffer::CopyBufferToImageInfo, + format::Format, + image::{Image, ImageCreateInfo, ImageType, ImageUsage}, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, + DeviceSize, +}; + +use super::WlxCommandBuffer; + +pub trait WlxCommandBufferDds { + fn texture2d_dds(&mut self, r: R) -> anyhow::Result> + where + R: Read; +} + +impl WlxCommandBufferDds for WlxCommandBuffer { + fn texture2d_dds(&mut self, r: R) -> anyhow::Result> + where + R: Read, + { + let Ok(dds) = image_dds::ddsfile::Dds::read(r) else { + anyhow::bail!("Not a valid DDS file.\nSee: https://github.com/galister/wlx-overlay-s/wiki/Custom-Textures"); + }; + + let surface = Surface::from_dds(&dds)?; + + if surface.depth != 1 { + anyhow::bail!("Not a 2D texture.") + } + + let image = Image::new( + self.graphics.memory_allocator.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format: dds_to_vk(surface.image_format)?, + extent: [surface.width, surface.height, surface.depth], + usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC | ImageUsage::SAMPLED, + ..Default::default() + }, + AllocationCreateInfo::default(), + )?; + + let buffer: Subbuffer<[u8]> = Buffer::new_slice( + self.graphics.memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_HOST + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + surface.data.len() as DeviceSize, + )?; + + buffer.write()?.copy_from_slice(surface.data); + + self.command_buffer + .copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(buffer, image.clone()))?; + + Ok(image) + } +} + +pub fn dds_to_vk(dds_fmt: ImageFormat) -> anyhow::Result { + match dds_fmt { + ImageFormat::R8Unorm => Ok(Format::R8_UNORM), + ImageFormat::Rgba8Unorm => Ok(Format::R8G8B8A8_UNORM), + ImageFormat::Rgba8UnormSrgb => Ok(Format::R8G8B8A8_SRGB), + ImageFormat::Rgba16Float => Ok(Format::R16G16B16A16_SFLOAT), + ImageFormat::Rgba32Float => Ok(Format::R32G32B32A32_SFLOAT), + ImageFormat::Bgra8Unorm => Ok(Format::B8G8R8A8_UNORM), + ImageFormat::Bgra8UnormSrgb => Ok(Format::B8G8R8A8_SRGB), + // DXT1 + ImageFormat::BC1RgbaUnorm => Ok(Format::BC1_RGBA_UNORM_BLOCK), + ImageFormat::BC1RgbaUnormSrgb => Ok(Format::BC1_RGBA_SRGB_BLOCK), + // DXT3 + ImageFormat::BC2RgbaUnorm => Ok(Format::BC2_UNORM_BLOCK), + ImageFormat::BC2RgbaUnormSrgb => Ok(Format::BC2_SRGB_BLOCK), + // DXT5 + ImageFormat::BC3RgbaUnorm => Ok(Format::BC3_UNORM_BLOCK), + ImageFormat::BC3RgbaUnormSrgb => Ok(Format::BC3_SRGB_BLOCK), + // RGTC1 + ImageFormat::BC4RUnorm => Ok(Format::BC4_UNORM_BLOCK), + ImageFormat::BC4RSnorm => Ok(Format::BC4_SNORM_BLOCK), + // RGTC2 + ImageFormat::BC5RgUnorm => Ok(Format::BC5_UNORM_BLOCK), + ImageFormat::BC5RgSnorm => Ok(Format::BC5_SNORM_BLOCK), + // BPTC + ImageFormat::BC6hRgbUfloat => Ok(Format::BC6H_UFLOAT_BLOCK), + ImageFormat::BC6hRgbSfloat => Ok(Format::BC6H_SFLOAT_BLOCK), + // BPTC + ImageFormat::BC7RgbaUnorm => Ok(Format::BC7_UNORM_BLOCK), + ImageFormat::BC7RgbaUnormSrgb => Ok(Format::BC7_SRGB_BLOCK), + _ => anyhow::bail!("Unsupported format {:?}", dds_fmt), + } +} diff --git a/src/graphics.rs b/src/graphics/mod.rs similarity index 99% rename from src/graphics.rs rename to src/graphics/mod.rs index 49d3f95..5a713c8 100644 --- a/src/graphics.rs +++ b/src/graphics/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod dds; + use std::{ collections::HashMap, os::fd::{FromRawFd, IntoRawFd}, @@ -1083,7 +1085,7 @@ impl WlxCommandBuffer { Ok(()) } - pub fn texture2d( + pub fn texture2d_raw( &mut self, width: u32, height: u32, @@ -1657,3 +1659,15 @@ fn memory_allocator(device: Arc) -> Arc { Arc::new(StandardMemoryAllocator::new(device, create_info)) } + +pub fn format_is_srgb(format: Format) -> bool { + matches!( + format, + Format::R8G8B8A8_SRGB + | Format::B8G8R8A8_SRGB + | Format::BC1_RGBA_SRGB_BLOCK + | Format::BC2_SRGB_BLOCK + | Format::BC3_SRGB_BLOCK + | Format::BC7_SRGB_BLOCK + ) +} diff --git a/src/gui/canvas/builder.rs b/src/gui/canvas/builder.rs new file mode 100644 index 0000000..74f1b01 --- /dev/null +++ b/src/gui/canvas/builder.rs @@ -0,0 +1,240 @@ +use glam::Vec4; +use std::sync::Arc; + +use vulkano::format::Format; + +use crate::{ + graphics::WlxGraphics, + gui::{canvas::control::ControlRenderer, GuiColor, KeyCapType}, +}; + +use super::{control::Control, Canvas, Rect}; + +pub struct CanvasBuilder { + canvas: Canvas, + + pub fg_color: GuiColor, + pub bg_color: GuiColor, + pub font_size: isize, +} + +impl CanvasBuilder { + pub fn new( + width: usize, + height: usize, + graphics: Arc, + format: Format, + data: D, + ) -> anyhow::Result { + Ok(Self { + canvas: Canvas::new(width, height, graphics, format, data)?, + bg_color: Vec4::ZERO, + fg_color: Vec4::ONE, + font_size: 16, + }) + } + + pub fn build(self) -> Canvas { + self.canvas + } + + // Creates a panel with bg_color inherited from the canvas + pub fn panel(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + bg_color: self.bg_color, + on_render_bg: Some(Control::render_rect), + ..Control::new() + }); + &mut self.canvas.controls[idx] + } + + // Creates a label with fg_color, font_size inherited from the canvas + pub fn label(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + text, + fg_color: self.fg_color, + size: self.font_size, + on_render_fg: Some(Control::render_text), + ..Control::new() + }); + &mut self.canvas.controls[idx] + } + + // Creates a label with fg_color, font_size inherited from the canvas + #[allow(dead_code)] + pub fn label_centered( + &mut self, + x: f32, + y: f32, + w: f32, + h: f32, + text: Arc, + ) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + text, + fg_color: self.fg_color, + size: self.font_size, + on_render_fg: Some(Control::render_text_centered), + ..Control::new() + }); + &mut self.canvas.controls[idx] + } + + // Creates a sprite. Will not draw anything until set_sprite is called. + pub fn sprite(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + on_render_bg: Some(Control::render_sprite_bg), + ..Control::new() + }); + &mut self.canvas.controls[idx] + } + + // Creates a sprite that highlights on pointer hover. Will not draw anything until set_sprite is called. + pub fn sprite_interactive(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + on_render_bg: Some(Control::render_sprite_bg), + on_render_hl: Some(Control::render_sprite_hl), + ..Control::new() + }); + &mut self.canvas.controls[idx] + } + + // Creates a button with fg_color, bg_color, font_size inherited from the canvas + pub fn button(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { + let idx = self.canvas.controls.len(); + + self.canvas.interactive_set_idx(x, y, w, h, idx); + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + text, + fg_color: self.fg_color, + bg_color: self.bg_color, + size: self.font_size, + on_render_bg: Some(Control::render_rect), + on_render_fg: Some(Control::render_text_centered), + on_render_hl: Some(Control::render_highlight), + ..Control::new() + }); + + &mut self.canvas.controls[idx] + } + + pub fn key_button( + &mut self, + x: f32, + y: f32, + w: f32, + h: f32, + cap_type: KeyCapType, + label: &[String], + ) -> &mut Control { + let idx = self.canvas.controls.len(); + self.canvas.interactive_set_idx(x, y, w, h, idx); + + self.canvas.controls.push(Control { + rect: Rect { x, y, w, h }, + bg_color: self.bg_color, + on_render_bg: Some(Control::render_rect), + on_render_hl: Some(Control::render_highlight), + ..Control::new() + }); + + let renders = match cap_type { + KeyCapType::Regular => { + let render: ControlRenderer = Control::render_text_centered; + let rect = Rect { + x, + y, + w, + h: h - self.font_size as f32, + }; + vec![(render, rect, 1f32)] + } + KeyCapType::RegularAltGr => { + let render: ControlRenderer = Control::render_text; + let rect0 = Rect { + x: x + 12., + y: y + (self.font_size as f32) + 12., + w, + h, + }; + let rect1 = Rect { + x: x + w * 0.5 + 12., + y: y + h - (self.font_size as f32) + 8., + w, + h, + }; + vec![(render, rect0, 1.0), (render, rect1, 0.8)] + } + KeyCapType::Reversed => { + let render: ControlRenderer = Control::render_text_centered; + let rect0 = Rect { + x, + y: y + 2.0, + w, + h: h * 0.5, + }; + let rect1 = Rect { + x, + y: y + h * 0.5 + 2.0, + w, + h: h * 0.5, + }; + vec![(render, rect1, 1.0), (render, rect0, 0.8)] + } + KeyCapType::ReversedAltGr => { + let render: ControlRenderer = Control::render_text; + let rect0 = Rect { + x: x + 12., + y: y + (self.font_size as f32) + 8., + w, + h, + }; + let rect1 = Rect { + x: x + 12., + y: y + h - (self.font_size as f32) + 4., + w, + h, + }; + let rect2 = Rect { + x: x + w * 0.5 + 8., + y: y + h - (self.font_size as f32) + 4., + w, + h, + }; + vec![ + (render, rect1, 1.0), + (render, rect0, 0.8), + (render, rect2, 0.8), + ] + } + }; + + for (idx, (render, rect, alpha)) in renders.into_iter().enumerate() { + if idx >= label.len() { + break; + } + + self.canvas.controls.push(Control { + rect, + text: Arc::from(label[idx].as_str()), + fg_color: self.fg_color * alpha, + size: self.font_size, + on_render_fg: Some(render), + ..Control::new() + }); + } + + &mut self.canvas.controls[idx] + } +} diff --git a/src/gui/canvas/control.rs b/src/gui/canvas/control.rs new file mode 100644 index 0000000..b294329 --- /dev/null +++ b/src/gui/canvas/control.rs @@ -0,0 +1,352 @@ +use glam::Vec4; +use std::sync::Arc; +use vulkano::image::view::ImageView; + +use crate::{ + backend::input::PointerMode, graphics::WlxCommandBuffer, gui::GuiColor, state::AppState, +}; + +use super::{CanvasData, Rect}; + +pub type ControlRenderer = + fn(&Control, &CanvasData, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>; + +pub type ControlRendererHl = fn( + &Control, + &CanvasData, + &mut AppState, + &mut WlxCommandBuffer, + Vec4, +) -> anyhow::Result<()>; + +pub(crate) struct Control { + pub state: Option, + pub rect: Rect, + pub fg_color: GuiColor, + pub bg_color: GuiColor, + pub text: Arc, + pub size: isize, + pub sprite: Option>, + pub sprite_st: Vec4, + pub(super) dirty: bool, + + pub on_update: Option, + pub on_press: Option, + pub on_release: Option, + pub on_scroll: Option, + pub test_highlight: Option Option>, + + pub(super) on_render_bg: Option>, + pub(super) on_render_hl: Option>, + pub(super) on_render_fg: Option>, +} + +impl Control { + pub(super) fn new() -> Self { + Self { + rect: Rect { + x: 0., + y: 0., + w: 0., + h: 0., + }, + fg_color: Vec4::ONE, + bg_color: Vec4::ZERO, + text: Arc::from(""), + sprite: None, + sprite_st: Vec4::new(1., 1., 0., 0.), + dirty: true, + size: 24, + state: None, + on_update: None, + on_render_bg: None, + on_render_hl: None, + on_render_fg: None, + test_highlight: None, + on_press: None, + on_release: None, + on_scroll: None, + } + } + + #[inline(always)] + pub fn set_text(&mut self, text: &str) { + if *self.text == *text { + return; + } + self.text = text.into(); + self.dirty = true; + } + + #[inline(always)] + pub fn set_sprite(&mut self, sprite: Arc) { + self.sprite.replace(sprite); + self.dirty = true; + } + + #[inline(always)] + pub fn set_sprite_st(&mut self, sprite_st: Vec4) { + if self.sprite_st == sprite_st { + return; + } + self.sprite_st = sprite_st; + self.dirty = true; + } + + #[inline(always)] + pub fn set_fg_color(&mut self, color: GuiColor) { + if self.fg_color == color { + return; + } + self.fg_color = color; + self.dirty = true; + } + + pub(super) fn render_rect( + &self, + canvas: &CanvasData, + _: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result<()> { + let pass = { + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + self.rect.x, + self.rect.y, + self.rect.w, + self.rect.h, + )?; + let set0 = canvas + .pipeline_bg_color + .uniform_buffer(0, self.bg_color.to_array().to_vec())?; + canvas.pipeline_bg_color.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer, + canvas.graphics.quad_indices.clone(), + vec![set0], + )? + }; + + cmd_buffer.run_ref(&pass) + } + + pub(super) fn render_highlight( + &self, + canvas: &CanvasData, + _: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + color: GuiColor, + ) -> anyhow::Result<()> { + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + self.rect.x, + self.rect.y, + self.rect.w, + self.rect.h, + )?; + + let set0 = canvas + .pipeline_bg_color + .uniform_buffer(0, color.to_array().to_vec())?; + + let pass = canvas.pipeline_bg_color.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer.clone(), + canvas.graphics.quad_indices.clone(), + vec![set0], + )?; + + cmd_buffer.run_ref(&pass) + } + + pub(super) fn render_text( + &self, + canvas: &CanvasData, + app: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result<()> { + let mut cur_y = self.rect.y; + for line in self.text.lines() { + let mut cur_x = self.rect.x; + for glyph in app + .fc + .get_glyphs(line, self.size, canvas.graphics.clone())? + { + if let Some(tex) = glyph.tex.clone() { + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + cur_x + glyph.left, + cur_y - glyph.top, + glyph.width, + glyph.height, + )?; + let set0 = canvas.pipeline_fg_glyph.uniform_sampler( + 0, + ImageView::new_default(tex)?, + app.graphics.texture_filtering, + )?; + let set1 = canvas + .pipeline_fg_glyph + .uniform_buffer(1, self.fg_color.to_array().to_vec())?; + let pass = canvas.pipeline_fg_glyph.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer, + canvas.graphics.quad_indices.clone(), + vec![set0, set1], + )?; + cmd_buffer.run_ref(&pass)?; + } + cur_x += glyph.advance; + } + cur_y += (self.size as f32) * 1.5; + } + Ok(()) + } + + pub(super) fn render_text_centered( + &self, + canvas: &CanvasData, + app: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result<()> { + let (w, h) = app + .fc + .get_text_size(&self.text, self.size, canvas.graphics.clone())?; + + let mut cur_y = self.rect.y + (self.rect.h) - (h * 0.5) - (self.size as f32 * 0.25); + for line in self.text.lines() { + let mut cur_x = self.rect.x + (self.rect.w * 0.5) - (w * 0.5); + for glyph in app + .fc + .get_glyphs(line, self.size, canvas.graphics.clone())? + { + if let Some(tex) = glyph.tex.clone() { + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + cur_x + glyph.left, + cur_y - glyph.top, + glyph.width, + glyph.height, + )?; + let set0 = canvas.pipeline_fg_glyph.uniform_sampler( + 0, + ImageView::new_default(tex)?, + app.graphics.texture_filtering, + )?; + let set1 = canvas + .pipeline_fg_glyph + .uniform_buffer(1, self.fg_color.to_array().to_vec())?; + let pass = canvas.pipeline_fg_glyph.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer, + canvas.graphics.quad_indices.clone(), + vec![set0, set1], + )?; + cmd_buffer.run_ref(&pass)?; + } + cur_x += glyph.advance; + } + cur_y += (self.size as f32) * 1.5; + } + Ok(()) + } + + pub(super) fn render_sprite_bg( + &self, + canvas: &CanvasData, + app: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + ) -> anyhow::Result<()> { + let Some(view) = self.sprite.as_ref() else { + return Ok(()); + }; + + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + self.rect.x, + self.rect.y, + self.rect.w, + self.rect.h, + )?; + let set0 = canvas.pipeline_bg_sprite.uniform_sampler( + 0, + view.clone(), + app.graphics.texture_filtering, + )?; + + let uniform = vec![ + self.sprite_st.x, + self.sprite_st.y, + self.sprite_st.z, + self.sprite_st.w, + self.fg_color.x, + self.fg_color.y, + self.fg_color.z, + self.fg_color.w, + ]; + + let set1 = canvas.pipeline_bg_sprite.uniform_buffer(1, uniform)?; + + let pass = canvas.pipeline_bg_sprite.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer, + canvas.graphics.quad_indices.clone(), + vec![set0, set1], + )?; + cmd_buffer.run_ref(&pass)?; + Ok(()) + } + + pub(super) fn render_sprite_hl( + &self, + canvas: &CanvasData, + app: &mut AppState, + cmd_buffer: &mut WlxCommandBuffer, + color: GuiColor, + ) -> anyhow::Result<()> { + let Some(view) = self.sprite.as_ref() else { + return Ok(()); + }; + + let vertex_buffer = canvas.graphics.upload_verts( + canvas.width as _, + canvas.height as _, + self.rect.x, + self.rect.y, + self.rect.w, + self.rect.h, + )?; + let set0 = canvas.pipeline_hl_sprite.uniform_sampler( + 0, + view.clone(), + app.graphics.texture_filtering, + )?; + + let uniform = vec![ + self.sprite_st.x, + self.sprite_st.y, + self.sprite_st.z, + self.sprite_st.w, + color.x, + color.y, + color.z, + color.w, + ]; + + let set1 = canvas.pipeline_hl_sprite.uniform_buffer(1, uniform)?; + + let pass = canvas.pipeline_hl_sprite.create_pass( + [canvas.width as _, canvas.height as _], + vertex_buffer, + canvas.graphics.quad_indices.clone(), + vec![set0, set1], + )?; + cmd_buffer.run_ref(&pass)?; + Ok(()) + } +} diff --git a/src/gui/canvas/mod.rs b/src/gui/canvas/mod.rs new file mode 100644 index 0000000..00107fa --- /dev/null +++ b/src/gui/canvas/mod.rs @@ -0,0 +1,378 @@ +pub(crate) mod builder; +pub(crate) mod control; + +use std::sync::Arc; + +use glam::{Vec2, Vec4}; +use vulkano::{ + command_buffer::CommandBufferUsage, + format::Format, + image::{view::ImageView, ImageLayout}, +}; + +use crate::{ + backend::{ + input::{Haptics, InteractionHandler, PointerHit}, + overlay::{OverlayBackend, OverlayRenderer}, + }, + graphics::{WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy, BLEND_ALPHA}, + state::AppState, +}; + +const RES_DIVIDER: usize = 4; + +pub struct Rect { + x: f32, + y: f32, + w: f32, + h: f32, +} + +pub struct CanvasData { + pub data: D, + pub width: usize, + pub height: usize, + + graphics: Arc, + + pipeline_bg_color: Arc>, + pipeline_fg_glyph: Arc>, + pipeline_bg_sprite: Arc>, + pipeline_hl_sprite: Arc>, + pipeline_final: Arc>, +} + +pub struct Canvas { + controls: Vec>, + canvas: CanvasData, + + hover_controls: [Option; 2], + pressed_controls: [Option; 2], + + interact_map: Vec>, + interact_stride: usize, + interact_rows: usize, + + view_final: Arc, + + pass_fg: WlxPass, + pass_bg: WlxPass, +} + +impl Canvas { + fn new( + width: usize, + height: usize, + graphics: Arc, + format: Format, + data: D, + ) -> anyhow::Result { + let tex_fg = graphics.render_texture(width as _, height as _, format)?; + let tex_bg = graphics.render_texture(width as _, height as _, format)?; + let tex_final = graphics.render_texture(width as _, height as _, format)?; + + let view_fg = ImageView::new_default(tex_fg.clone())?; + let view_bg = ImageView::new_default(tex_bg.clone())?; + let view_final = ImageView::new_default(tex_final.clone())?; + + let Ok(shaders) = graphics.shared_shaders.read() else { + anyhow::bail!("Failed to lock shared shaders for reading"); + }; + + let pipeline_bg_color = graphics.create_pipeline( + view_bg.clone(), + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_color").unwrap().clone(), // want panic + format, + Some(BLEND_ALPHA), + )?; + + let pipeline_fg_glyph = graphics.create_pipeline( + view_fg.clone(), + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_glyph").unwrap().clone(), // want panic + format, + Some(BLEND_ALPHA), + )?; + + let pipeline_bg_sprite = graphics.create_pipeline( + view_fg.clone(), + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_sprite2").unwrap().clone(), // want panic + format, + Some(BLEND_ALPHA), + )?; + + let pipeline_hl_sprite = graphics.create_pipeline( + view_fg.clone(), + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_sprite2_hl").unwrap().clone(), // want panic + format, + Some(BLEND_ALPHA), + )?; + + let vertex_buffer = + graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?; + + let pipeline_final = graphics.create_pipeline_with_layouts( + view_final.clone(), + shaders.get("vert_common").unwrap().clone(), // want panic + shaders.get("frag_sprite").unwrap().clone(), // want panic + format, + Some(BLEND_ALPHA), + ImageLayout::TransferSrcOptimal, + ImageLayout::TransferSrcOptimal, + )?; + + let set_fg = + pipeline_final.uniform_sampler(0, view_fg.clone(), graphics.texture_filtering)?; + let set_bg = + pipeline_final.uniform_sampler(0, view_bg.clone(), graphics.texture_filtering)?; + let pass_fg = pipeline_final.create_pass( + [width as _, height as _], + vertex_buffer.clone(), + graphics.quad_indices.clone(), + vec![set_fg], + )?; + let pass_bg = pipeline_final.create_pass( + [width as _, height as _], + vertex_buffer.clone(), + graphics.quad_indices.clone(), + vec![set_bg], + )?; + + let stride = width / RES_DIVIDER; + let rows = height / RES_DIVIDER; + + Ok(Self { + canvas: CanvasData { + data, + width, + height, + graphics: graphics.clone(), + pipeline_bg_color, + pipeline_fg_glyph, + pipeline_bg_sprite, + pipeline_hl_sprite, + pipeline_final, + }, + controls: Vec::new(), + hover_controls: [None, None], + pressed_controls: [None, None], + interact_map: vec![None; stride * rows], + interact_stride: stride, + interact_rows: rows, + view_final, + pass_fg, + pass_bg, + }) + } + + fn interactive_set_idx(&mut self, x: f32, y: f32, w: f32, h: f32, idx: usize) { + let (x, y, w, h) = (x as usize, y as usize, w as usize, h as usize); + + let x_min = (x / RES_DIVIDER).max(0); + let y_min = (y / RES_DIVIDER).max(0); + let x_max = (x_min + (w / RES_DIVIDER)).min(self.interact_stride - 1); + let y_max = (y_min + (h / RES_DIVIDER)).min(self.interact_rows - 1); + + for y in y_min..y_max { + for x in x_min..x_max { + self.interact_map[y * self.interact_stride + x] = Some(idx as u16); + } + } + } + + fn interactive_get_idx(&self, uv: Vec2) -> Option { + let x = (uv.x * self.canvas.width as f32) as usize; + let y = (uv.y * self.canvas.height as f32) as usize; + let x = (x / RES_DIVIDER).max(0).min(self.interact_stride - 1); + let y = (y / RES_DIVIDER).max(0).min(self.interact_rows - 1); + self.interact_map[y * self.interact_stride + x].map(|x| x as usize) + } + + fn render_bg(&mut self, app: &mut AppState) -> anyhow::Result<()> { + let mut cmd_buffer = self + .canvas + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_render_pass(&self.canvas.pipeline_bg_color)?; + for c in self.controls.iter_mut() { + if let Some(fun) = c.on_render_bg { + fun(c, &self.canvas, app, &mut cmd_buffer)?; + } + } + cmd_buffer.end_render_pass()?; + cmd_buffer.build_and_execute_now() + } + + fn render_fg(&mut self, app: &mut AppState) -> anyhow::Result<()> { + let mut cmd_buffer = self + .canvas + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_render_pass(&self.canvas.pipeline_fg_glyph)?; + for c in self.controls.iter_mut() { + if let Some(fun) = c.on_render_fg { + fun(c, &self.canvas, app, &mut cmd_buffer)?; + } + } + cmd_buffer.end_render_pass()?; + cmd_buffer.build_and_execute_now() + } +} + +impl InteractionHandler for Canvas { + fn on_left(&mut self, _app: &mut AppState, pointer: usize) { + self.hover_controls[pointer] = None; + } + fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option { + let old = self.hover_controls[hit.pointer]; + if let Some(i) = self.interactive_get_idx(hit.uv) { + self.hover_controls[hit.pointer] = Some(i); + } else { + self.hover_controls[hit.pointer] = None; + } + if old != self.hover_controls[hit.pointer] { + Some(Haptics { + intensity: 0.1, + duration: 0.01, + frequency: 5.0, + }) + } else { + None + } + } + fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { + let idx = if pressed { + self.interactive_get_idx(hit.uv) + } else { + self.pressed_controls[hit.pointer] + }; + + if let Some(idx) = idx { + let c = &mut self.controls[idx]; + if pressed { + if let Some(ref mut f) = c.on_press { + self.pressed_controls[hit.pointer] = Some(idx); + f(c, &mut self.canvas.data, app, hit.mode); + } + } else if let Some(ref mut f) = c.on_release { + self.pressed_controls[hit.pointer] = None; + f(c, &mut self.canvas.data, app); + } + } + } + fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { + let idx = self.hover_controls[hit.pointer]; + + if let Some(idx) = idx { + let c = &mut self.controls[idx]; + if let Some(ref mut f) = c.on_scroll { + f(c, &mut self.canvas.data, app, delta); + } + } + } +} + +impl OverlayRenderer for Canvas { + fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { + self.render_bg(app)?; + self.render_fg(app) + } + fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { + let mut dirty = false; + + for c in self.controls.iter_mut() { + if let Some(fun) = c.on_update { + fun(c, &mut self.canvas.data, app); + } + if c.dirty { + dirty = true; + c.dirty = false; + } + } + + if dirty { + self.render_bg(app)?; + self.render_fg(app)?; + } + + /* + let image = self.view_final.image().clone(); + if self.first_render { + self.first_render = false; + } else { + self.canvas + .graphics + .transition_layout( + image.clone(), + ImageLayout::TransferSrcOptimal, + ImageLayout::ColorAttachmentOptimal, + ) + .wait(None) + .unwrap(); + } + */ + + let mut cmd_buffer = self + .canvas + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?; + + // static background + cmd_buffer.run_ref(&self.pass_bg)?; + + for (i, c) in self.controls.iter_mut().enumerate() { + if let Some(render) = c.on_render_hl { + if let Some(test) = c.test_highlight { + if let Some(hl_color) = test(c, &mut self.canvas.data, app) { + render(c, &self.canvas, app, &mut cmd_buffer, hl_color)?; + } + } + if self.hover_controls.contains(&Some(i)) { + render( + c, + &self.canvas, + app, + &mut cmd_buffer, + Vec4::new(1., 1., 1., 0.3), + )?; + } + } + } + + // mostly static text + cmd_buffer.run_ref(&self.pass_fg)?; + + cmd_buffer.end_render_pass()?; + cmd_buffer.build_and_execute_now() + + /* + self.canvas + .graphics + .transition_layout( + image, + ImageLayout::ColorAttachmentOptimal, + ImageLayout::TransferSrcOptimal, + ) + .wait(None) + .unwrap(); + */ + } + fn view(&mut self) -> Option> { + Some(self.view_final.clone()) + } +} + +impl OverlayBackend for Canvas { + fn set_renderer(&mut self, _renderer: Box) {} + fn set_interaction(&mut self, _interaction: Box) {} +} diff --git a/src/gui/font.rs b/src/gui/font.rs index 73363b6..6f987a0 100644 --- a/src/gui/font.rs +++ b/src/gui/font.rs @@ -222,7 +222,7 @@ impl FontCache { }; let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - let texture = cmd_buffer.texture2d(bmp.width() as _, bmp.rows() as _, format, &buf)?; + let texture = cmd_buffer.texture2d_raw(bmp.width() as _, bmp.rows() as _, format, &buf)?; cmd_buffer.build_and_execute_now()?; let g = Glyph { diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 2990c28..a48b024 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,37 +1,12 @@ -use std::sync::Arc; - -use anyhow::bail; -use glam::{Vec2, Vec4}; -use vulkano::{ - command_buffer::CommandBufferUsage, - format::Format, - image::{view::ImageView, ImageLayout}, -}; - -use crate::{ - backend::{ - input::{Haptics, InteractionHandler, PointerHit, PointerMode}, - overlay::{OverlayBackend, OverlayRenderer}, - }, - graphics::{ - WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline, WlxPipelineLegacy, BLEND_ALPHA, - }, - state::AppState, -}; - -use self::modular::GuiColor; +use glam::Vec4; +use once_cell::sync::Lazy; +pub mod canvas; pub mod font; pub mod modular; -const RES_DIVIDER: usize = 4; - -struct Rect { - x: f32, - y: f32, - w: f32, - h: f32, -} +pub type GuiColor = Vec4; +pub(super) static FALLBACK_COLOR: Lazy = Lazy::new(|| Vec4::new(1., 0., 1., 1.)); // Parses a color from a HTML hex string pub fn color_parse(html_hex: &str) -> anyhow::Result { @@ -63,775 +38,12 @@ pub fn color_parse(html_hex: &str) -> anyhow::Result { )); } } - bail!( + anyhow::bail!( "Invalid color string: '{}', must be 7 characters long (e.g. #FF00FF)", &html_hex ) } -pub struct CanvasBuilder { - canvas: Canvas, - - pub fg_color: GuiColor, - pub bg_color: GuiColor, - pub font_size: isize, -} - -impl CanvasBuilder { - pub fn new( - width: usize, - height: usize, - graphics: Arc, - format: Format, - data: D, - ) -> anyhow::Result { - Ok(Self { - canvas: Canvas::new(width, height, graphics, format, data)?, - bg_color: Vec4::ZERO, - fg_color: Vec4::ONE, - font_size: 16, - }) - } - - pub fn build(self) -> Canvas { - self.canvas - } - - // Creates a panel with bg_color inherited from the canvas - pub fn panel(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control { - let idx = self.canvas.controls.len(); - self.canvas.controls.push(Control { - rect: Rect { x, y, w, h }, - bg_color: self.bg_color, - on_render_bg: Some(Control::render_rect), - ..Control::new() - }); - &mut self.canvas.controls[idx] - } - - // Creates a label with fg_color, font_size inherited from the canvas - pub fn label(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { - let idx = self.canvas.controls.len(); - self.canvas.controls.push(Control { - rect: Rect { x, y, w, h }, - text, - fg_color: self.fg_color, - size: self.font_size, - on_render_fg: Some(Control::render_text), - ..Control::new() - }); - &mut self.canvas.controls[idx] - } - - // Creates a label with fg_color, font_size inherited from the canvas - #[allow(dead_code)] - pub fn label_centered( - &mut self, - x: f32, - y: f32, - w: f32, - h: f32, - text: Arc, - ) -> &mut Control { - let idx = self.canvas.controls.len(); - self.canvas.controls.push(Control { - rect: Rect { x, y, w, h }, - text, - fg_color: self.fg_color, - size: self.font_size, - on_render_fg: Some(Control::render_text_centered), - ..Control::new() - }); - &mut self.canvas.controls[idx] - } - - // Creates a button with fg_color, bg_color, font_size inherited from the canvas - pub fn button(&mut self, x: f32, y: f32, w: f32, h: f32, text: Arc) -> &mut Control { - let idx = self.canvas.controls.len(); - - self.canvas.interactive_set_idx(x, y, w, h, idx); - self.canvas.controls.push(Control { - rect: Rect { x, y, w, h }, - text, - fg_color: self.fg_color, - bg_color: self.bg_color, - size: self.font_size, - on_render_bg: Some(Control::render_rect), - on_render_fg: Some(Control::render_text_centered), - on_render_hl: Some(Control::render_highlight), - ..Control::new() - }); - - &mut self.canvas.controls[idx] - } - - pub fn key_button( - &mut self, - x: f32, - y: f32, - w: f32, - h: f32, - cap_type: KeyCapType, - label: &[String], - ) -> &mut Control { - let idx = self.canvas.controls.len(); - self.canvas.interactive_set_idx(x, y, w, h, idx); - - self.canvas.controls.push(Control { - rect: Rect { x, y, w, h }, - bg_color: self.bg_color, - on_render_bg: Some(Control::render_rect), - on_render_hl: Some(Control::render_highlight), - ..Control::new() - }); - - let renders = match cap_type { - KeyCapType::Regular => { - let render: ControlRenderer = Control::render_text_centered; - let rect = Rect { - x, - y, - w, - h: h - self.font_size as f32, - }; - vec![(render, rect, 1f32)] - } - KeyCapType::RegularAltGr => { - let render: ControlRenderer = Control::render_text; - let rect0 = Rect { - x: x + 12., - y: y + (self.font_size as f32) + 12., - w, - h, - }; - let rect1 = Rect { - x: x + w * 0.5 + 12., - y: y + h - (self.font_size as f32) + 8., - w, - h, - }; - vec![(render, rect0, 1.0), (render, rect1, 0.8)] - } - KeyCapType::Reversed => { - let render: ControlRenderer = Control::render_text_centered; - let rect0 = Rect { - x, - y: y + 2.0, - w, - h: h * 0.5, - }; - let rect1 = Rect { - x, - y: y + h * 0.5 + 2.0, - w, - h: h * 0.5, - }; - vec![(render, rect1, 1.0), (render, rect0, 0.8)] - } - KeyCapType::ReversedAltGr => { - let render: ControlRenderer = Control::render_text; - let rect0 = Rect { - x: x + 12., - y: y + (self.font_size as f32) + 8., - w, - h, - }; - let rect1 = Rect { - x: x + 12., - y: y + h - (self.font_size as f32) + 4., - w, - h, - }; - let rect2 = Rect { - x: x + w * 0.5 + 8., - y: y + h - (self.font_size as f32) + 4., - w, - h, - }; - vec![ - (render, rect1, 1.0), - (render, rect0, 0.8), - (render, rect2, 0.8), - ] - } - }; - - for (idx, (render, rect, alpha)) in renders.into_iter().enumerate() { - if idx >= label.len() { - break; - } - - self.canvas.controls.push(Control { - rect, - text: Arc::from(label[idx].as_str()), - fg_color: self.fg_color * alpha, - size: self.font_size, - on_render_fg: Some(render), - ..Control::new() - }); - } - - &mut self.canvas.controls[idx] - } -} - -pub struct CanvasData { - pub data: D, - pub width: usize, - pub height: usize, - - graphics: Arc, - - pipeline_bg_color: Arc>, - pipeline_fg_glyph: Arc>, - pipeline_final: Arc>, -} - -pub struct Canvas { - controls: Vec>, - canvas: CanvasData, - - hover_controls: [Option; 2], - pressed_controls: [Option; 2], - - interact_map: Vec>, - interact_stride: usize, - interact_rows: usize, - - view_final: Arc, - - pass_fg: WlxPass, - pass_bg: WlxPass, -} - -impl Canvas { - fn new( - width: usize, - height: usize, - graphics: Arc, - format: Format, - data: D, - ) -> anyhow::Result { - let tex_fg = graphics.render_texture(width as _, height as _, format)?; - let tex_bg = graphics.render_texture(width as _, height as _, format)?; - let tex_final = graphics.render_texture(width as _, height as _, format)?; - - let view_fg = ImageView::new_default(tex_fg.clone())?; - let view_bg = ImageView::new_default(tex_bg.clone())?; - let view_final = ImageView::new_default(tex_final.clone())?; - - let Ok(shaders) = graphics.shared_shaders.read() else { - bail!("Failed to lock shared shaders for reading"); - }; - - let pipeline_bg_color = graphics.create_pipeline( - view_bg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_color").unwrap().clone(), // want panic - format, - Some(BLEND_ALPHA), - )?; - - let pipeline_fg_glyph = graphics.create_pipeline( - view_fg.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_glyph").unwrap().clone(), // want panic - format, - Some(BLEND_ALPHA), - )?; - - let vertex_buffer = - graphics.upload_verts(width as _, height as _, 0., 0., width as _, height as _)?; - - let pipeline_final = graphics.create_pipeline_with_layouts( - view_final.clone(), - shaders.get("vert_common").unwrap().clone(), // want panic - shaders.get("frag_sprite").unwrap().clone(), // want panic - format, - Some(BLEND_ALPHA), - ImageLayout::TransferSrcOptimal, - ImageLayout::TransferSrcOptimal, - )?; - - let set_fg = - pipeline_final.uniform_sampler(0, view_fg.clone(), graphics.texture_filtering)?; - let set_bg = - pipeline_final.uniform_sampler(0, view_bg.clone(), graphics.texture_filtering)?; - let pass_fg = pipeline_final.create_pass( - [width as _, height as _], - vertex_buffer.clone(), - graphics.quad_indices.clone(), - vec![set_fg], - )?; - let pass_bg = pipeline_final.create_pass( - [width as _, height as _], - vertex_buffer.clone(), - graphics.quad_indices.clone(), - vec![set_bg], - )?; - - let stride = width / RES_DIVIDER; - let rows = height / RES_DIVIDER; - - Ok(Self { - canvas: CanvasData { - data, - width, - height, - graphics: graphics.clone(), - pipeline_bg_color, - pipeline_fg_glyph, - pipeline_final, - }, - controls: Vec::new(), - hover_controls: [None, None], - pressed_controls: [None, None], - interact_map: vec![None; stride * rows], - interact_stride: stride, - interact_rows: rows, - view_final, - pass_fg, - pass_bg, - }) - } - - fn interactive_set_idx(&mut self, x: f32, y: f32, w: f32, h: f32, idx: usize) { - let (x, y, w, h) = (x as usize, y as usize, w as usize, h as usize); - - let x_min = (x / RES_DIVIDER).max(0); - let y_min = (y / RES_DIVIDER).max(0); - let x_max = (x_min + (w / RES_DIVIDER)).min(self.interact_stride - 1); - let y_max = (y_min + (h / RES_DIVIDER)).min(self.interact_rows - 1); - - for y in y_min..y_max { - for x in x_min..x_max { - self.interact_map[y * self.interact_stride + x] = Some(idx as u16); - } - } - } - - fn interactive_get_idx(&self, uv: Vec2) -> Option { - let x = (uv.x * self.canvas.width as f32) as usize; - let y = (uv.y * self.canvas.height as f32) as usize; - let x = (x / RES_DIVIDER).max(0).min(self.interact_stride - 1); - let y = (y / RES_DIVIDER).max(0).min(self.interact_rows - 1); - self.interact_map[y * self.interact_stride + x].map(|x| x as usize) - } - - fn render_bg(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut cmd_buffer = self - .canvas - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_bg_color)?; - for c in self.controls.iter_mut() { - if let Some(fun) = c.on_render_bg { - fun(c, &self.canvas, app, &mut cmd_buffer)?; - } - } - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - } - - fn render_fg(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut cmd_buffer = self - .canvas - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_fg_glyph)?; - for c in self.controls.iter_mut() { - if let Some(fun) = c.on_render_fg { - fun(c, &self.canvas, app, &mut cmd_buffer)?; - } - } - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - } -} - -impl InteractionHandler for Canvas { - fn on_left(&mut self, _app: &mut AppState, pointer: usize) { - self.hover_controls[pointer] = None; - } - fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> Option { - let old = self.hover_controls[hit.pointer]; - if let Some(i) = self.interactive_get_idx(hit.uv) { - self.hover_controls[hit.pointer] = Some(i); - } else { - self.hover_controls[hit.pointer] = None; - } - if old != self.hover_controls[hit.pointer] { - Some(Haptics { - intensity: 0.1, - duration: 0.01, - frequency: 5.0, - }) - } else { - None - } - } - fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { - let idx = if pressed { - self.interactive_get_idx(hit.uv) - } else { - self.pressed_controls[hit.pointer] - }; - - if let Some(idx) = idx { - let c = &mut self.controls[idx]; - if pressed { - if let Some(ref mut f) = c.on_press { - self.pressed_controls[hit.pointer] = Some(idx); - f(c, &mut self.canvas.data, app, hit.mode); - } - } else if let Some(ref mut f) = c.on_release { - self.pressed_controls[hit.pointer] = None; - f(c, &mut self.canvas.data, app); - } - } - } - fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { - let idx = self.hover_controls[hit.pointer]; - - if let Some(idx) = idx { - let c = &mut self.controls[idx]; - if let Some(ref mut f) = c.on_scroll { - f(c, &mut self.canvas.data, app, delta); - } - } - } -} - -impl OverlayRenderer for Canvas { - fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> { - self.render_bg(app)?; - self.render_fg(app) - } - fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> { - Ok(()) - } - fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> { - Ok(()) - } - fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> { - let mut dirty = false; - - for c in self.controls.iter_mut() { - if let Some(fun) = c.on_update { - fun(c, &mut self.canvas.data, app); - } - if c.dirty { - dirty = true; - c.dirty = false; - } - } - - if dirty { - self.render_bg(app)?; - self.render_fg(app)?; - } - - /* - let image = self.view_final.image().clone(); - if self.first_render { - self.first_render = false; - } else { - self.canvas - .graphics - .transition_layout( - image.clone(), - ImageLayout::TransferSrcOptimal, - ImageLayout::ColorAttachmentOptimal, - ) - .wait(None) - .unwrap(); - } - */ - - let mut cmd_buffer = self - .canvas - .graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; - cmd_buffer.begin_render_pass(&self.canvas.pipeline_final)?; - - // static background - cmd_buffer.run_ref(&self.pass_bg)?; - - for (i, c) in self.controls.iter_mut().enumerate() { - if let Some(render) = c.on_render_hl { - if let Some(test) = c.test_highlight { - if let Some(hl_color) = test(c, &mut self.canvas.data, app) { - render(c, &self.canvas, app, &mut cmd_buffer, hl_color)?; - } - } - if self.hover_controls.contains(&Some(i)) { - render( - c, - &self.canvas, - app, - &mut cmd_buffer, - Vec4::new(1., 1., 1., 0.3), - )?; - } - } - } - - // mostly static text - cmd_buffer.run_ref(&self.pass_fg)?; - - cmd_buffer.end_render_pass()?; - cmd_buffer.build_and_execute_now() - - /* - self.canvas - .graphics - .transition_layout( - image, - ImageLayout::ColorAttachmentOptimal, - ImageLayout::TransferSrcOptimal, - ) - .wait(None) - .unwrap(); - */ - } - fn view(&mut self) -> Option> { - Some(self.view_final.clone()) - } -} - -impl OverlayBackend for Canvas { - fn set_renderer(&mut self, _renderer: Box) {} - fn set_interaction(&mut self, _interaction: Box) {} -} - -pub type ControlRenderer = - fn(&Control, &CanvasData, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>; - -pub type ControlRendererHl = fn( - &Control, - &CanvasData, - &mut AppState, - &mut WlxCommandBuffer, - Vec4, -) -> anyhow::Result<()>; - -pub struct Control { - pub state: Option, - rect: Rect, - fg_color: GuiColor, - bg_color: GuiColor, - text: Arc, - size: isize, - dirty: bool, - - pub on_update: Option, - pub on_press: Option, - pub on_release: Option, - pub on_scroll: Option, - pub test_highlight: Option Option>, - - on_render_bg: Option>, - on_render_hl: Option>, - on_render_fg: Option>, -} - -impl Control { - fn new() -> Self { - Self { - rect: Rect { - x: 0., - y: 0., - w: 0., - h: 0., - }, - fg_color: Vec4::ONE, - bg_color: Vec4::ZERO, - text: Arc::from(""), - dirty: true, - size: 24, - state: None, - on_update: None, - on_render_bg: None, - on_render_hl: None, - on_render_fg: None, - test_highlight: None, - on_press: None, - on_release: None, - on_scroll: None, - } - } - - #[inline(always)] - pub fn set_text(&mut self, text: &str) { - if *self.text == *text { - return; - } - self.text = text.into(); - self.dirty = true; - } - - #[inline(always)] - pub fn set_fg_color(&mut self, color: GuiColor) { - if self.fg_color == color { - return; - } - self.fg_color = color; - self.dirty = true; - } - - fn render_rect( - &self, - canvas: &CanvasData, - _: &mut AppState, - cmd_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result<()> { - let pass = { - let vertex_buffer = canvas.graphics.upload_verts( - canvas.width as _, - canvas.height as _, - self.rect.x, - self.rect.y, - self.rect.w, - self.rect.h, - )?; - let set0 = canvas - .pipeline_bg_color - .uniform_buffer(0, self.bg_color.to_array().to_vec())?; - canvas.pipeline_bg_color.create_pass( - [canvas.width as _, canvas.height as _], - vertex_buffer, - canvas.graphics.quad_indices.clone(), - vec![set0], - )? - }; - - cmd_buffer.run_ref(&pass) - } - - fn render_highlight( - &self, - canvas: &CanvasData, - _: &mut AppState, - cmd_buffer: &mut WlxCommandBuffer, - color: GuiColor, - ) -> anyhow::Result<()> { - let vertex_buffer = canvas.graphics.upload_verts( - canvas.width as _, - canvas.height as _, - self.rect.x, - self.rect.y, - self.rect.w, - self.rect.h, - )?; - - let set0 = canvas - .pipeline_bg_color - .uniform_buffer(0, color.to_array().to_vec())?; - - let pass = canvas.pipeline_bg_color.create_pass( - [canvas.width as _, canvas.height as _], - vertex_buffer.clone(), - canvas.graphics.quad_indices.clone(), - vec![set0], - )?; - - cmd_buffer.run_ref(&pass) - } - - fn render_text( - &self, - canvas: &CanvasData, - app: &mut AppState, - cmd_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result<()> { - let mut cur_y = self.rect.y; - for line in self.text.lines() { - let mut cur_x = self.rect.x; - for glyph in app - .fc - .get_glyphs(line, self.size, canvas.graphics.clone())? - { - if let Some(tex) = glyph.tex.clone() { - let vertex_buffer = canvas.graphics.upload_verts( - canvas.width as _, - canvas.height as _, - cur_x + glyph.left, - cur_y - glyph.top, - glyph.width, - glyph.height, - )?; - let set0 = canvas.pipeline_fg_glyph.uniform_sampler( - 0, - ImageView::new_default(tex)?, - app.graphics.texture_filtering, - )?; - let set1 = canvas - .pipeline_fg_glyph - .uniform_buffer(1, self.fg_color.to_array().to_vec())?; - let pass = canvas.pipeline_fg_glyph.create_pass( - [canvas.width as _, canvas.height as _], - vertex_buffer, - canvas.graphics.quad_indices.clone(), - vec![set0, set1], - )?; - cmd_buffer.run_ref(&pass)?; - } - cur_x += glyph.advance; - } - cur_y += (self.size as f32) * 1.5; - } - Ok(()) - } - fn render_text_centered( - &self, - canvas: &CanvasData, - app: &mut AppState, - cmd_buffer: &mut WlxCommandBuffer, - ) -> anyhow::Result<()> { - let (w, h) = app - .fc - .get_text_size(&self.text, self.size, canvas.graphics.clone())?; - - let mut cur_y = self.rect.y + (self.rect.h) - (h * 0.5) - (self.size as f32 * 0.25); - for line in self.text.lines() { - let mut cur_x = self.rect.x + (self.rect.w * 0.5) - (w * 0.5); - for glyph in app - .fc - .get_glyphs(line, self.size, canvas.graphics.clone())? - { - if let Some(tex) = glyph.tex.clone() { - let vertex_buffer = canvas.graphics.upload_verts( - canvas.width as _, - canvas.height as _, - cur_x + glyph.left, - cur_y - glyph.top, - glyph.width, - glyph.height, - )?; - let set0 = canvas.pipeline_fg_glyph.uniform_sampler( - 0, - ImageView::new_default(tex)?, - app.graphics.texture_filtering, - )?; - let set1 = canvas - .pipeline_fg_glyph - .uniform_buffer(1, self.fg_color.to_array().to_vec())?; - let pass = canvas.pipeline_fg_glyph.create_pass( - [canvas.width as _, canvas.height as _], - vertex_buffer, - canvas.graphics.quad_indices.clone(), - vec![set0, set1], - )?; - cmd_buffer.run_ref(&pass)?; - } - cur_x += glyph.advance; - } - cur_y += (self.size as f32) * 1.5; - } - Ok(()) - } -} - pub enum KeyCapType { /// Label is in center of keycap Regular, diff --git a/src/gui/modular/mod.rs b/src/gui/modular/mod.rs index 239ea0a..d3eaa25 100644 --- a/src/gui/modular/mod.rs +++ b/src/gui/modular/mod.rs @@ -1,29 +1,30 @@ pub mod button; pub mod label; -//pub mod slider; -use std::sync::Arc; +use std::{fs::File, sync::Arc}; use glam::Vec4; -use once_cell::sync::Lazy; use serde::Deserialize; +use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView}; -use crate::{backend::common::OverlaySelector, state::AppState}; +use crate::{ + backend::common::OverlaySelector, config::AStrMapExt, config_io::CONFIG_ROOT_PATH, + graphics::dds::WlxCommandBufferDds, state::AppState, +}; use self::{ button::{modular_button_init, ButtonAction, ButtonData, OverlayAction}, label::{modular_label_init, LabelContent, LabelData}, }; -use super::{color_parse, Canvas, CanvasBuilder, Control}; +use super::{ + canvas::{builder::CanvasBuilder, control::Control, Canvas}, + color_parse, GuiColor, FALLBACK_COLOR, +}; type ModularControl = Control<(), ModularData>; type ExecArgs = Vec>; -pub type GuiColor = Vec4; - -static FALLBACK_COLOR: Lazy = Lazy::new(|| Vec4::new(1., 0., 1., 1.)); - #[derive(Deserialize)] pub struct ModularUiConfig { pub width: f32, @@ -69,6 +70,11 @@ pub enum ModularElement { #[serde(flatten)] data: LabelContent, }, + Sprite { + rect: [f32; 4], + sprite: Arc, + sprite_st: Option<[f32; 4]>, + }, Button { rect: [f32; 4], font_size: isize, @@ -120,7 +126,7 @@ pub enum ModularData { pub fn modular_canvas( size: &[u32; 2], elements: &[ModularElement], - state: &AppState, + state: &mut AppState, ) -> anyhow::Result> { let mut canvas = CanvasBuilder::new( size[0] as _, @@ -161,6 +167,25 @@ pub fn modular_canvas( let label = canvas.label_centered(*x, *y, *w, *h, empty_str.clone()); modular_label_init(label, data); } + ModularElement::Sprite { + rect: [x, y, w, h], + sprite, + sprite_st, + } => match sprite_from_path(sprite.clone(), state) { + Ok(view) => { + let sprite = canvas.sprite(*x, *y, *w, *h); + sprite.fg_color = Vec4::ONE; + sprite.set_sprite(view); + + let st = sprite_st + .map(|st| Vec4::from_slice(&st)) + .unwrap_or_else(|| Vec4::new(1., 1., 0., 0.)); + sprite.set_sprite_st(st); + } + Err(e) => { + log::warn!("Could not load custom UI sprite: {:?}", e); + } + }, ModularElement::Button { rect: [x, y, w, h], font_size, @@ -349,3 +374,29 @@ pub fn color_parse_or_default(color: &str) -> GuiColor { *FALLBACK_COLOR }) } + +fn sprite_from_path(path: Arc, app: &mut AppState) -> anyhow::Result> { + if let Some(view) = app.sprites.arc_get(&path) { + return Ok(view.clone()); + } + + let real_path = CONFIG_ROOT_PATH.join(&*path); + + let Ok(f) = File::open(real_path) else { + anyhow::bail!("Could not open custom sprite at: {}", path); + }; + + let mut command_buffer = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + + match command_buffer.texture2d_dds(f) { + Ok(image) => { + command_buffer.build_and_execute_now()?; + Ok(ImageView::new_default(image)?) + } + Err(e) => { + anyhow::bail!("Could not use custom sprite at: {}\n{:?}", path, e); + } + } +} diff --git a/src/image.png b/src/image.png deleted file mode 100644 index b9e2802..0000000 Binary files a/src/image.png and /dev/null differ diff --git a/src/overlays/anchor.rs b/src/overlays/anchor.rs index dcbe9fd..1e44cf0 100644 --- a/src/overlays/anchor.rs +++ b/src/overlays/anchor.rs @@ -9,7 +9,7 @@ use crate::state::AppState; pub static ANCHOR_NAME: Lazy> = Lazy::new(|| Arc::from("anchor")); -pub fn create_anchor(state: &AppState) -> anyhow::Result> +pub fn create_anchor(state: &mut AppState) -> anyhow::Result> where O: Default, { diff --git a/src/overlays/custom.rs b/src/overlays/custom.rs index 4a773d6..c0296a1 100644 --- a/src/overlays/custom.rs +++ b/src/overlays/custom.rs @@ -12,7 +12,7 @@ use crate::{ const SETTINGS_NAME: &str = "settings"; pub fn create_custom( - state: &AppState, + state: &mut AppState, name: Arc, ) -> Option<(OverlayState, Box)> { let config = if &*name == SETTINGS_NAME { diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index 064d028..0569fe1 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -10,7 +10,10 @@ use crate::{ overlay::{OverlayData, OverlayState}, }, config::{self, ConfigType}, - gui::{color_parse, CanvasBuilder, Control, KeyCapType}, + gui::{ + canvas::{builder::CanvasBuilder, control::Control}, + color_parse, KeyCapType, + }, hid::{ get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META, NUM_LOCK, SHIFT, SUPER, diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index 71ccc80..d980576 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -214,7 +214,7 @@ impl ScreenPipeline { ]; let mouse_tex = - uploads.texture2d(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?; + uploads.texture2d_raw(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?; self.mouse = Some(ImageView::new_default(mouse_tex)?); Ok(()) } @@ -507,8 +507,12 @@ impl OverlayRenderer for ScreenRenderer { let data = unsafe { slice::from_raw_parts(map, len) }; - let image = - upload.texture2d(frame.format.width, frame.format.height, format, data)?; + let image = upload.texture2d_raw( + frame.format.width, + frame.format.height, + format, + data, + )?; upload.build_and_execute_now()?; unsafe { libc::munmap(map as *mut _, len) }; @@ -526,8 +530,12 @@ impl OverlayRenderer for ScreenRenderer { let data = unsafe { slice::from_raw_parts(frame.ptr as *const u8, frame.size) }; - let image = - upload.texture2d(frame.format.width, frame.format.height, format, data)?; + let image = upload.texture2d_raw( + frame.format.width, + frame.format.height, + format, + data, + )?; let pipeline = Some(match self.pipeline { Some(ref mut p) => p, diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 1d6d884..01f59af 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -11,7 +11,7 @@ use crate::{ overlay::{OverlayBackend, OverlayState, RelativeTo}, task::TaskType, }, - gui::{color_parse, CanvasBuilder}, + gui::{canvas::builder::CanvasBuilder, color_parse}, state::{AppState, LeftRight}, }; diff --git a/src/overlays/watch.rs b/src/overlays/watch.rs index c67ad0c..a28d847 100644 --- a/src/overlays/watch.rs +++ b/src/overlays/watch.rs @@ -4,15 +4,15 @@ use crate::{ backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo}, config::{load_known_yaml, ConfigType}, gui::{ + canvas::Canvas, modular::{modular_canvas, ModularData, ModularUiConfig}, - Canvas, }, state::AppState, }; pub const WATCH_NAME: &str = "watch"; -pub fn create_watch(state: &AppState) -> anyhow::Result> +pub fn create_watch(state: &mut AppState) -> anyhow::Result> where O: Default, { @@ -40,7 +40,7 @@ where pub fn create_watch_canvas( config: Option, - state: &AppState, + state: &mut AppState, ) -> anyhow::Result> { let config = config.unwrap_or_else(|| load_known_yaml::(ConfigType::Watch)); diff --git a/src/res/table_mountain_2.dds b/src/res/table_mountain_2.dds new file mode 100644 index 0000000..edaac3e Binary files /dev/null and b/src/res/table_mountain_2.dds differ diff --git a/src/shaders/mod.rs b/src/shaders/mod.rs index 2d36739..350de3d 100644 --- a/src/shaders/mod.rs +++ b/src/shaders/mod.rs @@ -61,6 +61,52 @@ pub mod frag_glyph { } } +pub mod frag_sprite2 { + vulkano_shaders::shader! { + ty: "fragment", + src: r"#version 310 es + precision highp float; + + layout (location = 0) in vec2 in_uv; + layout (location = 0) out vec4 out_color; + + layout (set = 0, binding = 0) uniform sampler2D in_texture; + layout (set = 1, binding = 0) uniform UniBlock { + uniform vec4 st; + uniform vec4 mul; + }; + + void main() + { + out_color = texture(in_texture, (in_uv * st.xy) + st.zw) * mul; + } + ", + } +} + +pub mod frag_sprite2_hl { + vulkano_shaders::shader! { + ty: "fragment", + src: r"#version 310 es + precision highp float; + + layout (location = 0) in vec2 in_uv; + layout (location = 0) out vec4 out_color; + + layout (set = 0, binding = 0) uniform sampler2D in_texture; + layout (set = 1, binding = 0) uniform UniBlock { + uniform vec4 st; + uniform vec4 mul; + }; + + void main() + { + out_color = texture(in_texture, (in_uv * st.xy) + st.zw).a * mul; + } + ", + } +} + pub mod frag_sprite { vulkano_shaders::shader! { ty: "fragment", @@ -80,6 +126,31 @@ pub mod frag_sprite { } } +pub mod frag_grid { + vulkano_shaders::shader! { + ty: "fragment", + src: r"#version 310 es + precision highp float; + + layout (location = 0) in vec2 in_uv; + layout (location = 0) out vec4 out_color; + + void main() + { + float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0); + float grid; + + if (fract(in_uv.x / 0.0005) < 0.01 || fract(in_uv.y / 0.0005) < 0.01) { + grid = 1.0; + } else { + grid = 0.0; + } + out_color = vec4(1.0, 1.0, 1.0, grid * fade); + } + ", + } +} + pub mod frag_screen { vulkano_shaders::shader! { ty: "fragment", @@ -153,7 +224,7 @@ pub mod frag_swapchain { void main() { out_color = texture(in_texture, in_uv); - out_color.a = alpha; + out_color.a *= alpha; } ", } diff --git a/src/state.rs b/src/state.rs index 2192d14..e092136 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,16 +6,20 @@ use idmap::IdMap; use rodio::{Decoder, OutputStream, OutputStreamHandle, Source}; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; +use vulkano::image::view::ImageView; use crate::{ backend::{input::InputState, task::TaskContainer}, - config::GeneralConfig, + config::{AStrMap, GeneralConfig}, config_io, graphics::WlxGraphics, gui::font::FontCache, hid::HidProvider, overlays::toast::{DisplayMethod, ToastTopic}, - shaders::{frag_color, frag_glyph, frag_screen, frag_sprite, frag_swapchain, vert_common}, + shaders::{ + frag_color, frag_glyph, frag_grid, frag_screen, frag_sprite, frag_sprite2, frag_sprite2_hl, + frag_swapchain, vert_common, + }, }; pub struct AppState { @@ -28,6 +32,7 @@ pub struct AppState { pub audio: AudioOutput, pub screens: SmallVec<[ScreenMeta; 8]>, pub anchor: Affine3A, + pub sprites: AStrMap>, } impl AppState { @@ -47,9 +52,18 @@ impl AppState { let shader = frag_glyph::load(graphics.device.clone())?; shaders.insert("frag_glyph", shader); + let shader = frag_grid::load(graphics.device.clone())?; + shaders.insert("frag_grid", shader); + let shader = frag_sprite::load(graphics.device.clone())?; shaders.insert("frag_sprite", shader); + let shader = frag_sprite2::load(graphics.device.clone())?; + shaders.insert("frag_sprite2", shader); + + let shader = frag_sprite2_hl::load(graphics.device.clone())?; + shaders.insert("frag_sprite2_hl", shader); + let shader = frag_screen::load(graphics.device.clone())?; shaders.insert("frag_screen", shader); @@ -69,6 +83,7 @@ impl AppState { audio: AudioOutput::new(), screens: smallvec![], anchor: Affine3A::IDENTITY, + sprites: AStrMap::new(), }) } }