Vulkan编程指南翻译 第六章 着色器和管线 第2节 SPIR-V 概述

本文介绍了SPIR-V着色语言及其在Vulkan图形API中的应用。SPIR-V被设计成易于工具和驱动使用的形式,提高了兼容性。文章详细解释了SPIR-V的文本形式、指令集以及如何通过Vulkan API创建着色器模块。

6.2  SPIR-V 概述

 

SPIR-V着色器嵌入在module里。每一个module都可以包含一个或多个着色器。每一个着色器都有一个固定名字入口点和着色器类型,这用来定义着色器在哪个着色阶段来使用。入口点之着色器开始运行时的起始位置。一个SPIR-Vmodule都随着一些附带信息传递 给VulkanVulkan返回一个对象来表示这个module。此module可以用来构造一个管线,管线是附带信息的着色器经过编译的版本,可以在设备上运行。

 

6.2.1  如何表示SPIR-V

SPIR-VVulkan官方唯一支持的着色语言。在API层面被Vulkan接受,最终用来构建管线,管线对象可配置一个设备来完成应用程序的工作。

SPIR-V被设计为可被工具和驱动很简单就能使用。这减少了不同实现之间的不同意提高兼容性。SPIR-V module在内存的32bi每字数据流形式存储的。除非你是工具作者或者计划自己生成SPIR-V,你不大可能会去直接处理二进制编码的SPIR-V。相反,你要么会去看SPIR-V的文本形式,或者使用诸如glslangvalidatorChronos官方GLSL编译器)的工具来生成SPIR-V

把着色器程序以.comp后缀名的方式保存为文本文件,告诉glslangvalidator把它当作计算着色器编译。我们可以通过使用glslangvalidator命令行的方式来编译着色器:

glslangvalidator simple.comp -o simple.spv

这会产生一个名为simple.spv SPIR-V二进制文件。我们可以使用SPIR-V反汇编工具,spirv-dis,来反编译这个二进制文件,会输出一份人可读的反汇编代码。如Listing 6.2所示:

Listing 6.2: Simplest SPIR-V

; SPIR-V

; Version: 1.0

; Generator: Khronos Glslang Reference Front End; 1

; Bound: 6

; Schema: 0

OpCapability Shader

%1 = OpExtInstImport "GLSL.std.450"

OpMemoryModel Logical GLSL450

OpEntryPoint GLCompute %4 "main"

OpExecutionMode %4 LocalSize 1 1 1

OpSource GLSL 450

OpName %4 "main"

%2 = OpTypeVoid

%3 = OpTypeFunction %2

%4 = OpFunction %2 None %3

%5 = OpLabel

OpReturn

OpFunctionEnd

 

你可以看到SPIR-V的文本形式看起来像汇编语言的变体。我们可深入这段反汇编,来看看他和原始的输入文件如何关联。反汇编文件的每一行待变一个SPIR-V指令,可能由多个token组成。

第一个指令OpCapability Shader,要求这个着色器开启兼容模式。SPIR-V功能粗略的分为“指令”和“特征”。在你的着色器程序可以使用这些特征之前,shader必须要声明使用的特征是哪一部分的。Listing 6.2的着色器程序是一个图形着色器,因此使用Shader能力。这是最基础的能力了。没有这个,我们不能编译图形着色器。随着我们引入更多SPIR-VVulkan功能,我们将介绍这些不同能力所依赖哦特征。

下一行,我们看到 %1 = OpExtInstImport "GLSL.std.450"。 这就是引入了一些附加的指令,对应着GLSL 450包含的功能,这也是原始着色器程序所写入的版本。注意这个指令以 %1 = 开头。这把指令计算的结果使用ID命名。OpExtInstImport的结果是一个有效的库。当我们需要调用这个库的函数,我们可以使用OpExtInst指令,它接受一个库(OpExtInstImport指令的结果)和一个指令索引。这允许SPIR-指令集被任意的拓展。

下一行,我们看到一些附加的声明,OpMemoryModel指定了这个module的工作内存的模型,这是对应GLSL 450的逻辑上的内存模型。这意味着所有的内存访问是通过资源而不是通过指针访问内存的物理内存模型。

下一行是这个module入口点的声明。OpEntryPoint GLCompute %4 "main" 指令表明对应的OpenGL计算着色器有一个可用的入口,以函数名字main导出为ID 4。这个名字被用来当把结果着色器module返回Vulkan时的参考入口点。

我们在后续的指令中使用这个IDOpExecutionMode %4 LocalSize 1 1 1, 它定义了这个着色器的工作组大小为 1 × 1 × 1 work item。 如果没有局部大小的layout限定符,这在GLSL中是隐式的。

下面两个指令是简单的声明类型的。OpSource GLSL 450表示这个moduleGLSL 450 版本编译而来,OpName 4 "main"提供了ID4token的名字。

现在,我们看到这个函数的真身了。第一, %2 = OpTypeVoid声明了我们想要以void类型使用ID 2. 所有东西在SPIR-V中都有一个ID,甚至是类型声明。大型、复合的类型可以通过连续的小的,简单的类型组成。然而,我们需要从某处开始,并且指定我们开始的地方类型为void

%3 = OpTypeFunction %2便是我们定义了ID 3 为一个函数类型,类型为void,不接受参数。我们在下一行使用它,%4 = OpFunction %2 None %3。这表示我们声明ID 4(知其那被命名为“main”)为函数3的一个实例(在上一行声明),返回void(如ID 2),且没有特殊的声明,这通过指令中的None表明,而且可以被用来表示诸如inline,不管变量是否为常量(常量性)等等。

最后,我们可以看到一个label(没有被用到,编译器操作的副作用)的声明,隐式的返回语句,和函数的结束。这是我们SPIR-Vmodule的结尾。

这个着色器程序的二进制dump192字节长。SPIR-V是非常详尽的,因为192字节比原来的着色器要长。然而,SPIR-V把原来的着色语言中隐式的东西变得显式了。比如,在GLSL中无需声明内存模型,因为它只支持一种逻辑内存模型。更有,这里编译的SPIR-V module有一些冗余信息,我们不关心main函数的名字,ID 5 label从来没有被使用,且着色器引入了GLSL.std.450库,但是从来没有使用过。我们可以把这个module中多余的信息抽离出去。即使在此之后,因为SPIR-V编码方式相对稀疏,产生的二进制文件使用一个通用的压缩器也相当容易被压缩,而且使用一个专门的压缩库可以把它压缩的更紧致 。

所有的SPIR-V编码都通过SSA来书写(single static assignment),这表明每一个虚拟寄存器(在上面的List中写作%ntoken)都被仅写入一次。几乎所有的工作的指令产生一个结果标识符。当我们开始写更复杂的着色器时,你将看到机器离线生成的SPIR-V有点笨拙,因为它的详尽特性和SSA形式,非常难以手写。非常建议你在应用程序中以lib形式使用编译器来离线生成SPIR-V

如果你计划自己生成或者解释SPIR-V module,你可以使用预定义的二进制编码器来构建工具,来解析或生成它们。然而,它们都有格式良好的二进制存储格式,我们在本章稍后讲解。

所有的SPIR-V module文件都以一个magic number开始,这可以用来简单的炎症二进制块,实际上就是SPIR-V module。这个魔法数字以无符号整型来看是0x07230203。这个数字也可以用来退单module的字节顺序。因为每一个SPIR-V toke 都是一个32-bit word,如果一个SPIR-V module通过磁盘或网络传输到有不同字节顺序的主机端,这些在一个word内的字节都被交换位置了,它的值给改变了。例如,如贵哦一个SPIR-V module 存储为小端格式,被加载到一个大端格式主机CPU,那么魔法书记就会被读为0x03022307,所以这个CPU就知道需要在这个module里交换字节顺序。

在魔法数字的后面有几个word,描述了module的属性。第一个是SPIR-V使用的的版本数字。它被编码在一个32-bit word里,其中16-23 bit为包含主版本号,8-15 包含次版本号。 SPIR-V 1.0 因此使用编码0x00010000。版本号剩下的bit位是保留的。下一个token包含生成SPIR-V module的工具的版本号。这个值由工具自定义。

下一个是本module中最大的ID号码。所有的变量,函数,和SPIR-V module的其他成员都被赋值到一个比这个数字小的ID,所以,在最前面包含这个数字允许工具分配好数组来存放它们,而不是随时的分配内存。头部最后一个word是保留的,应被置为0。接下来的是指令流。

 

6.2.2  SPIR-V传递给Vulkan

Vulkan并不关心SPIR-V 着色器和module从哪里来。通常,它们会被离线编译好作为应用程序的一部分,或者在应用程序中直接生成。一旦你有了一个SPIR-V module,你需要把它传递给Vulkan,以便可以使用它创建一个着色器module 对象。可以调用vkCreateShaderModule()来做到,其原型如下:

VkResult vkCreateShaderModule (

VkDevice device,

const VkShaderModuleCreateInfo* pCreateInfo,

const VkAllocationCallbacks* pAllocator,

VkShaderModule* pShaderModule);

和所有的Vulkan对象创建函数一样,vkCreateShaderModule()接受一个设备handle输入,和一个指向包含创建对象信息的数据的指针。此时,就是VkShaderModuleCreateInfo类型,其定义为:

typedef struct VkShaderModuleCreateInfo {

VkStructureType sType;

const void* pNext;

VkShaderModuleCreateFlags flags;

size_t codeSize;

const uint32_t* pCode;

} VkShaderModuleCreateInfo;

VkShaderModuleCreateInfosType域应置为VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFOpNext应置为nullptrflags域保留使用应置为0codeSize域包含了SPIR-V module字节单位的大小,代码通过pCode传入。

如果这个SPIR-V代码是有效的,且能够被Vulkan理解,那么vkCreateShaderModule()将返回VK_SUCCESS,并

<think> 首先,用户的问题是:“我想了解在Vulkan中创建着色器模块时,pcode指向的不是SPIR-V的含义”。用户提到“pcode”,但在Vulkan的标准术语中,当创建着色器模块时使用的API是`vkCreateShaderModule`,它的参数包括一个`const VkShaderModuleCreateInfo* pCreateInfo`结构体。在这个结构体中,有一个字段是`const uint32_t* pCode`,这个`pCode`指向的是SPIR-V字码。 参考用户的引用: - 引用[1]提到:通过调用`vkCreateShaderModule()`创建着色器模块对象,传递SPIR-V module。 - 引用[2][3]也强调Vulkan着色器必须使用SPIR-V字码格式。 所以,在正常情况下,`pCode`必须指向有效的SPIR-V字码。但用户的问题是问“pcode指向的不是SPIR-V的含义”,意思是如果`pCode`指向的不是SPIR-V数据会怎样。 关键点: - 在`vkCreateShaderModule`函数的参数中: - `pCreateInfo`指向一个`VkShaderModuleCreateInfo`结构体。 - `pCreateInfo->pCode`是一个指向uint32_t数组的指针,这个数组包含SPIR-V字码。 - `pCreateInfo->codeSize`指定了字码的大小(以字为单位)。 如果`pCode`指向的不是有效的SPIR-V数据: - Vulkan规范要求着色器模块创建时输入必须是SPIR-V格式。如果传递的不是SPIR-V,创建过程可能会失败。 - 具体行为:`vkCreateShaderModule`返回一个错误码。常见的错误是`VK_ERROR_INVALID_SHADER_NV`或类似错误(确切的错误枚举可能因实现而异)。Vulkan规范定义了错误处理。 - 在创建着色器模块时,Vulkan实现会对输入数据进行验证。如果数据不是有效的SPIR-V,函数会返回一个错误,比如`VK_ERROR_INVALID_SHADER_NV`或`VK_ERROR_INVALID_EXTERNAL_HANDLE`(但更具体的是`VK_ERROR_INVALID_SHADER_NV`)。 参考Vulkan规范: - 官方Vulkan规范指出,`pCode`必须指向一个有效的SPIR-V模块。否则,行为是未定义的,或者函数返回错误。 - 在错误处理中:如果`pCode`不指向有效的SPIR-V,`vkCreateShaderModule`可能返回`VK_ERROR_INVALID_SHADER_NV`(假设扩展可用),否则返回一个一般性的错误。 含义: - 如果`pCode`指向的不是SPIR-V数据,那么创建着色器模块会失败。函数会返回一个错误码,而不是一个有效的`VkShaderModule`句柄。 - 这意味着着色器模块不会被创建,后续使用这个着色器模块的操作(比如在管道中使用)也会失败。 - 在应用程序中,开发者应该检查`vkCreateShaderModule`的返回值来捕获这种错误。 为什么用户会问这个问题: - 用户可能在阅读代码或文档时看到“pcode”,并好奇如果传错了数据会怎样。 - 在上下文中,用户引用了关于SPIR-V的内容,所以强调SPIR-V的必要性。 回答结构: - 基于系统指令: - 使用行内数学表达式如$...$(但这个问题可能不需要数学,除非涉及计算)。 - 独立公式用$$...$$(同样,这里可能不适用)。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 引用的段落末尾添加引用标识(用户提供了引用,我需要引用它们)。 内容: 1. 解释`vkCreateShaderModule`的`pCode`参数:它必须指向SPIR-V字码。 2. 如果指向的不是SPIR-V:创建失败,返回错误。 3. 含义:应用程序无法使用无效着色器模块;可能导致渲染错误或崩溃。 4. 引用相关段落来支持。 生成相关问题: - 结束时生成3个相关问题,类似示例。 最终响应结构: - 回答用户的问题。 - 解释含义。 - 引用提供的参考资料作为支持。 - 结束时有“相关问题”部分。 </think> ### Vulkan着色器模块创建:当pCode指向非SPIR-V数据的含义 在Vulkan API中,创建着色器模块使用`vkCreateShaderModule`函数,其参数`pCreateInfo->pCode`(常简称为`pCode`)必须指向有效的SPIR-V(Standard Portable Intermediate Representation)字码。SPIR-V是为Vulkan设计的中间字码格式,它确保了着色器代码的平台无关性高效执行[^2][^3]。如果`pCode`指向的数据不是SPIR-V格式,会导致着色器模块创建失败,并产生特定错误。以下是详细解释: #### 1. **正常情况下`pCode`的作用** - `vkCreateShaderModule`的原型为: ```c VkResult vkCreateShaderModule( VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule ); ``` 其中,`pCreateInfo`结构体中的`pCode`字段是一个指向`uint32_t`数组的指针,该数组存储SPIR-V字码。`codeSize`指定数据大小(以字为单位)。 - Vulkan要求着色器必须编译为SPIR-V格式,因为它是跨平台的中间表示,可直接由GPU驱动程序优化执行[^2][^3]。例如,应用程序通常使用工具(如glslangValidator)将GLSL或HLSL源代码离线编译为SPIR-V字码[^1][^3]。 #### 2. **当`pCode`指向非SPIR-V数据的含义与后果** - **含义**:如果`pCode`指向的数据不是有效的SPIR-V字码(例如,指向原始GLSL代码、随机二进制数据或错误格式),则违反了Vulkan规范。这意味着: - **着色器模块创建失败**:`vkCreateShaderModule`函数会返回一个错误代码,而不是成功创建`VkShaderModule`对象。 - **错误类型**:常见的错误码包括: - `VK_ERROR_INVALID_SHADER_NV`:表示着色器数据无效(NVIDIA扩展错误码)。 - `VK_ERROR_INITIALIZATION_FAILED`:如果实现无法解析或验证输入数据。 - 更一般性的错误如`VK_ERROR_UNKNOWN`,具体取决于驱动程序平台实现。 - **行为未定义**:在错误情况下,Vulkan可能尝试解析数据但失败,导致驱动程序日志记录错误信息,甚至应用程序崩溃(如果未正确处理返回值)。无效数据不会生成可用的着色器模块,因此后续操作(如绑定到图形管道)会失败或引发进一步错误[^1][^2]。 - **根本原因**:SPIR-V字码具有严格的结构(如魔术字`0x07230203`标识头部),驱动程序会在创建时验证其格式。非SPIR-V数据无法通过验证,因为Vulkan设计为直接处理字码以提高性能安全性[^2][^3]。 #### 3. **如何避免处理此问题** - **正确使用SPIR-V**:始终确保`pCode`指向有效的SPIR-V数据。这通常通过: - 离线编译:使用工具如`glslangValidator`将GLSL编译为SPIR-V。 - 运行时验证:在调用`vkCreateShaderModule`前,检查数据是否符合SPIR-V规范(例如,验证魔术字)。 - **错误处理**:在Vulkan应用程序中,必须检查`vkCreateShaderModule`的返回值: ```c VkShaderModule shaderModule; VkResult result = vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule); if (result != VK_SUCCESS) { // 处理错误:输出日志、回退或退出 fprintf(stderr, "Shader module creation failed: %d\n", result); } ``` - 参考示例:从引用[1]可知,SPIR-V模块必须作为应用程序的一部分提供或生成,错误输入会导致初始化失败[^1]。 总之,当`pCode`指向非SPIR-V数据时,其含义是着色器模块创建失败,核心问题是输入数据格式无效。这突显了Vulkan的设计原则:强制使用SPIR-V以确保高效性跨平台一致性,但也要求开发者严格验证输入[^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值