Unity5.4でComputeShader(HLSL)のコンパイルに失敗する
Boltzmannです.
ラベリングの並列実装を試みていたところ, あるアルゴリズムを実装したところでUnityに異変が生じました.
具体的には, エディタ(VisualStudio2015を使用)でComputeShader(HLSL)のスクリプトを編集した後, Unityに移行するとUnityがフリーズし, その後PCもフリーズするという状態です.
これが二回ほど起きて, さすがにこれはまずいと思い原因を調べ始めました.
結論から言えば, もっとも考えられる原因はこれでした.
[COMPUTE] HLSLCC TIMES OUT COMPILING COMPLEX SHADERS (BAD PERF ON COMPLEX SHADERS) -Unity Issue Tracker
もともと5.3で作業していて, このページによれば5.4.0で解消されたということで5.4.2にアップグレードして再度試しましたがやはりだめでした.
具体的な症状は, 「Unity Shader Compilerが大量にメモリを食ってからタイムアウトで終了する」というものです.
問題が発生した環境は以下の通りです.
Unity5.3.?(ちゃんとメモしなかった), 5.4.2
CPU:Intel Corei5-5200U
メモリ:8GB
(GPU:Intel HD Graphics 5500)
次にShaderComplierのタスクを以下に載せます. だいぶメモリを食っていることがわかるかと思います.
これが5~10分ほど続いて, 最終的にタイムアウトで終了します. タイムアウトで終了するので, シェーダーはコンパイルできていません. この状態でPlayしても以下のようにカーネル関数を見つけることができないというエラーが出ます. (もちろんスクリプト上は存在するカーネル関数です. )
最後に, この問題が発生した際のスクリプトを記載します. スクリプト全体ではありませんが, この部分をコメントアウトすれば正常にコンパイルされる, という部分を記載します.
なお, 以下のスクリプトの参考文献を次に示します.
柴田直樹, 山本眞也. SumiTag : あまり目立たないARマーカーとGPGPUを利用した読み取り方法. 研究報告マルチメディア通信と分散処理(DPS). 2011, 2011-DPS-149巻, 7号
<br /> bin = lerp(0, 1, step((float) grayscale, 0.4));<br /> Input.GetDimensions(width, height);<br /> index = width * thid.y + thid.x;</p> <p> LabelBuffer[index] = bin * index;</p> <p> for (i = 0; i < 1000; i++)<br /> {<br /> AllMemoryBarrierWithGroupSync();</p> <p> label = LabelBuffer[index];<br /> orig = label;</p> <p> if (bin != 0)<br /> {<br /> if ((thid.x > 0) && (label < LabelBuffer[index – 1]))<br /> label = LabelBuffer[index – 1];<br /> if ((thid.x < width – 1) && (label < LabelBuffer[index + 1]))<br /> label = LabelBuffer[index + 1];<br /> if ((thid.y > 0) && (label < LabelBuffer[index – width]))<br /> label = LabelBuffer[index – width];<br /> if ((thid.y < height – 1) && (label < LabelBuffer[index + width]))<br /> label = LabelBuffer[index + width];</p> <p> label = LabelBuffer[LabelBuffer[LabelBuffer[LabelBuffer[label]]]];</p> <p> if (label != orig)<br /> {<br /> InterlockedMax(LabelBuffer[orig], label);<br /> InterlockedMax(LabelBuffer[index], label);<br /> }<br /> }<br /> }</p> <p> AllMemoryBarrierWithGroupSync();</p> <p> switch (LabelBuffer[index] % 20)<br /> {<br /> case 0:<br /> color = float3(1, 0, 0);<br /> break;<br /> case 1:<br /> color = float3(0, 1, 0);<br /> break;<br /> case 2:<br /> color = float3(0, 0, 1);<br /> break;<br /> case 3:<br /> color = float3(1, 1, 0);<br /> break;<br /> case 4:<br /> color = float3(1, 0, 1);<br /> break;<br /> case 5:<br /> color = float3(0, 1, 1);<br /> break;<br /> case 6:<br /> color = float3(0.1, 0.2, 0.5);<br /> break;<br /> case 7:<br /> color = float3(0.2, 0.5, 0.1);<br /> break;<br /> case 8:<br /> color = float3(0.5, 0.1, 0.2);<br /> break;<br /> case 9:<br /> color = float3(0.5, 0.2, 0.1);<br /> break;<br /> case 10:<br /> color = float3(0.1, 0.1, 0.1);<br /> break;<br /> case 11:<br /> color = float3(0.2, 0.2, 0.2);<br /> break;<br /> case 12:<br /> color = float3(0.23, 0.56, 0);<br /> break;<br /> case 13:<br /> color = float3(0.9, 0.2, 0.5);<br /> break;<br /> case 14:<br /> color = float3(0.4, 0.8, 0.2);<br /> break;<br /> case 15:<br /> color = float3(0.4, 0.3, 0.6);<br /> break;<br /> case 16:<br /> color = float3(0.1, 0.3, 0.2);<br /> break;<br /> case 17:<br /> color = float3(0.5, 0.5, 0.2);<br /> break;<br /> case 18:<br /> color = float3(0.5, 0.1, 0.2);<br /> break;<br /> case 19:<br /> color = float3(0.8, 0.8, 0.3);<br /> break;<br /> default:<br /> color = float3(1, 1, 1);<br /> break;<br /> }</p> <p> Result[thid.xy] = float4(color, bin);<br />
ちなみにswitch文のブロックのみをコメントアウトしても問題は解消されません.
なんとなく, スクリプトを編集しているときの感覚として, InterlockedMaxをfor文の中に組み込んだのがとどめの一撃になった気がします.
とりあえず実装を見直しますが, 実装を誤るたびにUnityが大量にメモリを食ってフリーズするのは勘弁してほしいです.
<2017/2/22 追記>
原因不明として放置してあったのでとりあえずあの後の調査で考えられる原因を書いておこうと思います.
原因として有力なのはコンパイラによるfor文の自動展開です.
GPUデバイスコードは命令数が多いほど並列性が高くなったりするのでコンパイラが可能な範囲で積極的にfor文を展開することがあります.
今回の場合は展開した後のコードがあまりに長くなったためオーバーフローしてフリーズしたのではないかと考えられます.
調べていないので何とも言えませんが, こういった自動展開に関しては必ずそれを制御する#pragmaがあるはずなので, それを使うことで解決できるのではないかと思います.
他の解決策としては, 展開できない程度にコードを複雑にする, というものがあげられます.
上記のスクリプトに関しても, 終了条件をfor (i = 0; i < 1000; i++)のようにべた書きするのではなく, tmp=1000のようにしてからfor (i = 0; i < tmp; i++)のようにすることで回復したように思います.
<追記終わり>