iOSでのLuaのffiパフォーマンス

今回の主な話題：iOSでのLuaのffiパフォーマンス、Shader.CreateGPUProgram 最適化、VRデバイス最適化。

---
###レンダリング
Q1：このゲームでは、キャスト範囲を表示するためにプロジェクターを使用する必要があります。画像の端に数ピクセルを透明にしても問題ありませんが、こうすれば実際の範囲が表示と異なる場合があります。投影された画像が端を離れない場合は、十字が顕示されます。 良い解決策はありませんか？使っているのはUnity5.6.2です。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/213cd2ec-2230-503a-96d0-84ceb5f8b2c9.png)
>テクスチャサンプリング時に、テクスチャ座標が[0、1]の範囲を超えると、最も近い（0または1）テクスチャピクセル、つまりテクスチャのエッジにあるピクセルが取得されます。 このとき、テクスチャのエッジに1〜2ピクセルの空白（alpha= 0）がある場合、空白（alpha= 0）ピクセルが取得されます。そうでない場合は、上記の状況になります。
問題主にお勧めするソリューションは二つあります。
1）テクスチャの周囲に1〜2ピクセルの空白スペースを残し、Projector sizeを設定するときに残されたピクセル数によりProjector sizeをscaleします。そうすればscale後の可視部分は、要求と合うsizeであり、範囲の不正確問題を修正できます。
2）Builtin Shaderを変更します。テクスチャサンプリングの時判断をします、テクスチャ座標が[0、1]を超える場合は、alphaを0に設定します。

---
###ロード
Q2:UWAパフォーマンスレポートにShader.CreateGpuProgramの時間コストはとても高いいと見ましたが、これを最適化できますか？
>Shader.CreateGPUProgramが1〜2回しか表示されない場合、問題は大きくありませんが、頻繁に表示される場合は、厳密な最適化が必要です。Shader.CreateGPUProgramの最適化に関しては、主に下記の点に分けることができます。
1）プロジェクトがResources.Loadを通してロードされる場合、この二つの方法て実現できます。
●よく使うシェーダーを「Always Included Shader」に入れ、ゲームがスタート時に直接ロードできるようにします。
●よく使うシェーダーを「Shader Preloading」に入れ、ShaderVariantCollectionの形式でロードします。利点は、動的ロードの時間とロードされるキーワードを指定でき、便利になれるだけでなく、ロード時間も短縮できます。

>上記のはShader.Parseの作業を完了させだけで、Shader.CreateGPUProgramに対しては、ロードしてインスタンス化した後、カメラのビューボリュームに1フレームを直接「レンダリング」できます。こうすれば、Unityエンジンがトリガーされ、CreateGPUProgram操作が実行され、実行中のコストを前にさせます。通常、この操作はゲームのロード中またはシーンの切り替え中に実行することをお勧めします。
2）プロジェクトがResources.Loadを通してロードされる場合、AssetBundle.Loadで直接にShaderをロードして、Shader.Parse操作を完了させます。この後の作業は上記と同じです。

>注意：上記の方法**はUnity 5.0以降のバージョンにのみ**適用であり、ロード後にシェーダーをメモリに常駐させる必要があります。その後アンロードすると、Shader.ParseおよびCreateGPUProgram操作が再度トリガーされます。

---
###レンダリング
Q3：[「Lua + Unityを上手に使い、パフォーマンスを飛ばせよう-LuaJITパフォーマンスピットの詳細説明」](https://blog.uwa4d.com/archives/usparkle_luajit.html)（中国語注意）という記事を読みました。ここでは、「LuaJITはiOSでサポートされていませんが、Interpreterモードは使用できます。」と書いてあります。文章に「注意すべきのは、ffiはJITがオンになっているときにのみパフォーマンスを発揮できます。iOSの場合、逆にパフォーマンスを低下させます。そのため、使うときはすぐにオフにする必要があります。」という言葉があります。

私の質問：
IOS上のJITインタープリターはネイティブLuaよりも早いですか？
Interpreterモードでffiを使ってCライブラリを呼び出すと、ネイティブLuaよりも遅くなりますか？
>Cでバイナリプロトコルを分析したいと思います。たとえば、LuaでCをreadIntやreadShortします。Lua自身はビット操作をサポートしていませんが、Luaのビットライブラリを使ってビット操作を行い、 パフォーマンス分析が非常に遅いため、ffiを使って実現したいです。もう一つCをコール方法もあります、これはRequire Cダイナミックリンクライブラリです。iOSでのffiのパフォーマンスが良くないから、これが最優の選択ですか？
Yunfengのpbcなど、インターネットでは多くのLua関連のプロトコル分析ライブラリがあり、基本的には製品レベルの可用性に達しており、直接参照または使用できます。これらのライブラリのほとんどは、Luaのフィールドごとに展開していませんが、Cによって均一に展開するおり、lazy initの最適化をするかもしれません。フィールドごとにLuaでC function展開をコールすれば必ず言語の相互作用に多くの時間を使います。
ffiをJITから分離すると、パフォーマンスが大幅に低下し（10倍レベル）、大規模なプロトコル分析に使用することは推奨しません。ただし、一般に、クライアントのプロトコル分析はボトルネックではありません（大きなプロトコルをデザインできない、例えばログインによって発行されたユーザーデータなど）。サーバーに対して、JITをオンにできるから、パフォーマンスははるかに高くなります。また、JITするかどうかも構わず、ffiはいづも「メモリが小さい（c structと同じくらい）」という利点があります。
ただし、ffiを選択することはLuaJITをバインドすることと同じであるため、注意すべきです。パフォーマンスの原因で、言語標準、保守性、およびコミュニティ活躍度三つの方面にも、LuaJITはLua5.3ほど良くありません。

---
###VRパフォーマンス
Q4：私たちのプロジェクトはVRに基づいており、単一の建物を作成しますため、2000〜3000面ぐらいは必要です。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/50bb1248-60fb-5468-1c07-d825841e9b91.png)
アーティストはシーン内の 建物を手動で合併し、同じタイプに基づいてマージします。位置は特定の距離がありますので、カメラが合併後ある建物が見えますが、他の建物が見えない状況もあります。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/4d1f632e-bb70-a5a8-1866-7b44de317036.png)
手動で合併した後にSimpleLODスクリプトを追加し、合併された各メッシュでLODを行い、静的に設定し、実行時にUnityをStatic Batchします。これは、実行中には静的なバッチ処理効果ですが、近所の小さな町の家は、非常に遠い鉱山のいくつかの柱と合併して大きなメッシュになります。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2c8fdb88-5f68-8237-46b6-19fccd42891b.png)
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/79bbe905-5252-b525-5f4f-daf1e5197b59.png)
質問は下記のように（読みやすいのため、質問と回答は以下にリストされています）：
Q4.1：私たちのシーンは正確にオクルージョンのあるように特別に設定されています。オクルージョンカリングがレンダリングの最適化に役立つはずです。Cullingの効率を向上させる方法は？
>A：実際、カリングのレンダリング効率を向上するため、レポートでカリングのボトルネックを確認する必要があります。
次の図は、問題主が今回のレポートの具体的なCulling時間コストを確認するとき（コード効率ページで確認できます）、パーティクルシステムの時間コストは高いとみられました。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d6b63e89-cf53-89ed-21f3-2cf82ee33d55.png)
次の図は、パーティクルシステムの具体的なCulling時間コスト状況です。パーティクルシステムのレンダリング数を制御することをお勧めします。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/e1b79194-7928-a585-78da-4785617031f3.png)

Q4.2：同じタイプのモデルを使って手動で合併することは正しいですか？
>A：Offlineで合併する可能性はあり、間違った操作ではありません。しかし、Offline合併で視野以外にあるオブジェクトをあまり多く計算に取り入れないように注意する必要があります。これは実際には非常に難しいので、一般的にStatic Batchingの方はより良い選択です。

Q4.3：そのような静的なバッチ処理の結果は正しいですか？柱が鉱山で見られたときに町のすべての建物がレンダリングされ、レンダリングがCPU時間コストを高すぎになさせませんか？現在、DrawCallのピーク値は305で、三角形の数のピーク値は640,000です。Xiaomi 5以上のスマートフォンを対象としている場合、それはCPU時間コストに影響を与える鍵ですか？Xiaomi VRはrtで実装されているため、2つの目が2回レンダリングされます。 しかし、アーティストはアートアセットの精度を直す気もありません。
>A：60万のパッチ数は確かに高いです。2つの目があるため、シングルでは30万のレンダリングパッチになります。このモバイルデバイスに対してはまた発熱しやすいです。Unity Editorでチェックし、問題２に述べされている合併しすぎ状況は実際に存在しているかどうかを確認してください。図から、Combined MeshはStatic Batchingで合併することがわかっています。これは大丈夫です。つまり、レンダリングの時、視野以外にあるMeshはレンダリングに参加しません。

Q4.4：たとえば、クライアントが30msまたは40msごとに更新されるなど、設計されたフレーム更新間隔を増やす必要がありますか？ それは、CPU時間を削減する方法が他にないためです。
>A：下図からわかるように、UWA-FrameCheckHandle-FrameFastForwardでもUWA-CVRRaceScene-StepUpdateでも、主な時間のかかるボトルネックはこれら3つの関数、特にUWA-CVRRaceScene-ObstacleManager.updateです。これについては、問題主にこの関数を分析し続け、時間コストのボトルネックを正確に特定し、最適化の案を確定するとお勧めします。フレーム制御でコストを低下させるのは仕方がない方法です、具体的な時間間隔は適切なパラメータを見つけるために多くの実験を必要とするかもしれません。ただし、これは本質的な問題を解決するものではなく、本質的な問題は上記の方法で特定して解決する必要があります。
![8 - 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2da59438-b867-8c34-507c-c7295eb7663a.png)
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/34d7f792-26ad-56f0-a9eb-0e3ef2b88b69.png)
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/9b175969-46fc-78be-9bc4-b1658e31a3a1.png)

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com