feat: ui sprites + openxr skybox

This commit is contained in:
galister
2024-07-24 22:58:42 +09:00
parent 17addcde78
commit 7a6040bfee
30 changed files with 1926 additions and 1063 deletions

385
Cargo.lock generated
View File

@@ -86,7 +86,7 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"cc", "cc",
"cesu8", "cesu8",
"jni 0.21.1", "jni",
"jni-sys", "jni-sys",
"libc", "libc",
"log", "log",
@@ -207,9 +207,9 @@ dependencies = [
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@@ -229,7 +229,7 @@ version = "0.38.0+1.3.281"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
dependencies = [ dependencies = [
"libloading 0.8.4", "libloading",
] ]
[[package]] [[package]]
@@ -276,9 +276,9 @@ dependencies = [
[[package]] [[package]]
name = "async-executor" name = "async-executor"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
dependencies = [ dependencies = [
"async-task", "async-task",
"concurrent-queue", "concurrent-queue",
@@ -367,14 +367,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
name = "async-signal" name = "async-signal"
version = "0.2.8" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32"
dependencies = [ dependencies = [
"async-io", "async-io",
"async-lock", "async-lock",
@@ -402,7 +402,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -456,7 +456,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -468,7 +468,7 @@ dependencies = [
"autocxx-engine", "autocxx-engine",
"env_logger", "env_logger",
"indexmap 1.9.3", "indexmap 1.9.3",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -494,7 +494,7 @@ dependencies = [
"regex", "regex",
"rustversion", "rustversion",
"serde_json", "serde_json",
"syn 2.0.70", "syn 2.0.72",
"tempfile", "tempfile",
"thiserror", "thiserror",
"version_check", "version_check",
@@ -510,7 +510,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -527,7 +527,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"syn 2.0.70", "syn 2.0.72",
"thiserror", "thiserror",
] ]
@@ -552,6 +552,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bcdec_rs"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9934c2b68e46448d814db20e34a840ef9b4e7b3b7c8b1da91161481230f6350"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.69.4" version = "0.69.4"
@@ -570,7 +576,7 @@ dependencies = [
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -642,7 +648,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -653,23 +659,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
[[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",
]
[[package]] [[package]]
name = "calloop" name = "calloop"
@@ -685,25 +677,13 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "calloop-wayland-source" name = "calloop-wayland-source"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [ dependencies = [
"calloop 0.13.0", "calloop",
"rustix", "rustix",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
@@ -711,13 +691,12 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.0" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
"once_cell",
] ]
[[package]] [[package]]
@@ -807,14 +786,14 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [ dependencies = [
"glob", "glob",
"libc", "libc",
"libloading 0.8.4", "libloading",
] ]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.9" version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -822,9 +801,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.9" version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -841,7 +820,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1034,7 +1013,7 @@ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"coreaudio-rs", "coreaudio-rs",
"dasp_sample", "dasp_sample",
"jni 0.21.1", "jni",
"js-sys", "js-sys",
"libc", "libc",
"mach2", "mach2",
@@ -1153,7 +1132,7 @@ dependencies = [
"codespan-reporting", "codespan-reporting",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1170,7 +1149,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1193,7 +1172,7 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1204,7 +1183,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1224,6 +1203,18 @@ dependencies = [
"winapi", "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]] [[package]]
name = "derivative" name = "derivative"
version = "2.2.0" version = "2.2.0"
@@ -1245,7 +1236,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1270,7 +1261,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [ dependencies = [
"libloading 0.8.4", "libloading",
] ]
[[package]] [[package]]
@@ -1312,6 +1303,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" 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]] [[package]]
name = "enumflags2" name = "enumflags2"
version = "0.7.10" version = "0.7.10"
@@ -1330,28 +1332,28 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
name = "enumset" name = "enumset"
version = "1.1.3" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293"
dependencies = [ dependencies = [
"enumset_derive", "enumset_derive",
] ]
[[package]] [[package]]
name = "enumset_derive" name = "enumset_derive"
version = "0.8.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1466,7 +1468,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1575,7 +1577,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -1795,6 +1797,19 @@ dependencies = [
"unicode-normalization", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@@ -1834,9 +1849,9 @@ dependencies = [
[[package]] [[package]]
name = "input-linux-sys" name = "input-linux-sys"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a687a25a4973027df9153753a5589f97fe1e958f694a34eea5606ae65299ab8" checksum = "1c7ef95c35c8ef8d114f5e197a5ac9554dc4afdd19ae78ae1fd0fc0944cb1340"
dependencies = [ dependencies = [
"libc", "libc",
"nix 0.26.4", "nix 0.26.4",
@@ -1890,20 +1905,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 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]] [[package]]
name = "jni" name = "jni"
version = "0.21.1" version = "0.21.1"
@@ -1928,9 +1929,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.31" version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -1990,19 +1991,9 @@ dependencies = [
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.7.4" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libloading"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.52.6", "windows-targets 0.52.6",
@@ -2174,7 +2165,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -2336,7 +2327,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -2366,7 +2357,7 @@ dependencies = [
"proc-macro-crate 2.0.2", "proc-macro-crate 2.0.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -2596,7 +2587,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
dependencies = [ dependencies = [
"jni 0.21.1", "jni",
"ndk 0.8.0", "ndk 0.8.0",
"ndk-context", "ndk-context",
"num-derive", "num-derive",
@@ -2621,23 +2612,20 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "openxr" name = "openxr"
version = "0.17.1" version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76"
checksum = "d7d5c194407c4fb5d3bf08c34ae57f3ea6cc9d9cfbe0594ce066896c809d9215"
dependencies = [ dependencies = [
"libc", "libc",
"libloading 0.7.4", "libloading",
"ndk-context", "ndk-context",
"openxr-sys", "openxr-sys",
] ]
[[package]] [[package]]
name = "openxr-sys" name = "openxr-sys"
version = "0.9.3" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/galister/openxrs?rev=af4a55d#af4a55df60125491c80c61464c824219c6019b76"
checksum = "fa8f022053ecd7989d86f867b4fb8c3520347612b9d637e217077a0d6b4a6634"
dependencies = [ dependencies = [
"jni 0.19.0",
"libc", "libc",
] ]
@@ -2729,7 +2717,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.5.2", "redox_syscall 0.5.3",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@@ -2786,7 +2774,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -2855,7 +2843,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -2943,7 +2931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3093,9 +3081,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.2" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
] ]
@@ -3259,14 +3247,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "sctk-adwaita" name = "sctk-adwaita"
version = "0.9.1" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7555fcb4f753d095d734fdefebb0ad8c98478a21db500492d87c55913d3b0086" checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"log", "log",
"memmap2 0.9.4", "memmap2 0.9.4",
"smithay-client-toolkit 0.18.1", "smithay-client-toolkit",
"tiny-skia", "tiny-skia",
] ]
@@ -3293,7 +3281,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3326,7 +3314,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3444,39 +3432,14 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
version = "0.18.1" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
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"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"bytemuck", "bytemuck",
"calloop 0.13.0", "calloop",
"calloop-wayland-source 0.3.0", "calloop-wayland-source",
"cursor-icon", "cursor-icon",
"libc", "libc",
"log", "log",
@@ -3488,8 +3451,8 @@ dependencies = [
"wayland-client", "wayland-client",
"wayland-csd-frame", "wayland-csd-frame",
"wayland-cursor", "wayland-cursor",
"wayland-protocols 0.32.2", "wayland-protocols",
"wayland-protocols-wlr 0.3.2", "wayland-protocols-wlr",
"wayland-scanner", "wayland-scanner",
"xkbcommon", "xkbcommon",
"xkeysym", "xkeysym",
@@ -3541,7 +3504,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3557,9 +3520,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.70" version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3623,22 +3586,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.61" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.61" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3764,7 +3727,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3896,7 +3859,7 @@ dependencies = [
"half", "half",
"heck 0.4.1", "heck 0.4.1",
"indexmap 2.2.6", "indexmap 2.2.6",
"libloading 0.8.4", "libloading",
"nom", "nom",
"objc", "objc",
"once_cell", "once_cell",
@@ -3921,7 +3884,7 @@ dependencies = [
"proc-macro-crate 2.0.2", "proc-macro-crate 2.0.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]
@@ -3934,7 +3897,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"shaderc", "shaderc",
"syn 2.0.70", "syn 2.0.72",
"vulkano", "vulkano",
] ]
@@ -3975,7 +3938,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4009,7 +3972,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4022,9 +3985,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "269c04f203640d0da2092d1b8d89a2d081714ae3ac2f1b53e99f205740517198" checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
@@ -4036,9 +3999,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.4" version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bd0f46c069d3382a36c8666c1b9ccef32b8b04f41667ca1fef06a1adcc2982" checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"rustix", "rustix",
@@ -4059,9 +4022,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-cursor" name = "wayland-cursor"
version = "0.31.4" version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09414bcf0fd8d9577d73e9ac4659ebc45bcc9cff1980a350543ad8e50ee263b2" checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95"
dependencies = [ dependencies = [
"rustix", "rustix",
"wayland-client", "wayland-client",
@@ -4070,21 +4033,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.31.2" version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa"
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"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"wayland-backend", "wayland-backend",
@@ -4094,48 +4045,35 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-plasma" name = "wayland-protocols-plasma"
version = "0.2.0" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-scanner", "wayland-scanner",
] ]
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.2.0" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols 0.31.2", "wayland-protocols",
"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-scanner", "wayland-scanner",
] ]
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.3" version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edf466fc49a4feb65a511ca403fec3601494d0dee85dbf37fff6fa0dd4eec3b6" checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml 0.34.0", "quick-xml 0.34.0",
@@ -4144,9 +4082,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.3" version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6754825230fa5b27bafaa28c30b3c9e72c55530581220cef401fa422c0fae7" checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148"
dependencies = [ dependencies = [
"dlib", "dlib",
"log", "log",
@@ -4460,9 +4398,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.30.3" version = "0.30.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f45a7b7e2de6af35448d7718dab6d95acec466eb3bb7a56f4d31d1af754004" checksum = "4225ddd8ab67b8b59a2fee4b34889ebf13c0460c1c3fa297c58e21eb87801b33"
dependencies = [ dependencies = [
"ahash", "ahash",
"android-activity", "android-activity",
@@ -4470,7 +4408,7 @@ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"block2", "block2",
"bytemuck", "bytemuck",
"calloop 0.12.4", "calloop",
"cfg_aliases 0.2.1", "cfg_aliases 0.2.1",
"concurrent-queue", "concurrent-queue",
"core-foundation", "core-foundation",
@@ -4492,7 +4430,7 @@ dependencies = [
"redox_syscall 0.4.1", "redox_syscall 0.4.1",
"rustix", "rustix",
"sctk-adwaita", "sctk-adwaita",
"smithay-client-toolkit 0.18.1", "smithay-client-toolkit",
"smol_str", "smol_str",
"tracing", "tracing",
"unicode-segmentation", "unicode-segmentation",
@@ -4500,7 +4438,7 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-protocols-plasma", "wayland-protocols-plasma",
"web-sys", "web-sys",
"web-time", "web-time",
@@ -4532,9 +4470,9 @@ dependencies = [
"once_cell", "once_cell",
"pipewire", "pipewire",
"rxscreen", "rxscreen",
"smithay-client-toolkit 0.19.1", "smithay-client-toolkit",
"wayland-client", "wayland-client",
"wayland-protocols 0.32.2", "wayland-protocols",
"xcb", "xcb",
] ]
@@ -4558,11 +4496,12 @@ dependencies = [
"glam", "glam",
"idmap", "idmap",
"idmap-derive", "idmap-derive",
"image_dds",
"input-linux", "input-linux",
"json", "json",
"json5", "json5",
"libc", "libc",
"libloading 0.8.4", "libloading",
"log", "log",
"log-panics", "log-panics",
"once_cell", "once_cell",
@@ -4608,7 +4547,7 @@ dependencies = [
"as-raw-xcb-connection", "as-raw-xcb-connection",
"gethostname", "gethostname",
"libc", "libc",
"libloading 0.8.4", "libloading",
"once_cell", "once_cell",
"rustix", "rustix",
"x11rb-protocol", "x11rb-protocol",
@@ -4634,9 +4573,9 @@ dependencies = [
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7"
[[package]] [[package]]
name = "xdg" name = "xdg"
@@ -4811,7 +4750,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.70", "syn 2.0.72",
] ]
[[package]] [[package]]

View File

@@ -33,7 +33,9 @@ libc = "0.2.155"
libloading = "0.8.3" libloading = "0.8.3"
log = "0.4.21" log = "0.4.21"
once_cell = "1.19.0" 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_overlay = { features = [
"ovr_input", "ovr_input",
"ovr_system", "ovr_system",
@@ -62,6 +64,9 @@ xkbcommon = { version = "0.7.0" }
xcb = { version = "1.4.0", optional = true, features = [ xcb = { version = "1.4.0", optional = true, features = [
"as-raw-xcb-connection", "as-raw-xcb-connection",
] } ] }
image_dds = { version = "0.5.1", default-features = false, features = [
"ddsfile",
] }
[features] [features]
default = ["openvr", "openxr", "osc", "x11", "wayland"] default = ["openvr", "openxr", "osc", "x11", "wayland"]

View File

@@ -32,7 +32,7 @@ impl LinePool {
let buf = vec![255; 16]; 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()?; command_buffer.build_and_execute_now()?;
graphics graphics

View File

@@ -35,6 +35,11 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
} else { } else {
log::warn!("Missing EXT_hp_mixed_reality_controller extension."); 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))] //#[cfg(not(debug_assertions))]
let layers = []; let layers = [];

View File

@@ -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::<RootDestroy>(b"mnd_root_destroy\0") else {
return;
};
root_destroy(&mut self.mnd_root);
}
}
}
impl LibMonado {
pub fn new() -> anyhow::Result<Self> {
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<extern "C" fn(*mut *mut c_void) -> 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<String>,
}
#[derive(Deserialize)]
struct XrRuntimeManifest {
file_format_version: String,
runtime: XrRuntimeManifestRuntime,
}
fn xr_runtime_manifest() -> anyhow::Result<XrRuntimeManifest> {
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)
}

View File

@@ -17,7 +17,7 @@ use crate::{
}; };
use super::{ use super::{
swapchain::{create_swapchain_render_data, SwapchainRenderData}, swapchain::{create_swapchain_render_data, SwapchainOpts, SwapchainRenderData},
CompositionLayer, XrState, CompositionLayer, XrState,
}; };
@@ -46,7 +46,7 @@ impl LinePool {
let views: anyhow::Result<Vec<Arc<ImageView>>> = colors let views: anyhow::Result<Vec<Arc<ImageView>>> = colors
.into_iter() .into_iter()
.map( .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)), Ok(tex) => ImageView::new_default(tex).map_err(|e| anyhow::anyhow!(e)),
Err(e) => Err(e), Err(e) => Err(e),
}, },
@@ -68,7 +68,8 @@ impl LinePool {
) -> anyhow::Result<usize> { ) -> anyhow::Result<usize> {
let id = AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed); 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( self.lines.insert(
id, id,
LineContainer { LineContainer {

View File

@@ -10,6 +10,7 @@ use std::{
use glam::{Affine3A, Vec3}; use glam::{Affine3A, Vec3};
use openxr as xr; use openxr as xr;
use skybox::create_skybox;
use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject}; use vulkano::{command_buffer::CommandBufferUsage, Handle, VulkanObject};
use crate::{ use crate::{
@@ -34,6 +35,7 @@ mod input;
mod lines; mod lines;
mod overlay; mod overlay;
mod playspace; mod playspace;
mod skybox;
mod swapchain; mod swapchain;
const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
@@ -122,6 +124,8 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
stage_offset: Affine3A::IDENTITY, stage_offset: Affine3A::IDENTITY,
}; };
let mut skybox = create_skybox(&xr_state, &app_state);
let pointer_lines = [ let pointer_lines = [
lines.allocate(&xr_state, app_state.graphics.clone())?, lines.allocate(&xr_state, app_state.graphics.clone())?,
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<AtomicBool>, show_by_default: bool) -> Result<(),
let mut due_tasks = VecDeque::with_capacity(4); let mut due_tasks = VecDeque::with_capacity(4);
let mut main_session_visible = false;
'main_loop: loop { 'main_loop: loop {
let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed); let cur_frame = FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
@@ -179,6 +185,19 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
EventsLost(e) => { EventsLost(e) => {
log::warn!("lost {} events", e.lost_event_count()); 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<AtomicBool>, show_by_default: bool) -> Result<(),
.graphics .graphics
.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; .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() { for o in overlays.iter_mut() {
if !o.state.want_visible { if !o.state.want_visible {
continue; continue;
@@ -327,6 +358,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
.map(|f| match f.1 { .map(|f| match f.1 {
CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>, CompositionLayer::Quad(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>, CompositionLayer::Cylinder(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::Equirect2(ref l) => l as &xr::CompositionLayerBase<xr::Vulkan>,
CompositionLayer::None => unreachable!(), CompositionLayer::None => unreachable!(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -413,4 +445,5 @@ pub(super) enum CompositionLayer<'a> {
None, None,
Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>), Quad(xr::CompositionLayerQuad<'a, xr::Vulkan>),
Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>), Cylinder(xr::CompositionLayerCylinderKHR<'a, xr::Vulkan>),
Equirect2(xr::CompositionLayerEquirect2KHR<'a, xr::Vulkan>),
} }

View File

@@ -1,11 +1,14 @@
use glam::Vec3A; use glam::Vec3A;
use openxr as xr; use openxr::{self as xr, CompositionLayerFlags};
use std::{f32::consts::PI, sync::Arc}; use std::{f32::consts::PI, sync::Arc};
use xr::EyeVisibility; use xr::EyeVisibility;
use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState}; use super::{helpers, swapchain::SwapchainRenderData, CompositionLayer, XrState};
use crate::{ use crate::{
backend::{openxr::swapchain::create_swapchain_render_data, overlay::OverlayData}, backend::{
openxr::swapchain::{create_swapchain_render_data, SwapchainOpts},
overlay::OverlayData,
},
graphics::WlxCommandBuffer, graphics::WlxCommandBuffer,
state::AppState, state::AppState,
}; };
@@ -40,8 +43,12 @@ impl OverlayData<OpenXrOverlayData> {
let data = match self.data.swapchain { let data = match self.data.swapchain {
Some(ref mut data) => data, Some(ref mut data) => data,
None => { None => {
let srd = let srd = create_swapchain_render_data(
create_swapchain_render_data(xr, command_buffer.graphics.clone(), extent)?; xr,
command_buffer.graphics.clone(),
extent,
SwapchainOpts::new(),
)?;
log::debug!( log::debug!(
"{}: Created swapchain {}x{}, {} images, {} MB", "{}: Created swapchain {}x{}, {} images, {} MB",
self.state.name, self.state.name,
@@ -75,6 +82,7 @@ impl OverlayData<OpenXrOverlayData> {
let angle = 2.0 * (scale_x / (2.0 * radius)); let angle = 2.0 * (scale_x / (2.0 * radius));
let cylinder = xr::CompositionLayerCylinderKHR::new() let cylinder = xr::CompositionLayerCylinderKHR::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(posef) .pose(posef)
.sub_image(sub_image) .sub_image(sub_image)
.eye_visibility(EyeVisibility::BOTH) .eye_visibility(EyeVisibility::BOTH)
@@ -86,6 +94,7 @@ impl OverlayData<OpenXrOverlayData> {
} else { } else {
let posef = helpers::transform_to_posef(&self.state.transform); let posef = helpers::transform_to_posef(&self.state.transform);
let quad = xr::CompositionLayerQuad::new() let quad = xr::CompositionLayerQuad::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(posef) .pose(posef)
.sub_image(sub_image) .sub_image(sub_image)
.eye_visibility(EyeVisibility::BOTH) .eye_visibility(EyeVisibility::BOTH)

View File

@@ -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<ImageView>,
srd: Option<(SwapchainRenderData, SwapchainRenderData)>,
}
impl Skybox {
pub fn new(app: &AppState) -> anyhow::Result<Self> {
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<Vec<CompositionLayer>> {
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<xr::Posef> = 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<Skybox> {
if !app.session.config.use_skybox {
return None;
}
xr.instance
.exts()
.khr_composition_layer_equirect2
.and_then(|_| Skybox::new(app).ok())
}

View File

@@ -16,13 +16,45 @@ use crate::graphics::{WlxCommandBuffer, WlxGraphics, WlxPipeline, WlxPipelineDyn
use super::XrState; 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( pub(super) fn create_swapchain_render_data(
xr: &XrState, xr: &XrState,
graphics: Arc<WlxGraphics>, graphics: Arc<WlxGraphics>,
extent: [u32; 3], extent: [u32; 3],
opts: SwapchainOpts,
) -> anyhow::Result<SwapchainRenderData> { ) -> anyhow::Result<SwapchainRenderData> {
let create_flags = if opts.immutable {
xr::SwapchainCreateFlags::STATIC_IMAGE
} else {
xr::SwapchainCreateFlags::EMPTY
};
let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo { let swapchain = xr.session.create_swapchain(&xr::SwapchainCreateInfo {
create_flags: xr::SwapchainCreateFlags::EMPTY, create_flags,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED, usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT | xr::SwapchainUsageFlags::SAMPLED,
format: Format::R8G8B8A8_SRGB as _, format: Format::R8G8B8A8_SRGB as _,
sample_count: 1, sample_count: 1,
@@ -36,10 +68,22 @@ pub(super) fn create_swapchain_render_data(
let Ok(shaders) = graphics.shared_shaders.read() else { let Ok(shaders) = graphics.shared_shaders.read() else {
bail!("Failed to lock shared shaders for reading"); 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( let pipeline = graphics.create_pipeline_dynamic(
shaders.get("vert_common").unwrap().clone(), // want panic shaders.get("vert_common").unwrap().clone(), // want panic
shaders.get("frag_swapchain").unwrap().clone(), // want panic shaders.get(frag_shader).unwrap().clone(), // want panic
Format::R8G8B8A8_UNORM, image_fmt,
Some(AttachmentBlend::alpha()), Some(AttachmentBlend::alpha()),
)?; )?;
@@ -54,7 +98,7 @@ pub(super) fn create_swapchain_render_data(
graphics.device.clone(), graphics.device.clone(),
vk_image, vk_image,
ImageCreateInfo { ImageCreateInfo {
format: Format::R8G8B8A8_UNORM, // actually SRGB but we lie format: image_fmt, // actually SRGB but we lie
extent, extent,
usage: ImageUsage::COLOR_ATTACHMENT, usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default() ..Default::default()
@@ -72,6 +116,7 @@ pub(super) fn create_swapchain_render_data(
pipeline, pipeline,
images, images,
extent, extent,
target_extent: [0, 0, 0],
}) })
} }
@@ -79,6 +124,7 @@ pub(super) struct SwapchainRenderData {
pub(super) swapchain: xr::Swapchain<xr::Vulkan>, pub(super) swapchain: xr::Swapchain<xr::Vulkan>,
pub(super) pipeline: Arc<WlxPipeline<WlxPipelineDynamic>>, pub(super) pipeline: Arc<WlxPipeline<WlxPipelineDynamic>>,
pub(super) extent: [u32; 3], pub(super) extent: [u32; 3],
pub(super) target_extent: [u32; 3],
pub(super) images: SmallVec<[Arc<ImageView>; 4]>, pub(super) images: SmallVec<[Arc<ImageView>; 4]>,
} }
@@ -95,7 +141,7 @@ impl SwapchainRenderData {
let render_target = &mut self.images[idx]; let render_target = &mut self.images[idx];
command_buffer.begin_rendering(render_target.clone())?; 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( let set0 = self.pipeline.uniform_sampler(
0, 0,
@@ -106,7 +152,7 @@ impl SwapchainRenderData {
let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?; let set1 = self.pipeline.uniform_buffer(1, vec![alpha])?;
let pass = self.pipeline.create_pass( 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_verts.clone(),
command_buffer.graphics.quad_indices.clone(), command_buffer.graphics.quad_indices.clone(),
vec![set0, set1], vec![set0, set1],
@@ -121,8 +167,60 @@ impl SwapchainRenderData {
.image_rect(xr::Rect2Di { .image_rect(xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 }, offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di { extent: xr::Extent2Di {
width: target_extent[0] as _, width: self.target_extent[0] as _,
height: target_extent[1] 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<xr::SwapchainSubImage<xr::Vulkan>> {
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<xr::SwapchainSubImage<xr::Vulkan>> {
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)) .image_array_index(0))

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use vulkano::{ use vulkano::{
command_buffer::CommandBufferUsage, command_buffer::CommandBufferUsage,
image::{sampler::Filter, view::ImageView, ImageUsage}, image::{view::ImageView, ImageUsage},
swapchain::{ swapchain::{
acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, acquire_next_image, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
}, },
@@ -21,8 +21,8 @@ use crate::{
config_io, config_io,
graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA}, graphics::{DynamicPass, DynamicPipeline, WlxGraphics, BLEND_ALPHA},
gui::{ gui::{
canvas::Canvas,
modular::{modular_canvas, ModularData}, modular::{modular_canvas, ModularData},
Canvas,
}, },
hid::USE_UINPUT, hid::USE_UINPUT,
state::{AppState, ScreenMeta}, state::{AppState, ScreenMeta},

View File

@@ -141,6 +141,10 @@ fn def_auto() -> Arc<str> {
"auto".into() "auto".into()
} }
fn def_empty() -> Arc<str> {
"".into()
}
fn def_toast_topics() -> IdMap<ToastTopic, DisplayMethod> { fn def_toast_topics() -> IdMap<ToastTopic, DisplayMethod> {
IdMap::new() IdMap::new()
} }
@@ -243,6 +247,12 @@ pub struct GeneralConfig {
#[serde(default = "def_one")] #[serde(default = "def_one")]
pub space_drag_multiplier: f32, pub space_drag_multiplier: f32,
#[serde(default = "def_empty")]
pub skybox_texture: Arc<str>,
#[serde(default = "def_true")]
pub use_skybox: bool,
} }
impl GeneralConfig { impl GeneralConfig {

102
src/graphics/dds.rs Normal file
View File

@@ -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<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
where
R: Read;
}
impl WlxCommandBufferDds for WlxCommandBuffer {
fn texture2d_dds<R>(&mut self, r: R) -> anyhow::Result<Arc<Image>>
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<Format> {
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),
}
}

View File

@@ -1,3 +1,5 @@
pub(crate) mod dds;
use std::{ use std::{
collections::HashMap, collections::HashMap,
os::fd::{FromRawFd, IntoRawFd}, os::fd::{FromRawFd, IntoRawFd},
@@ -1083,7 +1085,7 @@ impl WlxCommandBuffer {
Ok(()) Ok(())
} }
pub fn texture2d( pub fn texture2d_raw(
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
@@ -1657,3 +1659,15 @@ fn memory_allocator(device: Arc<Device>) -> Arc<StandardMemoryAllocator> {
Arc::new(StandardMemoryAllocator::new(device, create_info)) 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
)
}

240
src/gui/canvas/builder.rs Normal file
View File

@@ -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<D, S> {
canvas: Canvas<D, S>,
pub fg_color: GuiColor,
pub bg_color: GuiColor,
pub font_size: isize,
}
impl<D, S> CanvasBuilder<D, S> {
pub fn new(
width: usize,
height: usize,
graphics: Arc<WlxGraphics>,
format: Format,
data: D,
) -> anyhow::Result<Self> {
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<D, S> {
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<D, S> {
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<str>) -> &mut Control<D, S> {
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<str>,
) -> &mut Control<D, S> {
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<D, S> {
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<D, S> {
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<str>) -> &mut Control<D, S> {
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<D, S> {
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<D, S> = 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<D, S> = 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<D, S> = 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<D, S> = 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]
}
}

352
src/gui/canvas/control.rs Normal file
View File

@@ -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<D, S> =
fn(&Control<D, S>, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>;
pub type ControlRendererHl<D, S> = fn(
&Control<D, S>,
&CanvasData<D>,
&mut AppState,
&mut WlxCommandBuffer,
Vec4,
) -> anyhow::Result<()>;
pub(crate) struct Control<D, S> {
pub state: Option<S>,
pub rect: Rect,
pub fg_color: GuiColor,
pub bg_color: GuiColor,
pub text: Arc<str>,
pub size: isize,
pub sprite: Option<Arc<ImageView>>,
pub sprite_st: Vec4,
pub(super) dirty: bool,
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32)>,
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> Option<Vec4>>,
pub(super) on_render_bg: Option<ControlRenderer<D, S>>,
pub(super) on_render_hl: Option<ControlRendererHl<D, S>>,
pub(super) on_render_fg: Option<ControlRenderer<D, S>>,
}
impl<D, S> Control<D, S> {
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<ImageView>) {
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<D>,
_: &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<D>,
_: &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<D>,
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<D>,
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<D>,
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<D>,
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(())
}
}

378
src/gui/canvas/mod.rs Normal file
View File

@@ -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<D> {
pub data: D,
pub width: usize,
pub height: usize,
graphics: Arc<WlxGraphics>,
pipeline_bg_color: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_fg_glyph: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_bg_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_hl_sprite: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_final: Arc<WlxPipeline<WlxPipelineLegacy>>,
}
pub struct Canvas<D, S> {
controls: Vec<control::Control<D, S>>,
canvas: CanvasData<D>,
hover_controls: [Option<usize>; 2],
pressed_controls: [Option<usize>; 2],
interact_map: Vec<Option<u16>>,
interact_stride: usize,
interact_rows: usize,
view_final: Arc<ImageView>,
pass_fg: WlxPass<WlxPipelineLegacy>,
pass_bg: WlxPass<WlxPipelineLegacy>,
}
impl<D, S> Canvas<D, S> {
fn new(
width: usize,
height: usize,
graphics: Arc<WlxGraphics>,
format: Format,
data: D,
) -> anyhow::Result<Self> {
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<usize> {
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<D, S> InteractionHandler for Canvas<D, S> {
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<Haptics> {
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<D, S> OverlayRenderer for Canvas<D, S> {
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<Arc<ImageView>> {
Some(self.view_final.clone())
}
}
impl<D, S> OverlayBackend for Canvas<D, S> {
fn set_renderer(&mut self, _renderer: Box<dyn OverlayRenderer>) {}
fn set_interaction(&mut self, _interaction: Box<dyn InteractionHandler>) {}
}

View File

@@ -222,7 +222,7 @@ impl FontCache {
}; };
let mut cmd_buffer = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit)?; 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()?; cmd_buffer.build_and_execute_now()?;
let g = Glyph { let g = Glyph {

View File

@@ -1,37 +1,12 @@
use std::sync::Arc; use glam::Vec4;
use once_cell::sync::Lazy;
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;
pub mod canvas;
pub mod font; pub mod font;
pub mod modular; pub mod modular;
const RES_DIVIDER: usize = 4; pub type GuiColor = Vec4;
pub(super) static FALLBACK_COLOR: Lazy<GuiColor> = Lazy::new(|| Vec4::new(1., 0., 1., 1.));
struct Rect {
x: f32,
y: f32,
w: f32,
h: f32,
}
// Parses a color from a HTML hex string // Parses a color from a HTML hex string
pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> { pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
@@ -63,775 +38,12 @@ pub fn color_parse(html_hex: &str) -> anyhow::Result<GuiColor> {
)); ));
} }
} }
bail!( anyhow::bail!(
"Invalid color string: '{}', must be 7 characters long (e.g. #FF00FF)", "Invalid color string: '{}', must be 7 characters long (e.g. #FF00FF)",
&html_hex &html_hex
) )
} }
pub struct CanvasBuilder<D, S> {
canvas: Canvas<D, S>,
pub fg_color: GuiColor,
pub bg_color: GuiColor,
pub font_size: isize,
}
impl<D, S> CanvasBuilder<D, S> {
pub fn new(
width: usize,
height: usize,
graphics: Arc<WlxGraphics>,
format: Format,
data: D,
) -> anyhow::Result<Self> {
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<D, S> {
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<D, S> {
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<str>) -> &mut Control<D, S> {
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<str>,
) -> &mut Control<D, S> {
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<str>) -> &mut Control<D, S> {
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<D, S> {
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<D, S> = 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<D, S> = 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<D, S> = 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<D, S> = 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<D> {
pub data: D,
pub width: usize,
pub height: usize,
graphics: Arc<WlxGraphics>,
pipeline_bg_color: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_fg_glyph: Arc<WlxPipeline<WlxPipelineLegacy>>,
pipeline_final: Arc<WlxPipeline<WlxPipelineLegacy>>,
}
pub struct Canvas<D, S> {
controls: Vec<Control<D, S>>,
canvas: CanvasData<D>,
hover_controls: [Option<usize>; 2],
pressed_controls: [Option<usize>; 2],
interact_map: Vec<Option<u16>>,
interact_stride: usize,
interact_rows: usize,
view_final: Arc<ImageView>,
pass_fg: WlxPass<WlxPipelineLegacy>,
pass_bg: WlxPass<WlxPipelineLegacy>,
}
impl<D, S> Canvas<D, S> {
fn new(
width: usize,
height: usize,
graphics: Arc<WlxGraphics>,
format: Format,
data: D,
) -> anyhow::Result<Self> {
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<usize> {
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<D, S> InteractionHandler for Canvas<D, S> {
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<Haptics> {
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<D, S> OverlayRenderer for Canvas<D, S> {
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<Arc<ImageView>> {
Some(self.view_final.clone())
}
}
impl<D, S> OverlayBackend for Canvas<D, S> {
fn set_renderer(&mut self, _renderer: Box<dyn OverlayRenderer>) {}
fn set_interaction(&mut self, _interaction: Box<dyn InteractionHandler>) {}
}
pub type ControlRenderer<D, S> =
fn(&Control<D, S>, &CanvasData<D>, &mut AppState, &mut WlxCommandBuffer) -> anyhow::Result<()>;
pub type ControlRendererHl<D, S> = fn(
&Control<D, S>,
&CanvasData<D>,
&mut AppState,
&mut WlxCommandBuffer,
Vec4,
) -> anyhow::Result<()>;
pub struct Control<D, S> {
pub state: Option<S>,
rect: Rect,
fg_color: GuiColor,
bg_color: GuiColor,
text: Arc<str>,
size: isize,
dirty: bool,
pub on_update: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_press: Option<fn(&mut Self, &mut D, &mut AppState, PointerMode)>,
pub on_release: Option<fn(&mut Self, &mut D, &mut AppState)>,
pub on_scroll: Option<fn(&mut Self, &mut D, &mut AppState, f32)>,
pub test_highlight: Option<fn(&Self, &mut D, &mut AppState) -> Option<Vec4>>,
on_render_bg: Option<ControlRenderer<D, S>>,
on_render_hl: Option<ControlRendererHl<D, S>>,
on_render_fg: Option<ControlRenderer<D, S>>,
}
impl<D, S> Control<D, S> {
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<D>,
_: &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<D>,
_: &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<D>,
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<D>,
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 { pub enum KeyCapType {
/// Label is in center of keycap /// Label is in center of keycap
Regular, Regular,

View File

@@ -1,29 +1,30 @@
pub mod button; pub mod button;
pub mod label; pub mod label;
//pub mod slider;
use std::sync::Arc; use std::{fs::File, sync::Arc};
use glam::Vec4; use glam::Vec4;
use once_cell::sync::Lazy;
use serde::Deserialize; 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::{ use self::{
button::{modular_button_init, ButtonAction, ButtonData, OverlayAction}, button::{modular_button_init, ButtonAction, ButtonData, OverlayAction},
label::{modular_label_init, LabelContent, LabelData}, 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 ModularControl = Control<(), ModularData>;
type ExecArgs = Vec<Arc<str>>; type ExecArgs = Vec<Arc<str>>;
pub type GuiColor = Vec4;
static FALLBACK_COLOR: Lazy<GuiColor> = Lazy::new(|| Vec4::new(1., 0., 1., 1.));
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ModularUiConfig { pub struct ModularUiConfig {
pub width: f32, pub width: f32,
@@ -69,6 +70,11 @@ pub enum ModularElement {
#[serde(flatten)] #[serde(flatten)]
data: LabelContent, data: LabelContent,
}, },
Sprite {
rect: [f32; 4],
sprite: Arc<str>,
sprite_st: Option<[f32; 4]>,
},
Button { Button {
rect: [f32; 4], rect: [f32; 4],
font_size: isize, font_size: isize,
@@ -120,7 +126,7 @@ pub enum ModularData {
pub fn modular_canvas( pub fn modular_canvas(
size: &[u32; 2], size: &[u32; 2],
elements: &[ModularElement], elements: &[ModularElement],
state: &AppState, state: &mut AppState,
) -> anyhow::Result<Canvas<(), ModularData>> { ) -> anyhow::Result<Canvas<(), ModularData>> {
let mut canvas = CanvasBuilder::new( let mut canvas = CanvasBuilder::new(
size[0] as _, size[0] as _,
@@ -161,6 +167,25 @@ pub fn modular_canvas(
let label = canvas.label_centered(*x, *y, *w, *h, empty_str.clone()); let label = canvas.label_centered(*x, *y, *w, *h, empty_str.clone());
modular_label_init(label, data); 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 { ModularElement::Button {
rect: [x, y, w, h], rect: [x, y, w, h],
font_size, font_size,
@@ -349,3 +374,29 @@ pub fn color_parse_or_default(color: &str) -> GuiColor {
*FALLBACK_COLOR *FALLBACK_COLOR
}) })
} }
fn sprite_from_path(path: Arc<str>, app: &mut AppState) -> anyhow::Result<Arc<ImageView>> {
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);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -9,7 +9,7 @@ use crate::state::AppState;
pub static ANCHOR_NAME: Lazy<Arc<str>> = Lazy::new(|| Arc::from("anchor")); pub static ANCHOR_NAME: Lazy<Arc<str>> = Lazy::new(|| Arc::from("anchor"));
pub fn create_anchor<O>(state: &AppState) -> anyhow::Result<OverlayData<O>> pub fn create_anchor<O>(state: &mut AppState) -> anyhow::Result<OverlayData<O>>
where where
O: Default, O: Default,
{ {

View File

@@ -12,7 +12,7 @@ use crate::{
const SETTINGS_NAME: &str = "settings"; const SETTINGS_NAME: &str = "settings";
pub fn create_custom( pub fn create_custom(
state: &AppState, state: &mut AppState,
name: Arc<str>, name: Arc<str>,
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> { ) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
let config = if &*name == SETTINGS_NAME { let config = if &*name == SETTINGS_NAME {

View File

@@ -10,7 +10,10 @@ use crate::{
overlay::{OverlayData, OverlayState}, overlay::{OverlayData, OverlayState},
}, },
config::{self, ConfigType}, config::{self, ConfigType},
gui::{color_parse, CanvasBuilder, Control, KeyCapType}, gui::{
canvas::{builder::CanvasBuilder, control::Control},
color_parse, KeyCapType,
},
hid::{ hid::{
get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META, get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META,
NUM_LOCK, SHIFT, SUPER, NUM_LOCK, SHIFT, SUPER,

View File

@@ -214,7 +214,7 @@ impl ScreenPipeline {
]; ];
let mouse_tex = 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)?); self.mouse = Some(ImageView::new_default(mouse_tex)?);
Ok(()) Ok(())
} }
@@ -507,8 +507,12 @@ impl OverlayRenderer for ScreenRenderer {
let data = unsafe { slice::from_raw_parts(map, len) }; let data = unsafe { slice::from_raw_parts(map, len) };
let image = let image = upload.texture2d_raw(
upload.texture2d(frame.format.width, frame.format.height, format, data)?; frame.format.width,
frame.format.height,
format,
data,
)?;
upload.build_and_execute_now()?; upload.build_and_execute_now()?;
unsafe { libc::munmap(map as *mut _, len) }; 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 data = unsafe { slice::from_raw_parts(frame.ptr as *const u8, frame.size) };
let image = let image = upload.texture2d_raw(
upload.texture2d(frame.format.width, frame.format.height, format, data)?; frame.format.width,
frame.format.height,
format,
data,
)?;
let pipeline = Some(match self.pipeline { let pipeline = Some(match self.pipeline {
Some(ref mut p) => p, Some(ref mut p) => p,

View File

@@ -11,7 +11,7 @@ use crate::{
overlay::{OverlayBackend, OverlayState, RelativeTo}, overlay::{OverlayBackend, OverlayState, RelativeTo},
task::TaskType, task::TaskType,
}, },
gui::{color_parse, CanvasBuilder}, gui::{canvas::builder::CanvasBuilder, color_parse},
state::{AppState, LeftRight}, state::{AppState, LeftRight},
}; };

View File

@@ -4,15 +4,15 @@ use crate::{
backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo}, backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo},
config::{load_known_yaml, ConfigType}, config::{load_known_yaml, ConfigType},
gui::{ gui::{
canvas::Canvas,
modular::{modular_canvas, ModularData, ModularUiConfig}, modular::{modular_canvas, ModularData, ModularUiConfig},
Canvas,
}, },
state::AppState, state::AppState,
}; };
pub const WATCH_NAME: &str = "watch"; pub const WATCH_NAME: &str = "watch";
pub fn create_watch<O>(state: &AppState) -> anyhow::Result<OverlayData<O>> pub fn create_watch<O>(state: &mut AppState) -> anyhow::Result<OverlayData<O>>
where where
O: Default, O: Default,
{ {
@@ -40,7 +40,7 @@ where
pub fn create_watch_canvas( pub fn create_watch_canvas(
config: Option<ModularUiConfig>, config: Option<ModularUiConfig>,
state: &AppState, state: &mut AppState,
) -> anyhow::Result<Canvas<(), ModularData>> { ) -> anyhow::Result<Canvas<(), ModularData>> {
let config = config.unwrap_or_else(|| load_known_yaml::<ModularUiConfig>(ConfigType::Watch)); let config = config.unwrap_or_else(|| load_known_yaml::<ModularUiConfig>(ConfigType::Watch));

Binary file not shown.

View File

@@ -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 { pub mod frag_sprite {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", 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 { pub mod frag_screen {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
@@ -153,7 +224,7 @@ pub mod frag_swapchain {
void main() void main()
{ {
out_color = texture(in_texture, in_uv); out_color = texture(in_texture, in_uv);
out_color.a = alpha; out_color.a *= alpha;
} }
", ",
} }

View File

@@ -6,16 +6,20 @@ use idmap::IdMap;
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source}; use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use vulkano::image::view::ImageView;
use crate::{ use crate::{
backend::{input::InputState, task::TaskContainer}, backend::{input::InputState, task::TaskContainer},
config::GeneralConfig, config::{AStrMap, GeneralConfig},
config_io, config_io,
graphics::WlxGraphics, graphics::WlxGraphics,
gui::font::FontCache, gui::font::FontCache,
hid::HidProvider, hid::HidProvider,
overlays::toast::{DisplayMethod, ToastTopic}, 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 { pub struct AppState {
@@ -28,6 +32,7 @@ pub struct AppState {
pub audio: AudioOutput, pub audio: AudioOutput,
pub screens: SmallVec<[ScreenMeta; 8]>, pub screens: SmallVec<[ScreenMeta; 8]>,
pub anchor: Affine3A, pub anchor: Affine3A,
pub sprites: AStrMap<Arc<ImageView>>,
} }
impl AppState { impl AppState {
@@ -47,9 +52,18 @@ impl AppState {
let shader = frag_glyph::load(graphics.device.clone())?; let shader = frag_glyph::load(graphics.device.clone())?;
shaders.insert("frag_glyph", shader); 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())?; let shader = frag_sprite::load(graphics.device.clone())?;
shaders.insert("frag_sprite", shader); 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())?; let shader = frag_screen::load(graphics.device.clone())?;
shaders.insert("frag_screen", shader); shaders.insert("frag_screen", shader);
@@ -69,6 +83,7 @@ impl AppState {
audio: AudioOutput::new(), audio: AudioOutput::new(),
screens: smallvec![], screens: smallvec![],
anchor: Affine3A::IDENTITY, anchor: Affine3A::IDENTITY,
sprites: AStrMap::new(),
}) })
} }
} }