mirror of
https://github.com/Supreeeme/xrizer.git
synced 2026-08-04 00:47:59 -04:00
Initial commit (but not really)
This commit is contained in:
commit
6d13e87d24
46 changed files with 63131 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/runtime
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "openxr-sdk"]
|
||||
path = openxr-sdk
|
||||
url = https://github.com/khronosgroup/openxr-sdk
|
||||
847
Cargo.lock
generated
Normal file
847
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.38.0+1.3.281"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fakexr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"crossbeam-utils",
|
||||
"openxr-sys",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openxr"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/ralith/openxrs?rev=d8ea555#d8ea5553d52c4bbaefe2537b8d70b8752f73694d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"ndk-context",
|
||||
"openxr-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openxr-sys"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/ralith/openxrs?rev=d8ea555#d8ea5553d52c4bbaefe2537b8d70b8752f73694d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "seq-macro"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.209"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-cdylib"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8f41b1f729f5ff5177beab62e5a9251e318df8386e260ab3c944cff502ee78d"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrizer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"ash",
|
||||
"bindgen",
|
||||
"cmake",
|
||||
"enum_dispatch",
|
||||
"env_logger",
|
||||
"fakexr",
|
||||
"glam",
|
||||
"libloading",
|
||||
"log",
|
||||
"macros",
|
||||
"num_enum",
|
||||
"openxr",
|
||||
"paste",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"seq-macro",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slotmap",
|
||||
"syn",
|
||||
"test-cdylib",
|
||||
"time",
|
||||
"xrizer",
|
||||
]
|
||||
57
Cargo.toml
Normal file
57
Cargo.toml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
[package]
|
||||
name = "xrizer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[workspace]
|
||||
|
||||
#[profile.release]
|
||||
#panic = "abort"
|
||||
#
|
||||
#[profile.dev]
|
||||
#panic = "abort"
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
ash = "0.38.0"
|
||||
openxr = { git = "https://github.com/ralith/openxrs", rev = "d8ea555", version = "0.19.0", default-features = false, features = ["linked"] }
|
||||
openxr-sys = { git = "https://github.com/ralith/openxrs", rev = "d8ea555", version = "0.11.0" }
|
||||
|
||||
[features]
|
||||
unittest = []
|
||||
|
||||
[dependencies]
|
||||
arc-swap = "1.7.1"
|
||||
ash = { workspace = true }
|
||||
enum_dispatch = "0.3.13"
|
||||
env_logger = "0.11.5"
|
||||
glam = "0.28.0"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.3"
|
||||
openxr = { workspace = true }
|
||||
paste = "1.0.15"
|
||||
seq-macro = "0.3.5"
|
||||
macros = { path = "macros" }
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
slotmap = "1.0.7"
|
||||
time = { version = "0.3.36", features = ["formatting", "local-offset", "macros"] }
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69.4"
|
||||
cmake = "0.1.51"
|
||||
prettyplease = "0.2.20"
|
||||
proc-macro2 = "1.0.86"
|
||||
quote = "1.0.36"
|
||||
regex = "1.10.5"
|
||||
syn = { version = "2.0.71", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
libloading = "0.8.5"
|
||||
test-cdylib = "1.1.0"
|
||||
xrizer = { path = ".", features = ["unittest"] }
|
||||
fakexr = { path = "fakexr" }
|
||||
32
fakexr/Cargo.lock
generated
Normal file
32
fakexr/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "fakexr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"openxr-sys",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "openxr-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f10e7e38c47f2175fc39363713b656db899fa0b4a14341029702cbdfa6f44d05"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
10
fakexr/Cargo.toml
Normal file
10
fakexr/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "fakexr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
openxr-sys = { workspace = true }
|
||||
paste = "1.0.15"
|
||||
ash = { workspace = true }
|
||||
crossbeam-utils = "0.8.20"
|
||||
719
fakexr/src/lib.rs
Normal file
719
fakexr/src/lib.rs
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
pub mod vulkan;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use openxr_sys as xr;
|
||||
use paste::paste;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
mpsc, Arc, OnceLock, Weak,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ActionState {
|
||||
Bool(bool),
|
||||
Pose,
|
||||
Float(f32),
|
||||
}
|
||||
pub fn set_action_state(action: xr::Action, state: ActionState) {
|
||||
let action = Action::from_xr(action);
|
||||
let s = action.state.load();
|
||||
assert_eq!(std::mem::discriminant(&state), std::mem::discriminant(&s));
|
||||
action.pending_state.store(Some(state));
|
||||
}
|
||||
|
||||
macro_rules! fn_unimplemented_impl {
|
||||
($($param:ident),+) => {
|
||||
fn_unimplemented_impl!($($param),+ -> []);
|
||||
};
|
||||
($param:ident $(,$rest:ident)* -> [$($params:ident),*]) => {
|
||||
paste! {
|
||||
#[allow(dead_code)]
|
||||
trait [<FnUnimplemented $param>]<$($params,)* $param> {
|
||||
extern "system" fn unimplemented($(_: $params,)* _: $param) -> xr::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($params,)* $param> [<FnUnimplemented $param>]<$($params,)* $param> for unsafe extern "system" fn($($params,)* $param) -> xr::Result {}
|
||||
}
|
||||
|
||||
fn_unimplemented_impl!($($rest),* -> [$($params,)* $param]);
|
||||
};
|
||||
(-> [$($params:ident),+]) => {}
|
||||
}
|
||||
|
||||
fn_unimplemented_impl!(A, B, C, D, E, F);
|
||||
|
||||
pub extern "system" fn get_instance_proc_addr(
|
||||
instance: xr::Instance,
|
||||
name: *const c_char,
|
||||
function: *mut Option<xr::pfn::VoidFunction>,
|
||||
) -> xr::Result {
|
||||
let name = unsafe { CStr::from_ptr(name) };
|
||||
|
||||
/// Generates match arms for supported functions.
|
||||
/// Functions in parenthesis are returned as unimplemented functions - they should be
|
||||
/// implemented if a test needs it.
|
||||
macro_rules! get_fn {
|
||||
([$($func:tt),+] $pat:pat => $expr:expr) => {
|
||||
get_fn!(@arm [$($func),+] -> [] {$pat => $expr})
|
||||
};
|
||||
(@arm [$name:ident $(,$rest:tt)*] -> [$($arms:tt),*] {$pat:pat => $expr:expr}) => {
|
||||
get_fn!(
|
||||
@arm
|
||||
[$($rest),*] ->
|
||||
[
|
||||
$($arms,)*
|
||||
[
|
||||
x if x == const {
|
||||
CStr::from_bytes_with_nul_unchecked(concat!("xr", stringify!($name), "\0").as_bytes())
|
||||
} => Some(std::mem::transmute( paste! { [<$name:snake>] as xr::pfn::$name }))
|
||||
]
|
||||
]
|
||||
{$pat => $expr}
|
||||
)
|
||||
};
|
||||
(@arm [($name:ident) $(,$rest:tt)*] -> [$($arms:tt),*] {$pat:pat => $expr:expr}) => {
|
||||
get_fn!(
|
||||
@arm
|
||||
[$($rest),*] ->
|
||||
[
|
||||
$($arms,)*
|
||||
[
|
||||
x if x == const {
|
||||
CStr::from_bytes_with_nul_unchecked(concat!("xr", stringify!($name), "\0").as_bytes())
|
||||
} => Some(std::mem::transmute(xr::pfn::$name::unimplemented as xr::pfn::$name))
|
||||
]
|
||||
]
|
||||
{$pat => $expr}
|
||||
)
|
||||
};
|
||||
(@arm []-> [$([$($arms:tt)*]),+] {$pat:pat => $expr:expr}) => {
|
||||
match name {
|
||||
$($($arms)*,)+
|
||||
$pat => $expr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if instance == xr::Instance::NULL {
|
||||
unsafe {
|
||||
*function = get_fn!([CreateInstance, (EnumerateInstanceExtensionProperties), (EnumerateApiLayerProperties)]
|
||||
other => {
|
||||
println!("unknown func without instance: {other:?}");
|
||||
return xr::Result::ERROR_HANDLE_INVALID;
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let instance = instance.into_raw() as *const Instance;
|
||||
if unsafe { (*instance).debug } != Instance::DEBUG_VAL {
|
||||
return xr::Result::ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
use vulkan::xr::*;
|
||||
|
||||
unsafe {
|
||||
*function = get_fn![[
|
||||
GetInstanceProcAddr,
|
||||
CreateInstance,
|
||||
DestroyInstance,
|
||||
(EnumerateInstanceExtensionProperties),
|
||||
(EnumerateApiLayerProperties),
|
||||
GetVulkanInstanceExtensionsKHR,
|
||||
GetVulkanDeviceExtensionsKHR,
|
||||
GetVulkanGraphicsDeviceKHR,
|
||||
GetVulkanGraphicsRequirementsKHR,
|
||||
GetSystem,
|
||||
CreateSession,
|
||||
DestroySession,
|
||||
BeginSession,
|
||||
EndSession,
|
||||
CreateReferenceSpace,
|
||||
PollEvent,
|
||||
DestroySpace,
|
||||
(LocateViews),
|
||||
RequestExitSession,
|
||||
(ResultToString),
|
||||
(StructureTypeToString),
|
||||
(GetInstanceProperties),
|
||||
(GetSystemProperties),
|
||||
CreateSwapchain,
|
||||
DestroySwapchain,
|
||||
EnumerateSwapchainImages,
|
||||
AcquireSwapchainImage,
|
||||
WaitSwapchainImage,
|
||||
ReleaseSwapchainImage,
|
||||
(EnumerateSwapchainFormats),
|
||||
(EnumerateReferenceSpaces),
|
||||
CreateActionSpace,
|
||||
LocateSpace,
|
||||
(EnumerateViewConfigurations),
|
||||
(EnumerateEnvironmentBlendModes),
|
||||
(GetViewConfigurationProperties),
|
||||
(EnumerateViewConfigurationViews),
|
||||
BeginFrame,
|
||||
EndFrame,
|
||||
WaitFrame,
|
||||
(ApplyHapticFeedback),
|
||||
(StopHapticFeedback),
|
||||
(PollEvent),
|
||||
StringToPath,
|
||||
(PathToString),
|
||||
(GetReferenceSpaceBoundsRect),
|
||||
GetActionStateBoolean,
|
||||
GetActionStateFloat,
|
||||
(GetActionStateVector2f),
|
||||
(GetActionStatePose),
|
||||
CreateActionSet,
|
||||
DestroyActionSet,
|
||||
CreateAction,
|
||||
DestroyAction,
|
||||
SuggestInteractionProfileBindings,
|
||||
AttachSessionActionSets,
|
||||
(GetCurrentInteractionProfile),
|
||||
SyncActions,
|
||||
(EnumerateBoundSourcesForAction),
|
||||
(GetInputSourceLocalizedName)
|
||||
]
|
||||
|
||||
other => {
|
||||
println!("unknown func: {other:?}");
|
||||
return xr::Result::ERROR_FUNCTION_UNSUPPORTED;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
trait Handle: Sized {
|
||||
type XrType;
|
||||
const DEBUG_VAL: u64;
|
||||
const TO_RAW: fn(Self::XrType) -> u64;
|
||||
|
||||
fn get_debug_val(ptr: *const Self) -> u64;
|
||||
|
||||
fn validate(ptr: *const Self) -> bool {
|
||||
Self::get_debug_val(ptr) == Self::DEBUG_VAL
|
||||
}
|
||||
fn from_xr(xr: Self::XrType) -> Arc<Self> {
|
||||
let ptr = Self::TO_RAW(xr) as *const Self;
|
||||
assert!(Self::validate(ptr));
|
||||
unsafe {
|
||||
Arc::increment_strong_count(ptr);
|
||||
Arc::from_raw(ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_handle {
|
||||
($ty:ty, $xr:ty, $debug:literal) => {
|
||||
impl Handle for $ty {
|
||||
type XrType = $xr;
|
||||
const DEBUG_VAL: u64 = $debug;
|
||||
const TO_RAW: fn(Self::XrType) -> u64 = <$xr>::into_raw;
|
||||
|
||||
fn get_debug_val(ptr: *const Self) -> u64 {
|
||||
unsafe { *(ptr as *const u64) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Instance {
|
||||
debug: u64,
|
||||
event_receiver: mpsc::Receiver<xr::EventDataBuffer>,
|
||||
event_sender: mpsc::Sender<xr::EventDataBuffer>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Session {
|
||||
debug: u64,
|
||||
instance: Weak<Instance>,
|
||||
event_sender: mpsc::Sender<xr::EventDataBuffer>,
|
||||
vk_device: AtomicU64,
|
||||
attached_sets: OnceLock<Box<[xr::ActionSet]>>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ActionSet {
|
||||
debug: u64,
|
||||
pending_actions: AtomicCell<Option<mpsc::Receiver<Arc<Action>>>>,
|
||||
action_sender: mpsc::Sender<Arc<Action>>,
|
||||
actions: OnceLock<Vec<Arc<Action>>>,
|
||||
active: AtomicBool,
|
||||
}
|
||||
impl ActionSet {
|
||||
fn make_immutable(&self) {
|
||||
let actions = self.pending_actions.take().unwrap().try_iter().collect();
|
||||
self.actions
|
||||
.set(actions)
|
||||
.unwrap_or_else(|_| panic!("Action set already immutable"));
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Action {
|
||||
debug: u64,
|
||||
state: AtomicCell<ActionState>,
|
||||
pending_state: AtomicCell<Option<ActionState>>,
|
||||
}
|
||||
|
||||
impl_handle!(Instance, xr::Instance, 342);
|
||||
impl_handle!(Session, xr::Session, 442);
|
||||
impl_handle!(ActionSet, xr::ActionSet, 542);
|
||||
impl_handle!(Action, xr::Action, 662);
|
||||
|
||||
macro_rules! destroy_handle {
|
||||
($ty:ty, $handle:expr) => {{
|
||||
unsafe { Arc::from_raw($handle.into_raw() as *const $ty) };
|
||||
|
||||
xr::Result::SUCCESS
|
||||
}};
|
||||
}
|
||||
|
||||
extern "system" fn create_instance(
|
||||
_info: *const xr::InstanceCreateInfo,
|
||||
instance: *mut xr::Instance,
|
||||
) -> xr::Result {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let inst = Arc::new(Instance {
|
||||
debug: Instance::DEBUG_VAL,
|
||||
event_receiver: rx,
|
||||
event_sender: tx,
|
||||
});
|
||||
unsafe {
|
||||
*instance = xr::Instance::from_raw(Arc::into_raw(inst) as u64);
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_instance(instance: xr::Instance) -> xr::Result {
|
||||
destroy_handle!(Instance, instance)
|
||||
}
|
||||
|
||||
extern "system" fn create_session(
|
||||
instance: xr::Instance,
|
||||
create_info: *const xr::SessionCreateInfo,
|
||||
session: *mut xr::Session,
|
||||
) -> xr::Result {
|
||||
let instance = Instance::from_xr(instance);
|
||||
let info = unsafe { create_info.as_ref().unwrap() };
|
||||
let vk = unsafe {
|
||||
(info.next as *const xr::GraphicsBindingVulkanKHR)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(vk.ty, xr::GraphicsBindingVulkanKHR::TYPE);
|
||||
let sess = Arc::new(Session {
|
||||
debug: Session::DEBUG_VAL,
|
||||
instance: Arc::downgrade(&instance),
|
||||
event_sender: instance.event_sender.clone(),
|
||||
vk_device: (vk.device as u64).into(),
|
||||
attached_sets: OnceLock::new(),
|
||||
});
|
||||
|
||||
let tx = sess.event_sender.clone();
|
||||
unsafe {
|
||||
*session = xr::Session::from_raw(Arc::into_raw(sess) as u64);
|
||||
}
|
||||
|
||||
send_event(
|
||||
&tx,
|
||||
xr::EventDataSessionStateChanged {
|
||||
ty: xr::EventDataSessionStateChanged::TYPE,
|
||||
next: std::ptr::null(),
|
||||
session: unsafe { *session },
|
||||
state: xr::SessionState::READY,
|
||||
time: xr::Time::from_nanos(0),
|
||||
},
|
||||
);
|
||||
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_session(session: xr::Session) -> xr::Result {
|
||||
let session = unsafe { Arc::from_raw(session.into_raw() as *const Session) };
|
||||
// Our Vulkan device needs to still exist when we destroy the session - a real runtime will use
|
||||
// it!
|
||||
let device = session.vk_device.load(Ordering::Relaxed);
|
||||
if !vulkan::Device::validate(device) {
|
||||
panic!("Vulkan device invalid ({device})")
|
||||
}
|
||||
assert_eq!(Arc::strong_count(&session), 1);
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn create_action_set(
|
||||
_: xr::Instance,
|
||||
_: *const xr::ActionSetCreateInfo,
|
||||
set: *mut xr::ActionSet,
|
||||
) -> xr::Result {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let s = ActionSet {
|
||||
debug: ActionSet::DEBUG_VAL,
|
||||
actions: OnceLock::new(),
|
||||
action_sender: tx,
|
||||
pending_actions: Some(rx).into(),
|
||||
active: false.into(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*set = xr::ActionSet::from_raw(Arc::into_raw(s.into()) as u64);
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_action_set(set: xr::ActionSet) -> xr::Result {
|
||||
destroy_handle!(ActionSet, set)
|
||||
}
|
||||
|
||||
extern "system" fn create_action(
|
||||
set: xr::ActionSet,
|
||||
info: *const xr::ActionCreateInfo,
|
||||
action: *mut xr::Action,
|
||||
) -> xr::Result {
|
||||
let a = Arc::new(Action {
|
||||
debug: Action::DEBUG_VAL,
|
||||
state: match unsafe { (*info).action_type } {
|
||||
xr::ActionType::BOOLEAN_INPUT => ActionState::Bool(false),
|
||||
xr::ActionType::POSE_INPUT => ActionState::Pose,
|
||||
xr::ActionType::FLOAT_INPUT => ActionState::Float(0.0),
|
||||
other => unimplemented!("unhandled action type: {other:?}"),
|
||||
}
|
||||
.into(),
|
||||
pending_state: None.into(),
|
||||
});
|
||||
let set = ActionSet::from_xr(set);
|
||||
set.action_sender.send(a.clone()).unwrap();
|
||||
|
||||
unsafe {
|
||||
*action = xr::Action::from_raw(Arc::into_raw(a) as u64);
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_action(action: xr::Action) -> xr::Result {
|
||||
destroy_handle!(Action, action)
|
||||
}
|
||||
|
||||
extern "system" fn create_action_space(
|
||||
_: xr::Session,
|
||||
_: *const xr::ActionSpaceCreateInfo,
|
||||
_: *mut xr::Space,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn get_system(
|
||||
_: xr::Instance,
|
||||
_: *const xr::SystemGetInfo,
|
||||
system_id: *mut xr::SystemId,
|
||||
) -> xr::Result {
|
||||
unsafe { *system_id = xr::SystemId::from_raw(1) };
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
fn send_event<T>(tx: &mpsc::Sender<xr::EventDataBuffer>, event: T) {
|
||||
const {
|
||||
assert!(std::mem::size_of::<T>() <= std::mem::size_of::<xr::EventDataBuffer>());
|
||||
}
|
||||
|
||||
let mut raw_event = xr::EventDataBuffer {
|
||||
ty: xr::EventDataBuffer::TYPE,
|
||||
next: std::ptr::null(),
|
||||
varying: [0; 4000],
|
||||
};
|
||||
unsafe {
|
||||
(&mut raw_event as *mut _ as *mut T).copy_from_nonoverlapping(&event, 1);
|
||||
}
|
||||
tx.send(raw_event).unwrap();
|
||||
}
|
||||
|
||||
extern "system" fn begin_session(_: xr::Session, _: *const xr::SessionBeginInfo) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_space(_: xr::Space) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn create_reference_space(
|
||||
_: xr::Session,
|
||||
create_info: *const xr::ReferenceSpaceCreateInfo,
|
||||
space: *mut xr::Space,
|
||||
) -> xr::Result {
|
||||
let info = unsafe { create_info.as_ref().unwrap() };
|
||||
let val = match info.reference_space_type {
|
||||
xr::ReferenceSpaceType::VIEW => 1,
|
||||
xr::ReferenceSpaceType::LOCAL => 2,
|
||||
xr::ReferenceSpaceType::STAGE => 3,
|
||||
other => panic!("unimplemented reference space type: {other:?}"),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*space = xr::Space::from_raw(val);
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn poll_event(
|
||||
instance: xr::Instance,
|
||||
buffer: *mut xr::EventDataBuffer,
|
||||
) -> xr::Result {
|
||||
let instance = Instance::from_xr(instance);
|
||||
match instance.event_receiver.try_recv() {
|
||||
Ok(event) => {
|
||||
unsafe { *buffer = event };
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
Err(mpsc::TryRecvError::Empty) => xr::Result::EVENT_UNAVAILABLE,
|
||||
Err(mpsc::TryRecvError::Disconnected) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
extern "system" fn string_to_path(
|
||||
_: xr::Instance,
|
||||
_: *const c_char,
|
||||
_: *mut xr::Path,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn request_exit_session(session: xr::Session) -> xr::Result {
|
||||
let sess = Session::from_xr(session);
|
||||
send_event(
|
||||
&sess.event_sender,
|
||||
xr::EventDataSessionStateChanged {
|
||||
ty: xr::EventDataSessionStateChanged::TYPE,
|
||||
next: std::ptr::null(),
|
||||
session,
|
||||
state: xr::SessionState::STOPPING,
|
||||
time: xr::Time::from_nanos(0),
|
||||
},
|
||||
);
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn end_session(session: xr::Session) -> xr::Result {
|
||||
let sess = Session::from_xr(session);
|
||||
send_event(
|
||||
&sess.event_sender,
|
||||
xr::EventDataSessionStateChanged {
|
||||
ty: xr::EventDataSessionStateChanged::TYPE,
|
||||
next: std::ptr::null(),
|
||||
session,
|
||||
state: xr::SessionState::EXITING,
|
||||
time: xr::Time::from_nanos(0),
|
||||
},
|
||||
);
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn suggest_interaction_profile_bindings(
|
||||
_: xr::Instance,
|
||||
_: *const xr::InteractionProfileSuggestedBinding,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn attach_session_action_sets(
|
||||
session: xr::Session,
|
||||
info: *const xr::SessionActionSetsAttachInfo,
|
||||
) -> xr::Result {
|
||||
let sesh = Session::from_xr(session);
|
||||
let sets =
|
||||
unsafe { std::slice::from_raw_parts((*info).action_sets, (*info).count_action_sets as _) };
|
||||
if sesh.attached_sets.set(sets.into()).is_ok() {
|
||||
// make action sets immutable
|
||||
for set in sesh.attached_sets.get().unwrap() {
|
||||
let set = ActionSet::from_xr(*set);
|
||||
set.make_immutable();
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
} else {
|
||||
xr::Result::ERROR_ACTIONSETS_ALREADY_ATTACHED
|
||||
}
|
||||
}
|
||||
|
||||
extern "system" fn sync_actions(
|
||||
session: xr::Session,
|
||||
info: *const xr::ActionsSyncInfo,
|
||||
) -> xr::Result {
|
||||
let session = Session::from_xr(session);
|
||||
let Some(attached) = session.attached_sets.get() else {
|
||||
return xr::Result::ERROR_ACTIONSET_NOT_ATTACHED;
|
||||
};
|
||||
for set in attached {
|
||||
let set = ActionSet::from_xr(*set);
|
||||
set.active.store(false, Ordering::Relaxed);
|
||||
}
|
||||
let sets = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*info).active_action_sets,
|
||||
(*info).count_active_action_sets as _,
|
||||
)
|
||||
};
|
||||
for set in sets {
|
||||
if !attached.contains(&set.action_set) {
|
||||
return xr::Result::ERROR_ACTIONSET_NOT_ATTACHED;
|
||||
}
|
||||
let set = ActionSet::from_xr(set.action_set);
|
||||
let Some(actions) = set.actions.get() else {
|
||||
return xr::Result::ERROR_ACTIONSET_NOT_ATTACHED;
|
||||
};
|
||||
set.active.store(true, Ordering::Relaxed);
|
||||
|
||||
for action in actions {
|
||||
if let Some(state) = action.pending_state.take() {
|
||||
action.state.store(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
fn get_action_if_attached(
|
||||
session: &Session,
|
||||
info: *const xr::ActionStateGetInfo,
|
||||
) -> Option<(Arc<ActionSet>, Arc<Action>)> {
|
||||
let sets = session.attached_sets.get()?;
|
||||
let action = Action::from_xr(unsafe { (*info).action });
|
||||
sets.into_iter().find_map(|set| {
|
||||
let set = ActionSet::from_xr(*set);
|
||||
for a in set.actions.get().unwrap() {
|
||||
if Arc::as_ptr(a) == Arc::as_ptr(&action) {
|
||||
return Some((set, action.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
extern "system" fn get_action_state_boolean(
|
||||
session: xr::Session,
|
||||
info: *const xr::ActionStateGetInfo,
|
||||
state: *mut xr::ActionStateBoolean,
|
||||
) -> xr::Result {
|
||||
let session = Session::from_xr(session);
|
||||
let Some((set, action)) = get_action_if_attached(&session, info) else {
|
||||
return xr::Result::ERROR_ACTIONSET_NOT_ATTACHED;
|
||||
};
|
||||
|
||||
let ActionState::Bool(b) = action.state.load() else {
|
||||
return xr::Result::ERROR_ACTION_TYPE_MISMATCH;
|
||||
};
|
||||
let state = unsafe { state.as_mut().unwrap() };
|
||||
if set.active.load(Ordering::Relaxed) {
|
||||
state.is_active = true.into();
|
||||
state.current_state = b.into();
|
||||
} else {
|
||||
state.is_active = false.into();
|
||||
state.current_state = false.into();
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn get_action_state_float(
|
||||
session: xr::Session,
|
||||
info: *const xr::ActionStateGetInfo,
|
||||
state: *mut xr::ActionStateFloat,
|
||||
) -> xr::Result {
|
||||
let session = Session::from_xr(session);
|
||||
let Some((set, action)) = get_action_if_attached(&session, info) else {
|
||||
return xr::Result::ERROR_ACTIONSET_NOT_ATTACHED;
|
||||
};
|
||||
|
||||
let ActionState::Float(f) = action.state.load() else {
|
||||
return xr::Result::ERROR_ACTION_TYPE_MISMATCH;
|
||||
};
|
||||
let state = unsafe { state.as_mut().unwrap() };
|
||||
if set.active.load(Ordering::Relaxed) {
|
||||
state.is_active = true.into();
|
||||
state.current_state = f;
|
||||
} else {
|
||||
state.is_active = false.into();
|
||||
state.current_state = 0.0;
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn locate_space(
|
||||
_space: xr::Space,
|
||||
_base_space: xr::Space,
|
||||
_time: xr::Time,
|
||||
_location: *mut xr::SpaceLocation,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn create_swapchain(
|
||||
_session: xr::Session,
|
||||
_info: *const xr::SwapchainCreateInfo,
|
||||
_swapchain: *mut xr::Swapchain,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_swapchain(_: xr::Swapchain) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn enumerate_swapchain_images(
|
||||
_swapchain: xr::Swapchain,
|
||||
_: u32,
|
||||
output: *mut u32,
|
||||
_: *mut xr::SwapchainImageBaseHeader,
|
||||
) -> xr::Result {
|
||||
if let Some(output) = unsafe { output.as_mut() } {
|
||||
*output = 0;
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn acquire_swapchain_image(
|
||||
_swapchain: xr::Swapchain,
|
||||
_info: *const xr::SwapchainImageAcquireInfo,
|
||||
_index: *mut u32,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn wait_swapchain_image(
|
||||
_swapchain: xr::Swapchain,
|
||||
_info: *const xr::SwapchainImageWaitInfo,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn release_swapchain_image(
|
||||
_swapchain: xr::Swapchain,
|
||||
_info: *const xr::SwapchainImageReleaseInfo,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn wait_frame(
|
||||
_session: xr::Session,
|
||||
_info: *const xr::FrameWaitInfo,
|
||||
state: *mut xr::FrameState,
|
||||
) -> xr::Result {
|
||||
unsafe {
|
||||
(*state).should_render = false.into();
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn begin_frame(
|
||||
_session: xr::Session,
|
||||
_info: *const xr::FrameBeginInfo,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn end_frame(_session: xr::Session, _info: *const xr::FrameEndInfo) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
212
fakexr/src/vulkan.rs
Normal file
212
fakexr/src/vulkan.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use ash::vk::{self, Handle};
|
||||
use openxr_sys as xr_sys;
|
||||
use paste::paste;
|
||||
use std::ffi::{c_char, CStr};
|
||||
|
||||
macro_rules! get_fn {
|
||||
($name:expr => $($fn:ident),+) => {
|
||||
match $name {
|
||||
$(
|
||||
x if x == unsafe {
|
||||
const {
|
||||
CStr::from_bytes_with_nul_unchecked(concat!("vk", stringify!($fn), "\0").as_bytes())
|
||||
}
|
||||
} => paste! {
|
||||
Some(unsafe { std::mem::transmute([<$fn:snake>] as vk::[<PFN_vk $fn>]) })
|
||||
},
|
||||
)+
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_create_info() -> xr_sys::GraphicsBindingVulkanKHR {
|
||||
xr_sys::GraphicsBindingVulkanKHR {
|
||||
ty: xr_sys::GraphicsBindingVulkanKHR::TYPE,
|
||||
next: std::ptr::null(),
|
||||
instance: std::ptr::null(),
|
||||
physical_device: std::ptr::null(),
|
||||
device: std::ptr::null(),
|
||||
queue_family_index: 0,
|
||||
queue_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "system" fn get_instance_proc_addr(
|
||||
instance: vk::Instance,
|
||||
name: *const c_char,
|
||||
) -> vk::PFN_vkVoidFunction {
|
||||
let name = unsafe { CStr::from_ptr(name) };
|
||||
|
||||
if instance.is_null() {
|
||||
get_fn![name => CreateInstance]
|
||||
} else {
|
||||
get_fn![name =>
|
||||
GetPhysicalDeviceQueueFamilyProperties,
|
||||
CreateDevice,
|
||||
GetDeviceProcAddr,
|
||||
GetDeviceQueue,
|
||||
DestroyInstance
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extern "system" fn get_device_proc_addr(
|
||||
device: vk::Device,
|
||||
name: *const c_char,
|
||||
) -> vk::PFN_vkVoidFunction {
|
||||
if device.is_null() {
|
||||
None
|
||||
} else {
|
||||
let name = unsafe { CStr::from_ptr(name) };
|
||||
get_fn![name => GetDeviceQueue, DeviceWaitIdle, DestroyDevice]
|
||||
}
|
||||
}
|
||||
|
||||
struct Instance;
|
||||
|
||||
#[repr(C)]
|
||||
pub(crate) struct Device {
|
||||
debug: u64,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
const DEBUG_VAL: u64 = 946;
|
||||
pub fn validate(ptr: u64) -> bool {
|
||||
if ptr == 0 {
|
||||
return false;
|
||||
}
|
||||
let debug = ptr as *const u64;
|
||||
let val = unsafe { *debug };
|
||||
val == Device::DEBUG_VAL
|
||||
}
|
||||
}
|
||||
|
||||
extern "system" fn create_instance(
|
||||
_: *const vk::InstanceCreateInfo<'_>,
|
||||
_: *const vk::AllocationCallbacks<'_>,
|
||||
instance: *mut vk::Instance,
|
||||
) -> vk::Result {
|
||||
let inst = Box::new(Instance);
|
||||
unsafe {
|
||||
*instance = vk::Instance::from_raw(Box::into_raw(inst) as u64);
|
||||
}
|
||||
vk::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_instance(instance: vk::Instance, _: *const vk::AllocationCallbacks<'_>) {
|
||||
drop(unsafe { Box::from_raw(instance.as_raw() as *mut Instance) });
|
||||
}
|
||||
|
||||
extern "system" fn create_device(
|
||||
_: vk::PhysicalDevice,
|
||||
_: *const vk::DeviceCreateInfo,
|
||||
_: *const vk::AllocationCallbacks<'_>,
|
||||
device: *mut vk::Device,
|
||||
) -> vk::Result {
|
||||
let d = Box::new(Device {
|
||||
debug: Device::DEBUG_VAL,
|
||||
});
|
||||
unsafe {
|
||||
*device = vk::Device::from_raw(Box::into_raw(d) as u64);
|
||||
}
|
||||
vk::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn destroy_device(device: vk::Device, _: *const vk::AllocationCallbacks<'_>) {
|
||||
drop(unsafe { Box::from_raw(device.as_raw() as *mut Device) });
|
||||
}
|
||||
|
||||
extern "system" fn get_device_queue(
|
||||
_: vk::Device,
|
||||
queue_family_index: u32,
|
||||
queue_index: u32,
|
||||
queue: *mut vk::Queue,
|
||||
) {
|
||||
if queue_index == 0 && queue_family_index == 0 {
|
||||
unsafe {
|
||||
*queue = vk::Queue::from_raw(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "system" fn device_wait_idle(_: vk::Device) -> vk::Result {
|
||||
vk::Result::SUCCESS
|
||||
}
|
||||
|
||||
extern "system" fn get_physical_device_queue_family_properties(
|
||||
_physical_device: vk::PhysicalDevice,
|
||||
queue_family_property_count: *mut u32,
|
||||
queue_family_properties: *mut vk::QueueFamilyProperties,
|
||||
) {
|
||||
unsafe { *queue_family_property_count = 1 };
|
||||
if let Some(props) = unsafe { queue_family_properties.as_mut() } {
|
||||
*props = vk::QueueFamilyProperties {
|
||||
queue_count: 1,
|
||||
queue_flags: vk::QueueFlags::GRAPHICS,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod xr {
|
||||
use openxr_sys as xr;
|
||||
use std::ffi::c_char;
|
||||
|
||||
pub extern "system" fn get_vulkan_instance_extensions_k_h_r(
|
||||
_: xr::Instance,
|
||||
_: xr::SystemId,
|
||||
buffer_capacity_input: u32,
|
||||
buffer_count_output: *mut u32,
|
||||
buffer: *mut c_char,
|
||||
) -> xr::Result {
|
||||
if !buffer_count_output.is_null() {
|
||||
unsafe { *buffer_count_output = 1 };
|
||||
}
|
||||
if buffer_capacity_input >= 1 {
|
||||
unsafe { *buffer = 0 };
|
||||
}
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
pub extern "system" fn get_vulkan_device_extensions_k_h_r(
|
||||
instance: xr::Instance,
|
||||
system_id: xr::SystemId,
|
||||
buffer_capacity_input: u32,
|
||||
buffer_count_output: *mut u32,
|
||||
buffer: *mut c_char,
|
||||
) -> xr::Result {
|
||||
get_vulkan_instance_extensions_k_h_r(
|
||||
instance,
|
||||
system_id,
|
||||
buffer_capacity_input,
|
||||
buffer_count_output,
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
pub extern "system" fn get_vulkan_graphics_device_k_h_r(
|
||||
_: xr::Instance,
|
||||
_: xr::SystemId,
|
||||
_: xr::platform::VkInstance,
|
||||
_: *mut xr::platform::VkPhysicalDevice,
|
||||
) -> xr::Result {
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
|
||||
pub extern "system" fn get_vulkan_graphics_requirements_k_h_r(
|
||||
_: xr::Instance,
|
||||
_: xr::SystemId,
|
||||
graphics_requirements: *mut xr::GraphicsRequirementsVulkanKHR,
|
||||
) -> xr::Result {
|
||||
unsafe {
|
||||
*graphics_requirements = xr::GraphicsRequirementsVulkanKHR {
|
||||
ty: xr::GraphicsRequirementsVulkanKHR::TYPE,
|
||||
next: std::ptr::null_mut(),
|
||||
min_api_version_supported: xr::Version::new(1, 0, 0),
|
||||
max_api_version_supported: xr::Version::new(1, 0, 0),
|
||||
}
|
||||
};
|
||||
xr::Result::SUCCESS
|
||||
}
|
||||
}
|
||||
12
macros/Cargo.toml
Normal file
12
macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.37"
|
||||
syn = "2.0.77"
|
||||
|
||||
146
macros/src/lib.rs
Normal file
146
macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{punctuated::Punctuated, ItemStruct, Token};
|
||||
|
||||
#[proc_macro_derive(InterfaceImpl, attributes(interface, versions))]
|
||||
pub fn derive_interface_impl(tokens: TokenStream) -> TokenStream {
|
||||
let s: ItemStruct = syn::parse(tokens).unwrap();
|
||||
let name = s.ident;
|
||||
let generics_with_bounds = s.generics;
|
||||
let generics_idents: Punctuated<&syn::Ident, Token![,]> = generics_with_bounds
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let syn::GenericParam::Type(p) = p else {
|
||||
panic!("expected type generic");
|
||||
};
|
||||
&p.ident
|
||||
})
|
||||
.collect();
|
||||
|
||||
let versions_attr = &s
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|a| a.style == syn::AttrStyle::Outer && a.meta.path().is_ident("versions"))
|
||||
.expect("missing versions attribute")
|
||||
.meta;
|
||||
let versions = versions_attr
|
||||
.require_list()
|
||||
.expect("versions attribute should be a list of versions")
|
||||
.parse_args_with(Punctuated::<syn::LitInt, Token![,]>::parse_separated_nonempty)
|
||||
.expect("parsing versions failed");
|
||||
|
||||
let interface_attr = s
|
||||
.attrs
|
||||
.into_iter()
|
||||
.find(|a| a.style == syn::AttrStyle::Outer && a.meta.path().is_ident("interface"))
|
||||
.expect("missing interface attribute")
|
||||
.meta;
|
||||
let interface = interface_attr
|
||||
.require_name_value()
|
||||
.expect("parsing interface failed");
|
||||
|
||||
let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(interface),
|
||||
..
|
||||
}) = &interface.value
|
||||
else {
|
||||
panic!("expected string literal for interface");
|
||||
};
|
||||
let interface = interface.value();
|
||||
|
||||
let interface_versions_and_variants: Vec<(syn::Ident, syn::Ident)> = versions
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let num: u8 = v.base10_parse().unwrap();
|
||||
(format_ident!("{interface}{num:03}"), format_ident!("V{v}"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let wrapped_variants = interface_versions_and_variants.iter().map(|(interface, variant)| {
|
||||
quote! { #variant(crate::VtableWrapper<vr::#interface, super::#name<#generics_idents>>) }
|
||||
});
|
||||
let interface_versions = interface_versions_and_variants
|
||||
.iter()
|
||||
.map(|(interface, _)| {
|
||||
quote! { vr::#interface::VERSION }
|
||||
});
|
||||
|
||||
let get_vtable_arms = interface_versions_and_variants.iter().enumerate().map(|(index, (interface, variant))| {
|
||||
quote! {
|
||||
x if x == vr::#interface::VERSION => {
|
||||
Some(Box::new(|this: &std::sync::Arc<Self>| {
|
||||
let vtable = this.vtables.0[#index]
|
||||
.get_or_init(|| {
|
||||
WrappedVtable::#variant(<Self as crate::Inherits<vr::#interface>>::new_wrapped(this))
|
||||
});
|
||||
|
||||
let WrappedVtable::#variant(vtable) = vtable else {
|
||||
unreachable!("vtable currently holding incorrect version {:?} (expected {})", vtable, stringify!(#variant))
|
||||
};
|
||||
&vtable.base as *const vr::#interface as *mut std::ffi::c_void
|
||||
}))
|
||||
}
|
||||
x if x
|
||||
.to_string_lossy()
|
||||
.strip_prefix("FnTable:")
|
||||
.is_some_and(|x| x == vr::#interface::VERSION.to_string_lossy()) =>
|
||||
{
|
||||
Some(Box::new(<Self as crate::Inherits<vr::#interface>>::init_fntable))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let debug_arms = interface_versions_and_variants.iter().map(|(_, variant)| {
|
||||
quote! { Self::#variant(_) => f.write_str(stringify!(#variant)) }
|
||||
});
|
||||
|
||||
let mod_name = format_ident!("{}_gen", name.to_string().to_lowercase());
|
||||
let num_versions = versions.len();
|
||||
|
||||
quote! {
|
||||
use #mod_name::Vtables;
|
||||
mod #mod_name {
|
||||
use crate::vr;
|
||||
use std::sync::OnceLock;
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub struct Vtables #generics_with_bounds ([OnceLock<WrappedVtable <#generics_idents>>; #num_versions]);
|
||||
impl #generics_with_bounds Default for Vtables<#generics_idents> {
|
||||
fn default() -> Self {
|
||||
Self(std::array::from_fn(|_| OnceLock::new()))
|
||||
}
|
||||
}
|
||||
enum WrappedVtable #generics_with_bounds {
|
||||
#(#wrapped_variants),*
|
||||
}
|
||||
|
||||
unsafe impl #generics_with_bounds Sync for WrappedVtable<#generics_idents> {}
|
||||
unsafe impl #generics_with_bounds Send for WrappedVtable<#generics_idents> {}
|
||||
|
||||
impl #generics_with_bounds std::fmt::Debug for WrappedVtable<#generics_idents> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
#(#debug_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #generics_with_bounds crate::InterfaceImpl for super::#name<#generics_idents>{
|
||||
fn supported_versions() -> &'static [&'static CStr] {
|
||||
&[
|
||||
#(#interface_versions),*
|
||||
]
|
||||
}
|
||||
|
||||
fn get_version(version: &std::ffi::CStr) -> Option<Box<dyn FnOnce(&std::sync::Arc<Self>) -> *mut std::ffi::c_void>> {
|
||||
match version {
|
||||
#(#get_vtable_arms)*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
35
openvr/ivrclientcore_002.h
Normal file
35
openvr/ivrclientcore_002.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//========= Copyright Valve Corporation ============//
|
||||
#pragma once
|
||||
|
||||
namespace vr
|
||||
{
|
||||
|
||||
class IVRClientCore
|
||||
{
|
||||
public:
|
||||
/** Initializes the system */
|
||||
virtual EVRInitError Init( vr::EVRApplicationType eApplicationType ) = 0;
|
||||
|
||||
/** cleans up everything in vrclient.dll and prepares the DLL to be unloaded */
|
||||
virtual void Cleanup() = 0;
|
||||
|
||||
/** checks to see if the specified interface/version is supported in this vrclient.dll */
|
||||
virtual EVRInitError IsInterfaceVersionValid( const char *pchInterfaceVersion ) = 0;
|
||||
|
||||
/** Retrieves any interface from vrclient.dll */
|
||||
virtual void *GetGenericInterface( const char *pchNameAndVersion, EVRInitError *peError ) = 0;
|
||||
|
||||
/** Returns true if any driver has an HMD attached. Can be called outside of Init/Cleanup */
|
||||
virtual bool BIsHmdPresent() = 0;
|
||||
|
||||
/** Returns an English error string from inside vrclient.dll which might be newer than the API DLL */
|
||||
virtual const char *GetEnglishStringForHmdError( vr::EVRInitError eError ) = 0;
|
||||
|
||||
/** Returns an error symbol from inside vrclient.dll which might be newer than the API DLL */
|
||||
virtual const char *GetIDForVRInitError( vr::EVRInitError eError ) = 0;
|
||||
};
|
||||
|
||||
static const char * const IVRClientCore_Version = "IVRClientCore_002";
|
||||
|
||||
|
||||
}
|
||||
35
openvr/ivrclientcore_003.h
Normal file
35
openvr/ivrclientcore_003.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//========= Copyright Valve Corporation ============//
|
||||
#pragma once
|
||||
|
||||
namespace vr
|
||||
{
|
||||
|
||||
class IVRClientCore
|
||||
{
|
||||
public:
|
||||
/** Initializes the system */
|
||||
virtual EVRInitError Init( vr::EVRApplicationType eApplicationType, const char *pStartupInfo ) = 0;
|
||||
|
||||
/** cleans up everything in vrclient.dll and prepares the DLL to be unloaded */
|
||||
virtual void Cleanup() = 0;
|
||||
|
||||
/** checks to see if the specified interface/version is supported in this vrclient.dll */
|
||||
virtual EVRInitError IsInterfaceVersionValid( const char *pchInterfaceVersion ) = 0;
|
||||
|
||||
/** Retrieves any interface from vrclient.dll */
|
||||
virtual void *GetGenericInterface( const char *pchNameAndVersion, EVRInitError *peError ) = 0;
|
||||
|
||||
/** Returns true if any driver has an HMD attached. Can be called outside of Init/Cleanup */
|
||||
virtual bool BIsHmdPresent() = 0;
|
||||
|
||||
/** Returns an English error string from inside vrclient.dll which might be newer than the API DLL */
|
||||
virtual const char *GetEnglishStringForHmdError( vr::EVRInitError eError ) = 0;
|
||||
|
||||
/** Returns an error symbol from inside vrclient.dll which might be newer than the API DLL */
|
||||
virtual const char *GetIDForVRInitError( vr::EVRInitError eError ) = 0;
|
||||
};
|
||||
|
||||
static const char * const IVRClientCore_Version = "IVRClientCore_003";
|
||||
|
||||
|
||||
}
|
||||
4635
openvr/openvr-1.0.17.h
Normal file
4635
openvr/openvr-1.0.17.h
Normal file
File diff suppressed because it is too large
Load diff
3567
openvr/openvr-1.0.5.h
Normal file
3567
openvr/openvr-1.0.5.h
Normal file
File diff suppressed because it is too large
Load diff
3661
openvr/openvr-1.0.7.h
Normal file
3661
openvr/openvr-1.0.7.h
Normal file
File diff suppressed because it is too large
Load diff
3728
openvr/openvr-1.0.9.h
Normal file
3728
openvr/openvr-1.0.9.h
Normal file
File diff suppressed because it is too large
Load diff
5526
openvr/openvr-1.14.15.h
Normal file
5526
openvr/openvr-1.14.15.h
Normal file
File diff suppressed because it is too large
Load diff
5155
openvr/openvr-1.18.19.h
Normal file
5155
openvr/openvr-1.18.19.h
Normal file
File diff suppressed because it is too large
Load diff
5008
openvr/openvr-1.3.22.h
Normal file
5008
openvr/openvr-1.3.22.h
Normal file
File diff suppressed because it is too large
Load diff
5047
openvr/openvr-1.4.18.h
Normal file
5047
openvr/openvr-1.4.18.h
Normal file
File diff suppressed because it is too large
Load diff
5171
openvr/openvr-1.7.15.h
Normal file
5171
openvr/openvr-1.7.15.h
Normal file
File diff suppressed because it is too large
Load diff
5799
openvr/openvr-2.0.10.h
Normal file
5799
openvr/openvr-2.0.10.h
Normal file
File diff suppressed because it is too large
Load diff
5853
openvr/openvr-2.5.1.h
Normal file
5853
openvr/openvr-2.5.1.h
Normal file
File diff suppressed because it is too large
Load diff
1
openxr-sdk
Submodule
1
openxr-sdk
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f90488c4fb1537f4256d09d4a4d3ad5543ebaf24
|
||||
146
src/applications.rs
Normal file
146
src/applications.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use crate::vr;
|
||||
use std::ffi::c_char;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVRApplications"]
|
||||
#[versions(007)]
|
||||
pub struct Applications {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
impl vr::IVRApplications007_Interface for Applications {
|
||||
fn GetCurrentSceneProcessId(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn LaunchInternalProcess(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: *const c_char,
|
||||
_: *const c_char,
|
||||
) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetSceneApplicationStateNameFromEnum(
|
||||
&self,
|
||||
_: vr::EVRSceneApplicationState,
|
||||
) -> *const c_char {
|
||||
todo!()
|
||||
}
|
||||
fn PerformApplicationPrelaunchCheck(&self, _: *const c_char) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetSceneApplicationState(&self) -> vr::EVRSceneApplicationState {
|
||||
todo!()
|
||||
}
|
||||
fn GetStartingApplication(&self, _: *mut c_char, _: u32) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationLaunchArguments(&self, _: u32, _: *mut c_char, _: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationsThatSupportMimeType(&self, _: *const c_char, _: *mut c_char, _: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationSupportedMimeTypes(&self, _: *const c_char, _: *mut c_char, _: u32) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetDefaultApplicationForMimeType(&self, _: *const c_char, _: *mut c_char, _: u32) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn SetDefaultApplicationForMimeType(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: *const c_char,
|
||||
) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationAutoLaunch(&self, _: *const c_char) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn SetApplicationAutoLaunch(&self, _: *const c_char, _: bool) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationPropertyUint64(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: vr::EVRApplicationProperty,
|
||||
_: *mut vr::EVRApplicationError,
|
||||
) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationPropertyBool(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: vr::EVRApplicationProperty,
|
||||
_: *mut vr::EVRApplicationError,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationPropertyString(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: vr::EVRApplicationProperty,
|
||||
_: *mut c_char,
|
||||
_: u32,
|
||||
_: *mut vr::EVRApplicationError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationsErrorNameFromEnum(&self, _: vr::EVRApplicationError) -> *const c_char {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationProcessId(&self, _: *const c_char) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn IdentifyApplication(&self, _: u32, _: *const c_char) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn CancelApplicationLaunch(&self, _: *const c_char) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn LaunchDashboardOverlay(&self, _: *const c_char) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn LaunchApplicationFromMimeType(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: *const c_char,
|
||||
) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn LaunchTemplateApplication(
|
||||
&self,
|
||||
_: *const c_char,
|
||||
_: *const c_char,
|
||||
_: *const vr::AppOverrideKeys_t,
|
||||
_: u32,
|
||||
) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn LaunchApplication(&self, _: *const c_char) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationKeyByProcessId(
|
||||
&self,
|
||||
_: u32,
|
||||
_: *mut c_char,
|
||||
_: u32,
|
||||
) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationKeyByIndex(&self, _: u32, _: *mut c_char, _: u32) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn GetApplicationCount(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn IsApplicationInstalled(&self, _: *const c_char) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn RemoveApplicationManifest(&self, _: *const c_char) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
fn AddApplicationManifest(&self, _: *const c_char, _: bool) -> vr::EVRApplicationError {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
53
src/chaperone.rs
Normal file
53
src/chaperone.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use crate::vr;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVRChaperone"]
|
||||
#[versions(004, 003)]
|
||||
pub struct Chaperone {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
impl vr::IVRChaperone004_Interface for Chaperone {
|
||||
fn ResetZeroPose(&self, _: vr::ETrackingUniverseOrigin) {
|
||||
crate::warn_unimplemented!("ResetZeroPose");
|
||||
}
|
||||
fn ForceBoundsVisible(&self, _: bool) {
|
||||
todo!()
|
||||
}
|
||||
fn AreBoundsVisible(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn GetBoundsColor(
|
||||
&self,
|
||||
_: *mut vr::HmdColor_t,
|
||||
_: std::os::raw::c_int,
|
||||
_: f32,
|
||||
_: *mut vr::HmdColor_t,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
fn SetSceneColor(&self, _: vr::HmdColor_t) {
|
||||
todo!()
|
||||
}
|
||||
fn ReloadInfo(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn GetPlayAreaRect(&self, rect: *mut vr::HmdQuad_t) -> bool {
|
||||
crate::warn_unimplemented!("GetPlayAreaRect");
|
||||
unsafe {
|
||||
*rect = Default::default();
|
||||
}
|
||||
false
|
||||
}
|
||||
fn GetPlayAreaSize(&self, size_x: *mut f32, size_z: *mut f32) -> bool {
|
||||
crate::warn_unimplemented!("GetPlayAreaSize");
|
||||
unsafe {
|
||||
*size_x = 1.0;
|
||||
*size_z = 1.0;
|
||||
};
|
||||
true
|
||||
}
|
||||
fn GetCalibrationState(&self) -> vr::ChaperoneCalibrationState {
|
||||
vr::ChaperoneCalibrationState::ChaperoneCalibrationState_OK
|
||||
}
|
||||
}
|
||||
464
src/clientcore.rs
Normal file
464
src/clientcore.rs
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
use crate::{
|
||||
applications::Applications,
|
||||
chaperone::Chaperone,
|
||||
compositor::Compositor,
|
||||
input::Input,
|
||||
misc_unknown::UnknownInterfaces,
|
||||
openxr_data::{OpenXrData, RealOpenXrData},
|
||||
overlay::OverlayMan,
|
||||
overlayview::OverlayView,
|
||||
rendermodels::RenderModels,
|
||||
screenshots::Screenshots,
|
||||
system::System,
|
||||
vr::{self, IVRClientCore003_Interface},
|
||||
Inherits, InterfaceImpl,
|
||||
};
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::sync::{Arc, LazyLock, Mutex, OnceLock, RwLock, Weak};
|
||||
|
||||
type ErasedInterface = dyn Any + Sync + Send;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Default)]
|
||||
struct InterfaceStore(HashMap<TypeId, Arc<ErasedInterface>>);
|
||||
|
||||
impl InterfaceStore {
|
||||
fn get<T: 'static + InterfaceImpl>(&self) -> Option<Arc<T>> {
|
||||
self.0
|
||||
.get(&TypeId::of::<T>())
|
||||
.map(|i| i.clone().downcast().unwrap())
|
||||
}
|
||||
|
||||
fn entry<T: 'static + InterfaceImpl>(&mut self) -> Entry<'_, TypeId, Arc<ErasedInterface>> {
|
||||
self.0.entry(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Vtable {
|
||||
V2(crate::VtableWrapper<vr::IVRClientCore002, ClientCore>),
|
||||
V3(crate::VtableWrapper<vr::IVRClientCore003, ClientCore>),
|
||||
}
|
||||
|
||||
unsafe impl Sync for Vtable {}
|
||||
unsafe impl Send for Vtable {}
|
||||
|
||||
pub struct ClientCore {
|
||||
pub base: OnceLock<Vtable>,
|
||||
interface_store: Arc<Mutex<InterfaceStore>>,
|
||||
openxr: RwLock<Option<Arc<RealOpenXrData>>>,
|
||||
}
|
||||
|
||||
impl ClientCore {
|
||||
pub fn new(version: &CStr) -> Option<Arc<Self>> {
|
||||
crate::init_logging();
|
||||
|
||||
if ![c"IVRClientCore_003", c"IVRClientCore_002"]
|
||||
.iter()
|
||||
.any(|s| *s == version)
|
||||
{
|
||||
error!("Application requested unknown ClientCore version: {version:?}");
|
||||
return None;
|
||||
}
|
||||
|
||||
info!("Creating ClientCore version {version:?}");
|
||||
let ret = Arc::new(Self {
|
||||
base: OnceLock::new(),
|
||||
interface_store: Default::default(),
|
||||
openxr: RwLock::default(),
|
||||
});
|
||||
|
||||
let base = match version {
|
||||
x if x == c"IVRClientCore_003" => {
|
||||
Vtable::V3(<Self as Inherits<vr::IVRClientCore003>>::new_wrapped(&ret))
|
||||
}
|
||||
x if x == c"IVRClientCore_002" => {
|
||||
Vtable::V2(<Self as Inherits<vr::IVRClientCore002>>::new_wrapped(&ret))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert!(ret.base.set(base).is_ok());
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
fn try_interface<T, InitFn>(&self, version: &CStr, init: InitFn) -> Option<*mut c_void>
|
||||
where
|
||||
T: InterfaceImpl + 'static,
|
||||
InitFn: FnOnce(&Injector) -> T,
|
||||
{
|
||||
let Some(get_interface) = T::get_version(version) else {
|
||||
return None;
|
||||
};
|
||||
let mut store = self.interface_store.lock().unwrap();
|
||||
let item = match store.entry::<T>() {
|
||||
Entry::Occupied(entry) => entry.get().clone(),
|
||||
Entry::Vacant(entry) => {
|
||||
let injector = Injector {
|
||||
store: self.interface_store.clone(),
|
||||
};
|
||||
let item = Arc::new(init(&injector));
|
||||
entry.insert(item).clone()
|
||||
}
|
||||
}
|
||||
.downcast()
|
||||
.unwrap();
|
||||
Some(get_interface(&item))
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRClientCore002On003 for ClientCore {
|
||||
fn Init(&self, app_type: vr::EVRApplicationType) -> vr::EVRInitError {
|
||||
<Self as vr::IVRClientCore003_Interface>::Init(self, app_type, std::ptr::null())
|
||||
}
|
||||
}
|
||||
|
||||
impl IVRClientCore003_Interface for ClientCore {
|
||||
fn Init(
|
||||
&self,
|
||||
_application_type: vr::EVRApplicationType,
|
||||
_startup_info: *const c_char,
|
||||
) -> vr::EVRInitError {
|
||||
match OpenXrData::new(&Injector {
|
||||
store: self.interface_store.clone(),
|
||||
}) {
|
||||
Ok(data) => {
|
||||
*self.openxr.write().unwrap() = Some(data.into());
|
||||
|
||||
vr::EVRInitError::VRInitError_None
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Creating OpenXR data failed: {e:?}");
|
||||
vr::EVRInitError::VRInitError_Init_VRServiceStartupFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
fn Cleanup(&self) {
|
||||
self.interface_store.lock().unwrap().clear();
|
||||
|
||||
let mut openxr = self.openxr.write().unwrap();
|
||||
assert_eq!(Arc::strong_count(&openxr.as_ref().unwrap()), 1);
|
||||
openxr.take();
|
||||
}
|
||||
fn GetIDForVRInitError(&self, _: vr::EVRInitError) -> *const c_char {
|
||||
std::ptr::null()
|
||||
}
|
||||
fn GetEnglishStringForHmdError(&self, _: vr::EVRInitError) -> *const c_char {
|
||||
std::ptr::null()
|
||||
}
|
||||
fn BIsHmdPresent(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn GetGenericInterface(
|
||||
&self,
|
||||
name_and_version: *const c_char,
|
||||
error: *mut vr::EVRInitError,
|
||||
) -> *mut c_void {
|
||||
let interface = unsafe { CStr::from_ptr(name_and_version) };
|
||||
debug!("requested interface {interface:?}");
|
||||
|
||||
if !error.is_null() {
|
||||
unsafe { *error = vr::EVRInitError::VRInitError_None };
|
||||
}
|
||||
|
||||
let openxr = self.openxr.read().unwrap();
|
||||
let openxr = openxr.as_ref().unwrap();
|
||||
|
||||
self.try_interface(interface, |injector| System::new(openxr.clone(), injector))
|
||||
.or_else(|| {
|
||||
self.try_interface(interface, |injector| {
|
||||
Compositor::new(openxr.clone(), injector)
|
||||
})
|
||||
})
|
||||
.or_else(|| self.try_interface(interface, |_| Input::new(openxr.clone())))
|
||||
.or_else(|| self.try_interface(interface, |_| RenderModels::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| OverlayMan::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| Chaperone::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| Applications::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| OverlayView::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| Screenshots::default()))
|
||||
.or_else(|| self.try_interface(interface, |_| UnknownInterfaces::default()))
|
||||
.unwrap_or_else(|| {
|
||||
warn!("app requested unknown interface {interface:?}");
|
||||
std::ptr::null_mut()
|
||||
})
|
||||
}
|
||||
fn IsInterfaceVersionValid(&self, interface_version: *const c_char) -> vr::EVRInitError {
|
||||
// Keep this in sync with GetGenericInterface above.
|
||||
static KNOWN_INTERFACES: LazyLock<Box<[&CStr]>> = LazyLock::new(|| {
|
||||
[
|
||||
System::supported_versions(),
|
||||
Compositor::supported_versions(),
|
||||
Input::<Compositor>::supported_versions(),
|
||||
RenderModels::supported_versions(),
|
||||
OverlayMan::supported_versions(),
|
||||
Chaperone::supported_versions(),
|
||||
Applications::supported_versions(),
|
||||
OverlayView::supported_versions(),
|
||||
Screenshots::supported_versions(),
|
||||
UnknownInterfaces::supported_versions(),
|
||||
]
|
||||
.concat()
|
||||
.into_boxed_slice()
|
||||
});
|
||||
|
||||
let interface = unsafe { CStr::from_ptr(interface_version) };
|
||||
debug!("app asking about interface: {interface:?}");
|
||||
if KNOWN_INTERFACES.contains(&interface) {
|
||||
vr::EVRInitError::VRInitError_None
|
||||
} else {
|
||||
warn!("app asked about unknown interface {interface:?}");
|
||||
vr::EVRInitError::VRInitError_Init_InvalidInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Injector {
|
||||
store: Arc<Mutex<InterfaceStore>>,
|
||||
}
|
||||
|
||||
impl Injector {
|
||||
pub fn inject<T: InterfaceImpl>(&self) -> Injected<T> {
|
||||
Injected::<T> {
|
||||
item: OnceLock::new(),
|
||||
store: self.store.clone(),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Injected<T> {
|
||||
item: OnceLock<Weak<ErasedInterface>>,
|
||||
store: Arc<Mutex<InterfaceStore>>,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: InterfaceImpl> Injected<T> {
|
||||
#[cfg(test)]
|
||||
pub fn set(&self, item: Weak<T>) {
|
||||
self.item.set(item).unwrap();
|
||||
}
|
||||
pub fn get(&self) -> Option<Arc<T>> {
|
||||
self.item
|
||||
.get()
|
||||
.or_else(|| {
|
||||
let Some(item) = self.store.lock().unwrap().get::<T>() else {
|
||||
return None;
|
||||
};
|
||||
let item: Arc<ErasedInterface> = item;
|
||||
self.item
|
||||
.set(Arc::downgrade(&item))
|
||||
.unwrap_or_else(|_| unreachable!());
|
||||
Some(self.item.get().unwrap())
|
||||
})
|
||||
.map(|i| i.upgrade().unwrap().downcast().unwrap())
|
||||
}
|
||||
|
||||
pub fn force(&self, init: impl FnOnce(&Injector) -> T) -> Arc<T> {
|
||||
self.item
|
||||
.get_or_init(|| {
|
||||
let injector = Injector {
|
||||
store: self.store.clone(),
|
||||
};
|
||||
// Item may get forced by another thread while we're in the process of forcing it,
|
||||
// so try getting it before inserting one.
|
||||
Arc::downgrade(
|
||||
self.store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry::<T>()
|
||||
.or_insert(Arc::new(init(&injector))),
|
||||
)
|
||||
})
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.downcast()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl ClientCore {
|
||||
fn get_interface<T: InterfaceImpl + 'static>(&self) -> Option<Arc<T>> {
|
||||
self.interface_store.lock().unwrap().get::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! interface {
|
||||
($interface:ident$(($($fields:tt)*))?, $version:literal) => {
|
||||
#[derive(Debug)]
|
||||
struct $interface$(($($fields)*))?;
|
||||
impl InterfaceImpl for $interface {
|
||||
fn get_version(
|
||||
version: &CStr,
|
||||
) -> Option<Box<dyn FnOnce(&Arc<Self>) -> *mut c_void>> {
|
||||
if version == $version {
|
||||
Some(Box::new(|this| Arc::as_ptr(this) as _))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn supported_versions() -> &'static [&'static CStr] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
interface!(Interface1, c"one");
|
||||
interface!(Interface2(Injected<Interface1>), c"two");
|
||||
interface!(Interface3(Injected<Interface1>), c"three");
|
||||
interface!(Interface4(Injected<Interface1>), c"four");
|
||||
|
||||
impl<T: std::fmt::Debug + InterfaceImpl> std::fmt::Debug for Injected<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"Injected<{}>({:?})",
|
||||
std::any::type_name::<T>(),
|
||||
self.item.get()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart() {
|
||||
let core = ClientCore::new(c"IVRClientCore_003").unwrap();
|
||||
core.clone().Init(
|
||||
vr::EVRApplicationType::VRApplication_Scene,
|
||||
std::ptr::null(),
|
||||
);
|
||||
core.clone().Cleanup();
|
||||
core.clone().Init(
|
||||
vr::EVRApplicationType::VRApplication_Scene,
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject() {
|
||||
let core = ClientCore::new(c"IVRClientCore_003").unwrap();
|
||||
|
||||
core.try_interface(c"two", |injector| Interface2(injector.inject()));
|
||||
let interface2_before = core
|
||||
.get_interface::<Interface2>()
|
||||
.expect("Interface2 missing from store");
|
||||
|
||||
assert!(
|
||||
interface2_before.0.get().is_none(),
|
||||
"Interface1 should not have been injected yet"
|
||||
);
|
||||
|
||||
core.try_interface(c"one", |_| Interface1);
|
||||
let interface1 = core
|
||||
.get_interface::<Interface1>()
|
||||
.expect("Interface1 missing from store");
|
||||
|
||||
let interface2 = core
|
||||
.get_interface::<Interface2>()
|
||||
.expect("Interface2 missing from store");
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&interface2),
|
||||
Arc::as_ptr(&interface2_before),
|
||||
"Interface2 changed in store"
|
||||
);
|
||||
|
||||
let injected = interface2.0.get().expect("Interface1 not injected");
|
||||
assert_eq!(Arc::as_ptr(&injected), Arc::as_ptr(&interface1),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_force() {
|
||||
let core = ClientCore::new(c"IVRClientCore_003").unwrap();
|
||||
|
||||
core.try_interface(c"two", |injector| Interface2(injector.inject()));
|
||||
let interface2 = core
|
||||
.get_interface::<Interface2>()
|
||||
.expect("Interface2 missing from store");
|
||||
assert!(
|
||||
interface2.0.get().is_none(),
|
||||
"Interface1 should not have been injected yet"
|
||||
);
|
||||
core.try_interface(c"three", |injector| Interface3(injector.inject()));
|
||||
let interface3 = core
|
||||
.get_interface::<Interface3>()
|
||||
.expect("Interface3 missing from store");
|
||||
assert!(
|
||||
interface3.0.get().is_none(),
|
||||
"Interface1 should not have been injected yet"
|
||||
);
|
||||
|
||||
interface2.0.force(|_| Interface1);
|
||||
let interface1 = core
|
||||
.get_interface::<Interface1>()
|
||||
.expect("Interface1 not injected");
|
||||
let injected2 = interface2
|
||||
.0
|
||||
.get()
|
||||
.expect("Interface1 not injected into Interface2");
|
||||
let injected3 = interface3
|
||||
.0
|
||||
.get()
|
||||
.expect("Interface1 not injected into Interface3");
|
||||
assert_eq!(Arc::as_ptr(&injected2), Arc::as_ptr(&interface1));
|
||||
assert_eq!(Arc::as_ptr(&injected3), Arc::as_ptr(&interface1));
|
||||
|
||||
core.try_interface(c"four", |injector| Interface4(injector.inject()));
|
||||
let interface4 = core
|
||||
.get_interface::<Interface4>()
|
||||
.expect("Interface4 missing from store");
|
||||
let injected4 = interface4
|
||||
.0
|
||||
.get()
|
||||
.expect("Interface1 not injected into Interface4");
|
||||
assert_eq!(Arc::as_ptr(&injected4), Arc::as_ptr(&interface1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_multithread_force() {
|
||||
let core = ClientCore::new(c"IVRClientCore_003").unwrap();
|
||||
|
||||
core.try_interface(c"two", |injector| Interface2(injector.inject()));
|
||||
let interface2 = core
|
||||
.get_interface::<Interface2>()
|
||||
.expect("Interface2 missing from store");
|
||||
|
||||
core.try_interface(c"three", |injector| Interface3(injector.inject()));
|
||||
let interface3 = core
|
||||
.get_interface::<Interface3>()
|
||||
.expect("Interface3 missing from store");
|
||||
|
||||
let (injected2, injected3) = std::thread::scope(|s| {
|
||||
let barrier = Arc::new(std::sync::Barrier::new(2));
|
||||
let b2 = barrier.clone();
|
||||
let i2 = &interface2.0;
|
||||
let ij2 = s.spawn(move || {
|
||||
b2.wait();
|
||||
i2.force(|_| Interface1)
|
||||
});
|
||||
let i3 = &interface3.0;
|
||||
let ij3 = s.spawn(move || {
|
||||
barrier.wait();
|
||||
i3.force(|_| Interface1)
|
||||
});
|
||||
(ij2.join().unwrap(), ij3.join().unwrap())
|
||||
});
|
||||
|
||||
core.try_interface(c"one", |_| Interface1);
|
||||
let interface1 = core
|
||||
.get_interface::<Interface1>()
|
||||
.expect("Interface1 missing from store");
|
||||
|
||||
assert_eq!(Arc::as_ptr(&injected2), Arc::as_ptr(&interface1));
|
||||
assert_eq!(Arc::as_ptr(&injected3), Arc::as_ptr(&interface1));
|
||||
}
|
||||
}
|
||||
911
src/compositor.rs
Normal file
911
src/compositor.rs
Normal file
|
|
@ -0,0 +1,911 @@
|
|||
use crate::{
|
||||
clientcore::{Injected, Injector},
|
||||
input::Input,
|
||||
openxr_data::{self, OpenXrData, SessionData},
|
||||
vr,
|
||||
vulkan::VulkanData,
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
use ash::vk::{self, Handle};
|
||||
use log::{debug, info, trace};
|
||||
use openxr as xr;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
struct FrameController {
|
||||
stream: xr::FrameStream<xr::vulkan::Vulkan>,
|
||||
waiter: xr::FrameWaiter,
|
||||
swapchain: xr::Swapchain<xr::vulkan::Vulkan>,
|
||||
image_index: usize,
|
||||
should_render: bool,
|
||||
eyes_submitted: [Option<SubmittedEye>; 2],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct SubmittedEye {
|
||||
extent: xr::Extent2Di,
|
||||
flip_vertically: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CompositorSessionData(Mutex<Option<FrameController>>);
|
||||
|
||||
#[derive(macros::InterfaceImpl)]
|
||||
#[interface = "IVRCompositor"]
|
||||
#[versions(028, 027, 026, 022, 020, 019)]
|
||||
pub struct Compositor {
|
||||
vtables: Vtables,
|
||||
openxr: Arc<OpenXrData<Self>>,
|
||||
input: Injected<Input<Self>>,
|
||||
backend: OnceLock<GraphicsApi>,
|
||||
swapchain_create_info: ArcSwap<xr::SwapchainCreateInfo<xr::vulkan::Vulkan>>,
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
pub fn new(openxr: Arc<OpenXrData<Self>>, injector: &Injector) -> Self {
|
||||
Self {
|
||||
vtables: Default::default(),
|
||||
openxr,
|
||||
input: injector.inject(),
|
||||
backend: Default::default(),
|
||||
swapchain_create_info: ArcSwap::new(
|
||||
// This will be overwritten before actually creating our swapchain.
|
||||
xr::SwapchainCreateInfo {
|
||||
create_flags: Default::default(),
|
||||
usage_flags: Default::default(),
|
||||
format: 0,
|
||||
sample_count: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
face_count: 0,
|
||||
array_size: 0,
|
||||
mip_count: 0,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a frame if we've created our frame controller.
|
||||
fn maybe_start_frame(&self, session_data: &SessionData) {
|
||||
let mut frame_lock = session_data.comp_data.0.lock().unwrap();
|
||||
let Some(ctrl) = frame_lock.as_mut() else {
|
||||
debug!("no frame controller - not starting frame");
|
||||
return;
|
||||
};
|
||||
|
||||
ctrl.image_index = ctrl
|
||||
.swapchain
|
||||
.acquire_image()
|
||||
.expect("Failed to acquire swapchain image") as usize;
|
||||
|
||||
trace!("waiting image");
|
||||
ctrl.swapchain
|
||||
.wait_image(xr::Duration::INFINITE)
|
||||
.expect("Failed to wait for swapchain image");
|
||||
|
||||
let frame_state = ctrl.waiter.wait().unwrap();
|
||||
ctrl.should_render = frame_state.should_render;
|
||||
self.openxr
|
||||
.display_time
|
||||
.set(frame_state.predicted_display_time);
|
||||
ctrl.stream.begin().unwrap();
|
||||
ctrl.eyes_submitted = [None; 2];
|
||||
trace!("frame begin");
|
||||
}
|
||||
|
||||
fn initialize_real_session(&self, texture: &vr::Texture_t, bounds: vr::VRTextureBounds_t) {
|
||||
let backend = GraphicsApi::new(texture);
|
||||
self.swapchain_create_info
|
||||
.store(backend.get_swapchain_create_info(texture, bounds).into());
|
||||
|
||||
self.backend.set(backend).unwrap_or_else(|_| unreachable!());
|
||||
|
||||
self.openxr.restart_session(); // calls init_frame_controller
|
||||
}
|
||||
}
|
||||
|
||||
impl openxr_data::Compositor for Compositor {
|
||||
fn current_session_create_info(&self) -> openxr::vulkan::SessionCreateInfo {
|
||||
self.backend
|
||||
.get()
|
||||
.expect("Backend is not set up!")
|
||||
.as_session_create_info()
|
||||
}
|
||||
|
||||
fn init_frame_controller(
|
||||
&self,
|
||||
session_data: &SessionData,
|
||||
waiter: xr::FrameWaiter,
|
||||
stream: xr::FrameStream<xr::vulkan::Vulkan>,
|
||||
) {
|
||||
// This function is called while a write lock is called on the session, and as such should
|
||||
// not use self.openxr.session_data.get().
|
||||
|
||||
let backend = self
|
||||
.backend
|
||||
.get()
|
||||
.expect("Backend should be set up before initializing frame controller");
|
||||
|
||||
let swapchain = session_data
|
||||
.session
|
||||
.create_swapchain(&*self.swapchain_create_info.load())
|
||||
.expect("Failed to create swapchain");
|
||||
|
||||
let images: Vec<vk::Image> = swapchain
|
||||
.enumerate_images()
|
||||
.expect("Failed to enumerate swapchain images")
|
||||
.into_iter()
|
||||
.map(|img| vk::Image::from_raw(img))
|
||||
.collect();
|
||||
|
||||
backend.post_swapchain_create(images);
|
||||
|
||||
*session_data.comp_data.0.lock().unwrap() = Some(FrameController {
|
||||
stream,
|
||||
waiter,
|
||||
swapchain,
|
||||
image_index: 0,
|
||||
should_render: false,
|
||||
eyes_submitted: Default::default(),
|
||||
});
|
||||
|
||||
self.maybe_start_frame(session_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl vr::IVRCompositor028_Interface for Compositor {
|
||||
fn GetPosesForFrame(
|
||||
&self,
|
||||
_unPosePredictionID: u32,
|
||||
_pPoseArray: *mut vr::TrackedDevicePose_t,
|
||||
_unPoseArrayCount: u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
todo!()
|
||||
}
|
||||
fn GetLastPosePredictionIDs(
|
||||
&self,
|
||||
_pRenderPosePredictionID: *mut u32,
|
||||
_pGamePosePredictionID: *mut u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
crate::warn_unimplemented!("GetLastPosePredictionIDs");
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
fn GetCompositorBenchmarkResults(
|
||||
&self,
|
||||
_pBenchmarkResults: *mut vr::Compositor_BenchmarkResults,
|
||||
_nSizeOfBenchmarkResults: u32,
|
||||
) -> bool {
|
||||
crate::warn_unimplemented!("GetCompositorBenchmarkResults");
|
||||
false
|
||||
}
|
||||
fn ClearStageOverride(&self) {}
|
||||
fn SetStageOverride_Async(
|
||||
&self,
|
||||
_pchRenderModelPath: *const std::ffi::c_char,
|
||||
_pTransform: *const vr::HmdMatrix34_t,
|
||||
_pRenderSettings: *const vr::Compositor_StageRenderSettings,
|
||||
_nSizeOfRenderSettings: u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
crate::warn_unimplemented!("SetStageOverride_Async");
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
fn IsCurrentSceneFocusAppLoading(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn IsMotionSmoothingSupported(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn IsMotionSmoothingEnabled(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn SubmitExplicitTimingData(&self) -> vr::EVRCompositorError {
|
||||
crate::warn_unimplemented!("SubmitExplicitTimingData");
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
fn SetExplicitTimingMode(&self, _eTimingMode: vr::EVRCompositorTimingMode) {
|
||||
crate::warn_unimplemented!("SetExplicitTimingMode");
|
||||
}
|
||||
|
||||
fn GetVulkanDeviceExtensionsRequired(
|
||||
&self,
|
||||
_physical_device: *mut crate::bindings::VkPhysicalDevice_T,
|
||||
buffer: *mut std::ffi::c_char,
|
||||
buffer_size: u32,
|
||||
) -> u32 {
|
||||
let exts = self
|
||||
.openxr
|
||||
.instance
|
||||
.vulkan_legacy_device_extensions(self.openxr.system_id)
|
||||
.unwrap();
|
||||
let bytes = unsafe { &*(exts.as_bytes() as *const [u8] as *const [std::ffi::c_char]) };
|
||||
if !buffer.is_null() && buffer_size > 0 {
|
||||
let size = buffer_size as usize;
|
||||
let ret_buf = unsafe { std::slice::from_raw_parts_mut(buffer, size) };
|
||||
ret_buf[0..size - 1].copy_from_slice(&bytes[0..size - 1]);
|
||||
ret_buf[size - 1] = 0;
|
||||
}
|
||||
exts.len() as u32 + 1
|
||||
}
|
||||
|
||||
fn GetVulkanInstanceExtensionsRequired(
|
||||
&self,
|
||||
buffer: *mut std::ffi::c_char,
|
||||
buffer_size: u32,
|
||||
) -> u32 {
|
||||
let exts = self
|
||||
.openxr
|
||||
.instance
|
||||
.vulkan_legacy_instance_extensions(self.openxr.system_id)
|
||||
.unwrap();
|
||||
let bytes = unsafe { &*(exts.as_bytes() as *const [u8] as *const [std::ffi::c_char]) };
|
||||
if !buffer.is_null() && buffer_size > 0 {
|
||||
let size = buffer_size as usize;
|
||||
let ret_buf = unsafe { std::slice::from_raw_parts_mut(buffer, size) };
|
||||
ret_buf[0..size - 1].copy_from_slice(&bytes[0..size - 1]);
|
||||
ret_buf[size - 1] = 0;
|
||||
}
|
||||
exts.len() as u32 + 1
|
||||
}
|
||||
|
||||
fn UnlockGLSharedTextureForAccess(&self, _glSharedTextureHandle: vr::glSharedTextureHandle_t) {
|
||||
todo!()
|
||||
}
|
||||
fn LockGLSharedTextureForAccess(&self, _glSharedTextureHandle: vr::glSharedTextureHandle_t) {
|
||||
todo!()
|
||||
}
|
||||
fn ReleaseSharedGLTexture(
|
||||
&self,
|
||||
_glTextureId: vr::glUInt_t,
|
||||
_glSharedTextureHandle: vr::glSharedTextureHandle_t,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetMirrorTextureGL(
|
||||
&self,
|
||||
_eEye: vr::EVREye,
|
||||
_pglTextureId: *mut vr::glUInt_t,
|
||||
_pglSharedTextureHandle: *mut vr::glSharedTextureHandle_t,
|
||||
) -> vr::EVRCompositorError {
|
||||
todo!()
|
||||
}
|
||||
fn ReleaseMirrorTextureD3D11(&self, _pD3D11ShaderResourceView: *mut std::ffi::c_void) {
|
||||
todo!()
|
||||
}
|
||||
fn GetMirrorTextureD3D11(
|
||||
&self,
|
||||
_eEye: vr::EVREye,
|
||||
_pD3D11DeviceOrResource: *mut std::ffi::c_void,
|
||||
_ppD3D11ShaderResourceView: *mut *mut std::ffi::c_void,
|
||||
) -> vr::EVRCompositorError {
|
||||
todo!()
|
||||
}
|
||||
fn SuspendRendering(&self, _bSuspend: bool) {
|
||||
crate::warn_unimplemented!("SuspendRendering");
|
||||
}
|
||||
fn ForceReconnectProcess(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn ForceInterleavedReprojectionOn(&self, _: bool) {
|
||||
crate::warn_unimplemented!("ForceInterleavedReprojectionOn");
|
||||
}
|
||||
fn ShouldAppRenderWithLowResources(&self) -> bool {
|
||||
// TODO
|
||||
false
|
||||
}
|
||||
fn CompositorDumpImages(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn IsMirrorWindowVisible(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn HideMirrorWindow(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn ShowMirrorWindow(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn CanRenderScene(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn GetLastFrameRenderer(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetCurrentSceneFocusProcess(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn IsFullscreen(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn CompositorQuit(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn CompositorGoToBack(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn CompositorBringToFront(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn ClearSkyboxOverride(&self) {
|
||||
crate::warn_unimplemented!("ClearSkyboxOverride");
|
||||
}
|
||||
fn SetSkyboxOverride(
|
||||
&self,
|
||||
_pTextures: *const vr::Texture_t,
|
||||
_unTextureCount: u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
crate::warn_unimplemented!("SetSkyboxOverride");
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
fn GetCurrentGridAlpha(&self) -> f32 {
|
||||
0.0
|
||||
}
|
||||
fn FadeGrid(&self, _fSeconds: f32, _bFadeGridIn: bool) {
|
||||
crate::warn_unimplemented!("FadeGrid");
|
||||
}
|
||||
fn GetCurrentFadeColor(&self, _bBackground: bool) -> vr::HmdColor_t {
|
||||
todo!()
|
||||
}
|
||||
fn FadeToColor(
|
||||
&self,
|
||||
_fSeconds: f32,
|
||||
_fRed: f32,
|
||||
_fGreen: f32,
|
||||
_fBlue: f32,
|
||||
_fAlpha: f32,
|
||||
_bBackground: bool,
|
||||
) {
|
||||
crate::warn_unimplemented!("FadeToColor");
|
||||
}
|
||||
fn GetCumulativeStats(
|
||||
&self,
|
||||
_pStats: *mut vr::Compositor_CumulativeStats,
|
||||
_nStatsSizeInBytes: u32,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
fn GetFrameTimeRemaining(&self) -> f32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetFrameTimings(&self, _pTiming: *mut vr::Compositor_FrameTiming, _nFrames: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetFrameTiming(&self, _pTiming: *mut vr::Compositor_FrameTiming, _unFramesAgo: u32) -> bool {
|
||||
crate::warn_unimplemented!("GetFrameTiming");
|
||||
false
|
||||
}
|
||||
fn PostPresentHandoff(&self) {
|
||||
crate::warn_unimplemented!("PostPresentHandoff");
|
||||
}
|
||||
fn ClearLastSubmittedFrame(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn SubmitWithArrayIndex(
|
||||
&self,
|
||||
_eEye: vr::EVREye,
|
||||
_pTexture: *const vr::Texture_t,
|
||||
_unTextureArrayIndex: u32,
|
||||
_pBounds: *const vr::VRTextureBounds_t,
|
||||
_nSubmitFlags: vr::EVRSubmitFlags,
|
||||
) -> vr::EVRCompositorError {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn Submit(
|
||||
&self,
|
||||
eye: vr::EVREye,
|
||||
texture: *const vr::Texture_t,
|
||||
bounds: *const vr::VRTextureBounds_t,
|
||||
submit_flags: vr::EVRSubmitFlags,
|
||||
) -> vr::EVRCompositorError {
|
||||
let bounds = unsafe { bounds.as_ref() }
|
||||
.copied()
|
||||
.unwrap_or(vr::VRTextureBounds_t {
|
||||
uMin: 0.0,
|
||||
vMin: 0.0,
|
||||
uMax: 1.0,
|
||||
vMax: 1.0,
|
||||
});
|
||||
|
||||
// Superhot passes crazy bounds on startup.
|
||||
if ![bounds.uMin, bounds.uMax, bounds.vMin, bounds.vMax]
|
||||
.into_iter()
|
||||
.all(|bound| (0.0..=1.0).contains(&bound))
|
||||
|| bounds.uMin == bounds.uMax
|
||||
|| bounds.vMin == bounds.vMax
|
||||
{
|
||||
return vr::EVRCompositorError::VRCompositorError_InvalidBounds;
|
||||
}
|
||||
|
||||
let Some(texture) = (unsafe { texture.as_ref() }) else {
|
||||
return vr::EVRCompositorError::VRCompositorError_InvalidTexture;
|
||||
};
|
||||
|
||||
let mut session_lock = self.openxr.session_data.get();
|
||||
let mut frame_lock = session_lock.comp_data.0.lock().unwrap();
|
||||
|
||||
let mut ctrl = match frame_lock.as_mut() {
|
||||
Some(ctrl) => ctrl,
|
||||
None => {
|
||||
drop(frame_lock);
|
||||
drop(session_lock);
|
||||
|
||||
info!("Received game texture, restarting session with new data");
|
||||
self.initialize_real_session(texture, bounds);
|
||||
|
||||
session_lock = self.openxr.session_data.get();
|
||||
frame_lock = session_lock.comp_data.0.lock().unwrap();
|
||||
frame_lock.as_mut().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
// No Man's Sky does this.
|
||||
if ctrl.eyes_submitted[eye as usize].is_some() {
|
||||
return vr::EVRCompositorError::VRCompositorError_AlreadySubmitted;
|
||||
}
|
||||
|
||||
ctrl.eyes_submitted[eye as usize] = if ctrl.should_render {
|
||||
// Make sure our image dimensions haven't changed.
|
||||
let backend = &self.backend.get().unwrap();
|
||||
let last_info = self.swapchain_create_info.load();
|
||||
if !backend.is_usable_swapchain(&last_info, texture, bounds) {
|
||||
debug!("recreating swapchain");
|
||||
self.swapchain_create_info
|
||||
.store(backend.get_swapchain_create_info(texture, bounds).into());
|
||||
let FrameController { stream, waiter, .. } = frame_lock.take().unwrap();
|
||||
drop(frame_lock);
|
||||
drop(session_lock);
|
||||
// init_frame_controller eventually calls xrBeginFrame again without calling
|
||||
// xrEndFrame, but this is legal.
|
||||
<Self as openxr_data::Compositor>::init_frame_controller(
|
||||
self,
|
||||
&self.openxr.session_data.get(),
|
||||
waiter,
|
||||
stream,
|
||||
);
|
||||
session_lock = self.openxr.session_data.get();
|
||||
frame_lock = session_lock.comp_data.0.lock().unwrap();
|
||||
ctrl = frame_lock.as_mut().unwrap()
|
||||
}
|
||||
Some(SubmittedEye {
|
||||
extent: self.backend.get().unwrap().copy_texture_to_swapchain(
|
||||
eye,
|
||||
texture,
|
||||
bounds,
|
||||
ctrl.image_index,
|
||||
submit_flags,
|
||||
),
|
||||
flip_vertically: bounds.vMin > bounds.vMax,
|
||||
})
|
||||
} else {
|
||||
Some(Default::default())
|
||||
};
|
||||
|
||||
trace!("submitted {eye:?}");
|
||||
if !ctrl.eyes_submitted.iter().all(|eye| eye.is_some()) {
|
||||
return vr::EVRCompositorError::VRCompositorError_None;
|
||||
}
|
||||
// Both eyes submitted: show our images
|
||||
|
||||
trace!("releasing image");
|
||||
ctrl.swapchain.release_image().unwrap();
|
||||
|
||||
let mut layers = Vec::new();
|
||||
if ctrl.should_render {
|
||||
let (flags, views) = session_lock
|
||||
.session
|
||||
.locate_views(
|
||||
xr::ViewConfigurationType::PRIMARY_STEREO,
|
||||
self.openxr.display_time.get(),
|
||||
&session_lock.tracking_space(),
|
||||
)
|
||||
.expect("Couldn't locate views");
|
||||
|
||||
layers = views
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(eye_index, view)| {
|
||||
let pose = xr::Posef {
|
||||
orientation: if flags.contains(xr::ViewStateFlags::ORIENTATION_VALID) {
|
||||
view.pose.orientation
|
||||
} else {
|
||||
xr::Quaternionf::IDENTITY
|
||||
},
|
||||
position: if flags.contains(xr::ViewStateFlags::POSITION_VALID) {
|
||||
view.pose.position
|
||||
} else {
|
||||
xr::Vector3f::default()
|
||||
},
|
||||
};
|
||||
|
||||
let SubmittedEye {
|
||||
extent,
|
||||
flip_vertically,
|
||||
} = ctrl.eyes_submitted[eye_index].unwrap();
|
||||
let mut fov = view.fov;
|
||||
if flip_vertically {
|
||||
std::mem::swap(&mut fov.angle_up, &mut fov.angle_down);
|
||||
}
|
||||
|
||||
let sub_image = xr::SwapchainSubImage::new()
|
||||
.swapchain(&ctrl.swapchain)
|
||||
.image_array_index(eye_index as u32)
|
||||
.image_rect(xr::Rect2Di {
|
||||
extent,
|
||||
offset: xr::Offset2Di::default(),
|
||||
});
|
||||
|
||||
xr::CompositionLayerProjectionView::new()
|
||||
.fov(fov)
|
||||
.pose(pose)
|
||||
.sub_image(sub_image)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
let mut proj_layer = None;
|
||||
if !layers.is_empty() {
|
||||
proj_layer = Some(
|
||||
xr::CompositionLayerProjection::new()
|
||||
.space(&session_lock.tracking_space())
|
||||
.views(&layers),
|
||||
);
|
||||
}
|
||||
|
||||
let mut layers: Vec<&xr::CompositionLayerBase<_>> = Vec::new();
|
||||
if let Some(l) = proj_layer.as_ref() {
|
||||
layers.push(&l);
|
||||
}
|
||||
|
||||
ctrl.stream
|
||||
.end(
|
||||
self.openxr.display_time.get(),
|
||||
xr::EnvironmentBlendMode::OPAQUE,
|
||||
&layers,
|
||||
)
|
||||
.unwrap();
|
||||
trace!("frame submitted");
|
||||
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
|
||||
fn GetLastPoseForTrackedDeviceIndex(
|
||||
&self,
|
||||
_unDeviceIndex: vr::TrackedDeviceIndex_t,
|
||||
_pOutputPose: *mut vr::TrackedDevicePose_t,
|
||||
_pOutputGamePose: *mut vr::TrackedDevicePose_t,
|
||||
) -> vr::EVRCompositorError {
|
||||
todo!()
|
||||
}
|
||||
fn GetLastPoses(
|
||||
&self,
|
||||
render_pose_array: *mut vr::TrackedDevicePose_t,
|
||||
render_pose_count: u32,
|
||||
game_pose_array: *mut vr::TrackedDevicePose_t,
|
||||
game_pose_count: u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
assert!(render_pose_count > 0);
|
||||
let render_poses = unsafe {
|
||||
std::slice::from_raw_parts_mut(render_pose_array, render_pose_count as usize)
|
||||
};
|
||||
self.input
|
||||
.force(|_| Input::new(self.openxr.clone()))
|
||||
.get_poses(render_poses, None);
|
||||
|
||||
// Not entirely sure how the game poses are supposed to differ from the render poses,
|
||||
// but a lot of games use the game pose array for controller positions.
|
||||
if game_pose_count > 0 {
|
||||
let game_poses = unsafe {
|
||||
std::slice::from_raw_parts_mut(game_pose_array, game_pose_count as usize)
|
||||
};
|
||||
game_poses.copy_from_slice(&render_poses[0..game_poses.len()]);
|
||||
}
|
||||
|
||||
vr::EVRCompositorError::VRCompositorError_None
|
||||
}
|
||||
|
||||
fn WaitGetPoses(
|
||||
&self,
|
||||
render_pose_array: *mut vr::TrackedDevicePose_t,
|
||||
render_pose_count: u32,
|
||||
game_pose_array: *mut vr::TrackedDevicePose_t,
|
||||
game_pose_count: u32,
|
||||
) -> vr::EVRCompositorError {
|
||||
// This should be called every frame - we must regularly poll events
|
||||
self.openxr.poll_events();
|
||||
{
|
||||
let session_data = self.openxr.session_data.get();
|
||||
self.maybe_start_frame(&session_data);
|
||||
}
|
||||
if let Some(input) = self.input.get() {
|
||||
input.frame_start_update();
|
||||
}
|
||||
|
||||
self.GetLastPoses(
|
||||
render_pose_array,
|
||||
render_pose_count,
|
||||
game_pose_array,
|
||||
game_pose_count,
|
||||
)
|
||||
}
|
||||
|
||||
fn GetTrackingSpace(&self) -> vr::ETrackingUniverseOrigin {
|
||||
self.openxr.get_tracking_space()
|
||||
}
|
||||
|
||||
fn SetTrackingSpace(&self, origin: vr::ETrackingUniverseOrigin) {
|
||||
self.openxr.set_tracking_space(origin);
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRCompositor026On027 for Compositor {
|
||||
fn FadeGrid(&self, seconds: f32, fade_in: bool) {
|
||||
<Self as vr::IVRCompositor028_Interface>::FadeGrid(self, seconds, fade_in);
|
||||
}
|
||||
}
|
||||
|
||||
enum GraphicsApi {
|
||||
Vulkan(VulkanData),
|
||||
#[cfg(test)]
|
||||
Fake(tests::FakeGraphicsData),
|
||||
}
|
||||
|
||||
impl GraphicsApi {
|
||||
fn new(texture: &vr::Texture_t) -> Self {
|
||||
match texture.eType {
|
||||
vr::ETextureType::TextureType_Vulkan => {
|
||||
let vk_texture = unsafe { &*(texture.handle as *const vr::VRVulkanTextureData_t) };
|
||||
Self::Vulkan(VulkanData::new(vk_texture))
|
||||
}
|
||||
#[cfg(test)]
|
||||
vr::ETextureType::TextureType_Reserved => {
|
||||
Self::Fake(tests::FakeGraphicsData::new(texture))
|
||||
}
|
||||
other => panic!("Unsupported texture type: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_session_create_info(&self) -> xr::vulkan::SessionCreateInfo {
|
||||
match self {
|
||||
Self::Vulkan(vk) => vk.as_session_create_info(),
|
||||
#[cfg(test)]
|
||||
Self::Fake(f) => f.as_session_create_info(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_swapchain_create_info(
|
||||
&self,
|
||||
texture: &vr::Texture_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
) -> xr::SwapchainCreateInfo<xr::vulkan::Vulkan> {
|
||||
match self {
|
||||
Self::Vulkan(vk) => {
|
||||
assert_eq!(texture.eType, vr::ETextureType::TextureType_Vulkan);
|
||||
let vk_texture = unsafe { &*(texture.handle as *const vr::VRVulkanTextureData_t) };
|
||||
vk.get_swapchain_create_info(vk_texture, bounds, texture.eColorSpace)
|
||||
}
|
||||
#[cfg(test)]
|
||||
Self::Fake(f) => f.check_swapchain(texture, bounds),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_usable_swapchain(
|
||||
&self,
|
||||
create_info: &xr::SwapchainCreateInfo<xr::vulkan::Vulkan>,
|
||||
texture: &vr::Texture_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
) -> bool {
|
||||
let new_info = self.get_swapchain_create_info(texture, bounds);
|
||||
|
||||
create_info.format == new_info.format
|
||||
&& create_info.width == new_info.width
|
||||
&& create_info.height == new_info.height
|
||||
&& create_info.array_size == new_info.array_size
|
||||
&& create_info.sample_count == new_info.sample_count
|
||||
}
|
||||
|
||||
fn post_swapchain_create(&self, images: Vec<vk::Image>) {
|
||||
match self {
|
||||
Self::Vulkan(vk) => vk.post_swapchain_create(images),
|
||||
#[cfg(test)]
|
||||
Self::Fake(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_texture_to_swapchain(
|
||||
&self,
|
||||
eye: vr::EVREye,
|
||||
texture: &vr::Texture_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
image_index: usize,
|
||||
submit_flags: vr::EVRSubmitFlags,
|
||||
) -> xr::Extent2Di {
|
||||
match self {
|
||||
Self::Vulkan(vk) => {
|
||||
assert_eq!(texture.eType, vr::ETextureType::TextureType_Vulkan);
|
||||
let vk_texture = unsafe { &*(texture.handle as *const vr::VRVulkanTextureData_t) };
|
||||
vk.copy_texture_to_swapchain(eye, vk_texture, bounds, image_index, submit_flags)
|
||||
}
|
||||
#[cfg(test)]
|
||||
Self::Fake(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vr::EVRCompositorError::*;
|
||||
use vr::IVRCompositor028_Interface;
|
||||
|
||||
pub struct FakeGraphicsData(Arc<VulkanData>);
|
||||
|
||||
impl FakeGraphicsData {
|
||||
fn texture(data: &Arc<VulkanData>) -> vr::Texture_t {
|
||||
vr::Texture_t {
|
||||
eType: vr::ETextureType::TextureType_Reserved,
|
||||
handle: Arc::into_raw(data.clone()) as _,
|
||||
eColorSpace: vr::EColorSpace::ColorSpace_Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(texture: &vr::Texture_t) -> Self {
|
||||
assert_eq!(texture.eType, vr::ETextureType::TextureType_Reserved);
|
||||
let ptr = texture.handle as *const VulkanData;
|
||||
let vk = unsafe {
|
||||
Arc::increment_strong_count(ptr);
|
||||
Arc::from_raw(ptr)
|
||||
};
|
||||
Self(vk)
|
||||
}
|
||||
|
||||
pub fn check_swapchain(
|
||||
&self,
|
||||
texture: &vr::Texture_t,
|
||||
_bounds: vr::VRTextureBounds_t,
|
||||
) -> xr::SwapchainCreateInfo<xr::vulkan::Vulkan> {
|
||||
assert_eq!(texture.eType, vr::ETextureType::TextureType_Reserved);
|
||||
xr::SwapchainCreateInfo {
|
||||
create_flags: xr::SwapchainCreateFlags::EMPTY,
|
||||
usage_flags: xr::SwapchainUsageFlags::EMPTY,
|
||||
format: 0,
|
||||
sample_count: 1,
|
||||
width: 10,
|
||||
height: 10,
|
||||
face_count: 1,
|
||||
array_size: 2,
|
||||
mip_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_session_create_info(&self) -> xr::vulkan::SessionCreateInfo {
|
||||
self.0.as_session_create_info()
|
||||
}
|
||||
|
||||
pub fn copy(&self, texture: &vr::Texture_t) -> xr::Extent2Di {
|
||||
assert_eq!(texture.eType, vr::ETextureType::TextureType_Reserved);
|
||||
xr::Extent2Di {
|
||||
width: 100,
|
||||
height: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
comp: Arc<Compositor>,
|
||||
vk: Arc<VulkanData>,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let xr = Arc::new(OpenXrData::new(&Injector::default()).unwrap());
|
||||
let vk = Arc::new(VulkanData::new_temporary(&xr.instance, xr.system_id));
|
||||
let comp = Arc::new(Compositor::new(xr.clone(), &Injector::default()));
|
||||
xr.compositor.set(Arc::downgrade(&comp));
|
||||
|
||||
Self { comp, vk }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_bounds() {
|
||||
let Fixture { comp, .. } = Fixture::new();
|
||||
|
||||
let bad_bounds = [
|
||||
// bound greater than 1 (Superhot does this)
|
||||
vr::VRTextureBounds_t {
|
||||
vMin: 0.0,
|
||||
vMax: 1.1,
|
||||
uMin: 0.0,
|
||||
uMax: 1.0,
|
||||
},
|
||||
// bound greater than 1
|
||||
vr::VRTextureBounds_t {
|
||||
vMin: 0.0,
|
||||
vMax: 1.0,
|
||||
uMin: 0.0,
|
||||
uMax: 1.1,
|
||||
},
|
||||
// negative bound
|
||||
vr::VRTextureBounds_t {
|
||||
vMin: -0.1,
|
||||
vMax: 1.0,
|
||||
uMin: 0.0,
|
||||
uMax: 1.0,
|
||||
},
|
||||
// min/max equal
|
||||
vr::VRTextureBounds_t {
|
||||
vMin: 1.0,
|
||||
vMax: 1.0,
|
||||
uMin: 0.0,
|
||||
uMax: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
for bound in bad_bounds {
|
||||
assert_eq!(
|
||||
comp.Submit(
|
||||
vr::EVREye::Eye_Left,
|
||||
std::ptr::null(),
|
||||
&bound,
|
||||
vr::EVRSubmitFlags::Submit_Default
|
||||
),
|
||||
VRCompositorError_InvalidBounds,
|
||||
"Bound didn't return InvalidBounds: {bound:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_flipped_bounds() {
|
||||
let Fixture { comp, .. } = Fixture::new();
|
||||
|
||||
assert_ne!(
|
||||
comp.Submit(
|
||||
vr::EVREye::Eye_Left,
|
||||
std::ptr::null(),
|
||||
&vr::VRTextureBounds_t {
|
||||
uMin: 0.0,
|
||||
vMin: 1.0,
|
||||
uMax: 1.0,
|
||||
vMax: 0.0
|
||||
},
|
||||
vr::EVRSubmitFlags::Submit_Default
|
||||
),
|
||||
VRCompositorError_InvalidBounds
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_on_submit_without_waitgetposes() {
|
||||
let Fixture { comp, vk } = Fixture::new();
|
||||
|
||||
let wait = || {
|
||||
comp.WaitGetPoses(
|
||||
&mut vr::TrackedDevicePose_t::default(),
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
let submit = |eye| {
|
||||
comp.Submit(
|
||||
eye,
|
||||
&FakeGraphicsData::texture(&vk),
|
||||
std::ptr::null(),
|
||||
vr::EVRSubmitFlags::Submit_Default,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(wait(), VRCompositorError_None);
|
||||
assert_eq!(submit(vr::EVREye::Eye_Left), VRCompositorError_None);
|
||||
assert_eq!(submit(vr::EVREye::Eye_Right), VRCompositorError_None);
|
||||
assert_eq!(
|
||||
submit(vr::EVREye::Eye_Left),
|
||||
VRCompositorError_AlreadySubmitted
|
||||
);
|
||||
|
||||
assert_eq!(wait(), VRCompositorError_None);
|
||||
assert_eq!(submit(vr::EVREye::Eye_Left), VRCompositorError_None);
|
||||
}
|
||||
}
|
||||
98
src/convert.rs
Normal file
98
src/convert.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use crate::vr;
|
||||
use glam::{Mat3, Mat4, Quat, Vec3};
|
||||
use openxr as xr;
|
||||
|
||||
pub fn space_relation_to_openvr_pose(
|
||||
location: xr::SpaceLocation,
|
||||
velocity: xr::SpaceVelocity,
|
||||
) -> vr::TrackedDevicePose_t {
|
||||
if !location.location_flags.contains(
|
||||
xr::SpaceLocationFlags::POSITION_VALID | xr::SpaceLocationFlags::ORIENTATION_VALID,
|
||||
) {
|
||||
return vr::TrackedDevicePose_t {
|
||||
bPoseIsValid: false,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
let location = vr::HmdMatrix34_t::from(location.pose);
|
||||
let linear_velo = velocity
|
||||
.velocity_flags
|
||||
.contains(xr::SpaceVelocityFlags::LINEAR_VALID)
|
||||
.then(|| velocity.linear_velocity.into());
|
||||
let angular_velo = velocity
|
||||
.velocity_flags
|
||||
.contains(xr::SpaceVelocityFlags::ANGULAR_VALID)
|
||||
.then(|| velocity.angular_velocity.into());
|
||||
|
||||
vr::TrackedDevicePose_t {
|
||||
mDeviceToAbsoluteTracking: location,
|
||||
vVelocity: linear_velo.unwrap_or_default(),
|
||||
vAngularVelocity: angular_velo.unwrap_or_default(),
|
||||
eTrackingResult: vr::ETrackingResult::TrackingResult_Running_OK,
|
||||
bPoseIsValid: true,
|
||||
bDeviceIsConnected: true,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mat4> for vr::HmdMatrix44_t {
|
||||
fn from(value: Mat4) -> Self {
|
||||
// OpenVR wants data in row major order, so we transpose it
|
||||
Self {
|
||||
m: value.transpose().to_cols_array_2d(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xr::Vector3f> for vr::HmdVector3_t {
|
||||
fn from(value: xr::Vector3f) -> Self {
|
||||
Self {
|
||||
v: [value.x, value.y, value.z],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec3> for vr::HmdVector3_t {
|
||||
fn from(value: Vec3) -> Self {
|
||||
Self {
|
||||
v: value.to_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quat> for vr::HmdQuaternionf_t {
|
||||
fn from(value: Quat) -> Self {
|
||||
Self {
|
||||
x: value.x,
|
||||
y: value.y,
|
||||
z: value.z,
|
||||
w: value.w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xr::Posef> for vr::HmdMatrix34_t {
|
||||
fn from(pose: xr::Posef) -> Self {
|
||||
// openvr matrices are row major, glam matrices are column major
|
||||
|
||||
let rot = Mat3::from_quat(Quat::from_xyzw(
|
||||
pose.orientation.x,
|
||||
pose.orientation.y,
|
||||
pose.orientation.z,
|
||||
pose.orientation.w,
|
||||
))
|
||||
.transpose();
|
||||
|
||||
let gen_array = |translation, rot_axis: Vec3| {
|
||||
std::array::from_fn(|i| if i == 3 { translation } else { rot_axis[i] })
|
||||
};
|
||||
|
||||
Self {
|
||||
m: [
|
||||
gen_array(pose.position.x, rot.x_axis),
|
||||
gen_array(pose.position.y, rot.y_axis),
|
||||
gen_array(pose.position.z, rot.z_axis),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
712
src/input/action_manifest.rs
Normal file
712
src/input/action_manifest.rs
Normal file
|
|
@ -0,0 +1,712 @@
|
|||
use super::Input;
|
||||
use crate::{
|
||||
openxr_data::{self, Hand, SessionData},
|
||||
vr,
|
||||
};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use openxr as xr;
|
||||
use serde::{
|
||||
de::{Error, Unexpected},
|
||||
Deserialize,
|
||||
};
|
||||
use slotmap::SecondaryMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::RwLock;
|
||||
|
||||
impl<C: openxr_data::Compositor> Input<C> {
|
||||
pub(super) fn load_action_manifest(
|
||||
&self,
|
||||
session_data: &SessionData,
|
||||
manifest_path: &Path,
|
||||
) -> Result<(), vr::EVRInputError> {
|
||||
match self.loaded_actions_path.get() {
|
||||
Some(p) => {
|
||||
assert_eq!(p, manifest_path);
|
||||
}
|
||||
None => self
|
||||
.loaded_actions_path
|
||||
.set(manifest_path.to_path_buf())
|
||||
.unwrap(),
|
||||
}
|
||||
|
||||
let data = std::fs::read(manifest_path).map_err(|e| {
|
||||
error!("Failed to read manifest {}: {e}", manifest_path.display());
|
||||
vr::EVRInputError::VRInputError_InvalidParam
|
||||
})?;
|
||||
|
||||
let manifest: ActionManifest = serde_json::from_slice(&data).map_err(|e| {
|
||||
error!("Failed to parse action manifest: {e}");
|
||||
vr::EVRInputError::VRInputError_InvalidParam
|
||||
})?;
|
||||
|
||||
// TODO: support non english localization?
|
||||
let english = manifest
|
||||
.localization
|
||||
.and_then(|l| l.into_iter().find(|l| l.language_tag == "en_US"));
|
||||
|
||||
let sets = load_action_sets(
|
||||
&self.openxr.instance,
|
||||
english.as_ref(),
|
||||
manifest.action_sets,
|
||||
)?;
|
||||
debug!("Loaded {} action sets.", sets.len());
|
||||
|
||||
let actions = load_actions(
|
||||
&session_data.session,
|
||||
english.as_ref(),
|
||||
&sets,
|
||||
manifest.actions,
|
||||
self.openxr.left_hand.path,
|
||||
self.openxr.right_hand.path,
|
||||
)?;
|
||||
debug!("Loaded {} actions.", actions.len());
|
||||
|
||||
// Games can mix legacy and normal input, and the legacy bindings are used for
|
||||
// WaitGetPoses, so attach the legacy set here as well.
|
||||
let legacy = session_data.input_data.legacy_actions.get_or_init(|| {
|
||||
super::LegacyActions::new(
|
||||
&self.openxr.instance,
|
||||
&session_data.session,
|
||||
self.openxr.left_hand.path,
|
||||
self.openxr.right_hand.path,
|
||||
)
|
||||
});
|
||||
|
||||
self.load_bindings(
|
||||
manifest_path.parent().unwrap(),
|
||||
&sets,
|
||||
&actions,
|
||||
manifest.default_bindings,
|
||||
legacy,
|
||||
);
|
||||
|
||||
let xr_sets: Vec<_> = sets.values().chain(std::iter::once(&legacy.set)).collect();
|
||||
session_data.session.attach_action_sets(&xr_sets).unwrap();
|
||||
|
||||
// Transform actions and sets into maps
|
||||
// If the application has already requested the handle for an action/set, we need to
|
||||
// reuse the corresponding slot. Otherwise just create a new one.
|
||||
let mut set_guard = self.set_map.write().unwrap();
|
||||
let sets: SecondaryMap<_, _> = sets
|
||||
.into_iter()
|
||||
.map(|(set_name, set)| {
|
||||
// This function is only called when loading the action manifest, and most games
|
||||
// don't have a ton of actions, so a linear search through the map is probably fine.
|
||||
let key = set_guard
|
||||
.iter()
|
||||
.find_map(|(key, set_path)| (*set_path == set_name).then_some(key))
|
||||
.unwrap_or_else(|| set_guard.insert(set_name));
|
||||
(key, set)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut act_guard = self.action_map.write().unwrap();
|
||||
let actions: SecondaryMap<_, _> = actions
|
||||
.into_iter()
|
||||
.map(|LoadedActionInfo { name, action }| {
|
||||
let key = act_guard
|
||||
.iter()
|
||||
.find_map(|(key, super::Action { path })| (*path == name).then_some(key))
|
||||
.unwrap_or_else(|| act_guard.insert(super::Action { path: name }));
|
||||
|
||||
(key, action)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let loaded = super::LoadedActions { sets, actions };
|
||||
|
||||
match session_data.input_data.loaded_actions.get() {
|
||||
Some(lock) => {
|
||||
*lock.write().unwrap() = loaded;
|
||||
}
|
||||
None => {
|
||||
session_data
|
||||
.input_data
|
||||
.loaded_actions
|
||||
.set(RwLock::new(loaded))
|
||||
.unwrap_or_else(|_| unreachable!());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/ValveSoftware/openvr/wiki/Action-manifest
|
||||
#[derive(Deserialize)]
|
||||
struct ActionManifest {
|
||||
default_bindings: Vec<DefaultBindings>,
|
||||
action_sets: Vec<ActionSetJson>,
|
||||
actions: Vec<ActionJson>,
|
||||
localization: Option<Vec<Localization>>,
|
||||
// localization_files
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DefaultBindings {
|
||||
binding_url: PathBuf,
|
||||
controller_type: ControllerType,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum ControllerType {
|
||||
ViveController,
|
||||
OculusTouch,
|
||||
Knuckles,
|
||||
#[serde(untagged)]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActionSetJson {
|
||||
#[serde(rename = "name")]
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActionJson {
|
||||
#[serde(rename = "name")]
|
||||
path: String,
|
||||
#[serde(rename = "type")]
|
||||
ty: ActionType,
|
||||
#[serde(default, deserialize_with = "parse_skeleton")]
|
||||
skeleton: Option<Hand>,
|
||||
}
|
||||
|
||||
fn parse_skeleton<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<Hand>, D::Error> {
|
||||
let path: &str = Deserialize::deserialize(d)?;
|
||||
let Some(hand) = path.strip_prefix("/skeleton/hand") else {
|
||||
return Err(D::Error::invalid_value(
|
||||
Unexpected::Str(path),
|
||||
&"path starting with /skeleton/hand",
|
||||
));
|
||||
};
|
||||
|
||||
match hand {
|
||||
"/left" => Ok(Some(Hand::Left)),
|
||||
"/right" => Ok(Some(Hand::Right)),
|
||||
_ => Err(D::Error::invalid_value(
|
||||
Unexpected::Str(hand),
|
||||
&r#""/left" or "/right""#,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Localization {
|
||||
language_tag: String,
|
||||
#[serde(flatten)]
|
||||
localized_names: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum ActionType {
|
||||
Boolean,
|
||||
Vector1,
|
||||
Vector2,
|
||||
Vibration,
|
||||
Pose,
|
||||
Skeleton,
|
||||
}
|
||||
|
||||
fn load_action_sets(
|
||||
instance: &xr::Instance,
|
||||
english: Option<&Localization>,
|
||||
sets: Vec<ActionSetJson>,
|
||||
) -> Result<HashMap<String, xr::ActionSet>, vr::EVRInputError> {
|
||||
let mut action_sets = HashMap::new();
|
||||
for ActionSetJson { path } in sets {
|
||||
let localized = english
|
||||
.and_then(|e| e.localized_names.get(&path))
|
||||
.unwrap_or(&path);
|
||||
|
||||
let path = path.to_lowercase();
|
||||
// OpenXR does not like the "/actions/<set name>" format, so we need to strip the prefix
|
||||
let Some(xr_friendly_name) = path.strip_prefix("/actions/") else {
|
||||
error!("Action set {path} missing actions prefix.");
|
||||
return Err(vr::EVRInputError::VRInputError_InvalidParam);
|
||||
};
|
||||
|
||||
trace!("Creating action set {xr_friendly_name} ({path:?}) (localized: {localized})");
|
||||
let set = instance
|
||||
.create_action_set(&xr_friendly_name, localized, 0)
|
||||
.map_err(|e| {
|
||||
error!("Failed to create action set: {e}");
|
||||
vr::EVRInputError::VRInputError_InvalidParam
|
||||
})?;
|
||||
|
||||
action_sets.insert(path, set);
|
||||
}
|
||||
Ok(action_sets)
|
||||
}
|
||||
|
||||
struct LoadedActionInfo {
|
||||
name: String,
|
||||
action: super::InputAction,
|
||||
}
|
||||
|
||||
fn load_actions(
|
||||
session: &xr::Session<xr::vulkan::Vulkan>,
|
||||
english: Option<&Localization>,
|
||||
sets: &HashMap<String, xr::ActionSet>,
|
||||
actions: Vec<ActionJson>,
|
||||
left_hand: xr::Path,
|
||||
right_hand: xr::Path,
|
||||
) -> Result<Vec<LoadedActionInfo>, vr::EVRInputError> {
|
||||
let mut ret = Vec::new();
|
||||
for ActionJson { path, ty, skeleton } in actions {
|
||||
let localized = english
|
||||
.and_then(|e| e.localized_names.get(&path))
|
||||
.map(|s| s.as_str());
|
||||
|
||||
let path = path.to_lowercase();
|
||||
let set_end_idx = path.match_indices('/').nth(2).unwrap().0;
|
||||
let set_name = &path[0..set_end_idx];
|
||||
let xr_friendly_name = path.rsplit_once('/').unwrap().1;
|
||||
let localized = localized.unwrap_or(xr_friendly_name);
|
||||
|
||||
trace!("Creating action {xr_friendly_name} (localized: {localized}) in set {set_name:?}");
|
||||
let set = &sets[set_name];
|
||||
use super::InputAction::*;
|
||||
|
||||
fn create_action<T: xr::ActionTy>(
|
||||
set: &xr::ActionSet,
|
||||
name: &str,
|
||||
localized: &str,
|
||||
paths: &[xr::Path],
|
||||
) -> xr::Result<xr::Action<T>> {
|
||||
set.create_action(name, localized, paths).or_else(|err| {
|
||||
// If we get a duplicated localized name, just unduplicate it and try again
|
||||
if err == xr::sys::Result::ERROR_LOCALIZED_NAME_DUPLICATED {
|
||||
let localized = format!("{localized} (copy)");
|
||||
create_action(set, name, &localized, paths)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let paths = &[left_hand, right_hand];
|
||||
let action = match ty {
|
||||
ActionType::Boolean => {
|
||||
Bool(create_action::<bool>(&set, &xr_friendly_name, localized, paths).unwrap())
|
||||
}
|
||||
ActionType::Vector1 => Float {
|
||||
action: create_action::<f32>(&set, &xr_friendly_name, localized, paths).unwrap(),
|
||||
last_value: super::AtomicF32::new(0.0),
|
||||
},
|
||||
ActionType::Vector2 => Vector2(
|
||||
create_action::<xr::Vector2f>(&set, &xr_friendly_name, localized, paths).unwrap(),
|
||||
),
|
||||
ActionType::Pose => {
|
||||
let action =
|
||||
create_action::<xr::Posef>(&set, &xr_friendly_name, localized, paths).unwrap();
|
||||
let left_space = action
|
||||
.create_space(session, left_hand, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
let right_space = action
|
||||
.create_space(session, right_hand, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
Pose {
|
||||
action,
|
||||
left_space,
|
||||
right_space,
|
||||
}
|
||||
}
|
||||
ActionType::Skeleton => {
|
||||
let hand = skeleton.unwrap();
|
||||
let action =
|
||||
create_action::<xr::Posef>(&set, &xr_friendly_name, localized, paths).unwrap();
|
||||
let space = action
|
||||
.create_space(
|
||||
session,
|
||||
match hand {
|
||||
Hand::Left => left_hand,
|
||||
Hand::Right => right_hand,
|
||||
},
|
||||
xr::Posef::IDENTITY,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Skeleton {
|
||||
action,
|
||||
space,
|
||||
hand,
|
||||
}
|
||||
}
|
||||
ActionType::Vibration => continue, // TODO
|
||||
};
|
||||
ret.push(LoadedActionInfo { name: path, action });
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Bindings {
|
||||
bindings: HashMap<String, ActionSetBinding>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActionSetBinding {
|
||||
sources: Vec<ActionBinding>,
|
||||
poses: Option<Vec<SimpleActionBinding>>,
|
||||
haptics: Option<Vec<SimpleActionBinding>>,
|
||||
skeleton: Option<Vec<SkeletonActionBinding>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SimpleActionBinding {
|
||||
output: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SkeletonActionBinding {
|
||||
output: String,
|
||||
#[serde(deserialize_with = "path_to_skeleton")]
|
||||
path: Hand,
|
||||
}
|
||||
|
||||
fn path_to_skeleton<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Hand, D::Error> {
|
||||
let path: &str = Deserialize::deserialize(d)?;
|
||||
match path {
|
||||
"/user/hand/left/input/skeleton/left" => Ok(Hand::Left),
|
||||
"/user/hand/right/input/skeleton/right" => Ok(Hand::Right),
|
||||
other => Err(D::Error::invalid_value(
|
||||
Unexpected::Str(other),
|
||||
&"left or right hand skeleton path",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
//#[derive(Deserialize)]
|
||||
//struct PoseActionBinding {
|
||||
// output: String,
|
||||
// #[serde(deserialize_with = "path_to_pose")]
|
||||
// path: (String, Hand, PoseType),
|
||||
//}
|
||||
//
|
||||
//#[derive(Deserialize)]
|
||||
//enum PoseType {
|
||||
// Raw,
|
||||
//}
|
||||
//
|
||||
//fn path_to_pose<'de, D: serde::Deserializer<'de>>(
|
||||
// d: D,
|
||||
//) -> Result<(String, Hand, PoseType), D::Error> {
|
||||
// let path: String = Deserialize::deserialize(d)?;
|
||||
// // find 3rd slash
|
||||
// let (idx, _) = path
|
||||
// .match_indices('/')
|
||||
// .nth(3)
|
||||
// .ok_or_else(|| D::Error::invalid_value(Unexpected::Str(&path), &"at least 4 slashes"))?;
|
||||
//
|
||||
// let hand = match &path[0..idx] {
|
||||
// "/user/hand/left" => Ok(Hand::Left),
|
||||
// "/user/hand/right" => Ok(Hand::Right),
|
||||
// unknown => Err(D::Error::invalid_value(
|
||||
// Unexpected::Str(unknown),
|
||||
// &"/user/hand/left/ or /user/hand/right/",
|
||||
// )),
|
||||
// }?;
|
||||
//
|
||||
// let ty = match &path[idx + 1..] {
|
||||
// "pose/raw" => Ok(PoseType::Raw),
|
||||
// other => Err(D::Error::invalid_value(
|
||||
// Unexpected::Str(other),
|
||||
// &"a known pose path",
|
||||
// )),
|
||||
// }?;
|
||||
//
|
||||
// Ok((path, hand, ty))
|
||||
//}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ActionBinding {
|
||||
mode: ActionMode,
|
||||
path: String,
|
||||
inputs: ActionInput,
|
||||
parameters: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum ActionMode {
|
||||
Dpad,
|
||||
Button,
|
||||
Trigger,
|
||||
None,
|
||||
#[serde(untagged)]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum ActionInput {
|
||||
Button {
|
||||
click: ActionBindingInput,
|
||||
},
|
||||
Trigger {
|
||||
pull: ActionBindingInput,
|
||||
},
|
||||
Vector2 {
|
||||
position: ActionBindingInput,
|
||||
},
|
||||
Dpad {
|
||||
east: Option<ActionBindingInput>,
|
||||
south: Option<ActionBindingInput>,
|
||||
north: ActionBindingInput, // Assuming a dpad binding has at least the north component,
|
||||
// potentially a dangerous assumption!
|
||||
west: Option<ActionBindingInput>,
|
||||
center: Option<ActionBindingInput>,
|
||||
},
|
||||
Unknown(serde_json::Value),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ActionBindingInput {
|
||||
output: String,
|
||||
}
|
||||
|
||||
/// Call a generic function with each supported interaction profile.
|
||||
/// All supported interaction profiles should be added here.
|
||||
macro_rules! for_each_profile {
|
||||
($fn:ident($($arg:expr),*)) => {{
|
||||
$fn::<crate::input::vive_controller::ViveWands>($($arg),*);
|
||||
$fn::<crate::input::simple_controller::SimpleController>($($arg),*);
|
||||
}};
|
||||
($pfx:ident.$fn:ident($($arg:expr),*$(,)?)) => {{
|
||||
$pfx.$fn::<crate::input::vive_controller::ViveWands>($($arg),*);
|
||||
$pfx.$fn::<crate::input::simple_controller::SimpleController>($($arg),*);
|
||||
}}
|
||||
}
|
||||
pub(super) use for_each_profile;
|
||||
|
||||
impl<C: openxr_data::Compositor> Input<C> {
|
||||
fn load_bindings<'a>(
|
||||
&self,
|
||||
parent_path: &Path,
|
||||
action_sets: &HashMap<String, xr::ActionSet>,
|
||||
actions: &[LoadedActionInfo],
|
||||
bindings: Vec<DefaultBindings>,
|
||||
legacy_actions: &super::LegacyActions,
|
||||
) {
|
||||
if let Some(DefaultBindings {
|
||||
binding_url,
|
||||
controller_type,
|
||||
}) = bindings
|
||||
.iter()
|
||||
.find(|b| b.controller_type == ControllerType::ViveController)
|
||||
{
|
||||
let bindings_path = parent_path.join(binding_url);
|
||||
debug!(
|
||||
"Reading bindings for {controller_type:?} (at {})",
|
||||
bindings_path.display()
|
||||
);
|
||||
|
||||
let Ok(data) = std::fs::read(bindings_path)
|
||||
.inspect_err(|e| error!("Couldn't load bindings for {controller_type:?}: {e}"))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(Bindings { bindings }) = serde_json::from_slice(&data)
|
||||
.inspect_err(|e| error!("Failed to parse bindings for {controller_type:?}: {e}"))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
for_each_profile! {
|
||||
self.load_bindings_for_profile(
|
||||
action_sets,
|
||||
actions,
|
||||
legacy_actions,
|
||||
&bindings,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_bindings_for_profile<P: InteractionProfile>(
|
||||
&self,
|
||||
action_sets: &HashMap<String, xr::ActionSet>,
|
||||
actions: &[LoadedActionInfo],
|
||||
legacy_actions: &super::LegacyActions,
|
||||
bindings: &HashMap<String, ActionSetBinding>,
|
||||
) {
|
||||
use super::InputAction::*;
|
||||
|
||||
info!("loading bindings for {}", P::PROFILE_PATH);
|
||||
let legal_paths = P::legal_paths();
|
||||
let translate_map = P::TRANSLATE_MAP;
|
||||
let path_translator = |path: &str| {
|
||||
let mut translated = path.to_string();
|
||||
for PathTranslation { from, to } in translate_map {
|
||||
if translated.find(from).is_some() {
|
||||
translated = translated.replace(from, to);
|
||||
break;
|
||||
}
|
||||
}
|
||||
trace!("translated {path} to {translated}");
|
||||
if !legal_paths.contains(&translated) {
|
||||
warn!("Action for invalid path {translated}, ignoring");
|
||||
None
|
||||
} else {
|
||||
Some(translated)
|
||||
}
|
||||
};
|
||||
|
||||
macro_rules! find_action {
|
||||
($name:expr) => {{
|
||||
let Some(action) = actions.iter().find(|a| a.name == *$name) else {
|
||||
warn!("Couldn't find action {}, skipping", $name);
|
||||
continue;
|
||||
};
|
||||
action
|
||||
}};
|
||||
}
|
||||
|
||||
let mut xr_bindings = Vec::new();
|
||||
for (action_set_name, bindings) in bindings.into_iter() {
|
||||
if !action_sets.contains_key(action_set_name) {
|
||||
warn!("Action set {action_set_name} missing.");
|
||||
continue;
|
||||
}
|
||||
|
||||
for SimpleActionBinding { output, path } in bindings.poses.iter().flat_map(|p| p.iter())
|
||||
{
|
||||
let Some(translated) = path_translator(&path) else {
|
||||
continue;
|
||||
};
|
||||
let xr_path = self.openxr.instance.string_to_path(&translated).unwrap();
|
||||
|
||||
let action = find_action!(output);
|
||||
let binding = match &action.action {
|
||||
Pose { action, .. } => xr::Binding::new(action, xr_path),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
xr_bindings.push(binding);
|
||||
}
|
||||
|
||||
for SkeletonActionBinding { output, path } in
|
||||
bindings.skeleton.iter().flat_map(|s| s.iter())
|
||||
{
|
||||
let path = match path {
|
||||
Hand::Left => {
|
||||
trace!("suggested left grip for {output}");
|
||||
self.openxr
|
||||
.instance
|
||||
.string_to_path("/user/hand/left/input/grip/pose")
|
||||
.unwrap()
|
||||
}
|
||||
Hand::Right => {
|
||||
trace!("suggested right grip for {output}");
|
||||
self.openxr
|
||||
.instance
|
||||
.string_to_path("/user/hand/right/input/grip/pose")
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
let action = find_action!(output);
|
||||
let binding = match &action.action {
|
||||
Skeleton { action, .. } => xr::Binding::new(action, path),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
xr_bindings.push(binding);
|
||||
}
|
||||
|
||||
for ActionBinding {
|
||||
mode,
|
||||
path,
|
||||
inputs,
|
||||
parameters,
|
||||
} in &bindings.sources
|
||||
{
|
||||
if matches!(mode, ActionMode::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(translated) = path_translator(&path) else {
|
||||
continue;
|
||||
};
|
||||
let xr_path = || self.openxr.instance.string_to_path(&translated).unwrap();
|
||||
|
||||
match mode {
|
||||
ActionMode::None => unreachable!(),
|
||||
ActionMode::Button => {
|
||||
let ActionInput::Button { click } = inputs else {
|
||||
error!(
|
||||
"Input for button action for {path} in {action_set_name} is wrong."
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let action = find_action!(click.output);
|
||||
let binding = match &action.action {
|
||||
Bool(b) => xr::Binding::new(b, xr_path()),
|
||||
Float { action, .. } => xr::Binding::new(action, xr_path()),
|
||||
_ => {
|
||||
panic!("Action for {} should be a bool or float", click.output)
|
||||
}
|
||||
};
|
||||
trace!("suggesting {translated} for {}", click.output);
|
||||
xr_bindings.push(binding);
|
||||
}
|
||||
ActionMode::Trigger => {
|
||||
let output = match inputs {
|
||||
ActionInput::Button { click } => &click.output,
|
||||
ActionInput::Trigger { pull } => &pull.output,
|
||||
other => {
|
||||
error!("Expected button or trigger path for trigger action for {path} in {action_set_name}, got: {other:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let action = find_action!(output);
|
||||
let binding = match &action.action {
|
||||
Bool(b) => xr::Binding::new(b, xr_path()),
|
||||
Float { action, .. } => xr::Binding::new(action, xr_path()),
|
||||
_ => panic!("Trigger action should be a bool or float"),
|
||||
};
|
||||
trace!("suggesting {translated} for {}", output);
|
||||
xr_bindings.push(binding);
|
||||
}
|
||||
ActionMode::Dpad => {}
|
||||
other => {
|
||||
warn!("unhandled action mode: {other:?}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stp = |s| self.openxr.instance.string_to_path(s).unwrap();
|
||||
let legacy_bindings = P::legacy_bindings(stp, &legacy_actions);
|
||||
let path = stp(P::PROFILE_PATH);
|
||||
xr_bindings.extend_from_slice(&legacy_bindings);
|
||||
self.openxr
|
||||
.instance
|
||||
.suggest_interaction_profile_bindings(path, &xr_bindings)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct PathTranslation {
|
||||
pub from: &'static str,
|
||||
pub to: &'static str,
|
||||
}
|
||||
|
||||
pub(super) trait InteractionProfile {
|
||||
const PROFILE_PATH: &'static str;
|
||||
const TRANSLATE_MAP: &'static [PathTranslation];
|
||||
|
||||
fn legal_paths() -> Box<[String]>;
|
||||
fn legacy_bindings<'a>(
|
||||
string_to_path: impl Fn(&'a str) -> xr::Path,
|
||||
actions: &super::LegacyActions,
|
||||
) -> Vec<xr::Binding>;
|
||||
}
|
||||
1061
src/input/mod.rs
Normal file
1061
src/input/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
70
src/input/simple_controller.rs
Normal file
70
src/input/simple_controller.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use super::{
|
||||
action_manifest::{InteractionProfile, PathTranslation},
|
||||
LegacyActions,
|
||||
};
|
||||
use openxr as xr;
|
||||
|
||||
pub struct SimpleController;
|
||||
|
||||
impl InteractionProfile for SimpleController {
|
||||
const PROFILE_PATH: &'static str = "/interaction_profiles/khr/simple_controller";
|
||||
const TRANSLATE_MAP: &'static [PathTranslation] = &[
|
||||
PathTranslation {
|
||||
from: "pose/raw",
|
||||
to: "input/grip/pose",
|
||||
},
|
||||
PathTranslation {
|
||||
from: "trigger",
|
||||
to: "select/click",
|
||||
},
|
||||
PathTranslation {
|
||||
from: "application_menu",
|
||||
to: "menu/click",
|
||||
},
|
||||
];
|
||||
|
||||
fn legacy_bindings<'a>(
|
||||
string_to_path: impl Fn(&'a str) -> openxr::Path,
|
||||
actions: &LegacyActions,
|
||||
) -> Vec<openxr::Binding> {
|
||||
let mut ret = Vec::new();
|
||||
let stp = string_to_path;
|
||||
macro_rules! both {
|
||||
($action:expr, $path:expr) => {
|
||||
ret.push(xr::Binding::new(
|
||||
$action,
|
||||
stp(concat!("/user/hand/left/", $path)),
|
||||
));
|
||||
ret.push(xr::Binding::new(
|
||||
$action,
|
||||
stp(concat!("/user/hand/right/", $path)),
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
both!(&actions.pose, "input/grip/pose");
|
||||
both!(&actions.trigger, "input/select/click");
|
||||
both!(&actions.trigger_click, "input/select/click");
|
||||
both!(&actions.app_menu, "input/menu/click");
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn legal_paths() -> Box<[String]> {
|
||||
[
|
||||
"input/select/click",
|
||||
"input/menu/click",
|
||||
"input/grip/pose",
|
||||
"input/aim/pose",
|
||||
"output/haptic",
|
||||
]
|
||||
.iter()
|
||||
.flat_map(|s| {
|
||||
[
|
||||
format!("/user/hand/left/{s}"),
|
||||
format!("/user/hand/right/{s}"),
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
332
src/input/tests.rs
Normal file
332
src/input/tests.rs
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
use super::{Input, InputAction};
|
||||
use crate::{
|
||||
openxr_data::OpenXrData,
|
||||
vr::{self, IVRInput010_Interface},
|
||||
};
|
||||
use openxr as xr;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Arc;
|
||||
use vr::EVRInputError::*;
|
||||
|
||||
static ACTIONS_JSON: &'static CStr = unsafe {
|
||||
CStr::from_bytes_with_nul_unchecked(
|
||||
concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/input_data/actions.json\0"
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
};
|
||||
|
||||
impl std::fmt::Debug for InputAction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InputAction::Bool(_) => f.write_str("InputAction::Bool"),
|
||||
InputAction::Float { .. } => f.write_str("InputAction::Float"),
|
||||
InputAction::Vector2(_) => f.write_str("InputAction::Vector2"),
|
||||
InputAction::Pose { .. } => f.write_str("InputAction::Pose"),
|
||||
InputAction::Skeleton { .. } => f.write_str("InputAction::Skeleton"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FakeCompositor(crate::vulkan::VulkanData);
|
||||
impl crate::InterfaceImpl for FakeCompositor {
|
||||
fn get_version(_: &CStr) -> Option<Box<dyn FnOnce(&Arc<Self>) -> *mut std::ffi::c_void>> {
|
||||
None
|
||||
}
|
||||
fn supported_versions() -> &'static [&'static CStr] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
impl crate::openxr_data::Compositor for FakeCompositor {
|
||||
fn current_session_create_info(&self) -> openxr::vulkan::SessionCreateInfo {
|
||||
self.0.as_session_create_info()
|
||||
}
|
||||
fn init_frame_controller(
|
||||
&self,
|
||||
_: &crate::openxr_data::SessionData,
|
||||
_: openxr::FrameWaiter,
|
||||
_: openxr::FrameStream<openxr::vulkan::Vulkan>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
input: Arc<Input<FakeCompositor>>,
|
||||
_comp: Arc<FakeCompositor>,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
crate::init_logging();
|
||||
let xr = Arc::new(OpenXrData::new(&crate::clientcore::Injector::default()).unwrap());
|
||||
let comp = Arc::new(FakeCompositor(crate::vulkan::VulkanData::new_temporary(
|
||||
&xr.instance,
|
||||
xr.system_id,
|
||||
)));
|
||||
xr.compositor.set(Arc::downgrade(&comp));
|
||||
let ret = Self {
|
||||
input: Input::new(xr.clone()).into(),
|
||||
_comp: comp,
|
||||
};
|
||||
xr.input.set(Arc::downgrade(&ret.input));
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn get_action_handle(&self, name: &CStr) -> vr::VRActionHandle_t {
|
||||
let mut handle = 0;
|
||||
assert_eq!(
|
||||
self.input.GetActionHandle(name.as_ptr(), &mut handle),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_ne!(handle, 0);
|
||||
handle
|
||||
}
|
||||
|
||||
fn get_action_set_handle(&self, name: &CStr) -> vr::VRActionSetHandle_t {
|
||||
let mut handle = 0;
|
||||
assert_eq!(
|
||||
self.input.GetActionSetHandle(name.as_ptr(), &mut handle),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_ne!(handle, 0);
|
||||
handle
|
||||
}
|
||||
|
||||
fn load_actions(&self) {
|
||||
assert_eq!(
|
||||
self.input.SetActionManifestPath(ACTIONS_JSON.as_ptr()),
|
||||
VRInputError_None
|
||||
);
|
||||
}
|
||||
|
||||
fn sync(&self, mut active: vr::VRActiveActionSet_t) {
|
||||
assert_eq!(
|
||||
self.input.UpdateActionState(
|
||||
&mut active,
|
||||
std::mem::size_of::<vr::VRActiveActionSet_t>() as u32,
|
||||
1
|
||||
),
|
||||
VRInputError_None
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn get_bool_action(&self, handle: vr::VRActionHandle_t) -> xr::sys::Action {
|
||||
let data = self.input.openxr.session_data.get();
|
||||
let actions = data
|
||||
.input_data
|
||||
.get_loaded_actions()
|
||||
.expect("Actions aren't loaded");
|
||||
let action = match actions
|
||||
.try_get_action(handle)
|
||||
.expect("Couldn't find action for handle")
|
||||
{
|
||||
InputAction::Bool(a) => a,
|
||||
other => panic!("Expected boolean action, got {other:?}"),
|
||||
};
|
||||
action.as_raw()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_legacy_input_before_session_setup() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
let got_input = fixture.input.get_legacy_controller_state(
|
||||
1,
|
||||
&mut vr::VRControllerState_t::default(),
|
||||
std::mem::size_of::<vr::VRControllerState_t>() as _,
|
||||
);
|
||||
assert!(!got_input);
|
||||
|
||||
fixture.input.frame_start_update();
|
||||
let got_input = fixture.input.get_legacy_controller_state(
|
||||
1,
|
||||
&mut vr::VRControllerState_t::default(),
|
||||
std::mem::size_of::<vr::VRControllerState_t>() as _,
|
||||
);
|
||||
assert!(!got_input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_input() {
|
||||
let f = Fixture::new();
|
||||
f.input.openxr.restart_session();
|
||||
f.input.frame_start_update();
|
||||
let mut state = vr::VRControllerState_t::default();
|
||||
let got_input = f.input.get_legacy_controller_state(
|
||||
1,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::VRControllerState_t>() as _,
|
||||
);
|
||||
assert!(got_input);
|
||||
let last_packet_num = { state.unPacketNum };
|
||||
// The braces around state.ulButtonPressed are to force create a copy, because
|
||||
// VRControllerState_t is a packed struct and references to unaligned fields are undefined.
|
||||
assert_eq!(
|
||||
{ state.ulButtonPressed },
|
||||
0,
|
||||
"Trigger should be inactive ({:b})",
|
||||
{ state.ulButtonPressed }
|
||||
);
|
||||
|
||||
{
|
||||
fakexr::set_action_state(
|
||||
f.input
|
||||
.openxr
|
||||
.session_data
|
||||
.get()
|
||||
.input_data
|
||||
.legacy_actions
|
||||
.get()
|
||||
.unwrap()
|
||||
.trigger_click
|
||||
.as_raw(),
|
||||
fakexr::ActionState::Bool(true),
|
||||
);
|
||||
}
|
||||
let got_input = f.input.get_legacy_controller_state(
|
||||
1,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::VRControllerState_t>() as _,
|
||||
);
|
||||
assert!(got_input);
|
||||
assert_eq!({ state.unPacketNum }, last_packet_num);
|
||||
assert_eq!(
|
||||
{ state.ulButtonPressed },
|
||||
0,
|
||||
"Trigger should be inactive ({:b})",
|
||||
{ state.ulButtonPressed }
|
||||
);
|
||||
|
||||
f.input.frame_start_update();
|
||||
let got_input = f.input.get_legacy_controller_state(
|
||||
1,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::VRControllerState_t>() as _,
|
||||
);
|
||||
assert!(got_input);
|
||||
assert_ne!({ state.unPacketNum }, last_packet_num);
|
||||
let state = { state.ulButtonPressed };
|
||||
let expected = 1 << vr::EVRButtonId::k_EButton_SteamVR_Trigger as u64;
|
||||
assert_eq!(
|
||||
state, expected,
|
||||
"Trigger should be active (got {state:b}, expected {expected:b})",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_handles() {
|
||||
let f = Fixture::new();
|
||||
f.load_actions();
|
||||
|
||||
let handle = f.get_action_handle(c"/actions/set1/in/fakeaction");
|
||||
let mut state = Default::default();
|
||||
assert_ne!(
|
||||
f.input.GetDigitalActionData(
|
||||
handle,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::InputDigitalActionData_t>() as u32,
|
||||
0
|
||||
),
|
||||
VRInputError_None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_dont_change_after_load() {
|
||||
let f = Fixture::new();
|
||||
|
||||
let set1 = f.get_action_set_handle(c"/actions/set1");
|
||||
let boolact = f.get_action_handle(c"/actions/set1/in/boolact");
|
||||
|
||||
f.load_actions();
|
||||
|
||||
let set_load = f.get_action_set_handle(c"/actions/set1");
|
||||
assert_eq!(set_load, set1);
|
||||
let act_load = f.get_action_handle(c"/actions/set1/in/boolact");
|
||||
assert_eq!(act_load, boolact);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_state_flow() {
|
||||
let f = Fixture::new();
|
||||
|
||||
let set1 = f.get_action_set_handle(c"/actions/set1");
|
||||
let boolact = f.get_action_handle(c"/actions/set1/in/boolact");
|
||||
|
||||
f.load_actions();
|
||||
f.sync(vr::VRActiveActionSet_t {
|
||||
ulActionSet: set1,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut state = vr::InputDigitalActionData_t::default();
|
||||
assert_eq!(
|
||||
f.input.GetDigitalActionData(
|
||||
boolact,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::InputDigitalActionData_t>() as u32,
|
||||
0
|
||||
),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_eq!(state.bState, false);
|
||||
|
||||
fakexr::set_action_state(f.get_bool_action(boolact), fakexr::ActionState::Bool(true));
|
||||
|
||||
assert_eq!(
|
||||
f.input.GetDigitalActionData(
|
||||
boolact,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::InputDigitalActionData_t>() as u32,
|
||||
0
|
||||
),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_eq!(state.bState, false);
|
||||
|
||||
f.sync(vr::VRActiveActionSet_t {
|
||||
ulActionSet: set1,
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(
|
||||
f.input.GetDigitalActionData(
|
||||
boolact,
|
||||
&mut state,
|
||||
std::mem::size_of::<vr::InputDigitalActionData_t>() as u32,
|
||||
0
|
||||
),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_eq!(state.bState, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_manifest_on_session_restart() {
|
||||
let f = Fixture::new();
|
||||
|
||||
let set1 = f.get_action_set_handle(c"/actions/set1");
|
||||
let boolact = f.get_action_handle(c"/actions/set1/in/boolact");
|
||||
|
||||
f.load_actions();
|
||||
f.input.openxr.restart_session();
|
||||
|
||||
fakexr::set_action_state(f.get_bool_action(boolact), fakexr::ActionState::Bool(true));
|
||||
f.sync(vr::VRActiveActionSet_t {
|
||||
ulActionSet: set1,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut state = Default::default();
|
||||
assert_eq!(
|
||||
f.input
|
||||
.GetDigitalActionData(boolact, &mut state, std::mem::size_of_val(&state) as u32, 0),
|
||||
VRInputError_None
|
||||
);
|
||||
assert_eq!(state.bState, true);
|
||||
assert_eq!(state.bActive, true);
|
||||
}
|
||||
80
src/input/vive_controller.rs
Normal file
80
src/input/vive_controller.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use super::{
|
||||
action_manifest::{InteractionProfile, PathTranslation},
|
||||
LegacyActions,
|
||||
};
|
||||
use openxr as xr;
|
||||
|
||||
pub struct ViveWands;
|
||||
|
||||
impl InteractionProfile for ViveWands {
|
||||
const PROFILE_PATH: &'static str = "/interaction_profiles/htc/vive_controller";
|
||||
const TRANSLATE_MAP: &'static [PathTranslation] = &[
|
||||
PathTranslation {
|
||||
from: "pose/raw",
|
||||
to: "input/grip/pose",
|
||||
},
|
||||
PathTranslation {
|
||||
from: "input/grip",
|
||||
to: "input/squeeze/click",
|
||||
},
|
||||
PathTranslation {
|
||||
from: "trigger",
|
||||
to: "trigger/value",
|
||||
},
|
||||
PathTranslation {
|
||||
from: "application_menu",
|
||||
to: "menu/click",
|
||||
},
|
||||
];
|
||||
|
||||
fn legal_paths() -> Box<[String]> {
|
||||
[
|
||||
"input/squeeze/click",
|
||||
"input/menu/click",
|
||||
"input/trigger/click",
|
||||
"input/trigger/value",
|
||||
"input/trackpad",
|
||||
"input/trackpad/x",
|
||||
"input/trackpad/y",
|
||||
"input/trackpad/click",
|
||||
"input/trackpad/touch",
|
||||
"input/grip/pose",
|
||||
"input/aim/pose",
|
||||
"output/haptic",
|
||||
]
|
||||
.iter()
|
||||
.flat_map(|s| {
|
||||
[
|
||||
format!("/user/hand/left/{s}"),
|
||||
format!("/user/hand/right/{s}"),
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn legacy_bindings<'a>(
|
||||
stp: impl Fn(&'a str) -> openxr::Path,
|
||||
actions: &LegacyActions,
|
||||
) -> Vec<openxr::Binding> {
|
||||
let mut ret = Vec::new();
|
||||
macro_rules! both {
|
||||
($action:expr, $path:expr) => {
|
||||
ret.push(xr::Binding::new(
|
||||
$action,
|
||||
stp(concat!("/user/hand/left/", $path)),
|
||||
));
|
||||
ret.push(xr::Binding::new(
|
||||
$action,
|
||||
stp(concat!("/user/hand/right/", $path)),
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
both!(&actions.pose, "input/grip/pose");
|
||||
both!(&actions.trigger, "input/trigger/value");
|
||||
both!(&actions.trigger_click, "input/trigger/click");
|
||||
both!(&actions.app_menu, "input/menu/click");
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
168
src/lib.rs
Normal file
168
src/lib.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
mod applications;
|
||||
mod chaperone;
|
||||
mod clientcore;
|
||||
mod compositor;
|
||||
mod convert;
|
||||
mod input;
|
||||
mod misc_unknown;
|
||||
mod openxr_data;
|
||||
mod overlay;
|
||||
mod overlayview;
|
||||
mod rendermodels;
|
||||
mod screenshots;
|
||||
mod system;
|
||||
mod vulkan;
|
||||
|
||||
use clientcore::ClientCore;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
use bindings::vr;
|
||||
|
||||
/// Types that are interfaces. Should only be implemented by generated code.
|
||||
unsafe trait OpenVrInterface: 'static {
|
||||
type Vtable: Sync;
|
||||
}
|
||||
|
||||
/// Trait for inheriting from an interface. Implemented by generated code.
|
||||
/// The thread safety/usage patterns of OpenVR interfaces is not clear, so we err on the safe side and require
|
||||
/// inheritors to be Sync.
|
||||
unsafe trait Inherits<T: OpenVrInterface>: Sync
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn new_wrapped(wrapped: &Arc<Self>) -> VtableWrapper<T, Self>;
|
||||
fn init_fntable(init: &Arc<Self>) -> *mut c_void;
|
||||
}
|
||||
|
||||
/// A wrapper around a vtable, to safely pass across FFI.
|
||||
#[repr(C)]
|
||||
struct VtableWrapper<T: OpenVrInterface, Wrapped> {
|
||||
base: T,
|
||||
wrapped: Weak<Wrapped>,
|
||||
}
|
||||
|
||||
trait InterfaceImpl: Sync + Send + 'static {
|
||||
fn supported_versions() -> &'static [&'static CStr];
|
||||
/// Gets a specific interface version
|
||||
fn get_version(version: &CStr) -> Option<Box<dyn FnOnce(&Arc<Self>) -> *mut c_void>>;
|
||||
}
|
||||
|
||||
macro_rules! warn_unimplemented {
|
||||
($function:literal) => {
|
||||
crate::warn_once!(
|
||||
concat!("[ONCE] ", $function, " unimplemented ({}:{})"),
|
||||
file!(),
|
||||
line!()
|
||||
);
|
||||
};
|
||||
}
|
||||
use warn_unimplemented;
|
||||
macro_rules! warn_once {
|
||||
($($tt:tt)*) => {{
|
||||
static ONCE: std::sync::Once = std::sync::Once::new();
|
||||
ONCE.call_once(|| {
|
||||
log::warn!($($tt)*);
|
||||
});
|
||||
}}
|
||||
}
|
||||
use warn_once;
|
||||
|
||||
fn init_logging() {
|
||||
static ONCE: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
ONCE.call_once(|| {
|
||||
let mut builder = env_logger::Builder::new();
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
struct ComboWriter(std::fs::File, std::io::Stdout);
|
||||
|
||||
impl std::io::Write for ComboWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.write(buf)?;
|
||||
self.1.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.0.flush()?;
|
||||
self.1.flush()
|
||||
}
|
||||
}
|
||||
|
||||
let file = std::fs::File::create("/tmp/xrizer.txt").unwrap();
|
||||
let writer = ComboWriter(file, std::io::stdout());
|
||||
builder.target(env_logger::Target::Pipe(Box::new(writer)));
|
||||
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
log::error!("{info}");
|
||||
let backtrace = std::backtrace::Backtrace::force_capture();
|
||||
log::error!("Backtrace: \n{backtrace}");
|
||||
}));
|
||||
}
|
||||
|
||||
// safety: who cares lol
|
||||
unsafe {
|
||||
time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound)
|
||||
};
|
||||
|
||||
builder
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.parse_default_env()
|
||||
.is_test(cfg!(test))
|
||||
.format(|buf, record| {
|
||||
use std::io::Write;
|
||||
use time::macros::format_description;
|
||||
|
||||
let style = buf.default_level_style(record.level());
|
||||
let now = time::OffsetDateTime::now_local()
|
||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc());
|
||||
let now = now
|
||||
.format(format_description!(
|
||||
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
write!(buf, "[{now} {style}{:5}{style:#}", record.level())?;
|
||||
if let Some(path) = record.module_path() {
|
||||
write!(buf, " {}", path)?;
|
||||
}
|
||||
writeln!(buf, " {:?}] {}", std::thread::current().id(), record.args())
|
||||
})
|
||||
.init();
|
||||
});
|
||||
|
||||
log::info!("Initializing XRizer");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn VRClientCoreFactory(
|
||||
interface_name: *const c_char,
|
||||
return_code: *mut i32,
|
||||
) -> *mut c_void {
|
||||
let interface = unsafe { CStr::from_ptr(interface_name) };
|
||||
ClientCore::new(interface)
|
||||
.map(|c| {
|
||||
if let Some(ret) = unsafe { return_code.as_mut() } {
|
||||
*ret = 0;
|
||||
}
|
||||
let vtable = match c.base.get().unwrap() {
|
||||
clientcore::Vtable::V2(v) => v as *const _ as *const vr::IVRClientCore002 as _,
|
||||
clientcore::Vtable::V3(v) => v as *const _ as *const vr::IVRClientCore003 as _,
|
||||
};
|
||||
// Leak it!
|
||||
let _ = Arc::into_raw(c);
|
||||
vtable
|
||||
})
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
/// Needed for Proton, but seems unused.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn HmdSystemFactory(
|
||||
_interface_name: *const c_char,
|
||||
_return_code: *mut i32,
|
||||
) -> *mut c_void {
|
||||
unimplemented!()
|
||||
}
|
||||
165
src/misc_unknown.rs
Normal file
165
src/misc_unknown.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// The interfaces in this file are missing in openvr.h and any other form of OpenVR documentation,
|
||||
// but are used by games (typically Half Life Alyx.)
|
||||
|
||||
use crate::InterfaceImpl;
|
||||
use log::debug;
|
||||
use seq_macro::seq;
|
||||
use std::ffi::{c_char, c_int, c_void, CStr};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UnknownInterfaces {
|
||||
mailbox: Wrapper<Mailbox>,
|
||||
control_panel: Wrapper<ControlPanel>,
|
||||
}
|
||||
|
||||
impl InterfaceImpl for UnknownInterfaces {
|
||||
fn supported_versions() -> &'static [&'static CStr] {
|
||||
&[c"IVRMailbox_001", c"IVRControlPanel_006"]
|
||||
}
|
||||
|
||||
fn get_version(version: &CStr) -> Option<Box<dyn FnOnce(&Arc<Self>) -> *mut c_void>> {
|
||||
match version {
|
||||
x if x == c"IVRMailbox_001" => Some(Box::new(|this| &this.mailbox as *const _ as _)),
|
||||
x if x == c"IVRControlPanel_006" => {
|
||||
Some(Box::new(|this| &this.control_panel as *const _ as _))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps our unknown vtables.
|
||||
#[repr(C)]
|
||||
struct Wrapper<T: 'static> {
|
||||
vtable: &'static T,
|
||||
}
|
||||
|
||||
impl<T> Default for Wrapper<T>
|
||||
where
|
||||
T: 'static,
|
||||
&'static T: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vtable: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const UNKNOWN_TAG: &'static str = "unknown_interfaces";
|
||||
|
||||
macro_rules! gen_vtable {
|
||||
(struct $name:ident {
|
||||
$(
|
||||
fn $fn_name:ident($($arg:ident: $ty:ty),*$(,)?) $(-> $output:ty)? {$($tt:tt)*}
|
||||
)+
|
||||
}) => {
|
||||
#[repr(C)]
|
||||
struct $name {
|
||||
$(
|
||||
$fn_name: extern "C" fn(*mut Wrapper<$name> $(,$ty)*) $(-> $output)?
|
||||
),*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
$(
|
||||
extern "C" fn $fn_name(_: *mut Wrapper<$name> $(,$arg:$ty)*) $(-> $output)? {$($tt)*}
|
||||
)*
|
||||
}
|
||||
|
||||
impl Default for &'static $name {
|
||||
fn default() -> Self {
|
||||
&$name {
|
||||
$(
|
||||
$fn_name: $name::$fn_name
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug)]
|
||||
struct MailboxHandle(u64);
|
||||
|
||||
// IVRMailbox - used by Alyx, only seems to be documnted here:
|
||||
// https://github.com/ValveSoftware/Proton/blob/proton_9.0/vrclient_x64/openvr_v1.10.30/openvr.h#L5108
|
||||
// See also OpenComposite's BaseMailbox.
|
||||
gen_vtable! {
|
||||
struct Mailbox {
|
||||
fn undoc1(a: *const c_char, b: *mut MailboxHandle) -> c_int {
|
||||
let a = unsafe { CStr::from_ptr(a) };
|
||||
debug!(target: UNKNOWN_TAG, "Entered IVRMailbox::undoc1 with arguments a: {a:?}, b: {b:?}");
|
||||
unsafe { *b = MailboxHandle(24) };
|
||||
0
|
||||
}
|
||||
fn undoc2(handle: MailboxHandle) -> c_int {
|
||||
debug!(target: UNKNOWN_TAG, "Entered IVRMailbox::undoc2 with arugments handle: {handle:?}");
|
||||
0
|
||||
}
|
||||
fn undoc3(
|
||||
handle: MailboxHandle,
|
||||
a: *const c_char,
|
||||
b: *const c_char,
|
||||
) -> c_int {
|
||||
let a = unsafe { CStr::from_ptr(a) };
|
||||
let b = unsafe { CStr::from_ptr(b) };
|
||||
debug!(target: UNKNOWN_TAG, "Entered IVRMailbox::undoc3 with arguments handle: {handle:?}, a: {a:?}, b: {b:?}");
|
||||
0
|
||||
}
|
||||
// Borrowed from OpenComposite
|
||||
fn undoc4(
|
||||
handle: MailboxHandle,
|
||||
out_buf: *mut c_char,
|
||||
out_len: u32,
|
||||
len: *mut u32,
|
||||
) -> c_int {
|
||||
debug!(target: UNKNOWN_TAG, "Entered IVRMailbox::undoc4 with arguments handle: {handle:?}, out_buf: {out_buf:?}, out_len: {out_len:?}, c: {len:?}");
|
||||
|
||||
static RECEIVED_MESSAGE: AtomicBool = AtomicBool::new(false);
|
||||
if !RECEIVED_MESSAGE.load(Ordering::Relaxed) {
|
||||
RECEIVED_MESSAGE.store(true, Ordering::Relaxed);
|
||||
|
||||
let msg = cr#"{"type": "ready"}"#;
|
||||
let msg_len = msg.count_bytes() as u32 + 1;
|
||||
unsafe {
|
||||
*len.as_mut().unwrap() = msg_len;
|
||||
}
|
||||
|
||||
if out_len < msg_len {
|
||||
return 2;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
out_buf.copy_from(msg.as_ptr(), msg_len as usize);
|
||||
}
|
||||
|
||||
debug!(target: UNKNOWN_TAG, "Sent ready message: {msg:?}");
|
||||
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IVRControlPanel - used by Alyx.
|
||||
// Without this interface, Alyx complains about "No response from Mongoose".
|
||||
seq!(N in 0..=25 {
|
||||
gen_vtable!(struct ControlPanel {
|
||||
#(fn unknown_func_~N() {
|
||||
debug!(target: UNKNOWN_TAG, "Entered ControlPanel::unknown_func_{}", stringify!(N));
|
||||
})*
|
||||
// This function gets called at startup
|
||||
fn unknown_func_26() -> c_int {
|
||||
debug!(target: UNKNOWN_TAG, "Entered ControlPanel::unknown_func_26");
|
||||
0
|
||||
}
|
||||
});
|
||||
});
|
||||
396
src/openxr_data.rs
Normal file
396
src/openxr_data.rs
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
use crate::{
|
||||
clientcore::{Injected, Injector},
|
||||
vr,
|
||||
vulkan::VulkanData,
|
||||
};
|
||||
use log::info;
|
||||
use openxr as xr;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicI64, Ordering},
|
||||
RwLock,
|
||||
};
|
||||
|
||||
pub trait Compositor: crate::InterfaceImpl {
|
||||
fn init_frame_controller(
|
||||
&self,
|
||||
session: &SessionData,
|
||||
waiter: xr::FrameWaiter,
|
||||
stream: xr::FrameStream<xr::vulkan::Vulkan>,
|
||||
);
|
||||
|
||||
fn current_session_create_info(&self) -> xr::vulkan::SessionCreateInfo;
|
||||
}
|
||||
|
||||
pub type RealOpenXrData = OpenXrData<crate::compositor::Compositor>;
|
||||
pub struct OpenXrData<C: Compositor> {
|
||||
_entry: xr::Entry,
|
||||
pub instance: xr::Instance,
|
||||
pub system_id: xr::SystemId,
|
||||
pub session_data: SessionReadGuard,
|
||||
pub display_time: AtomicXrTime,
|
||||
pub left_hand: HandInfo,
|
||||
pub right_hand: HandInfo,
|
||||
|
||||
/// should only be externally accessed for testing
|
||||
pub(crate) input: Injected<crate::input::Input<C>>,
|
||||
pub(crate) compositor: Injected<C>,
|
||||
}
|
||||
|
||||
impl<C: Compositor> Drop for OpenXrData<C> {
|
||||
fn drop(&mut self) {
|
||||
self.end_session();
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut *self.session_data.0.get_mut().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)] // Results aren't used, but they're printed
|
||||
pub enum InitError {
|
||||
InstanceCreationFailed(xr::sys::Result),
|
||||
SystemCreationFailed(xr::sys::Result),
|
||||
SessionCreationFailed(SessionCreationError),
|
||||
}
|
||||
|
||||
impl From<SessionCreationError> for InitError {
|
||||
fn from(value: SessionCreationError) -> Self {
|
||||
Self::SessionCreationFailed(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Compositor> OpenXrData<C> {
|
||||
pub fn new(injector: &Injector) -> Result<Self, InitError> {
|
||||
#[cfg(not(test))]
|
||||
let entry = xr::Entry::linked();
|
||||
|
||||
#[cfg(test)]
|
||||
let entry =
|
||||
unsafe { xr::Entry::from_get_instance_proc_addr(fakexr::get_instance_proc_addr) }
|
||||
.unwrap();
|
||||
let mut exts = xr::ExtensionSet::default();
|
||||
exts.khr_vulkan_enable = true;
|
||||
let instance = entry
|
||||
.create_instance(
|
||||
&xr::ApplicationInfo {
|
||||
application_name: "XRizer",
|
||||
application_version: 0,
|
||||
..Default::default()
|
||||
},
|
||||
&exts,
|
||||
&[],
|
||||
)
|
||||
.map_err(|e| InitError::InstanceCreationFailed(e))?;
|
||||
|
||||
let system_id = instance
|
||||
.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)
|
||||
.map_err(|e| InitError::SystemCreationFailed(e))?;
|
||||
|
||||
let session_data = SessionReadGuard(RwLock::new(ManuallyDrop::new(
|
||||
SessionData::new(
|
||||
&instance,
|
||||
system_id,
|
||||
vr::ETrackingUniverseOrigin::TrackingUniverseSeated,
|
||||
None,
|
||||
)?
|
||||
.0,
|
||||
)));
|
||||
|
||||
let left_hand = HandInfo::new(&instance, "/user/hand/left");
|
||||
let right_hand = HandInfo::new(&instance, "/user/hand/right");
|
||||
|
||||
Ok(Self {
|
||||
_entry: entry,
|
||||
instance,
|
||||
system_id,
|
||||
session_data,
|
||||
display_time: AtomicXrTime(1.into()),
|
||||
left_hand,
|
||||
right_hand,
|
||||
input: injector.inject(),
|
||||
compositor: injector.inject(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn poll_events(&self) {
|
||||
let mut buf = xr::EventDataBuffer::new();
|
||||
while let Some(event) = self.instance.poll_event(&mut buf).unwrap() {
|
||||
match event {
|
||||
xr::Event::SessionStateChanged(event) => {
|
||||
self.session_data.0.write().unwrap().state = event.state();
|
||||
info!("OpenXR session state changed: {:?}", event.state());
|
||||
}
|
||||
xr::Event::InteractionProfileChanged(_) => {
|
||||
let session = self.session_data.get();
|
||||
for info in [&self.left_hand, &self.right_hand] {
|
||||
let profile = session
|
||||
.session
|
||||
.current_interaction_profile(info.path)
|
||||
.unwrap();
|
||||
|
||||
let profile = match profile {
|
||||
xr::Path::NULL => {
|
||||
info.connected.store(false, Ordering::Relaxed);
|
||||
"<null>".to_owned()
|
||||
}
|
||||
path => {
|
||||
info.connected.store(true, Ordering::Relaxed);
|
||||
self.instance.path_to_string(path).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
info!("{} interaction profile changed: {}", info.s, profile);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
info!("unknown event");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: support non vulkan session
|
||||
pub fn restart_session(&self) {
|
||||
self.end_session();
|
||||
let mut session_guard = self.session_data.0.write().unwrap();
|
||||
|
||||
let origin = session_guard.current_origin;
|
||||
// We need to destroy the old session before creating the new one.
|
||||
let _ = unsafe { ManuallyDrop::take(&mut *session_guard) };
|
||||
|
||||
let comp = self
|
||||
.compositor
|
||||
.get()
|
||||
.expect("Session is being restarted, but compositor has not been set up!");
|
||||
|
||||
let info = comp.current_session_create_info();
|
||||
|
||||
let (session, waiter, stream) =
|
||||
SessionData::new(&self.instance, self.system_id, origin, Some(&info))
|
||||
.expect("Failed to initalize new session");
|
||||
|
||||
comp.init_frame_controller(&session, waiter, stream);
|
||||
|
||||
if let Some(input) = self.input.get() {
|
||||
input.post_session_restart(&session);
|
||||
}
|
||||
|
||||
*session_guard = ManuallyDrop::new(session);
|
||||
}
|
||||
|
||||
pub fn set_tracking_space(&self, space: vr::ETrackingUniverseOrigin) {
|
||||
self.session_data.0.write().unwrap().current_origin = space;
|
||||
}
|
||||
|
||||
pub fn get_tracking_space(&self) -> vr::ETrackingUniverseOrigin {
|
||||
self.session_data.get().current_origin
|
||||
}
|
||||
|
||||
fn end_session(&self) {
|
||||
self.session_data.get().session.request_exit().unwrap();
|
||||
let mut state = self.session_data.get().state;
|
||||
while state != xr::SessionState::STOPPING {
|
||||
self.poll_events();
|
||||
state = self.session_data.get().state;
|
||||
}
|
||||
self.session_data.get().session.end().unwrap();
|
||||
while state != xr::SessionState::EXITING {
|
||||
self.poll_events();
|
||||
state = self.session_data.get().state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AtomicXrTime(AtomicI64);
|
||||
|
||||
impl AtomicXrTime {
|
||||
#[inline]
|
||||
pub fn set(&self, time: xr::Time) {
|
||||
self.0.store(time.as_nanos(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self) -> xr::Time {
|
||||
xr::Time::from_nanos(self.0.load(Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionReadGuard(RwLock<ManuallyDrop<SessionData>>);
|
||||
impl SessionReadGuard {
|
||||
pub fn get(&self) -> std::sync::RwLockReadGuard<'_, ManuallyDrop<SessionData>> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn get_mut(&self) -> std::sync::RwLockWriteGuard<'_, ManuallyDrop<SessionData>> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionData {
|
||||
pub session: xr::Session<xr::vulkan::Vulkan>,
|
||||
pub state: xr::SessionState,
|
||||
pub view_space: xr::Space,
|
||||
local_space: xr::Space,
|
||||
stage_space: xr::Space,
|
||||
pub current_origin: vr::ETrackingUniverseOrigin,
|
||||
|
||||
pub input_data: crate::input::InputSessionData,
|
||||
pub comp_data: crate::compositor::CompositorSessionData,
|
||||
/// OpenXR requires graphics information before creating a session, but OpenVR clients don't
|
||||
/// have to provide that information until they actually submit a frame. Yet, we need some
|
||||
/// information only available behind a session (i.e., calling xrLocateViews for
|
||||
/// GetProjectionMatrix), so we will create a session with fake graphics info to appease OpenXR,
|
||||
/// that will be replaced with a real one after the application calls IVRSystem::Submit.
|
||||
/// When we're using the real session, this will be None.
|
||||
/// Note that it also important that this comes after all members which internally use a xr::Session
|
||||
/// - structs are dropped in declaration order, and if we drop our temporary Vulkan data
|
||||
/// before the session, the runtime will likely be very unhappy.
|
||||
temp_vulkan: Option<VulkanData>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)] // Results aren't used, but they're printed
|
||||
pub enum SessionCreationError {
|
||||
SessionCreationFailed(xr::sys::Result),
|
||||
PollEventFailed(xr::sys::Result),
|
||||
BeginSessionFailed(xr::sys::Result),
|
||||
}
|
||||
|
||||
impl SessionData {
|
||||
fn new(
|
||||
instance: &xr::Instance,
|
||||
system_id: xr::SystemId,
|
||||
current_origin: vr::ETrackingUniverseOrigin,
|
||||
create_info: Option<&xr::vulkan::SessionCreateInfo>,
|
||||
) -> Result<(Self, xr::FrameWaiter, xr::FrameStream<xr::vulkan::Vulkan>), SessionCreationError>
|
||||
{
|
||||
// required to call
|
||||
let _ = instance
|
||||
.graphics_requirements::<xr::vulkan::Vulkan>(system_id)
|
||||
.unwrap();
|
||||
|
||||
let info;
|
||||
let (temp_vulkan, info) = if let Some(info) = create_info {
|
||||
// Monado seems to (wrongly) give validation errors unless we call this.
|
||||
let pd = unsafe { instance.vulkan_graphics_device(system_id, info.instance) }.unwrap();
|
||||
assert_eq!(pd, info.physical_device);
|
||||
(None, info)
|
||||
} else {
|
||||
let vk = VulkanData::new_temporary(&instance, system_id);
|
||||
info = vk.as_session_create_info();
|
||||
(Some(vk), &info)
|
||||
};
|
||||
|
||||
let (session, waiter, stream) =
|
||||
unsafe { instance.create_session::<xr::vulkan::Vulkan>(system_id, &info) }
|
||||
.map_err(|e| SessionCreationError::SessionCreationFailed(e))?;
|
||||
info!("New session created!");
|
||||
|
||||
let view_space = session
|
||||
.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
let local_space = session
|
||||
.create_reference_space(xr::ReferenceSpaceType::LOCAL, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
let stage_space = session
|
||||
.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
|
||||
let mut buf = xr::EventDataBuffer::new();
|
||||
loop {
|
||||
if let Some(xr::Event::SessionStateChanged(state)) = instance
|
||||
.poll_event(&mut buf)
|
||||
.map_err(|e| SessionCreationError::PollEventFailed(e))?
|
||||
{
|
||||
if state.state() == xr::SessionState::READY {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"OpenXR session state changed: {:?}",
|
||||
xr::SessionState::READY
|
||||
);
|
||||
session
|
||||
.begin(xr::ViewConfigurationType::PRIMARY_STEREO)
|
||||
.map_err(|e| SessionCreationError::BeginSessionFailed(e))?;
|
||||
info!("Began OpenXR session.");
|
||||
|
||||
Ok((
|
||||
SessionData {
|
||||
temp_vulkan,
|
||||
session,
|
||||
state: xr::SessionState::READY,
|
||||
view_space,
|
||||
local_space,
|
||||
stage_space,
|
||||
input_data: Default::default(),
|
||||
comp_data: Default::default(),
|
||||
current_origin,
|
||||
},
|
||||
waiter,
|
||||
stream,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn tracking_space(&self) -> &xr::Space {
|
||||
self.get_space_for_origin(self.current_origin)
|
||||
}
|
||||
|
||||
pub fn get_space_for_origin(&self, origin: vr::ETrackingUniverseOrigin) -> &xr::Space {
|
||||
match origin {
|
||||
vr::ETrackingUniverseOrigin::TrackingUniverseSeated => &self.local_space,
|
||||
vr::ETrackingUniverseOrigin::TrackingUniverseStanding => &self.stage_space,
|
||||
vr::ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this session is not using a temporary graphics setup.
|
||||
#[inline]
|
||||
pub fn is_real_session(&self) -> bool {
|
||||
self.temp_vulkan.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HandInfo {
|
||||
s: &'static str,
|
||||
connected: AtomicBool,
|
||||
pub path: xr::Path,
|
||||
}
|
||||
|
||||
impl HandInfo {
|
||||
#[inline]
|
||||
pub fn connected(&self) -> bool {
|
||||
self.connected.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn new(instance: &xr::Instance, path: &'static str) -> Self {
|
||||
Self {
|
||||
s: path,
|
||||
connected: false.into(),
|
||||
path: instance.string_to_path(path).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Hand {
|
||||
Left = 1,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl TryFrom<vr::TrackedDeviceIndex_t> for Hand {
|
||||
type Error = ();
|
||||
#[inline]
|
||||
fn try_from(value: vr::TrackedDeviceIndex_t) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Hand::Left as u32 => Ok(Hand::Left),
|
||||
x if x == Hand::Right as u32 => Ok(Hand::Right),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
743
src/overlay.rs
Normal file
743
src/overlay.rs
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
use crate::vr;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVROverlay"]
|
||||
#[versions(027, 024, 021, 020, 019, 018, 016)]
|
||||
pub struct OverlayMan {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
#[allow(unused_variables, non_snake_case)]
|
||||
impl vr::IVROverlay027_Interface for OverlayMan {
|
||||
fn CloseMessageOverlay(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn ShowMessageOverlay(
|
||||
&self,
|
||||
pchText: *const std::os::raw::c_char,
|
||||
pchCaption: *const std::os::raw::c_char,
|
||||
pchButton0Text: *const std::os::raw::c_char,
|
||||
pchButton1Text: *const std::os::raw::c_char,
|
||||
pchButton2Text: *const std::os::raw::c_char,
|
||||
pchButton3Text: *const std::os::raw::c_char,
|
||||
) -> vr::VRMessageOverlayResponse {
|
||||
todo!()
|
||||
}
|
||||
fn SetKeyboardPositionForOverlay(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
avoidRect: vr::HmdRect2_t,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
fn SetKeyboardTransformAbsolute(
|
||||
&self,
|
||||
eTrackingOrigin: vr::ETrackingUniverseOrigin,
|
||||
pmatTrackingOriginToKeyboardTransform: *const vr::HmdMatrix34_t,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
fn HideKeyboard(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn GetKeyboardText(&self, pchText: *mut std::os::raw::c_char, cchText: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn ShowKeyboardForOverlay(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eInputMode: vr::EGamepadTextInputMode,
|
||||
eLineInputMode: vr::EGamepadTextInputLineMode,
|
||||
unFlags: u32,
|
||||
pchDescription: *const std::os::raw::c_char,
|
||||
unCharMax: u32,
|
||||
pchExistingText: *const std::os::raw::c_char,
|
||||
uUserValue: u64,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ShowKeyboard(
|
||||
&self,
|
||||
eInputMode: vr::EGamepadTextInputMode,
|
||||
eLineInputMode: vr::EGamepadTextInputLineMode,
|
||||
unFlags: u32,
|
||||
pchDescription: *const std::os::raw::c_char,
|
||||
unCharMax: u32,
|
||||
pchExistingText: *const std::os::raw::c_char,
|
||||
uUserValue: u64,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetPrimaryDashboardDevice(&self) -> vr::TrackedDeviceIndex_t {
|
||||
todo!()
|
||||
}
|
||||
fn ShowDashboard(&self, pchOverlayToShow: *const std::os::raw::c_char) {
|
||||
todo!()
|
||||
}
|
||||
fn GetDashboardOverlaySceneProcess(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
punProcessId: *mut u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetDashboardOverlaySceneProcess(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
unProcessId: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn IsActiveDashboardOverlay(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn IsDashboardVisible(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn CreateDashboardOverlay(
|
||||
&self,
|
||||
pchOverlayKey: *const std::os::raw::c_char,
|
||||
pchOverlayFriendlyName: *const std::os::raw::c_char,
|
||||
pMainHandle: *mut vr::VROverlayHandle_t,
|
||||
pThumbnailHandle: *mut vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTextureSize(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pWidth: *mut u32,
|
||||
pHeight: *mut u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ReleaseNativeOverlayHandle(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pNativeTextureHandle: *mut std::os::raw::c_void,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTexture(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pNativeTextureHandle: *mut *mut std::os::raw::c_void,
|
||||
pNativeTextureRef: *mut std::os::raw::c_void,
|
||||
pWidth: *mut u32,
|
||||
pHeight: *mut u32,
|
||||
pNativeFormat: *mut u32,
|
||||
pAPIType: *mut vr::ETextureType,
|
||||
pColorSpace: *mut vr::EColorSpace,
|
||||
pTextureBounds: *mut vr::VRTextureBounds_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayFromFile(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pchFilePath: *const std::os::raw::c_char,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayRaw(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvBuffer: *mut std::os::raw::c_void,
|
||||
unWidth: u32,
|
||||
unHeight: u32,
|
||||
unBytesPerPixel: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ClearOverlayTexture(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTexture(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pTexture: *const vr::Texture_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ClearOverlayCursorPositionOverride(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayCursorPositionOverride(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvCursor: *const vr::HmdVector2_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayCursor(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
ulCursorHandle: vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn TriggerLaserMouseHapticVibration(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fDurationSeconds: f32,
|
||||
fFrequency: f32,
|
||||
fAmplitude: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayIntersectionMask(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pMaskPrimitives: *mut vr::VROverlayIntersectionMaskPrimitive_t,
|
||||
unNumMaskPrimitives: u32,
|
||||
unPrimitiveSize: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn IsHoverTargetOverlay(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn ComputeOverlayIntersection(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pParams: *const vr::VROverlayIntersectionParams_t,
|
||||
pResults: *mut vr::VROverlayIntersectionResults_t,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayMouseScale(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvecMouseScale: *const vr::HmdVector2_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayMouseScale(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvecMouseScale: *mut vr::HmdVector2_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayInputMethod(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eInputMethod: vr::VROverlayInputMethod,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayInputMethod(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
peInputMethod: *mut vr::VROverlayInputMethod,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn PollNextOverlayEvent(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pEvent: *mut vr::VREvent_t,
|
||||
uncbVREvent: u32,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn WaitFrameSync(&self, nTimeoutMs: u32) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetTransformForOverlayCoordinates(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eTrackingOrigin: vr::ETrackingUniverseOrigin,
|
||||
coordinatesInOverlay: vr::HmdVector2_t,
|
||||
pmatTransform: *mut vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn IsOverlayVisible(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn HideOverlay(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ShowOverlay(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
crate::warn_unimplemented!("ShowOverlay");
|
||||
vr::EVROverlayError::VROverlayError_None
|
||||
}
|
||||
fn SetOverlayTransformProjection(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eTrackingOrigin: vr::ETrackingUniverseOrigin,
|
||||
pmatTrackingOriginToOverlayTransform: *const vr::HmdMatrix34_t,
|
||||
pProjection: *const vr::VROverlayProjection_t,
|
||||
eEye: vr::EVREye,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformCursor(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvHotspot: *mut vr::HmdVector2_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTransformCursor(
|
||||
&self,
|
||||
ulCursorOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvHotspot: *const vr::HmdVector2_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformTrackedDeviceComponent(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
punDeviceIndex: *mut vr::TrackedDeviceIndex_t,
|
||||
pchComponentName: *mut std::os::raw::c_char,
|
||||
unComponentNameSize: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTransformTrackedDeviceComponent(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
unDeviceIndex: vr::TrackedDeviceIndex_t,
|
||||
pchComponentName: *const std::os::raw::c_char,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformTrackedDeviceRelative(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
punTrackedDevice: *mut vr::TrackedDeviceIndex_t,
|
||||
pmatTrackedDeviceToOverlayTransform: *mut vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTransformTrackedDeviceRelative(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
unTrackedDevice: vr::TrackedDeviceIndex_t,
|
||||
pmatTrackedDeviceToOverlayTransform: *const vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformAbsolute(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
peTrackingOrigin: *mut vr::ETrackingUniverseOrigin,
|
||||
pmatTrackingOriginToOverlayTransform: *mut vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTransformAbsolute(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eTrackingOrigin: vr::ETrackingUniverseOrigin,
|
||||
pmatTrackingOriginToOverlayTransform: *const vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformType(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
peTransformType: *mut vr::VROverlayTransformType,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTextureBounds(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pOverlayTextureBounds: *mut vr::VRTextureBounds_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTextureBounds(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pOverlayTextureBounds: *const vr::VRTextureBounds_t,
|
||||
) -> vr::EVROverlayError {
|
||||
crate::warn_unimplemented!("SetOverlayTextureBounds");
|
||||
vr::EVROverlayError::VROverlayError_None
|
||||
}
|
||||
fn GetOverlayTextureColorSpace(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
peTextureColorSpace: *mut vr::EColorSpace,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTextureColorSpace(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eTextureColorSpace: vr::EColorSpace,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayPreCurvePitch(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfRadians: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayPreCurvePitch(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fRadians: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayCurvature(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfCurvature: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayCurvature(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fCurvature: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayWidthInMeters(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfWidthInMeters: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayWidthInMeters(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fWidthInMeters: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
crate::warn_unimplemented!("SetOverlayWidthInMeters");
|
||||
vr::EVROverlayError::VROverlayError_None
|
||||
}
|
||||
fn GetOverlaySortOrder(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
punSortOrder: *mut u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlaySortOrder(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
unSortOrder: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTexelAspect(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfTexelAspect: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayTexelAspect(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fTexelAspect: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayAlpha(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfAlpha: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayAlpha(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fAlpha: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayColor(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pfRed: *mut f32,
|
||||
pfGreen: *mut f32,
|
||||
pfBlue: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayColor(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
fRed: f32,
|
||||
fGreen: f32,
|
||||
fBlue: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayFlags(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pFlags: *mut u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayFlag(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eOverlayFlag: vr::VROverlayFlags,
|
||||
pbEnabled: *mut bool,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayFlag(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
eOverlayFlag: vr::VROverlayFlags,
|
||||
bEnabled: bool,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayRenderingPid(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayRenderingPid(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
unPID: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayErrorNameFromEnum(
|
||||
&self,
|
||||
error: vr::EVROverlayError,
|
||||
) -> *const std::os::raw::c_char {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayImageData(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pvBuffer: *mut std::os::raw::c_void,
|
||||
unBufferSize: u32,
|
||||
punWidth: *mut u32,
|
||||
punHeight: *mut u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayName(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pchName: *const std::os::raw::c_char,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayName(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pchValue: *mut std::os::raw::c_char,
|
||||
unBufferSize: u32,
|
||||
pError: *mut vr::EVROverlayError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayKey(
|
||||
&self,
|
||||
ulOverlayHandle: vr::VROverlayHandle_t,
|
||||
pchValue: *mut std::os::raw::c_char,
|
||||
unBufferSize: u32,
|
||||
pError: *mut vr::EVROverlayError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn DestroyOverlay(&self, ulOverlayHandle: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn CreateOverlay(
|
||||
&self,
|
||||
pchOverlayKey: *const std::os::raw::c_char,
|
||||
pchOverlayName: *const std::os::raw::c_char,
|
||||
pOverlayHandle: *mut vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn FindOverlay(
|
||||
&self,
|
||||
pchOverlayKey: *const std::os::raw::c_char,
|
||||
pOverlayHandle: *mut vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
crate::warn_unimplemented!("FindOverlay");
|
||||
vr::EVROverlayError::VROverlayError_None
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVROverlay024On027 for OverlayMan {
|
||||
fn SetOverlayTransformOverlayRelative(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *const vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayTransformOverlayRelative(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *mut vr::VROverlayHandle_t,
|
||||
_: *mut vr::HmdMatrix34_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVROverlay021On024 for OverlayMan {
|
||||
fn ShowKeyboardForOverlay(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::EGamepadTextInputMode,
|
||||
_: vr::EGamepadTextInputLineMode,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: bool,
|
||||
_: u64,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn ShowKeyboard(
|
||||
&self,
|
||||
_: vr::EGamepadTextInputMode,
|
||||
_: vr::EGamepadTextInputLineMode,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: bool,
|
||||
_: u64,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayRaw(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *mut std::os::raw::c_void,
|
||||
_: u32,
|
||||
_: u32,
|
||||
_: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayDualAnalogTransform(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::EDualAnalogWhich,
|
||||
_: *mut vr::HmdVector2_t,
|
||||
_: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayDualAnalogTransform(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::EDualAnalogWhich,
|
||||
_: *const vr::HmdVector2_t,
|
||||
_: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayRenderModel(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const vr::HmdColor_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayRenderModel(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *mut vr::HmdColor_t,
|
||||
_: *mut vr::EVROverlayError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVROverlay020On021 for OverlayMan {
|
||||
fn MoveGamepadFocusToNeighbor(
|
||||
&self,
|
||||
_: vr::EOverlayDirection,
|
||||
_: vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayNeighbor(
|
||||
&self,
|
||||
_: vr::EOverlayDirection,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::VROverlayHandle_t,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetGamepadFocusOverlay(&self, _: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn GetGamepadFocusOverlay(&self) -> vr::VROverlayHandle_t {
|
||||
todo!()
|
||||
}
|
||||
fn GetOverlayAutoCurveDistanceRangeInMeters(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *mut f32,
|
||||
_: *mut f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
fn SetOverlayAutoCurveDistanceRangeInMeters(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: f32,
|
||||
_: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// The OpenVR commit messages mention that these functions just go through the standard overlay
|
||||
// rendering path now.
|
||||
impl vr::IVROverlay019On020 for OverlayMan {
|
||||
fn GetHighQualityOverlay(&self) -> vr::VROverlayHandle_t {
|
||||
unimplemented!()
|
||||
}
|
||||
fn SetHighQualityOverlay(&self, _: vr::VROverlayHandle_t) -> vr::EVROverlayError {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVROverlay018On019 for OverlayMan {
|
||||
#[inline]
|
||||
fn SetOverlayDualAnalogTransform(
|
||||
&self,
|
||||
overlay: vr::VROverlayHandle_t,
|
||||
which: vr::EDualAnalogWhich,
|
||||
center: *const vr::HmdVector2_t,
|
||||
radius: f32,
|
||||
) -> vr::EVROverlayError {
|
||||
<Self as vr::IVROverlay021_Interface>::SetOverlayDualAnalogTransform(
|
||||
self, overlay, which, center, radius,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVROverlay016On018 for OverlayMan {
|
||||
fn HandleControllerOverlayInteractionAsMouse(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: vr::TrackedDeviceIndex_t,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
29
src/overlayview.rs
Normal file
29
src/overlayview.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::vr;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVROverlayView"]
|
||||
#[versions(003)]
|
||||
pub struct OverlayView {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
impl vr::IVROverlayView003_Interface for OverlayView {
|
||||
fn IsViewingPermitted(&self, _: vr::VROverlayHandle_t) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn PostOverlayEvent(&self, _: vr::VROverlayHandle_t, _: *const vr::VREvent_t) {
|
||||
todo!()
|
||||
}
|
||||
fn ReleaseOverlayView(&self, _: *mut vr::VROverlayView_t) -> vr::EVROverlayError {
|
||||
vr::EVROverlayError::VROverlayError_InvalidHandle
|
||||
}
|
||||
fn AcquireOverlayView(
|
||||
&self,
|
||||
_: vr::VROverlayHandle_t,
|
||||
_: *mut vr::VRNativeDevice_t,
|
||||
_: *mut vr::VROverlayView_t,
|
||||
_: u32,
|
||||
) -> vr::EVROverlayError {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
135
src/rendermodels.rs
Normal file
135
src/rendermodels.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use crate::vr;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVRRenderModels"]
|
||||
#[versions(006, 005)]
|
||||
pub struct RenderModels {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl vr::IVRRenderModels006_Interface for RenderModels {
|
||||
fn GetRenderModelErrorNameFromEnum(
|
||||
&self,
|
||||
_: vr::EVRRenderModelError,
|
||||
) -> *const std::os::raw::c_char {
|
||||
c"<unknown>".as_ptr()
|
||||
}
|
||||
fn GetRenderModelOriginalPath(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *mut vr::EVRRenderModelError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetRenderModelThumbnailURL(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *mut vr::EVRRenderModelError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn RenderModelHasComponent(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentState(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const vr::VRControllerState_t,
|
||||
_: *const vr::RenderModel_ControllerMode_State_t,
|
||||
_: *mut vr::RenderModel_ComponentState_t,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentStateForDevicePath(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: vr::VRInputValueHandle_t,
|
||||
_: *const vr::RenderModel_ControllerMode_State_t,
|
||||
_: *mut vr::RenderModel_ComponentState_t,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentRenderModelName(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentButtonMask(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
) -> u64 {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentName(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetComponentCount(&self, _: *const std::os::raw::c_char) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetRenderModelCount(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetRenderModelName(&self, _: u32, _: *mut std::os::raw::c_char, _: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn FreeTextureD3D11(&self, _: *mut std::os::raw::c_void) {
|
||||
todo!()
|
||||
}
|
||||
fn LoadIntoTextureD3D11_Async(
|
||||
&self,
|
||||
_: vr::TextureID_t,
|
||||
_: *mut std::os::raw::c_void,
|
||||
) -> vr::EVRRenderModelError {
|
||||
todo!()
|
||||
}
|
||||
fn LoadTextureD3D11_Async(
|
||||
&self,
|
||||
_: vr::TextureID_t,
|
||||
_: *mut std::os::raw::c_void,
|
||||
_: *mut *mut std::os::raw::c_void,
|
||||
) -> vr::EVRRenderModelError {
|
||||
todo!()
|
||||
}
|
||||
fn FreeTexture(&self, _: *mut vr::RenderModel_TextureMap_t) {
|
||||
todo!()
|
||||
}
|
||||
fn LoadTexture_Async(
|
||||
&self,
|
||||
_: vr::TextureID_t,
|
||||
_: *mut *mut vr::RenderModel_TextureMap_t,
|
||||
) -> vr::EVRRenderModelError {
|
||||
todo!()
|
||||
}
|
||||
fn FreeRenderModel(&self, _: *mut vr::RenderModel_t) {
|
||||
todo!()
|
||||
}
|
||||
fn LoadRenderModel_Async(
|
||||
&self,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *mut *mut vr::RenderModel_t,
|
||||
) -> vr::EVRRenderModelError {
|
||||
vr::EVRRenderModelError::VRRenderModelError_NotSupported
|
||||
}
|
||||
}
|
||||
68
src/screenshots.rs
Normal file
68
src/screenshots.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::vr;
|
||||
|
||||
#[derive(Default, macros::InterfaceImpl)]
|
||||
#[interface = "IVRScreenshots"]
|
||||
#[versions(001)]
|
||||
pub struct Screenshots {
|
||||
vtables: Vtables,
|
||||
}
|
||||
|
||||
impl vr::IVRScreenshots001_Interface for Screenshots {
|
||||
fn SubmitScreenshot(
|
||||
&self,
|
||||
_: vr::ScreenshotHandle_t,
|
||||
_: vr::EVRScreenshotType,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
) -> vr::EVRScreenshotError {
|
||||
todo!()
|
||||
}
|
||||
fn TakeStereoScreenshot(
|
||||
&self,
|
||||
_: *mut vr::ScreenshotHandle_t,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
) -> vr::EVRScreenshotError {
|
||||
todo!()
|
||||
}
|
||||
fn UpdateScreenshotProgress(
|
||||
&self,
|
||||
_: vr::ScreenshotHandle_t,
|
||||
_: f32,
|
||||
) -> vr::EVRScreenshotError {
|
||||
todo!()
|
||||
}
|
||||
fn GetScreenshotPropertyFilename(
|
||||
&self,
|
||||
_: vr::ScreenshotHandle_t,
|
||||
_: vr::EVRScreenshotPropertyFilenames,
|
||||
_: *mut std::os::raw::c_char,
|
||||
_: u32,
|
||||
_: *mut vr::EVRScreenshotError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetScreenshotPropertyType(
|
||||
&self,
|
||||
_: vr::ScreenshotHandle_t,
|
||||
_: *mut vr::EVRScreenshotError,
|
||||
) -> vr::EVRScreenshotType {
|
||||
todo!()
|
||||
}
|
||||
fn HookScreenshot(
|
||||
&self,
|
||||
_: *const vr::EVRScreenshotType,
|
||||
_: std::os::raw::c_int,
|
||||
) -> vr::EVRScreenshotError {
|
||||
vr::EVRScreenshotError::VRScreenshotError_None
|
||||
}
|
||||
fn RequestScreenshot(
|
||||
&self,
|
||||
_: *mut vr::ScreenshotHandle_t,
|
||||
_: vr::EVRScreenshotType,
|
||||
_: *const std::os::raw::c_char,
|
||||
_: *const std::os::raw::c_char,
|
||||
) -> vr::EVRScreenshotError {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
701
src/system.rs
Normal file
701
src/system.rs
Normal file
|
|
@ -0,0 +1,701 @@
|
|||
use crate::{
|
||||
clientcore::{Injected, Injector},
|
||||
input::Input,
|
||||
openxr_data::{Hand, RealOpenXrData},
|
||||
vr,
|
||||
};
|
||||
use glam::{Mat3, Quat, Vec3};
|
||||
use log::debug;
|
||||
use openxr as xr;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct ConnectedHands {
|
||||
left: AtomicBool,
|
||||
right: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(macros::InterfaceImpl)]
|
||||
#[interface = "IVRSystem"]
|
||||
#[versions(022, 021, 020, 019, 016, 015)]
|
||||
pub struct System {
|
||||
openxr: Arc<RealOpenXrData>, // We don't need to test session restarting.
|
||||
input: Injected<Input<crate::compositor::Compositor>>,
|
||||
vtables: Vtables,
|
||||
last_connected_hands: ConnectedHands,
|
||||
}
|
||||
|
||||
mod log_tags {
|
||||
pub const TRACKED_PROP: &'static str = "tracked_property";
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn new(openxr: Arc<RealOpenXrData>, injector: &Injector) -> Self {
|
||||
Self {
|
||||
openxr,
|
||||
input: injector.inject(),
|
||||
vtables: Default::default(),
|
||||
last_connected_hands: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_views(&self) -> [xr::View; 2] {
|
||||
let data = self.openxr.session_data.get();
|
||||
let (_, views) = data
|
||||
.session
|
||||
.locate_views(
|
||||
xr::ViewConfigurationType::PRIMARY_STEREO,
|
||||
self.openxr.display_time.get(),
|
||||
&data.tracking_space(),
|
||||
)
|
||||
.expect("Couldn't locate views");
|
||||
|
||||
views
|
||||
.try_into()
|
||||
.unwrap_or_else(|v: Vec<xr::View>| panic!("Expected 2 views, got {}", v.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRSystem022_Interface for System {
|
||||
fn GetRecommendedRenderTargetSize(&self, width: *mut u32, height: *mut u32) {
|
||||
let views = self
|
||||
.openxr
|
||||
.instance
|
||||
.enumerate_view_configuration_views(
|
||||
self.openxr.system_id,
|
||||
xr::ViewConfigurationType::PRIMARY_STEREO,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !width.is_null() {
|
||||
unsafe { *width = views[0].recommended_image_rect_width };
|
||||
}
|
||||
|
||||
if !height.is_null() {
|
||||
unsafe { *height = views[0].recommended_image_rect_height };
|
||||
}
|
||||
}
|
||||
fn GetProjectionMatrix(&self, eye: vr::EVREye, near_z: f32, far_z: f32) -> vr::HmdMatrix44_t {
|
||||
// https://github.com/ValveSoftware/openvr/wiki/IVRSystem::GetProjectionRaw
|
||||
let [mut left, mut right, mut up, mut down] = [0.0; 4];
|
||||
self.GetProjectionRaw(eye, &mut left, &mut right, &mut down, &mut up);
|
||||
|
||||
let idx = 1.0 / (right - left);
|
||||
let idy = 1.0 / (up - down);
|
||||
let idz = 1.0 / (far_z - near_z);
|
||||
let sx = right + left;
|
||||
let sy = up + down;
|
||||
|
||||
vr::HmdMatrix44_t {
|
||||
m: [
|
||||
[2.0 * idx, 0.0, sx * idx, 0.0],
|
||||
[0.0, 2.0 * idy, sy * idy, 0.0],
|
||||
[0.0, 0.0, -far_z * idz, -far_z * near_z * idz],
|
||||
[0.0, 0.0, -1.0, 0.0],
|
||||
],
|
||||
}
|
||||
}
|
||||
fn GetProjectionRaw(
|
||||
&self,
|
||||
eye: vr::EVREye,
|
||||
left: *mut f32,
|
||||
right: *mut f32,
|
||||
top: *mut f32,
|
||||
bottom: *mut f32,
|
||||
) {
|
||||
let view = self.get_views()[eye as usize];
|
||||
|
||||
// Top and bottom are flipped, for some reason
|
||||
unsafe {
|
||||
*left = view.fov.angle_left.tan();
|
||||
*right = view.fov.angle_right.tan();
|
||||
*bottom = view.fov.angle_up.tan();
|
||||
*top = view.fov.angle_down.tan();
|
||||
}
|
||||
}
|
||||
fn ComputeDistortion(
|
||||
&self,
|
||||
_: vr::EVREye,
|
||||
_: f32,
|
||||
_: f32,
|
||||
_: *mut vr::DistortionCoordinates_t,
|
||||
) -> bool {
|
||||
crate::warn_unimplemented!("ComputeDistortion");
|
||||
false
|
||||
}
|
||||
fn GetEyeToHeadTransform(&self, eye: vr::EVREye) -> vr::HmdMatrix34_t {
|
||||
let data = self.openxr.session_data.get();
|
||||
|
||||
let (_flags, views) = data
|
||||
.session
|
||||
.locate_views(
|
||||
xr::ViewConfigurationType::PRIMARY_STEREO,
|
||||
self.openxr.display_time.get(),
|
||||
&data.view_space,
|
||||
)
|
||||
.expect("Couldn't locate views");
|
||||
|
||||
let view = views[eye as usize];
|
||||
let view_rot = view.pose.orientation;
|
||||
|
||||
let rot = Mat3::from_quat(Quat::from_xyzw(
|
||||
view_rot.x, view_rot.y, view_rot.z, view_rot.w,
|
||||
))
|
||||
.transpose();
|
||||
|
||||
let gen_array = |translation, rot_axis: Vec3| {
|
||||
std::array::from_fn(|i| if i == 3 { translation } else { rot_axis[i] })
|
||||
};
|
||||
vr::HmdMatrix34_t {
|
||||
m: [
|
||||
gen_array(view.pose.position.x, rot.x_axis),
|
||||
gen_array(view.pose.position.y, rot.y_axis),
|
||||
gen_array(view.pose.position.z, rot.z_axis),
|
||||
],
|
||||
}
|
||||
}
|
||||
fn GetTimeSinceLastVsync(&self, _: *mut f32, _: *mut u64) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetRuntimeVersion(&self) -> *const std::os::raw::c_char {
|
||||
static VERSION: &'static CStr = c"2.5.1";
|
||||
VERSION.as_ptr()
|
||||
}
|
||||
fn GetAppContainerFilePaths(&self, _: *mut std::os::raw::c_char, _: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn AcknowledgeQuit_Exiting(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn PerformFirmwareUpdate(&self, _: vr::TrackedDeviceIndex_t) -> vr::EVRFirmwareError {
|
||||
todo!()
|
||||
}
|
||||
fn ShouldApplicationReduceRenderingWork(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn ShouldApplicationPause(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn IsSteamVRDrawingControllers(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn IsInputAvailable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn GetControllerAxisTypeNameFromEnum(
|
||||
&self,
|
||||
_: vr::EVRControllerAxisType,
|
||||
) -> *const std::os::raw::c_char {
|
||||
todo!()
|
||||
}
|
||||
fn GetButtonIdNameFromEnum(&self, _: vr::EVRButtonId) -> *const std::os::raw::c_char {
|
||||
todo!()
|
||||
}
|
||||
fn TriggerHapticPulse(&self, _: vr::TrackedDeviceIndex_t, _: u32, _: std::os::raw::c_ushort) {
|
||||
crate::warn_unimplemented!("TriggerHapticPulse");
|
||||
}
|
||||
fn GetControllerStateWithPose(
|
||||
&self,
|
||||
origin: vr::ETrackingUniverseOrigin,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
state: *mut vr::VRControllerState_t,
|
||||
state_size: u32,
|
||||
pose: *mut vr::TrackedDevicePose_t,
|
||||
) -> bool {
|
||||
if self.GetControllerState(device_index, state, state_size) {
|
||||
unsafe {
|
||||
*pose.as_mut().unwrap() = self
|
||||
.input
|
||||
.get()
|
||||
.unwrap()
|
||||
.get_controller_pose(Hand::try_from(device_index).unwrap(), Some(origin))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn GetControllerState(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
state: *mut vr::VRControllerState_t,
|
||||
state_size: u32,
|
||||
) -> bool {
|
||||
self.input
|
||||
.force(|_| Input::new(self.openxr.clone()))
|
||||
.get_legacy_controller_state(device_index, state, state_size)
|
||||
}
|
||||
fn GetHiddenAreaMesh(&self, _: vr::EVREye, _: vr::EHiddenAreaMeshType) -> vr::HiddenAreaMesh_t {
|
||||
crate::warn_unimplemented!("GetHiddenAreaMesh");
|
||||
vr::HiddenAreaMesh_t::default()
|
||||
}
|
||||
fn GetEventTypeNameFromEnum(&self, _: vr::EVREventType) -> *const std::os::raw::c_char {
|
||||
todo!()
|
||||
}
|
||||
fn PollNextEventWithPose(
|
||||
&self,
|
||||
_: vr::ETrackingUniverseOrigin,
|
||||
_: *mut vr::VREvent_t,
|
||||
_: u32,
|
||||
_: *mut vr::TrackedDevicePose_t,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
fn PollNextEvent(&self, event: *mut vr::VREvent_t, _size: u32) -> bool {
|
||||
use std::ptr::addr_of_mut as ptr;
|
||||
use vr::EVREventType::*;
|
||||
let (left_hand_connected, right_hand_connected) = (
|
||||
self.openxr.left_hand.connected(),
|
||||
self.openxr.right_hand.connected(),
|
||||
);
|
||||
|
||||
let device_state_event = |current_state, last_state: &AtomicBool, tracked_device| {
|
||||
last_state.store(current_state, Ordering::Relaxed);
|
||||
|
||||
// Since the VREvent_t struct can be a variable size, it seems a little dangerous to
|
||||
// create a reference to it, so we'll just operate through pointers.
|
||||
// The eventType, trackedDeviceIndex, and eventAgeSeconds fields have always existed.
|
||||
unsafe {
|
||||
ptr!((*event).eventType).write(if current_state {
|
||||
VREvent_TrackedDeviceActivated as u32
|
||||
} else {
|
||||
VREvent_TrackedDeviceDeactivated as u32
|
||||
});
|
||||
|
||||
ptr!((*event).trackedDeviceIndex).write(tracked_device);
|
||||
ptr!((*event).eventAgeSeconds).write(0.0);
|
||||
}
|
||||
};
|
||||
|
||||
if left_hand_connected != self.last_connected_hands.left.load(Ordering::Relaxed) {
|
||||
debug!(
|
||||
"sending left hand {}connected",
|
||||
if left_hand_connected { "" } else { "not " }
|
||||
);
|
||||
device_state_event(
|
||||
left_hand_connected,
|
||||
&self.last_connected_hands.left,
|
||||
Hand::Left as u32,
|
||||
);
|
||||
true
|
||||
} else if right_hand_connected != self.last_connected_hands.right.load(Ordering::Relaxed) {
|
||||
debug!(
|
||||
"sending right hand {}connected",
|
||||
if right_hand_connected { "" } else { "not " }
|
||||
);
|
||||
device_state_event(
|
||||
right_hand_connected,
|
||||
&self.last_connected_hands.right,
|
||||
Hand::Right as u32,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn GetPropErrorNameFromEnum(
|
||||
&self,
|
||||
_: vr::ETrackedPropertyError,
|
||||
) -> *const std::os::raw::c_char {
|
||||
c"Unknown error".as_ptr()
|
||||
}
|
||||
fn GetStringTrackedDeviceProperty(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
prop: vr::ETrackedDeviceProperty,
|
||||
value: *mut std::os::raw::c_char,
|
||||
size: u32,
|
||||
error: *mut vr::ETrackedPropertyError,
|
||||
) -> u32 {
|
||||
debug!(target: log_tags::TRACKED_PROP, "requesting string property: {prop:?} ({device_index})");
|
||||
|
||||
if !self.IsTrackedDeviceConnected(device_index) {
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_InvalidDevice;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_Success;
|
||||
}
|
||||
|
||||
let buf = if !value.is_null() && size > 0 {
|
||||
unsafe { std::slice::from_raw_parts_mut(value, size as usize) }
|
||||
} else {
|
||||
&mut []
|
||||
};
|
||||
|
||||
use vr::ETrackedDeviceProperty::*;
|
||||
let data = match device_index {
|
||||
vr::k_unTrackedDeviceIndex_Hmd => match prop {
|
||||
// The Unity OpenVR sample appears to have a hard requirement on these first three properties returning
|
||||
// something to even get the game to recognize the HMD's location. However, the value
|
||||
// itself doesn't appear to be that important.
|
||||
Prop_SerialNumber_String
|
||||
| Prop_ManufacturerName_String
|
||||
| Prop_ControllerType_String => Some(c"<unknown>"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let Some(data) = data else {
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
let data =
|
||||
unsafe { std::slice::from_raw_parts(data.as_ptr(), data.to_bytes_with_nul().len()) };
|
||||
if buf.len() < data.len() {
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_BufferTooSmall;
|
||||
}
|
||||
} else {
|
||||
buf[0..data.len()].copy_from_slice(data);
|
||||
}
|
||||
|
||||
data.len() as u32
|
||||
}
|
||||
fn GetArrayTrackedDeviceProperty(
|
||||
&self,
|
||||
_: vr::TrackedDeviceIndex_t,
|
||||
_: vr::ETrackedDeviceProperty,
|
||||
_: vr::PropertyTypeTag_t,
|
||||
_: *mut std::os::raw::c_void,
|
||||
_: u32,
|
||||
_: *mut vr::ETrackedPropertyError,
|
||||
) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
fn GetMatrix34TrackedDeviceProperty(
|
||||
&self,
|
||||
_: vr::TrackedDeviceIndex_t,
|
||||
_: vr::ETrackedDeviceProperty,
|
||||
_: *mut vr::ETrackedPropertyError,
|
||||
) -> vr::HmdMatrix34_t {
|
||||
todo!()
|
||||
}
|
||||
fn GetUint64TrackedDeviceProperty(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
prop: vr::ETrackedDeviceProperty,
|
||||
err: *mut vr::ETrackedPropertyError,
|
||||
) -> u64 {
|
||||
debug!(target: log_tags::TRACKED_PROP, "requesting uint64 property: {prop:?} ({device_index})");
|
||||
if !self.IsTrackedDeviceConnected(device_index) {
|
||||
if let Some(err) = unsafe { err.as_mut() } {
|
||||
*err = vr::ETrackedPropertyError::TrackedProp_InvalidDevice;
|
||||
}
|
||||
}
|
||||
if let Some(err) = unsafe { err.as_mut() } {
|
||||
*err = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
fn GetInt32TrackedDeviceProperty(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
prop: vr::ETrackedDeviceProperty,
|
||||
err: *mut vr::ETrackedPropertyError,
|
||||
) -> i32 {
|
||||
debug!(target: log_tags::TRACKED_PROP, "requesting int32 property: {prop:?} ({device_index})");
|
||||
if !self.IsTrackedDeviceConnected(device_index) {
|
||||
if let Some(err) = unsafe { err.as_mut() } {
|
||||
*err = vr::ETrackedPropertyError::TrackedProp_InvalidDevice;
|
||||
}
|
||||
}
|
||||
|
||||
match device_index {
|
||||
x if Hand::try_from(x).is_ok() => match prop {
|
||||
vr::ETrackedDeviceProperty::Prop_Axis1Type_Int32 => {
|
||||
Some(vr::EVRControllerAxisType::k_eControllerAxis_Trigger as _)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
if let Some(err) = unsafe { err.as_mut() } {
|
||||
*err = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
fn GetFloatTrackedDeviceProperty(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
prop: vr::ETrackedDeviceProperty,
|
||||
error: *mut vr::ETrackedPropertyError,
|
||||
) -> f32 {
|
||||
debug!(target: log_tags::TRACKED_PROP, "requesting float property: {prop:?} ({device_index})");
|
||||
if device_index != vr::k_unTrackedDeviceIndex_Hmd {
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
match prop {
|
||||
vr::ETrackedDeviceProperty::Prop_UserIpdMeters_Float => {
|
||||
let views = self.get_views();
|
||||
views[1].pose.position.x - views[0].pose.position.y
|
||||
}
|
||||
vr::ETrackedDeviceProperty::Prop_DisplayFrequency_Float => 90.0,
|
||||
_ => {
|
||||
if let Some(error) = unsafe { error.as_mut() } {
|
||||
*error = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
fn GetBoolTrackedDeviceProperty(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
prop: vr::ETrackedDeviceProperty,
|
||||
err: *mut vr::ETrackedPropertyError,
|
||||
) -> bool {
|
||||
debug!(target: log_tags::TRACKED_PROP, "requesting bool property: {prop:?} ({device_index})");
|
||||
if let Some(err) = unsafe { err.as_mut() } {
|
||||
*err = vr::ETrackedPropertyError::TrackedProp_UnknownProperty;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn IsTrackedDeviceConnected(&self, device_index: vr::TrackedDeviceIndex_t) -> bool {
|
||||
match device_index {
|
||||
vr::k_unTrackedDeviceIndex_Hmd => true,
|
||||
x if Hand::try_from(x).is_ok() => match Hand::try_from(x).unwrap() {
|
||||
Hand::Left => self.openxr.left_hand.connected(),
|
||||
Hand::Right => self.openxr.right_hand.connected(),
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn GetTrackedDeviceClass(&self, index: vr::TrackedDeviceIndex_t) -> vr::ETrackedDeviceClass {
|
||||
match index {
|
||||
vr::k_unTrackedDeviceIndex_Hmd => vr::ETrackedDeviceClass::TrackedDeviceClass_HMD,
|
||||
x if Hand::try_from(x).is_ok() => {
|
||||
if self.IsTrackedDeviceConnected(x) {
|
||||
vr::ETrackedDeviceClass::TrackedDeviceClass_Controller
|
||||
} else {
|
||||
vr::ETrackedDeviceClass::TrackedDeviceClass_Invalid
|
||||
}
|
||||
}
|
||||
_ => vr::ETrackedDeviceClass::TrackedDeviceClass_Invalid,
|
||||
}
|
||||
}
|
||||
fn GetControllerRoleForTrackedDeviceIndex(
|
||||
&self,
|
||||
index: vr::TrackedDeviceIndex_t,
|
||||
) -> vr::ETrackedControllerRole {
|
||||
match index {
|
||||
x if Hand::try_from(x).is_ok() => match Hand::try_from(x).unwrap() {
|
||||
Hand::Left => vr::ETrackedControllerRole::TrackedControllerRole_LeftHand,
|
||||
Hand::Right => vr::ETrackedControllerRole::TrackedControllerRole_RightHand,
|
||||
},
|
||||
_ => vr::ETrackedControllerRole::TrackedControllerRole_Invalid,
|
||||
}
|
||||
}
|
||||
fn GetTrackedDeviceIndexForControllerRole(
|
||||
&self,
|
||||
role: vr::ETrackedControllerRole,
|
||||
) -> vr::TrackedDeviceIndex_t {
|
||||
match role {
|
||||
vr::ETrackedControllerRole::TrackedControllerRole_LeftHand => {
|
||||
if self.openxr.left_hand.connected() {
|
||||
Hand::Left as u32
|
||||
} else {
|
||||
vr::k_unTrackedDeviceIndexInvalid
|
||||
}
|
||||
}
|
||||
vr::ETrackedControllerRole::TrackedControllerRole_RightHand => {
|
||||
if self.openxr.right_hand.connected() {
|
||||
Hand::Right as u32
|
||||
} else {
|
||||
vr::k_unTrackedDeviceIndexInvalid
|
||||
}
|
||||
}
|
||||
_ => vr::k_unTrackedDeviceIndexInvalid,
|
||||
}
|
||||
}
|
||||
fn ApplyTransform(
|
||||
&self,
|
||||
_: *mut vr::TrackedDevicePose_t,
|
||||
_: *const vr::TrackedDevicePose_t,
|
||||
_: *const vr::HmdMatrix34_t,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
fn GetTrackedDeviceActivityLevel(
|
||||
&self,
|
||||
device_index: vr::TrackedDeviceIndex_t,
|
||||
) -> vr::EDeviceActivityLevel {
|
||||
match device_index {
|
||||
vr::k_unTrackedDeviceIndex_Hmd => {
|
||||
vr::EDeviceActivityLevel::k_EDeviceActivityLevel_UserInteraction
|
||||
}
|
||||
x if Hand::try_from(x).is_ok() => {
|
||||
if self.IsTrackedDeviceConnected(x) {
|
||||
vr::EDeviceActivityLevel::k_EDeviceActivityLevel_UserInteraction
|
||||
} else {
|
||||
vr::EDeviceActivityLevel::k_EDeviceActivityLevel_Unknown
|
||||
}
|
||||
}
|
||||
_ => vr::EDeviceActivityLevel::k_EDeviceActivityLevel_Unknown,
|
||||
}
|
||||
}
|
||||
fn GetSortedTrackedDeviceIndicesOfClass(
|
||||
&self,
|
||||
_: vr::ETrackedDeviceClass,
|
||||
_: *mut vr::TrackedDeviceIndex_t,
|
||||
_: u32,
|
||||
_: vr::TrackedDeviceIndex_t,
|
||||
) -> u32 {
|
||||
0
|
||||
}
|
||||
fn GetRawZeroPoseToStandingAbsoluteTrackingPose(&self) -> vr::HmdMatrix34_t {
|
||||
todo!()
|
||||
}
|
||||
fn GetSeatedZeroPoseToStandingAbsoluteTrackingPose(&self) -> vr::HmdMatrix34_t {
|
||||
todo!()
|
||||
}
|
||||
fn GetDeviceToAbsoluteTrackingPose(
|
||||
&self,
|
||||
origin: vr::ETrackingUniverseOrigin,
|
||||
_seconds_to_photon_from_now: f32,
|
||||
pose_array: *mut vr::TrackedDevicePose_t,
|
||||
pose_count: u32,
|
||||
) {
|
||||
self.input
|
||||
.force(|_| Input::new(self.openxr.clone()))
|
||||
.get_poses(
|
||||
unsafe { std::slice::from_raw_parts_mut(pose_array, pose_count as usize) },
|
||||
Some(origin),
|
||||
);
|
||||
}
|
||||
fn SetDisplayVisibility(&self, _: bool) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn IsDisplayOnDesktop(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn GetOutputDevice(
|
||||
&self,
|
||||
device: *mut u64,
|
||||
texture_type: vr::ETextureType,
|
||||
instance: *mut crate::bindings::VkInstance_T,
|
||||
) {
|
||||
if texture_type != vr::ETextureType::TextureType_Vulkan {
|
||||
todo!("Support textures besides Vulkan ({texture_type:?})")
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*device = self
|
||||
.openxr
|
||||
.instance
|
||||
.vulkan_graphics_device(self.openxr.system_id, instance as _)
|
||||
.expect("Failed to get vulkan physical device") as _;
|
||||
}
|
||||
}
|
||||
fn GetDXGIOutputInfo(&self, _: *mut i32) {
|
||||
todo!()
|
||||
}
|
||||
fn GetD3D9AdapterIndex(&self) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRSystem021On022 for System {
|
||||
fn ResetSeatedZeroPose(&self) {
|
||||
crate::warn_unimplemented!("ResetSeatedZeroPose");
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRSystem020On021 for System {
|
||||
fn AcknowledgeQuit_UserPrompt(&self) {}
|
||||
}
|
||||
|
||||
impl vr::IVRSystem019On020 for System {
|
||||
fn DriverDebugRequest(
|
||||
&self,
|
||||
_un_device_index: vr::TrackedDeviceIndex_t,
|
||||
_pch_request: *const std::os::raw::c_char,
|
||||
_pch_response_buffer: *mut std::os::raw::c_char,
|
||||
_un_response_buffer_size: u32,
|
||||
) -> u32 {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl vr::IVRSystem016On019 for System {
|
||||
fn IsInputFocusCapturedByAnotherProcess(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn ReleaseInputFocus(&self) {}
|
||||
fn CaptureInputFocus(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn GetOutputDevice(&self, _device: *mut u64, _texture_type: vr::ETextureType) {
|
||||
// TODO: figure out what to pass for the instance...
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::clientcore::Injector;
|
||||
use std::ffi::CStr;
|
||||
use vr::IVRSystem022_Interface;
|
||||
|
||||
#[test]
|
||||
fn unity_required_properties() {
|
||||
let xr = Arc::new(RealOpenXrData::new(&Injector::default()).unwrap());
|
||||
let injector = Injector::default();
|
||||
let system = System::new(xr, &injector);
|
||||
|
||||
let test_prop = |property| {
|
||||
let mut err = vr::ETrackedPropertyError::TrackedProp_Success;
|
||||
let len = system.GetStringTrackedDeviceProperty(
|
||||
vr::k_unTrackedDeviceIndex_Hmd,
|
||||
property,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
&mut err,
|
||||
);
|
||||
assert_eq!(err, vr::ETrackedPropertyError::TrackedProp_BufferTooSmall);
|
||||
assert!(len > 0);
|
||||
let mut buf = vec![0; len as usize];
|
||||
|
||||
let len = system.GetStringTrackedDeviceProperty(
|
||||
vr::k_unTrackedDeviceIndex_Hmd,
|
||||
property,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as u32,
|
||||
&mut err,
|
||||
);
|
||||
assert_eq!(err, vr::ETrackedPropertyError::TrackedProp_Success);
|
||||
assert_eq!(len, buf.len() as u32);
|
||||
|
||||
let slice = unsafe { std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()) };
|
||||
CStr::from_bytes_with_nul(slice)
|
||||
.expect("Failed to convert returned buffer for {property:?} to CStr");
|
||||
};
|
||||
|
||||
test_prop(vr::ETrackedDeviceProperty::Prop_SerialNumber_String);
|
||||
test_prop(vr::ETrackedDeviceProperty::Prop_ManufacturerName_String);
|
||||
test_prop(vr::ETrackedDeviceProperty::Prop_ControllerType_String);
|
||||
}
|
||||
}
|
||||
433
src/vulkan.rs
Normal file
433
src/vulkan.rs
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
use crate::vr;
|
||||
use arc_swap::ArcSwapOption;
|
||||
use ash::vk::{self, Handle};
|
||||
use openxr as xr;
|
||||
use std::ffi::{c_char, CString};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RealSessionData {
|
||||
images: Vec<vk::Image>,
|
||||
pool: vk::CommandPool,
|
||||
bufs: Vec<vk::CommandBuffer>,
|
||||
}
|
||||
|
||||
pub struct VulkanData {
|
||||
_entry: ash::Entry,
|
||||
pub instance: ash::Instance,
|
||||
pub physical_device: vk::PhysicalDevice,
|
||||
pub device: ash::Device,
|
||||
pub queue: vk::Queue,
|
||||
pub queue_family_index: u32,
|
||||
real_data: ArcSwapOption<RealSessionData>,
|
||||
}
|
||||
|
||||
impl Drop for VulkanData {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device.device_wait_idle().unwrap();
|
||||
}
|
||||
match &*self.real_data.load() {
|
||||
// Temporary session - we created these handles, so let's destroy them
|
||||
None => unsafe {
|
||||
self.device.destroy_device(None);
|
||||
self.instance.destroy_instance(None);
|
||||
},
|
||||
// Real session - the handles come from the app, only destroy the command pool we created
|
||||
Some(bufs) => unsafe {
|
||||
self.device.destroy_command_pool(bufs.pool, None);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VulkanData {
|
||||
pub fn get_swapchain_create_info(
|
||||
&self,
|
||||
texture: &vr::VRVulkanTextureData_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
color_space: vr::EColorSpace,
|
||||
) -> xr::SwapchainCreateInfo<xr::vulkan::Vulkan> {
|
||||
let (extent, _) = texture_extent_from_bounds(texture, bounds);
|
||||
xr::SwapchainCreateInfo {
|
||||
create_flags: xr::SwapchainCreateFlags::EMPTY,
|
||||
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT
|
||||
| xr::SwapchainUsageFlags::TRANSFER_DST,
|
||||
format: get_colorspace_corrected_format(
|
||||
vk::Format::from_raw(texture.m_nFormat as _),
|
||||
color_space,
|
||||
)
|
||||
.as_raw() as _,
|
||||
sample_count: texture.m_nSampleCount,
|
||||
width: extent.width,
|
||||
height: extent.height,
|
||||
face_count: 1,
|
||||
array_size: 2,
|
||||
mip_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_texture_to_swapchain(
|
||||
&self,
|
||||
eye: vr::EVREye,
|
||||
texture: &vr::VRVulkanTextureData_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
image_index: usize,
|
||||
submit_flags: vr::EVRSubmitFlags,
|
||||
) -> xr::Extent2Di {
|
||||
let (texture, array_data) =
|
||||
if (submit_flags & vr::EVRSubmitFlags::Submit_VulkanTextureWithArrayData).0 > 0 {
|
||||
let data = unsafe {
|
||||
&*(texture as *const vr::VRVulkanTextureData_t
|
||||
as *const vr::VRVulkanTextureArrayData_t)
|
||||
};
|
||||
(&data._base, Some(data))
|
||||
} else {
|
||||
(texture, None)
|
||||
};
|
||||
|
||||
let guard = self.real_data.load();
|
||||
let data = guard.as_ref().unwrap();
|
||||
let swapchain_image = data.images[image_index];
|
||||
let buf = data.bufs[2 * image_index + eye as usize];
|
||||
drop(guard);
|
||||
|
||||
let (extent, offset) = texture_extent_from_bounds(texture, bounds);
|
||||
log::trace!("{:?} extent: {:?}", eye, extent);
|
||||
unsafe {
|
||||
self.device
|
||||
.begin_command_buffer(
|
||||
buf,
|
||||
&vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// transition swapchain image to TRANSFER_DST
|
||||
let swapchain_res = vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: eye as u32,
|
||||
layer_count: 1,
|
||||
};
|
||||
|
||||
self.device.cmd_pipeline_barrier(
|
||||
buf,
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[vk::ImageMemoryBarrier {
|
||||
src_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||
dst_access_mask: vk::AccessFlags::TRANSFER_READ,
|
||||
old_layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
image: swapchain_image,
|
||||
subresource_range: swapchain_res,
|
||||
..Default::default()
|
||||
}],
|
||||
);
|
||||
|
||||
// transfer image
|
||||
let subresource = vk::ImageSubresourceLayers {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
mip_level: 0,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
};
|
||||
|
||||
let game_image = vk::Image::from_raw(texture.m_nImage);
|
||||
let game_layer = array_data.map(|d| d.m_unArrayIndex).unwrap_or(0);
|
||||
|
||||
let copy = vk::ImageResolve {
|
||||
src_subresource: vk::ImageSubresourceLayers {
|
||||
base_array_layer: game_layer,
|
||||
..subresource
|
||||
},
|
||||
src_offset: offset,
|
||||
dst_subresource: vk::ImageSubresourceLayers {
|
||||
base_array_layer: eye as u32,
|
||||
..subresource
|
||||
},
|
||||
dst_offset: vk::Offset3D::default(),
|
||||
extent,
|
||||
};
|
||||
if texture.m_nSampleCount > 1 {
|
||||
self.device.cmd_resolve_image(
|
||||
buf,
|
||||
game_image,
|
||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
swapchain_image,
|
||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
&[copy],
|
||||
);
|
||||
} else {
|
||||
self.device.cmd_copy_image(
|
||||
buf,
|
||||
game_image,
|
||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
swapchain_image,
|
||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
&[
|
||||
// SAFETY: ImageResolve and ImageCopy have the same fields and layout.
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
std::mem::transmute(copy)
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// transition swapchain image back to OPTIMAL
|
||||
self.device.cmd_pipeline_barrier(
|
||||
buf,
|
||||
vk::PipelineStageFlags::TRANSFER,
|
||||
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
|
||||
vk::DependencyFlags::empty(),
|
||||
&[],
|
||||
&[],
|
||||
&[vk::ImageMemoryBarrier {
|
||||
src_access_mask: vk::AccessFlags::TRANSFER_WRITE,
|
||||
dst_access_mask: vk::AccessFlags::empty(),
|
||||
old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
new_layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
image: swapchain_image,
|
||||
subresource_range: swapchain_res,
|
||||
..Default::default()
|
||||
}],
|
||||
);
|
||||
|
||||
self.device.end_command_buffer(buf).unwrap();
|
||||
|
||||
self.device
|
||||
.queue_submit(
|
||||
self.queue,
|
||||
&[vk::SubmitInfo::default().command_buffers(&[buf])],
|
||||
vk::Fence::null(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
xr::Extent2Di {
|
||||
width: extent.width as _,
|
||||
height: extent.height as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VulkanData {
|
||||
pub fn as_session_create_info(&self) -> xr::vulkan::SessionCreateInfo {
|
||||
let queue_families = unsafe {
|
||||
self.instance
|
||||
.get_physical_device_queue_family_properties(self.physical_device)
|
||||
};
|
||||
let family = queue_families[self.queue_family_index as usize];
|
||||
let queue_index = (0..family.queue_count)
|
||||
.find(|idx| {
|
||||
let queue = unsafe { self.device.get_device_queue(self.queue_family_index, *idx) };
|
||||
queue == self.queue
|
||||
})
|
||||
.expect(&format!(
|
||||
"Could not find queue index for queue {:?} in family {}",
|
||||
self.queue, self.queue_family_index
|
||||
));
|
||||
|
||||
xr::vulkan::SessionCreateInfo {
|
||||
instance: self.instance.handle().as_raw() as _,
|
||||
physical_device: self.physical_device.as_raw() as _,
|
||||
device: self.device.handle().as_raw() as _,
|
||||
queue_family_index: self.queue_family_index,
|
||||
queue_index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post_swapchain_create(&self, images: Vec<vk::Image>) {
|
||||
let pool = unsafe {
|
||||
self.device
|
||||
.create_command_pool(
|
||||
&vk::CommandPoolCreateInfo::default()
|
||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||
.queue_family_index(self.queue_family_index),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let bufs = unsafe {
|
||||
self.device
|
||||
.allocate_command_buffers(
|
||||
&vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
// We have to copy 2 eyes per swapchain image
|
||||
.command_buffer_count(images.len() as u32 * 2),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
if let Some(data) = self
|
||||
.real_data
|
||||
.swap(Some(RealSessionData { images, pool, bufs }.into()))
|
||||
{
|
||||
unsafe {
|
||||
self.device.destroy_command_pool(data.pool, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(data: &vr::VRVulkanTextureData_t) -> Self {
|
||||
let entry = new_entry();
|
||||
let instance = unsafe {
|
||||
ash::Instance::load(
|
||||
entry.static_fn(),
|
||||
vk::Instance::from_raw(data.m_pInstance as _),
|
||||
)
|
||||
};
|
||||
let device = unsafe {
|
||||
ash::Device::load(
|
||||
instance.fp_v1_0(),
|
||||
vk::Device::from_raw(data.m_pDevice as _),
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
_entry: entry,
|
||||
instance,
|
||||
physical_device: vk::PhysicalDevice::from_raw(data.m_pPhysicalDevice as _),
|
||||
device,
|
||||
queue: vk::Queue::from_raw(data.m_pQueue as _),
|
||||
queue_family_index: data.m_nQueueFamilyIndex,
|
||||
real_data: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_temporary(xr_instance: &xr::Instance, system_id: xr::SystemId) -> Self {
|
||||
let entry = new_entry();
|
||||
|
||||
let inst_exts = xr_instance
|
||||
.vulkan_legacy_instance_extensions(system_id)
|
||||
.unwrap();
|
||||
let inst_exts: Vec<CString> = inst_exts
|
||||
.split_ascii_whitespace()
|
||||
.map(|ext| CString::new(ext).unwrap())
|
||||
.collect();
|
||||
let inst_exts: Vec<*const c_char> = inst_exts.iter().map(|ext| ext.as_ptr()).collect();
|
||||
|
||||
let instance = unsafe {
|
||||
entry
|
||||
.create_instance(
|
||||
&vk::InstanceCreateInfo::default()
|
||||
.application_info(
|
||||
&vk::ApplicationInfo::default()
|
||||
.api_version(vk::API_VERSION_1_0)
|
||||
.application_name(c"XRizer temporary session"),
|
||||
)
|
||||
.enabled_extension_names(&inst_exts),
|
||||
None,
|
||||
)
|
||||
.expect("Failed to create temporary Vulkan instance")
|
||||
};
|
||||
|
||||
let physical_device = vk::PhysicalDevice::from_raw(unsafe {
|
||||
xr_instance
|
||||
.vulkan_graphics_device(system_id, instance.handle().as_raw() as _)
|
||||
.expect("Failed to get temporary Vulkan physical device") as _
|
||||
});
|
||||
|
||||
let dev_exts = xr_instance
|
||||
.vulkan_legacy_device_extensions(system_id)
|
||||
.unwrap();
|
||||
let dev_exts: Vec<CString> = dev_exts
|
||||
.split_ascii_whitespace()
|
||||
.map(|ext| CString::new(ext).unwrap())
|
||||
.collect();
|
||||
let dev_exts: Vec<*const c_char> = dev_exts.iter().map(|ext| ext.as_ptr()).collect();
|
||||
|
||||
// find whatever graphics queue family
|
||||
let queue_family_index =
|
||||
unsafe { instance.get_physical_device_queue_family_properties(physical_device) }
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, family)| {
|
||||
(family.queue_flags.contains(vk::QueueFlags::GRAPHICS)).then_some(idx)
|
||||
})
|
||||
.unwrap() as u32;
|
||||
|
||||
let device = unsafe {
|
||||
instance
|
||||
.create_device(
|
||||
physical_device,
|
||||
&vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(std::slice::from_ref(
|
||||
&vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue_family_index)
|
||||
.queue_priorities(&[1.0]),
|
||||
))
|
||||
.enabled_extension_names(&dev_exts),
|
||||
None,
|
||||
)
|
||||
.expect("Could not create temporary vulkan device")
|
||||
};
|
||||
|
||||
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
|
||||
|
||||
Self {
|
||||
_entry: entry,
|
||||
instance,
|
||||
physical_device,
|
||||
device,
|
||||
queue,
|
||||
queue_family_index,
|
||||
real_data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_colorspace_corrected_format(format: vk::Format, color_space: vr::EColorSpace) -> vk::Format {
|
||||
// https://github.com/ValveSoftware/openvr/wiki/Vulkan#image-formats
|
||||
match color_space {
|
||||
vr::EColorSpace::ColorSpace_Auto | vr::EColorSpace::ColorSpace_Gamma => match format {
|
||||
vk::Format::R8G8B8A8_UNORM | vk::Format::R8G8B8A8_SRGB => vk::Format::R8G8B8A8_SRGB,
|
||||
vk::Format::B8G8R8A8_UNORM | vk::Format::B8G8R8A8_SRGB => vk::Format::B8G8R8A8_SRGB,
|
||||
_ => panic!("Unhandled texture format: {format:?}"),
|
||||
},
|
||||
vr::EColorSpace::ColorSpace_Linear => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_extent_from_bounds(
|
||||
texture: &vr::VRVulkanTextureData_t,
|
||||
bounds: vr::VRTextureBounds_t,
|
||||
) -> (vk::Extent3D, vk::Offset3D) {
|
||||
let width_min = bounds.uMin * texture.m_nWidth as f32;
|
||||
let width_max = bounds.uMax * texture.m_nWidth as f32;
|
||||
let height_min = bounds.vMin * texture.m_nHeight as f32;
|
||||
let height_max = bounds.vMax * texture.m_nHeight as f32;
|
||||
|
||||
(
|
||||
vk::Extent3D {
|
||||
width: (width_max - width_min).abs() as u32,
|
||||
height: (height_max - height_min).abs() as u32,
|
||||
depth: 1,
|
||||
},
|
||||
vk::Offset3D {
|
||||
x: width_min.min(width_max) as i32,
|
||||
y: height_min.min(height_max) as i32,
|
||||
z: 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn new_entry() -> ash::Entry {
|
||||
#[cfg(not(test))]
|
||||
unsafe {
|
||||
ash::Entry::load().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
unsafe {
|
||||
ash::Entry::from_static_fn(ash::StaticFn {
|
||||
get_instance_proc_addr: fakexr::vulkan::get_instance_proc_addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
26
tests/input_data/actions.json
Normal file
26
tests/input_data/actions.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"action_sets": [
|
||||
{
|
||||
"name": "/actions/set1",
|
||||
"usage": "leftright"
|
||||
},
|
||||
{
|
||||
"name": "/actions/set2",
|
||||
"usage": "leftright"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "/actions/set1/in/boolact",
|
||||
"requirement": "mandatory",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "/actions/set1/in/vecact",
|
||||
"requirement": "optional",
|
||||
"type": "vector1"
|
||||
}
|
||||
],
|
||||
"default_bindings": [],
|
||||
"localization": []
|
||||
}
|
||||
7
tests/tests.rs
Normal file
7
tests/tests.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use libloading::{Library, Symbol};
|
||||
|
||||
#[test]
|
||||
fn smoke_test() {
|
||||
let path = test_cdylib::build_current_project();
|
||||
let _ = unsafe { Library::new(path) }.unwrap();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue