Open2

C-WebGL: シェーダのHLSLへの変換

okuokuokuoku

VulkanはSPIR-Vと呼ばれる中間言語をGPUドライバに渡して処理を行うが、他のGPU API(WebGPU、Metal、DirectX11)ではソースコードの形でGPUドライバに渡す必要がある。これ用の変換は SPIRV-Cross で行える。

(DirectXは伝統的にDXBC(DirectX11やそれ以前)やDXIL(DirectX12)と呼ばれる中間言語を使用する。しかし、Vulkanとは異なり最近のDirectXではAPIの実行環境にシェーダコンパイラが存在することが保証されているので、ソースレベルの処理で実装すれば十分と言える。)

...そもそもGoogleのOpenGL ES実装であるANGLEのMetalレンダラが既に同様の方針でSPIRV-Cross を使っていて、C-WebGLにも既に組込まれているので、それを直接呼べば目的を達成できる。

https://github.com/okuoku/yuniframe/commit/5464d7052f94d39efc27e3fad851b604dabaa47b

okuokuokuoku

変換されたシェーダ

https://gist.github.com/okuoku/ce336966dc687a0fdd4688dc7b3f3236

GLSLとHLSLは割とセマンティクスが異なり、特にHLSLはnVidiaのCgを元にして作られた歴史的事情から自然なCプログラムからは割と乖離がある。

定数バッファ

layout(set = 0, binding = 0, std140) uniform cwgl_ubo_s
{
    float _Extrude;
    vec4 hlslcc_mtx4x4unity_ObjectToWorld[4];
    vec4 hlslcc_mtx4x4unity_MatrixVP[4];
    vec4 _MainTex_ST;
} cwgl_ubo;
cbuffer cwgl_ubo_s : register(b0)
{
    float cwgl_ubo_Extrude : packoffset(c0);
    float4 cwgl_ubo_hlslcc_mtx4x4unity_ObjectToWorld[4] : packoffset(c1);
    float4 cwgl_ubo_hlslcc_mtx4x4unity_MatrixVP[4] : packoffset(c5);
    float4 cwgl_ubo_MainTex_ST : packoffset(c9);
};

HLSLでは、基本的に各レジスタはfloat4のサイズがあると仮定し、レジスタ単位で値をストアしていく。(GLSLも、標準ではfloat4単位でpackingを行う点は共通している。)

例えば、 packoffset(c5) は constant の 5番目から値をストアすることを示す。GLSLの例では密にpackできているので、offsetは省略されている。

入出力

layout(location = 0) in vec3 in_NORMAL0;
layout(location = 1) in vec4 in_POSITION0;
layout(location = 0) out vec2 vs_TEXCOORD0;
layout(location = 2) in vec2 in_TEXCOORD0;
struct SPIRV_Cross_Input
{
    float3 in_NORMAL0 : TEXCOORD0;
    float4 in_POSITION0 : TEXCOORD1;
    float2 in_TEXCOORD0 : TEXCOORD2;
};

GLSLでは、シェーダ入出力はグローバル変数によって行う。HLSLでは structmain 関数で入出力することで行い、その structセマンティクス で修飾することで頂点ストリームやテクスチャのようなopaqueなuniformを表現する。

この差を埋めるために、SPIRV-Crossは自動的に main 関数をwrapするようになっている。

... この例では、GLSL側の変数が in_NORMAL0 だの in_TEXCOORD0 だの命名されているので非常にややこしい。。これはUnityが供給してくるGLSLソースがそうなっているため。。UnityはCgというかHLSLというか何というかなシェーダ言語を、各処理系向けに変換して使用している。