Hardware Composer HAL の実装

Hardware Composer(HWC)HAL は、SurfaceFlinger から受け取ったレイヤを合成し、OpenGL ES(GLES)と GPU が行う合成の量を減らします。

HWC は、オーバーレイや 2D ブリッタなどのオブジェクトを複合サーフェスに抽象化し、専用のウィンドウ合成ハードウェアとやり取りしてウィンドウを合成します。SurfaceFlinger に GPU でウィンドウを合成させる代わりに、HWC を使用して合成してください。ほとんどの GPU は合成に最適化されておらず、GPU が SurfaceFlinger からのレイヤを合成する間、アプリは自身のレンダリングに GPU を使用できません。

HWC の実装では、以下がサポートされます。

  • 4 つ以上のオーバーレイ
    • ステータスバー
    • システムバー
    • アプリ
    • 壁紙 / 背景
  • ディスプレイよりも大きなレイヤ(壁紙など)
  • ピクセル単位の乗算済みアルファ ブレンディングとプレーン単位の乗算済みアルファ ブレンディングの同時実行
  • 保護された動画再生のハードウェア パス
  • RGBA のパッキング順、YUV 形式、タイリング、スウィズリング、ストライドの各プロパティ

HWC は次のように実装します。

  1. 何もしない HWC を実装し、すべての合成処理を GLES に送ります。
  2. 合成を HWC に委譲するアルゴリズムを段階的に実装します。 たとえば、最初の 3 つか 4 つのサーフェスのみを HWC のオーバーレイ ハードウェアに委譲します。
  3. HWC を最適化します。以下の最適化があります。
    • GPU にかかる負荷が最大になるサーフェスを選択し、HWC に送る。
    • 画面が更新中かどうかを検出する。更新中でない場合は、合成を HWC から GLES に委譲して電力を節約する。再び画面が更新されるようになったら、合成を HWC にオフロードする。
    • 次のような一般的なユースケースに備える。
      • ステータスバー、システムバー、アプリ ウィンドウ、ライブ壁紙を含むホーム画面
      • 縦向きモードと横向きモードでのフルスクリーン ゲーム
      • 字幕と再生コントロール付きのフルスクリーン動画
      • 保護された動画再生
      • 分割画面のマルチウィンドウ

HWC プリミティブ

HWC は、レイヤディスプレイという、合成処理とディスプレイ ハードウェアとの相互作用をそれぞれ表す 2 つのプリミティブを提供します。また、HWC は、VSYNC の制御と、VSYNC イベントの発生時に通知する SurfaceFlinger へのコールバックも提供します。

HIDL インターフェース

Android 8.0 以降では、HWC と SurfaceFlinger との間でバインダー化された IPC に、Composer HAL という HIDL インターフェースが使用されます。Composer HAL は、従来の hwcomposer2.h インターフェースを置き換えるものです。ベンダーが HWC の Composer HAL 実装を提供する場合、Composer HAL は SurfaceFlinger からの HIDL 呼び出しを直接受け入れます。ベンダーが HWC の従来の実装を提供している場合、Composer HAL は、hwcomposer2.h から関数ポインタを読み込み、HIDL 呼び出しを関数ポインタ呼び出しに転送します。

HWC は、ディスプレイの特性を判定する関数、ディスプレイ構成(4K や 1080p など)とカラーモード(ネイティブ カラーや True sRGB など)を切り替える関数、ディスプレイのオン / オフ、または低電力モードへの切り替え(サポートされている場合)を行う関数を提供します。

関数ポインタ

ベンダーが Composer HAL を直接実装する場合、SurfaceFlinger は HIDL IPC を使用して関数を呼び出します。たとえば、レイヤを作成する場合、SurfaceFlinger は Composer HAL の createLayer() を呼び出します。

ベンダーが hwcomposer2.h インターフェースを実装している場合、Composer HAL は hwcomposer2.h の関数ポインタを呼び出します。hwcomposer2.h のコメント内では HWC インターフェース関数を loserCamelCase 名で参照していますが、これらの名前はインターフェースに名前付きフィールドとして存在しません。ほぼすべての関数は、hwc2_device_t が提供する getFunction で関数ポインタをリクエストすることにより読み込まれます。たとえば、関数 createLayerHWC2_PFN_CREATE_LAYER 型の関数ポインタであり、列挙値 HWC2_FUNCTION_CREATE_LAYERgetFunction に渡されたときに返されます。

Composer HAL 関数と HWC 関数のパススルー関数の詳細については、composer をご覧ください。HWC 関数ポインタの詳細については、hwcomposer2.h をご覧ください。

レイヤハンドルとディスプレイ ハンドル

レイヤとディスプレイは、HWC が生成したハンドルによって操作されます。 これらのハンドルは SurfaceFlinger から見えません。

SurfaceFlinger は、新しいレイヤを作成すると、createLayer を呼び出し、これが Layer 型(直接実装用)または hwc2_layer_t(パススルー実装用)を返します。SurfaceFlinger は、そのレイヤのプロパティを変更すると、hwc2_layer_t の値を、変更に必要なその他の情報とともに、適切な変更関数に渡します。hwc2_layer_t 型は、ポインタまたはインデックスを保持するのに十分な大きさです。

物理ディスプレイは、ホットプラグされることにより作成されます。物理ディスプレイがホットプラグされると、HWC がハンドルを作成し、ホットプラグ コールバックを通じてハンドルを SurfaceFlinger に渡します。仮想ディスプレイは、SurfaceFlinger が createVirtualDisplay() を呼び出し、ディスプレイをリクエストすることにより作成されます。HWC は、仮想ディスプレイ合成に対応している場合、ハンドルを返します。次に、SurfaceFlinger はディスプレイの合成を HWC に委譲します。HWC が仮想ディスプレイの合成をサポートしていない場合、SurfaceFlinger がハンドルを作成してディスプレイを合成します。

ディスプレイの合成オペレーション

SurfaceFlinger は、VSYNC のたびに 1 回、合成する新しいコンテンツがあれば復帰します。この新しいコンテンツとは、アプリからの新しい画像バッファや 1 枚以上のレイヤのプロパティに発生した変更です。SurfaceFlinger が復帰すると、次の処理が行われます。

  1. トランザクションが存在すれば処理します。
  2. 新しいグラフィック バッファが存在すれば、ラッチします。
  3. 前述のステップ 1 またはステップ 2 によりディスプレイ コンテンツが変更された場合は、新しい合成を実行します。

新しい合成を実行するために、SurfaceFlinger はレイヤの作成と破棄、またはレイヤの状態の変更を行います。また、setLayerBuffersetLayerColor などの呼び出しを使用して、現在のコンテンツでレイヤを更新します。すべてのレイヤが更新されると、SurfaceFlinger は validateDisplay を呼び出します。呼ばれた関数は、レイヤの状態を調べて合成の進め方を決定するよう HWC に指示します。SurfaceFlinger は、デフォルトでは、HWC によって合成されるようにすべてのレイヤを設定しますが、状況によっては GPU フォールバックによってレイヤを合成します。

validateDisplay の呼び出し後、SurfaceFlinger は getChangedCompositionTypes を呼び出し、合成を実行する前に HWC がレイヤ合成のタイプの変更を求めているかどうかを確認します。変更を認める場合、SurfaceFlinger は acceptDisplayChanges を呼び出します。

SurfaceFlinger 合成のマークが付けられているレイヤがあれば、SurfaceFlinger はそれらをターゲット バッファに合成します。次に SurfaceFlinger は setClientTarget を呼び出してバッファをディスプレイに与え、バッファを画面に表示したり、さらに SurfaceFlinger 合成のマークが付けられていないレイヤで合成したりできるようにします。SurfaceFlinger 合成のマークが付けられたレイヤがない場合、SurfaceFlinger は合成ステップを省略します。

最後に、SurfaceFlinger は presentDisplay を呼び出し、合成プロセスを完了して最終的な結果を表示するよう HWC に指示します。

複数のディスプレイ

Android 10 では複数の物理ディスプレイがサポートされています。 Android 7.0 以降で使用する HWC 実装を設計する場合、次のような HWC 定義には存在しない制約があります。

  • 内部ディスプレイが 1 つだけ存在すると想定しています。内部ディスプレイとは、起動時の初期ホットプラグで報告されるディスプレイです。ホットプラグで検出された内部ディスプレイは、その後に接続を解除することができません。
  • デバイスの通常動作中は、内部ディスプレイに加えて任意の数の外部ディスプレイをホットプラグで接続できます。フレームワークでは、最初の内部ディスプレイの後にホットプラグされたものは、すべて外部ディスプレイであると想定されています。そのため、さらに内部ディスプレイが追加されると、Display.TYPE_BUILT_IN ではなく Display.TYPE_HDMI に誤って分類されてしまいます。

前述の SurfaceFlinger のオペレーションはディスプレイごとに実行されますが、更新されたのが 1 つのディスプレイの内容だけの場合でも、有効なすべてのディスプレイに対して順番に実行されます。

たとえば、外部ディスプレイが更新された場合、次の順序で実行されます。

// In Android 9 and lower:

// Update state for internal display
// Update state for external display
validateDisplay(<internal display>)
validateDisplay(<external display>)
presentDisplay(<internal display>)
presentDisplay(<external display>)

// In Android 10 and higher:

// Update state for internal display
// Update state for external display
validateInternal(<internal display>)
presentInternal(<internal display>)
validateExternal(<external display>)
presentExternal(<external display>)

仮想ディスプレイの合成

仮想ディスプレイの合成は、外部ディスプレイの合成と同様です。仮想ディスプレイの合成と物理ディスプレイの合成の違いは、仮想ディスプレイが画面ではなく Gralloc バッファに出力を送る点にあります。Hardware Composer(HWC)は、バッファに出力を書き込み、完了フェンス(completion fence)を与え、バッファをコンシューマ(動画エンコーダ、GPU、CPU など)に送ります。ディスプレイ パイプラインがメモリに書き込む場合、仮想ディスプレイは 2D/ブリッタまたはオーバーレイを使用できます。

モード

SurfaceFlinger が validateDisplay() HWC メソッドを呼び出した後、各フレームは次の 3 つのモードのうちのいずれかになります。

  • GLES - GPU がすべてのレイヤを合成し、出力バッファに直接書き込みます。HWC は合成に関与しません。
  • MIXED - 一部のレイヤを GPU がフレーム バッファに合成し、残りのレイヤを HWC が合成して、出力バッファに直接書き込みます。
  • HWC - HWC がすべてのレイヤを合成し、出力バッファに直接書き込みます。

出力形式

仮想ディスプレイ バッファの出力形式は、モードによって異なります。

  • GLES モード - EGL ドライバが出力バッファの形式を dequeueBuffer() で設定します。RGBA_8888 が一般的です。コンシューマは、ドライバが設定した出力形式を受け付け可能である必要があります。可能でない場合、バッファを読み取ることができません。
  • MIXED モードと HWC モード - コンシューマが CPU アクセスを必要とする場合、コンシューマが形式を設定します。それ以外の場合、形式は IMPLEMENTATION_DEFINED です。Gralloc が、用途フラグに基づいて最適な形式を設定します。たとえば、コンシューマが動画エンコーダであり、HWC が YCbCr 形式を効率的に書き込める場合、Gralloc はこの形式を設定します。

同期フェンス

同期(sync)フェンスは、Android グラフィックス システムの重要な要素です。フェンスは CPU の処理を GPU の並行処理とは独立に進行させ、真に依存関係がある場合にのみブロックします。

たとえば、アプリが GPU で作成中のバッファを投入するとき、同期フェンス(sync fence)オブジェクトも投入します。このフェンスは、GPU がバッファへの書き込みを終了するとシグナル状態になります。

HWC では、バッファが表示される前に GPU がバッファの書き込みを終了する必要があります。同期フェンスは、バッファとともにグラフィックス パイプラインを通過し、バッファが書き込まれたときにシグナル状態になります。HWC は、バッファが表示される前に、同期フェンスがシグナル状態にあるかどうかを確認し、シグナル状態にある場合はバッファを表示します。

同期フェンスの詳細については、Hardware Composer の統合をご覧ください。