OVR 層
OVR 層は、OpenVR の生のバインディングを包む
Section titled “OVR 層は、OpenVR の生のバインディングを包む”OVR 層(FloatSoda.OVR)は、OpenVR の初期化、オーバーレイ、イベント、アクション入力を、C# の型で包む層です。ほかの FloatSoda のプロジェクトを参照せず、ネイティブの openvr_api.dll を同梱します。上の層(エンジン層、フレームワーク層)は、この層の型を通して OpenVR を使います。
openvr_api.cs、openvr_api.dll OpenVR の生のバインディングと、ネイティブライブラリ ▲OVRApplication、オーバーレイの型、イベントディスパッチャー、InputAction C# の型で包んだ層 ▲エンジン層、フレームワーク層直したい箇所は、この図で探します。OpenVR の呼び出し自体を直したいときは、2段目の型を見ます。生のバインディングは、次の節のとおり、直さずに読みます。
OpenVR の資料の読み方
Section titled “OpenVR の資料の読み方”OpenVR の挙動は、FloatSoda のコードからは変えられません。調べるときの入口は、OpenVR の Wiki(https://github.com/ValveSoftware/openvr/wiki)です。C# の呼び出し署名は、同梱の src/FloatSoda.OVR/openvr_api.cs で確認します。挙動が資料と食い違うときは、資料とバインディングと実行環境のバージョンを記録し、食い違いを未解決として報告します。
openvr_api.cs は、Valve が自動生成したバインディングです。ファイルの冒頭に、編集しないよう書かれています。名前空間は FloatSoda.OVR です。
資料を読むときは、次の順に進めます。
- 調べたい API を
openvr_api.csで探す - 同じ名前の項目を Wiki で探して、説明を読む
- 署名に食い違いがあれば、
openvr_api.csの署名に従う
入力方式を例にすると、該当するのは OpenVR.Overlay.SetOverlayInputMethod です。FloatSoda では OverlayInput.Method が包んでいます。
アプリケーションとオーバーレイ
Section titled “アプリケーションとオーバーレイ”OVRApplication は、作ると OpenVR.Init を呼びます。失敗は例外になります(VRInitializeException など)。種類を決める OVRAppInfo の Type の既定は Overlay です。Dispose で、OpenVR.Shutdown を呼びます。
AppKey は、SteamVR がアプリケーションを識別するキーです。型の説明が求める形は、ASCII の小文字、数字、ドット、アンダースコアだけで、127 文字までです。この規則は、Value の init アクセサーが検証します。つまり、with 式や初期化子で Value を設定すると、大文字を含む値は FormatException、128 文字以上は ArgumentOutOfRangeException になります。一方で、new AppKey("...") のコンストラクターは、検証を通りません。コンストラクターに渡す値は、規則に合う値にすること。
オーバーレイは、IOverlay の型で表します。種類は3つです。
| 型 | 位置の決まり方 | 追加で持つもの |
|---|---|---|
DashboardOverlay |
SteamVR のダッシュボードが管理する。位置を持たない | Thumbnail(ダッシュボードのサムネイル) |
DeviceTrackedOverlay |
追跡デバイスからの相対位置。既定の追従先は HMD | Visibility、Intersection、Transform(Target で追従先を選ぶ) |
WorldSpaceOverlay |
追跡空間の絶対位置。Origin の既定は立った状態の空間 |
Visibility、Intersection、Transform |
どの種類も、機能を小さな部品に分けて持ちます。Opacity、WidthInMeters、Curvature、Texture、State、Vibration、Input、EventDispatcher です。位置を動かせる2種類は、さらに Visibility、Intersection、Transform を持ちます。
OverlayIdentity は、作るときに OpenVR.Overlay.CreateOverlay を呼び、Dispose で DestroyOverlay を呼びます。OverlayTexture は、絵を2通りで設定します。
| 呼び出し | 渡すもの | 使いどころ |
|---|---|---|
FromFile |
画像ファイルのパス。OpenVR がファイルを読む | 画像を出すだけのとき |
FromTexture_t |
OpenVR のテクスチャ記述(Texture_t)。GL テクスチャのハンドルなどを包む |
描画した結果を出すとき(エンジン層がこちらを使う) |
どの型で作るかは、自分のオーバーレイが、ダッシュボードのタブか、頭やコントローラーに付くものか、空間に固定するものかで選びます。
イベントは、ポーリングしたスレッドで呼ばれる
Section titled “イベントは、ポーリングしたスレッドで呼ばれる”OpenVR のイベントは、VREventDispatcher に Register した処理が、PollEvents を呼んだスレッドで呼ばれます。PollEvents を呼ぶまで、ハンドラーは動きません。
ディスパッチャーは、見る先が違う2つがあります。
| 型 | ポーリング先 | 受け取るイベントの例 |
|---|---|---|
VRSystemEventDispatcher |
プロセス全体のシステムのイベントの待ち行列 | VREvent_Quit、デバイスの接続 |
OverlayEventDispatcher |
そのオーバーレイ宛のイベントの待ち行列 | VREvent_FocusEnter、VREvent_MouseMove、VREvent_MouseButtonDown |
イベントを配るとき、ハンドラーの一覧は複製されます。そのため、ハンドラーの中で Register や Unregister を呼べます。
ハンドラーの中で触る状態が、どのスレッドのものかを確かめます。FloatSoda は、システムのイベントも、入力を持つオーバーレイのイベントも、メインスレッドで PollEvents を呼びます。ハンドラーはメインスレッドで動くので、描画スレッドの状態に触れる処理は、描画スレッドへ渡します(→ エンジン層)。
アクション入力とマニフェスト
Section titled “アクション入力とマニフェスト”コントローラーのボタンは、アクション入力で読みます。InputAction<T> が1つの入力を表し、T に使えるのは bool、float、System.Numerics.Vector2 だけです。ほかの型は NotSupportedException で拒否されます。InputActionMap が複数のアクションを束ね、Enabled でまとめて有効にしたり無効にしたりします。
VRInputUpdater が、マニフェストを SteamVR に登録し、毎周 Update でアクションの値とイベント(OnPerformed、OnReleased)を更新します。入力の定義は C# のコードだけで書きます。マニフェストの JSON は、FloatSoda が自動で作ります。
2種類のマニフェストを比べる
Section titled “2種類のマニフェストを比べる”SteamVR にはマニフェストが2種類あり、FloatSoda での扱いが違います。アクションマニフェストは自動で作られますが、アプリケーションマニフェストは作られません。
| 観点 | アクションマニフェスト | アプリケーションマニフェスト |
|---|---|---|
| 何のためか | 入力アクションの名前、型、既定の割り当てを SteamVR に伝える | プロセスを app_key に結びつけ、AutoLaunch やシーン遷移などの連携を使えるようにする |
| だれが作るか | FloatSoda が起動のたびに自動で作る。入力の定義が1つ以上あるときだけ | 利用者。FloatSoda は作らない |
| どこに置くか | 一時ディレクトリ配下の %TEMP%/FloatSoda/{AppKey}/input |
利用者が決める絶対パス |
| いつ登録するか | 起動時に VRInputUpdater が IVRInput.SetActionManifestPath で登録する |
OVRApplications.AddManifest(path, temporal) を呼んだとき |
| 使えないと何が起きるか | 警告にして続行する。オーバーレイの表示は続き、アクション入力だけ使えない | OVRApplication.Identify() が UnknownApplication で失敗し、警告にして続行する。マニフェスト連携の機能だけ動かない |
アプリケーションマニフェストは、OVRApplication.Identify() がプロセスと app_key を結びつけるときに要ります。フレームワークの FloatSodaApp は起動時に Identify() を呼びますが、AppKey が登録されていなくても止まりません。OVR 層を単独で使う場合は、Identify() の呼び出しと失敗時の処理を、呼び出し側が決めます。AutoLaunch やシーン遷移を使うアプリは、AddManifest で登録します。
足りない側は、症状で見分けます。入力が動かなければアクションマニフェストを、AutoLaunch などが動かなければアプリケーションマニフェストを確かめます。
つまずきやすい点
Section titled “つまずきやすい点”OpenVR まわりで詰まりやすい点を、症状、原因、取る行動の順に並べます。
マニフェストが2種類あり、FloatSoda での扱いが違う
Section titled “マニフェストが2種類あり、FloatSoda での扱いが違う”- 症状:
AutoLaunchやシーン遷移が動かない - 原因: アクションマニフェストは自動で登録されるが、アプリケーションマニフェストは自動では登録されない
- 取る行動:
OVRApplications.AddManifestでアプリケーションマニフェストを登録する。詳細は前の節の比較表にある
OpenVR.IsHmdPresent() が True でも、初期化が失敗する
Section titled “OpenVR.IsHmdPresent() が True でも、初期化が失敗する”- 症状:
OVRApplicationを作ると、Init_HmdNotFoundのVRInitializeExceptionになる - 原因:
IsHmdPresent()がTrueでも起きる。条件は分かっていない - 取る行動: 初期化の成否は、
IsHmdPresent()ではなく、OVRApplicationの例外で判断する。SteamVR の状態と HMD の接続を確かめる - 確かめた程度: 1台の環境で再現した。どの状態で起きるかは分かっていない
初期化が通ったあと、OverlayIdentity の作成で NullReferenceException になる
Section titled “初期化が通ったあと、OverlayIdentity の作成で NullReferenceException になる”- 症状:
OVRApplicationの作成は通り、new OverlayIdentity(...)でNullReferenceExceptionになる。スタックトレースはOverlayIdentityのコンストラクターのOpenVR.Overlay.CreateOverlayの行を指す - 原因: 分かっていない。スタックトレースから、
OpenVR.Overlayがnullだった可能性を推定している - 取る行動: 起きたときは、SteamVR の起動状態とスタックトレースの全文を添えて、Issue に報告する(→ 不具合を切り分けて報告する)
- 確かめた程度: 一度だけ再現した。同じ操作で起きないときもあった
AppKey の検証が、コンストラクターでは働かない
Section titled “AppKey の検証が、コンストラクターでは働かない”- 症状: 大文字を含む
AppKeyを作っても、例外にならない - 原因: 検証は
Valueのinitアクセサーにあり、new AppKey("...")のコンストラクターは通らない - 取る行動: 小文字、数字、ドット、アンダースコアだけで、127 文字までの値を渡す
- 確かめた程度: 実行して確かめた。大文字、200 文字、空文字の3通りで、例外にならなかった
ポインター入力が届くのは、ダッシュボードのオーバーレイだけ
Section titled “ポインター入力が届くのは、ダッシュボードのオーバーレイだけ”- 症状: ダッシュボード以外のオーバーレイで、
GestureDetectorのコールバックが呼ばれない - 原因: エンジン層の
OverlayWindowが入力源を作るのは、ダッシュボードのときだけ。ControllerPointerSystemは、どこからも呼ばれていない - 取る行動: ダッシュボード以外へのポインター入力は、Issue で追跡している(追跡: #182)
- 確かめた程度: コードから読んだ。実機では確かめていない
ControllerPointerSystem は、MakeOverlaysInteractiveIfVisible を使わずに、コントローラーのレイとオーバーレイの交差を毎フレーム計算します。SteamVR の入力の横取りが起きないので、VRChat の AFK 判定を避けられる、とクラスの説明に書かれています。これはクラスの説明にあるだけで、VRChat では確かめていません。
OVR 層だけでオーバーレイを出して入力を受ける実験は、実験: OVR 層 にあります。