From e5ab46be9104c7aa176642fbabd5c6f880a763b7 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Fri, 10 Nov 2023 09:15:37 +0900 Subject: [PATCH] initial commit --- Cargo.lock | 363 +++++++++----- Cargo.toml | 4 +- LICENSE | 674 ++++++++++++++++++++++++++ README.md | 4 + src/backend/common.rs | 270 ++++++----- src/backend/input.rs | 362 ++++++++++++++ src/backend/mod.rs | 2 + src/backend/openvr/input.rs | 34 +- src/backend/openvr/mod.rs | 103 +++- src/backend/openvr/overlay.rs | 214 +++++++- src/backend/overlay.rs | 170 +++++++ src/graphics.rs | 519 +++++++++++++++----- src/gui/font.rs | 39 +- src/gui/mod.rs | 43 +- src/main.rs | 254 +--------- src/overlays/interactions.rs | 41 -- src/overlays/keyboard.rs | 25 +- src/overlays/mod.rs | 144 +----- src/overlays/screen.rs | 276 ++++++++++- src/overlays/watch.rs | 89 ++-- src/ovr.rs | 64 --- src/res/actions_binding_knuckles.json | 2 +- src/res/actions_binding_oculus.json | 2 +- src/res/actions_binding_vive.json | 2 +- src/state.rs | 35 +- 25 files changed, 2721 insertions(+), 1014 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/backend/input.rs create mode 100644 src/backend/overlay.rs delete mode 100644 src/overlays/interactions.rs delete mode 100644 src/ovr.rs diff --git a/Cargo.lock b/Cargo.lock index dac4990d..641b97b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,16 +207,29 @@ dependencies = [ ] [[package]] -name = "async-executor" -version = "1.6.0" +name = "async-channel" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ - "async-lock 2.8.0", + "concurrent-queue", + "event-listener 4.0.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +dependencies = [ + "async-lock 3.2.0", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite 1.13.0", + "futures-lite 2.1.0", "slab", ] @@ -234,16 +247,16 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526" dependencies = [ - "async-channel", + "async-channel 2.1.1", "async-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io 2.2.1", + "async-lock 3.2.0", "blocking", - "futures-lite 1.13.0", + "futures-lite 2.1.0", "once_cell", ] @@ -269,22 +282,21 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9d5715c2d329bf1b4da8d60455b99b187f27ba726df2883799af9af60997" +checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" dependencies = [ - "async-lock 3.0.0", + "async-lock 3.2.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.0.1", + "futures-lite 2.1.0", "parking", - "polling 3.3.0", - "rustix 0.38.21", + "polling 3.3.1", + "rustix 0.38.26", "slab", "tracing", - "waker-fn", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -298,11 +310,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e900cdcd39bb94a14487d3f7ef92ca222162e6c7c3fe7cb3550ea75fb486ed" +checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" dependencies = [ - "event-listener 3.0.1", + "event-listener 4.0.0", "event-listener-strategy", "pin-project-lite", ] @@ -318,9 +330,9 @@ dependencies = [ "async-signal", "blocking", "cfg-if", - "event-listener 3.0.1", + "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.21", + "rustix 0.38.26", "windows-sys 0.48.0", ] @@ -341,13 +353,13 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.2.0", + "async-io 2.2.1", "async-lock 2.8.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.21", + "rustix 0.38.26", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -359,7 +371,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io 1.13.0", "async-lock 2.8.0", @@ -550,9 +562,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.68.1" +version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" dependencies = [ "bitflags 2.4.1", "cexpr", @@ -616,16 +628,16 @@ dependencies = [ [[package]] name = "blocking" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel", - "async-lock 2.8.0", + "async-channel 2.1.1", + "async-lock 3.2.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.1.0", "piper", "tracing", ] @@ -815,9 +827,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] @@ -851,9 +863,9 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -861,9 +873,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" @@ -893,9 +905,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -915,11 +927,11 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8478e5bdad14dce236b9898ea002eabfa87cbe14f0aa538dbe3b6a4bec4332d" +checksum = "f3120ebb80a9de008e638ad833d4127d50ea3d3a960ea23ea69bc66d9358a028" dependencies = [ - "bindgen 0.68.1", + "bindgen 0.69.1", ] [[package]] @@ -1215,9 +1227,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -1234,12 +1246,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1250,9 +1262,20 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cec0252c2afff729ee6f00e903d479fba81784c8e2bd77447673471fdfaea1" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" dependencies = [ "concurrent-queue", "parking", @@ -1261,11 +1284,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 3.0.1", + "event-listener 4.0.0", "pin-project-lite", ] @@ -1371,9 +1394,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1399,6 +1422,21 @@ dependencies = [ "libc", ] +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.29" @@ -1406,6 +1444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1414,6 +1453,17 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.29" @@ -1437,11 +1487,14 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" +checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" dependencies = [ + "fastrand 2.0.1", "futures-core", + "futures-io", + "parking", "pin-project-lite", ] @@ -1474,6 +1527,7 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1552,9 +1606,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -1655,9 +1709,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1681,7 +1735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1740,7 +1794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", - "rustix 0.38.21", + "rustix 0.38.26", "windows-sys 0.48.0", ] @@ -1813,9 +1867,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1923,9 +1977,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -2367,9 +2421,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -2459,16 +2513,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.3.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.21", + "rustix 0.38.26", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2523,9 +2577,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -2693,15 +2747,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.12", + "windows-sys 0.52.0", ] [[package]] @@ -2766,18 +2820,18 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -2841,9 +2895,9 @@ dependencies = [ [[package]] name = "shaderc" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31cef52787a0db5108788ea20bed13d6bf4b96287c5c5201e55725f7070f3443" +checksum = "27e07913ada18607bb60d12431cbe3358d3bbebbe95948e1618851dc01e63b7b" dependencies = [ "libc", "shaderc-sys", @@ -2851,9 +2905,9 @@ dependencies = [ [[package]] name = "shaderc-sys" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8f8439fffcffd6efcd74197204addf935dbab5752696bd990a6cd36d54cf64" +checksum = "73120d240fe22196300f39ca8547ca2d014960f27b19b47b21288b396272f7f7" dependencies = [ "cmake", "libc", @@ -2898,18 +2952,18 @@ checksum = "a4f120bb98cb4cb0dab21c882968c3cbff79dd23b46f07b1cf5c25044945ce84" [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smithay-client-toolkit" @@ -3061,15 +3115,15 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.21", + "rustix 0.38.26", "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -3283,9 +3337,9 @@ checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3431,9 +3485,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3441,9 +3495,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -3456,9 +3510,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -3468,9 +3522,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3478,9 +3532,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -3491,9 +3545,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wayland-backend" @@ -3656,9 +3710,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3673,7 +3727,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.21", + "rustix 0.38.26", ] [[package]] @@ -3743,6 +3797,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3773,6 +3836,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3785,6 +3863,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3797,6 +3881,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3809,6 +3899,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3821,6 +3917,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3833,6 +3935,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3845,6 +3953,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3857,6 +3971,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winit" version = "0.28.7" @@ -3894,9 +4014,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "40460c13b1e9b107cfc9ede71232f204c36250bbf6f9c78515e2e27ead3122a7" dependencies = [ "memchr", ] @@ -3904,6 +4024,7 @@ dependencies = [ [[package]] name = "wlx-capture" version = "0.1.0" +source = "git+https://github.com/galister/wlx-capture.git#56eba19fac9834e2dfb77dd3a0a93b088a93dee9" dependencies = [ "ashpd", "drm-fourcc", @@ -3923,12 +4044,14 @@ dependencies = [ name = "wlx-overlay-s" version = "0.1.0" dependencies = [ + "ash", "ash-window", "chrono", "cstr", - "env_logger 0.10.0", + "env_logger 0.10.1", "fontconfig-rs", "freetype-rs", + "futures", "glam", "idmap", "idmap-derive", @@ -4097,18 +4220,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.25" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bf8c76e1..1aa792a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ash = "^0.37.2" ash-window = "0.12.0" chrono = "0.4.29" cstr = "0.2.11" env_logger = "0.10.0" fontconfig-rs = { version = "0.1.1", features = ["dlopen"] } freetype-rs = "0.32.0" +futures = "0.3.29" glam = { version = "0.24.1", features = ["approx"] } idmap = "0.2.21" idmap-derive = "0.1.2" @@ -34,5 +36,5 @@ vulkano-shaders = "0.33.0" vulkano-util = "0.33.0" vulkano-win = "0.33.0" winit = "0.28.6" -wlx-capture = { version = "0.1.0", path = "../wlx-capture" } +wlx-capture = { git = "https://github.com/galister/wlx-capture.git" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..7e94402e --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# WlxOverlay-S +[WIP] X11 & Wayland desktop overlay for SteamVR and OpenXR, Vulkan edition + +Nothing works yet. diff --git a/src/backend/common.rs b/src/backend/common.rs index 25bff554..b6e22645 100644 --- a/src/backend/common.rs +++ b/src/backend/common.rs @@ -1,152 +1,158 @@ -use std::{collections::VecDeque, time::Instant}; +use std::{ + collections::{BinaryHeap, VecDeque}, + sync::Arc, + time::Instant, +}; -use glam::{Affine3A, Vec2, Vec3, Vec3A}; -use ovr_overlay::TrackedDeviceIndex; +use idmap::IdMap; -pub struct InputState { - pub hmd: Affine3A, - pub pointers: [Pointer; 2], - pub devices: Vec, - pub(super) data: TState, +use crate::{ + overlays::{ + keyboard::create_keyboard, + screen::{get_screens_wayland, get_screens_x11}, + watch::create_watch, + }, + state::AppState, +}; + +use super::overlay::{OverlayData, OverlayState}; + +pub struct OverlayContainer +where + T: Default, +{ + overlays: IdMap>, } -impl InputState { - pub fn pre_update(&mut self) { - self.pointers[0].before = self.pointers[0].now; - self.pointers[1].before = self.pointers[1].now; +impl OverlayContainer +where + T: Default, +{ + pub fn new(app: &mut AppState) -> Self { + let mut overlays = IdMap::new(); + + let screens = if std::env::var("WAYLAND_DISPLAY").is_ok() { + get_screens_wayland(&app.session) + } else { + get_screens_x11() + }; + + let watch = create_watch::(&app, &screens); + overlays.insert(watch.state.id, watch); + + let keyboard = create_keyboard(&app); + overlays.insert(keyboard.state.id, keyboard); + + let mut first = true; + for mut screen in screens { + if first { + screen.state.want_visible = true; + first = false; + } + overlays.insert(screen.state.id, screen); + } + + Self { overlays } } - pub fn post_update(&mut self) { - for hand in &mut self.pointers { - if hand.now.click_modifier_right { - hand.interaction.mode = PointerMode::Right; - continue; - } - - if hand.now.click_modifier_middle { - hand.interaction.mode = PointerMode::Middle; - continue; - } - - let hmd_up = self.hmd.transform_vector3a(Vec3A::Y); - let dot = - hmd_up.dot(hand.pose.transform_vector3a(Vec3A::X)) * (1.0 - 2.0 * hand.hand as f32); - - hand.interaction.mode = if dot < -0.85 { - PointerMode::Right - } else if dot > 0.7 { - PointerMode::Middle - } else { - PointerMode::Left - }; - - let middle_click_orientation = false; - let right_click_orientation = false; - match hand.interaction.mode { - PointerMode::Middle => { - if !middle_click_orientation { - hand.interaction.mode = PointerMode::Left; - } - } - PointerMode::Right => { - if !right_click_orientation { - hand.interaction.mode = PointerMode::Left; - } - } - _ => {} - }; + pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData> { + match selector { + OverlaySelector::Id(id) => self.mut_by_id(*id), + OverlaySelector::Name(name) => self.mut_by_name(name), } } + + pub fn get_by_id<'a>(&'a mut self, id: usize) -> Option<&'a OverlayData> { + self.overlays.get(&id) + } + + pub fn mut_by_id<'a>(&'a mut self, id: usize) -> Option<&'a mut OverlayData> { + self.overlays.get_mut(&id) + } + + pub fn get_by_name<'a>(&'a mut self, name: &str) -> Option<&'a OverlayData> { + self.overlays.values().find(|o| *o.state.name == *name) + } + + pub fn mut_by_name<'a>(&'a mut self, name: &str) -> Option<&'a mut OverlayData> { + self.overlays.values_mut().find(|o| *o.state.name == *name) + } + + pub fn iter<'a>(&'a self) -> impl Iterator> { + self.overlays.values() + } + + pub fn iter_mut<'a>(&'a mut self) -> impl Iterator> { + self.overlays.values_mut() + } } -pub struct Pointer { - pub hand: usize, - pub pose: Affine3A, - pub now: PointerState, - pub before: PointerState, - pub(super) interaction: InteractionState, - pub(super) data: THand, +pub enum OverlaySelector { + Id(usize), + Name(Arc), } -#[derive(Debug, Clone, Copy, Default)] -pub struct PointerState { - pub scroll: f32, - pub click: bool, - pub grab: bool, - pub alt_click: bool, - pub show_hide: bool, - pub space_drag: bool, - pub click_modifier_right: bool, - pub click_modifier_middle: bool, +struct AppTask { + pub not_before: Instant, + pub task: TaskType, } -pub struct InteractionState { - pub mode: PointerMode, - pub grabbed: Option, - pub clicked_id: Option, - pub hovered_id: Option, - pub release_actions: VecDeque>, - pub next_push: Instant, +impl PartialEq for AppTask { + fn eq(&self, other: &Self) -> bool { + self.not_before == other.not_before + } +} +impl PartialOrd for AppTask { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.not_before.cmp(&other.not_before).reverse()) + } +} +impl Eq for AppTask {} +impl Ord for AppTask { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.not_before.cmp(&other.not_before).reverse() + } } -impl Default for InteractionState { - fn default() -> Self { +pub enum TaskType { + Global(Box), + Overlay( + OverlaySelector, + Box, + ), +} + +pub struct TaskContainer { + tasks: BinaryHeap, +} + +impl TaskContainer { + pub fn new() -> Self { Self { - mode: PointerMode::Left, - grabbed: None, - clicked_id: None, - hovered_id: None, - release_actions: VecDeque::new(), - next_push: Instant::now(), + tasks: BinaryHeap::new(), + } + } + + pub fn enqueue(&mut self, task: TaskType) { + self.tasks.push(AppTask { + not_before: Instant::now(), + task, + }); + } + + pub fn enqueue_at(&mut self, task: TaskType, not_before: Instant) { + self.tasks.push(AppTask { not_before, task }); + } + + pub fn retrieve_due(&mut self, dest_buf: &mut VecDeque) { + let now = Instant::now(); + + while let Some(task) = self.tasks.peek() { + if task.not_before > now { + break; + } + + dest_buf.push_back(self.tasks.pop().unwrap().task); } } } - -pub struct PointerHit { - pub hand: usize, - pub mode: PointerMode, - pub primary: bool, - pub uv: Vec2, - pub dist: f32, -} - -struct RayHit { - idx: usize, - ray_pos: Vec3, - hit_pos: Vec3, - uv: Vec2, - dist: f32, -} - -#[derive(Debug, Clone, Copy, Default)] -pub struct GrabData { - pub offset: Vec3, - pub grabbed_id: usize, -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, Default)] -pub enum PointerMode { - #[default] - Left, - Right, - Middle, -} - -pub struct TrackedDevice { - pub index: TrackedDeviceIndex, - pub valid: bool, - pub soc: Option, - pub charging: bool, - pub role: TrackedDeviceRole, -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TrackedDeviceRole { - None, - Hmd, - LeftHand, - RightHand, - Tracker, -} diff --git a/src/backend/input.rs b/src/backend/input.rs new file mode 100644 index 00000000..625c7182 --- /dev/null +++ b/src/backend/input.rs @@ -0,0 +1,362 @@ +use std::{collections::VecDeque, time::Instant}; + +use glam::{Affine3A, Vec2, Vec3A}; +use log::warn; +use ovr_overlay::TrackedDeviceIndex; +use tinyvec::array_vec; + +use crate::state::AppState; + +use super::{common::OverlayContainer, overlay::OverlayData}; + +pub struct TrackedDevice { + pub index: TrackedDeviceIndex, + pub valid: bool, + pub soc: Option, + pub charging: bool, + pub role: TrackedDeviceRole, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TrackedDeviceRole { + None, + Hmd, + LeftHand, + RightHand, + Tracker, +} + +pub struct InputState { + pub hmd: Affine3A, + pub pointers: [Pointer; 2], + pub devices: Vec, + pub(super) data: TState, +} + +impl InputState { + pub fn pre_update(&mut self) { + self.pointers[0].before = self.pointers[0].now; + self.pointers[1].before = self.pointers[1].now; + } + + pub fn post_update(&mut self) { + for hand in &mut self.pointers { + if hand.now.click_modifier_right { + hand.interaction.mode = PointerMode::Right; + continue; + } + + if hand.now.click_modifier_middle { + hand.interaction.mode = PointerMode::Middle; + continue; + } + + let hmd_up = self.hmd.transform_vector3a(Vec3A::Y); + let dot = + hmd_up.dot(hand.pose.transform_vector3a(Vec3A::X)) * (1.0 - 2.0 * hand.hand as f32); + + hand.interaction.mode = if dot < -0.85 { + PointerMode::Right + } else if dot > 0.7 { + PointerMode::Middle + } else { + PointerMode::Left + }; + + let middle_click_orientation = false; + let right_click_orientation = false; + match hand.interaction.mode { + PointerMode::Middle => { + if !middle_click_orientation { + hand.interaction.mode = PointerMode::Left; + } + } + PointerMode::Right => { + if !right_click_orientation { + hand.interaction.mode = PointerMode::Left; + } + } + _ => {} + }; + } + } +} + +pub struct InteractionState { + pub mode: PointerMode, + pub grabbed: Option, + pub clicked_id: Option, + pub hovered_id: Option, + pub release_actions: VecDeque>, + pub next_push: Instant, +} + +impl Default for InteractionState { + fn default() -> Self { + Self { + mode: PointerMode::Left, + grabbed: None, + clicked_id: None, + hovered_id: None, + release_actions: VecDeque::new(), + next_push: Instant::now(), + } + } +} + +pub struct Pointer { + pub idx: usize, + pub hand: u8, + pub pose: Affine3A, + pub now: PointerState, + pub before: PointerState, + pub(super) interaction: InteractionState, + pub(super) data: THand, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct PointerState { + pub scroll: f32, + pub click: bool, + pub grab: bool, + pub alt_click: bool, + pub show_hide: bool, + pub space_drag: bool, + pub click_modifier_right: bool, + pub click_modifier_middle: bool, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct PointerHit { + pub pointer: usize, + pub overlay: usize, + pub mode: PointerMode, + pub primary: bool, + pub uv: Vec2, + pub dist: f32, +} + +pub trait InteractionHandler { + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit); + fn on_left(&mut self, app: &mut AppState, pointer: usize); + fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool); + fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32); +} + +pub struct DummyInteractionHandler; + +impl InteractionHandler for DummyInteractionHandler { + fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {} + fn on_hover(&mut self, _app: &mut AppState, _hit: &PointerHit) {} + fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {} + fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {} +} + +#[derive(Debug, Clone, Copy, Default)] +struct RayHit { + overlay: usize, + hit_pos: Vec3A, + dist: f32, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct GrabData { + pub offset: Vec3A, + pub grabbed_id: usize, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Default)] +pub enum PointerMode { + #[default] + Left, + Right, + Middle, +} + +impl Pointer { + pub fn interact(&mut self, overlays: &mut OverlayContainer, app: &mut AppState) + where + O: Default, + { + if let Some(grab_data) = self.interaction.grabbed { + if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) { + self.handle_grabbed(grabbed, grab_data.offset); + } else { + warn!("Grabbed overlay {} does not exist", grab_data.grabbed_id); + self.interaction.grabbed = None; + } + return; + } + + let Some(mut hit) = self.get_nearest_hit(overlays) else { + if let Some(hovered_id) = self.interaction.hovered_id.take() { + if let Some(hovered) = overlays.mut_by_id(hovered_id) { + hovered.backend.on_left(app, self.idx); + } + self.interaction.hovered_id = None; + } + if let Some(clicked_id) = self.interaction.clicked_id.take() { + if let Some(clicked) = overlays.mut_by_id(clicked_id) { + let hit = PointerHit { + pointer: self.idx, + overlay: clicked_id, + mode: self.interaction.mode, + ..Default::default() + }; + clicked.backend.on_pointer(app, &hit, false); + } + } + return; + }; + + if let Some(hovered_id) = self.interaction.hovered_id { + if hovered_id != hit.overlay { + if let Some(old_hovered) = overlays.mut_by_id(hovered_id) { + if Some(self.idx) == old_hovered.primary_pointer { + old_hovered.primary_pointer = None; + } + old_hovered.backend.on_left(app, self.idx); + } + } + } + let Some(hovered) = overlays.mut_by_id(hit.overlay) else { + warn!("Hit overlay {} does not exist", hit.overlay); + return; + }; + + self.interaction.hovered_id = Some(hit.overlay); + + if let Some(primary_pointer) = hovered.primary_pointer { + if hit.pointer < primary_pointer { + hovered.primary_pointer = Some(hit.pointer); + hit.primary = true; + } + } else { + hovered.primary_pointer = Some(hit.pointer); + hit.primary = true; + } + hovered.backend.on_hover(app, &hit); + + if self.now.scroll.abs() > 0.1 { + hovered.backend.on_scroll(app, &hit, self.now.scroll); + } + + if self.now.click && !self.before.click { + self.interaction.clicked_id = Some(hit.overlay); + hovered.backend.on_pointer(app, &hit, true); + } else if !self.now.click && self.before.click { + if let Some(clicked_id) = self.interaction.clicked_id.take() { + if let Some(clicked) = overlays.mut_by_id(clicked_id) { + clicked.backend.on_pointer(app, &hit, false); + } + } else { + hovered.backend.on_pointer(app, &hit, false); + } + } + } + + fn get_nearest_hit(&mut self, overlays: &mut OverlayContainer) -> Option + where + O: Default, + { + let mut hits = array_vec!([RayHit; 8]); + + for overlay in overlays.iter() { + if !overlay.state.want_visible { + continue; + } + + if let Some(hit) = self.ray_test(overlay.state.id, &overlay.state.transform) { + hits.try_push(hit); + } + } + + hits.sort_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap()); + + for hit in hits.iter() { + let uv = overlays + .get_by_id(hit.overlay) + .unwrap() // this is safe + .state + .transform + .inverse() + .transform_point3a(hit.hit_pos) + .truncate(); + + if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 { + continue; + } + + return Some(PointerHit { + pointer: self.idx, + overlay: hit.overlay, + mode: self.interaction.mode, + primary: false, + uv, + dist: hit.dist, + }); + } + + None + } + + fn start_grab(&mut self, overlay: &mut OverlayData) + where + O: Default, + { + let offset = self + .pose + .inverse() + .transform_point3a(overlay.state.transform.translation); + + self.interaction.grabbed = Some(GrabData { + offset, + grabbed_id: overlay.state.id, + }); + } + fn handle_grabbed(&mut self, overlay: &mut OverlayData, offset: Vec3A) + where + O: Default, + { + if self.now.grab { + overlay.state.transform.translation = self.pose.transform_point3a(offset); + + if self.now.click && !self.before.click { + warn!("todo: click-while-grabbed"); + } + + match self.interaction.mode { + PointerMode::Left => { + overlay.state.transform.translation.y += self.now.scroll * 0.01; + } + _ => { + overlay.state.transform.matrix3 = overlay + .state + .transform + .matrix3 + .mul_scalar(1.0 + 0.01 * self.now.scroll); + } + } + } else { + overlay.state.spawn_point = overlay.state.transform.translation; + self.interaction.grabbed = None; + } + } + fn ray_test(&self, overlay: usize, plane: &Affine3A) -> Option { + let plane_normal = plane.transform_vector3a(Vec3A::NEG_Z); + let ray_dir = self.pose.transform_vector3a(Vec3A::NEG_Z); + + let d = plane.translation.dot(-plane_normal); + let dist = -(d + self.pose.translation.dot(plane_normal)) / ray_dir.dot(plane_normal); + + let hit_pos = self.pose.translation + ray_dir * dist; + + Some(RayHit { + overlay, + hit_pos, + dist, + }) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 55f52d26..01bfa9a2 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,3 +1,5 @@ pub mod common; +pub mod input; pub mod openvr; pub mod openxr; +pub mod overlay; diff --git a/src/backend/openvr/input.rs b/src/backend/openvr/input.rs index d9439433..5c5509b0 100644 --- a/src/backend/openvr/input.rs +++ b/src/backend/openvr/input.rs @@ -1,4 +1,4 @@ -use std::array; +use std::{array, io::Write, path::Path}; use glam::Affine3A; use ovr_overlay::{ @@ -11,7 +11,7 @@ use ovr_overlay::{ TrackedDeviceIndex, }; -use crate::backend::common::{ +use crate::backend::input::{ InputState, InteractionState, Pointer, PointerState, TrackedDevice, TrackedDeviceRole, }; @@ -96,7 +96,8 @@ impl InputState { .collect::>()?; let hands: [Pointer; 2] = array::from_fn(|i| Pointer:: { - hand: i, + idx: i, + hand: i as u8, now: PointerState::default(), before: PointerState::default(), pose: Affine3A::IDENTITY, @@ -286,3 +287,30 @@ fn copy_from_hmd(in_mat: &HmdMatrix34_t, out_mat: &mut Affine3A) { out_mat.w_axis[1] = in_mat.m[1][3]; out_mat.w_axis[2] = in_mat.m[2][3]; } + +pub fn action_manifest_path() -> &'static Path { + let action_path = "/tmp/wlxoverlay-s/actions.json"; + std::fs::create_dir_all("/tmp/wlxoverlay-s").unwrap(); + + std::fs::File::create(action_path) + .unwrap() + .write_all(include_bytes!("../../res/actions.json")) + .unwrap(); + + std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_knuckles.json") + .unwrap() + .write_all(include_bytes!("../../res/actions_binding_knuckles.json")) + .unwrap(); + + std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_vive.json") + .unwrap() + .write_all(include_bytes!("../../res/actions_binding_vive.json")) + .unwrap(); + + std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_oculus.json") + .unwrap() + .write_all(include_bytes!("../../res/actions_binding_oculus.json")) + .unwrap(); + + Path::new(action_path) +} diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index b2aa43d3..609b3491 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -1,4 +1,5 @@ use std::{ + collections::VecDeque, path::Path, time::{Duration, Instant}, }; @@ -8,45 +9,73 @@ use ovr_overlay::{ sys::{ETrackedDeviceProperty, EVRApplicationType, EVREventType}, TrackedDeviceIndex, }; +use vulkano::{ + device::{physical::PhysicalDevice, DeviceExtensions}, + instance::InstanceExtensions, + Handle, VulkanObject, +}; -use super::common::InputState; +use crate::state::AppState; + +use self::{input::action_manifest_path, overlay::OpenVrOverlayData}; + +use super::{ + common::{OverlayContainer, TaskType}, + input::InputState, +}; pub mod input; pub mod overlay; -fn openvr_run() { +pub fn openvr_run() { let app_type = EVRApplicationType::VRApplication_Overlay; let Ok(context) = ovr_overlay::Context::init(app_type) else { error!("Failed to initialize OpenVR"); return; }; - let mut overlay = context.overlay_mngr(); - let mut settings = context.settings_mngr(); - let mut input = context.input_mngr(); - let mut system = context.system_mngr(); - let mut compositor = context.compositor_mngr(); + let mut overlay_mngr = context.overlay_mngr(); + //let mut settings_mngr = context.settings_mngr(); + let mut input_mngr = context.input_mngr(); + let mut system_mngr = context.system_mngr(); + let mut compositor_mngr = context.compositor_mngr(); - let Ok(_) = input.set_action_manifest(Path::new("resources/actions.json")) else { - error!("Failed to set action manifest"); + let device_extensions_fn = |device: &PhysicalDevice| { + let names = compositor_mngr.get_vulkan_device_extensions_required(device.handle().as_raw()); + let ext = DeviceExtensions::from_iter(names.iter().map(|s| s.as_str())); + ext + }; + + let mut compositor_mngr = context.compositor_mngr(); + let instance_extensions = { + let names = compositor_mngr.get_vulkan_instance_extensions_required(); + InstanceExtensions::from_iter(names.iter().map(|s| s.as_str())) + }; + + let mut state = AppState::new(instance_extensions, device_extensions_fn); + let mut overlays = OverlayContainer::::new(&mut state); + + if let Err(e) = input_mngr.set_action_manifest(action_manifest_path()) { + error!("Failed to set action manifest: {}", e.description()); return; }; - let Ok(mut input_state) = InputState::new(&mut input) else { + let Ok(mut input) = InputState::new(&mut input_mngr) else { error!("Failed to initialize input"); return; }; - let Ok(refresh_rate) = system.get_tracked_device_property::(TrackedDeviceIndex::HMD, ETrackedDeviceProperty::Prop_DisplayFrequency_Float) else { + let Ok(refresh_rate) = system_mngr.get_tracked_device_property::(TrackedDeviceIndex::HMD, ETrackedDeviceProperty::Prop_DisplayFrequency_Float) else { error!("Failed to get display refresh rate"); return; }; let frame_time = (1000.0 / refresh_rate).floor() * 0.001; let mut next_device_update = Instant::now(); + let mut due_tasks = VecDeque::with_capacity(4); loop { - while let Some(event) = system.poll_next_event() { + while let Some(event) = system_mngr.poll_next_event() { match event.event_type { EVREventType::VREvent_Quit => { info!("Received quit event, shutting down."); @@ -61,35 +90,57 @@ fn openvr_run() { } if next_device_update <= Instant::now() { - input_state.update_devices(&mut system); + input.update_devices(&mut system_mngr); next_device_update = Instant::now() + Duration::from_secs(30); } - input_state.pre_update(); - input_state.update(&mut input, &mut system); - input_state.post_update(); + state.tasks.retrieve_due(&mut due_tasks); + while let Some(task) = due_tasks.pop_front() { + match task { + TaskType::Global(f) => f(&mut state), + TaskType::Overlay(sel, f) => { + if let Some(o) = overlays.mut_by_selector(&sel) { + f(&mut state, &mut o.state); + } + } + } + } - // task scheduler + input.pre_update(); + input.update(&mut input_mngr, &mut system_mngr); + input.post_update(); - // after input + input + .pointers + .iter_mut() + .for_each(|p| p.interact(&mut overlays, &mut state)); - // interactions + overlays + .iter_mut() + .for_each(|o| o.after_input(&mut overlay_mngr, &mut state)); - // show overlays + log::debug!("Rendering frame"); + + overlays + .iter_mut() + .filter(|o| o.state.want_visible) + .for_each(|o| o.render(&mut state)); + + log::debug!("Rendering overlays"); + + overlays + .iter_mut() + .for_each(|o| o.after_render(&mut overlay_mngr, &state.graphics)); // chaperone - // render overlays - - // hide overlays - - // close font handles + // close font handles? // playspace moved end frame let mut seconds_since_vsync = 0f32; std::thread::sleep(Duration::from_secs_f32( - if system.get_time_since_last_vsync(&mut seconds_since_vsync, &mut 0u64) { + if system_mngr.get_time_since_last_vsync(&mut seconds_since_vsync, &mut 0u64) { frame_time - seconds_since_vsync } else { 0.011 diff --git a/src/backend/openvr/overlay.rs b/src/backend/openvr/overlay.rs index 85ba9e4e..47ae59c5 100644 --- a/src/backend/openvr/overlay.rs +++ b/src/backend/openvr/overlay.rs @@ -1,15 +1,207 @@ -use ovr_overlay::sys::VRVulkanTextureData_t; +use glam::Vec4; +use ovr_overlay::{ + overlay::{OverlayHandle, OverlayManager}, + sys::VRVulkanTextureData_t, +}; +use vulkano::{ + command_buffer::{ + synced::{ + SyncCommandBuffer, SyncCommandBufferBuilder, SyncCommandBufferBuilderExecuteCommands, + }, + AutoCommandBufferBuilder, CommandBufferExecFuture, + }, + image::{ImageAccess, ImageLayout}, + sync::{future::NowFuture, ImageMemoryBarrier}, + Handle, VulkanObject, +}; -use crate::overlays::OverlayData; +use crate::{backend::overlay::OverlayData, graphics::WlxGraphics, state::AppState}; -pub(super) struct OpenVrOverlayManager { - pub(super) overlays: Vec, -} - -pub(super) struct OpenVrOverlay { +#[derive(Default)] +pub(super) struct OpenVrOverlayData { + handle: Option, + last_image: Option, pub(super) visible: bool, - pub(super) color: [f32; 4], - overlay: OverlayData, - handle: u32, - ovr_texture: VRVulkanTextureData_t, + pub(super) color: Vec4, + pub(super) curvature: f32, + pub(super) sort_order: u32, +} + +impl OverlayData { + pub fn initialize( + &mut self, + overlay: &mut OverlayManager, + app: &mut AppState, + ) -> OverlayHandle { + let key = format!("wlx-{}", self.state.name); + let handle = match overlay.create_overlay(&key, &key) { + Ok(handle) => handle, + Err(e) => { + panic!("Failed to create overlay: {}", e); + } + }; + log::debug!("{}: initialize", self.state.name); + + self.data.handle = Some(handle); + + self.init(app); + + self.upload_width(overlay); + self.upload_color(overlay); + self.upload_curvature(overlay); + + handle + } + + pub fn after_input(&mut self, overlay: &mut OverlayManager, app: &mut AppState) { + if self.state.want_visible && !self.data.visible { + self.show(overlay, app); + } else if !self.state.want_visible && self.data.visible { + self.hide(overlay); + } + } + + pub fn after_render(&mut self, overlay: &mut OverlayManager, graphics: &WlxGraphics) { + if self.data.visible { + self.upload_texture(overlay, graphics); + } + } + + fn show(&mut self, overlay: &mut OverlayManager, app: &mut AppState) { + let handle = match self.data.handle { + Some(handle) => handle, + None => self.initialize(overlay, app), + }; + log::debug!("{}: show", self.state.name); + if let Err(e) = overlay.set_visibility(handle, true) { + panic!("Failed to show overlay: {}", e); + } + self.data.visible = true; + } + + fn hide(&mut self, overlay: &mut OverlayManager) { + let Some(handle) = self.data.handle else { + return; + }; + log::debug!("{}: hide", self.state.name); + if let Err(e) = overlay.set_visibility(handle, false) { + panic!("Failed to hide overlay: {}", e); + } + self.data.visible = false; + } + + fn upload_color(&self, overlay: &mut OverlayManager) { + let Some(handle) = self.data.handle else { + log::debug!("{}: No overlay handle", self.state.name); + return; + }; + if let Err(e) = overlay.set_opacity(handle, self.data.color.w) { + panic!("Failed to set overlay opacity: {}", e); + } + if let Err(e) = overlay.set_tint( + handle, + ovr_overlay::ColorTint { + r: self.data.color.x, + g: self.data.color.y, + b: self.data.color.z, + a: 1.0, + }, + ) { + panic!("Failed to set overlay tint: {}", e); + } + } + + fn upload_width(&self, overlay: &mut OverlayManager) { + let Some(handle) = self.data.handle else { + log::debug!("{}: No overlay handle", self.state.name); + return; + }; + if let Err(e) = overlay.set_width(handle, self.state.width) { + panic!("Failed to set overlay width: {}", e); + } + } + + fn upload_curvature(&self, overlay: &mut OverlayManager) { + let Some(handle) = self.data.handle else { + log::debug!("{}: No overlay handle", self.state.name); + return; + }; + if let Err(e) = overlay.set_curvature(handle, self.data.curvature) { + panic!("Failed to set overlay curvature: {}", e); + } + } + + fn upload_sort_order(&self, overlay: &mut OverlayManager) { + let Some(handle) = self.data.handle else { + log::debug!("{}: No overlay handle", self.state.name); + return; + }; + if let Err(e) = overlay.set_sort_order(handle, self.data.sort_order) { + panic!("Failed to set overlay z order: {}", e); + } + } + + fn upload_texture(&mut self, overlay: &mut OverlayManager, graphics: &WlxGraphics) { + let Some(handle) = self.data.handle else { + log::debug!("{}: No overlay handle", self.state.name); + return; + }; + + let Some(view) = self.backend.view() else { + log::debug!("{}: Not rendered", self.state.name); + return; + }; + + let image = view.image().inner().image.clone(); + + let raw_image = image.handle().as_raw(); + + if let Some(last_image) = self.data.last_image { + if last_image == raw_image { + return; + } + } + + let Some(format) = image.format() else { + panic!("{}: Image format is None", self.state.name); + }; + + let dimensions = image.dimensions(); + + let mut texture = VRVulkanTextureData_t { + m_nImage: raw_image, + m_nFormat: format as _, + m_nWidth: dimensions.width(), + m_nHeight: dimensions.height(), + m_nSampleCount: image.samples() as u32, + m_pDevice: graphics.device.handle().as_raw() as *mut _, + m_pPhysicalDevice: graphics.device.physical_device().handle().as_raw() as *mut _, + m_pInstance: graphics.instance.handle().as_raw() as *mut _, + m_pQueue: graphics.queue.handle().as_raw() as *mut _, + m_nQueueFamilyIndex: graphics.queue.queue_family_index(), + }; + + graphics + .transition_layout( + image.clone(), + ImageLayout::ColorAttachmentOptimal, + ImageLayout::TransferSrcOptimal, + ) + .wait(None) + .unwrap(); + + log::info!("nImage: {}, nFormat: {:?}, nWidth: {}, nHeight: {}, nSampleCount: {}, nQueueFamilyIndex: {}", texture.m_nImage, format, texture.m_nWidth, texture.m_nHeight, texture.m_nSampleCount, texture.m_nQueueFamilyIndex); + if let Err(e) = overlay.set_image_vulkan(handle, &mut texture) { + panic!("Failed to set overlay texture: {}", e); + } + + graphics + .transition_layout( + image, + ImageLayout::TransferSrcOptimal, + ImageLayout::ColorAttachmentOptimal, + ) + .wait(None) + .unwrap(); + } } diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs new file mode 100644 index 00000000..d094e45e --- /dev/null +++ b/src/backend/overlay.rs @@ -0,0 +1,170 @@ +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +use glam::{Affine3A, Quat, Vec3A}; +use vulkano::image::ImageViewAbstract; + +use crate::state::AppState; + +use super::input::{DummyInteractionHandler, InteractionHandler, PointerHit}; + +static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); + +pub trait OverlayBackend: OverlayRenderer + InteractionHandler {} + +pub struct OverlayState { + pub id: usize, + pub name: Arc, + pub width: f32, + pub size: (i32, i32), + pub want_visible: bool, + pub show_hide: bool, + pub grabbable: bool, + pub transform: Affine3A, + pub spawn_point: Vec3A, + pub spawn_rotation: Quat, + pub relative_to: RelativeTo, + pub primary_pointer: Option, + pub interaction_transform: Affine3A, +} + +impl Default for OverlayState { + fn default() -> Self { + OverlayState { + id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed), + name: Arc::from(""), + width: 1., + size: (0, 0), + want_visible: false, + show_hide: false, + grabbable: false, + relative_to: RelativeTo::None, + spawn_point: Vec3A::NEG_Z, + spawn_rotation: Quat::IDENTITY, + transform: Affine3A::IDENTITY, + primary_pointer: None, + interaction_transform: Affine3A::IDENTITY, + } + } +} + +pub struct OverlayData +where + T: Default, +{ + pub state: OverlayState, + pub backend: Box, + pub primary_pointer: Option, + pub data: T, +} + +impl Default for OverlayData +where + T: Default, +{ + fn default() -> Self { + OverlayData { + state: Default::default(), + backend: Box::new(SplitOverlayBackend::default()), + primary_pointer: None, + data: Default::default(), + } + } +} + +impl OverlayState { + pub fn reset(&mut self, _app: &mut AppState) { + todo!() + } +} + +impl OverlayData +where + T: Default, +{ + pub fn init(&mut self, app: &mut AppState) { + self.backend.init(app); + } + pub fn render(&mut self, app: &mut AppState) { + self.backend.render(app); + } + pub fn view(&mut self) -> Option> { + self.backend.view() + } +} + +pub trait OverlayRenderer { + fn init(&mut self, app: &mut AppState); + fn pause(&mut self, app: &mut AppState); + fn resume(&mut self, app: &mut AppState); + fn render(&mut self, app: &mut AppState); + fn view(&mut self) -> Option>; +} + +pub struct FallbackRenderer; + +impl OverlayRenderer for FallbackRenderer { + fn init(&mut self, _app: &mut AppState) {} + fn pause(&mut self, _app: &mut AppState) {} + fn resume(&mut self, _app: &mut AppState) {} + fn render(&mut self, _app: &mut AppState) {} + fn view(&mut self) -> Option> { + unimplemented!() + } +} +// Boilerplate and dummies + +pub enum RelativeTo { + None, + Head, + Hand(usize), +} + +pub struct SplitOverlayBackend { + pub renderer: Box, + pub interaction: Box, +} + +impl Default for SplitOverlayBackend { + fn default() -> SplitOverlayBackend { + SplitOverlayBackend { + renderer: Box::new(FallbackRenderer), + interaction: Box::new(DummyInteractionHandler), + } + } +} + +impl OverlayBackend for SplitOverlayBackend {} +impl OverlayRenderer for SplitOverlayBackend { + fn init(&mut self, app: &mut AppState) { + self.renderer.init(app); + } + fn pause(&mut self, app: &mut AppState) { + self.renderer.pause(app); + } + fn resume(&mut self, app: &mut AppState) { + self.renderer.resume(app); + } + fn render(&mut self, app: &mut AppState) { + self.renderer.render(app); + } + fn view(&mut self) -> Option> { + self.renderer.view() + } +} +impl InteractionHandler for SplitOverlayBackend { + fn on_left(&mut self, app: &mut AppState, pointer: usize) { + self.interaction.on_left(app, pointer); + } + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) { + self.interaction.on_hover(app, hit); + } + fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { + self.interaction.on_scroll(app, hit, delta); + } + fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { + self.interaction.on_pointer(app, hit, pressed); + } +} diff --git a/src/graphics.rs b/src/graphics.rs index 725b3a3f..5ce061a4 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,10 +1,67 @@ -use std::{sync::Arc, slice::Iter, io::Cursor, error::Error}; +use std::{error::Error, io::Cursor, slice::Iter, sync::Arc}; -use log::{info,error}; -use vulkano::{format::Format, device::{physical::PhysicalDeviceType, QueueFlags, DeviceExtensions, Device, DeviceCreateInfo, Features, QueueCreateInfo, Queue}, Version, VulkanLibrary, instance::{Instance, InstanceCreateInfo}, memory::allocator::{StandardMemoryAllocator, AllocationCreateInfo, MemoryUsage}, command_buffer::{allocator::StandardCommandBufferAllocator, CommandBufferUsage, AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderingAttachmentInfo, RenderingInfo, PrimaryCommandBufferAbstract, CommandBufferExecFuture, SubpassContents, SecondaryAutoCommandBuffer, CommandBufferInheritanceInfo, CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderingInfo}, descriptor_set::{allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet}, buffer::{Buffer, BufferCreateInfo, BufferUsage, BufferContents, Subbuffer, allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}}, sampler::{Filter, SamplerCreateInfo, SamplerAddressMode, Sampler}, pipeline::{GraphicsPipeline, Pipeline, graphics::{render_pass::PipelineRenderingCreateInfo, vertex_input::Vertex, input_assembly::InputAssemblyState, viewport::{ViewportState, Viewport}, color_blend::{ColorBlendState, AttachmentBlend}}, PipelineBindPoint}, image::{ImageViewAbstract, ImageUsage, SwapchainImage, ImageDimensions, ImmutableImage, MipmapsCount, StorageImage, ImageError, SubresourceData, ImageCreateFlags, AttachmentImage}, swapchain::{Surface, Swapchain, SwapchainCreateInfo, CompositeAlpha}, shader::ShaderModule, render_pass::{StoreOp, LoadOp}, sync::future::NowFuture}; -use winit::{event_loop::EventLoop, window::{WindowBuilder, Window}}; +use ash::vk::SubmitInfo; +use log::{debug, error, info}; +use smallvec::smallvec; +use vulkano::{ + buffer::{ + allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, + Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, + }, + command_buffer::{ + allocator::{ + CommandBufferAllocator, CommandBufferBuilderAlloc, StandardCommandBufferAllocator, + }, + sys::{CommandBufferBeginInfo, UnsafeCommandBufferBuilder}, + AutoCommandBufferBuilder, CommandBufferExecFuture, CommandBufferInheritanceInfo, + CommandBufferInheritanceRenderPassType, CommandBufferInheritanceRenderingInfo, + CommandBufferLevel, CommandBufferUsage, PrimaryAutoCommandBuffer, + PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, + SecondaryAutoCommandBuffer, SubpassContents, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet, + }, + device::{ + physical::{PhysicalDevice, PhysicalDeviceType}, + Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, QueueFlags, + }, + format::Format, + image::{ + sys::Image, AttachmentImage, ImageAccess, ImageCreateFlags, ImageDimensions, ImageError, + ImageLayout, ImageUsage, ImageViewAbstract, ImmutableImage, MipmapsCount, StorageImage, + SubresourceData, SwapchainImage, + }, + instance::{Instance, InstanceCreateInfo, InstanceExtensions}, + memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}, + pipeline::{ + graphics::{ + color_blend::{AttachmentBlend, ColorBlendState}, + input_assembly::InputAssemblyState, + render_pass::PipelineRenderingCreateInfo, + vertex_input::Vertex, + viewport::{Viewport, ViewportState}, + }, + GraphicsPipeline, Pipeline, PipelineBindPoint, + }, + render_pass::{LoadOp, StoreOp}, + sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, + shader::ShaderModule, + swapchain::{CompositeAlpha, Surface, Swapchain, SwapchainCreateInfo}, + sync::{ + fence::Fence, future::NowFuture, AccessFlags, DependencyInfo, ImageMemoryBarrier, + PipelineStages, + }, + Version, VulkanLibrary, VulkanObject, +}; use vulkano_win::VkSurfaceBuild; -use wlx_capture::frame::{DmabufFrame, DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888}; +use winit::{ + event_loop::EventLoop, + window::{Window, WindowBuilder}, +}; +use wlx_capture::frame::{ + DmabufFrame, DRM_FORMAT_ABGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888, +}; #[repr(C)] #[derive(BufferContents, Vertex, Copy, Clone, Debug)] @@ -15,10 +72,10 @@ pub struct Vert2Uv { pub in_uv: [f32; 2], } -pub const INDICES : [u16; 6] = [2, 1, 0, 1, 2, 3]; +pub const INDICES: [u16; 6] = [2, 1, 0, 1, 2, 3]; pub struct WlxGraphics { - pub instance: Arc, + pub instance: Arc, pub device: Arc, pub queue: Arc, @@ -28,18 +85,26 @@ pub struct WlxGraphics { pub command_buffer_allocator: Arc, pub descriptor_set_allocator: Arc, + pub quad_verts: Subbuffer<[Vert2Uv]>, pub quad_indices: Subbuffer<[u16]>, } impl WlxGraphics { - pub fn new() -> (Arc, EventLoop<()>) { + pub fn new( + vk_instance_extensions: InstanceExtensions, + mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions, + ) -> (Arc, EventLoop<()>) { #[cfg(debug_assertions)] let layers = vec!["VK_LAYER_KHRONOS_validation".to_owned()]; #[cfg(not(debug_assertions))] let layers = vec![]; let library = VulkanLibrary::new().unwrap(); - let required_extensions = vulkano_win::required_extensions(&library); + let library_extensions = vulkano_win::required_extensions(&library); + let required_extensions = library_extensions.union(&vk_instance_extensions); + + debug!("Instance exts for app: {:?}", &required_extensions); + debug!("Instance exts for runtime: {:?}", &vk_instance_extensions); let instance = Instance::new( library, @@ -61,13 +126,14 @@ impl WlxGraphics { ..DeviceExtensions::empty() }; + debug!("Device exts for app: {:?}", &device_extensions); + // TODO headless let event_loop = EventLoop::new(); let surface = WindowBuilder::new() .build_vk_surface(&event_loop, instance.clone()) .unwrap(); - let (physical_device, queue_family_index) = instance .enumerate_physical_devices() .unwrap() @@ -75,7 +141,14 @@ impl WlxGraphics { p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering }) .filter(|p| { - p.supported_extensions().contains(&device_extensions) + let runtime_extensions = vk_device_extensions_fn(p); + debug!( + "Device exts for {}: {:?}", + p.properties().device_name, + &runtime_extensions + ); + let my_extensions = runtime_extensions.union(&device_extensions); + p.supported_extensions().contains(&my_extensions) }) .filter_map(|p| { p.queue_family_properties() @@ -87,20 +160,18 @@ impl WlxGraphics { }) .map(|i| (p, i as u32)) }) - .min_by_key(|(p, _)| { - match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - } + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, }) .expect("no suitable physical device found"); info!( - "Nice {} you have there.", + "Using vkPhysicalDevice: {}", physical_device.properties().device_name, ); @@ -128,8 +199,44 @@ impl WlxGraphics { let queue = queues.next().unwrap(); let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(device.clone(), Default::default())); - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(device.clone())); + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + let descriptor_set_allocator = + Arc::new(StandardDescriptorSetAllocator::new(device.clone())); + + let vertices = [ + Vert2Uv { + in_pos: [0., 0.], + in_uv: [0., 0.], + }, + Vert2Uv { + in_pos: [0., 1.], + in_uv: [0., 1.], + }, + Vert2Uv { + in_pos: [1., 0.], + in_uv: [1., 0.], + }, + Vert2Uv { + in_pos: [1., 1.], + in_uv: [1., 1.], + }, + ]; + let quad_verts = Buffer::from_iter( + &memory_allocator, + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + usage: MemoryUsage::Upload, + ..Default::default() + }, + vertices.into_iter(), + ) + .unwrap(); let quad_indices = Buffer::from_iter( &memory_allocator, @@ -142,7 +249,8 @@ impl WlxGraphics { ..Default::default() }, INDICES.iter().cloned(), - ).unwrap(); + ) + .unwrap(); let me = Self { instance, @@ -153,21 +261,30 @@ impl WlxGraphics { command_buffer_allocator, descriptor_set_allocator, quad_indices, + quad_verts, }; (Arc::new(me), event_loop) } - pub fn create_swapchain(&self, format: Option) -> (Arc, Vec>) { + pub fn create_swapchain( + &self, + format: Option, + ) -> (Arc, Vec>) { let (min_image_count, composite_alpha, image_format) = if let Some(format) = format { (1, CompositeAlpha::Opaque, format) } else { - let surface_capabilities = self.device + let surface_capabilities = self + .device .physical_device() .surface_capabilities(&self.surface, Default::default()) .unwrap(); - - let composite_alpha = surface_capabilities.supported_composite_alpha.into_iter().next().unwrap(); + + let composite_alpha = surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(); let image_format = Some( self.device @@ -176,9 +293,18 @@ impl WlxGraphics { .unwrap()[0] .0, ); - (surface_capabilities.min_image_count, composite_alpha, image_format.unwrap()) + ( + surface_capabilities.min_image_count, + composite_alpha, + image_format.unwrap(), + ) }; - let window = self.surface.object().unwrap().downcast_ref::().unwrap(); + let window = self + .surface + .object() + .unwrap() + .downcast_ref::() + .unwrap(); let swapchain = Swapchain::new( self.device.clone(), self.surface.clone(), @@ -196,7 +322,15 @@ impl WlxGraphics { swapchain } - pub fn upload_verts(&self, width: f32, height: f32, x: f32, y: f32, w: f32, h: f32) -> Subbuffer<[Vert2Uv]> { + pub fn upload_verts( + &self, + width: f32, + height: f32, + x: f32, + y: f32, + w: f32, + h: f32, + ) -> Subbuffer<[Vert2Uv]> { let rw = width; let rh = height; @@ -207,16 +341,30 @@ impl WlxGraphics { let y1 = h / rh + y0; let vertices = [ - Vert2Uv { in_pos: [x0, y0], in_uv: [0.0, 0.0] }, - Vert2Uv { in_pos: [x0, y1], in_uv: [0.0, 1.0] }, - Vert2Uv { in_pos: [x1, y0], in_uv: [1.0, 0.0] }, - Vert2Uv { in_pos: [x1, y1], in_uv: [1.0, 1.0] }, + Vert2Uv { + in_pos: [x0, y0], + in_uv: [0.0, 0.0], + }, + Vert2Uv { + in_pos: [x0, y1], + in_uv: [0.0, 1.0], + }, + Vert2Uv { + in_pos: [x1, y0], + in_uv: [1.0, 0.0], + }, + Vert2Uv { + in_pos: [x1, y1], + in_uv: [1.0, 1.0], + }, ]; self.upload_buffer(BufferUsage::VERTEX_BUFFER, vertices.iter()) } pub fn upload_buffer(&self, usage: BufferUsage, contents: Iter<'_, T>) -> Subbuffer<[T]> - where T: BufferContents + Clone { + where + T: BufferContents + Clone, + { Buffer::from_iter( &self.memory_allocator, BufferCreateInfo { @@ -228,8 +376,9 @@ impl WlxGraphics { ..Default::default() }, contents.cloned(), - ).unwrap() - } + ) + .unwrap() + } pub fn dmabuf_texture(&self, frame: DmabufFrame) -> Result, ImageError> { let dimensions = ImageDimensions::Dim2d { @@ -246,7 +395,8 @@ impl WlxGraphics { _ => panic!("Unsupported dmabuf format {:x}", frame.format.fourcc), }; - let planes = frame.planes + let planes = frame + .planes .iter() .take(frame.num_planes) .filter_map(|plane| { @@ -258,7 +408,8 @@ impl WlxGraphics { offset: plane.offset as _, row_pitch: plane.stride as _, }) - }).collect(); + }) + .collect(); StorageImage::new_from_dma_buf_fd( &self.memory_allocator, @@ -272,28 +423,112 @@ impl WlxGraphics { frame.format.modifier, ) } + pub fn render_texture(&self, width: u32, height: u32, format: Format) -> Arc { let tex = AttachmentImage::with_usage( - &self.memory_allocator, - [width, height], + &self.memory_allocator, + [width, height], format, ImageUsage::SAMPLED | ImageUsage::TRANSFER_SRC | ImageUsage::COLOR_ATTACHMENT, - ).unwrap(); + ) + .unwrap(); tex } - pub fn create_pipeline(self: &Arc, vert: Arc, frag: Arc, format: Format) -> Arc { + + pub fn create_pipeline( + self: &Arc, + vert: Arc, + frag: Arc, + format: Format, + ) -> Arc { Arc::new(WlxPipeline::new(self.clone(), vert, frag, format)) } - pub fn create_command_buffer(self: &Arc, usage: CommandBufferUsage) -> WlxCommandBuffer { + + pub fn create_command_buffer( + self: &Arc, + usage: CommandBufferUsage, + ) -> WlxCommandBuffer { let command_buffer = AutoCommandBufferBuilder::primary( &self.command_buffer_allocator, self.queue.queue_family_index(), usage, - ).unwrap(); - WlxCommandBuffer { graphics: self.clone(), command_buffer } + ) + .unwrap(); + WlxCommandBuffer { + graphics: self.clone(), + command_buffer, + } } + pub fn transition_layout( + &self, + image: Arc, + old_layout: ImageLayout, + new_layout: ImageLayout, + ) -> Fence { + let barrier = ImageMemoryBarrier { + src_stages: PipelineStages::ALL_TRANSFER, + src_access: AccessFlags::TRANSFER_WRITE, + dst_stages: PipelineStages::ALL_TRANSFER, + dst_access: AccessFlags::TRANSFER_READ, + old_layout, + new_layout, + subresource_range: image.subresource_range(), + ..ImageMemoryBarrier::image(image) + }; + + let builder_alloc = self + .command_buffer_allocator + .allocate( + self.queue.queue_family_index(), + CommandBufferLevel::Primary, + 1, + ) + .unwrap() + .next() + .unwrap(); + + let command_buffer = unsafe { + let mut builder = UnsafeCommandBufferBuilder::new( + &builder_alloc.inner(), + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); + + builder.pipeline_barrier(&DependencyInfo { + image_memory_barriers: smallvec![barrier], + ..Default::default() + }); + builder.build().unwrap() + }; + + let fence = vulkano::sync::fence::Fence::new( + self.device.clone(), + vulkano::sync::fence::FenceCreateInfo::default(), + ) + .unwrap(); + + let fns = self.device.fns(); + unsafe { + (fns.v1_0.queue_submit)( + self.queue.handle(), + 1, + [SubmitInfo::builder() + .command_buffers(&[command_buffer.handle()]) + .build()] + .as_ptr(), + fence.handle(), + ) + } + .result() + .unwrap(); + + fence + } } pub struct WlxCommandBuffer { @@ -306,7 +541,9 @@ impl WlxCommandBuffer { &self.command_buffer } - pub fn inner_mut(&mut self) -> &mut AutoCommandBufferBuilder> { + pub fn inner_mut( + &mut self, + ) -> &mut AutoCommandBufferBuilder> { &mut self.command_buffer } @@ -314,37 +551,55 @@ impl WlxCommandBuffer { self.command_buffer } - pub fn begin(mut self, render_target: Arc) -> Self - { + pub fn begin( + mut self, + render_target: Arc, + want_layout: Option, + ) -> Self { + if let Some(want_layout) = want_layout { + let mut barrier = + ImageMemoryBarrier::image(render_target.image().inner().image.clone()); + barrier.old_layout = ImageLayout::ColorAttachmentOptimal; + barrier.new_layout = want_layout; + } + self.command_buffer - .begin_rendering(RenderingInfo { - contents: SubpassContents::SecondaryCommandBuffers, - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: LoadOp::Clear, - store_op: StoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 0.0].into()), - ..RenderingAttachmentInfo::image_view( - render_target.clone(), - ) - })], - ..Default::default() - }).unwrap(); + .begin_rendering(RenderingInfo { + contents: SubpassContents::SecondaryCommandBuffers, + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: LoadOp::Clear, + store_op: StoreOp::Store, + clear_value: Some([0.0, 0.0, 0.0, 0.0].into()), + ..RenderingAttachmentInfo::image_view(render_target.clone()) + })], + ..Default::default() + }) + .unwrap(); self } - pub fn run_ref(&mut self, pass: &WlxPass) -> &mut Self - { - let _ = self.command_buffer.execute_commands(pass.command_buffer.clone()).unwrap(); + pub fn run_ref(&mut self, pass: &WlxPass) -> &mut Self { + let _ = self + .command_buffer + .execute_commands(pass.command_buffer.clone()) + .unwrap(); self } - pub fn run(mut self, pass: &WlxPass) -> Self - { - let _ = self.command_buffer.execute_commands(pass.command_buffer.clone()); + pub fn run(mut self, pass: &WlxPass) -> Self { + let _ = self + .command_buffer + .execute_commands(pass.command_buffer.clone()); self } - pub fn texture2d(&mut self, width: u32, height: u32, format: Format, data: Vec) -> Arc { + pub fn texture2d( + &mut self, + width: u32, + height: u32, + format: Format, + data: Vec, + ) -> Arc { let dimensions = ImageDimensions::Dim2d { width, height, @@ -374,7 +629,6 @@ impl WlxCommandBuffer { reader.next_frame(&mut image_data).unwrap(); self.texture2d(width, height, Format::R8G8B8A8_UNORM, image_data) } - } impl WlxCommandBuffer { @@ -414,26 +668,33 @@ pub struct WlxPipeline { } impl WlxPipeline { - fn new(graphics: Arc, vert: Arc, frag: Arc, format: Format) -> Self { + fn new( + graphics: Arc, + vert: Arc, + frag: Arc, + format: Format, + ) -> Self { let vep = vert.entry_point("main").unwrap(); let fep = frag.entry_point("main").unwrap(); let pipeline = GraphicsPipeline::start() - .render_pass(PipelineRenderingCreateInfo { - color_attachment_formats: vec![Some(format)], - ..Default::default() - }) - .color_blend_state(ColorBlendState::default().blend( - AttachmentBlend::alpha() - )) - .vertex_input_state(Vert2Uv::per_vertex()) - .input_assembly_state(InputAssemblyState::new()) - .vertex_shader(vep, ()) - .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) - .fragment_shader(fep, ()) - .build(graphics.device.clone()) - .unwrap(); + .render_pass(PipelineRenderingCreateInfo { + color_attachment_formats: vec![Some(format)], + ..Default::default() + }) + .color_blend_state(ColorBlendState::default().blend(AttachmentBlend::alpha())) + .vertex_input_state(Vert2Uv::per_vertex()) + .input_assembly_state(InputAssemblyState::new()) + .vertex_shader(vep, ()) + .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) + .fragment_shader(fep, ()) + .build(graphics.device.clone()) + .unwrap(); - Self { graphics, pipeline, format} + Self { + graphics, + pipeline, + format, + } } pub fn inner(&self) -> Arc { @@ -444,7 +705,12 @@ impl WlxPipeline { self.graphics.clone() } - pub fn uniform_sampler(&self, set: usize, texture: Arc, filter: Filter) -> Arc { + pub fn uniform_sampler( + &self, + set: usize, + texture: Arc, + filter: Filter, + ) -> Arc { let sampler = Sampler::new( self.graphics.device.clone(), SamplerCreateInfo { @@ -455,7 +721,7 @@ impl WlxPipeline { }, ) .unwrap(); - + let layout = self.pipeline.layout().set_layouts().get(set).unwrap(); PersistentDescriptorSet::new( @@ -467,7 +733,9 @@ impl WlxPipeline { } pub fn uniform_buffer(&self, set: usize, data: Vec) -> Arc - where T: BufferContents + Copy { + where + T: BufferContents + Copy, + { let uniform_buffer = SubbufferAllocator::new( self.graphics.memory_allocator.clone(), SubbufferAllocatorCreateInfo { @@ -486,27 +754,44 @@ impl WlxPipeline { PersistentDescriptorSet::new( &self.graphics.descriptor_set_allocator, layout.clone(), - [WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)] - ).unwrap() + [WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)], + ) + .unwrap() } - pub fn create_pass(self: &Arc, dimensions: [f32; 2], vertex_buffer: Subbuffer<[Vert2Uv]>, index_buffer: Subbuffer<[u16]>, descriptor_sets: Vec>) -> WlxPass { - WlxPass::new(self.clone(), dimensions, vertex_buffer, index_buffer, descriptor_sets) + pub fn create_pass( + self: &Arc, + dimensions: [f32; 2], + vertex_buffer: Subbuffer<[Vert2Uv]>, + index_buffer: Subbuffer<[u16]>, + descriptor_sets: Vec>, + ) -> WlxPass { + WlxPass::new( + self.clone(), + dimensions, + vertex_buffer, + index_buffer, + descriptor_sets, + ) } } -pub struct WlxPass -{ +pub struct WlxPass { pipeline: Arc, vertex_buffer: Subbuffer<[Vert2Uv]>, index_buffer: Subbuffer<[u16]>, descriptor_sets: Vec>, pub command_buffer: Arc, -} +} -impl WlxPass -{ - fn new(pipeline: Arc, dimensions: [f32; 2], vertex_buffer: Subbuffer<[Vert2Uv]>, index_buffer: Subbuffer<[u16]>, descriptor_sets: Vec>) -> Self { +impl WlxPass { + fn new( + pipeline: Arc, + dimensions: [f32; 2], + vertex_buffer: Subbuffer<[Vert2Uv]>, + index_buffer: Subbuffer<[u16]>, + descriptor_sets: Vec>, + ) -> Self { let viewport = Viewport { origin: [0.0, 0.0], dimensions, @@ -515,21 +800,23 @@ impl WlxPass let pipeline_inner = pipeline.inner().clone(); let mut command_buffer = AutoCommandBufferBuilder::secondary( - &pipeline.graphics.command_buffer_allocator, - pipeline.graphics.queue.queue_family_index(), - CommandBufferUsage::MultipleSubmit, - CommandBufferInheritanceInfo { - render_pass: Some(CommandBufferInheritanceRenderPassType::BeginRendering( - CommandBufferInheritanceRenderingInfo { - color_attachment_formats: vec![Some(pipeline.format)], - ..Default::default() - })), - ..Default::default() - } - ) - .unwrap(); + &pipeline.graphics.command_buffer_allocator, + pipeline.graphics.queue.queue_family_index(), + CommandBufferUsage::MultipleSubmit, + CommandBufferInheritanceInfo { + render_pass: Some(CommandBufferInheritanceRenderPassType::BeginRendering( + CommandBufferInheritanceRenderingInfo { + color_attachment_formats: vec![Some(pipeline.format)], + ..Default::default() + }, + )), + ..Default::default() + }, + ) + .unwrap(); - command_buffer.set_viewport(0, [viewport]) + command_buffer + .set_viewport(0, [viewport]) .bind_pipeline_graphics(pipeline_inner) .bind_descriptor_sets( PipelineBindPoint::Graphics, @@ -545,9 +832,10 @@ impl WlxPass error!("Failed to draw: {}", source); } Err(err) - }).unwrap(); + }) + .unwrap(); - Self { + Self { pipeline, vertex_buffer, index_buffer, @@ -556,4 +844,3 @@ impl WlxPass } } } - diff --git a/src/gui/font.rs b/src/gui/font.rs index 19641b5e..15b48d73 100644 --- a/src/gui/font.rs +++ b/src/gui/font.rs @@ -4,7 +4,7 @@ use fontconfig::{FontConfig, OwnedPattern}; use freetype::{bitmap::PixelMode, face::LoadFlag, Face, Library}; use idmap::IdMap; use log::debug; -use vulkano::{format::Format, command_buffer::CommandBufferUsage, image::ImmutableImage}; +use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::ImmutableImage}; use crate::graphics::WlxGraphics; @@ -23,9 +23,6 @@ struct FontCollection { struct Font { face: Face, - path: String, - index: isize, - size: isize, glyphs: IdMap>, } @@ -50,7 +47,12 @@ impl FontCache { } } - pub fn get_text_size(&mut self, text: &str, size: isize, graphics: Arc) -> (f32, f32) { + pub fn get_text_size( + &mut self, + text: &str, + size: isize, + graphics: Arc, + ) -> (f32, f32) { let sizef = size as f32; let height = sizef + ((text.lines().count() as f32) - 1f32) * (sizef * 1.5); @@ -59,7 +61,10 @@ impl FontCache { for line in text.lines() { let w: f32 = line .chars() - .map(|c| self.get_glyph_for_cp(c as usize, size, graphics.clone()).advance) + .map(|c| { + self.get_glyph_for_cp(c as usize, size, graphics.clone()) + .advance + }) .sum(); if w > max_w { @@ -69,7 +74,12 @@ impl FontCache { (max_w, height) } - pub fn get_glyphs(&mut self, text: &str, size: isize, graphics: Arc) -> Vec> { + pub fn get_glyphs( + &mut self, + text: &str, + size: isize, + graphics: Arc, + ) -> Vec> { let mut glyphs = Vec::new(); for line in text.lines() { for c in line.chars() { @@ -143,13 +153,7 @@ impl FontCache { let mut glyphs = IdMap::new(); glyphs.insert(0, zero_glyph); - let font = Font { - face, - path: path.to_string(), - size, - index: font_idx as _, - glyphs, - }; + let font = Font { face, glyphs }; coll.fonts.push(font); idx @@ -159,7 +163,12 @@ impl FontCache { } } - fn get_glyph_for_cp(&mut self, cp: usize, size: isize, graphics: Arc) -> Rc { + fn get_glyph_for_cp( + &mut self, + cp: usize, + size: isize, + graphics: Arc, + ) -> Rc { let key = self.get_font_for_cp(cp, size); let font = &mut self.collections[size].fonts[key]; diff --git a/src/gui/mod.rs b/src/gui/mod.rs index cfb9734f..111ca1fd 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -4,16 +4,16 @@ use glam::{Vec2, Vec3}; use vulkano::{ command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer}, format::Format, - image::{view::ImageView, AttachmentImage}, + image::{view::ImageView, AttachmentImage, ImageLayout, ImageViewAbstract}, sampler::Filter, }; use crate::{ - graphics::{WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline}, - overlays::{ - interactions::{InteractionHandler, PointerHit}, - OverlayBackend, OverlayRenderer, + backend::{ + input::{InteractionHandler, PointerHit}, + overlay::{OverlayBackend, OverlayRenderer}, }, + graphics::{WlxCommandBuffer, WlxGraphics, WlxPass, WlxPipeline}, shaders::{frag_color, frag_glyph, frag_sprite, vert_common}, state::AppState, }; @@ -203,11 +203,8 @@ pub struct Canvas { interact_stride: usize, interact_rows: usize, - tex_fg: Arc, view_fg: Arc>, - tex_bg: Arc, view_bg: Arc>, - tex_final: Arc, view_final: Arc>, pass_fg: WlxPass, @@ -284,11 +281,8 @@ impl Canvas { interact_map: vec![None; stride * rows], interact_stride: stride, interact_rows: rows, - tex_fg, view_fg, - tex_bg, view_bg, - tex_final, view_final, pass_fg, pass_bg, @@ -323,7 +317,7 @@ impl Canvas { .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit) - .begin(self.view_bg.clone()); + .begin(self.view_bg.clone(), None); for c in self.controls.iter_mut() { if let Some(fun) = c.on_render_bg { fun(c, &self.canvas, app, &mut cmd_buffer); @@ -337,7 +331,7 @@ impl Canvas { .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit) - .begin(self.view_fg.clone()); + .begin(self.view_fg.clone(), None); for c in self.controls.iter_mut() { if let Some(fun) = c.on_render_fg { fun(c, &self.canvas, app, &mut cmd_buffer); @@ -352,32 +346,32 @@ impl Canvas { } impl InteractionHandler for Canvas { - fn on_left(&mut self, _app: &mut AppState, hand: usize) { - self.hover_controls[hand] = None; + 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) { if let Some(i) = self.interactive_get_idx(hit.uv) { - self.hover_controls[hit.hand] = Some(i); + self.hover_controls[hit.pointer] = Some(i); } else { - self.hover_controls[hit.hand] = None; + self.hover_controls[hit.pointer] = 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.hand] + 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.hand] = Some(idx); + self.pressed_controls[hit.pointer] = Some(idx); f(c, &mut self.canvas.data, app); } } else if let Some(ref mut f) = c.on_release { - self.pressed_controls[hit.hand] = None; + self.pressed_controls[hit.pointer] = None; f(c, &mut self.canvas.data, app); } } @@ -410,7 +404,10 @@ impl OverlayRenderer for Canvas { .canvas .graphics .create_command_buffer(CommandBufferUsage::OneTimeSubmit) - .begin(self.view_final.clone()); + .begin( + self.view_final.clone(), + Some(ImageLayout::TransferSrcOptimal), + ); if dirty { self.render_fg(app); @@ -437,8 +434,8 @@ impl OverlayRenderer for Canvas { let _ = cmd_buffer.end_render_and_execute(); } - fn view(&mut self) -> Arc { - self.view_final.clone() + fn view(&mut self) -> Option> { + Some(self.view_final.clone()) } } diff --git a/src/main.rs b/src/main.rs index fac077a6..1ec3ec54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,43 +4,12 @@ mod graphics; mod gui; mod input; mod overlays; -mod ovr; mod shaders; mod state; -use std::collections::VecDeque; -use std::sync::Arc; - -use crate::graphics::{Vert2Uv, WlxGraphics, INDICES}; -use crate::input::initialize_input; -use crate::overlays::watch::create_watch; -use crate::{ - shaders::{frag_sprite, vert_common}, - state::AppState, -}; +use crate::backend::openvr::openvr_run; use env_logger::Env; -use log::{info, warn}; -use vulkano::{ - buffer::BufferUsage, - command_buffer::CommandBufferUsage, - image::{ - view::{ImageView, ImageViewCreateInfo}, - ImageAccess, ImageSubresourceRange, ImageViewType, SwapchainImage, - }, - pipeline::graphics::viewport::Viewport, - sampler::Filter, - swapchain::{ - acquire_next_image, AcquireError, SwapchainCreateInfo, SwapchainCreationError, - SwapchainPresentInfo, - }, - sync::{self, FlushError, GpuFuture}, -}; -use winit::{ - event::{Event, WindowEvent}, - event_loop::ControlFlow, - window::Window, -}; -use wlx_capture::{frame::WlxFrame, wayland::WlxClient, wlr::WlrDmabufCapture, WlxCapture}; +use log::info; fn main() { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); @@ -50,222 +19,5 @@ fn main() { env!("CARGO_PKG_VERSION") ); - let (graphics, event_loop) = WlxGraphics::new(); - let (mut swapchain, images) = graphics.create_swapchain(None); - - let mut app = AppState { - fc: crate::gui::font::FontCache::new(), - session: crate::state::AppSession::load(), - tasks: VecDeque::with_capacity(16), - graphics: graphics.clone(), - format: swapchain.image_format(), - input: initialize_input(), - }; - - let wl = WlxClient::new().unwrap(); - let output_id = wl.outputs[0].id; - let mut capture = WlrDmabufCapture::new(wl, output_id).unwrap(); - let rx = capture.init(); - - let vertices = [ - Vert2Uv { - in_pos: [0., 0.], - in_uv: [0., 0.], - }, - Vert2Uv { - in_pos: [0., 1.], - in_uv: [0., 1.], - }, - Vert2Uv { - in_pos: [1., 0.], - in_uv: [1., 0.], - }, - Vert2Uv { - in_pos: [1., 1.], - in_uv: [1., 1.], - }, - ]; - - let vertex_buffer = graphics.upload_buffer(BufferUsage::VERTEX_BUFFER, vertices.iter()); - let index_buffer = graphics.upload_buffer(BufferUsage::INDEX_BUFFER, INDICES.iter()); - - let vs = vert_common::load(graphics.device.clone()).unwrap(); - let fs = frag_sprite::load(graphics.device.clone()).unwrap(); - - let uploads = graphics.create_command_buffer(CommandBufferUsage::OneTimeSubmit); - - let mut watch = create_watch(&app, vec![]); - watch.init(&mut app); - watch.render(&mut app); - - let pipeline1 = graphics.create_pipeline(vs.clone(), fs.clone(), swapchain.image_format()); - let set1 = pipeline1.uniform_sampler(0, watch.view(), Filter::Nearest); - - capture.request_new_frame(); - - let pipeline = graphics.create_pipeline(vs, fs, swapchain.image_format()); - let set0; - loop { - if let Ok(frame) = rx.try_recv() { - match frame { - WlxFrame::Dmabuf(dmabuf_frame) => match graphics.dmabuf_texture(dmabuf_frame) { - Ok(tex) => { - let format = tex.format(); - let view = ImageView::new( - tex, - ImageViewCreateInfo { - format: Some(format), - view_type: ImageViewType::Dim2d, - subresource_range: ImageSubresourceRange::from_parameters( - format, 1, 1, - ), - ..Default::default() - }, - ) - .unwrap(); - set0 = pipeline.uniform_sampler(0, view, Filter::Nearest); - break; - } - Err(e) => { - warn!("Failed to create texture from dmabuf: {}", e); - } - }, - _ => { - warn!("Received non-dmabuf frame"); - } - } - } - } - - //let set1 = graphics.uniform_buffer(1, vec![1.0, 1.0, 1.0, 1.0]); - let image_extent_f32 = [ - swapchain.image_extent()[0] as f32, - swapchain.image_extent()[1] as f32, - ]; - let image_extent2_f32 = [ - swapchain.image_extent()[0] as f32 / 2., - swapchain.image_extent()[1] as f32 / 2., - ]; - let pass = pipeline.create_pass( - image_extent_f32, - vertex_buffer.clone(), - index_buffer.clone(), - vec![set0], - ); - let pass2 = pipeline1.create_pass(image_extent2_f32, vertex_buffer, index_buffer, vec![set1]); - - let mut viewport = Viewport { - origin: [0.0, 0.0], - dimensions: [1024.0, 1024.0], - depth_range: 0.0..1.0, - }; - - let mut attachment_image_views = window_size_dependent_setup(&images, &mut viewport); - - //let set1 = pipeline.uniform_buffer(1, vec![1.0, 0.0, 1.0, 1.0]); - - let mut recreate_swapchain = false; - let mut previous_frame_end = //Some(sync::now(graphics.device.clone()).boxed()); - Some(uploads.end_and_execute().boxed()); - - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } - Event::WindowEvent { - event: WindowEvent::Resized(_), - .. - } => { - recreate_swapchain = true; - } - Event::RedrawEventsCleared => { - previous_frame_end.as_mut().unwrap().cleanup_finished(); - - if recreate_swapchain { - let window = graphics - .surface - .object() - .unwrap() - .downcast_ref::() - .unwrap(); - let (new_swapchain, new_images) = match swapchain.recreate(SwapchainCreateInfo { - image_extent: window.inner_size().into(), - ..swapchain.create_info() - }) { - Ok(r) => r, - Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("failed to recreate swapchain: {e}"), - }; - - swapchain = new_swapchain; - attachment_image_views = window_size_dependent_setup(&new_images, &mut viewport); - recreate_swapchain = false; - } - - let (image_index, suboptimal, acquire_future) = - match acquire_next_image(swapchain.clone(), None) { - Ok(r) => r, - Err(AcquireError::OutOfDate) => { - recreate_swapchain = true; - return; - } - Err(e) => panic!("failed to acquire next image: {e}"), - }; - - if suboptimal { - recreate_swapchain = true; - } - - let cmd = graphics - .create_command_buffer(CommandBufferUsage::OneTimeSubmit) - .begin(attachment_image_views[image_index as usize].clone()) - .run(&pass) - .run(&pass2) - .end_render(); - - let future = previous_frame_end - .take() - .unwrap() - .join(acquire_future) - .then_execute(graphics.queue.clone(), cmd) - .unwrap() - .then_swapchain_present( - graphics.queue.clone(), - SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), - ) - .then_signal_fence_and_flush(); - - match future { - Ok(future) => { - previous_frame_end = Some(future.boxed()); - } - Err(FlushError::OutOfDate) => { - recreate_swapchain = true; - previous_frame_end = Some(sync::now(graphics.device.clone()).boxed()); - } - Err(e) => { - println!("failed to flush future: {e}"); - previous_frame_end = Some(sync::now(graphics.device.clone()).boxed()); - } - } - } - _ => (), - }); -} - -/// This function is called once during initialization, then again whenever the window is resized. -fn window_size_dependent_setup( - images: &[Arc], - viewport: &mut Viewport, -) -> Vec>> { - let dimensions = images[0].dimensions().width_height(); - viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32]; - - images - .iter() - .map(|image| ImageView::new_default(image.clone()).unwrap()) - .collect::>() + openvr_run(); } diff --git a/src/overlays/interactions.rs b/src/overlays/interactions.rs deleted file mode 100644 index 6f052269..00000000 --- a/src/overlays/interactions.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{collections::VecDeque, time::Instant}; - -use glam::{Affine3A, Vec2, Vec3}; - -use crate::state::AppState; - -pub const HAND_LEFT: usize = 0; -pub const HAND_RIGHT: usize = 1; - -pub const POINTER_NORM: u16 = 0; -pub const POINTER_SHIFT: u16 = 1; -pub const POINTER_ALT: u16 = 2; - -pub trait InteractionHandler { - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit); - fn on_left(&mut self, app: &mut AppState, hand: usize); - fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool); - fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32); -} - -// --- Dummies & plumbing below --- - -impl Default for PointerState { - fn default() -> Self { - Self { - click: false, - grab: false, - show_hide: false, - scroll: 0., - } - } -} - -pub struct DummyInteractionHandler; - -impl InteractionHandler for DummyInteractionHandler { - fn on_left(&mut self, _app: &mut AppState, _hand: usize) {} - fn on_hover(&mut self, _app: &mut AppState, _hit: &PointerHit) {} - fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {} - fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta: f32) {} -} diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index 63540ac6..b7e0ce17 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -10,23 +10,25 @@ use std::{ }; use crate::{ + backend::overlay::{OverlayData, OverlayState}, gui::{color_parse, CanvasBuilder, Control}, input::{KeyModifier, VirtualKey, KEYS_TO_MODS}, state::AppState, }; -use glam::{vec2, vec3}; +use glam::{vec2, vec3a}; use log::error; use once_cell::sync::Lazy; use regex::Regex; use rodio::{Decoder, OutputStream, Source}; use serde::{Deserialize, Serialize}; -use super::OverlayData; - const PIXELS_PER_UNIT: f32 = 80.; const BUTTON_PADDING: f32 = 4.; -pub fn create_keyboard(app: &AppState) -> OverlayData { +pub fn create_keyboard(app: &AppState) -> OverlayData +where + O: Default, +{ let size = vec2( LAYOUT.row_size * PIXELS_PER_UNIT, (LAYOUT.main_layout.len() as f32) * PIXELS_PER_UNIT, @@ -106,12 +108,15 @@ pub fn create_keyboard(app: &AppState) -> OverlayData { let canvas = canvas.build(); OverlayData { - name: Arc::from("Kbd"), - show_hide: true, - width: LAYOUT.row_size * 0.05, - size: (size.x as _, size.y as _), - grabbable: true, - spawn_point: vec3(0., -0.5, -1.), + state: OverlayState { + name: Arc::from("Kbd"), + show_hide: true, + width: LAYOUT.row_size * 0.05, + size: (size.x as _, size.y as _), + grabbable: true, + spawn_point: vec3a(0., -0.5, -1.), + ..Default::default() + }, backend: Box::new(canvas), ..Default::default() } diff --git a/src/overlays/mod.rs b/src/overlays/mod.rs index a49a8a9c..01e06979 100644 --- a/src/overlays/mod.rs +++ b/src/overlays/mod.rs @@ -1,145 +1,3 @@ -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; - -use glam::{Affine3A, Quat, Vec3}; -use vulkano::image::ImageViewAbstract; - -use crate::state::AppState; - -use self::interactions::{DummyInteractionHandler, InteractionHandler, PointerHit}; - -pub mod interactions; pub mod keyboard; +pub mod screen; pub mod watch; - -static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); - -pub enum RelativeTo { - None, - Head, - Hand(usize), -} - -pub trait OverlayBackend: OverlayRenderer + InteractionHandler {} - -pub struct OverlayData { - pub id: usize, - pub name: Arc, - pub width: f32, - pub size: (i32, i32), - pub want_visible: bool, - pub show_hide: bool, - pub grabbable: bool, - pub transform: Affine3A, - pub spawn_point: Vec3, - pub spawn_rotation: Quat, - pub relative_to: RelativeTo, - pub interaction_transform: Affine3A, - pub backend: Box, - pub primary_pointer: Option, -} -impl Default for OverlayData { - fn default() -> OverlayData { - OverlayData { - id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed), - name: Arc::from(""), - width: 1., - size: (0, 0), - want_visible: false, - show_hide: false, - grabbable: false, - relative_to: RelativeTo::None, - spawn_point: Vec3::NEG_Z, - spawn_rotation: Quat::IDENTITY, - transform: Affine3A::IDENTITY, - interaction_transform: Affine3A::IDENTITY, - backend: Box::new(SplitOverlayBackend::default()), - primary_pointer: None, - } - } -} - -impl OverlayData { - pub fn reset(&mut self, app: &mut AppState) { - todo!() - } - pub fn init(&mut self, app: &mut AppState) { - self.backend.init(app); - } - pub fn render(&mut self, app: &mut AppState) { - self.backend.render(app); - } - pub fn view(&mut self) -> Arc { - self.backend.view() - } -} - -pub trait OverlayRenderer { - fn init(&mut self, app: &mut AppState); - fn pause(&mut self, app: &mut AppState); - fn resume(&mut self, app: &mut AppState); - fn render(&mut self, app: &mut AppState); - fn view(&mut self) -> Arc; -} - -pub struct FallbackRenderer; - -impl OverlayRenderer for FallbackRenderer { - fn init(&mut self, _app: &mut AppState) {} - fn pause(&mut self, _app: &mut AppState) {} - fn resume(&mut self, _app: &mut AppState) {} - fn render(&mut self, _app: &mut AppState) {} - fn view(&mut self) -> Arc { - unimplemented!() - } -} -// Boilerplate and dummies - -pub struct SplitOverlayBackend { - pub renderer: Box, - pub interaction: Box, -} - -impl Default for SplitOverlayBackend { - fn default() -> SplitOverlayBackend { - SplitOverlayBackend { - renderer: Box::new(FallbackRenderer), - interaction: Box::new(DummyInteractionHandler), - } - } -} - -impl OverlayBackend for SplitOverlayBackend {} -impl OverlayRenderer for SplitOverlayBackend { - fn init(&mut self, app: &mut AppState) { - self.renderer.init(app); - } - fn pause(&mut self, app: &mut AppState) { - self.renderer.pause(app); - } - fn resume(&mut self, app: &mut AppState) { - self.renderer.resume(app); - } - fn render(&mut self, app: &mut AppState) { - self.renderer.render(app); - } - fn view(&mut self) -> Arc { - self.renderer.view() - } -} -impl InteractionHandler for SplitOverlayBackend { - fn on_left(&mut self, app: &mut AppState, hand: usize) { - self.interaction.on_left(app, hand); - } - fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) { - self.interaction.on_hover(app, hit); - } - fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) { - self.interaction.on_scroll(app, hit, delta); - } - fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { - self.interaction.on_pointer(app, hit, pressed); - } -} diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index 72566077..9de27c68 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -1,20 +1,59 @@ +use log::{info, warn}; +use std::{ + f32::consts::PI, + path::Path, + sync::{mpsc::Receiver, Arc}, + time::{Duration, Instant}, +}; +use vulkano::{ + command_buffer::CommandBufferUsage, + format::Format, + image::{view::ImageView, ImageAccess, ImageViewAbstract, ImmutableImage}, + Handle, VulkanObject, +}; +use wlx_capture::{ + frame::WlxFrame, + pipewire::{pipewire_select_screen, PipewireCapture}, + wayland::{Transform, WlxClient, WlxOutput}, + wlr::WlrDmabufCapture, + WlxCapture, +}; -pub struct ScreenInteractionData { +use glam::{vec2, Affine2, Quat, Vec2, Vec3}; + +use crate::{ + backend::{ + input::{InteractionHandler, PointerHit, PointerMode}, + overlay::{OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend}, + }, + input::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT}, + state::{AppSession, AppState}, +}; + +pub struct ScreenInteractionHandler { next_scroll: Instant, next_move: Instant, mouse_transform: Affine2, } -impl ScreenInteractionData { +impl ScreenInteractionHandler { fn new(pos: Vec2, size: Vec2, transform: Transform) -> ScreenInteractionHandler { let transform = match transform { - Transform::_90 | Transform::Flipped90 => - Affine2::from_cols(vec2(0., size.y), vec2(-size.x, 0.), vec2(pos.x + size.x, pos.y)), - Transform::_180 | Transform::Flipped180 => - Affine2::from_cols(vec2(-size.x, 0.), vec2(0., -size.y), vec2(pos.x + size.x, pos.y + size.y)), - Transform::_270 | Transform::Flipped270 => - Affine2::from_cols(vec2(0., -size.y), vec2(size.x, 0.), vec2(pos.x, pos.y + size.y)), - _ => - Affine2::from_cols(vec2(size.x, 0.), vec2(0., size.y), pos), + Transform::_90 | Transform::Flipped90 => Affine2::from_cols( + vec2(0., size.y), + vec2(-size.x, 0.), + vec2(pos.x + size.x, pos.y), + ), + Transform::_180 | Transform::Flipped180 => Affine2::from_cols( + vec2(-size.x, 0.), + vec2(0., -size.y), + vec2(pos.x + size.x, pos.y + size.y), + ), + Transform::_270 | Transform::Flipped270 => Affine2::from_cols( + vec2(0., -size.y), + vec2(size.x, 0.), + vec2(pos.x, pos.y + size.y), + ), + _ => Affine2::from_cols(vec2(size.x, 0.), vec2(0., size.y), pos), }; ScreenInteractionHandler { @@ -25,7 +64,222 @@ impl ScreenInteractionData { } } -struct ScreenInteractionHandler { +impl InteractionHandler for ScreenInteractionHandler { + fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) { + if self.next_move < Instant::now() { + let pos = self.mouse_transform.transform_point2(hit.uv); + app.input.mouse_move(pos); + } + } + fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) { + let pos = self.mouse_transform.transform_point2(hit.uv); + app.input.mouse_move(pos); + let btn = match hit.mode { + PointerMode::Right => MOUSE_RIGHT, + PointerMode::Middle => MOUSE_MIDDLE, + _ => MOUSE_LEFT, + }; + + if pressed { + self.next_move = Instant::now() + Duration::from_millis(300); + } + + app.input.send_button(btn, pressed); + } + fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta: f32) { + let millis = (1. - delta.abs()) * delta; + if let Some(next_scroll) = Instant::now().checked_add(Duration::from_millis(millis as _)) { + self.next_scroll = next_scroll; + } + app.input.wheel(if delta < 0. { -1 } else { 1 }) + } + fn on_left(&mut self, _app: &mut AppState, _hand: usize) {} } +pub struct ScreenRenderer { + capture: Box, + resolution: (i32, i32), + receiver: Option>, + view: Option>, +} + +impl ScreenRenderer { + pub fn new_wlr(output: &WlxOutput) -> Option { + let Some(client) = WlxClient::new() else { + return None; + }; + let Some(capture) = WlrDmabufCapture::new(client, output.id) else { + return None; + }; + Some(ScreenRenderer { + capture: Box::new(capture), + resolution: output.size, + receiver: None, + view: None, + }) + } + + pub fn new_pw( + output: &WlxOutput, + token: Option<&str>, + _fallback: bool, + ) -> Option { + let name = output.name.clone(); + let node_id = futures::executor::block_on(pipewire_select_screen(token)).ok()?; + + let capture = PipewireCapture::new(name, node_id, 60); + + Some(ScreenRenderer { + capture: Box::new(capture), + resolution: output.size, + receiver: None, + view: None, + }) + } + + pub fn new_xshm() -> ScreenRenderer { + todo!() + } +} + +impl OverlayRenderer for ScreenRenderer { + fn init(&mut self, app: &mut AppState) { + self.receiver = Some(self.capture.init()); + let mut cmd = app + .graphics + .create_command_buffer(CommandBufferUsage::OneTimeSubmit); + let default_image = cmd.texture2d(1, 1, Format::R8G8B8A8_UNORM, vec![255, 0, 255, 255]); + let _ = cmd.end_and_execute(); + } + fn render(&mut self, app: &mut AppState) { + let Some(receiver) = self.receiver.as_mut() else { + log::error!("No receiver"); + return; + }; + + for frame in receiver.try_iter() { + match frame { + WlxFrame::Dmabuf(frame) => { + if let Ok(new) = app.graphics.dmabuf_texture(frame) { + if let Some(current) = self.view.as_ref() { + if current.image().inner().image.handle().as_raw() + == new.inner().image.handle().as_raw() + { + return; + } + } + self.view = Some(ImageView::new_default(new).unwrap()); + } + } + WlxFrame::MemFd(frame) => { + todo!() + } + WlxFrame::MemPtr(frame) => { + todo!() + } + _ => {} + }; + } + self.capture.request_new_frame(); + } + fn pause(&mut self, _app: &mut AppState) { + self.capture.pause(); + } + fn resume(&mut self, _app: &mut AppState) { + self.capture.resume(); + } + fn view(&mut self) -> Option> { + self.view.as_ref().and_then(|v| Some(v.clone())) + } +} + +fn try_create_screen(wl: &WlxClient, idx: usize, session: &AppSession) -> Option> +where + O: Default, +{ + let output = &wl.outputs[idx]; + info!( + "{}: Res {}x{} Size {:?} Pos {:?}", + output.name, output.size.0, output.size.1, output.logical_size, output.logical_pos, + ); + + let size = (output.size.0, output.size.1); + let mut capture: Option = None; + + if session.capture_method == "auto" && wl.maybe_wlr_dmabuf_mgr.is_some() { + info!("{}: Using Wlr DMA-Buf", &output.name); + capture = ScreenRenderer::new_wlr(output); + } + + if capture.is_none() { + info!("{}: Using Pipewire capture", &output.name); + let file_name = format!("{}.token", &output.name); + let full_path = Path::new(&session.config_path).join(file_name); + let token = std::fs::read_to_string(full_path).ok(); + + capture = ScreenRenderer::new_pw( + output, + token.as_deref(), + session.capture_method == "pw_fallback", + ); + } + if let Some(capture) = capture { + let backend = Box::new(SplitOverlayBackend { + renderer: Box::new(capture), + interaction: Box::new(ScreenInteractionHandler::new( + vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32), + vec2(output.logical_size.0 as f32, output.logical_size.1 as f32), + output.transform, + )), + }); + + let axis = Vec3::new(0., 0., 1.); + + let angle = match output.transform { + Transform::_90 | Transform::Flipped90 => PI / 2., + Transform::_180 | Transform::Flipped180 => PI, + Transform::_270 | Transform::Flipped270 => -PI / 2., + _ => 0., + }; + + Some(OverlayData { + state: OverlayState { + name: output.name.clone(), + size, + want_visible: idx == 0, + show_hide: true, + grabbable: true, + spawn_rotation: Quat::from_axis_angle(axis, angle), + ..Default::default() + }, + backend, + ..Default::default() + }) + } else { + warn!("{}: Will not be used", &output.name); + None + } +} + +pub fn get_screens_wayland(session: &AppSession) -> Vec> +where + O: Default, +{ + let mut overlays = vec![]; + let wl = WlxClient::new().unwrap(); + + for idx in 0..wl.outputs.len() { + if let Some(overlay) = try_create_screen(&wl, idx, &session) { + overlays.push(overlay); + } + } + overlays +} + +pub fn get_screens_x11() -> Vec> +where + O: Default, +{ + todo!() +} diff --git a/src/overlays/watch.rs b/src/overlays/watch.rs index 98922e02..7f66ac94 100644 --- a/src/overlays/watch.rs +++ b/src/overlays/watch.rs @@ -1,19 +1,20 @@ use std::{sync::Arc, time::Instant}; use chrono::Local; -use glam::{Quat, Vec3}; use crate::{ + backend::{ + common::{OverlaySelector, TaskType}, + overlay::{OverlayData, OverlayState, RelativeTo}, + }, gui::{color_parse, CanvasBuilder}, state::AppState, }; -use super::{OverlayData, RelativeTo}; - -pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(0., 0., 0.15); -pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963); - -pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc)>) -> OverlayData { +pub fn create_watch(state: &AppState, screens: &[OverlayData]) -> OverlayData +where + O: Default, +{ let mut canvas = CanvasBuilder::new(400, 200, state.graphics.clone(), state.format, ()); let empty_str: Arc = Arc::from(""); @@ -93,22 +94,19 @@ pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc)>) -> Overla .as_millis() < 2000 { - app.tasks.push_back(Box::new(|_app, o| { - for overlay in o { - if &*overlay.name == "Kbd" { - overlay.want_visible = !overlay.want_visible; - return; - } - } - })); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Name("Kbd".into()), + Box::new(|_app, o| { + o.want_visible = !o.want_visible; + }), + )); } else { - app.tasks.push_back(Box::new(|app, o| { - for overlay in o { - if &*overlay.name == "Kbd" { - overlay.reset(app); - } - } - })); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Name("Kbd".into()), + Box::new(|app, o| { + o.reset(app); + }), + )); } } }); @@ -116,11 +114,17 @@ pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc)>) -> Overla canvas.bg_color = color_parse("#405060"); - for (scr_idx, scr_name) in screens.into_iter() { - let button = canvas.button(button_x + 2., 162., button_width - 4., 36., scr_name); + for screen in screens.into_iter() { + let button = canvas.button( + button_x + 2., + 162., + button_width - 4., + 36., + screen.state.name.clone(), + ); button.state = Some(WatchButtonState { pressed_at: Instant::now(), - scr_idx, + scr_idx: screen.state.id, }); button.on_press = Some(|control, _data, _app| { @@ -136,13 +140,19 @@ pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc)>) -> Overla .as_millis() < 2000 { - app.tasks.push_back(Box::new(move |_app, o| { - o[scr_idx].want_visible = !o[scr_idx].want_visible; - })); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Id(scr_idx), + Box::new(|_app, o| { + o.want_visible = !o.want_visible; + }), + )); } else { - app.tasks.push_back(Box::new(move |app, o| { - o[scr_idx].reset(app); - })); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Id(scr_idx), + Box::new(|app, o| { + o.reset(app); + }), + )); } } }); @@ -152,14 +162,17 @@ pub fn create_watch(state: &AppState, screens: Vec<(usize, Arc)>) -> Overla let relative_to = RelativeTo::Hand(state.session.watch_hand); OverlayData { - name: "Watch".into(), - size: (400, 200), - width: 0.065, + state: OverlayState { + name: "Watch".into(), + size: (400, 200), + width: 0.065, + want_visible: true, + spawn_point: state.session.watch_pos.into(), + spawn_rotation: state.session.watch_rot, + relative_to, + ..Default::default() + }, backend: Box::new(canvas.build()), - want_visible: true, - relative_to, - spawn_point: state.session.watch_pos, - spawn_rotation: state.session.watch_rot, ..Default::default() } } diff --git a/src/ovr.rs b/src/ovr.rs deleted file mode 100644 index 9831f5d5..00000000 --- a/src/ovr.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{path::Path, sync::Arc}; - -use vulkano::{ - image::{ - sys::{Image, RawImage}, - ImageViewAbstract, - }, - Handle, VulkanObject, -}; - -use crate::graphics::WlxGraphics; - -pub struct OpenVrState { - pub context: ovr_overlay::Context, -} - - -pub struct OvrTextureData { - image_handle: u64, - device: u64, - physical: u64, - instance: u64, - queue: u64, - queue_family_index: u32, - width: u32, - height: u32, - format: u32, - sample_count: u32, -} - -impl OvrTextureData { - pub fn new(graphics: Arc, view: Arc) -> OvrTextureData { - let image = view.image(); - - let device = graphics.device.handle().as_raw(); - let physical = graphics.device.physical_device().handle().as_raw(); - let instance = graphics.instance.handle().as_raw(); - let queue = graphics.queue.handle().as_raw(); - let queue_family_index = graphics.queue.queue_family_index(); - - let (width, height) = { - let dim = image.dimensions(); - (dim.width() as u32, dim.height() as u32) - }; - - let sample_count = image.samples() as u32; - let format = image.format() as u32; - - let image_handle = image.inner().image.handle().as_raw(); - - OvrTextureData { - image_handle, - device, - physical, - instance, - queue, - queue_family_index, - width, - height, - format, - sample_count, - } - } -} diff --git a/src/res/actions_binding_knuckles.json b/src/res/actions_binding_knuckles.json index 1f19b086..23d41c89 100644 --- a/src/res/actions_binding_knuckles.json +++ b/src/res/actions_binding_knuckles.json @@ -1,6 +1,6 @@ { "action_manifest_version" : 0, - "app_key" : "galister.wlxoverlay", + "app_key" : "galister.wlxoverlay-s", "bindings" : { "/actions/default" : { "haptics" : [ diff --git a/src/res/actions_binding_oculus.json b/src/res/actions_binding_oculus.json index 37c82684..5f252022 100644 --- a/src/res/actions_binding_oculus.json +++ b/src/res/actions_binding_oculus.json @@ -1,6 +1,6 @@ { "action_manifest_version" : 0, - "app_key" : "galister.wlxoverlay", + "app_key" : "galister.wlxoverlay-s", "bindings" : { "/actions/default" : { "haptics" : [ diff --git a/src/res/actions_binding_vive.json b/src/res/actions_binding_vive.json index a3db7d80..b1013c14 100644 --- a/src/res/actions_binding_vive.json +++ b/src/res/actions_binding_vive.json @@ -1,6 +1,6 @@ { "action_manifest_version" : 0, - "app_key" : "galister.wlxoverlay", + "app_key" : "galister.wlxoverlay-s", "bindings" : { "/actions/default": { "haptics" : [ diff --git a/src/state.rs b/src/state.rs index 6eeb1b73..c2df4c6b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,27 +1,50 @@ -use std::{collections::VecDeque, env::VarError, path::Path, sync::Arc}; +use std::{env::VarError, path::Path, sync::Arc}; use glam::{Quat, Vec3}; use log::warn; +use vulkano::{ + device::{physical::PhysicalDevice, DeviceExtensions}, + format::Format, + instance::InstanceExtensions, +}; use crate::{ - graphics::WlxGraphics, gui::font::FontCache, input::InputProvider, overlays::OverlayData, + backend::common::TaskContainer, graphics::WlxGraphics, gui::font::FontCache, + input::InputProvider, }; pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(0., 0., 0.15); pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963); -pub type Task = Box; - pub struct AppState { pub fc: FontCache, //pub input: InputState, pub session: AppSession, - pub tasks: VecDeque, + pub tasks: TaskContainer, pub graphics: Arc, pub format: vulkano::format::Format, pub input: Box, } +impl AppState { + pub fn new( + vk_instance_extensions: InstanceExtensions, + vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions, + ) -> Self { + let (graphics, _event_loop) = + WlxGraphics::new(vk_instance_extensions, vk_device_extensions_fn); + + AppState { + fc: FontCache::new(), + session: AppSession::load(), + tasks: TaskContainer::new(), + graphics: graphics.clone(), + format: Format::R8G8B8A8_UNORM, + input: crate::input::initialize_input(), + } + } +} + pub struct AppSession { pub config_path: String, @@ -50,7 +73,7 @@ pub struct AppSession { } impl AppSession { - pub fn load() -> AppSession { + pub fn load() -> Self { let config_path = std::env::var("XDG_CONFIG_HOME") .or_else(|_| std::env::var("HOME").map(|home| format!("{}/.config", home))) .or_else(|_| {