C-WebGL: Vulkanのシェーダを気合でデバッグする会
これはマジで解ける気がしない...
あらすじ
描画が変。
RenderDocでバーテックスシェーダ後の出力を見ると、頂点はちゃんと送られている。というわけでピクセルシェーダか同期が悪い。
... が、同期はVulkanのバリデーションで文句言われない程度に修正しているし、今は描画を1度やるたびにバリアを入れているので問題になるとは考えづらい。というわけでピクセルシェーダを変更してデバッグしていく。
テクスチャフェッチを止めてみる
元のピクセルシェーダは:
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
これを
gl_FragColor = Frag_Color;
に変更してみるといきなり絵が出た。テクスチャのフェッチ texture2D
を抜くと問題が見えなくなるので、テクスチャのUVかデスクリプタがおかしいとわかる。
染色で値を確認する
シェーダからは printf
とか(通常は)できないので、if文で値をチェックして染色を行う。例えば、
if(Frag_UV.s > 0.5){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}else{
gl_FragColor = Frag_Color;
}
のようにすれば、テクスチャの読み出し座標である Frag_UV
の s
要素が 0.5
を越えているピクセルを赤色にできる。
これを 0.0 未満とか 1.0 以上のような条件にしてみて赤くなるピクセルがあれば、異常な値を頂点シェーダから受けとっていそうということになるが、そういう問題は見られなかった。
GPUエラーが出るようになった
ちょっとコードを変更すると VK_ERROR_DEVICE_LOST
でsubmitが失敗するようになった。
確かにDebug出力にも
0x00007FFFA7904ED9 で例外がスローされました (fwtest.exe 内): Microsoft C++ の例外: VK::Exception (メモリの場所 0x00000075C1F7EA00)。
OBS_CreateDevice: texture sharing is not supported
が出てるな。。 OBSのImplicit layerが不味かったりする。。? → Vulkan SDKのconfiguratorで無効にしても現象は変わらなかった。ここの例外は解消したけど、これ自体はDevice Lostには繋がらないようだ。
Intelのグラフィックスドライバのシンボルをあててみる
なんとグラフィックスドライバのシンボルは https://software.intel.com/sites/downloads/symbols/ で公開されているらしい。
SYMSRV: HTTPGET: /sites/downloads/symbols/igvk64.pdb/D0B95413859D4F2CA9452570F383344E1/file.ptr
SYMSRV: HttpQueryInfo(HTTP_QUERY_CONTENT_LENGTH): 800C2F76 - ERROR_HTTP_HEADER_NOT_FOUND
SYMSRV: HttpQueryInfo: 80190194 - HTTP_STATUS_NOT_FOUND
SYMSRV: RESULT: 0x80190194
https://software.intel.com/sites/downloads/symbols: シンボル サーバーでシンボルが見つかりませんでした。
... ダメじゃねぇか!
そもそも https://software.intel.com/sites/downloads/symbols/igvk64.dll/ を見るとDLLしか無い。ということはエクスポートしか見られないのか。。
Rust製のVulkan実装で様子を見てみる
Intelの実装のバグかもしれないので、Rustのgfx-rs上の実装である:
を使ってみる。
cargo build --all --release --features dx12 --target x86_64-pc-windows-msvc
のようにビルドして、環境変数を
set VK_ICD_FILENAMES=C:\cygwin64\home\oku\repos\portability\libportability-icd\portability-x86_64-pc-windows-msvc.json
のようにセットすれば、システムのVulkanの代わりに使用され、DirectX12を使ってVulkanをエミュレーションする。
これバリデーションに引っかからないのか。。
TRANSFER_DST
は要らないので削除。
vkBindBufferMemory
で死ぬ
次。 vkBindBufferMemory
で死ぬ。
これはUniform bufferのアロケーションで、サイズを本来のサイズの1024倍である64KiBに拡大するとクラッシュしなくなった(描画はしない -- そもそもVisualStudioのGPUデバッグセッションが開始できない)。
...確かに、 vkGetBufferMemoryRequirements
しているコードがどこにも無い!これをやらないとメモリ破壊が発生する可能性があるので、全部にこれをやるコードを挿入する必要がある。。
これで、 IntelのVulkanでも安定するようになった ...が、現象は全く改善しない。。RenderDocでのキャプチャ時に変な模様が出るようになったのでほぼほぼメモリ同期の問題に見えるが。。
texture2D
でメモリアクセスが入るとレンダリングに掛かる時間が微妙に長くなるはずで、それが 何か を追い越してしまっている、つまりフレームが半完成のまま表示されているのではないだろうか。。
EDIT: 何度か起動/終了していたら再発するようになった。。
MoltenVK(Metal) だと正常
とりあえず適当にMoltenVKでもビルドできるようにしてみた(macOS以外未対応 -- AppleプラットフォームではMetalを使いたいので真面目にやる予定がない)。... すると一発完動してしまった。。
こういうの一番コメントに困るんだよな。。つまりシェーダコンパイラが出力しているバイナリは正常な可能性が高く、Windowsで動いていないのはintelのVulkanドライバの何か秘孔を突いていると考えるしかない。
怪しいポイントがあるとすれば、XcodeのGPUフレームキャプチャがこの状態では正常に動作しないこと。ANGLE + Metalでは正常にキャプチャできるので、そこに影響するような形で何か間違えている可能性はある。
... あとScissorの座標設定をY-Flipするの忘れてるな。。
nullDescriptorにしてもゴミが残る
VK_EXT_robustness2
を使うと、GPUにnull descriptor(CPUで言うところのNULLポインタ)を渡した時の挙動を固定できる。
... が、なんとこれを使ってテクスチャとしてNULLポインタを渡しても挙動が変わらず、ゴミが描画される。Robustnessの仕様では、NULLポインタをリードするとゼロが読めるとされている:
- https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/chap14.html#vkUpdateDescriptorSets
If the
nullDescriptor
feature is enabled, the buffer, acceleration structure, imageView, or bufferView can beVK_NULL_HANDLE
. Loads from a null descriptor return zero values and stores and atomics to a null descriptor are discarded. A null acceleration structure descriptor results in the miss shader being invoked.
この挙動が実現されていないので、デスクリプタを通してテクスチャを渡すの自体が上手く行っていない可能性がある。
今のシェーダトランスレータは、使っていないUniformをoptmize outする挙動になっている。このあたりが不味いのではないだろうか。。
Output float4* gl_FragColor : [[Location(0)]];
Input float4* Frag_Color : [[Location(1)]];
UniformConstant SampledImage<float2D>* Texture : [[DescriptorSet(0), Binding(1)]];
Input float2* Frag_UV : [[Location(0)]];
struct cwgl_ubo_s : [[Block]] { ★ GLSLでは生成されないはずの空の構造体ができる
}
Uniform cwgl_ubo_s* cwgl_ubo : [[DescriptorSet(0), Binding(0)]];
void main() {
float4 _12 = *Frag_Color;
SampledImage<float2D> _17 = *Texture;
float2 _21 = *Frag_UV;
float4 _22 = ImageSampleImplicitLod(_17, _21, );
float4 _23 = _12 * _22;
*gl_FragColor = _23;
return;
}
諦めてSwiftShaderで開発を進める
もう諦めてGoogleのソフトウェアVulkan実装であるところのSwiftShaderで開発を進めることにした。絵も出るし。。
SwiftShaderは適当にビルドして環境変数を設定すれば使える。
set VK_ICD_FILENAMES=F:\build\swiftshader\Windows\vk_swiftshader_icd.json
ただし実際のGPU実装と違ってDepthは32bit floatのみのサポートとなるので、Depthのフォーマットを調整する必要がある。