Vulkan 中的 HLSL
Vulkan 不直接使用人类可读的文本格式的着色器,而是使用 SPIR-V 作为中间表示。这为使用除 GLSL 之外的着色器语言打开了选项,只要它们可以针对 Vulkan SPIR-V 环境即可。
其中一种语言是 Microsoft 的高级着色语言 (HLSL),DirectX 使用了它。得益于 最近添加到 Vulkan 1.2 的内容,它现在被认为是 Vulkan 的一流着色语言,可以像使用 GLSL 一样轻松使用。
除了 一些例外,所有 GLSL 可用的 Vulkan 功能和着色器阶段也可以与 HLSL 一起使用,包括最近添加的 Vulkan 功能,如硬件加速光线追踪。另一方面,HLSL 到 SPIR-V 支持 Vulkan 独有的功能,这些功能在 DirectX 中(尚未)可用。

教育资源
如果您是 HLSL 的新手,一个好的起点是 Microsoft Learn 上的 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.Pos = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Position.xyz, 1.0))));
return output;
}
除了语法差异之外,内置函数使用 HLSL 名称。例如,gl_vertex
在 HLSL 中变为 VertexIndex
。GLSL 到 HLSL 内置映射的列表可以在 此处找到。
DirectXShaderCompiler (DXC)
与 GLSL 到 SPIR-V 的情况一样,要在 Vulkan 中使用 HLSL,需要一个着色器编译器。虽然 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
然后可以直接加载生成的 SPIR-V,就像从 GLSL 生成的 SPIR-V 一样。
使用库进行运行时编译
DXC 也可以使用 DirectX 编译器 API 集成到 Vulkan 应用程序中。这允许在运行时编译着色器。这样做需要包含 dxcapi.h
头文件并链接 dxcompiler
库。最简单的方法是使用动态库并将其与您的应用程序一起分发(例如 Windows 上的 dxcompiler.dll
)。
然后在运行时将 HLSL 编译为 SPIR-V 就非常简单了。
#include "include/dxc/dxcapi.h"
...
HRESULT hres;
// Initialize DXC library
CComPtr<IDxcLibrary> library;
hres = DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&library));
if (FAILED(hres)) {
throw std::runtime_error("Could not init DXC Library");
}
// Initialize DXC compiler
CComPtr<IDxcCompiler3> compiler;
hres = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&compiler));
if (FAILED(hres)) {
throw std::runtime_error("Could not init DXC Compiler");
}
// Initialize DXC utility
CComPtr<IDxcUtils> utils;
hres = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&utils));
if (FAILED(hres)) {
throw std::runtime_error("Could not init DXC Utiliy");
}
// Load the HLSL text shader from disk
uint32_t codePage = DXC_CP_ACP;
CComPtr<IDxcBlobEncoding> sourceBlob;
hres = utils->LoadFile(filename.c_str(), &codePage, &sourceBlob);
if (FAILED(hres)) {
throw std::runtime_error("Could not load shader file");
}
// Select target profile based on shader file extension
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";
}
// Mapping for other file types go here (cs_x_y, lib_x_y, etc.)
}
// Configure the compiler arguments for compiling the HLSL shader to SPIR-V
std::vector<LPCWSTR> arguments = {
// (Optional) name of the shader file to be displayed e.g. in an error message
filename.c_str(),
// Shader main entry point
L"-E", L"main",
// Shader target profile
L"-T", targetProfile,
// Compile to SPIRV
L"-spirv"
};
// Compile shader
DxcBuffer buffer{};
buffer.Encoding = DXC_CP_ACP;
buffer.Ptr = sourceBlob->GetBufferPointer();
buffer.Size = sourceBlob->GetBufferSize();
CComPtr<IDxcResult> result{ nullptr };
hres = compiler->Compile(
&buffer,
arguments.data(),
(uint32_t)arguments.size(),
nullptr,
IID_PPV_ARGS(&result));
if (SUCCEEDED(hres)) {
result->GetStatus(&hres);
}
// Output error if compilation failed
if (FAILED(hres) && (result)) {
CComPtr<IDxcBlobEncoding> errorBlob;
hres = result->GetErrorBuffer(&errorBlob);
if (SUCCEEDED(hres) && errorBlob) {
std::cerr << "Shader compilation failed :\n\n" << (const char*)errorBlob->GetBufferPointer();
throw std::runtime_error("Compilation failed");
}
}
// Get compilation result
CComPtr<IDxcBlob> code;
result->GetResult(&code);
// Create a Vulkan shader module from the compilation result
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 目标着色器配置文件 | 备注 |
---|---|---|
|
|
|
|
|
在 HLSL 术语中称为 Hull 着色器 |
|
|
在 HLSL 术语中称为 Domain 着色器 |
|
|
|
|
|
在 HLSL 术语中称为 Pixel 着色器 |
|
|
|
|
|
所有与光线追踪相关的着色器都使用 |
|
|
在 HLSL 术语中称为放大着色器。必须至少使用着色器模型 6.5(例如 |
|
|
必须至少使用着色器模型 6.5(例如 |
因此,例如,如果要编译针对着色器模型 6.6 功能的计算着色器,则目标着色器配置文件将为 cs_6_6
。对于光线追踪的任何命中着色器,它将为 lib_6_3
。
着色器模型覆盖范围
DirectX 和 HLSL 使用固定的着色器模型概念来描述支持的功能集。这与 Vulkan 和 SPIR-V 的灵活扩展方式不同,后者允许向着色器添加功能。下表尝试列出 HLSL 着色器模型对 Vulkan 的覆盖范围,但不保证完整性
着色器模型 | 支持 | 备注 |
---|---|---|
着色器模型 5.1 及以下 |
✔ |
不包括没有 Vulkan 等效项的功能 |
✔ |
波形内在函数,64 位整数 |
|
✔ |
SV_ViewID, SV_Barycentrics |
|
✔ |
16 位类型,非规范模式 |
|
✔ |
硬件加速光线追踪 |
|
✔ |
着色器整数点积,SV_ShadingRate |
|
⚠️ (部分支持) |
DXR1.1 (KHR 光线追踪), 网格和放大着色器, 附加的波形内在函数 |
|
⚠️ (部分支持) |
VK_NV_compute_shader_derivatives, VK_KHR_shader_atomic_int64, VK_EXT_descriptor_buffer, VK_EXT_mutable_descriptor_type |
|
⚠️ (部分支持) |
VK_KHR_shader_quad_control, VkPhysicalDeviceFeatures::shaderStorageImageMultisample |