C-WebGL: Unityを動かす(Vulkan編)
ようやくここまで来た。。
Vulkanで実装した自前のWebGLでUnity WebGLを動かす。
Render-To-Texture 対応
... さらにdepth textureに対応しないと影も出ないんだけどとりあえず最低限必要なRender-To-Textureに対応する。
デフォルトフレームバッファはかなり特殊なので、方々で特別扱いしている。将来的にMRTにも対応したいので可能な限り color0
のようにゼロ番目であることを明示して対応が必要な箇所があとからわかるように。
空のBufferとTextureに対応
glBufferData
や glTexImage2D
にNULLを渡すと領域だけ確保できるのを実装しわすれていたので実装。
DEPTH24_STENCIL8
Renderbufferでとりあえずエラーにしない
本来はこれをエラーにして別のフォーマットをUnity側に提案させる必要がある。ちょっとデバッグ面倒なのでとりあえず通す。まだDepth textureをサポートしてないから違いは見えないし。
この定数自体はWebGL2のもので、Unityがどこから拾っているのかは謎。たぶん真面目に調べればわかるんだろうけど。
Render-to-Textureの実装漏れに対処
vkCmdBeginRenderPass
の積み忘れ:
glClear
の実装漏れ:
WASMから渡されるシェーダが化けている問題
シェーダのコンパイルに失敗して真っ黒になる。
printf("Src:[%s]\n", src);
printf("src.: %c\n", src[len - 2]);
printf("src0: %c\n", src[len - 1]);
printf("src1: %c\n", src[len]);
こんな感じにprintfすると、 src[len]
が実際の末尾になっていることがわかる。
Src:[#version 100
uniform vec4 unity_LightShadowBias;
uniform vec4 hlslcc_mtx4x4unity_ObjectToWorld[4];
uniform vec4 hlslcc_mtx4x4unity_MatrixVP[4];
attribute highp vec4 in_POSITION0;
vec4 u_xlat0;
vec4 u_xlat1;
float u_xlat4;
void main()
{
u_xlat0 = in_POSITION0.yyyy * hlslcc_mtx4x4unity_ObjectToWorld[1];
u_xlat0 = hlslcc_mtx4x4unity_ObjectToWorld[0] * in_POSITION0.xxxx + u_xlat0;
u_xlat0 = hlslcc_mtx4x4unity_ObjectToWorld[2] * in_POSITION0.zzzz + u_xlat0;
u_xlat0 = u_xlat0 + hlslcc_mtx4x4unity_ObjectToWorld[3];
u_xlat1 = u_xlat0.yyyy * hlslcc_mtx4x4unity_MatrixVP[1];
u_xlat1 = hlslcc_mtx4x4unity_MatrixVP[0] * u_xlat0.xxxx + u_xlat1;
u_xlat1 = hlslcc_mtx4x4unity_MatrixVP[2] * u_xlat0.zzzz + u_xlat1;
u_xlat0 = hlslcc_mtx4x4unity_MatrixVP[3] * u_xlat0.wwww + u_xlat1;
u_xlat1.x = unity_LightShadowBias.x / u_xlat0.w;
u_xlat1.x = clamp(u_xlat1.x, 0.0, 1.0);
u_xlat4 = u_xlat0.z + u_xlat1.x;
u_xlat1.x = max((-u_xlat0.w), u_xlat4);
gl_Position.xyw = u_xlat0.xyw;
u_xlat0.x = (-u_xlat4) + u_xlat1.x;
gl_Position.z = unity_LightShadowBias.y * u_xlat0.x + u_xlat4;
return;
}h6O]
src.: ;
src0:
src1: }
典型的なoff-by-oneバグ。昔はバッファに常にnull文字を入れていたので -1
が必要だった。もう要らないので消す。
Uniform内に配列書けない問題
変換したSPIR-Vにパッチした内容が正しくないようだ。
ERROR: 119: The result pointer storage class and base pointer storage class in OpAccessChain do not match.
%22 = OpAccessChain %_ptr_UniformConstant_v4float %hlslcc_mtx4x4unity_ObjectToWorld %int_1
ええと、ココ https://gist.github.com/okuoku/d15584e2fc6556d0437255624a29e685#file-after-spv-L123 か。。
パッチ後:
15: 14(int) Constant 4
16: TypeArray 7(fvec4) 15
128: TypePointer Private 16 // ★ Uniform → Privateにパッチしている
18(hlslcc_mtx4x4unity_ObjectToWorld): 128(ptr) Variable Private
6: TypeFloat 32
7: TypeVector 6(float) 4
19: TypeInt 32 1
20: 19(int) Constant 1
21: TypePointer UniformConstant 7(fvec4) // ★ こっちをパッチしてない
22: 21(ptr) AccessChain 18(hlslcc_mtx4x4unity_ObjectToWorld) 20
パッチ前は: https://gist.github.com/okuoku/d15584e2fc6556d0437255624a29e685#file-before-spv-L59
15: 14(int) Constant 4
16: TypeArray 7(fvec4) 15
17: TypePointer UniformConstant 16
18(hlslcc_mtx4x4unity_ObjectToWorld): 17(ptr) Variable UniformConstant
6: TypeFloat 32
7: TypeVector 6(float) 4
19: TypeInt 32 1
20: 19(int) Constant 1
21: TypePointer UniformConstant 7(fvec4)
22: 21(ptr) AccessChain 18(hlslcc_mtx4x4unity_ObjectToWorld) 20
まぁぶっちゃけいきなり要求されるとは思ってなかったので未実装なんですけどね。。
実装は簡単。
ピクセルシェーダに消費されない出力があると死ぬ
次。頂点シェーダが生成したvaryingで、ピクセルシェーダに消費されない(= 有効なlocationが存在しない)ものがあるとエラーになる。
ERROR: 124: Variable must be decorated with a location
%vs_TEXCOORD7 = OpVariable %_ptr_Output_v4float Output
UnityってそもそもHLSLソースから変換してGLSLを出力してるわけで、なんでこういう不一致がそのまま出てくるんだろう。。
... とりあえずPrivateにでもしておく。。?適当にlocation振ってもたぶんエラーだし。。
OpEntryPoint
も一緒にパッチしないといけないのが面倒だった(KONAMI)
ここまでで、最初の drawElements
に到達した。 ...そういや drawArrays
も実装してなかったな。。
頂点アトリビュートが配列でないケースに対応
これは前調べた奴だな。
シェーダのトランスレータ(SHXM)では、ここで指定するstride 0のバッファを用意できるように情報収集するようにする。
で、描画前にステートを見て必要に応じてGPU側に転送すればOK。
このためのバッファは専用品を用意しようかとも思ったけど、uniform bufferの末尾を拡張してそこに置くことにした。
デプスバッファなしでrender-to-textureした場合の挙動
Depthバッファって確保する必要無いの。。?
とりあえず初期化漏れとdepthアタッチメントなしのフレームバッファが正常に生成されていなかった問題を修正。
ここまででまっくら描画。たぶんRender-to-Textureが正常にできてないのかな。
Usage bitの立て忘れ
バリデーションを有効にすると vkCmdBeginRenderPass
でクラッシュするので、その辺のバリデーションエラーを修正していく。
VUID-VkFramebufferCreateInfo-pAttachments-00877(ERROR / SPEC): msgNum: 1622982839 - Validation Error: [ VUID-VkFramebufferCreateInfo-pAttachments-00877 ] Object 0: handle = 0x2c5ca53f0e8, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x60bcc0b7 | vkCreateFramebuffer: Framebuffer Attachment (0) conflicts with the image's IMAGE_USAGE flags (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT). The Vulkan spec states: If flags does not include VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT, each element of pAttachments that is used as a color attachment or resolve attachment by renderPass must have been created with a usage value including VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT (https://vulkan.lunarg.com/doc/view/1.2.182.0/windows/1.2-extensions/vkspec.html#VUID-VkFramebufferCreateInfo-pAttachments-00877)
OpenGLの場合全てのテクスチャはrender targetになれるから、事実上全部のテクスチャに VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
が必要。。空のテクスチャが実際に使われるまでテクスチャの生成をdeferするとかしないと最適なフラグは与えられない。
あと、ClearがTransferで実装されているので、 TRANSFER_DST
も同時に必要になる。 ...今は実装してないけど glCopyTexImage2D
とかでrender target → textureの転送をするために TRANSFER_SRC
も必要になるな。。
VUID-vkCmdBindVertexBuffers-pBuffers-00627(ERROR / SPEC): msgNum: -1696391559 - Validation Error: [ VUID-vkCmdBindVertexBuffers-pBuffers-00627 ] Object 0: handle = 0xe8126c0000000505, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0x9ae31e79 | Invalid usage flag for VkBuffer 0xe8126c0000000505[] used by vkCmdBindVertexBuffers(). In this case, VkBuffer should have VK_BUFFER_USAGE_VERTEX_BUFFER_BIT set during creation. The Vulkan spec states: All elements of pBuffers must have been created with the VK_BUFFER_USAGE_VERTEX_BUFFER_BIT flag (https://vulkan.lunarg.com/doc/view/1.2.182.0/windows/1.2-extensions/vkspec.html#VUID-vkCmdBindVertexBuffers-pBuffers-00627)
これはUniform bufferを頂点シェーダにアトリビュートを供給する目的でも使っていることの弊害。
頂点シェーダが正常に動作していない
座標の出力がゼロや NaN になっている。
入力は正しそうなので、Uniformが正常に渡せていないと見られる。
struct cwgl_ubo_s
{
vec4 hlslcc_mtx4x4unity_ObjectToWorld[4];
vec4 hlslcc_mtx4x4unity_MatrixVP[4];
vec4 hlslcc_mtx4x4unity_MatrixV[4];
vec4 hlslcc_mtx4x4unity_WorldToObject[4];
};
トランスレータが生成したUBOのレイアウトはこんな感じで、バッファの内容は:
hlslcc_mtx4x4unity_MatrixVP
以降がゼロ値になってるな。。
オフセットの数え方がシェーダトランスレータとGLES2ステートトラッカーで違ったのを修正したら、一応描画処理は上手く動くようになった。(cubemapとmipmapを実装していないのでテクスチャが貼れていない -- Mipmapの下端である 1x1 テクスチャで描画されてしまっている)