Android 向け Godot Engine Vulkan の最適化

Godot Engine のマスコットの画像

概要

Godot Engine は、Android を堅牢にサポートする人気の高いマルチプラットフォームのオープンソース ゲームエンジンです。Godot は、ほぼすべてのジャンルのゲームの作成に使用でき、2D グラフィックと 3D グラフィックの両方に対応しています。Godot バージョン 4 では、高忠実度グラフィック向けの高度な機能を備えた新しいレンダリング システムが導入されました。Godot 4 レンダラは、Vulkan などの最新のグラフィック API 向けに設計されています。

Godot Foundation は、The Forge Interactive のグラフィック最適化の専門家を起用し、Google と協力して Godot 4 Vulkan レンダラを分析し、さらに改善し、その最適化をプロジェクト リポジトリに統合しました。この最適化により、デベロッパーは Android でカスタム Vulkan レンダラを改善できます。

最適化の方法と結果

最適化プロセスでは、ベンチマーク ターゲットとして Godot の 2 つの異なる 3D シーンを使用しました。シーンのレンダリング時間は、最適化の各反復処理中に複数のデバイスで測定されました。レンダラへの変更が含まれるには、テストしたデバイスの少なくとも一部でパフォーマンスの向上が示され、どのデバイスでもパフォーマンスの低下が起きないことが必要でした。

テストでは、一般的な複数の Android GPU アーキテクチャが使用されました。多くの最適化によって一般的な改善がもたらされましたが、一部の最適化は特定の GPU アーキテクチャに大きな影響を与えました。すべての最適化作業の合計により、GPU フレーム時間が全体で 10 ~ 20% 短縮されました。

一般的な Vulkan の最適化

Forge では、パフォーマンスを向上させ、コンテンツ レンダリングの需要の増加に伴うバックエンドのスケーリングを支援するために、Godot Vulkan レンダリング バックエンドで一般的なアーキテクチャのリファクタリングを行いました。この最適化はモバイル ハードウェアに固有のものではなく、すべての Godot Vulkan プラットフォームにメリットがあります。

動的 UBO オフセットのサポート

動的ユニフォーム バッファ オブジェクト(UBO)を含む記述子セットをバインドする場合、Vulkan では、バインディング パラメータで UBO への動的オフセットを指定できます。この機能を使用すると、複数のレンダリング オペレーションのデータを 1 つの UBO にパックし、別の動的オフセットで記述子セットを再バインドして、シェーダーに適したデータを選択できます。Godot Vulkan レンダラが更新され、オフセットを常にゼロに初期化するのではなく、動的オフセットを使用できるようになりました。この改善により、将来の効率の最適化が可能になります。

リニア記述子セット プール

以前、Godot Vulkan レンダラのデフォルトの動作では、VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT フラグを使用してすべての記述子セットプールが作成されていました。つまり、記述子セットの割り当てをプールに戻して解放し、プールから再割り当てを行うことができました。このモードでは、線形割り当てとプールの完全なリセットのみを許可する記述子セット プールと比較して、追加のオーバーヘッドが発生しました。

可能な場合は、記述子セット プールが VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT を設定せずにリニア プールとして作成されるようになりました。必要に応じて、リニア プールは全体としてリセットされます。また、可能であれば、バッチ記述子セット バインディングの追加の最適化も行われ、vkCmdBindDescriptorSets() への個別の呼び出し回数が削減されます。

不変サンプラーのサポート

サンプリング構成データを含むサンプラー オブジェクトは、従来は記述子セット データの一部としてバインドされていました。この方法では、サンプラー オブジェクトを記述子セットデータで動的にスワップアウトできます。Vulkan は、サンプラデータが記述子セット レイアウトに直接エンコードされる不変のサンプラーもサポートしています。このサンプラーの構成は、記述子セットとパイプライン ステートの作成時にバインドされ、作成後に変更することはできません。

不変のサンプラーは、個別のサンプラー オブジェクトを管理してバインドする必要がなくなるという柔軟性の代わりに、柔軟性を犠牲にしています。Godot Vulkan レンダラが更新され、不変サンプラの使用がサポートされました。サンプラの使用方法が変更され、可能な場合は不変サンプラが使用されるようになりました。

モバイル重視の最適化

モバイル グラフィック ハードウェアでのレンダリング パフォーマンスを特に改善するために、追加の最適化が実装されました。アーキテクチャ設計が異なるため、これらの最適化は通常、デスクトップ クラスのグラフィック ハードウェアには関係ありません。

最適化には次のようなものがあります。

  • 大きな push 定数の使用を置き換える
  • 遅延バッファ割り当て
  • 永続バッファのサポート
  • ASTC デコード モードの変更
  • 画面の事前回転

大きな push 定数の使用を置き換える

プッシュ定数は、アクティブなシェーダー プログラムの定数値をコマンド バッファに挿入できる機能です。プッシュ定数は、バッファの作成と入力を必要とせず、記述子に関連付けられていないため便利です。ただし、プッシュ定数には最大サイズの制限があり、モバイル ハードウェアのパフォーマンスに悪影響を及ぼす可能性があります。

Android デバイスでのテストでは、16 バイトを超える push 定数使用を均一なバッファに置き換えることで、パフォーマンスが向上しました。16 バイト以下の定数データを使用したシェーダーは、プッシュ定数を使用するとパフォーマンスが向上しました。パフォーマンスの考慮事項に加えて、一部のグラフィック ハードウェアでは、均一バッファの 64 バイトのアライメントが最小要件となっています。これにより、プッシュ定数を使用する場合と比較して、未使用のパディングが原因でメモリ効率が低下します。

遅延バッファ割り当て

ほとんどのモバイル グラフィック ハードウェアは、タイルベースの遅延レンダリング(TBDR)アーキテクチャを使用しています。TBDR を使用する GPU は、大きな画面領域を小さなタイルのグリッドに分割し、タイルごとにレンダリングします。各タイルは、GPU がタイルをレンダリングするときに GPU によってストレージに使用される少量の高速 RAM によってバッキングされます。TBDR では、レンダリング パスの外部で別のターゲットによってサンプリングされることのないレンダリング ターゲットを、タイルの RAM に完全に保持できます。メインメモリ バッキング ストア用のバッファは必要ありません。

VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT は、メイン カラー ターゲットや深度ターゲットなどの適切なレンダリング ターゲットの作成時に追加され、使用されないバッファメモリの割り当てを回避します。サンプル シーンでの遅延割り当てメモリの節約は、最大で約 50 MB の RAM と測定されました。

永続バッファのサポート

モバイル ハードウェアでは、メイン RAM とグラフィックス RAM をハードウェアで区別するのではなく、統合メモリ アーキテクチャ(UMA)を使用します。メイン RAM とグラフィック RAM が別々の場合、GPU で使用するためにデータをメイン RAM からグラフィック RAM に転送する必要があります。Godot では、ステージング バッファを使用して、この転送プロセスがすでに Vulkan レンダラに実装されています。UMA ハードウェアでは、多くの種類のデータにステージング バッファは不要です。メモリは CPU と GPU の両方で使用できます。Forge では、サポートされているハードウェアで永続的な共有バッファのサポートを実装し、可能な場合はステージングを排除しました。

永続バッファを有効にしたときと無効にしたときのプロファイリング情報を示す Godot シーンの画像。
図 1. サンプル シーンで永続バッファを有効にしたときと無効にしたときのプロファイリングの違い。

ASTC デコード モードの変更

モバイル ハードウェアでは、Adaptive Scalable Texture Compression(ASTC)が推奨される最新テクスチャ圧縮形式です。デコンプレッション中に、GPU はデフォルトでテクセルを視覚的な忠実度に必要な精度よりも大きい中間値にデコードし、テクスチャリングの効率が低下する可能性があります。ターゲット ハードウェアでサポートされている場合、VK_EXT_astc_decode_mode 拡張機能を使用して、デコード時に 16 ビットの浮動小数点値ではなく、コンポーネントごとに 8 ビットの正規化されていない値を指定します。

画面の事前回転

Android で Vulkan を使用する際の最適なパフォーマンスを得るには、ゲームで画面のデバイスの向きとレンダリング サーフェスの向きを調整する必要があります。このプロセスは「プリローテーション」と呼ばれます。事前回転を実行しないと、Android OS がコンポジター パスを追加して画像を手動で回転する必要があるため、パフォーマンスが低下する可能性があります。Android での事前回転のサポートが Godot レンダラに追加されました。

デバッグの改善

The Forge では、パフォーマンスの最適化に加えて、Godot レンダラでグラフィックスの問題をデバッグするエクスペリエンスを改善するために、次の機能を追加しました。

  • デバイスの欠陥の延長
  • パンくずリスト
  • デバッグ マーカー

デバイスの欠陥の延長

レンダリング オペレーション中に GPU で問題が発生すると、Vulkan ドライバは Vulkan API 呼び出しから VK_ERROR_DEVICE_LOST 結果を返すことができます。デフォルトでは、ドライバが VK_ERROR_DEVICE_LOST を返した理由に関する追加のコンテキスト情報は提供されません。VK_EXT_device_fault 拡張機能は、ドライバが障害の性質に関する追加情報を提供するメカニズムを提供します。Godot に、デバイス障害拡張機能(利用可能な場合)の有効化と、ドライバから返された情報の報告のサポートが追加されました。

GPU のクラッシュや実行の停止はデバッグが難しい場合があります。障害が発生したときにレンダリングされた可能性のあるグラフィック コンテンツを特定できるように、Godot レンダラにパンくずリストのサポートが追加されました。パンくずリストは、レンダリング グラフの描画リスト内のコンテンツにアタッチできるユーザー定義値です。パンくずリストのデータは、新しいレンダリング パスが開始される前に書き込まれます。クラッシュや実行の停止が発生した場合は、現在のパンくずリストの値を使用して、問題の原因となったデータを確認できます。

デバッグ マーカー

デバッグマーカーは、ドライバでサポートされている場合に、リソースの命名に使用されます。これにより、RenderDoc などのグラフィック ツールを使用するときに、ユーザーが読み取り可能な文字列をレンダリング パスなどのオペレーションや、バッファやテクスチャなどのリソースに関連付けることができます。Godot Vulkan レンダラにデバッグ マーカー アノテーションのサポートが追加されました。

Godot Engine ブログ - Google と The Forge とのコラボレーションに関する最新情報

Godot Engine Vulkan コラボレーションの pull リクエスト