Vulkan は、人間が読めるテキスト形式のシェーダを直接使うのではなく、中間表現として SPIR-V を使用します。これにより、Vulkan SPIR-V 環境をターゲットにできる限り、たとえば GLSL 以外のシェーダ言語を使用することもできます。
そのひとつが、DirectX で採用されている Microsoft 社の High Level Shading Language(HLSL)です。Vulkan 1.2への追加のおかげで、今では GLSL と同様に簡単に使用できる Vulkan 用の第一級シェーディング言語と見なされています。
一部の例外を除き、GLSL で利用可能なすべての Vulkan 機能とシェーダステージは HLSL でも使用でき、ハードウェアアクセラレーテッドレイトレーシングなど最近の Vulkan の追加機能も含まれます。一方、HLSL から SPIR-V は、DirectX では(まだ)利用できない Vulkan 専用の機能をサポートしています。
Microsoft Learnには、HLSLを初めて使う人のために、HLSLに関する素晴らしい情報が掲載されています。また、DirectX-Specs のドキュメントも素晴らしく、最近のシェーダー機能と HLSL のシェーダーモデルに関する貴重な情報が掲載されています。
アプリケーションから見ると、HLSL の使用は GLSL の使用と全く同じです。アプリケーションは常に SPIR-V 形式のシェーダを使うため、唯一の違いは、希望のシェーディング言語から SPIR-V シェーダを生成するツールだけです。
SPIR-V を経由して Vulkan で HLSL を使用するための良い出発点は、HLSL から SPIR-V への機能マッピングマニュアルです。セマンティクス、シンタックス、サポートされる機能や拡張機能など、詳細な情報が記載されており、必読の書です。また、用語の対応表には、Vulkan と DirectX で使用される概念や用語の翻訳表があります。
HLSL を Vulkan に対応させるため、Vulkan 固有の機能に対するインタフェースを提供する暗黙の名前空間が導入されています。
通常のプログラミング言語と同様に、HLSL と GLSL は構文に違いがあります。GLSLが(C言語のように)手続き型であるのに対して、HLSLは(C++のように)オブジェクト指向型です。
ここでは、両言語で書かれた同じシェーダを、前述した名前空間による明示的な位置指定などを含めて、どのように異なるかを簡単に比較します。
#version 450
layout (location = 0) in vec3 inPosition;
layout (location = 1) in vec3 inColor;
layout (binding = 0) uniform UBO
{
mat4 projectionMatrix;
mat4 modelMatrix;
mat4 viewMatrix;
} ubo;
layout (location = 0) out vec3 outColor;
void main()
{
outColor = inColor * float(gl_VertexIndex);
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPosition.xyz, 1.0);
}
struct VSInput
{
[[vk::location(0)]] float3 Position : POSITION0;
[[vk::location(1)]] float3 Color : COLOR0;
};
struct UBO
{
float4x4 projectionMatrix;
float4x4 modelMatrix;
float4x4 viewMatrix;
};
cbuffer ubo : register(b0, space0) { UBO ubo; }
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 Color : COLOR0;
};
VSOutput main(VSInput input, uint VertexIndex : SV_VertexID)
{
VSOutput output = (VSOutput)0;
output.Color = input.Color * float(VertexIndex);
output.Position = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Position.xyz, 1.0))));
return output;
}
構文の違い以外では、ビルトインの名前が異なります。たとえば、gl_vertex
は HLSL では VertexIndex
になります。GLSL から HLSL へのビルトインマッピングのリストはこちらです。
GLSL から SPIR-Vと同様に、HLSL を Vulkan で使用するためには、シェーダコンパイラが必要です。glslang が GLSL から SPIR-V のリファレンスコンパイラであるのに対し、DirectXShaderCompiler(DXC)は HLSL から SPIR-V のリファレンスコンパイラとなります。オープンソースの貢献により、DXCのSPIR-Vバックエンドは現在、公式リリースビルドでサポートされ有効になっており、そのまま使用することが可能です。glslang のような他のシェーダコンパイルツールも HLSL をサポートしていますが、DXC は最も完全で最新のサポートを持っており、HLSL から SPIR-V を生成する方法として推奨されています。
LunarG Vulkan SDK には、コンパイル済みの DXC バイナリ、ライブラリ、ヘッダが含まれており、すぐに使うことができます。最新のリリースをお探しの場合は、DXC の公式リポジトリをご確認ください。
コンパイル済みの DXC バイナリを使ってオフラインでシェーダをコンパイルするのは、glslang でコンパイルするのと似ています。
dxc.exe -spirv -T vs_6_0 -E main .\triangle.vert -Fo .\triangle.vert.spv
T
はシェーダをコンパイルするプロファイルを選択します (vs_6_0
= バーテックスシェーダモデル6、ps_6_0
= ピクセル/フラグメントシェーダモデル6など).
E
はシェーダのメインエントリポイントを選択します。
拡張機能は、機能の使用状況に応じて暗黙的に有効化されますが、明示的に指定することも可能です。
dxc.exe -spirv -T vs_6_1 -E main .\input.vert -Fo .\output.vert.spv -fspv-extension=SPV_EXT_descriptor_indexing
その結果、GLSL から生成した SPIR-V と同じように、直接読み込めるようになります。
DXC は、DirectX Compiler API を使用して、Vulkan アプリケーションに統合することもできます。これにより、シェーダを実行時にコンパイルすることができます。これを行うには、dxcapi.h
ヘッダをインクルードし、dxcompiler
ライブラリに対してリンクする必要があります。最も簡単な方法は、動的ライブラリを使用し、アプリケーションと一緒に配布することです(例:Windows では dxcompiler.dll
)。
HLSL を実行時にSPIR-Vにコンパイルするのは、非常に簡単です。
#include "include/dxc/dxcapi.h"
...
HRESULT hres;
// DXC ライブラリの初期化
CComPtr<IDxcLibrary> library;
hres = DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&library));
if (FAILED(hres)) {
throw std::runtime_error("Could not init DXC Library");
}
// DXC コンパイラの初期化
CComPtr<IDxcCompiler> compiler;
hres = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler));
if (FAILED(hres)) {
throw std::runtime_error("Could not init DXC Compiler");
}
// HLSL シェーダをディスクから読み込む
uint32_t codePage = CP_UTF8;
CComPtr<IDxcBlobEncoding> sourceBlob;
hres = library->CreateBlobFromFile(filename.c_str(), &codePage, &sourceBlob);
if (FAILED(hres)) {
throw std::runtime_error("Could not load shader file");
}
// シェーダコンパイラに渡す引数の設定
// コンパイラに SPIR-V を出力するように指示する
std::vector<LPCWSTR> arguments;
arguments.push_back(L"-spirv");
// シェーダファイルの拡張子をもとにターゲットプロファイルを選択する
LPCWSTR targetProfile{};
size_t idx = filename.rfind('.');
if (idx != std::string::npos) {
std::wstring extension = filename.substr(idx + 1);
if (extension == L"vert") {
targetProfile = L"vs_6_1";
}
if (extension == L"frag") {
targetProfile = L"ps_6_1";
}
// 他のファイルタイプのマッピング (cs_x_y, lib_x_y, 等)
}
// シェーダをコンパイルする
CComPtr<IDxcOperationResult> resultOp;
hres = compiler->Compile(
sourceBlob,
nullptr,
L"main",
targetProfile,
arguments.data(),
(uint32_t)arguments.size(),
nullptr,
0,
nullptr,
&resultOp);
if (SUCCEEDED(hres)) {
resultOp->GetStatus(&hres);
}
// コンパイルに失敗した場合はエラーを出力
if (FAILED(hres) && (resultOp)) {
CComPtr<IDxcBlobEncoding> errorBlob;
hres = resultOp->GetErrorBuffer(&errorBlob);
if (SUCCEEDED(hres) && errorBlob) {
std::cerr << "Shader compilation failed :\n\n" << (const char*)errorBlob->GetBufferPointer();
throw std::runtime_error("Compilation failed");
}
}
// コンパイル結果の取得
CComPtr<IDxcBlob> code;
resultOp->GetResult(&code);
// コンパイル結果からVulkanシェーダモジュールを作成する
VkShaderModuleCreateInfo shaderModuleCI{};
shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shaderModuleCI.codeSize = code->GetBufferSize();
shaderModuleCI.pCode = (uint32_t*)code->GetBufferPointer();
VkShaderModule shaderModule;
vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule);
DXC で HLSL をコンパイルする場合、ターゲットシェーダプロファイルを選択する必要があります。プロファイルの名前は、シェーダタイプと目的のシェーダモデルで構成されます。
Vulkan シェーダステージ | HLSL ターゲットシェーダプロファイル | 備考 |
---|---|---|
|
|
|
|
|
HLSL におけるハルシェーダ |
|
|
HLSL におけるドメインシェーダ |
|
|
|
|
|
HLSL におけるピクセルシェーダ |
|
|
|
|
|
レイトレーシング関連のシェーダはすべて |
|
|
HLSL における Amplification シェーダ。少なくともシェーダモデル 6.5 (例: |
|
|
少なくともシェーダモデル 6.5 (例: |
たとえば、シェーダモデル6.6の機能をターゲットとするコンピュートシェーダをコンパイルする場合、ターゲットシェーダプロファイルは`cs_6_6` となります。レイトレーシングの any hit シェーダの場合は、lib_6_3
となります。
DirectX と HLSL は、サポートされる機能セットを記述するために、固定されたシェーダモデル の概念を使用しています。これは、Vulkan と SPIR-V の、シェーダに機能を追加する拡張ベースの柔軟な方法とは異なります。以下の表は、HLSL シェーダモデルに対する Vulkan の対応範囲を一覧にしたものですが、完全性を保証するものではありません。
シェーダモデル | 対応 | 備考 |
---|---|---|
Shader Model 5.1 以下 |
✔ |
Vulkan に相当する機能がないものは除く |
✔ |
Wave intrinsics、64-bit 整数型 |
|
✔ |
SV_ViewID、SV_Barycentrics |
|
✔ |
16-bit 型、Denorm モード |
|
✔ |
ハードウェアアクセラレーテッドレイトレーシング |
|
✔ |
シェーダ整数内積、SV_ShadingRate |
|
❌ (部分的に) |
DXR1.1 (KHR ray tracing)、Mesh/Amplification シェーダ、追加の Wave intrinsics |
|
❌ (部分的に) |
VK_NV_compute_shader_derivatives、VK_KHR_shader_atomic_int64 |