同期フレームワークは、Android グラフィック システムにおけるさまざまな非同期操作の依存関係を明示的に記述します。このフレームワークには、コンポーネントがバッファの解放時期を示せる API が用意されています。また、このフレームワークにより、カーネルからユーザー空間までにあるドライバ同士、およびユーザー空間プロセス同士で同期プリミティブを渡すこともできます。
たとえば、アプリが GPU で実行する作業をキューに入れるとします。GPU はその画像の描画を開始します。画像はまだメモリに描画されていませんが、GPU の処理が終了する時期を示すフェンスとともにバッファ ポインタがウィンドウ コンポジタに渡されます。ウィンドウ コンポジタは事前に処理を開始し、作業をディスプレイ コントローラに渡します。同様に、CPU 処理も事前に行われます。GPU が処理を終了すると、ディスプレイ コントローラは直ちに画像を表示します。
同期フレームワークにより、実装者独自のハードウェア コンポーネントで同期リソースを利用することも可能です。最後に、このフレームワークではデバッグに有用なグラフィック パイプラインを可視化できます。
明示的な同期
明示的な同期は、グラフィック バッファのプロデューサーとコンシューマーがそれぞれバッファの使用を終了したときにシグナルで通知を行うことを可能にするものです。明示的な同期はカーネル空間に実装されます。
明示的な同期のメリットには次のものがあります。
- デバイス間での動作の違いが少ない
- 優れたデバッグ サポート
- 改善されたテスト指標
同期フレームワークには次の 3 つのオブジェクト タイプがあります。
sync_timeline
sync_pt
sync_fence
sync_timeline
sync_timeline
は単調増加するタイムラインであり、ベンダーが GL コンテキスト、ディスプレイ コントローラ、2D ブリッタなどのドライバ インスタンスごとに実装する必要があります。sync_timeline
は、特定のハードウェアのカーネルに送信されたジョブをカウントします。sync_timeline
によって操作の順序が保証され、ハードウェア固有の実装が可能になります。
sync_timeline
を実装する際のガイドラインは次のとおりです。
- デバッグを簡略化するために、すべてのドライバ、タイムライン、フェンスに実用的な名前を付けます。
- デバッグ出力を読みやすくするために、
timeline_value_str
演算子とpt_value_str
演算子をタイムラインに実装します。 - 塗りつぶしの
driver_data
を実装し、必要に応じて GL ライブラリなどのユーザー空間ライブラリに非公開タイムライン データへのアクセス権を付与します。data_driver
によって、ベンダーはコマンドラインをビルドする際のベースとなる不変のsync_fence
とsync_pts
に関する情報を渡せます。 - ユーザー空間での明示的なフェンスの作成やフェンスのシグナルを許容しないでください。シグナルまたはフェンスを明示的に作成することは、パイプライン機能を停止させるサービス拒否攻撃につながります。
sync_timeline
、sync_pt
、sync_fence
の各要素に明示的にアクセスしないでください。必要な関数はすべて API に用意されています。
sync_pt
sync_pt
は sync_timeline
上の 1 つの値またはポイントです。ポイントには、アクティブ、シグナル、エラーの 3 つの状態があります。ポイントはアクティブ状態から始まり、シグナル状態またはエラー状態に遷移します。たとえば、画像コンシューマーがバッファを必要としなくなると、sync_pt
がシグナルされ、画像プロデューサーはバッファに再度書き込めるようになったことを認識します。
sync_fence
sync_fence
は sync_pt
値の集まりであり、値ごとに sync_timeline
上の親(ディスプレイ コントローラや GPU など)が異なることも多くあります。sync_fence
、sync_pt
、sync_timeline
は、ドライバとユーザー空間が依存関係を伝えるために使用する主要なプリミティブです。カーネル ドライバまたはハードウェア ブロックはコマンドを順番に実行するため、フェンスがシグナル状態になることによって、フェンスの前に発行されたすべてのコマンドが完了したことが保証されます。
同期フレームワークでは、複数のコンシューマーまたはプロデューサーがバッファの使用を終了した際にシグナル通知して、1 つの関数パラメータで依存関係の情報を伝えることができます。フェンスの背後にはファイル記述子があり、カーネル空間からユーザー空間に渡されます。たとえば、2 つの独立した画像コンシューマーがバッファの読み込みを完了したことを示す 2 つの sync_pt
値を 1 つのフェンスに含めることができます。このフェンスが送信されると、画像プロデューサーは両方のコンシューマーが使用を完了したことを認識します。
sync_pt
値のようなフェンスはアクティブ状態で始まって、それぞれのポイントの状態に基づいて状態が変化します。すべての sync_pt
値がシグナル状態になると、sync_fence
はシグナル状態になります。1 つの sync_pt
がエラー状態になると、sync_fence
全体がエラー状態になります。
フェンスが作成された後で sync_fence
のメンバーを変更することはできません。1 つのフェンスに複数のポイントを含めるには、2 つの異なるフェンスのポイントを 3 つ目のフェンスに追加するマージを実施します。これらのポイントの 1 つが元のフェンスでシグナル状態になり、もう 1 つがシグナル状態にならなかった場合は、3 つ目のフェンスもシグナル状態になりません。
明示的な同期を実装するには、以下が必要です。
- 特定のハードウェア ドライバの同期フレームワークを実装するカーネル空間サブシステム。Hardware Composer にアクセスする、または Hardware Composer と通信するドライバは、一般的にフェンスを認識する必要があります。重要なファイルには以下のものがあります。
- コア実装:
kernel/common/include/linux/sync.h
kernel/common/drivers/base/sync.c
- ドキュメント(
kernel/common/Documentation/sync.txt
) - カーネル空間と通信するためのライブラリ(
platform/system/core/libsync
)
- コア実装:
- ベンダーは HAL の
validateDisplay()
関数とpresentDisplay()
関数に対して、適切な同期フェンスをパラメータとして用意する必要があります。 - グラフィック ドライバの 2 つのフェンス関連 GL 拡張(
EGL_ANDROID_native_fence_sync
とEGL_ANDROID_wait_sync
)とフェンス サポート。
ケーススタディ: ディスプレイ ドライバを実装する
同期関数をサポートする API を使用するために、ディスプレイ バッファ関数を持つディスプレイ ドライバを開発します。同期フレームワークが存在する前は、dma-buf
オブジェクトを受け取ってそれらのバッファをディスプレイに配置し、バッファが表示されている間、他の作業をブロックするという方法が採られていました。次に例を示します。
/* * assumes buffer is ready to be displayed. returns when buffer is no longer on * screen. */ void display_buffer(struct dma_buf *buffer);
同期フレームワークを使用すると、display_buffer
関数はより複雑になります。ディスプレイにバッファを配置すると、バッファが使用可能になる時期を示すフェンスに関連付けられます。作業をキューに入れておき、フェンスのクリア後に開始することができるようになります。
作業をキューに入れてフェンスのクリア後に開始する方法を採ると、作業をブロックする必要はなくなります。その代わりに、バッファがディスプレイを占有しなくなったことが保証できるタイミングを示す独自のフェンスを直ちに返すようにします。バッファをキューに入れると、カーネルが同期フレームワークで依存関係の一覧を表示します。
/* * displays buffer when fence is signaled. returns immediately with a fence * that signals when buffer is no longer displayed. */ struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence *fence);
同期の統合
このセクションでは、カーネル空間の同期フレームワークを Android フレームワークのユーザー空間部分および相互に通信する必要があるドライバと統合する方法について説明します。カーネル空間のオブジェクトは、ユーザー空間でファイル記述子として表されます。
統合の規則
Android HAL インターフェースの規則を遵守してください。
- API が
sync_pt
を参照するファイル記述子を備えている場合、API を使用するベンダー ドライバまたは HAL は、ファイル記述子を閉じる必要があります。 - ベンダー ドライバまたは HAL が、
sync_pt
を含むファイル記述子を API 関数に渡す場合はファイル記述子を閉じないでください。 - フェンスのファイル記述子を引き続き使用するには、ベンダー ドライバまたは HAL で記述子を複製する必要があります。
フェンス オブジェクトは、BufferQueue を通過するたびに名前が変更されます。カーネルのフェンス サポートにより、フェンスの名前に文字列を使用できるため、同期フレームワークではウィンドウ名と、キューに入っているバッファ インデックスを使用してフェンスに名前を付けます(SurfaceView:0
など)。この名前が /d/sync
の出力とバグレポートに表示されるため、デッドロックの発生元を特定するデバッグに対して有用です。
ANativeWindow の統合
ANativeWindow はフェンスを認識します。dequeueBuffer
、queueBuffer
、cancelBuffer
にそれぞれフェンスに関するパラメータがあります。
OpenGL ES の統合
OpenGL ES 同期の統合では、次の 2 つの EGL 拡張が使用されます。
EGL_ANDROID_native_fence_sync
では、EGLSyncKHR
オブジェクトでネイティブな Android フェンスのファイル記述子をラップまたは作成できます。EGL_ANDROID_wait_sync
は CPU 側ではなく GPU 側での停止を可能にして、GPU にEGLSyncKHR
を待機させます。EGL_ANDROID_wait_sync
拡張は、EGL_KHR_wait_sync
拡張と同じです。
これらの拡張を個別に使用するには、EGL_ANDROID_native_fence_sync
拡張とともに関連するカーネル サポートを実装します。次に、ドライバで EGL_ANDROID_wait_sync
拡張を有効にします。EGL_ANDROID_native_fence_sync
拡張は、独自のネイティブ フェンスの EGLSyncKHR
オブジェクト タイプで構成されます。そのため、既存の EGLSyncKHR
オブジェクト タイプに適用される拡張を EGL_ANDROID_native_fence
オブジェクトに必ずしも適用する必要はありません(不要な操作を回避できます)。
EGL_ANDROID_native_fence_sync
拡張は、対応するネイティブ フェンスのファイル記述子属性(作成時にのみ設定でき、既存の同期オブジェクトから直接クエリできない)を使用します。この属性は、次の 2 つのモードのいずれかに設定できます。
- 有効なフェンスのファイル記述子の場合は、
EGLSyncKHR
オブジェクトで既存のネイティブな Android フェンスのファイル記述子がラップされます。 - -1 の場合は、
EGLSyncKHR
オブジェクトからネイティブな Android フェンスのファイル記述子が作成されます。
DupNativeFenceFD()
関数呼び出しを使用して、ネイティブな Android フェンスのファイル記述子から EGLSyncKHR
オブジェクトを取得します。これは、set 属性のクエリと同じ結果になりますが、受信者がフェンスを閉じるという規則を遵守したものです(したがって操作が重複します)。最後に、EGLSyncKHR
オブジェクトを破棄して内部フェンス属性を閉じます。
Hardware Composer の統合
Hardware Composer では、次の 3 種類の同期フェンスが処理されます。
- acquire フェンスは、入力バッファとともに
setLayerBuffer
およびsetClientTarget
の呼び出しに渡されます。これらはバッファへの保留中の書き込みを表し、SurfaceFlinger または HWC が関連バッファからの読み取りと合成の実行を試行する前にシグナルを送信する必要があります。 - release フェンスは、
presentDisplay
への呼び出し後にgetReleaseFences
呼び出しを使用して取得されます。これらは、同じレイヤにある前のバッファからの保留中の読み取りを表します。release フェンスは、現在のバッファがディスプレイ上の以前のバッファに取って代わったため、HWC が以前のバッファを使用しなくなったときにシグナルを送信します。現在の合成中に置き換えられる以前のバッファとともに release フェンスがアプリに返されます。アプリは release フェンスのシグナルを待ってから、返されたバッファに新しいコンテンツを書き込む必要があります。 - present フェンスは、
presentDisplay
への呼び出しの一部として、1 フレームにつき 1 つ返されます。present フェンスは、このフレームの合成が完了したこと、または前のフレームの合成結果が不要になったことを表します。物理ディスプレイの場合、presentDisplay
は現在のフレームが画面に表示されたときに present フェンスを返します。present フェンスが返された後は、SurfaceFlinger のターゲット バッファに再度書き込みを行っても問題ありません(該当する場合)。仮想ディスプレイの場合、出力バッファからの読み取りを安全に行える場合に present フェンスが返されます。