一、解析SPIR-V
编译
首先,我们需要将每个着色器阶段从源代码文件编译为SPIR-V 文件。有多种工具可用于执行此操作,比如GLSlangValidator-它支持 Google #include 扩展。
将顶点着色器代码放入名为“shader.vert”的文本文件中,将像素着色器代码放入名为“shader.frag”的文件中。在同一目录中创建一个包含以下内容的 .bat 文件:
glslangValidator.exe“shader.vert”-V -o“vert.spv”
glslangValidator.exe“shader.frag”-V -o“frag.spv”
运行bat文件,将保存两个.spv文件。
链接
现在我们想要将代表不同着色器阶段的两个文件合并到一个文件中。这是通过Khronos 的链接工具完成的。将以下行添加到 .bat 文件中,将两个 .spv 文件编译为一个。它还将删除现有文件以进行一些清理:
spirv-link "vert.spv" "frag.spv" -o "shader.spv"
del "vert.spv"
del "frag.spv"
这将保存一个名为“shader.spv”的文件,您可以将其作为一个着色器模块加载并用于 Vulkan 中的不同阶段。
解析
如果您始终使用顶点和片段阶段,那么没有问题,但如果组合的 .spv 文件包含其他阶段,或者缺少片段阶段怎么办?我们可以使用最小的 SPIR-V 文件解析器轻松解释这一点。我们不会包含任何大型臃肿的库来执行此操作,因为我们只需要有关着色器中包含哪些阶段的一些基本信息。幸运的是,SPIR-V 规范非常简单,不需要太多代码即可提取我们想要的信息:
std::string entrypointname[6];
auto stream = ReadFile(L"Shaders/shader.spv");
// Parse SPIR-V data
Assert(stream->ReadInt() == 0x07230203);
int version = stream->ReadInt();
int genmagnum = stream->ReadInt();
int bound = stream->ReadInt();
int reserved = stream->ReadInt();
bool stages[6] = {false,false,false,false,false,false};
// Instruction stream
while (stream->Ended() == false)
{
int pos = stream->GetPos();
unsigned int bytes = stream->ReadUInt();
int opcode = LOWORD(bytes);
int wordcount = HIWORD(bytes);
if (opcode == 15)
{
int executionmodel = stream->ReadInt();
Assert(executionmodel >= 0);
if (executionmodel < 6)
{
stream->ReadInt(); // entry point
stages[executionmodel] = true;
entrypointname[executionmodel] = stream->ReadString();
}
}
stream->Seek(pos + wordcount * 4);
}
这个代码可以检索每个阶段的入口点名称,因此可以确保正确加载着色器。
以下是 SPIR-V 规范中的不同着色器阶段:
- 0: Vertex
- 1: TessellationControl
- 2: TessellationEvaluation
- 3: Geometry
- 4: Fragment
- 5: GLCompute
我们现在为 Vulkan 程序提供了标准的单文件着色器格式。用于创建这些的代码将如下所示:
VkShaderModule shadermodule;
// Create shader module
VkShaderModuleCreateInfo shaderCreateInfo = {};
shaderCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shaderCreateInfo.codeSize = bank->GetSize();
shaderCreateInfo.pCode = reinterpret_cast<const uint32_t*>(bank->buf);
VkAssert(vkCreateShaderModule(device->device, &shaderCreateInfo, nullptr, &shadermodule));
// Create vertex stage info
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = shadermodule;
vertShaderStageInfo.pName = entrypointname[0].c_str();
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {};
if (stages[4])
{
// Create fragment stage info
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = shadermodule;
fragShaderStageInfo.pName = entrypointname[4].c_str();
}
// Create your graphics pipeline...