Unity は、先日閉幕した GDC 2019 における基調講演の中で、Adaptive Performance についての紹介を行いました。この機能のプレビュー版と『Megacity』のモバイルサンプルがリリースされ、実際にお試しいただけるようになりました。本記事では Adaptive Performance について、プロジェクトへの適用方法も含め、さらにご紹介して行きます。
PC 向けやコンソール向けゲームの場合とは異なり、モバイルハードウェアの力を最大限に活用するゲームを開発すためには、美しいビジュアルとスムーズなゲームプレイとの間で絶妙なバランスが実現されなければなりません。デバイス性能の限界まで使い切れば、ハードウェアに過大な負荷が掛かってゲームの実行性能が犠牲となり、スロットリングやバッテリー寿命の短縮、不安定なパフォーマンスに繋がり易くなります。これは特に、ローエンド・ハイエンドの各種ターゲットデバイスを幅広く考慮しなければならないデベロッパーにとっては、ことさらに頭の痛い問題となります。
今日デベロッパーは様々な戦略でこの問題を解決しています。そのうち私達が目にした主要なアプローチは 2 つあります。ひとつ目は全てのターゲットハードウェアで出来る限り高いパフォーマンスを実現しようとするもので、これはグラフィックスの忠実度とフレームレートを犠牲にすることになります。もうひとつはハードウェアの挙動の予測を試みるアプローチですが、ハードウェアの傾向を正確に測定するための手段が限られているため、これを行うのは非常に困難です。
Adaptive Performance を使用すると、デバイスにおける温度とゲームのパフォーマンスを、リアルタイムでより効率的に管理することができます。ゲームのパフォーマンスと品質設定の予測的な調整がリアルタイムで行え、デバイスに過大な負荷を掛けることなくハードウェアを活用することが可能となります。この結果フレームレートが安定して熱の蓄積が抑制され、バッテリー寿命を保護しながら、より長いプレイ時間・より快適なプレイヤー体験が実現されます。
Adaptive Performance によってデベロッパーは、従来なかった形でハードウェアをより深く理解し、よりダイナミックで柔軟なゲームを作るための新しいツールを手に入れました ― これにより、モバイルデバイスで最高にスムーズでパフォーマンスの高いプレイ体験をプレイヤーに提供することが可能となります。通常は OS によって決定される事柄(高いクロック周波数で実行するタイミングや、スロットリング回避のために何を調整するかなど)を、デベロッパー自身で決定することができます。
GDC 2019 で、この機能に関するいくつかの講演を行いました。そのスライド集をこちらでご覧いただけます。また、GDC の Unity ブースでの講演「Megacity on mobile: How we optimized it with Adaptive Performance」(英語)を以下でご視聴いただけます。
Unity は、このソリューションを結実させるために世界最大の Android モバイルデバイスメーカーである Samsung と提携しました。Samsung の GameSDK を基盤に構築された Adaptive Performance は、まず Samsung Galaxy S10 や Galaxy Fold などの Samsung Galaxy デバイス向けに公開され、今年中にはさらに多くの Samsung Galaxy デバイスに対応していく予定です。
GDC 2019 の Unity 基調講演で使用された以下のグラフをご覧ください。Adaptive Performance によって、最新の Samsung Galaxy S10 で実行した『Megacity』で、安定した高フレームレートが実現されていることがわかります。
赤いグラフは Adaptive Performance を追加する前の『Megacity』のフレームレートです。青いグラフは Adaptive Performance を追加した結果です。Adaptive Performance によって、デモが 30 FPS で実行される時間が大幅に長くなり、更に大幅に安定しています。
『Megacity』は無数のエンティティを内包する未来的なインタラクティブ都市で、Unity ならどんなに複雑なプロジェクトでも現世代モバイルハードウェアで実行できることを証明したものです。このデモでは Data-Oriented Technology Stack(DOTS)の最新の進化がご覧いただけるようになっています。DOTS とは「苦労せずにハイパフォーマンスを実現」(Performance by Default)を謳った全てのプロジェクト(Entity Component System (ECS)、Native Collections、C# Job System、Burst Compiler など)の総称です。『Megacity』は Unite Los Angeles 2018 で発表され、GDC 2019 開催期間中にデスクトップ向けに公開されました。
『Megacity』は Adaptive Performance のサンプル実装の実演にうってつけのプロジェクトです。なぜならこの機能は、ハードウェアを最大活用するために動的に先を見越してゲームを適応させる柔軟な対応を可能にするものだからです。Adaptive Performance はスケーラビリティを考慮して設計されているため、『Megacity』 の基盤の構築に使用されている DOTS の原理にも良く適合します。
本プロジェクトのモバイル版には、450 万のメッシュレンダラー、20 万の建物構成要素、10 万のオーディオソース、600 万を超えるエンティティが含まれています ― つまり、Adaptive Performance の性能を実演するのに理想的なプロジェクトなのです。
Unity パッケージマネージャーから Adaptive Performance をインストールすると、デバイス向けにビルドを行った際に Unity が自動的に Samsung GameSDK サブシステムをプロジェクトに追加します。Unity がランタイム中に対応デバイス上で Adaptive Performance Manager を作成・起動します。これがモバイルデバイスの熱状態に関するフィードバックを提供します。ランタイム中に Adaptive Performance Manager からイベントにサブスクライブしたり情報のクエリを行えば、リアルタイムでそれに対応することができます。これを行わない場合は、データはコンソールにのみレポートされます。
例えば、提供されている API を使用して、デバイスの温度の変化やイベントに対応するアプリケーションを作成できます。これにより、サーマルスロットリングを(発生前から)回避しつつ、固定フレームレートをより長時間維持することができます。『Megacity』 における Adaptive Performance のサンプル実装では、以下の 3 つの方法でフレームレートを平滑化しています。
これらの機能によって、徐々にゲームのパフォーマンスがスムーズになります。デバイスの温度の変化をトラッキングすることで、その場でパフォーマンス設定を調整してスロットリングを完全に回避することが可能となります。
『Megacity』 モバイルサンプルプロジェクトをこちらからダウンロードして、上記がどのように行われたかを実際にご覧ください。『Megacity』に関するフィードバックやご質問はフォーラムの関連スレッド にご投稿ください。
本パッケージの中核を成すのは Adaptive Performance Manager です。これは Unity によって起動時に作成され、これにより温度やパフォーマンスに関するイベント通知への簡単なアクセスとサブスクライブが可能になります。以下の例では、MonoBehaviour の Start 関数内で IAdaptivePerformance インターフェース を使って Adaptive Performance Manager にアクセスする方法をご確認いただけます。
private IAdaptivePerformance ap = null; void Start() { ap = Holder.instance; }
デバイスの熱状態が変わるたびに、熱に関するイベントが Unity によって送られます。重要な状態は、スロットリングの発生が差し迫った状態およびスロットリングが発生した状態です。下の例では ThermalEvents にサブスクライブすることで lodBias を削減したり増加させたりしており、これが GPU 負荷の抑制に貢献しています。
using UnityEngine; using UnityEngine.Mobile.AdaptivePerformance; public class AdaptiveLOD : MonoBehaviour { private IAdaptivePerformance ap = null; void Start() { if (Holder.instance == null) return; ap = Holder.instance; if (!ap.active) return; QualitySettings.lodBias = 1; ap.ThermalEvent += OnThermalEvent; } void OnThermalEvent(object obj, ThermalEventArgs ev) { switch (ev.warningLevel) { case PerformanceWarningLevel.NoWarning: QualitySettings.lodBias = 1; break; case PerformanceWarningLevel.ThrottlingImminent: QualitySettings.lodBias = 0.75f; break; case PerformanceWarningLevel.Throttling: QualitySettings.lodBias = 0.5f; break; } } }
lodBias の値を 1 より低くすると、しばしばビジュアルに影響し、LOD に関連してオブジェクトのポッピングが発生する可能性がありますが、これは、ゲーム体験を維持する上で問題ない場合にグラフィック負荷を簡単に削減できる方法のひとつです。これより更に精密な制御でゲームのグラフィックスと挙動の処理を微調整したい場合は、ボトルネックイベントが非常に役立ちます。
モバイルデバイスにおいては、(特にゲームの実行時においては)CPU と GPU がその電力消費の大部分を占めます。通常は OS が、どのクロック周波数を CPU と GPU に使用するかを決定します。
CPU コアと GPU は、最大クロック周波数で実行中は効率性が低くなります。高いクロック周波数での実行はモバイルデバイスを容易にオーバーヒートさせ、OS が CPU と GPU の周波数を抑制(スロットリング)してデバイスを冷却します。
このような状況は、以下のプロパティで最大クロック周波数を制限すれば回避できます。
IAdaptivePerformance.cpuLevel
IAdaptivePerformance.gpuLevel
アプリケーションは、現在のパフォーマンス要件に関する特定の情報に基づいてこれらのプロパティを設定し、状況に応じて、実行速度を低くするか高くするか決定することができます。
アプリケーションは直前のフレームでターゲットのフレームレートに達したか?
アプリケーションは、ゲーム内シーンとメニューのどちらを開いているか?
次に重いシーンが来るか?
CPU 負荷あるいは GPU 負荷の高いイベントが目前に迫っているか?
高い CPU・GPU の実行速度を必要としない広告を表示するか?
public void EnterMenu(){ if (!ap.active) return; // メニューを開いているときに CPU および GPU の実行速度を低く設定する ap.cpuLevel = 0; ap.gpuLevel = 0; // ターゲット FPS を低く設定する Application.targetFrameRate = 15; } public void ExitMenu(){ // ゲームに戻る時に CPU および GPU の実行速度をより高く設定する ap.cpuLevel = ap.maxCpuPerformanceLevel; ap.gpuLevel = ap.maxGpuPerformanceLevel; }
Adaptive Performance Manager では、GPU、CPU、あるいは「フレームレートバウンド」のどれであるかを通知するパフォーマンスボトルネックイベントにサブスクライブして受け取ることができます。フレームレートバウンドとは、ゲームが Application.targetFrameRate によって制限されていることを意味し、この場合アプリケーションはそのパフォーマンス要件の緩和を検討する必要があります。
バックグラウンドで実行されてボトルネックの決定を行っているのは(Manager からクエリ可能な)GPU フレーム時間ドライバーで、これが直前のフレームで GPU が消費したハードウェア時間をモニタリングします。現時点では、CPU 時間は Unity の内部的なサブシステムの総計の計算によって求められています。ゲームや状況に合わせ、熱状態の変化に応じてゲームが CPU バウンドになった時と GPU バウンドになった時で異なる処理が行われるようにすることができます。
void OnBottleneckChange(object obj, PerformanceBottleneckChangeEventArgs ev) { switch (ev.bottleneck) { case PerformanceBottleneck.TargetFrameRate: if (ap.cpuLevel > 0) { ap.cpuLevel--; } if (ap.gpuLevel > 0) { ap.gpuLevel--; } break; case PerformanceBottleneck.GPU: if (ap.gpuLevel ゲームを最適化する方法は様々あり、上の例や『Megacity』 の例はそのうちの一部に過ぎません。最終的には、何が最も適しているかはそれぞれのゲームによって変わります。詳細は本パッケージに関するドキュメンテーション(英語)をご覧ください。
これは、ほんの始まりに過ぎません!私達は今後も Adaptive Performance の進化に向けた取り組みを続け、機能と対応デバイスをさらに追加してまいります。現在のパッケージには低水準 API が含まれていますが、DOTS と互換性のあるコンポーネントベースの高水準 API の開発がすでに進んでいます。これにより、Unity プロジェクトにおけるパフォーマンスの適応がさらに行いやすくなるはずです。今後の関連情報をお届けしてまいりますので、どうぞお見逃しなく。