ローディング画面が好きという人は誰もいないでしょう。ロード時間は Async Upload Pipeline(AUP)パラメーターを簡単に調節するだけで大幅に改善できることをご存知でしたか?本記事では、AUP によるメッシュとテクスチャをローディングする仕組みを詳しくご紹介します。ロード時間を大幅に短縮する上で、この仕組みを理解しておくと役に立つと思います。これによって実際にパフォーマンスが 2 倍改善されたプロジェクトもあります。
AUP が機能する技術的な仕組みや、その効果を最大限に引き出すために使う API を知りたい方は、ぜひ以下を読み進めてください。
最適化された最新版の AUP の実装は Unity 2018.3 ベータ版でご使用いただけます。
まず最初に、AUP がどんな場合に使用されるか、およびローディング処理のプロセスについて、詳しくご紹介します。
バージョン 2018.3 以前は AUP はテクスチャのみを扱っていました。2018.3 ベータ版からはテクスチャとメッシュがロード可能になりましたが、一部例外もあります。読み込み/書き込み有効のテクスチャとメッシュ、および Compressed(圧縮)メッシュには AUP は使用できません。([注]バージョン 2018.2 で追加された Texture Mipmap Streaming も AUP を使用します。)
ビルド処理中、テクスチャやメッシュオブジェクトはシリアライズされたファイルに書き込まれ、大きなバイナリデータ(テクスチャあるいは頂点データ)は付随の .resS ファイルに書き込まれます。このレイアウトはプレイヤーデータとアセットバンドルの両方に当てはまります。オブジェクトとバイナリデータを分けることにより(通常、小さなオブジェクトが入る)シリアライズされたファイルのより高速な読み込みが可能になり、大きなバイナリデータは .resS から効率的に読み込めるようになります。テクスチャオブジェクトやメッシュオブジェクトは、デシリアライズされた時に AUP のコマンドキューへコマンドをサブミットします。コマンドが完了した時点で、そのテクスチャあるいはメッシュデータは GPU へのアップロードされており、オブジェクトがメインスレッドに統合可能になっています。
アップロード処理中、.resS ファイルからの大きなバイナリデータは固定サイズのリングバッファに読み込まれます。一旦メモリに入ったデータはレンダースレッド上でタイムスライス方式で GPU にアップロードされます。システムの挙動を変えるために変更できる 2 つのパラメーターは、リングバッファと、タイムスライスの長さです。
AUP では各コマンドごとに以下の処理が行われます。
複数のコマンドが同時に進行可能ですが、すべてのコマンドが 1 つの共有されたリングバッファにそれぞれ必要なメモリを割り当てる必要があります。リングバッファが一杯になると、新しいコマンドは待機することになります。この待機は非同期読み込み処理の速度を遅くしますが、メインスレッドのブロックを引き起こしたりフレームレートに影響を及ぼしたりすることはありません。
以下は、それぞれの特徴を簡単に整理したものです。
読み込みパイプラインの比較 | |||
AUP なし | AUP | 効果 | |
メモリ使用 | データをデフォルトヒープから読み出しながら割り当てる(ハイウォーターマーク) | 固定サイズのリングバッファ | ハイウォーターマークの削減 |
アップロード処理 | データが使用可能になると同時にアップロード | 固定タイムスライスで少しずつアップロード | ヒッチのないアップロード |
ポストプロセッシング | 読み込みスレッドで実行(読み込みスレッドがブロックされる) | バックグラウンドのジョブで実行 | 高速なローディング |
バージョン 2018.3 で AUP を最大限に活用するために、ランタイムで調整できる 3 つのパラメーターがあります。
これらの設定はスクリプティング API を使うか、または Quality Settings(品質設定)メニューから調整可能です。
大量のテクスチャとメッシュを、AUP でデフォルトのタイムスライス(2 ms)およびリングバッファ(4 MB)でアップロードする場合の負荷を見てみましょう。読み込みが行われているので、1 レンダーフレームごとに 2 回のタイムスライスが入るため、アップロード時間は 4 ms 取られるはずです。しかし、プロファイラーのデータを見ると、約 1.5 ms しか使われていません。また、リングバッファ内でメモリが使用可能になっているので、アップロード直後に新しい読み出し処理が発行されているのも確認できます。このことから、より大きなリングバッファが必要であることが分かります。
試しにリングバッファを増加させてみましょう。ローディング画面なので、アップロードタイムスライスも増加させた方が良いでしょう。リングバッファを 16 MB、タイムスライスを 4 ms とすると、以下のようになります。
今度は、ほぼすべてのレンダースレッドの時間がアップロードに使用されており、アップロードとアップロードの間のわずかな時間がレンダリングに使用されていることが分かります。
以下は、この負荷サンプルでアップロードタイムスライスとリングバッファサイズの設定を変えた場合の、それぞれのロード時間を比較したグラフです。テストは MacBook Pro(2.8GHz Intel Core i7 搭載、OS X El Capitan)で実行されました。アップロード速度と入出力速度はプラットフォームやデバイスによって異なります。負荷サンプルとして使用したのは、Unity 社内でパフォーマンステストに使用されるサンプルプロジェクト『Viking Village』のサブセットです。他に読み込まれているオブジェクトがあるので、値を変更したときにどの程度パフォーマンスが改善したかは正確に確認できません。しかし、このサンプルの場合、4 MB・2 ms から 16 MB・4 ms に変更すると、テクスチャとメッシュの読み込みが少なくとも 2 倍以上速くなることは確実です。
各パラメーターを変更してみると、以下のような結果になります。
したがって、このサンプルプロジェクトでロード時間を最適化できる設定は以下のようになります。
QualitySettings.asyncUploadTimeSlice = 4 QualitySettings.asyncUploadBufferSize = 16 QualitySettings.asyncUploadPersistentBuffer = true
テクスチャとメッシュの読み込み速度を最適化するときの推奨事項は下記のとおりです。
Q: タイムスライスされたアップロードがレンダースレッドで起こる頻度はどの程度ですか?
Q: AUP 経由で読み込まれるものは何ですか?
Q: 非常に大きなテクスチャをアップロードする場合など、アップロードされるデータに対して、リングバッファの大きさが足りない場合はどうなりますか?
Q: 同期ロード API(Resources.Load, AssetBundle.LoadAsset など)はどのように機能しますか?
皆様のフィードバックをお待ちしています。本ページのコメント欄か Unity 2018.3 ベータ版のフォーラムから、ぜひご意見をお寄せください!
Is this article helpful for you?
Thank you for your feedback!