Open3

C-WebGL: SPIR-V上で直接ESSL1をVulkan用に変換できるか問題

okuokuokuoku

... というわけで、ESSL → GLSLのトランスパイラを書くよりSPIR-V上で直接変換してしまう方が100倍簡単なので挑戦してみる。

簡単な例

ESSL1シェーダ(変換元):

uniform bool paint;

void main() {
  if(paint){
    gl_FragColor = vec4(1.0,1.0,1.0,1.0);
  }else{
    gl_FragColor = vec4(0.0,0.0,0.0,1.0);
  }
}

SPIR-V Crossでのラウンドトリップ(ESSL1を出力する):

$ /cygdrive/c/VulkanSDK/1.2.162.0/Bin/spirv-cross.exe out.frag.spv
#version 100
precision mediump float;
precision highp int;

uniform bool paint;

void main()
{
    if (paint)
    {
        gl_FragData[0] = vec4(1.0);
    }
    else
    {
        gl_FragData[0] = vec4(0.0, 0.0, 0.0, 1.0);
    }
}

SPIR-V CrossでのGLSL450変換:

#version 450

uniform bool paint;

out vec4 _RESERVED_IDENTIFIER_FIXUP_gl_FragColor;

void main()
{
    if (paint)
    {
        _RESERVED_IDENTIFIER_FIXUP_gl_FragColor = vec4(1.0);
    }
    else
    {
        _RESERVED_IDENTIFIER_FIXUP_gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    }
}

out への貼り替えやリネームは勝手にやってくれることがわかる。この状況では bool 型が外に見えているのでバリデーションに通らない。

$ /cygdrive/c/VulkanSDK/1.2.162.0/Bin/spirv-val.exe out.frag.spv
error: line 15: If OpTypeBool is stored in conjunction with OpVariable, it can only be used with non-externally visible shader Storage Classes: Workgroup, CrossWorkgroup, Private, and Function
  %paint = OpVariable %_ptr_UniformConstant_bool UniformConstant
okuokuokuoku

手動で変換する

#version 450

layout(binding = 0) uniform indataStruct {
    bool paint; // ★ Uniform に入れる
}indata ;

bool paint; // ★ 元のUniformはグローバル変数にする
layout(location = 0) out vec4 xgl_FragColor;

void main() {
  paint = indata.paint; // ★ 先頭でグローバル変数に代入する
  if(paint){
    xgl_FragColor = vec4(1.0,1.0,1.0,1.0);
  }else{
    xgl_FragColor = vec4(0.0,0.0,0.0,1.0);
  }
}

これを spirv-opt -Os で最適化したあとに、再度GLSLに変換すると、

#version 450

layout(binding = 0, std140) uniform indataStruct
{
    uint paint; // ★ 何か勝手にuintになっている
} indata;

layout(location = 0) out vec4 xgl_FragColor;

void main()
{
    if (indata.paint != 0u) // ★ 代入文が消えている
    {
        xgl_FragColor = vec4(1.0);
    }
    else
    {
        xgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    }
}

のように、求めているコードを得られた。後は手変換前後で逆アセンブリを比較してやれば良さそう。

okuokuokuoku

逆アセンブリの比較

https://gist.github.com/okuoku/3f43ad29792122c4adeff1bc6169a703/revisions

... 変数の宣言だけでも割と冗長だな。。

やらないといけないのは、

  1. 対象の変数を OpVariable 命令を走査して探す。 UniformConstant で、かつ samplerやテクスチャでないものが対象
  2. グローバル変数に変換するための OpTypePointer 命令を生成(UniformConstantPrivate)
  3. 対象の変数の OpVariable を 2. で生成した型にパッチ
  4. UBOになる構造体を OpTypeStruct で生成
  5. 代入文を main の先頭に生成

SPIR-Vは32bitのワード単位で自由に命令列の挿入や削除が可能なため、GLSLフロントエンドのASTを直接編集するよりは簡単に変換できる...と、思う。