6.2  SPIR-V 概述

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

6.2.1  如何表示SPIR-V

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

SPIR-V被设计为可被工具和驱动很简单就能使用。这减少了不同实现之间的不同意提高兼容性。SPIR-V module在内存的32bi每字数据流形式存储的。除非你是工具作者或者计划自己生成SPIR-V,你不大可能会去直接处理二进制编码的SPIR-V。相反,你要么会去看SPIR-V的文本形式,或者使用诸如glslangvalidator(Chronos官方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-V和Vulkan功能,我们将介绍这些不同能力所依赖哦特征。

下一行,我们看到 %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时的参考入口点。

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

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

现在,我们看到这个函数的真身了。第一, %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的结尾。

这个着色器程序的二进制dump是192字节长。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中写作%n的token)都被仅写入一次。几乎所有的工作的指令产生一个结果标识符。当我们开始写更复杂的着色器时,你将看到机器离线生成的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;

VkShaderModuleCreateInfo的sType域应置为VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,pNext应置为nullptr。flags域保留使用应置为0。codeSize域包含了SPIR-V module字节单位的大小,代码通过pCode传入。

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

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

  1. Vulkan编程指南翻译 第七章 图形管线 第2节 Renderpasses(未完成)

    7.2  renderpass Vulkan图形管线和计算管线的区别之一是,你使用图形管线来渲染出像素,组成图像以供处理或显示给用户.在复杂的图形应用程序中,图片经过很多遍构建,每一遍都生成场景的一部 ...

  2. Gradle2.0用户指南翻译——第六章. 构建脚本基础

    翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tre ...

  3. 《OpenGL编程指南》一第2章 着色器基础

    本节书摘来自华章出版社<OpenGL编程指南>一书中的第2章,作者 Bill Licea-Kane ,更多章节内容可以访问云栖社区"华章计算机"公众号查看 第2章 着色 ...

  4. 【重识云原生】第六章容器基础6.4.5.1节——Deployment概述

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  5. 【重识云原生】第六章容器基础6.4.9.2节——使用 Service 连接到应用

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  6. 【重识云原生】第六章容器基础6.4.7.1节——K8S Job组件

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  7. 【重识云原生】第六章容器基础6.4.5.2节——Deployment配置详细说明

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  8. 【重识云原生】第六章容器基础6.4.5.3节——Deployment实现原理解析

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  9. 翻译:探索GLSL-用几何着色器(着色器库)实现法线可视化

    翻译:探索GLSL-用几何着色器(着色器库)实现法线可视化 翻译自: Exploring GLSL – Normal Visualizer with Geometry Shaders (Shader ...

  10. 【 第六章 拦截器,注解配置springMVC,springMVC执行流程】

    第六章 拦截器,注解配置springMVC,springMVC执行流程 1.拦截器: ①springMVC中的拦截器用于拦截控制器方法的执行. ②springMVC的拦截器需要实现HandlerInt ...

最新文章

  1. RxJava 操作符 do
  2. 分享一个基于 Shiro 的权限管理系统,亮点是支持 restful 风格 URL.
  3. NYOJ 643 发短信
  4. 50个常用元器件图片_50个最常用的英语短语(一),值得收藏!
  5. 记录工作中常用的CSS3
  6. Servlet教程第7讲笔记
  7. windows下的工具链 树莓派_Lab2树莓派交叉编译(windows平台)
  8. linux中定义用户账户的文件为,Linux中用户和组中认证库和解析库的文件格式以及默认参数定义文件...
  9. [讲课视频]谈谈C/C++中的整数
  10. Android面试总结-中高级
  11. 接上个文章(mzsock
  12. C# 实现对PPT插入、编辑、删除表格
  13. 下载与安装nexus3
  14. SQL Server最受欢迎技巧:解读DBA
  15. Simulink代码生成: Model Reference及其代码
  16. 第十六节 springboot 打包vue代码实现前后端统一部署
  17. 阿里巴巴 Java开发手册
  18. 云栖大会:友盟+举办“移动互联网开发提效与实践论坛”,产品矩阵助力开发者业务增长...
  19. c语言和地球物理,2016年东北理工大学核工程与地球物理学院C语言程序设计(同等学力加试)复试笔试最后押题五套卷...
  20. segfault 问题分析

热门文章

  1. 图像放大的三种处理方法:最近邻插值,双线性插值 ,双三次插值算法
  2. 2017-AHU校赛网络赛解题报告
  3. 解决phpstudy的Apache启动失败
  4. sqlserver 时间计算函数
  5. 只有程序员才能读懂的西游记
  6. 静态单赋值(一)—gcc中的支配树
  7. chrome浏览器版本与驱动不匹配问题的解决办法
  8. 易语言新手入门教程第十四课 - QQ自动登录器第二部分
  9. php seek_cur,Golang中seek使用方法详解
  10. MonoJSG:联合语义和几何代价函数的单目3D目标检测(CVPR2022)