SPIR-V是描述vulkan使用的着色器的语言。通常,这些着色器用例如GLSL之类的高级语言编写,但有时可能需要查看、调试或修改相应的SPIR-V。这个教程目的在于一地的那点地介绍这些语言,使其更容易阅读。
SPIR-V语言采用SSA(Static single assignment)格式,是一个非常抽象的语法树(带有标题)。这意味着每个中间结果只写入一次。将其视为一种语言,每个变量都是const,就想函数式语言一样。在七班几个教程中,将重点关注标题。
在所有这些教程中,请教SPIR-V规范放在手边。
最小SPIR-V
我们把样板放在一边。下面是一个有效的着色器(什么都不做)所需的最低要求:
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %3 "main"
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
OpReturn
OpFunctionEnd
让我们逐行浏览:
OpCapability Shader
这是一个着色器。SPIR-V还可以预先声明其他功能,例如使用它可以使用16位浮点指令,或者它使用变换反馈。
OpMemoryModel Logical GLSL450
考虑这个模板。它声明着色器使用逻辑地址(而不是物理地址),并且使用GLSL内存模型。
OpEntryPoint Vertex %3 "main"
OpEntryPoint指令在着色器模块中声明一个“入口点”。对于这种情况,入口点是顶点着色器,函数由%3标识(下面将详细介绍),名称为“main”。该名称与VkPipelineShaderStageCreateInfo
一起使用来标识此入口点。
SPIR-V很可能包含多个着色器的代码,可能共享一些功能,并且它可以有多个入口点。
%1 = OpTypeVoid
SPIR-V使用“ids”来指代声明的所有内容,无论是类型、函数、变量、中间值等等。这些ids写成%id,其中id可以是数字或者C风格的变量名称。
这条指令声明void类型,并为其制定id %1。正如我们看到的那样,SPIR-V本身并没有预定义任何类型。
在SPIR-V规范中查找OpTypeVoid。只需要在规范中搜索OpTypeVoid,直到找到指令的链接或定义指令本身的表。无需为指令的二进制表示而烦恼。
%2 = OpTypeFunction %1
这条指令用C语言声明了函数类型void(*)()。%1就是上面声明的void类型,这就是返回类型。函数类型本身存储在一个新的id %2中。
如果函数类型需要参数怎么办?在SPIR-V规范中查找OpTypeFunction。
%3 = OpFunction %1 None %2
最后,声明函数本身。观察函数类型(%2),返回类型(%1)在这里呗冗余指定。这是SPIR-V中的一个常见模式,其中每条指令的结果类型都是冗余指定的。
None是什么?在规范中查找OpFunction,然后点击“Function Control”
%4 = OpLabel
标签,标记代码块的开始。用于跳转指令。目前可以忽略这个。
OpReturn
OpFunctionEnd
函数返回和结束指令。