UE4 ShaderCompileについて

 

UE4 ShaderCompileについて

 

f:id:CrabPunch:20201122111657p:plain

UE4確認バージョン  4.25.4

前書き

 

人生初のAdventCalendar参加です。

間違いなどあればご指摘いただけると幸いです。

UE4初心者なのでお手柔らかにお願いします。

 

Unreal Engine 4 (UE4) Advent Calendar 2020 - Qiita

Unreal Engine 4 (UE4) その2 Advent Calendar 2020 - Qiita

Unreal Engine 4 (UE4) その3 Advent Calendar 2020 - Qiita

 

今回はUE4上で作業中でよく遭遇する上記ウィンドウの

ShaderCompileについて何点かまとめていこうかと思います。

 

シェーダーコンパイルとは

このシェーダーとはなんなのか、この数字はなんなのか?

 

・シェーダーとは?

シェーダーとはGPUで実行されるプログラムにあたります。

基本的には画面に描画される処理の振る舞いが

大部分にあたると考えてもらって問題ありません。

 

そのためメッシュのマテリアルやポストプロセスのマテリアルとの

関連性が非常に高いものとなっています。

 

・シェーダーコンパイルって?

 

UE4のようにマルチプラットフォームに対応しているGameEngineなどですと

ノードエディターでマテリアルを作成し、どのような見た目になるな定義した

ものを、対象となるプラットフォームのGPUにあわせた

シェーダーを生成しないといけません。

 

シェーダーの言語はDirectXで(HLSL)  OpenGLで(GLSL)といった

シェーダーの言語としての書き方も違います。

 

UE4上ではマテリアルのノードで定義された内容から

UE4用の中間的なusfというUE独自のシェーダー言語にいったん変換されます。

 

続いてusfファイルからhlslファイルへと変換されます。

これはデバイスに依存しない最適化を行い。

場合によってはクロスコンパイラを使用してhlslからglslへと変換を行います。

 

この用にマテリアルを変更や作成した際は、このように

対象のプラットフォームが理解できる言語への変換作業が行われます。

 

 

・この膨大な数字はなに?

 

シェーダーがマテリアルと関連しているものとして認識できてきたかと

思いますが、じゃあこの数字はなに?

マテリアルこんなにあるの?

と疑問に思われるかたもいるかと思います。

 

シェーダーコンパイルでは主にマテリアルからの変換作業となりますが

シェーダーの数=マテリアルの数

ではありません。

 

1つのマテリアルを生成したとして、そこから

頂点での振る舞いを定義したバーテックスシェーダー

ピクセルの振る舞いを定義したピクセルシェーダー

さらにライトの状況や、マテリアルの使用箇所の状況にあわせた

最適化された最小のシェーダーがいくつか生成されます。

 

 

シェーダーの中身

実際にシェーダーの中身を確認してみることとします

新規でプロジェクトを作成

コンソールコマンドにて

RecompileShaders all

と入力をするとEngineとProjectのシェーダーのコンパイルがされます。

 

ログを確認すると

LogShaderCompilers: Display: Worker (19/21): shaders left to compile 5912

と5912のシェーダーコンパイルが走ります。

 

これを確認した後に新規作成でマテリアルを1つ作成します。(NewMaterial)

この状態で全てのシェーダーをコンパイルさせると

LogShaderCompilers: Display: Worker (19/21): shaders left to compile 5927

 

さらに追加で空のマテリアルを作成すると(NewMaterial1)

LogShaderCompilers: Display: Worker (19/21): shaders left to compile 5942

 

と、デフォルトの空のマテリアルを1つ作成するたびに

シェーダーの数が15づつ増えていることが確認できます。

 

ConsoleVariablesの

r.DumpShaderDebugInfo=1

を有効にした状態でシェーダーをコンパイルさせると

Windowsであれば

プロジェクト以下の/Saved/ShaderDebugInfo/PCD3D_SM5

以下にマテリアルごとなどのフォルダが確認できるはずです。

f:id:CrabPunch:20201122134002p:plain

 

実際にNewMaterialの中身を確認すると

FEvaluateSurfelMaterialCSのフォルダが1つ

FLocalVertexFactoryのフォルダの下にフォルダが14

合わせて15個のシェーダー用のフォルダがあることが確認できます。

f:id:CrabPunch:20201122134424p:plain

 

f:id:CrabPunch:20201122134436p:plain

 

 

実際に1つ フォルダの中身を確認してみましょう。

f:id:CrabPunch:20201122135213p:plain

この中で重要になってくるのがusfファイルと

d3dasmファイルとなります。

 

usfファイルの中身は先ほど説明したようにUE4用の中間シェーダー言語となっています

実際中身としてはhlslやglslなど書きなれているかたであれば

親しみやすい記述となっているかと思います。

f:id:CrabPunch:20201122140832p:plain

 

このusfファイルをhlsl用のコンパイラであるfxc.exeにて

directx用のシェーダーアセンブラのファイルが作成されます。

それがd3dasmにあたります。

f:id:CrabPunch:20201122141617p:plain

ネイティブでの制作になれているかたであれば

これらのファイルを確認して最適化の余地がないか、無駄がないかなど

確認してみるのも手かもしれません。

 

1つのマテリアルから複数のシェーダーが生成されることが

これで確認できました。

ただ、たとえばこのマテリアルの

ShaderModeをUnlit設定するや

Used with static Lighting のみとした場合であれば

f:id:CrabPunch:20201122145324p:plain

11シェーダーとシェーダーの数が減ります。

とくに意識してマテリアルでの設定を変更していない場合

プロジェクトの最後のほうになればなるほど無駄なシェーダーが増え

コンパイル時間が膨大となる危険性があります。

 

このあたりに関しての詳細は以下のリンクより

※参考資料

www.slideshare.net

 

ログ情報追加

マテリアルの設定でシェーダーの数がかわることがわかりました、

しかし、そのマテリアルがどれだけシェーダーを作成されたかなどの

確認がdebugのフォルダを確認しにいくのでは

手間ですし、認識しづらいです。

f:id:CrabPunch:20201122152017p:plain

 

また右下のようにシェーダーの数だけではなく

現在どのシェーダーがコンパイルされているのか

確認しやすくするためにログを少し改良します。

 

UnrealEngineをGitHubのほうから取得し

UnrealEngine\Engine\Source\Runtime\Engine\Private\ShaderCompiler

ShaderCompiler.cppを開きます。

1148行あたりの

int32 FShaderCompileThreadRunnable::PullTasksFromQueue()
 

 に f:id:CrabPunch:20201122152640p:plain

 

 のようにログを追加します。

 

逆に既存のログのほうは邪魔になるので

コメントアウトしておきましょう。

f:id:CrabPunch:20201122152811p:plain

 

結果はこのようになります

f:id:CrabPunch:20201122154048p:plain

 これによって現在どのマテリアルなどがコンパイルされ

どれだけあるのかが把握しやすくなったかと思います。

 

 この状態でマテリアルなどを開くとコンパイルが走り

どのシェーダーがコンパイルされたかも確認できます。

f:id:CrabPunch:20201122154330p:plain

 

Logへの追加として他にも確認したい項目などがあれば

お好みで追加するとよいかと思います。

 

f:id:CrabPunch:20201128092652p:plain

シェーダーの種類

UE4上でのシェーダーの種類として

意識しておかないといけないものが2つあります。

materialシェーダー

globalシェーダー

の2つです。

 

Materialシェーダー

MaterialシェーダーはUE4においては

MaterialEditorにて編集するものです。

故にメッシュなどと関連性が高いものです。

 

Globalシェーダー

他から使用できる共通のBlueprint Function libraryような

usfファイルと説明しようか悩んだのですが

 

公式説明にて

Globalシェーダーは、マテリアルエディタを使用して作成

されていないシェーダーです。

とあるので、このような定義でよいようです。

 

今回意識するものとしてはこの2点となります。

内部的に細かいもとしては以下のようなものもあります。

 

UE4内での他定義として

Global

Material

MeshMaterial

Niagara

OCIO   ※OpenColorIO

とタイプ分けはされています。

 

パイプラインでの管理として

Vertexシェーダー

Hullシェーダー

Domainシェーダー

Geometryシェーダー

Pixelシェーダー

 独立として

Computeシェーダー

コンソールコマンド

 recompileshaders Changed

globalシェーダーとmaterialシェーダーの変更とを見つけ

その依存したシェーダーを再度コンパイルするコマンド。

現状どこででどのように使うのがいいのかといわれると

わからない、、

materialであればその時Editorを立ち上げているでしょうし

usf変更であればEditor立ち上げ時にチェックがはしるので

現行Editorを立ち上げつつusfファイルを変更した時に

使えるといえば使えるくらいでしょうか。

 

変更点と依存部分だけコンパイルなので

変更が少なければ、すぐに終わりはします。

 

 recompileshaders Global

globalシェーダーを全て強制で再コンパイルする

変更などのチェックはせずに全てコンパイルするので

それなりに時間がかかる。

 

recompileshaders Material [name]

[name]の部分はコンパイルしたいマテリアルの名前をいれます

※[]はいりません

NewMaterialなどだけコンパイルしたいのであれば

recompileshaders Material NewMaterial

となります。

 

同一のマテリアルが存在する場合は最初に見つかったほうが

コンパイルされます。

 ※他注意点 下記にて

 

recompileshaders All

 全てのglobalシェーダーとMaterialシェーダーを

強制でコンパイルします。

最もコンパイルが時間がかかります。

 ※他注意点 下記にて

 

recompileshaders [path]

usfファイルをパスこみで直接指定することにって

単体でコンパイルできるコマンド。

recompileshaders /Engine/Private/BasePassPixelShader.usf

 

recompileshaders Platform [name]

ヘルプで出るコマンドなのですが

ソースコードを見るに、中身があるように思えない、、

 恐らく

シェーダーコンパイルのターゲットを指定しての

コンパイルされるもとかと

思うのですが、SM5なりES3_1なり。

情報募集中

 

 ※参考資料

medium.com

注意点

recompileshaders Material [name]

recompileshaders All

の使用にあたり注意点があります。

 

動作として挙動の仕様を理解せずに使用すると

意図しない挙動になることがあるということです。

 

順を追って説明しますと

まず私の今回のテスト用のプロジェクトですと

recompileshaders Allを実行した時の、

初期のシェーダーの数が5912

そこに新規マテリアルである[NewMaterial]のシェーダーが15

追加での新規マテリアルが[NewMaterial1]のシェーダーが15

合計5942となります。

 

この状態からNewMaterialのシェーダー数を減らす設定をし

NewMaterialのシェーダーの数を4にしました。

 

よって合計は5912+4+15の5931となるはずです。

この状態で保存して再度プロジェクトを立ち上げて

recompileshaders Allを実行すると5912!?

f:id:CrabPunch:20201128150843p:plain

なぜか新規で作成したマテリアル2つがカウントされていません。

 

recompileshaders Materialにて直接コンパイルしようとしてみます。

f:id:CrabPunch:20201128153633p:plain

マテリアルが見つからないといわれます。

 

f:id:CrabPunch:20201128153804p:plain

マテリアルはコンテンツブラウザで確認する限り存在しています。

 

初回時と起動しなおした時でなにが違うのでしょうか?

ためしにNewMaterialをMaterilEditorで開きます。

その後再度

recompileshaders Allを実行すると5916

となりました。

f:id:CrabPunch:20201128162401p:plain

これはNewMaterialの4がカウントされていることがログからも確認できます。

この状態

recompileshaders Material

にてNewMaterialを指定してみるとコンパイルできました。

NewMaterialを見つけることができたということになります。

 

検証を続けます。

NewMaterialとNewMaterial1はコンテンツブラウザ上では存在しますが

レベルに配置されたアクターとは紐づいておりません。

ためしにNewMaterialを起動レベルに配置

NewMaterialを別のレベルに配置した状態で

プロジェクトを起動しなおします。

今回はMaterialEditorを開くことなくそのまま

recompileshaders Allを実行すると5916

という結果になりました。

 

MaterialEditorを開くことなくNewMaterialがカウントされたことになります。

しかし別レベルに置いたNewMaterial1のほうはカウントされていません。

 

続いて、そのままNewMaterial1が置かれているレベルに移動して

recompileshaders Allを実行すると5931

となりNewMaterialとNewMaterial1がカウントされていることが確認できます。

 

再度実験としてプロジェクトを立ち上げなおします。

今度は起動レベルを開いた後、コマンドを使用せず、

NewMaterial1がある別レベルに移動してから 

recompileshaders Allを実行すると5921

となりました。

これは起動レベルに関連するMaterialがカウントされていないことになります。

  

まとめ

長くなり複雑になってきたので要点をまとめます。

recompileshaders Material [name]

recompileshaders All

でのコンパイルの対象となるMaterialシェーダーは

1:コマンド入力をした現在のレベルに紐づいたMaterialを対象とする

2:プロジェクト起動から現在までにMaterialEditorで開いたことのある

Materialを対象とする

3:コマンドにてコンパイルしたことがあるMaterialを対象とする

 

といった挙動になっています。

 

 

コマンド拡張

recompileshaders Material [name]

recompileshaders All

 の注意点を理解できたところで

現状ですと使用用途が難しいところです。

名前にAllってあるけどぜんぜんAllちゃうやん、、、

 

指定のシェーダーをコンパイルをして欲しい状況として

マーケットプレイスでDLしたアセットのコンパイルをしておきたい。

自動テストで指定のレベルなどのコンパイルをしておきたい。

などといった用途には向いていません。

 

別のレベルのマテリアルを指定してコンパイルできると便利なのですが

現状recompileshaders Material

はマテリアルの名前を受けつているのであって

マテリアルのパスは受け付けていません。

 

そこで別レベルのマテリアルを指定することにってコンパイルできる

コマンドを拡張して作成してみようと思います。

recompileshaders Material [name]

を参考に

recompileshaders MaterialPath [path]

を作成します。 f:id:CrabPunch:20201128171433p:plain

 

動作確認

起動直後のレベルでNewMaterial1を

 recompileshaders Material [name]

コンパイルできないことを確認した後、

 recompileshaders MaterialPath [path]

にてNewMaterial1がコンパイルできることを確認します。

f:id:CrabPunch:20201128171732p:plain

上手くコンパイルが動きました。

現在のレベルとは別のマテリアルを指定してコンパイルできた

 

あとは使いたい方法にあわせて

EditorUtilityやBlueprintやC++でもいいのですが

任意のレベルのマテリアルを拾ってくる。

任意のフォルダのマテリアルを拾ってくる。

プロジェクト以下のマテリアルを拾ってくる。

などしてから上記コマンドに流し込めればレベル移動をしてから

コマンドを打つや

レベルに紐づかないソフト参照なマテリアルをコンパイルするなど

使い方ができそうです。

 

そのあたりは各自ご自由にコマンドの投げ方や使い方をしてみると

よいかと思います。

 

ShaderCompileの分散

コアの数

 さて、大量にあるシェーダーファイルなのですが

これらのコンパイルには時間がかかります。

 

シェーダーをコンパイルする処理時間はCPUの性能に依存しますが

昨今のCPUでの処理速度はいろいろな事情により

頭打ちの傾向です。

処理速度が頭打ちの傾向のかわりにコアを増やし

複数の処理を同時に実行できることによって

処理にかかる時間を減らすことができたりします。

 

ShaderCompileにおいても作業を分散して複数のコアによって

コンパイルすることによって大量のシェーダーの

コンパイル時間を短縮することができます。

 

今回の検証における私の環境のCPUはRyzen3900x

こちらはコアが12Coreとなっています。

 

CPUには物理コアと論理コアというものがあります。

今回の環境では、物理コアは12になるのですが

論理コアとは物理コアが分散処理としてつかえるスレッドの処理を

同時にいくつうけつけれるかの数となってきます。

上記のものであれば1コアにあたり論理コアは2になるので

論理コアの総数としては24となります。

※サーバー向けのCPUなどであれば1コアにつき論理コア10のものなどもあります。

 

では24の論理コアを利用してフルに24スレッドにて

シェーダーコンパイルを走らせればよいかというと

そういうわけにもいきません。コンパイル時のタイミングが

Editor作業中であればフルにつかわれればEditor上での作業をする余力はなく

PCは固まってしまいますし、ゲーム中であればさらに他のスレッドにも

処理をまわしたいところです。

人によってはMayaなり別のツールを使っているかもしれません。

 

分散の種類

UE4のプロセスが立ち上がったのちShaderManagerというシェーダーコンパイル

管理部分が動いており、シェーダーの変更があるかどうか監視をしたり

変更されたものを各種スレッドを作成なりに

コンパイルの依頼をなげることになります。

 

それらの細かい設定についてはBaseEngine.iniにいくつか設定できる

項目がすでに用意されています。

f:id:CrabPunch:20201205152853p:plain

ここでコアをどのようにつかって分散するかの設定などができます。

分散の設定の考え方としてはここでは4つ説明します。

1:ShaderCompileWorkerがんばって

現在のCPUでの利用可能な論理コアの数ーNumUnusedShaderCompilingThreads

の数の

ShaderCompileWorkerを立ち上げ分散作業をする。

※bAllowCompilingThroughWorkers=True

2:裏でがんばって

UE4のメインのスレッドでなく裏で使用できる別スレッドにて

シェーダコンパイルをする

裏で動くでのEdtor作用などは可能

※bAllowCompilingThroughWorkers=False

※bAllowAsynchronousShaderCompiling=True

3:ひとりでがんばって

分散をせずにUE4で使用しているメインのスレッドにて

シェーダコンパイルをする

※bAllowCompilingThroughWorkers=False

※bAllowAsynchronousShaderCompiling=False

とてもとても時間がかかる

※ただしシェーダーのデバッグなどの時に使える

 

4: みんなの力をおらにわけてくれ

同一ネットワーク上にある別のPCのコアにシェーダーコンパイル

お手伝いをしてもらう。

IncredibuildなどであればUE4の分散コンパイル

シェーダーの分散コンパイルを標準でしてくれるので

大変高速です。1台のPCであればコアにもある程度しれていますが、

他の現在手のあいているPCのコアであれば

場合によっては数百コアによっての作業などしてもらうこともありえるので、

大量のシェーダーコンパイルも短時間で完了できます。

 

 Incredibuildであれば標準対応されているので

ライセンス購入ししだいすぐにでもつかえます。

 

あとはIncredibuild 以外にも

FASTBuildに対応のための拡張の話などされているところもあります。

 ※今回はこれらの外部の分散コンパイルについてふれるつもりはありません。

 

 

分散設定

たとえば今回の環境ですと論理コア24ですが

デフォルト設定ですと

 NumUnusedShaderCompilingThreads=3

と設定されており、シェーダーコンパイルで3コアは使用しないようになっています

そのためログを確認すると

f:id:CrabPunch:20201205155538p:plain

24-3の21コアのShaderCompileWorkerによって

シェーダーがコンパイルされていることが

確認できます。

左が現在のShaderCompileWorkerのIDで

右が最大ShaderCompileWorkerとなります。

仮にNumUnusedShaderCompilingThreadsを5とした場合

f:id:CrabPunch:20201205155723p:plain

24-5=19となり、コアの使用数を制限できます。

またMaxShaderJobBatchSize=10の

数により1回で各種ShaderCompileWorkerに依頼するシェーダーの数が

調整できます。

各人の作業スタイルにあわせて、このあたりを調整するとよいかと思います。

 

他にも

ProcessGameThreadTargetTimeやらメモリまわりの設定もありはしますが、

今回は時間切れでここまでとなります。

 

Incredibuildのような外部の分散機能を使える状況にないかたは

せめて論理コアの多いものでシェーダーコンパイルをできる

環境にすることによって作業時間が短くなるかとおもます。

※待ち時間を考えたら一番人件費が浮くと思うので

※費用対効果としては悪くないかいものだとおもうので。

 

そういえば、もうすぐクリスマスなので

自分用のクリスマスプレゼントとして購入を検討されてみてはいかがでしょうか?

www.amazon.co.jp

 

未検証

今回シェーダーコンパイルまわりを挙動を確認するにあたって

シェーダー関連の未検証の項目を残しておきます。

人によってはどなたかすでに答えお持ちで、どなたか記事を書いてくださるなり、

分かってる人には

分からない人がなにがわからないかがわからない場合があるので

残しておきます。

 

・シェーダーコンパイルはどのタイミングで走るのか

 レベル起動時、エディター編集時、パッケージ時

 それらはなぜ走り、どういった違いがあるのか。

・レベル移動時に走るシェーダーコンパイルはどこまで対象なのか?

 

DDCとシェーダーキャッシュの関係性

 

・r.InvalidateCashedShadersとの違い

https://qiita.com/EGJ-Nori_Shinoyama/items/e37bcc79866a528841ca

ですとDDCのキャッシュとの兼ね合いが悪いなどの話がありますが、

今回の検証だとキャッシュによりコンパイルが回避される

現状を確認できなかったため。

 

・シェーダーデバッグについて

https://bebylon.dev/ue4guide/graphics-development/shader-development/shader-debugging/debugging-shaders/

 

・UnrealC++でのRHIでのシェーダーの記述について

usfファイルの記述はわかるが上記の情報がすくないため。

 

UE4+FastBuildについて

 

・BaseEngine.iniのシェーダー関連の設定の詳細について

 

 

後書き

以上、今回は簡単なシェーダーコンパイルまわりの

まとめとなりました。

 

もともとアドカレに参加する予定はなかったのですが

突発的に書くことになりました。

 

ことの発端

おかず on Twitter: "ですです!マケプレから大量に落としたアセットで走らせたいという目的なので!
(RecomipleShaders all の仕様をちゃんと調べられてなく…!)… "

 

2020年は世界的にもいろいろあり、大変な年でしたが、

みなさん良いお年を