Search Unity

Unity 4.6 / 5.0では、レンダリングで行うUIシステムのバッチングの生成はとても遅いものでした。これにはいくつかの要因がありましたが、究極的にはエンジンを予定通り出荷するための限られた時間のなかでの優先度の問題で、UIシステムについては速度よりも使いやすさやAPIを優先せざるをえなかったのが要因でした。

とはいえ、UIシステムを仕上げる最後の開発スプリントで幸運な事に最適化を行うための助力を得ることが出来ました。UIシステムのリリース後、わたしたちはまとまった時間を取り、何がUIを遅くしていて、どうすれば改善できるかを分析・理解することにしました。

この記事は長いので、ザックリ要約するとこういう話です:私たちはUnity 5.2で(ジョブスケジューリング以外の)すべてをメインスレッドから外して、バッチング用のソートアルゴリズムをそれに合わせて大幅に修正し、UIのパフォーマンスが大幅に向上しました。

パフォーマンス評価プロジェクト

まず、私たちはUIのパフォーマンスを調べるためのテストシーンをいくつか作り、パフォーマンスの変更を把握できるようにしました。これらのテストは、UIシステムにさまざまな方法で負荷をかけます。キャンバスのソート/バッチングの生成にもっとも効果的だったテストは、キャンバスを一面中ボタンで埋め尽くすものでした。テキストとボタン、およびボタンの背景が重ね合わさっているので、どれがバッチング可能かの計算に必ずオーバーヘッドが発生します。テストはUIの要素をつねに変更しつづけるので、毎フレーム再バッチングが発生します。

テストはUIの要素を順番に並べて位置関係を最適化に利用しやすいようにも、ランダムに並べて潜在的にソートアルゴリズムにより負荷をかけるようにも設定可能にしました。バッチングのソート処理はこのどちらのケースでも高速に動作する必要があるのは分かっていましたが、4.6 / 5.0 の実装ではどちらのケースでも速くはありませんでした。

さらに、これらのパフォーマンステストでは、大体〜10000個のUI要素が使われていることも記しておきます。これは実際には現実のプロジェクトに即した要素数より非常に多い構成です。私たちが現実のプロジェクトで目にするUI要素数は多くてキャンバスあたり300程度のUI要素で構築されています。

さらに、この記事における全てのパフォーマンス・プロファイリングに関する数値・グラフは、わたしのMacBook Air(13-inch, Mid 2013)での数値です。
テストシーンの一部:unnamed

元の状態(4.6以前、比較情報なし)

4.6のベータの時に、キャンバスに多くのUI要素があるとバッチングのソートが非常に遅くなるというフィードバックをいただいていました。これはバッチングの描画順をきめる処理の実装が短にアホで何の工夫もなかったことが原因でした。この時は、キャンバス内のUI要素を短に列挙して、たがいにぶつかる要素を調べながら一定のルールでそれぞれの要素にデプスを設定するという方法を採っていました。この方法では新しいUI要素が加わる度に計算速度的には O(N^2) の効率でガンガン遅くなっていきます。パフォーマンスの観点から見ると、こりゃ〜イヤな雰囲気が漂うやつです。

4.6 / 5.0 リリース時(比較対象)

幸いUIシステムのリリース前に最適化をする機会を得られたので、ソート処理の改善に取り組みました。この時活用したアイディアは「順番に描画される要素はおおむね、互いに画面上の近い位置関係にある」というものでした。この方法ではまず、N個の要素を持つUI要素のグループについて、バウンディングボックスを作成し、新しい要素が追加されたときには個別のUI要素ではなく、まずこのバウンディングボックスと衝突するかを調べ、それから必要に応じて個々のUI要素との関係を調べるようにします。この方法ではUI要素の位置がある程度固まっているようなシーンについては真っ当なパフォーマンスの改善が見られたのですが、UI要素がランダムに並んだシーンや、UI要素同士が遠く離れているシーンについては、パフォーマンスの改善効果はほとんどありませんでした。

 このバージョンでUI要素がランダムに並んだシーンのバッチングについてパフォーマンスを見てみると、ソートと描画でだいたい100ms もかかってしまっています。うーん、こりゃ〜ダメだ。遅いですね。

unnamed (1)

プロファイラーのタイムラインを見てみると、他にも心配なことが浮かび上がってきました。UIシステムの処理が完全に他のことをブロッキングしてしまっているのです。特に処理に時間がかかっているバッチング処理はUIの描画の直前に行われる設計でした。これはLateUpdate() の後に発生するのですが、しばしばシーンに配置されたカメラのいくつかが描画されたあとになっています。バッチング処理はLateUpdate()の直後に始まるようにし、シーンが他のものを描画しているあいだに並行して行われるようにした方がよさそうです。

unnamed (2)

ソートの改善(一回目)

取り急ぎ、まずはソートの改善に取り組んでみました。この時はまだ前述の「UI要素が偏在する」というアイディアに基づいていましたが、さらにいくつか知恵を投入しました。上記のソート処理の中で作ったグループを可能な限りバッチ可能な状態で保持するようにし、バッチ処理についてグループ単位で投入/除外ができるようにしました。これは高速化につながりましたが、広い空間に散らばったシーンでは効果が薄く、またUI要素の増加数に対しても改善効果がうまくスケールしませんでした。

空間的にグループ化できないUI要素のシーンunnamed (3)

空間的にグループ化できるUI要素のシーンunnamed (4)

うーん、こりゃアカンですな…。結果を見ると別のアプローチが必要なことが分かります。

ソートの改善(2回目)

先に書いたように、ソートはUI要素が離れたところに散らばっているシーンで遅くなる傾向があります。そこで、一歩下がって別のアプローチを考えてみることにしました。色々検討した結果、最終的にキャンバスにグリッド構造を実装する方法を採ることにしました。この方法では、キャンバスのそれぞれの四角形グリッドが「バケツ(bucket)」として機能をし、このグリッドに接するUI要素はバケツに追加されます。この方法なら新しいUI要素が追加されたときに、その要素が接するグリッドを調べてバッチングが可能かどうかをチェックするだけでよくなるわけです。この方法はランダムなシーンでも非常によいパフォーマンスの改善をもたらしました。

空間的にグループ化できないUI要素のシーンunnamed (5)

空間的にグループ化できるUI要素のシーンunnamed (6)両方の設定で同じように改善されています!いい感じですね。

ジオメトリジョブ

次に、UIシステムの処理をメインスレッドから引きはがして別のスレッドに移す作業を行うことにしました。このためには Unity 5 から導入されたジオメトリジョブシステムを利用することにしました。ジオメトリジョブシステムはUnityの内部的な機能で、頂点バッファ/インデックスバッファの生成を別のスレッドで行うことが出来るようにするものです。これを使う事で、先のプロファイラーのタイムラインでメインスレッドにドカンと乗っかっていた処理を他のスレッドに移すことが出来るようになります。メインスレッドにはジオメトリジョブ・およびジョブインストラクションを管理するための処理が若干残っていますが、導入前にメインスレッドにかかっていたコストに比べたらなんてことはないものです。

unnamed (7)

バッチソートの単純化

この最適化のプロセスを通して、プロファイラーの情報を元に他にも沢山の小さな改善を行いました。もっとも大きなパフォーマンスの改善は、ソート中に矩形の重なりをチェックするコードをベクトル化したことです。基本的には、内部のデータ構造について、丁寧にDOD(Data Oriented Design)に沿って重なりチェックのために最適なメモリレイアウトに変更し、1回の関数コールで全部チェックできるようにしました。これでC++プロファイラーの方で出ていたホットスポットを除去し、処理の60%くらいの時間を削減する事ができました。これはソートのパフォーマンスを大きく改善しました!

・・・が、まだです。まだ、メインスレッドに残っている7msを除去したいのです。

 ベクトル化したコードunnamed (8)

メインスレッドから全部の処理を引っぺがす

次のステップはUIの生成処理自体をメインスレッドから削除して他に移すことです。これを行うには、Unity内部のジョブシステムを使っていくつかのタスクを発行します。いくつかの処理は依存関係があるので順々に行われますが、それ以外はバーンと並列化できます。以下がその詳細です:

1) UIの描画を複数の描画命令(renderable instructions)に分割します。1つのUI描画は複数のマテリアルと複数のサブメッシュによって構成され得るので、沢山のドローコールによって構成されることがあります。このタスクは分散可能です。まず、このタスクは論理的に最大の描画処理を扱うために必要なメモリを確保します。次に、発行されてくる描画命令を並列で実行し、それぞれの結果を出力バッファに書き込んでいきます。最後にこの出力バッファを隣接するメモリに結合するジョブが実行されて、一つの有効な描画命令が作成されます。

2) 描画命令のソート。デプス、重なりなどを調べて処理していきます。基本的な方針として、描画時に描画ステートの変更量が最小になるようにコマンドバッファのソートを行います。

3) バッチの生成

  1. 描画のためのコマンドバッファを生成します。また、バッチ、およびサブバッチのドローコールを作成します。
  2. ジオメトリジョブが利用出来る、トランスフォーム命令を生成します。
このジョブはLateUpdate() の直後にスケジューリングされます。これによってUI以外のシーンがUIより前に描画されているあいだにこの処理を実行しておくことができます。これらのジョブがスケジュールされるときに、メインスレッドによってフェンスが設定されます。このフェンスの働きによって、ジオメトリジョブとキャンバスのレンダリングは、それぞれ必要なデータが作成されるまで適切に待つ形になります。

下記の例では、ジオメトリジョブがバッチ生成が終了しおわるまでストールしていることが分かるかと思います。このストールについてはさらに注意深くテストしていく必要がありますが、基本的にはテストシーンにUI以外に描画する要素がないのでこれが発生していて、シーンの複雑度が上がればおのずとこのストールの問題性は下がっていくはずだと考えられます。unnamed (9)

MacBook Airよりもうちょっとコア数の多いパソコンで実行した結果
unnamed (10)
やりました!これで非常に高価な(覚えてますか?テストシーンには10000個も要素があります)UIで、メインスレッドのコストが0.4msまで下がりました。

その他に行ったパフォーマンス改善事項

  • 2D 矩形クリッピングの追加。現実的には、ほとんどのUIはステンシルバッファが要らないということに気がつきました。2D Rect でクリッピングできれば、ドローコールも描画ステートの変更も削減できます。
  • 2D 矩形カリングの追加。UI要素が描画バウンダリの外なら…ま、カリングしたいですよね。
  • スマートキャンバスコマンドバッファ
    • テキストや通常の要素の間でシェーダーやマテリアルを共有
    • set pass callを劇的に削減
    • UIに関連するデータをマテリアルプロパティブロックに押し込む
    • 通常のケースではUIでは1回のSet pass callのみが実行され、そして複数のドローコールが呼ばれる
  • * UIを1つのメッシュ/インデックスバッファに結合
    • DrawIndexRangeを使った描画
    • 1つのVBO/インデックスバッファを使い、必要に応じてリサイズする
    • 2^16′(65536) を超えるインデックス数になったらドローコールを分割する

次のステップ

現在、ソートやバッチングの生成処理はそれなりにいい感じで動作しているようです。これをさらに改善するために出来ることはありますが、現在の一番大きな問題はジオメトリジョブを発行するためにかかっている時間です。すでにメインスレッドから引きはがされ、独立したジョブとして動作してはいますが、パフォーマンスを改善するならこの処理の改善はよい検討項目です。この一連の作業のなかで、まだわたしには自分が色々とアホなことをやっているのではないかという確信があります – 例えば重要度の高いループの中で分岐処理をしてしまっているとか、例えばそういうやつです。他にも現在通常の計算処理で対応してしまっているが、ベクトル化することでいい感じに高速化できるだろうという部分もあると思います。
ほかにも、もっと高い観点から考えていくと、現在再バッチングが発生しているものの、これを最小化するための工夫というのも考えられるでしょう。これからもやれることは沢山ありますが、とりあえず今日、この記事で紹介した改善内容はUnity 5.2 ですでに実装されていて、大きなパフォーマンスの改善を果たしています。

まとめ

Unity 5.2 の新機能は結構すごいです。これらの新機能がUIシステムのメインスレッド上のコストを最小化しましたし、バッチング処理の最適化にも役立ちました。私たちが今回の仕事をするにあたって、プロファイラーを非常に重要な指標として使い、問題がどこにあるのかを確認してきました。1つ2つの問題については古いやり方が適切でない事がわかったので、すでにリリース済のコードであっても古いアプローチを捨てて新しくやりなおすことにしました。Unityの内部では、こういった仕事を他にも沢山やっていて、皆さんが報告して下さるダメなところを特定して、Unityを誰にとってもより良くするために奮闘しています。
皆さんのバグレポート、そしてあわせて問題を調査できる実際のプロジェクトを頂けることが本当に助かっています。いつも協力してくださってありがとうございます!

-UIチーム

42 コメント

コメントの配信登録

コメント受付を終了しました。

  1. The UI is still broken in the latest 5.2.0p1. Half of the gui is unclickable. raycasts go thru.
    Maybe thats why the gui is faster, because IT IS NOT WORKING.

    1. ninjapretzel

      9月 21, 2015 5:08 pm

      What I want to know is if these improvements can be applied to the Legacy GUI system.
      The UGUI system is kinda unwieldly, and it’s difficult to make procedural layouts that use data, which is something the Legacy GUI is incredibly good at doing.

      After a little bit of fiddling with both, I’d even say the Legacy GUI is easier to make look good at different resolutions than UGUI is, and it doesn’t clutter up the hierarchy with tons of objects.

    2. If there is an eventsystem in the scene (and no graphic raycasters), then your UI will not be clickable.

      For every canvas root node you should ensure you have a Graphic Raycaster, as a graphicRaycaster does not raycast nest UI elements on canvas root nodes not in the same root node as tha Raycaster itself.

      You also need some kind of InputModule in the scene, either one of the default InputModules, or one created by yourself.

      I have no problems making the UGUI system work for me, using my own InputManager/Inut module handlers.

  2. I also upgraded to 5.2 and for my iOS game the CPU usage dropped from 45% to 20%. Great! But on the other hand the GPU usage increased, which now makes my game a lot slower. In the xcode performance profiler the renderer now runs constantly at 100%, before it only ran at 33%.

    Very strange…:(

  3. lukas heenan

    9月 13, 2015 1:26 am

    I upgraded to 5.2 yesterday and it’s killing me softly. So many weird issues now. My Android game is pretty much unplayable at this point. Reverting to 5.1.3.

  4. i see, tried the 5.2.0 and i think its faster because it is not working. complete canvases cant get raycasts.
    buttons not working.
    as you can see from here others also noticed it: http://forum.unity3d.com/threads/upgrading-to-unity-5-2-ui-problem-with-raycast-target.353586/
    the whole game is now broken. had to fall back to 5.1.3p2 and now everything works again.

  5. Max Stankevich

    9月 9, 2015 1:47 pm

    I don’t see shadows or other mesh modifiers in Performance Project (looking in picture), they heavily influence on UI performance / memory allocations and are the bottleneck of whole system.

  6. Interesting!I will update unity 5.2~

  7. This rules! Thanks for going so in depth. We are updating our UI heavy project, XPETS, to 5.2 right now. Excited to see if we get some performance increases!

  8. First tests shows a dramatic decrease in frame rate for our UI-based game.

    For one Android phone the frame rate dropped to about half of Unity 5.1.2. Will analyse why this is happening in the coming week. We had performance problems before and they are far worse now…

  9. Whether is the 2D Rect Mask only for rectangle mask, not for nonRectangle mask?

    1. အမ ရ ထ တ လ တ ဖတ ပ ည လ မ အ မ ပ ပသ ရတယ ..ဒ လ ပ ပ ..လ တစ ယ က မ တစ ခ ခ က တ ထ ခ ကတ ခ ည ပ ပ …အမမ လ ထ ခ တ attraction တရပ ရ နတယ ..အ ဒ က ဘ လ တ ပ တတ ဘ …အမစ က ဖတ ပ ရင သ တယ လ တ တ မ တယ ….အမ ပ ရ င ပ စ… န က ဆ ပ တ စ သ လ အ တ က က မ သဗ ..ဘ တ .. ရ င ရယ က ရ င ရယ ……… ခင တ …မ နတ

  10. Great job, one more reason to complete my transition to the new UI.

    Quick question: should we expect benefits from multithreading when making 3D UIs ? Are they also rendered at the end of the frame ? (I would assume they are not)
    What about rendering the UI to a rendertexture? I suspect multithreading would not provide the same gain for similar reasons

  11. Miguel Ferreira

    9月 8, 2015 9:10 am

    Hi,

    Great news, I just have one small question. What exactly do you mean with “vectorised a bunch of our rectangular overlap checks in the sorting” and “and it’s also using a bunch of slow maths that could handle being vectorised very nicely”? Could you give one small example?

    1. Allows us to perform the same operation on datasets in a parallel way. https://en.wikipedia.org/wiki/SIMD

  12. Do you rewrite the whole UI System and Change the API of UI System? And Is it expensive to update existing project to unity 5.2? finally, Whether is the new UI System compatible with the old UI System’s API?

    1. This was for the in game 4.6+ UI system. API is the same, just a backend upgrade.

  13. Since you mention command buffers, will we have the ability to use the UI system with the existing command buffer API? Specifically, I want to render canvas renderers to a render texture, but I think it only works with mesh renderers. Maybe also expose the UI rendering step in the CameraEvent. This would be quite useful for making special effects for the UI, especially for text.

  14. Yeah, just when I need it.

  15. That parallel UI/geometry rendering scheme is a thing of beauty! Great job guys!

    We are currently in the process of updating ALL of KSP’s UI to use 4.6/5 canvas components (from the many redundant/conflicting UI solutions we had before)… That in itself is already a massive load off our frame times, so Unity 5.2 should make our UI overhaul that much more worth the effort!

    Very great news indeed! Many thanks from the KSP team!

    Cheers

    1. Looking forward to seeing what you come up with :)

  16. Is there any hope of UI performance improvements in 4.6.x? (The move to 5 is not a trivial thing for projects with a lot of baked lighting…)

    1. 6 comments below, Tim wrote “Only for 5.2”.

  17. Francesco Miglietta

    9月 7, 2015 6:32 pm

    Good Job UI Team!

    You don’t know how many games are just UI-based ;)

    Cheers

  18. Is Unity 5.2 still slated for tomorrow?

    1. Aras Pranckevičius

      9月 7, 2015 6:03 pm

      Yes

  19. Hey, I’d love a blog post talking about what you are planning to visual scripting :D

    1. Just cause it’s simple doesn’t mean it’s not super helpufl.

    2. Heck yeah ba-ebye keep them coming!

  20. Is this UI optimisation benefit the 2d sprite sorting for isometric map ?

    1. *You* are awesome! And I might re-think cnninag at some point now that you’ve brought to mind the possibility of doing it in December rather than the summer! I loved cnninag days as a child, but it was alway so hot.

  21. Also take a look at UI performance when used with Unity’s animation system, for me, completely killing performance.

    1. Mr. Fortune

      9月 9, 2015 7:02 pm

      I’m experiencing the same problem when attempting to fade in/out a group of UI elements.

  22. Only for Unity 5.2? Not 4.x?

    1. Only for 5.2

  23. “We reached the first step on the path to pulling the UI off the main thread by using the new Geometry Job system which was introduced in Unity 5. This is an internal feature that can be used to populate a vertex / index buffers in a threaded way.”

    In this sentence, the word “internal” really sucks :D.

    Any idea when mere mortals.. I mean when “us users” can implement and shedule jobs like this? ;)

    1. I can’t say currently. I’m not sure if it’s on the roadmap.

      1. Aras Pranckevičius

        9月 7, 2015 5:44 pm

        Ability to efficiently “create” geometry from threads (i.e. exposing our “geometry job” thing to scripting) is on the wish list of things we want to do. We have some experiments in that area, but nothing we’re ready to ship/test yet. Stay tuned!

        1. we cannot wait to give this a go as the current dynamic mesh generation is the main cpu bottleneck for us

      2. Weeeeh! Same here, Bless, ang Triz maoy paspas kaayo mataikka sa iyahang name. Sa nag-tour mi sa school, ang amahan nawala pa, ang Triz diritso nakahinumdom sa locker nila. Ah kids!As for the bus riding, it’s Triz’s 3rd day today and for the past 2 days, she had enjoyed the experience. We asked her if she wants to be dropped by pero niingon man nga she is fine with riding the bus. I was all worried at first, pero pagkakita naku nga daghan pa mas gamay sa iyaha, katong mga kinder pa, na-comforted ra ko. Shiloh will do great for sure! And the mommy? Nah worries will always be there! Early BPC hopping here!

  24. Marc Schaerer

    9月 7, 2015 2:32 pm

    Now this is finally great news, especially for slow but heavily multicore oriented platforms like Android or iOS in the future (the ipad air2 sits on 3 cores) :D

    Congratulations, looking forward to run further tests on 5.2 more sooner than later.

  25. Great to see this sort of a hard focus on optimization, this actually might be the last kick I need to migrate over to the new UI & update to 5.2 once it’s out!