Vulkan 設計ガイドライン

Vulkan は、これまでのグラフィック API とは異なります。ドライバがアプリに対して特定の最適化(パイプラインの再利用など)を行わないというのがこれまでのグラフィック API でしたが、Vulkan を使用するアプリでは、このような最適化をアプリ自身で実装する必要があります。実装しないと、OpenGL ES を実行するアプリよりもパフォーマンスが低下する場合があります。

アプリは、特定のユースケースに関する、より詳細な情報にアクセスできます。したがって、アプリはドライバよりも適切に自身で最適化を実装できる可能性があります。そのため、Vulkan を使用するアプリを効果的に最適化すれば、アプリで OpenGL ES を使用する場合よりも高いパフォーマンスを実現することが可能になります。

このページでは、Vulkan で高パフォーマンスを実現するために Android アプリが実装できる最適化をいくつか紹介します。

レンダリング中に表示の回転を適用する

アプリの上方向がデバイスの画面の向きに一致しない場合、コンポジタがアプリのスワップチェーン画像を回転し、向きを一致させます。この回転は、画像を表示しながら実行するため、画像を回転しない場合よりも電池の消費量が(ときに大幅に)増えます。

一方、画像を生成時に回転すると、追加の電池消費を要する場合でも少量で済みます。VkSurfaceCapabilitiesKHR::currentTransform フィールドは、コンポジタがウィンドウに適用する回転を示します。アプリは、レンダリング中にその回転を適用した後、VkSwapchainCreateInfoKHR::preTransform フィールドを使用して回転が完了したことを報告します。

1 フレームあたりのレンダリング パスを最小限にする

ほとんどのモバイル GPU アーキテクチャにおいて、レンダリング パスの開始と終了は負荷の高い操作になります。レンダリング操作をできる限り少ないレンダリング パスにまとめることで、アプリのパフォーマンスを改善できます。

アタッチメントの読み込みと保存は、操作ごとにパフォーマンスのレベルが異なります。たとえば、アタッチメントの内容を保存する必要がない場合、VK_ATTACHMENT_LOAD_OP_CLEAR または VK_ATTACHMENT_LOAD_OP_DONT_CARE を使用するほうが VK_ATTACHMENT_LOAD_OP_LOAD より格段に早くなります。同様に、アタッチメントの最終的な値をメモリに書き込んで後から使用する必要がない場合は、VK_ATTACHMENT_STORE_OP_DONT_CARE を使用するほうが VK_ATTACHMENT_STORE_OP_STORE よりパフォーマンスを大幅に改善できます。

また、ほとんどのレンダリング パスでは、アプリで深度やステンシルのアタッチメントを読み込んだり保存したりする必要がありません。このような場合は、アタッチメント画像の作成時に VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT フラグを使用すれば、アタッチメントに物理メモリを割り当てる必要がなくなります。このフラグには、OpenGL ES の glFramebufferDiscard と同様のメリットがあります。

適切なメモリの種類を選択する

デバイスのメモリを割り当てるとき、アプリはメモリの種類を選択する必要があります。メモリの種類によって、アプリがメモリを使用できる方法が変わります。またメモリの種類は、メモリのキャッシュと一貫性のプロパティを表します。使用可能なメモリの種類はデバイスによって異なります。また、メモリの種類ごとにパフォーマンス特性が異なります。

アプリは、単純なアルゴリズムを使って、各使用方法に応じた最適なメモリの種類を選択できます。このアルゴリズムは、VkPhysicalDeviceMemoryProperties::memoryTypes 配列内にあるメモリの種類のうち 2 つの条件を満たす最初のものを選択します。その条件とは、「バッファまたは画像に使用できること」と、「アプリに必要な最小限のプロパティを備えていること」です。

モバイル システムは通常、CPU と GPU に別個の物理メモリヒープを使用することはありません。このようなシステムにおいては、独自の専用メモリに個別の GPU を使用するシステムと比べて、VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT は重要ではありません。アプリは、このプロパティを必須としないでください。

使用頻度に応じて記述子セットをグループ化する

使用頻度に応じて変わるリソース バインディングがある場合、すべてのリソースを各ドローに対して再バインドするのではなく、パイプラインごとに複数の記述子セットを使用します。たとえば、シーンごとのバインディング、マテリアルごとのバインディング、メッシュ インスタンスごとのバインディングに、それぞれ異なる記述子セットを使用できます。

各ドローの呼び出しで実行される変更など、最も頻度の高い変更には直接定数を使用します。