Open3

C-WebGL: SPIR-Vのパース

okuokuokuoku

シェーダーコンパイラを制作していく。といっても、GLSL言語フロントエンドとしては既にglslangがあり、最適化のバックエンドとしてSPIR-Toolsがあるので制作すべきものはそこまで多くない。

システムとしては:

(フロントエンド:glslang) → パース(リンクおよびエクスポート対象の変数の列挙) → リンク → Uniform構造体生成 → 命令パッチ → (バックエンド:SPIR-ToolsとGPUドライバ)

のような流れとなる。

SPIR-V

Vulkan(や、OpenCL、OpenGL)のGPUコードは一旦SPIR-V中間表現にコンパイルされる。実際のGPUのISAへのコンパイルはGPUドライバが行うことになる。

SPIR-Vは最初の5ワードが固定フィールドで、以降は可変長の命令を単に連結したものとなっている。

可変長といっても、命令の先頭16bitsが命令ワード数として予約されているため、命令フォーマットの知識はパースには一切不要となっている。つまり、パーサは興味のある命令の仕様だけを認識すれば十分なように配慮されている。

また、命令ストリームにはポインタが埋めこまれることが無いため、命令の境界さえ知っていれば自由に命令を加除できる。ポインタの代わりに命令は id 番号によって入出力を指定する。SPIR-VはSSA形式のIRであるため、全ての入出力にはユニークな id が振られることになる。

okuokuokuoku

インデックスの作成

可能な限り手抜きしたいので、バイナリ内で行われる各種定義をO(1)で引けるように事前にテーブルを構築しておくことにした。メモリがもったいないね。。

https://github.com/okuoku/shxm-proto/commit/dbfe7e7e5da68bc0a8b0add6555368058a985396#diff-bc4a93de52c8916b67351c38335034141982942e572427a612cf5221712c2d80R30

サポートすべき命令(Op)

次に GLSL → SPIR-Vの規格書 を眺めて、パースすべきOpを決める。この辺かな:

定数(OpConstant)とか必要なのかよという感じだが、 OpTypeArray の仕様には lengthidで定義されている = OpConstant で定数を id にアサインしておく必要がある というわけでConstantも収集する必要がある。。

okuokuokuoku

収集したいデータ

簡単な例で見てみる:

(シェーダは https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html のサンプルから取った)

main 関数

例えば、フラグメントシェーダのmainは逆アセンブルでは以下のような命令列で定義されている。(これらの命令を全て正常にパースできる必要がある)

(glslangの逆アセンブルではOpが省略されていることに注意)

OpEntryPoint によって、 ID 4 がエントリポイント(main)であることがわかる。 917 はシェーダとしての入出力の定義で、 main関数の入出力ではない 。main関数の入出力は TypeFunction (3) → TypeVoid (2) でvoid型の関数であることがわかる。

                              EntryPoint Fragment 4  "main" 9 17

                              Name 4  "main"
                              Name 9  "gl_FragColor"

                              Name 17  "v_texcoord"
                              Decorate 9(gl_FragColor) RelaxedPrecision
                              Decorate 17(v_texcoord) RelaxedPrecision

               2:             TypeVoid
               3:             TypeFunction 2

         4(main):           2 Function None 3
               5:             Label
              14:          11 Load 13(u_texture) // ★ main最初の命令

Uniform(通常の変数)

頂点シェーダ

uniform mat4 u_matrix;

は、逆アセンブルでは、

                              Name 12  "u_matrix"
               6:             TypeFloat 32
               7:             TypeVector 6(float) 4
              10:             TypeMatrix 7(fvec4) 4
              11:             TypePointer UniformConstant 10
    12(u_matrix):     11(ptr) Variable UniformConstant

のように、 OpVariableOpTypePointerOpTypeMatrixOpTypeVectorOpTypeFloat のような連鎖で定義されている。

Uniform(サンプラ/テクスチャ)

フラグメントシェーダ

uniform sampler2D u_texture;

は、逆アセンブルでは、

                              Name 13  "u_texture"
                              Decorate 13(u_texture) RelaxedPrecision
                              Decorate 13(u_texture) DescriptorSet 0
                              Decorate 13(u_texture) Binding 0
               6:             TypeFloat 32
              10:             TypeImage 6(float) 2D sampled format:Unknown
              11:             TypeSampledImage 10
              12:             TypePointer UniformConstant 11
   13(u_texture):     12(ptr) Variable UniformConstant

のように OpTypeSampledImageOpTypeImage の組合せで表現される。WebGL1ではこちらの形式でしかテクスチャを与えられないが、 Vulkanも一応この形式をCombined形式としてサポートしている

attribute

頂点シェーダ

attribute vec2 a_texcoord;

は、逆アセンブルでは、

                              Name 22  "a_texcoord"
               6:             TypeFloat 32
              18:             TypeVector 6(float) 2
              21:             TypePointer Input 18(fvec2)
  22(a_texcoord):     21(ptr) Variable Input

のように、 Input のstorage classを持つ OpVariable で表現される。

varying

varyingは頂点シェーダとフラグメントシェーダの両方で宣言される。

varying vec2 v_texcoord;

頂点シェーダ の逆アセンブルでは、

                              Name 20  "v_texcoord"
               6:             TypeFloat 32
              18:             TypeVector 6(float) 2
              19:             TypePointer Output 18(fvec2)
  20(v_texcoord):     19(ptr) Variable Output

のように、 Output のstorage classを持つ OpVariable で表現され、 フラグメントシェーダ では、

                              Name 17  "v_texcoord"
                              Decorate 17(v_texcoord) RelaxedPrecision
               6:             TypeFloat 32
              15:             TypeVector 6(float) 2
              16:             TypePointer Input 15(fvec2)
  17(v_texcoord):     16(ptr) Variable Input

InputOpVariable となる。フラグメントシェーダはattributeを持たないため、 Input であれば varying 由来とわかる。

gl_Position 等の組込み変数

頂点シェーダの出力は varying の他に gl_Position のような組込み変数が該当する。これはSPIR-Vでは、

                              Name 9  "gl_Position"
                              Decorate 9(gl_Position) BuiltIn Position
               6:             TypeFloat 32
               7:             TypeVector 6(float) 4
               8:             TypePointer Output 7(fvec4)
  9(gl_Position):      8(ptr) Variable Output

のように、 BuiltIn を持つ OpDecorate で表現される。