• 闭关之 Vulkan 应用开发指南笔记(三): 着色器和管线、图形管线


    第6章 着色器和管线

    6.1 GLSL 概述

    • 对 GLSL 的修改允许它用来产生 Vulkan 可用的 SPIR-V 着色器,这些修改记录在 GL_KHR_ vulkan_glsl 扩展的文档中
    • 内置函数 any()all() 分别用来判断数组中是否有一个为 true 以及是否所有的元素都是 true

    6.2 SPIR-V 概述

    6.2.1 如何表示 SPIR-V

    把 SPIR-V 传递给 Vulkan

    • 创建着色器模块对象
      VkResult vkCreateShaderModule (
        VkDevice                        device,
        const VkShaderModuleCreateInfo* pCreateInfo,
        const VkAllocationCallbacks*    pAllocator,
        VkShaderModule*                 pShaderModule
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkShaderModuleCreateInfo
      typedef struct VkShaderModuleCreateInfo 
      {
        //为 VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO 
        VkStructureType           sType;
        //nullptr
        const void*               pNext;
        //为 0
        VkShaderModuleCreateFlags flags;
        //SPIR-V 模块的大小
        size_t                    codeSize;
        const uint32_t*           pCode;
      } VkShaderModuleCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 销毁并释放资源
      void vkDestroyShaderModule (
        VkDevice                     device,
        VkShaderModule               shaderModule,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5

    6.3 管线

    • 在 Vulkan 中有两种管线
      • 计算
      • 图形

    6.3.1 计算管线

    • 本地工作组和全局工作组都是三维的。
    • 本地工作组的尺寸在计算着色器内部设置。在 GLSL 中,使用 layout 限定符
      layout (local_size_x = 4, local_size_y = 5, local_size_z 6) in;
      
      • 1
    • 计算着色器的本地工作组的最大尺寸一般来说比较小,仅仅要求 x 和 y 维度上至少有 128 次 调用,z 维度上至少有 64 次调用
    • 工作组的总“体积” (在 x、y 和 z 三个方向上的上限的乘积) 有额外的限制,仅仅要求至少有 128 次调用。尽管许多实现支持更高的上限,当想要超出最小值时,你需要总是查询这些上限
    • 查询工作组的最大尺寸
      • vkGetPhysicalDeviceProperties()
        • VkPhysicalDeviceLimits
          • maxComputeWorkGroupSize
    • 本地工作组里的最大调用次数
      • maxComputeWorkGroupInvocations

    6.3.2 创建管线

    • 创建一个或多个管线
      VkResult vkCreateComputePipelines (
        VkDevice                           device,
        //用来加速管线创建的一个对象的句柄
        VkPipelineCache                    pipelineCache,
        uint32_t                           createInfoCount,
        //个新管线的参数信息
        const VkComputePipelineCreateInfo* pCreateInfos,
        const VkAllocationCallbacks*       pAllocator,
        VkPipeline*                        pPipelines
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • VkComputePipelineCreateInfo
      typedef struct VkComputePipelineCreateInfo 
      {
        //VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO 
        VkStructureType                 sType;
        //nullptr
        const void*                     pNext;
        //0
        VkPipelineCreateFlags           flags;
        //包含着色器本身的信息
        VkPipelineShaderStageCreateInfo stage;
        VkPipelineLayout                layout;
        VkPipeline                      basePipelineHandle;
        int32_t                         basePipelineIndex;
      } VkComputePipelineCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • VkPipelineShaderStageCreateInfo
      typedef struct VkPipelineShaderStageCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO 
        VkStructureType                  sType;
        //nullptr
        const void*                      pNext;
        //0
        VkPipelineShaderStageCreateFlags flags;
        //管线创建的阶段
        //VK_SHADER_STAGE_COMPUTE_BIT 
        VkShaderStageFlagBits            stage;
        //着色器模块的句柄
        VkShaderModule                   module;
        //表示这个特别的管线的入口点
        const char*                      pName;
        //包含特化一个着色器所需的信息
        const VkSpecializationInfo*      pSpecializationInfo;
      } VkPipelineShaderStageCreateInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    6.3.3 特化常量

    • “特化”是指构建着色器时将一些常量编译进去
    • Vulkan 实现会延迟管线代码的最终生成时间
      • 直到调用 vkCreateComputePipelines() 函数
    • 这允许特化常量的值在着色器优化的最后通道中才考虑
    • 特化常量的典型应用包括 (详细解释 Page 146)
      • 通过分支产生特殊执行路径
      • 通过 switch 语句产生的特殊情形
      • 循环展开
      • 常量折叠
      • 运算符简化
    • 常量可以被创建管线时传递的新值覆盖
      typedef struct VkSpecializationInfo 
      {
        //需要设置新值的特化常量的个数
        uint32_t                        mapEntryCount;
        //表示特化常量
        const VkSpecializationMapEntry* pMapEntries;
        //数据大小
        size_t                          dataSize;
        //原生数据
        const void*                     pData;
      } VkSpecializationInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • VkSpecializationMapEntry
      typedef struct VkSpecializationMapEntry 
      {
        //特化常量的 ID, 使用 constant_id 布局限定符来设置值
        uint32_t constantID;
        //原生数据偏移量
        uint32_t offset;
        原生数据大小
        size_t   size;
      } VkSpecializationMapEntry;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 销毁管线对象
      void vkDestroyPipeline (
        VkDevice                     device,
        VkPipeline                   pipeline,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5

    6.3.4 加速管线的创建

    • 创建管线可能是应用程序开销最大的操作之一
    • 管线缓存
      VkResult vkCreatePipelineCache (
        VkDevice                         device,
        const VkPipelineCacheCreateInfo* pCreateInfo,
        const VkAllocationCallbacks *    pAllocator,
        VkPipelineCache*                 pPipelineCache
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkPipelineCacheCreateInfo
      typedef struct VkPipelineCacheCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO
        VkStructureType            sType;
        //nullptr
        const void *               pNext;
        //0
        VkPipelineCacheCreateFlags flags;
        size_t                     initialDataSize;
        //存在程序上一次运行产生的数据的地址
        const void *               pInitialData;
      } VkPipelineCacheCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 从缓存中取出数据
      VkResult vkGetPipelineCacheData (
        VkDevice        device,
        VkPipelineCache pipelineCache,
        //内存区域的大小
        size_t*         pDataSize,
        //缓存数据的内存区域
        void*           pData
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 可以调用 vkGetPipelineCacheData() 两次来存储所有的缓存数据
    • 将管线缓存数据保存到文件中
      • Page 149
    • 缓存数据文件头的定义
      • Page 149
    • 合并两个缓存对象
      • 在多个线程中创建管线时,特别有用
      VkResult vkMergePipelineCaches (
        VkDevice               device,
        //目标缓存的句柄
        VkPipelineCache        dstCache,
        //待融合缓存的个数
        uint32_t               srcCacheCount,
        const VkPipelineCache* pSrcCaches
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 销毁管线缓存对象
      void vkDestroyPipelineCache (
        VkDevice                     device,
        VkPipelineCache              pipelineCache,
        const VkAllocationCallbacks* pAllocator
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5

    6.3.5 绑定管线

    • 把管线绑定到一个命令缓冲区
      void vkCmdBindPipeline (
        VkCommandBuffer     commandBuffer,
        VkPipelineBindPoint pipelineBindPoint,
        VkPipeline          pipeline
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 每一个命令缓冲区上有两个管线绑定点
      • 图形绑定点
        • VK_PIPELINE_BIND_POINT_GRAPHICS
      • 计算绑定点
        • VK_PIPELINE_BIND_POINT_COMPUTE

    6.4 执行工作

    • 使用计算管线分发全局工作组
      void vkCmdDispatch (
        VkCommandBuffer commandBuffer,
        uint32_t x,
        uint32_t y,
        uint32_t z
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 间接分发
      • 在工作组中分发的个数来自缓冲区对象
      • 允许分发大小在命令缓冲区构建之后计算
        • 使用一个缓冲区来间接分发,然后用主机重写缓冲区里的内容
        void vkCmdDispatchIndirect (
          VkCommandBuffer commandBuffer,
          //工作组存储在 3 个连续的 uint32_t 类型的变量
          VkBuffer        buffer,
          //偏移量
          VkDeviceSize    offset
        );
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 缓冲区中的参数实质上代表了一个结构体
        typedef struct VkDispatchIndirectCommand 
        {
          uint32_t x;
          uint32_t y;
          uint32_t z;
        } VkDispatchIndirectCommand; 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

    6.5 在着色器中访问资源

    • 应用程序中的着色器以两种方式来使用和产生数据
      • 通过和固定功能的硬件进行交互
      • 直接读取和写入资源

    6.5.1 描述符集

    • 描述符集是作为整体绑定到管线的资源的集合
      • 可以同时将多个集合绑定到一个管线
      • 每一个集合都有一个布局
        • 布局描述了集合中资源的排列顺序和类型
        • 两个拥有相同布局的集合被视为兼容的和可相互交换的
    • 管线布局
      • 可被管线访问的集合的集合组成的对象
      • 管线通过参照这个管线布局对象来创建
    • 对于描述符集兼容的两个管线布局,它们必须符合如下两点
      • 使用相同的推送常量范围
      • 按照相同顺序使用相同的描述符集布局 (或等同的布局)
    • 符集布局和管线布局之间的关系
      • Page 153
    • 创建描述符集布局对象
      VkResult vkCreateDescriptorSetLayout (
        VkDevice                               device,
        const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
        const VkAllocationCallbacks*           pAllocator,
        VkDescriptorSetLayout*                 pSetLayout
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkDescriptorSetLayoutCreateInfo
      typedef struct VkDescriptorSetLayoutCreateInfo 
      {
        //VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO 
        VkStructureType                     sType;
        //nullptr
        const void*                         pNext;
        //0
        VkDescriptorSetLayoutCreateFlags    flags;
        uint32_t                            bindingCount;
        //把资源绑定到描述符集里的绑定点
        const VkDescriptorSetLayoutBinding* pBindings;
      } VkDescriptorSetLayoutCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • VkDescriptorSetLayoutBinding
      typedef struct VkDescriptorSetLayoutBinding 
      {
        //每个着色器可访问的资源都具有一个绑定序号
        //
        uint32_t           binding;
        //绑定点的描述符的类型
        VkDescriptorType   descriptorType;
        uint32_t           descriptorCount;
        VkShaderStageFlags stageFlags;
        const VkSampler*   pImmutableSamplers;
      } VkDescriptorSetLayoutBinding;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • VkDescriptorType
      • VK_DESCRIPTOR_TYPE_SAMPLER
        • 采样器是一个可以用来在从图像读入数据时执行诸如过滤、采样坐标转换等操作的对象
      • VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
        • 被采样的图像是一种图像,可用来和采样器连接,为着色器提供过滤过的数据
      • VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
        • 图像-采样器联合对象是采样器与图像的一个配对
        • 总是使用同一个采样器来对这个图像做采样,在一些架构上会更加高效
      • VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
        • 存储图像是不可以被采样器使用却可以写入的图像
      • VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
        • uniform 纹素缓冲区是一种缓冲区,里面填充了同构格式化数据
          • 不能被着色器写入
        • 如果知道该缓冲区的内容是不变的,那么某些 Vulkan 实现可以优化对该缓冲区的访问
      • VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
        • 存储纹素缓冲区是一种包含格式化数据的缓冲区
        • 与 uniform 纹素缓冲区非常像,但是可以写入该存储缓冲区
      • VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER 和 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
        • 数据是未格式化的,通过着色器里声明的结构体来描述
      • VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC和VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC
        • 包含了起始偏移量和大小,把描述符集绑定到管线时传入,而不是当把描述符绑定到集时传入
        • 这允许单个集合中的单个缓冲区高频地更新
      • VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
        • 输入附件是一种特殊类型的图像
        • 它的内容是由图形管线里同一个图像上的早期操作所生成的
    • 建议你不要创建稀疏填充的集合,因为这会浪费设备资源
    • 把两个或多个描述符集打包成管线可以使用的形式
      VkResult vkCreatePipelineLayout (
        VkDevice                          device,
        const VkPipelineLayoutCreateInfo* pCreateInfo,
        const VkAllocationCallbacks*      pAllocator,
        VkPipelineLayout*                 pPipelineLayout
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkPipelineLayoutCreateInfo
      typedef struct VkPipelineLayoutCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_ LAYOUT_CREATE_INFO 
        VkStructureType              sType;
        //nullptr
        const void*                  pNext;
        //0
        VkPipelineLayoutCreateFlags  flags;
        //描述符集布局的数量
        uint32_t                     setLayoutCount;
        const VkDescriptorSetLayout* pSetLayouts;
        //推送常量
        uint32_t                     pushConstantRangeCount;
        //推送常量
        const VkPushConstantRange*   pPushConstantRanges;
      } VkPipelineLayoutCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 一次可绑定的描述符集的最大数至少为 4
      • vkGetPhysicalDeviceProperties()
        • VkPhysicalDeviceLimits
          • maxBoundDescriptorSets
    • 管线资源限制表格
      • Page 158
    • 如果两个管线布局对于前面几个集合使用相同(或者等同的)的集合布局,而对于后面的集合使用不相同的集合布局,那么认为这两个管线布局是部分兼容的
    • 在两个部分兼容的管线之间切换时,无须重新绑定任何集合,直到管线共享布局的地方
    • 如果你有一系列的资源并想在全局范围内访问
      • 比如包含每帧所需常量的 uniform 块
      • 或者在每个着色器都需要访问的纹理
      • 就把这些资源放在第一个集合里
      • 频繁改变的资源可以放到高序号的集合里
    • 销毁管线布局
      void vkDestroyPipelineLayout (
        VkDevice                     device,
        VkPipelineLayout             pipelineLayout,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 销毁描述符集布局对象
      void vkDestroyDescriptorSetLayout (
        VkDevice                     device,
        VkDescriptorSetLayout        descriptorSetLayout,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5

    6.5.2 绑定资源到描述符集

    • 资源是通过描述符表示的,绑定到管线上的顺序是
      • 先把描述符绑定到集合上
      • 然后把描述符集绑定到管线
    • 创建描述符缓存池
      VkResult vkCreateDescriptorPool (
        VkDevice                          device,
        const VkDescriptorPoolCreateInfo* pCreateInfo,
        const VkAllocationCallbacks*      pAllocator,
        VkDescriptorPool*                 pDescriptorPool
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkDescriptorPoolCreateInfo
      typedef struct VkDescriptorPoolCreateInfo 
      {
        //VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO 
        VkStructureType             sType;
        //nullptr
        const void*                 pNext;
        //唯一定义VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT
        //应用程序可以释放从池中获取的单个描述符
        //如果不打算把单个描述符归还给缓存池,把 flags 设置为 0 即可
        VkDescriptorPoolCreateFlags flags;
        //可从池中分配的集合数量的最大值
        uint32_t                    maxSets;
        //可以存储在集合中的每种类型资源可用的描述符个数
        uint32_t                    poolSizeCount;
        const VkDescriptorPoolSize* pPoolSizes;
      } VkDescriptorPoolCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • VkDescriptorPoolSize
      typedef struct VkDescriptorPoolSize 
      {
        //资源的类型
        VkDescriptorType type;
        //池中该种类型资源的个数
        uint32_t         descriptorCount;
      } VkDescriptorPoolSize;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 创建描述符集对象
      VkResult vkAllocateDescriptorSets (
        VkDevice                           device,
        const VkDescriptorSetAllocateInfo* pAllocateInfo,
        VkDescriptorSet*                   pDescriptorSets
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • VkDescriptorSetAllocateInfo
      typedef struct VkDescriptorSetAllocateInfo 
      {
        //VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO
        VkStructureType              sType;
        //nullptr
        const void*                  pNext;
        //可以从中分配集合的描述符缓存池的句柄
        //在外部保持同步
        VkDescriptorPool             descriptorPool;
        //创建的集合的个数
        uint32_t                     descriptorSetCount;
        //每一个集合的布局
        const VkDescriptorSetLayout* pSetLayouts;
      } VkDescriptorSetAllocateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 释放一个或者多个描述符集
      VkResult vkFreeDescriptorSets (
        VkDevice               device,
        VkDescriptorPool       descriptorPool,
        uint32_t               descriptorSetCount,
        const VkDescriptorSet* pDescriptorSets
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 重置缓存池
      VkResult vkResetDescriptorPool (
        VkDevice                   device,
        VkDescriptorPool           descriptorPool,
        //为 0
        VkDescriptorPoolResetFlags flags
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 销毁缓冲池对象
      void vkDestroyDescriptorPool(
        VkDevice                     device,
        VkDescriptorPool             descriptorPool,
        const VkAllocationCallbacks* pAllocator
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 直接写入描述符集或者从另一个描述符集复制绑定,来把资源绑定到描述符集
      void vkUpdateDescriptorSets (
        VkDevice                    device,
        uint32_t                    descriptorWriteCount,
        const VkWriteDescriptorSet* pDescriptorWrites,
        //描述符复制的次数
        uint32_t                    descriptorCopyCount,
        const VkCopyDescriptorSet*  pDescriptorCopies
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • VkWriteDescriptorSet
      typedef struct VkWriteDescriptorSet 
      {
        //VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET 
        VkStructureType               sType;
        //nullptr
        const void*                   pNext;
        //目标描述符集
        VkDescriptorSet               dstSet;
        //绑定索引
        uint32_t                      dstBinding;
        //绑定资源类型的数组,更新起始的索引
        uint32_t                      dstArrayElement;
        //需要更新的连续描述符个数
        uint32_t                      descriptorCount;
        //更新的资源的类型
        VkDescriptorType              descriptorType;
        //图像资源
        const VkDescriptorImageInfo*  pImageInfo;
        //缓冲区资源
        const VkDescriptorBufferInfo* pBufferInfo;
        const VkBufferView*           pTexelBufferView;
      } VkWriteDescriptorSet;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • VkDescriptorImageInfo
      typedef struct VkDescriptorImageInfo 
      {
        VkSampler     sampler;
        //视图
        VkImageView   imageView;
        //所期望的布局
        VkImageLayout imageLayout;
      } VkDescriptorImageInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • VkDescriptorBufferInfo
      typedef struct VkDescriptorBufferInfo 
      {
        VkBuffer     buffer;
        VkDeviceSize offset;
        //如果是 uniform, 需要小于或者等于 maxUniformBufferRange 
        VkDeviceSize range;
      } VkDescriptorBufferInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • maxUniformBufferRangemaxStorageBufferRange 上限分别保证至少是 16384 与 2 27 2^{27} 227
    • minUniformBufferOffsetAlignmentminStorageBufferOffsetAlignment 要保证最多是 256 字节
    • VkCopyDescriptorSet
      typedef struct VkCopyDescriptorSet {
        //VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET 
        VkStructureType sType;
        //nullptr
        const void*     pNext;
        //源的描述符集的句柄
        VkDescriptorSet srcSet;
        //绑定索引
        uint32_t        srcBinding;
        //描述符数组
        uint32_t        srcArrayElement;
        //源的描述符集的句柄
        VkDescriptorSet dstSet;
        //绑定索引
        uint32_t        dstBinding;
        //描述符数组
        uint32_t        dstArrayElement;
        uint32_t        descriptorCount;
      } VkCopyDescriptorSet;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

    6.5.3 绑定描述符集

    • 把描述符集绑定到命令缓冲区
      void vkCmdBindDescriptorSets (
        VkCommandBuffer        commandBuffer,
        //VK_PIPELINE_BIND_POINT_COMPUTE
        //or
        //VK_PIPELINE_BIND_POINT_GRAPHICS
        VkPipelineBindPoint    pipelineBindPoint,
        //使用的管线布局
        VkPipelineLayout       layout,
        //管线布局可访问的集合的一个子集
        uint32_t               firstSet,
        uint32_t               descriptorSetCount,
        const VkDescriptorSet* pDescriptorSets,
        //动态 uniform 或着色器存储绑定的偏移量
        //动态偏移量的个数
        uint32_t               dynamicOffsetCount,
        //类型为 32 位偏移量的数组
        const uint32_t*        pDynamicOffsets
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    6.5.4 uniform、纹素和存储缓冲区

    • 着色器可以直接通过 3 种资源访问缓冲区内存的内容
      • uniform 块提供了对存储在缓冲区对象中常量(只读)数据的快速访问
      • 着色器块提供了对缓冲区对象的读写访问, 支持原子操作
      • 纹素缓冲区提供了对存储格式化纹素数据的长线性数组的访问能力
    uniform 和着色器块
    • 默认情况下
      • uniform 块使用 std140 规则
      • 着色器块使用 std430 规则
      layout (set = 0, binding = 1) uniform my_uniform_buffer_t
      {
        float foo;
        vec4  bar;
        int   baz[42];
      } my_uniform_buffer;
      
      layout (set = 0, binding = 2) buffer my_storage_buffer_t
      {
        int   peas;
        float carrots;
        vec3  potatoes[99];
      } my_storage_buffer; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    纹素缓冲区
    • 在数据读取时可进行格式转换
    • 纹素缓冲区是只读的
      layout (set = 0, binding = 3) uniform samplerBuffer  my_float_texel_buffer;
      layout (set = 0, binding = 4) uniform isamplerBuffer my_signed_texel_buffer;
      layout (set = 0, binding = 5) uniform usamplerBuffer my_unsigned_texel_buffer;
      
      • 1
      • 2
      • 3
    • 中纹素缓冲区要求的最小上限是 65535 个元素
    • 1D 纹素要求的最小尺寸是 4096 纹素

    6.5.5 推送常量

    • 不需要存储在内存里
      • 由 Vulkan 自身持有和更新
      • 的新值可以被直接从命令缓冲区推送到管线
    • 推送常量
      • VkPipelineLayoutCreateInfo
        • VkPushConstantRange
        typedef struct VkPushConstantRange 
        {
          //能看到常量的管线阶段
          VkShaderStageFlags stageFlags;
          uint32_t           offset;
          uint32_t           size;
        } VkPushConstantRange;
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • 向多个着色阶段传递一个常量也许要求广播它,并会消耗很多资源
    • GLSL 里声明推送常量
      layout (push_constant) uniform my_push_constants_t
      {
        int bourbon;
        int scotch;
        int beer;
      } my_push_constants;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 要更新一个或者多个推送常量
      void vkCmdPushConstants (
        VkCommandBuffer    commandBuffer,
        VkPipelineLayout   layout,
        //不更新没有包含的阶段来提升性能
        VkShaderStageFlags stageFlags,
        //第一个常量在虚拟块内的偏移量
        uint32_t           offset,
        //更新的量的大小
        uint32_t           size,
        //数据
        const void*        pValues
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 推送常量在逻辑上按照 std430 布局规则在内存中存储
    • 推送常量总共可用的空间至少是 128 字节
      • 对于两个 4×4 矩阵来说足够
    • 把推送常量当作稀缺资源
      • 优先使用正常的 uniform 块来存储大数据结构
      • 将推送常量用作整数或者更新非常频繁的数据

    6.5.6 采样图像

    • 当着色器从图像中读数据时,它们可以使用两种方式
      • 执行原始加载,从图像的指定位置直接读取格式化的或非格式化的数据
      • 使用采样器对图像采样
        • 采样可以包括如下操作
          • 在图像坐标上做基础变换,
          • 过滤纹素来向着色器返回光滑的图像数据
    • 创建采样器对象
      VkResult vkCreateSampler (
        VkDevice                     device,
        const VkSamplerCreateInfo*   pCreateInfo,
        const VkAllocationCallbacks* pAllocator,
        VkSampler*                   pSampler
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkSamplerCreateInfo
      typedef struct VkSamplerCreateInfo 
      {
        //VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO 
        VkStructureType      sType;
        //nullptr
        const void*          pNext;
        //0
        VkSamplerCreateFlags flags;
        //过滤模式
        //VK_FILTER_NEAREST
        //VK_FILTER_LINEAR
        VkFilter             magFilter;
        VkFilter             minFilter;
        //多重细节层
        //VK_SAMPLER_MIPMAP_MODE_NEAREST
        //VK_SAMPLER_MIPMAP_MODE_LINEAR
        VkSamplerMipmapMode  mipmapMode;
        //纹理坐标的变换方式
        //VK_SAMPLER_ADDRESS_MODE_REPEAT
        //VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT
        //VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE
        //VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER
        //VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE
        VkSamplerAddressMode addressModeU;
        VkSamplerAddressMode addressModeV;
        VkSamplerAddressMode addressModeW;
        //浮点型偏移量, 作用于 mipmap
        float                mipLodBias;
        //各向异性过滤
        //各向异性过滤通常在投影范围内做采样,而不是在固定的 2×2 范围内
        VkBool32             anisotropyEnable;
        //范围是 1.0 到设备允许的最大值
        //maxSamplerAnisotropy
        float                maxAnisotropy;
        //比较模式
        VkBool32             compareEnable;
        //ALWAYS
        //NEVER
        //LESS
        //LESS_OR_EQUAL
        //EQUAL
        //NOT_EQUAL
        //GREATER
        //GREATER_OR_EQUAL
        //Page 179
        VkCompareOp          compareOp;
        //在带有 mipmap 的图像中,采样器可配置成只在一个层级子集中采样
        //被限制的 mipmap 范围通过 minLod 和 maxLod 指定
        //要在整个 mipmap 链上做采样,设置 minLod 为 0.0,并设置 maxLod 为最高层
        float                minLod;
        float                maxLod;
        //
        VkBorderColor        borderColor;
        //当设置为 VK_TRUE 时,表示图像用于采样的坐标以原生纹素为单位,而不是 uv
        //使用限制
        //minFilter 和 magFilter 必须是相同的
        //mipmapMode 必须是 VK_SAMPLER_MIPMAP_MODE_NEAREST
        //anisotropyEnable 和 compareEnable 必须是 VK_FALSE
        VkBool32             unnormalizedCoordinates;
      } VkSamplerCreateInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
    • 一个设备上可以创建的采样器个数的上限取决于 Vulkan 实现。保证的是至少有 4000 个
      • VkPhysicalDeviceLimits
        • maxSamplerAllocationCount
    • 销毁采样器
      void vkDestroySampler (
        VkDevice                     device,
        VkSampler                    sampler,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5

    第7章 图形管线

    7.1 逻辑图形管线

    • 管线 (Page 181)
      • 绘制
      • 输入装配
      • 顶点着色器
      • 细分控制着色器
        • 产生细分因子和其他逐图片元(patch)数 据(被固定功能细分引擎使用)
      • 细分图元生成
        • 固定功能阶段使用在细分控制着色器中产生的 细分因子,来把图片元分解成许多更小的、更简单的图元,以供细分评估着色器使用
      • 细分评估着色器
        • 这个着色阶段运行在细分图元生成器产生的每一个顶点上。它和顶点着 色器的操作类似——除了输入顶点是生成的之外,而非从内存读取的
      • 几何着色器
      • 图元组装
      • 裁剪和剔除
      • 光栅器
      • 前置片段操作
        • 包括深度和模板测试(当开启了这两个测试时)。
      • 片段装配
        • 片段装配阶段接受光栅器的输出,以及任何逐片段数 据,将这些信息作为一组,发送给片段着色阶段
      • 片段着色器
      • 后置片段操作
        • 片段着色器会修改本应该在前置片段操作中使用的数据。 在这种情况下,这些前置片段操作转移到后置片段阶段中执行
      • 颜色混合
        • 颜色操作接受片段着色器和后置片段操作的结果,并使用它们更新帧缓冲区。 颜色操作包括混合与逻辑操作
    • 绘制命令
      void vkCmdDraw (
        VkCommandBuffer commandBuffer,
        //附加到管线的顶点的数量
        uint32_t        vertexCount,
        //1, 或实例绘制物体
        uint32_t        instanceCount,
        //可以从非零个顶点或者实例开始绘制
        uint32_t        firstVertex,
        uint32_t        firstInstance
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    7.2 渲染通道

    • 创建渲染通道对象
      VkResult vkCreateRenderPass (
        VkDevice                      device,
        const VkRenderPassCreateInfo* pCreateInfo,
        const VkAllocationCallbacks*  pAllocator,
        VkRenderPass*                 pRenderPass
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • VkRenderPassCreateInfo
      typedef struct VkRenderPassCreateInfo 
      {
        //VK_STRUCTURE_TYPE_RENDERPASS_CREATE_INFO 
        VkStructureType                sType;
        //nullptr
        const void*                    pNext;
        //0
        VkRenderPassCreateFlags        flags;
        uint32_t                       attachmentCount;
        //组定义和渲染通道关联的多个附件
        const VkAttachmentDescription* pAttachments;
        uint32_t                       subpassCount;
        const VkSubpassDescription*    pSubpasses;
        uint32_t                       dependencyCount;
        //依赖信息
        //当在一个渲染通道中有多个子通道时
        //Vulkan 可推算出一个附件依赖了哪些附件
        //这需要通过跟踪附件引用,并查找输入和输出完成
        //当无法自动跟踪时,使用该字段定义依赖信息
        const VkSubpassDependency*     pDependencies;
      } VkRenderPassCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • VkAttachmentDescription
      typedef struct VkAttachmentDescription 
      {
        //VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT 附件可能和同一个渲染通道引用的其他附件使用相同的内存
        //这告诉 Vulkan 不要做任何可能导致附件的数据不一致的事情
        //一般情况为 0
        VkAttachmentDescriptionFlags flags;
        VkFormat                     format;
        //图像中采样的次数
        //不使用多次采用:VK_SAMPLE_COUNT_1_BIT。
        VkSampleCountFlagBits        samples;
        //下面 4 个指定了在渲染通道的开始与结束时如何处理附件
        //VK_ATTACHMENT_LOAD_OP_LOAD 表示附件里已经有数据了,仍想继续对它进行渲染
        //VK_ATTACHMENT_LOAD_OP_CLEAR 在渲染通道开始时清除附件的内容
        //VK_ATTACHMENT_LOAD_OP_DONT_CARE 渲染通道开始时不关心附件的内容
        VkAttachmentLoadOp           loadOp;
        //VK_ATTACHMENT_STORE_OP_STORE 让 Vulkan 保留附件的内容以供稍后使用
        //VK_ATTACHMENT_STORE_OP_DONT_CARE 在渲染通道结束后不需要附件的内容
        VkAttachmentStoreOp          storeOp;
        VkAttachmentLoadOp           stencilLoadOp;
        VkAttachmentStoreOp          stencilStoreOp;
        //渲染通道开始期望图像是什么布局
        VkImageLayout                initialLayout;
        //在渲染通道结束时期望图像是什么布局
        VkImageLayout                finalLayout;
      } VkAttachmentDescription; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    • 定义子通道
      typedef struct VkSubpassDescription 
      {
        //0
        VkSubpassDescriptionFlags    flags;
        //现阶段为 VK_PIPELINE_BIND_POINT_GRAPHICS
        VkPipelineBindPoint          pipelineBindPoint;
        //个、输入附件,可以从中读出数据
        uint32_t                     inputAttachmentCount;
        const VkAttachmentReference* pInputAttachments;
        uint32_t                     colorAttachmentCount;
        //颜色附件是写入输出数据的附件
        const VkAttachmentReference* pColorAttachments;
        //解析附件是对多重采样图像数据进行解析后存储的附件
        const VkAttachmentReference* pResolveAttachments;
        const VkAttachmentReference* pDepthStencilAttachment;
        uint32_t                     preserveAttachmentCount;
        //如果希望附件的生存周期跨越一个子通道但并不被子通道直接引用
        //该引用将阻止 Vulkan 进行任何可能改动这些附件内容的优化
        const uint32_t*              pPreserveAttachments;
      } VkSubpassDescription; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    • VkAttachmentReference
      typedef struct VkAttachmentReference 
      {
        //附件数组的索引
        uint32_t      attachment;
        //图像布局
        VkImageLayout layout;
      } VkAttachmentReference;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 单个子通道可以渲染输出的颜色附件的最大个数
      • maxColorAttachments
      • 保证最少支持 4 个
    • VkSubpassDependency
      typedef struct VkSubpassDependency 
      {
        //源子通道
        uint32_t             srcSubpass;
        //目标子通道
        uint32_t             dstSubpass;
        //指定了源子通道的哪些管线阶段产生数据
        VkPipelineStageFlags srcStageMask;
        //指定了目标子通道的哪些管线阶段使用数据
        VkPipelineStageFlags dstStageMask;
        //如何访问数据
        VkAccessFlags        srcAccessMask;
        VkAccessFlags        dstAccessMask;
        VkDependencyFlags    dependencyFlags;
      } VkSubpassDependency;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • 销毁渲染通道
      void vkDestroyRenderPass (
        VkDevice                     device,
        VkRenderPass                 renderPass,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5

    7.3 帧缓冲区

    • 帧缓冲区影响管线的最后几个阶段
      • 深度和模板测试
      • 混合
      • 逻辑操作
      • 多采样
    • 帧缓冲区对象通过使用渲染通道的引用来创建
      • 可以和任何有类似的附件排布方式的渲染通道一同使用
    • 创建
      VkResult vkCreateFramebuffer (
        VkDevice                       device,
        const VkFramebufferCreateInfo* pCreateInfo,
        //如果要求使用主机内存,就将会用到 pAllocator 所指向的分配器
        const VkAllocationCallbacks*   pAllocator,
        VkFramebuffer*                 pFramebuffer
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • VkFramebufferCreateInfo
      typedef struct VkFramebufferCreateInfo 
      {
        //VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
        VkStructureType sType;
        //nullptr
        const void* pNext;
        //0
        VkFramebufferCreateFlags flags;
        VkRenderPass renderPass;
        //数组的长度
        uint32_t attachmentCount;
        const VkImageView* pAttachments;
        //必须指定帧缓冲区的维度
        uint32_t width;
        uint32_t height;
        uint32_t layers;
      } VkFramebufferCreateInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 支持的帧缓冲区最大尺寸依赖于设备
      • VkPhysicalDeviceLimits
        • maxFramebufferWidth
        • maxFramebufferHeight
        • maxFramebufferLayers
      • vulkan 标准保证最小支持的宽度和高度是 4096 像素
      • 层数至少为 256
      • 大多数桌面级硬件支持 16384 像素的宽度和高度
      • 2048 层
    • 无附件帧缓冲区
      • 存储图像
      • 遮挡查询
      • 无须在任何地方存储渲染结果
    • 销毁帧缓冲
      void vkDestroyFramebuffer (
        VkDevice                     device,
        VkFramebuffer                framebuffer,
        const VkAllocationCallbacks* pAllocator
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 销毁帧缓冲区对象并不影响附着到它上面的图像
    • 图像可以同时附着到多个帧缓冲区上

    7.4 创建一个简单的图形管线

    • 创建图形管线
      VkResult vkCreateGraphicsPipelines (
        VkDevice                            device,
        //管线缓存
        VkPipelineCache                     pipelineCache,
        uint32_t                            createInfoCount,
        const VkGraphicsPipelineCreateInfo* pCreateInfos,
        const VkAllocationCallbacks*        pAllocator,
        VkPipeline*                         pPipelines
      );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • VkGraphicsPipelineCreateInfo
      typedef struct VkGraphicsPipelineCreateInfo 
      {
        //VK_GRAPHICS_PIPELINE_CREATE_INFO
        VkStructureType                               sType;
        //nullptr,可以使用扩展
        const void*                                   pNext;
        //管线如何使用的信息
        VkPipelineCreateFlags                         flags;
        uint32_t                                      stageCount;
        //把着色器传递到管线的目标位置
        //描述了一个着色阶段
        const VkPipelineShaderStageCreateInfo*        pStages;
        //顶点输入状态
        const VkPipelineVertexInputStateCreateInfo*   pVertexInputState;
        //输入组装接受顶点数据
        const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState;
        //细分状态
        const VkPipelineTessellationStateCreateInfo*  pTessellationState;
        //视口状态
        const VkPipelineViewportStateCreateInfo*      pViewportState;
        //光栅化状态
        const VkPipelineRasterizationStateCreateInfo* pRasterizationState;
        //多重采样状态
        const VkPipelineMultisampleStateCreateInfo*   pMultisampleState;
        //深度和模板状态
        const VkPipelineDepthStencilStateCreateInfo*  pDepthStencilState;
        //颜色混合状态
        const VkPipelineColorBlendStateCreateInfo*    pColorBlendState;
        //动态状态
        const VkPipelineDynamicStateCreateInfo*       pDynamicState;
        VkPipelineLayout                              layout;
        VkRenderPass                                  renderPass;
        uint32_t                                      subpass;
        VkPipeline                                    basePipelineHandle;
        int32_t                                       basePipelineIndex;
      } VkGraphicsPipelineCreateInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
    • VkPipelineCreateFlags
      • VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT
        • 管线将不会在性能严苛的程序中使用
      • VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT 和 VK_PIPELINE_CREATE_DERIVATIVE_BIT
        • 衍生管线
        • 可把类似的管线分为一组, 告诉 Vulakn 你将在它们之间快速切换
        • VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT
          • 为这个新管线创建衍生管线
        • VK_PIPELINE_CREATE_DERIVATIVE_ BIT
          • 告诉 Vulkan 这个管线就是一个管线

    7.4.1 图形着色器阶段

    • VkPipelineShaderStageCreateInfo
      typedef struct VkPipelineShaderStageCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
        VkStructureType                  sType;
        //nullptr
        const void*                      pNext;
        //0
        VkPipelineShaderStageCreateFlags flags;
        VkShaderStageFlagBits            stage;
        VkShaderModule                   module;
        const char*                      pName;
        const VkSpecializationInfo*      pSpecializationInfo;
      } VkPipelineShaderStageCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 管线最多由 5 个着色器阶段组成
      • 顶点着色器
        • VK_SHADER_STAGE_VERTEX_BIT
      • 细分控制着色器
        • VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT
      • 细分评估着色器
        • VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT
      • 几何着色器
        • VK_SHADER_STAGE_GEOMETRY_BIT
      • 片段着色器
        • VK_SHADER_STAGE_FRAGMENT_BIT

    7.4.2 顶点输入状态

    • VertexInputStateCreateInfo
      typedef struct VkPipelineVertexInputStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
        VkStructureType                          sType;
        //nullptr
        const void*                              pNext;
        //0
        VkPipelineVertexInputStateCreateFlags    flags;
        //是管线使用的顶点绑定的个数
        uint32_t                                 vertexBindingDescriptionCount;
        const VkVertexInputBindingDescription*   pVertexBindingDescriptions;
        uint32_t                                 vertexAttributeDescriptionCount;
        //顶点属性都
        const VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
      } VkPipelineVertexInputStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • VkVertexInputBindingDescription
      typedef struct VkVertexInputBindingDescription 
      {
        //结构体描述的绑定的索引
        uint32_t          binding;
        //数组的步长
        uint32_t          stride;
        //遍历数组的方式
        //实例索引 VK_VERTEX_INPUT_RATE_VERTEX
        //顶点索引 VK_VERTEX_INPUT_RATE_INSTANCE。
        VkVertexInputRate inputRate;
      } VkVertexInputBindingDescription;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • VkVertexInputBindingDescription 数组声明的最后一个绑定索引必须比设备支持的最大绑定数要小
      • Vulkan 标准保证最少支持 16 个
      • VkPhysicalDeviceLimits
        • maxVertexInputBindings
    • stride 的最大值都是由 Vulkan 实现决定的,
      • Vulkan 标准保证至少是 2048 字节
      • VkPhysicalDeviceLimits
        • maxVertexInputBindingStride
    • VkVertexInputAttributeDescription
      typedef struct VkVertexInputAttributeDescription 
      {
        //属性的位置
        uint32_t location;
        //绑定缓冲区的位置
        uint32_t binding;
        //顶点数据的格式
        VkFormat format;
        //每个数据的偏移量
        uint32_t offset;
      } VkVertexInputAttributeDescription;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 每一个属性在数据结构内的偏移量也有上限
      • Vulkan 标准保证至少是 2047 字节
      • VkPhysicalDeviceLimits
        • maxVertexInput

    7.4.3 输入组装

    • VkPipelineInputAssemblyStateCreateInfo
      typedef struct VkPipelineInputAssemblyStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
        VkStructureType                         sType;
        //nullptr
        const void*                             pNext;
        //0
        VkPipelineInputAssemblyStateCreateFlags flags;
        //图元拓扑类型
        VkPrimitiveTopology                     topology;
        //允许条带与扇形被切除和重启
        VkBool32                                primitiveRestartEnable;
      } VkPipelineInputAssemblyStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • VkPrimitiveTopology
      • VK_PRIMITIVE_TOPOLOGY_POINT_LIST
        • 每一个顶点用来构造一个独立的点
      • VK_PRIMITIVE_TOPOLOGY_LINE_LIST
        • 顶点以成对的形式分组,每一对形成一条线段
      • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
        • 把每 3 个顶点分为一组,组成一个三角形
      • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP
        • 前两个顶点构成一条线段
        • 每一个新的顶点连接前一个处理的顶点构成一条新的线段
      • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
        • 前 3 个顶点构成一个三角形
        • 每一个接下来的顶点和前面处理的两个顶点构成一个新的三角形
      • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN
        • 前 3 个顶点构成一个三角形
        • 每个接下来的顶点和上一个顶点、本次绘制的第一个顶点构成新的三角形
      • VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY
        • 一次绘制中每 4 个顶点构成单个图元
        • 中间两个顶点构成线段
        • 把第一个和最后一个顶点传递给几何着色器
      • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY
        • 一次绘制中 4 个顶点构成单个图元
        • 中间的两个顶点构成线段
        • 第一个和最后一个顶点被当作邻接信息传递给几何着色器
      • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY
        • 每组 6 个顶点构成单个图元
        • 每组的第 1 个、第 3 个、第 5 个构成一个三角形
        • 第 2 个、第 4 个、第 6 个作为邻接信息传递给几何着色器
      • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY
        • 以前 6 个顶点作为开始的条带构成了一个带有邻接信息的三角形
        • 每两个新顶点构成一个新的三角形
        • 奇数序号的顶点构成三角形,偶数序号的顶点提供邻接信息

    7.4.4 细分状态

    • VkPipelineTessellationStateCreateInfo
      typedef struct VkPipelineTessellationStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO
        VkStructureType                        sType;
        //nullptr
        const void*                            pNext;
        //0
        VkPipelineTessellationStateCreateFlags flags;
        //设置了将分组到一个图元的控制点的个数
        uint32_t                               patchControlPoints;
      } VkPipelineTessellationStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

    7.4.5 视口状态

    • VkPipelineViewportStateCreateInfo
      typedef struct VkPipelineViewportStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
        VkStructureType                    sType;
        //nullptr
        const void*                        pNext;
        //0
        VkPipelineViewportStateCreateFlags flags;
        //视口的个数
        uint32_t                           viewportCount;
        //每一个视口的尺寸
        const VkViewport*                  pViewports;
        uint32_t                           scissorCount;
        //设置裁剪矩形
        const VkRect2D*                    pScissors;
      } VkPipelineViewportStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 视口变换是 Vulkan 管线中在栅格化之前的最后一个坐标变换
    • 如果支持多视口,下限是 16 个
      • VkPhysicalDeviceLimits
        • maxViewports

    7.4.6 光栅化状态

    • VkPipelineRasterizationStateCreateInfo
      typedef struct VkPipelineRasterizationStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO 
        VkStructureType                         sType;
        //nullptr
        const void*                             pNext;
        //0
        VkPipelineRasterizationStateCreateFlags flags;
        //开启或关闭深度夹持(哈哈)
        //本该被远近平面裁剪掉的片段投射到这些平面上
        //可用来填充因为裁剪造成的空洞
        VkBool32                                depthClampEnable;
        //关闭光栅化,光栅器将不会运行,将不会产生图元
        VkBool32                                rasterizerDiscardEnable;
        //自动地把三角形转化为点或者直线
        VkPolygonMode                           polygonMode;
        //剔除
        //VK_CULL_MODE_FRONT_BIT
        //VK_CULL_MODE_BACK_BIT
        VkCullModeFlags                         cullMode;
        //三角形的朝向由顶点的绕序
        //VK_FRONT_FACE_COUNTER_CLOCKWISE
        //VK_FRONT_FACE_CLOCKWISE
        VkFrontFace                             frontFace;
        //下面四个参数控制了深度偏移这一特性
        VkBool32                                depthBiasEnable;
        float                                   depthBiasConstantFactor;
        float                                   depthBiasClamp;
        float                                   depthBiasSlopeFactor;
        //置线段图元的宽度
        //一些 Vulkan 实现并不支持宽线段,会忽略这个字段
        //设置为 1.0 以外的值时也许会运行得相当缓慢
        //建议设置为 1 不变
        //线段的最大宽度,Vulkan 标准保证至少支持 8 像素
        float                                   lineWidth;
      } VkPipelineRasterizationStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
    • VkPolygonMode
      • VK_POLYGON_MODE_FILL
        • 三角形将会被绘制为实心的,内部的每一个点都会创建一个片段
      • VK_POLYGON_MODE_LINE
        • 每一个三角形的每一条边都变为线段
        • 绘制几何物体的线框模式时这个模式很有用
      • VK_POLYGON_MODE_POINT
        • 每一个顶点绘制为一个点

    7.4.7 多重采样状态

    • VkPipelineMultisampleStateCreateInfo
      typedef struct VkPipelineMultisampleStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO 
        VkStructureType                       sType;
        //nullptr
        const void*                           pNext;
        //0
        VkPipelineMultisampleStateCreateFlags flags;
        VkSampleCountFlagBits                 rasterizationSamples;
        VkBool32                              sampleShadingEnable;
        float                                 minSampleShading;
        const VkSampleMask*                   pSampleMask;
        VkBool32                              alphaToCoverageEnable;
        VkBool32                              alphaToOneEnable;
      } VkPipelineMultisampleStateCreateInfo; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • 当进行多重采样时,颜色和深度-模板附件必须是多重采样图像

    7.4.8 深度和模板状态

    • 深度测试在片段着色器运行之后发生

    • 要在深度测试之前运行片段着色器,可以在片段着色器入口设置 SPIR-V EarlyFragmentTests 执行模式

    • VkPipelineDepthStencilStateCreateInfo

      typedef struct VkPipelineDepthStencilStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_CREATE_INFO 
        VkStructureType                        sType;
        //nullptr
        const void*                            pNext;
        //0
        VkPipelineDepthStencilStateCreateFlags flags;
        //深度测试启用 第 10 章
        VkBool32                               depthTestEnable;
        VkBool32                               depthWriteEnable;
        VkCompareOp                            depthCompareOp;
        VkBool32                               depthBoundsTestEnable;
        VkBool32                               stencilTestEnable;
        VkStencilOpState                       front;
        VkStencilOpState                       back;
        float                                  minDepthBounds;
        float                                  maxDepthBounds;
      } VkPipelineDepthStencilStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 深度和模板测试可以在片段着色器运行之前或之后进行。默认情况下,深度测试在片段着 色器运行之后发生

    7.4.9 颜色混合状态

    • 这个阶段负责把片段写入颜色附件
    • VkPipelineColorBlendStateCreateInfo
      typedef struct VkPipelineColorBlendStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO 
        VkStructureType                            sType;
        //nullptr
        const void*                                pNext;
        //0
        VkPipelineColorBlendStateCreateFlags       flags;
        //色器的输出和颜色附件的内容之间是否进行逻辑操作 第十章
        VkBool32                                   clogicOpEnable;
        VkLogicOp                                  logicOp;
        uint32_t                                   attachmentCount;
        const VkPipelineColorBlendAttachmentState* pAttachments;
        float                                      blendConstants[4];
      } VkPipelineColorBlendStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • VkPipelineColorBlendAttachmentState
      //第 10 章
      typedef struct VkPipelineColorBlendAttachmentState 
      {
        VkBool32              blendEnable;
        VkBlendFactor         srcColorBlendFactor;
        VkBlendFactor         dstColorBlendFactor;
        VkBlendOp             colorBlendOp;
        VkBlendFactor         srcAlphaBlendFactor;
        VkBlendFactor         dstAlphaBlendFactor;
        VkBlendOp             alphaBlendOp;
        //控制了把输出图像的哪个通道写入附件中
        VkColorComponentFlags colorWriteMask;
      } VkPipelineColorBlendAttachmentState;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    7.5 动态状态

    • VkPipelineDynamicStateCreateInfo
      typedef struct VkPipelineDynamicStateCreateInfo 
      {
        //VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
        VkStructureType                   sType;
        //nullptr
        const void*                       pNext;
        //为 0
        VkPipelineDynamicStateCreateFlags flags;
        //动态状态的个数
        uint32_t                          dynamicStateCount;
        //想要使用对应的动态状态设置命令来改变状态
        const VkDynamicState*             pDynamicStates;
      } VkPipelineDynamicStateCreateInfo;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • VkDynamicState
      • VK_DYNAMIC_STATE_VIEWPOR
        • 视口矩形是动态的
        • 使用 vkCmdSetViewport() 更新
      • VK_DYNAMIC_STATE_SCISSOR
        • 裁剪矩形是动态的
        • vkCmdSetScissor()
      • VK_DYNAMIC_STATE_LINE_WIDTH
        • 线段宽度是动态的
        • vkCmdSetLineWidth()
      • VK_DYNAMIC_STATE_DEPTH_BIAS
        • 深度偏移参数是动态的
        • vkCmdSetDepthBias()
      • VK_DYNAMIC_STATE_BLEND_CONSTANTS
        • 颜色混合常量是动态的
        • vkCmdSetBlendConstants()
      • VK_DYNAMIC_STATE_DEPTH_BOUNDS
        • 深度界限参数是动态的
        • vkCmdSetDepthBounds()
      • VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK、VK_DYNAMIC_STATE_STENCIL_WRITE_MASK 和 VK_DYNAMIC_STATE_STENCIL_REFERENCE
        • 对应的模板参数是动态的
        • vkCmdSetStencilCompareMask()
        • vkCmdSetStencilWriteMask()
        • vkCmdSetStencilReference()
    • 动态和静态状态的有效性
      • Page 211
    • 最佳实践是,首先绑定管线,然后绑定任何相关状态,以避免潜在的未定义行为发生
  • 相关阅读:
    【架构与设计】常见微服务分层架构的区别和落地实践
    使用VMware安装系统Window、Linux操作系统
    460. LFU 缓存
    IT项目管理成功的技巧通常有哪些?
    redis基础2——key的基础使用、五大数据类型和三大特殊数据类型
    【Redis】一文掌握Redis原理及常见问题
    【USB电压电流表】基于STM32F103C8T6 for Arduino
    汽车出海标配DMS/OMS?座舱监测赛道开启黄金窗口期
    关于JSON对象和前端js对象
    Spring复习:(63)doCreateBean中BeanPostProcessor的调用顺序
  • 原文地址:https://blog.csdn.net/jiamada/article/details/127703364