XR SDK ディスプレイサブシステムは、テクスチャの割り当て、フレームのライフサイクル、cadence のためのブロッキングなどのインターフェースを提供します。
デバイス SDK のなかには、通常のグラフィックス API ではなく、SDK 自体でテクスチャを割り当てる必要があるものもあります。XR SDK ディスプレイサブシステムを使用している場合は、SDKテクスチャへの転送やコピーを外部プラグインに依存する必要はありません。
ディスプレイサブシステムを使うと、プラグインプロバイダーがテクスチャを割り当てることができるようになります。可能な限り、Unity は不必要なコピーを避けるためにテクスチャに直接レンダリングします。必要に応じて、Unity はテクスチャを割り当てることもできます。
以下のケースでは、Unity はテクスチャに直接レンダリングすることができず、代わりに中間テクスチャにレンダリングしてから、テクスチャに転送かコピーします。
EXT_multisampled_render_to_texture
拡張を参照してください。kUnityXRRenderTextureFlagsLockedWidthHeight
フラグが設定されていて、renderScaleが 1.0 でない場合。kUnityXRenderTextureFlagsWriteOnly
フラグが設定されていて、Unity がテクスチャから読み直す必要がある場合。PC でもモバイルでも、エンジンは常にプロバイダーのテクスチャに解決します。エンジンは、暗示的な解決 (テクスチャ拡張のためのマルチサンプルレンダリングがあるモバイルの場合) または明示的な解決を行います。
モバイルでは、プロバイダーは kUnityXRenderTextureFlagsAutoResolve
フラグを有効にして、1 サンプルでテクスチャを作成します。
UnityXRFrameSetupHints.appSetup.sRGB
を使用して、Unity が sRGB テクスチャ形式へのレンダリングを期待しているかどうかをチェックします。プロバイダーは最終的に、 UnityXRRenderTextureDesc
の colorFormat
フィールドから出力テクスチャ形式を選択します。形式が sRGB タイプの場合、Unity はアクティブなプロジェクトが選択している色空間に応じて、sRGB ライトをオンまたはオフにします。コンポジターで sRGB からリニアへの変換を行う sRGB テクスチャから常にサンプリングする必要があります。
SDK が深度情報を必要とする場合は、前述のカラーバッファと同じ方法で深度バッファを取得することができます。UnityXRRenderTextureDesc
上の nativeDepthTex
値は、ネイティブのリソースを指定します。デフォルトでは、nativeDepthTex
が kUnityXRRenderTextureIdDontCare
に設定されている場合、類似した Desc を持つテクスチャ間で深度バッファを共有しようとします。
SDK が深度情報を必要としない場合は、UnityXRRenderTextureDesc::depthFormat
を kUnityXRDepthTextureFormatNone
に設定して、不要な解決を避ける必要があります。
送信時 (後述の 着信未確認中のフレームの送信 セクションを参照) に、Unity がレンダリングする画像を SDK がダブルバッファまたはトリプルバッファにする必要がある場合に対処するために、フレームごとに異なるテクスチャ ID を指定できます。 プロバイダープラグインは、UnityXRRenderTextureId
のコレクションを管理します。
フレームのライフサイクルを担当するメソッドは 2 つあります。レンダリング開始直前に発生する PopulateNextFrameDesc
と、レンダリング完了直後に発生する SubmitCurrentFrame
です。どちらのメソッドもグラフィックススレッドで呼び出されます。
PopulateNextFrameDesc
の間、ディスプレイプロバイダーは以下のことを行うことが期待されます。
SubmitCurrentFrame
で行うこともできます。nextFrame
パラメーターで Unity に伝えます。SubmitCurrentFrame
メソッドの中で、ディスプレイプロバイダーは以下のことを行うことが期待されます。
PopulateNextFrameDesc
を待つ代わりに、cadence を待機。HMD のディスプレイにレンダリングする際に、可能な限り待機を少なくし、最大のスループットを維持するためには、ポーズの取得やテクスチャの送信などのタイミングを正確に取る必要があります。それぞれの HMD には、コンポジターが動作するネイティブの最新情報に更新するレートがあります。それ以上の速度でレンダリングすると、タイミングが合わなかったり、作業が重複したりして、最適ではなくなってしまいます。
Unity は、フレームのライフサイクルの間、ディスプレイプロバイダーがブロックすること、つまりフレームの cadence を待つことを想定します。Unity は、ブロッキングコールから “ウェイクアップ” した直後にレンダリングコマンドの送信を開始します。“ウェイクアップ” 時刻は、特定のウィンドウでコンポジターに同期させる必要があります。いくつかの SDK では、ヒューリスティックに基づいたフローティングウェイクアップタイムウィンドウを提供しています。Meta/Oculus では、これを “queue ahead” と呼んでいます (詳細については、Oculus 開発者ドキュメント を参照)。Valve 社はこれを “running start” と呼んでいます (このプレゼンテーション のスライド 18 と 19 を参照)。
Unity は、フレームのライフサイクルが完了するのを待ってから、ポーズに依存したグラフィックスコマンドの送信を開始します。
プロバイダーは、PopulateNextFrameDesc
または SubmitCurrentFrame
のいずれかで cadence を待つことができます。
Unity がグラフィックススレッドでフレームのグラフィックスコマンドを送信する間、次のフレームのシミュレーションループはメインスレッドで実行されます。それには、物理演算、スクリプトロジックなどが含まれます。PopulateNextFrameDesc
は、すべてのレンダリングコマンドが送信された後、次のフレームのシミュレーションと、そのフレームでスケジューリングされたすべてのグラフィックスジョブが完了してから、グラフィックススレッドで呼び出されます。PopulateNextFrameDesc
が待機するグラフィックスジョブの 1 つは、現在のフレームの SubmitCurrentFrame
です。これが、 SubmitCurrentFrame
で cadence を待つことが有効な理由です。さらに、Unity は、 PopulateNextFrameDesc
が完了するまで、レンダリングを開始しません。
これらの詳細を考慮すると、SubmitCurrentFrame
で cadence を待つことと、 PopulateNextFrameDesc
で cadence を待つことには、いくつかの妥協点があります。例えば、SubmitCurrentFrame
で cadence を待つと、シミュレーション中にアプリケーションが負荷の高いグラフィックスジョブをスケジュールしている場合、パフォーマンスに問題が生じます。SubmitCurrentFrame
はレンダリング後に実行されるようにスケジュールされているため、アプリケーションがスケジュールしたグラフィックスジョブは、SubmitCurrentFrame
の後、 PopulateNextFrameDesc
の前に実行されることになります。この場合、プロバイダーは SubmitCurrentFrame
で待機しており、その後、Unity がレンダリングを開始することを期待して目を覚まします。ただし、Unity は、PopulateNextFrameDesc
を呼び出す前に、アプリケーションがスケジューリングしたグラフィックスジョブを処理し、その結果、Unity がレンダリングを開始できるようになります。この、レンダリングのために目覚めてから、update メソッドでスケジュールされたグラフィックスジョブを処理するまでに発生する遅延が、待機の原因となります。開発者は、レンダリング後 にグラフィックスジョブをスケジュールし、グラフィックスジョブを SubmitCurrentFrame
の前にスケジュールすることによって、この問題を最適化することができます。
プロバイダーが SubmitCurrentFrame
の cadence を待機している間に、PopulateNextFrameDesc
で cadence を待機し、Unity のメインスレッドを完全にブロックして、グラフィックスジョブの計算をメインスレッドと並行して実行することは可能です。これは、シミュレーションや他のグラフィックスジョブがすでに完了しているため、問題ありません。問題は、シミュレーションやグラフィックスのスレッドがあまりにも多くの時間を占め、デバイスのターゲットフレームレートを超えてしまう場合に発生します。この場合、PopulateNextFrameDesc
が cadence の次のサイクルを待っている間、フレームレートが半分になってしまいます。
Unity が SubmitCurrentFrame
を呼び出すと、最後のフレームで設定したテクスチャがレンダリングされるか、Unity がグラフィックスドライバーにレンダーコマンドを送信してテクスチャをレンダリングすることになります。Unity はこれでレンダリングを終了したので、コンポジターに渡すことができます。
レンダリングする次のフレームをブロックまたは取得した後、次のフレームでどのテクスチャにレンダリングするかや、レンダリングパスのレイアウトを Unity に伝える必要があります (後述の “レンダーパス” を参照)。
UnityXRRenderPass
には、カリングパスとシーングラフのトラバーサルが含まれることがあります。これはリソースを大量に消費する操作なので、シングルパスレンダリングのようなトリックを使って、Unityがこの操作を行う回数を制限するようにしてください。
各 UnityXRRenderPass
には、出力テクスチャ (テクスチャ配列でもよい) と、出力の UnityXRRenderParams
(ビュー、投影マトリクス、レンダリング先の矩形、またはテクスチャ配列スライスなど) が含まれます。
各フレームごとに、ディスプレイプロバイダーは UnityXRRenderPass
を設定し、Unity が次のフレームにレンダリングする UnityXRRenderTextureId
を記入します。
UnityXRRenderPass
の使用例は以下の通りです。
API はこれらの追加ケースをサポートします (ただし、現時点では Unity が正しく反応しない可能性があります)。
以下の仮定は安全と言えます
ノート: シングルパスレンダリングの設定はユーザーのシェーダーに影響するため、Unity プロジェクトと XR SDK は同じ設定 (有効/無効) を使用する必要があります。シングルパスレンダリングが有効になっているかどうかを確認するには、 UnityXRFrameSetupHints.appSetup.singlePassRendering
を使用します。
2 つのレンダリングパスは、それらの cullingPassIndex
が同じ値に設定されている場合、カリングパスを共有することができます。cullingPassIndex
は、どの UnityXRCullingPass
を使用するかを選択します。カリングパスは、UnityXRNextFrameDesc
に記入する必要があります。