Android 2D レンダリング パイプラインはハードウェア アクセラレーションをサポートしています。つまり、キャンバス上で行われる描画オペレーションはすべて GPU を使用します。ハードウェア アクセラレーションを有効にするために必要なリソースが増えるため、アプリの RAM 使用量が増えます。
ハードウェア アクセラレーションはデフォルトで有効になっています。アプリケーションで標準のコンポーザブルのみを使用している場合は、グローバルにオンにしても描画に悪影響はありません。ただし、ハードウェア アクセラレーションはすべての 2D 描画オペレーションでサポートされているわけではないため、オンにすると一部のカスタム描画呼び出しに影響する場合があります。問題は通常、要素が表示されない、例外、またはピクセルが誤ってレンダリングされることとして現れます。この問題を解決するために、Android にはハードウェア アクセラレーションを複数のレベルで有効または無効にするオプションが用意されています。ハードウェア アクセラレーションを制御するをご覧ください。
アプリケーションでカスタム描画を行う場合は、ハードウェア アクセラレーションをオンにして実際のハードウェア デバイスでアプリケーションをテストし、問題がないか確認します。描画オペレーションのサポートのセクションでは、ハードウェア アクセラレーションの既知の問題と、その回避方法について説明します。
Framework API を使用した OpenGL もご覧ください。
ハードウェア アクセラレーションを制御する
ハードウェア アクセラレーションは、次のレベルで制御できます。
- アプリ
- アクティビティ
- ウィンドウ
- コンポーザブル
アプリレベル
Android マニフェスト ファイルで、次の属性を <application> タグに追加してアプリ全体のハードウェア アクセラレーションを有効にします。
<application android:hardwareAccelerated="true" ...>
アクティビティ レベル
ハードウェア アクセラレーションをグローバルにオンにしてもアプリが正しく動作しない場合は、個々のアクティビティに対して制御することもできます。ハードウェア アクセラレーションをアクティビティ レベルで有効または無効にするには、<activity> 要素の android:hardwareAccelerated 属性を使用します。次の例では、アプリケーション全体に対してハードウェア アクセラレーションを有効にしていますが、1 つのアクティビティに対しては無効にしています。
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
ウィンドウ レベル
さらに細かい制御が必要な場合は、次のコードを使用して特定のウィンドウのハードウェア アクセラレーションを有効にできます。
window.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
コンポーザブル レベル
Compose では、コンポーザブルごとにハードウェア アクセラレーションを無効にするスイッチはありません。
コンポーザブルを独自のレイヤにレンダリングするには、Modifier.graphicsLayer を使用します。これにより、コンポーザブルの描画コードを再実行せずに、変換プロパティ(alpha、scaleX、scaleY、translationX、translationY、rotationX、rotationY、rotationZ、transformOrigin など)を変更できます。最適なパフォーマンスを得るには、常に修飾子のラムダ形式を使用してこれらのプロパティを設定します。
レイヤ内のカスタム ブレンドなどの高度な描画オペレーションで、オフスクリーン バッファを明示的に強制するには、CompositingStrategy.Offscreen を使用します。詳しくは、グラフィック修飾子をご覧ください。
ソフトウェア レンダリングが厳密に必要となるカスタム描画オペレーションがある場合は、AndroidView を使用して以前の View をホストし、そのビューで setLayerType(View.LAYER_TYPE_SOFTWARE, null) を呼び出すことができます。
描画オペレーションのサポート
ハードウェア アクセラレーションを行うと、2D レンダリング パイプラインは、よく使用される Canvas 描画オペレーションと、あまり使用されない多くのオペレーションをサポートします。Android に付属するアプリのレンダリングに使用される描画オペレーション、標準のコンポーザブル、また反射やタイル テクスチャなどの、よく使われる高度な視覚効果がすべてサポートされています。
次の表に、API レベルにわたってさまざまなオペレーションのサポートレベルを示します。
| サポートされている最初の API レベル | ||||
| Canvas | ||||
| drawBitmapMesh()(色配列) | 18 | |||
| drawPicture() | 23 | |||
| drawPosText() | 16 | |||
| drawTextOnPath() | 16 | |||
| drawVertices() | 29 | |||
| setDrawFilter() | 16 | |||
| clipPath() | 18 | |||
| clipRegion() | 18 | |||
| clipRect(Region.Op.XOR) | 18 | |||
| clipRect(Region.Op.Difference) | 18 | |||
| clipRect(Region.Op.ReverseDifference) | 18 | |||
| clipRect()(rotation / perspective を指定) | 18 | |||
| Paint | ||||
| setAntiAlias()(テキストの場合) | 18 | |||
| setAntiAlias()(線の場合) | 16 | |||
| setFilterBitmap() | 17 | |||
| setLinearText() | ✗ | |||
| setMaskFilter() | ✗ | |||
| setPathEffect()(線の場合) | 28 | |||
| setShadowLayer()(テキスト以外) | 28 | |||
| setStrokeCap()(線の場合) | 18 | |||
| setStrokeCap()(点の場合) | 19 | |||
| setSubpixelText() | 28 | |||
| Xfermode | ||||
| PorterDuff.Mode.DARKEN(フレームバッファ) | 28 | |||
| PorterDuff.Mode.LIGHTEN(フレームバッファ) | 28 | |||
| PorterDuff.Mode.OVERLAY(フレームバッファ) | 28 | |||
| Shader | ||||
| ComposeShader 内の ComposeShader | 28 | |||
| ComposeShader 内にある同じタイプのシェーダー | 28 | |||
| ComposeShader のローカル マトリックス | 18 | |||
キャンバスのスケーリング
ハードウェア アクセラレーションされている 2D レンダリング パイプラインは最初、サイズ調整なしの描画をサポートするために作成されたため、スケール値の高い描画オペレーションでは質が著しく低下するものがあります。このようなオペレーションは、スケール 1.0 で描画され、GPU によって変換されるテクスチャとして実装されます。API レベル 28 以降、描画オペレーションはすべて問題なくスケーリングできます。
次の表は、大きなスケールを正しく処理するように実装が変更された場合を示しています。
| スケーリングされる描画オペレーション | サポートされている最初の API レベル |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 28 |
| 単純な図形 | 17 |
| 複雑な図形 | 28 |
| drawPath() | 28 |
| シャドウレイヤ | 28 |
依存している描画オペレーションがハードウェア アクセラレーションされていない場合は、影響を受ける描画をオフスクリーンのソフトウェア Bitmap(または ImageBitmap)にレンダリングし、結果を描画します。UI の残りの部分はハードウェア アクセラレーション パスを維持します。
ヒントとコツ
ハードウェア アクセラレーションされた 2D グラフィックスに切り替えるとパフォーマンスがすぐに向上しますが、次の推奨事項に従って、GPU を効果的に使用するようにアプリケーションを設計する必要があります。
- レイアウトの複雑さと再コンポーズを最小限に抑える
- レイアウト ツリーを浅く保ち、再コンポーズの量を制限します。状態の読み取りを最も狭いスコープに延期して、変更によって可能な限り小さい領域が再描画されるようにします。たとえば、コンポーザブルの本体ではなく、
Modifier.graphicsLayer { }内でアニメーション状態を読み取ります。詳しくは、Jetpack Compose のパフォーマンスをご覧ください。 - オーバードローを回避する
- あまり多くのレイヤを重ね合わせて描画しないでください。上にある他の不透明な要素で完全に隠されている UI 要素は削除します。複数のレイヤを重ね合わせて描画する必要がある場合は、1 つのレイヤに統合することを検討してください。現在のハードウェアでの経験則として、フレームごとに画面上のピクセル数(ビットマップ内の透明ピクセルのカウント)の 2.5 倍を超えて描画しないことをおすすめします。
- 描画メソッドでレンダリング オブジェクトを作成しない
- よくある間違いは、レンダリング メソッドを呼び出すたびに新しい
Paintまたは新しいPathを作成することです。そうすると、ガベージ コレクタの実行頻度が高くなり、ハードウェア パイプラインのキャッシュと最適化もバイパスされます。これを回避するには、オブジェクトを再利用して変更します。- 標準メソッドを使用する: 標準の
DrawScopeメソッド(drawRectやdrawCircleなど)は、デベロッパーによる割り当てを必要とせずに、内部でPaintオブジェクトを再利用します。 - 再割り当てではなく変更する: カスタム ロジックを記述するときは、新しい
Pathをインスタンス化するのではなく、path.rewindを使用して既存のPathをクリアします。 - 状態を効率的に保持する: コンポーザブル内で、
remember { Path() }を使用してオブジェクトを 1 回割り当てます。再利用可能なカスタム修飾子拡張機能を構築する場合は、DrawModifierNodeを使用してカスタムModifier.Nodeを実装し、新しいヒープ割り当てを発生させることなくオブジェクトを割り当てて再利用します。
- 標準メソッドを使用する: 標準の
- 図形を頻繁に変更しない
- 複雑な図形、パス、円などは、テクスチャ マスクを使用してレンダリングされます。パスを作成または変更するたびに、ハードウェア パイプラインによって新しいマスクが作成されます。これはコストが高くつく可能性があります。
- ビットマップを頻繁に変更しない
- ビットマップのコンテンツを変更するたびに、次回描画するときに GPU テクスチャとして再度アップロードされます。
- アルファは慎重に使用する
Modifier.alphaまたは Compose アニメーション API を使用してコンポーザブルを半透明にすると、通常は必要なフィルレートが倍になるオフスクリーン バッファにレンダリングされます。重複しないコンテンツのオフスクリーン バッファのオーバーヘッドを回避するには、CompositingStrategy.ModulateAlphaを設定します。個々のドローコールでは、レイヤを作成せずに、描画コマンドに直接アルファを適用します(color = Color.Red.copy(alpha = 0.5f)の場合と同様)。