テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】独自に作成したスレッドの処理時間をCPU Profiler(Timeline)で確認する

まんまタイトルの通りで、.NETで作成したスレッド上の処理時間をProfilerに乗せる方法についてです。

 

 

Profilerで負荷のかかっている場所を見つける

処理負荷や処理時間を知るのに、Profilerは非常に有用なツールです。
パレートの法則で言われるように、大抵の負荷は2割がボトルネックになっている事が多く、Profilerはその2割を見つけるのに有効です。

f:id:tsubaki_t1:20180430232723j:plain

さて、Profilerに処理時間を乗せたい訳ですが、特に何も設定しないならばMonoBehaviour.UpdateやECSのSystemといった単位でCPU Profilerに計上されます。
これでは「◯◯コンポーネントが遅い」事は解っても、何が問題とはハッキリ出来ません。

そこでProfiler.CustomSamplerで特定の処理時間をピックアップします。
サンプルとしてVector3を最後にApplyするだけのモノと、transformを取得してユニットを動かす場合、transformをキャッシュして行う場合の処理時間を見てみます。

gist.github.com

結果はProfilerのTimelineで確認できます。確認すると、Vector3だけ取り出しておいて最後に反映させるモノが、Transformに毎回数値を入れてるモノよりとても速い事が確認出来ます。
transformのposition等を上書きするだけでもソレナリのコストが発生していることです。あとgameObject.transformが内部的にキャッシュされるというのは迷信だと予想出来ます。

f:id:tsubaki_t1:20180501132033j:plain

ちなみにBeginSampleではなくCustomSamplerを使用しているのは、こちらのほうがオーバーヘッドが少なく、正確な値が取れるからです。

 

非同期処理とProfiler

負荷を計上するProfilerですが、実はスレッドが関わってくると途端に面倒になってきます。
C# Job SystemであればC# Job System側である程度の処理時間がProfilerのTimelineに出力されるので、そこまで致命的な問題にならないのですが、.NETの機能で作成したThreadはUnityのProfilerに計上されない為、どの程度の時間がかかっているのかが把握しにくくなっています。
またBeginSampleはメインスレッド上でしか動作しません。

 

この問題を解決するのが、Profiler.BeginThreadProfilingです。このAPIで囲った範囲のsampler.Begin~sampler.Endの範囲はProfilerのTimelineに乗るようになります。
例えば下のようなコードを記述すると、Profilerから処理時間が確認できます。

gist.github.com

f:id:tsubaki_t1:20180501135619j:plain

またsampler.Beginの引数にgameObjectをセットすれば、Profiler Timelineに計上される処理時間とHierarchy上のGameObjectを紐付ける事が出来ます。

 

複数フレームを跨げる

この処理時間は複数のフレームを跨いでもOKで、特にコルーチンのような遅延処理に対してThreadを使用する場合には有用そうです。
ただバージョンによっては載らない事もあるかもしれません。以前はsampler.Endまで到達した処理が無ければ載らなかった気がするので(Unity 2018.1f1で確認)

f:id:tsubaki_t1:20180501220611j:plain

逆にCPU ProfilerのHierarchyに処理時間が載らない点ので、その勘違いは気をつける必要があります。

 

関連

async/awaitのコードはココのを真似させてもらいました。

satoshi-maemoto.hatenablog.com

samplerについての説明。BeginSamplerは止めよう。
どちらかと言えばProfilerで見るような物をゲーム画面に乗せられる方が重要

tsubakit1.hateblo.jp

Profiler便利便利。

tsubakit1.hateblo.jp

【Unity】ECSを使用した大規模なデモプロジェクト "Unite Austin Technical Presentation"

f:id:tsubaki_t1:20180427001949j:plain

簡単なサンプルではなく、ECSを大規模に使用した技術デモ「Unite Austin Technical Presentation」の紹介です。

 

 

Unite Austin Technical Presentation

Unite Austin Technical Presentationは、Nordeus社が作成したゲームの一部プレビューです。内容は大量のミリオン同士の戦い…という感じです。

実際、数万体*1のミリオンがワールドの中央付近で戦闘を繰り広げています。

f:id:tsubaki_t1:20180427010109g:plain

また1キーや2キーでワールドの中身に介入することも出来ます。
例えばマウスの位置に隕石?を落下する攻撃を打ち込むと、大量のミリオンが一斉に吹き飛ぶのが確認出来ます。

プロジェクトは以下のGithubから入手出来ます。サイズがちょっと大きくて、3.3GBくらいあるので、モバイルWifiから落とす場合には注意が必要です。帯域制限を喰らいます(体験談

github.com

 

ユニットの動作や攻撃にはECSが使用されている

このゲームですが、キャラクターの移動や攻撃、キャラクター表現等、多くの部分にECSが使用されています。

キャラクター毎にEntityが割り当てられ、キャラクターの一体一体が各々で動いています。ミリオンだけでなく、弓の攻撃も一本一本がEntityです。

f:id:tsubaki_t1:20180427012516j:plain

ステージとの連携は、ふっ飛ばされた時などの物理挙動にはRaycastCommandで地面との相対距離等が求められていますが、移動はもっぱらNavMeshQueryでパスを取得してパスの通りに移動するというものみたいです。

 

逆に、判定やレンダリングなど、ユニットの動作に関する部分は多くの部分が自作コードになっていました。これは単純に用意されてないというのもありそうですが、幾つかは特別な最適化を施したいというのも理由の一つかもしれません。
例えば以下のような感じ

  • 当たり判定(判定の精度を下げたい)
  • レンダリング(DrawMeshIndirectで一括表示したい)
  • キャラクターのAI

f:id:tsubaki_t1:20180427010640j:plain

これらの処理は大まかな部分をSystemはJobSystemに移譲しており、ワーカースレッドはソレナリの範囲を埋めています。

特に長いのはMoveJobで、中でNavMeshQueryでパス計算とかしてます。

f:id:tsubaki_t1:20180427011010j:plain

 

キャラクターアニメーションは頂点シェーダー

さて、このプロジェクトは数万体という大量のユニットを同時に表現するためにDrawMeshIndirectでキャラクターを描画しています。
とは言え、DrawMeshIndirectで描画するものは基本的にアニメーションを使用できません。その辺り、このプロジェクトでは、頂点シェーダーでポリゴンを動かすことで実現しています。

f:id:tsubaki_t1:20180427015251g:plain

基本的には Animation Texture Baker と同じ系統の技術のようですが、テクスチャをベイクするというよりはバッファを云々してるように見えます。(ちゃんとコードを読んでいない)。このバッファを塗るのに、UnsafeUtilityもガンガン使うという中々に面白くないコードになっています。

 

 なお、何故かTextureAnimationSystemはComponentフォルダ以下にあります。多分ミスです。Systemフォルダに無かったので焦りました。

f:id:tsubaki_t1:20180427020546j:plain

 

状態の切り替えはComponentDataの追加で

ユニットには複数の状態が設定されています。例えば「移動中」とか「攻撃中」とか、あとは「死亡中」とか。

この状態の切替は、ComponentDataの抜き差しで行っていました。

f:id:tsubaki_t1:20180427025041p:plain

ComponentDataの抜き差し(AddComponent/RemoveComponent)は、ComponentDataが所属するチャンクを移動する事になるので余り効率的では無いように思ってたんですが、各動作毎に要求するデータが大きく違うので、その辺りを嫌がったのかもしれません。

まあチャンク間の移動といっても、数箇所の要素を塗り直すだけですし(新しくチャンクが作られなければ)案外問題は無いのかもしれません。

f:id:tsubaki_t1:20180427030732j:plain

 

ユニット情報はPrefabから取得

ECSを使用しているので殆どがコードで書かれるように思っていましたが、ユニット情報等はECSハイブリットの構造を使用して取得していました。
Prefab(GameObject)をInstantiateするのではなく、PrefabからEntityを作ってEntityをInstantiateする感じです。

これでコードを弄る事なく、複数パターンのEnittyを作成しています。

f:id:tsubaki_t1:20180427022246j:plain

このコードを見てEntityManagerのInstantiateにGameObject割り当てられる事に気づきました。

確かにこの使い方は理にかなってるように感じます。まぁ最終的にはScriptableObjectかEntityをシリアライズしたものに纏めてほしい気もします。

 

ユニットの生成位置はMonobehaviourから取得

ハイブリットと言えば、ユニットの生成位置等の情報はMonoBehaviourを継承したコンポーネントから取得していました。
出現位置などの情報はGameObjectでシーン上に配置(Gizmoでプレビュー)し、実際にUnitを生成するのはComponentSystemで行っている感じです。

f:id:tsubaki_t1:20180427032356j:plain

 

関連

このプロジェクトの紹介です

www.youtube.com

ECSとGameObjectのハイブリット

tsubakit1.hateblo.jpECSについて

tsubakit1.hateblo.jp

このプロジェクトのフォーラム

https://forum.unity.com/threads/unite-austin-technical-presentation.522628/

*1:ゲーム開始時は1万くらい、放置すると2万3万と増えていく

【Unity】C# Job Systemを使う時によく見るエラー

今回はC# Job Systemを使ってるとよく見るエラーについてです。

 

 

A Native Collection has not been disposed, resulting in a memory leak.

Googleで日本語にすると以下のような訳になります。

ネイティブコレクションが破棄されていないため、メモリリークが発生します。

 このエラーは内容の通りで、NativeArray等を生成した後に後片付けしてない場合に発生します。
Awake/OnDestroyやOnEnable/OnDisableのように対応した環境でちゃんと破棄しないとコレが出るので注意が必要です。

破棄はDestroyではなくDisposeです。

gist.github.com

JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak

Googleで日本語にすると、以下のような訳になります。

JobTempAllocは4フレーム以上古い割り当てを持っています - これは許可されておらず、漏れそうです

これはAllocatorがTempやTempJobに設定したNativeArrayが、複数のフレームを跨いだ時に発生します。

返却期限を超えないようにNativeArrayを破棄しておけばOKです。

gist.github.com

nvalidOperationException: The previously scheduled job AnyComponent:AnyJobName writes to the NativeArray AnyJobName.anyfield.

Googleで日本語にすると、以下のような訳になります。

nvalidOperationException:以前にスケジュールされたジョブAnyComponent:AnyJobNameがNativeArray AnyJobName.anyフィールドに書き込みます。

 これは少しややこしくて、複数のJobが同一のNativeArrayにアクセスしていた場合に発生します。
普通はNativeArrayに同時に書き込んだり、順不同で書き込んだ時になにかデータがおかしくなる的なバグなのですが、C# Job Systemでは複数のジョブから同一のコンテナにへ書き込む可能性がある実装に対してエラーを出すようになっています。要素単位ではない点に注意。*1

f:id:tsubaki_t1:20180425234951j:plain

この「書き込むか、書き込まないのか」については、[ReadOnly][WriteOnly]属性で判断しています。また、どちらの属性も無い場合には「書き込むし読み込む」という状態になります。

 

つまり、この問題を解決するならば、明確に同時にアクセスしないように定義する、もしくはジョブが同時に実行されないようにするが回答となります。

 

例えばReadOnlyを設定して明示的に同時アクセスしないようにする、

f:id:tsubaki_t1:20180426000533j:plain

https://gist.github.com/tsubaki/862adcd1d5922ac2dfddd25bed8b0f97#file-anycomponent-cs-L46

 

またはスケジュールを発行する際に依存関係を設定して、同時に実行しないようにする…といったモノが考えられます。

f:id:tsubaki_t1:20180426000747j:plain

https://gist.github.com/tsubaki/776470e2067ff6e542d884853c4e55f3#file-anycomponent-cs-L32

 

なお、Jobを使い回す(参照先のNativeArrayだけ書き換えてScheduleを発行)のような場合にもこのエラーが出ます。

 

InvalidOperationException: The native container has been declared as [ReadOnly] in the job, but you are writing to it.

[ReadOnly]を設定したコンテナにアクセスしたオブジェクトに書込が発生した場合にエラーとして出力されます。

注意点として「書込が発生したら」エラーになるらしく、ビルド時には分かりません。

 

InvalidOperationException: The native container has been declared as [WriteOnly] in the job, but you are reading from it.

[ReadOnly]を設定したコンテナにアクセスしたオブジェクトに書込が発生した場合にエラーとして出力されます。

こちらも「読込が発生したらエラー」になります。

 

例えば writeOnlyData += n; みたいなコードを書くと発生します。

 

InvalidOperationException: AnyJob.anyField is not a value type. Job structs may not contain any reference types.

JobSystem内で参照型のオブジェクトにアクセスした場合に発生します。例えば JobのフィールドにComponentを登録して、アクセスしようとしても出来ません。

 

対策は「参照型クラスを使うな」です。C# Jobsystemでは基本的にNativeArrayに結果を格納して、それを料理してもらう形になります。

 

FindObjectFromInstanceID can only be called from the main thread.

Unityのコンポーネントインスタンスにメインスレッド以外からアクセスしようとすると発生します。

 

IndexOutOfRangeException: Index 0 is out of restricted IJobParallelFor range

IJobParallelForを使用した場合に、indexで指定された要素以外の要素にアクセスが発生した発生します。実際に書き込まれるのか等は関係なくエラーになります。

対策としては、明示的にReadOnlyを付けて書込が発生しないようにすればOKです。

f:id:tsubaki_t1:20180426004000j:plain

https://gist.github.com/tsubaki/af86f8891780dd4c692e04b7b1352165

 ArgumentException: System.Boolean used in NativeArray<System.Boolean> must be blittable

NativeArrayに使用できるのはblittable型のみです。boolは非blittable型なので使用できません。
なので、int型にして 0 or 1 みたいな懐かしい感じのことをする必要があります。

Blittable 型と非 Blittable 型 | Microsoft Docs

関連

github.com

tsubakit1.hateblo.jp

www.slideshare.net

*1:要素単位の場合はJobParallelForを使う

【Unity】なんか影が出ない問題の対策

f:id:tsubaki_t1:20180424221235j:plain

サンプルを色々といじっていた所、どうも影が出なくなる現象に当たったので、その原因と対策についてココに書いておきます。

 

 

影が出ない

光あるところに闇があり。影は基本的に光を遮った所に存在し、逆を言えば光があれば影が出来ることを期待出来ます。

f:id:tsubaki_t1:20180424221850j:plain

ただし幾つかの設定によって、光を配置したのに影が出ないという事があります。

影は基本的に重い処理の一つであり、多くの影を無効化する設定が存在します。この影を無効化にする設定を間違えてONにしてしまう事は意外と存在し、その辺り知らないと影が出ないと困ることになります。

 

”影”を無効化する設定

この記事に辿り着くであろう人にまず見て欲しい項目は、Quality Settings(メニューアイテム > Edit > ProjectSettings > Quality Settings)です。
このShadowsの項目がDisable Shadowsになっていると影が表示されません。
ShadowsをHard And Soft Shadowsにします

f:id:tsubaki_t1:20180424222223j:plain

こんな所触ったこと無い? その場合は、もしかしたら2Dでプロジェクトを作ってしまったのかもしれません。最近Unityのプロジェクトを作成する際に2Dを指定すると、この項目やLightmapの設定が全てOFFになります

f:id:tsubaki_t1:20180424222541j:plain

なおScriptable Render Pipelineを利用している場合、この設定が無視される可能性があります。SRPのライト設定は別の場所にあるかもしれません。ScriptableObjectとか。
下の画像はLightweight RenderPipelineの設定ファイルです。影の項目がコチラに移動しています。

f:id:tsubaki_t1:20180424222851j:plain

 

光が影を出していない

次点で意外とあるのが、光が影を出していない事です。

凄く単純で、Lightの設定のShadow TypeがNo Shadowsになっていると影が出ません。
影を出したいなら、Hard ShadowかSoft Shadow辺りを設定する必要があります

GameObjectメニューから作成するLightの初期設定はNo Shadowなので、影がほしければShadow Typeを設定します。

f:id:tsubaki_t1:20180424223047j:plain

Lightmap Staticが付いていない(オブジェクトのStaticフラグがOFF)オブジェクトに影がかかるのは、RealtimeかMixedのライトのみです。

また、HD Render Pipelineは設定項目が大きく違うので注意が必要です。

f:id:tsubaki_t1:20180424223648j:plain

 

物質が光を遮らない

Shadowの設定が有効で、かつ光が影を発生させる設定になっている場合、オブジェクトが光を遮らない設定になっているかもしれません。
例えばTransparentなどのシェーダーは光を遮らない設定になっています。

f:id:tsubaki_t1:20180424224307j:plain

シェーダーが光を遮るのか遮らないのかは、Edit Shaderで表示されるビューで確認できます。Cast Shadowsがnoなら遮らないし、onなら遮ります。

f:id:tsubaki_t1:20180424224504g:plain

f:id:tsubaki_t1:20180424224332j:plain

またRendererのCastshadowがOFFの場合も、影を遮りません。

f:id:tsubaki_t1:20180424224633j:plain

 

SceneViewの設定

Shadowの設定でもLightの設定でも物質の設定でも無い場合、実はSceneViewで設定色々いじった結果なってた的なケースがあります。
SceneViewの上の方の太陽トグルが外れていると、光が出ません。

f:id:tsubaki_t1:20180424224954j:plain

 

それでも駄目なら

エディター再起動したら治ることがあります。

 

関連

tsubakit1.hateblo.jp