2015年1月23日金曜日

SwiftでOculus Rift DK2向け仮想デスクトップアプリを作った

先日Oculus Rift DK2を買ったので、Swiftで仮想デスクトップアプリを作りました。
http://vrdisplay.tumblr.com/post/108885580424/vrdisplay-vr-desktop-application-for-mac-with

このアプリを開発するにあたって、以下の点で色々と苦労しました。
  1. Swift関数ポインタを引数に取る関数がうまく呼べない
  2. 画面キャプチャが重い
  3. 仮想ディスプレイがソフトウェアレンダリングでしか動かない
  4. ウィンドウごとのリアルタイムキャプチャが取れない
  5. 開発中にVR酔いになる
  6. DK2でも解像度が足りない
  7. Mac AppStore用にsandbox化するとOculus Riftが動かない
1. Swift関数ポインタを引数に取る関数がうまく呼べない
Swift + OpenGL + OculusSDKというかなり無理のある構成だったのですが、OpenGLのセットアップを行うときに、CADisplayLinkを用いて画面のリフレッシュに合わせてレンダリングを行うということをしようとしたのですが、
どうしてもSwiftからいい感じに関数ポインタを渡すことができませんでした。
そのため、ここは諦めて素直にObjective-Cを利用し、あとは基本Swiftという構成でなんとか乗り切りました。

OpenGLやOculusに関しては意外にもすんなりとSwiftで利用することができたので、
とにかく関数ポインタをコールバックとして渡す系が鬼門なのかなという印象です。

2. 画面キャプチャが重い
ただでさえOculusの駆動にマシンパワーが必要で、さらに3Dのレンダリングも利用しているので、画面データの転送にはなるべく軽快に済ませる必要がありました。
はじめはCGWindowListCreateImageを利用して毎フレーム画像を作っていたのですが、さすがに重すぎたために、AVCaputureScreenInputを利用して書き直しました。
この関数は毎フレームのキャプチャに向いていて、iOSのカメラのようにひたすらCMSampleBufferが届くので、それをOpenGLのテクスチャとして貼り付けてレンダリングすることで、軽快な動作を実現しました。

3. 仮想ディスプレイがソフトウェアレンダリングでしか動かない
このアプリを利用することによって、実際に存在するディスプレイの制約から解放されます。すなわち、Oculus Riftさえあれば、実際のディスプレイは必要ないということです。

なので、Kernel Extensionを利用して、Macに仮想的なディスプレイが存在していると思い込ませ、そのディスプレイのキャプチャをリアルタイムでOculus Riftで表示してあげれば、
ディスプレイは無限に増やせることになります。

実際にEWProxyFramebufferなどを利用して、仮想ディスプレイを作り出し、それらをVRDisplay上で表示するテストなどを行ってみたのですが、ハードウェアアクセラレーションが効かないようで、非常に重たく、使い物にならないというのが現状でした。

なので、今回のバージョンでは仮想ディスプレイを利用することを一旦諦め、
潔く一枚の画面が目の前に表示されるという方法にしています。

また、現在開発に利用しているMacBookProが、ディスプレイを2枚までしかサポートしていないので、
もしかしたら複数枚サポートしているMac Proなどでは、ある程度軽快に動かすことができるのかもしれません。

4.ウィンドウごとのリアルタイムキャプチャが取れない
仮想ディスプレイデバイスを作成して、仮想空間に好きなだけディスプレイを置く作戦には失敗したので、どうにかしてウィンドウのキャプチャを取り続け、それらを独自に再構成することはできないものかと調べてみました。

2で説明した、もともと利用していたCGWindowListCreateImageは、個別のWindowのキャプチャを取れる点が非常に良いのですが、その分動作が重く、毎フレーム取得しようとすると、極端にFPSが低下し、全くもって使い物になりません。

MacのWindowServerに相当するアプリを一から作ることができれば、この辺りは好き勝手にできるようですが、ドキュメントなどが一切存在しないため、不可能に近い状態です。

5.開発中にVR酔いになる
例えば先ほどのFPSが非常に低下したバージョンを実際に試すと、頭を動かした瞬間に目の前全体がガクガクし、その勢いで強烈な吐き気がこみ上げてきます。

開発の序盤のうちは、正しくレンダリングができておらず、視界の隅を気にしてしまった瞬間に気分が悪くなり、そのまま復活せずにその日の開発は終了せざるを得なくなることが多々ありました。

幸いにして段々と自分の体に耐性がつき、またアプリもまともに動くなるように連れて、このような現象にさいなまれることは減りましたが、あるゲームベンダーは開発者がVR酔いをしてしまったがために、Oculus Riftへの対応を見送ったというニュースが出るくらいなので、これはこれで非常に大きな問題であるように思います。

80年代SFホラー『Routine』のOculus Riftサポートが廃止に「現行のキットでは開発全員がVR酔いする」

6.DK2でも解像度が足りない
Oculus Rift DK2はSamsungのGalaxy Note 3のフロントパネルをそのまま利用しており、解像度は1920x1080で、片目あたり960x1080の画面が利用されています。両眼でFullHDなので、テレビであれば非常に高精細で綺麗にみえますが、虫眼鏡で拡大して目の前に置くような用途では、ドットとドットの間が目立ちます。特にプログラムを書いている時などは、フォントを大きめに設定するか、画面に近づかないとうまく文字を読むことができません。

このあたりは、ハードウェア的な進化を待つしかなさそうですが、他にもソフトウェア的に虫眼鏡を実装したり、その他拡大縮小などの便利系コマンドを用意することで、多少の改善が見込めないかと考えています。

7.Mac AppStore用にSandbox化するとOculus Riftが動かない
Mac AppStoreでアプリを公開する場合には、sandbox化と呼ばれる作業が必須項目になり、USBデバイスやカメラにアクセスするようなアプリケーションは、Entitlementsに宣言しなくてはならなくなったり、アクセスするフォルダを事前に書いておかなければなりません。

XcodeのSandbox化用のUIから、USBを利用するというチェックボックスがあったのでそのチェックを入れてみたところ、画面は正しく表示されるものの、ヘッドトラッキング/ポジショントラッキングが完全に利用できなくなってしまいました。

何かしらのキーが足りていないせいだと思われるのですが、いろいろと調べてみてもあまり情報が載っておらず、またArduinoと通信するアプリケーションを作成している記事にあるのと同様の設定をしてみても利用できないので、現状AppStoreでは正常に動作するものが公開できませんでした。

他にもUSBを利用する系のアプリをSandbox化したところ、正しく動作しないという例が存在しており、Oculus Riftを利用するアプリは基本的にはMac AppStoreで配ることができなさそうです。
Apple XCode entitlements Sandbox - つらつらと日々のメモを公開するブログ

まとめ

  • SwiftでもCのライブラリは素直に利用できる
  • Oculus Riftすごい
  • 仮想ディスプレイのハードウェアアクセラレーションは課題。Mac Proならソフトウェアレンダリングでも速いかも
  • USBデバイスを使うアプリはMac AppStoreでは基本的にリリースできない
  • より高解像度のOculusが待ち遠しい


というわけで、もし気になりましたらダウンロードしてみてください。
Gumroadで$4で販売中です。
Oculus Rift DK2の元が取れたら万々歳かなと思っています。