Open12

C-WebGL: Unityを動かす(Vulkan編)

okuokuokuoku

ようやくここまで来た。。

Vulkanで実装した自前のWebGLでUnity WebGLを動かす。

okuokuokuoku

Render-To-Texture 対応

... さらにdepth textureに対応しないと影も出ないんだけどとりあえず最低限必要なRender-To-Textureに対応する。

https://github.com/okuoku/yuniframe/commit/37212ebf70eb25984b20bbc6e8e19086a71019ed

デフォルトフレームバッファはかなり特殊なので、方々で特別扱いしている。将来的にMRTにも対応したいので可能な限り color0 のようにゼロ番目であることを明示して対応が必要な箇所があとからわかるように。

okuokuokuoku

DEPTH24_STENCIL8 Renderbufferでとりあえずエラーにしない

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

本来はこれをエラーにして別のフォーマットをUnity側に提案させる必要がある。ちょっとデバッグ面倒なのでとりあえず通す。まだDepth textureをサポートしてないから違いは見えないし。

この定数自体はWebGL2のもので、Unityがどこから拾っているのかは謎。たぶん真面目に調べればわかるんだろうけど。

okuokuokuoku

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 が必要だった。もう要らないので消す。

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

okuokuokuoku

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

まぁぶっちゃけいきなり要求されるとは思ってなかったので未実装なんですけどね。。

https://github.com/okuoku/yuniframe/commit/1c96549d889350a9acf3887cc2019a8d635232e8

実装は簡単。

okuokuokuoku

ピクセルシェーダに消費されない出力があると死ぬ

次。頂点シェーダが生成したvaryingで、ピクセルシェーダに消費されない(= 有効なlocationが存在しない)ものがあるとエラーになる。

ERROR: 124: Variable must be decorated with a location
  %vs_TEXCOORD7 = OpVariable %_ptr_Output_v4float Output

UnityってそもそもHLSLソースから変換してGLSLを出力してるわけで、なんでこういう不一致がそのまま出てくるんだろう。。

... とりあえずPrivateにでもしておく。。?適当にlocation振ってもたぶんエラーだし。。

https://github.com/okuoku/yuniframe/commit/49ba21db1bf957056add95ccd3f553394c8509a8

OpEntryPoint も一緒にパッチしないといけないのが面倒だった(KONAMI)

ここまでで、最初の drawElements に到達した。 ...そういや drawArrays も実装してなかったな。。

okuokuokuoku

頂点アトリビュートが配列でないケースに対応

これは前調べた奴だな。

https://zenn.dev/okuoku/scraps/e900dd080e9a1a

シェーダのトランスレータ(SHXM)では、ここで指定するstride 0のバッファを用意できるように情報収集するようにする。

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

で、描画前にステートを見て必要に応じてGPU側に転送すればOK。

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

https://github.com/okuoku/yuniframe/commit/752faa4122769c83fa13801d41906da74522fdb0

このためのバッファは専用品を用意しようかとも思ったけど、uniform bufferの末尾を拡張してそこに置くことにした。

okuokuokuoku

デプスバッファなしでrender-to-textureした場合の挙動

Depthバッファって確保する必要無いの。。?

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

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

とりあえず初期化漏れとdepthアタッチメントなしのフレームバッファが正常に生成されていなかった問題を修正。

ここまででまっくら描画。たぶんRender-to-Textureが正常にできてないのかな。

okuokuokuoku

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)

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

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)

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

これはUniform bufferを頂点シェーダにアトリビュートを供給する目的でも使っていることの弊害。

okuokuokuoku

頂点シェーダが正常に動作していない

座標の出力がゼロや 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 以降がゼロ値になってるな。。

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

オフセットの数え方がシェーダトランスレータとGLES2ステートトラッカーで違ったのを修正したら、一応描画処理は上手く動くようになった。(cubemapとmipmapを実装していないのでテクスチャが貼れていない -- Mipmapの下端である 1x1 テクスチャで描画されてしまっている)