Open10

C-WebGL: Vulkanのシェーダを気合でデバッグする会

okuokuokuoku

これはマジで解ける気がしない...

あらすじ

描画が変。

RenderDocでバーテックスシェーダ後の出力を見ると、頂点はちゃんと送られている。というわけでピクセルシェーダか同期が悪い。

... が、同期はVulkanのバリデーションで文句言われない程度に修正しているし、今は描画を1度やるたびにバリアを入れているので問題になるとは考えづらい。というわけでピクセルシェーダを変更してデバッグしていく。

okuokuokuoku

テクスチャフェッチを止めてみる

元のピクセルシェーダは:

gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);

これを

gl_FragColor = Frag_Color;

に変更してみるといきなり絵が出た。テクスチャのフェッチ texture2D を抜くと問題が見えなくなるので、テクスチャのUVかデスクリプタがおかしいとわかる。

okuokuokuoku

染色で値を確認する

シェーダからは 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_UVs 要素が 0.5 を越えているピクセルを赤色にできる。

これを 0.0 未満とか 1.0 以上のような条件にしてみて赤くなるピクセルがあれば、異常な値を頂点シェーダから受けとっていそうということになるが、そういう問題は見られなかった。

okuokuokuoku

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には繋がらないようだ。

okuokuokuoku

Intelのグラフィックスドライバのシンボルをあててみる

https://twitter.com/intelgraphics/status/1088956957816434689

なんとグラフィックスドライバのシンボルは 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しか無い。ということはエクスポートしか見られないのか。。

okuokuokuoku

Rust製のVulkan実装で様子を見てみる

Intelの実装のバグかもしれないので、Rustのgfx-rs上の実装である:

https://github.com/gfx-rs/portability

を使ってみる。

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をエミュレーションする。

これバリデーションに引っかからないのか。。

https://github.com/okuoku/yuniframe/commit/e6473e730a2f4320902ea1528afd9960a20d4d1b

TRANSFER_DST は要らないので削除。

okuokuokuoku

vkBindBufferMemory で死ぬ

次。 vkBindBufferMemory で死ぬ。

これはUniform bufferのアロケーションで、サイズを本来のサイズの1024倍である64KiBに拡大するとクラッシュしなくなった(描画はしない -- そもそもVisualStudioのGPUデバッグセッションが開始できない)。

...確かに、 vkGetBufferMemoryRequirements しているコードがどこにも無い!これをやらないとメモリ破壊が発生する可能性があるので、全部にこれをやるコードを挿入する必要がある。。

https://github.com/okuoku/yuniframe/commit/0872a6c6ef69d5baee0b7144444283143b6b5808

これで、 IntelのVulkanでも安定するようになった ...が、現象は全く改善しない。。RenderDocでのキャプチャ時に変な模様が出るようになったのでほぼほぼメモリ同期の問題に見えるが。。

texture2D でメモリアクセスが入るとレンダリングに掛かる時間が微妙に長くなるはずで、それが 何か を追い越してしまっている、つまりフレームが半完成のまま表示されているのではないだろうか。。

EDIT: 何度か起動/終了していたら再発するようになった。。

okuokuokuoku

MoltenVK(Metal) だと正常

https://github.com/okuoku/em2native-proto/commit/ff7b2c2a8b4a106c642143a845d0f561f2e3210c

とりあえず適当にMoltenVKでもビルドできるようにしてみた(macOS以外未対応 -- AppleプラットフォームではMetalを使いたいので真面目にやる予定がない)。... すると一発完動してしまった。。

こういうの一番コメントに困るんだよな。。つまりシェーダコンパイラが出力しているバイナリは正常な可能性が高く、Windowsで動いていないのはintelのVulkanドライバの何か秘孔を突いていると考えるしかない。

怪しいポイントがあるとすれば、XcodeのGPUフレームキャプチャがこの状態では正常に動作しないこと。ANGLE + Metalでは正常にキャプチャできるので、そこに影響するような形で何か間違えている可能性はある。

... あとScissorの座標設定をY-Flipするの忘れてるな。。

okuokuokuoku

nullDescriptorにしてもゴミが残る

VK_EXT_robustness2 を使うと、GPUにnull descriptor(CPUで言うところのNULLポインタ)を渡した時の挙動を固定できる。

... が、なんとこれを使ってテクスチャとしてNULLポインタを渡しても挙動が変わらず、ゴミが描画される。Robustnessの仕様では、NULLポインタをリードするとゼロが読めるとされている:

If the nullDescriptor feature is enabled, the buffer, acceleration structure, imageView, or bufferView can be VK_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;
}
okuokuokuoku

諦めてSwiftShaderで開発を進める

もう諦めてGoogleのソフトウェアVulkan実装であるところのSwiftShaderで開発を進めることにした。絵も出るし。。

SwiftShaderは適当にビルドして環境変数を設定すれば使える。

set VK_ICD_FILENAMES=F:\build\swiftshader\Windows\vk_swiftshader_icd.json

ただし実際のGPU実装と違ってDepthは32bit floatのみのサポートとなるので、Depthのフォーマットを調整する必要がある。

https://github.com/okuoku/yuniframe/commit/0c4cb95cd24ff5e2f9eb6ad53804a69f0c19da78