Initial commit (but not really)

This commit is contained in:
Shawn Wallace 2024-07-26 15:33:36 -04:00
commit 6d13e87d24
46 changed files with 63131 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/runtime

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "openxr-sdk"]
path = openxr-sdk
url = https://github.com/khronosgroup/openxr-sdk

847
Cargo.lock generated Normal file
View 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
View 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" }

1072
build.rs Normal file

File diff suppressed because it is too large Load diff

32
fakexr/Cargo.lock generated Normal file
View 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
View 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
View 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
View 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
View 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
View 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()
}

View 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";
}

View 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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

1
openxr-sdk Submodule

@ -0,0 +1 @@
Subproject commit f90488c4fb1537f4256d09d4a4d3ad5543ebaf24

146
src/applications.rs Normal file
View 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
View 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
View 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
View 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
View 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),
],
}
}
}

View 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

File diff suppressed because it is too large Load diff

View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
})
}
}

View 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
View 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();
}