Skip to content

Latest commit

 

History

History
325 lines (241 loc) · 14.3 KB

hlsl.adoc

File metadata and controls

325 lines (241 loc) · 14.3 KB

Vulkan における HLSL

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 専用の機能をサポートしています。

what_is_spriv_dxc.png

教育リソース

Microsoft Learnには、HLSLを初めて使う人のために、HLSLに関する素晴らしい情報が掲載されています。また、DirectX-Specs のドキュメントも素晴らしく、最近のシェーダー機能と HLSL のシェーダーモデルに関する貴重な情報が掲載されています。

アプリケーションから見た場合

アプリケーションから見ると、HLSL の使用は GLSL の使用と全く同じです。アプリケーションは常に SPIR-V 形式のシェーダを使うため、唯一の違いは、希望のシェーディング言語から SPIR-V シェーダを生成するツールだけです。

HLSL から SPIR-V への機能マッピングマニュアル

SPIR-V を経由して Vulkan で HLSL を使用するための良い出発点は、HLSL から SPIR-V への機能マッピングマニュアルです。セマンティクス、シンタックス、サポートされる機能や拡張機能など、詳細な情報が記載されており、必読の書です。また、用語の対応表には、Vulkan と DirectX で使用される概念や用語の翻訳表があります。

Vulkan HLSL 名前空間

HLSL を Vulkan に対応させるため、Vulkan 固有の機能に対するインタフェースを提供する暗黙の名前空間が導入されています。

構文の比較

通常のプログラミング言語と同様に、HLSL と GLSL は構文に違いがあります。GLSLが(C言語のように)手続き型であるのに対して、HLSLは(C++のように)オブジェクト指向型です。

ここでは、両言語で書かれた同じシェーダを、前述した名前空間による明示的な位置指定などを含めて、どのように異なるかを簡単に比較します。

GLSL

#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);
}

HLSL

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 へのビルトインマッピングのリストはこちらです。

DirectXShaderCompiler (DXC)

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);

Vulkan シェーダステージから HLSL ターゲットシェーダプロファイルへのマッピング

DXC で HLSL をコンパイルする場合、ターゲットシェーダプロファイルを選択する必要があります。プロファイルの名前は、シェーダタイプと目的のシェーダモデルで構成されます。

Vulkan シェーダステージ HLSL ターゲットシェーダプロファイル 備考

VK_SHADER_STAGE_VERTEX_BIT

vs

VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT

hs

HLSL におけるハルシェーダ

VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT

ds

HLSL におけるドメインシェーダ

VK_SHADER_STAGE_GEOMETRY_BIT

gs

VK_SHADER_STAGE_FRAGMENT_BIT

ps

HLSL におけるピクセルシェーダ

VK_SHADER_STAGE_COMPUTE_BIT

cs

VK_SHADER_STAGE_RAYGEN_BIT_KHR, VK_SHADER_STAGE_ANY_HIT_BIT_KHR, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, VK_SHADER_STAGE_MISS_BIT_KHR, VK_SHADER_STAGE_INTERSECTION_BIT_KHR, VK_SHADER_STAGE_CALLABLE_BIT_KHR

lib

レイトレーシング関連のシェーダはすべて lib シェーダターゲットプロファイルを使ってビルドされ、少なくともシェーダモデル 6.3 (例: lib_6_3) を使う必要があります。

VK_SHADER_STAGE_TASK_BIT

as

HLSL における Amplification シェーダ。少なくともシェーダモデル 6.5 (例: as_6_5) を使う必要があります。

VK_SHADER_STAGE_MESH_BIT

ms

少なくともシェーダモデル 6.5 (例: as_6_5) を使う必要があります。

たとえば、シェーダモデル6.6の機能をターゲットとするコンピュートシェーダをコンパイルする場合、ターゲットシェーダプロファイルは`cs_6_6` となります。レイトレーシングの any hit シェーダの場合は、lib_6_3 となります。

シェーダモデル対応範囲

DirectX と HLSL は、サポートされる機能セットを記述するために、固定されたシェーダモデル の概念を使用しています。これは、Vulkan と SPIR-V の、シェーダに機能を追加する拡張ベースの柔軟な方法とは異なります。以下の表は、HLSL シェーダモデルに対する Vulkan の対応範囲を一覧にしたものですが、完全性を保証するものではありません。

Table 1. シェーダモデル
シェーダモデル 対応 備考

Shader Model 5.1 以下

Vulkan に相当する機能がないものは除く

Shader Model 6.0

Wave intrinsics、64-bit 整数型

Shader Model 6.1

SV_ViewID、SV_Barycentrics

Shader Model 6.2

16-bit 型、Denorm モード

Shader Model 6.3

ハードウェアアクセラレーテッドレイトレーシング

Shader Model 6.4

シェーダ整数内積、SV_ShadingRate

Shader Model 6.5

❌ (部分的に)

DXR1.1 (KHR ray tracing)、Mesh/Amplification シェーダ、追加の Wave intrinsics

Shader Model 6.6

❌ (部分的に)

VK_NV_compute_shader_derivatives、VK_KHR_shader_atomic_int64