2016年2月にVulkanの正式仕様とSDKがリリースされ、その後NVIDIAやAMDなど大手GPUベンダー各社からもPC向け正式ドライバーがリリースされ始めているので、そろそろ試してみることにしました。まずはシェーダーのコンパイラーまわりから入ります。ちなみにVulkan仕様とSDKは、最初の正式リリース以降かなり頻繁にリビジョンアップが続けられています。
Vulkan用GLSLコンパイラー
Vulkan SDKにはGLSLをコンパイルしてSPIR-Vを出力することのできるオフラインコンパイラー「glslangValidator」が付属します。
32bit版 (x86) は下記にインストールされます(Windows環境)。
%VK_SDK_PATH%/Bin32/glslangValidator.exe
64bit版 (x64) は下記にインストールされます。
%VK_SDK_PATH%/Bin/glslangValidator.exe
Visual Studio (Visual C++) の[カスタム ビルド ツール]でプロジェクトのビルド時にGLSLソースファイルからSPIR-Vバイナリを生成するとき、たとえば下記のように[コマンド ライン]の項目を設定します。
"$(VK_SDK_PATH)\Bin32\glslangValidator.exe" -V "%(Identity)" -o "$(OutDir)VkShaders\%(Filename).spv"
[出力ファイル]には「$(OutDir)VkShaders\%(Filename).spv」を指定します。[説明]には適当に「Now compiling GLSL source file to SPIR-V...」などを指定します*1。
例として "VkShaders" サブフォルダーを挟んでいますが、このフォルダーはVisual Studioが勝手に生成してくれます。
なお、glslangValidatorはBOM付きUTF-8を扱えないので注意。BOMなしのUTF-8もしくはASCIIを使う必要があります。
#includeディレクティブ
GLSLは標準で #include ディレクティブをサポートしませんが、Google拡張とARB拡張は存在する模様。
#extension GL_GOOGLE_include_directive : enable #extension GL_ARB_shading_language_include: enable
しかしVulkan SDK 1.0.13.0付属のglslangValidatorでGoogle拡張を有効にしても、実際に #include を使えるようにはなりませんでした。ARB拡張に至っては有効にすることすらできない模様。これのどこがリファレンスコンパイラーなのか……
なお、Googleは「glslc」と呼ばれるシェーダーコンパイラーを開発して公開している模様です。SPIR-Vにも対応しているらしい。試してはいませんが、こちらは #include が使えるものと思われます。
#extension GL_GOOGLE_cpp_style_line_directive : enable
のようにして拡張を有効にすると、
#line 1 "test.frag"
のように #line ディレクティブの第2引数にファイル番号の数値ではなく任意文字列を使えるようになるらしいです。この拡張機能はVulkan SDK 1.0.13.0付属のglslangValidatorでも使える模様。
もともと #line はGLSLコンパイル時にエラー/警告メッセージから問題発生個所を特定するなどの目的でプログラマーがファイル番号や行番号を明示的に制御するための機能で、OpenGLではコンパイル対象となる複数のGLSLソース文字列をオンメモリで結合するAPI (glShaderSource) が用意されている関係上、仕方なく存在しているような変な機能なんですが、せっかくオフラインコンパイラーがあるのにVulkan開発でも #line なんぞを明示的に使わないといけない時点でナンセンス極まりないです。
ちなみにDirectXのHLSLには #include が完備されている*2ので、#line を使う必要はありませんが、コンパイルエラー発生時のエラーメッセージは標準C/C++に比べるとやはり分かりづらいです。
なお、HLSLの #line はC/C++同様、第2引数にファイル名(文字列)を指定できるようになっています。さすがですね。
- #include ディレクティブ (DirectX HLSL) | Microsoft Docs (旧DX SDKヘルプ)
- #line ディレクティブ (DirectX HLSL) | Microsoft Docs (旧DX SDKヘルプ)
- include Directive - Win32 apps | Microsoft Docs
- line Directive - Win32 apps | Microsoft Docs
- Filename and line information - cppreference.com (C)
- Filename and line information - cppreference.com (C++)
#versionディレクティブ
glslangValidatorで #version 400 - #version 450 を指定したシェーダーをコンパイルすると、下記のような警告が出ます。
Warning, version 450 is not yet complete; most version-specific features are present, but some are missing.
要するにまだGLSL 4.xすなわちOpenGL 4.xの相当機能を完全に実装しきれていないらしい。#version 330 であれば警告は出ません。しかしこれではOpenGL 4.xアプリケーションを満足に移植できない可能性があります。なんとも情けない……
OpenGL 4.0で標準化されたシェーダーサブルーチン(Direct3D 11でいう動的シェーダーリンク)など、本質的にVulkanの仕様と相反する機能であれば使うことができないのも分かりますが、Vulkanと互換性のあるGLSL機能までも実装しきれていないのはダメすぎかと。こんなていたらくではだれも移植を始めてくれないでしょ……
HLSL互換機能
ちなみにVulkan SDK 1.0.13.0付属のglslangValidatorでは、-D オプションを付けることで、なんとHLSLが使えるようになる模様。確かSDK 1.0.5.0では -D オプションはまだ存在していなかったように思われます。Vulkanが要求している入力はSPIR-V中間表現であり、GLSLはあくまでフロントエンドでしかないため、別にHLSLが使えても不思議ではありませんが、もし本当にHLSLが使えるのであれば、多くの機能面で劣るGLSLは要らない子ですね*3。
たとえば下記のような最小のHLSLピクセルシェーダーは、普通にコンパイルが通ることを確認しました。Direct3D 10以降のシステム値セマンティクスも使えるようなので、おそらくシェーダーモデル4.0相当の機能は使えるものと思われます。Direct3D 11およびシェーダーモデル5.0に関しては未検証ですが、GLSL 4.xのサポートすらできていないことを考えると怪しいです。
float4 main() : SV_Target0 { return float4(0,0,0,0); }
ただしソースファイルの拡張子は、GLSLと同様のルールに従う必要があります(*.vert, *.frag など)。
また、今のところ #include も使えない模様。結局ただの劣化移植です。本家である fxc.exe には及びません。
結論
総論としては、やはりVulkanはSDKやドライバーを含めてまだまだ未成熟な模様です。実績および開発のしやすさでは、Direct3D 11/12とHLSLに軍配が上がるのは火を見るより明らか。特にせっかくのオフラインシェーダーコンパイラーの実装が現時点で微妙すぎるので、OpenGLから移行する気がまったく起こりません。あとVulkan API自体がDirect3D 12以上にローレベルなので、プログラミングの難易度も相当に高いです。
とはいえ、OpenGLでないがしろにされてきたシェーダーのオフラインコンパイルや、マルチスレッド・マルチGPU対応などに真面目に取り組んでいるVulkan APIの姿勢というか設計思想自体は評価できるので、開発環境も含めて今後の発展と進化に期待したいところです。Vulkanを用いて、Direct3D 11やOpenGL 4に近いインターフェイスを持つ上位ラッパーレイヤーを構築してくれれば、Direct3D/OpenGLからの移植も進むんじゃないでしょうか。あとC++バインディングが欲しい……
*1:[説明]の既定値は「Performing Custom Build Tools」となっているはずです。ここで指定した文字列がビルド時に出力ウィンドウに出力されるだけなので、指定は任意です。[コマンド ライン]にてechoコマンドを使ってもよいのですが、[説明]を使ったほうが楽です。
*2:HLSLはGLSLと違って登場当初からベンダー非依存のバイトコードをサポートしており、オフラインコンパイルが基本ですが、D3DCompilerランタイムAPIを使って実行時にコンパイルする場合も、ID3DInclude を利用することで #include が使えます。
*3:コンピュートシェーダーに関しては例外で、エクステンションまで含めると実はGLSLのほうがポテンシャルが高いです。