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

28 篇文章 12 订阅

第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
    );
    
  • 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;
    
  • 销毁并释放资源
    void vkDestroyShaderModule (
      VkDevice                     device,
      VkShaderModule               shaderModule,
      const VkAllocationCallbacks* pAllocator
    );
    

6.3 管线

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

6.3.1 计算管线

  • 本地工作组和全局工作组都是三维的。
  • 本地工作组的尺寸在计算着色器内部设置。在 GLSL 中,使用 layout 限定符
    layout (local_size_x = 4, local_size_y = 5, local_size_z 6) in;
    
  • 计算着色器的本地工作组的最大尺寸一般来说比较小,仅仅要求 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
    );
    
  • 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;
    
  • 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; 
    

6.3.3 特化常量

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

6.3.4 加速管线的创建

  • 创建管线可能是应用程序开销最大的操作之一
  • 管线缓存
    VkResult vkCreatePipelineCache (
      VkDevice                         device,
      const VkPipelineCacheCreateInfo* pCreateInfo,
      const VkAllocationCallbacks *    pAllocator,
      VkPipelineCache*                 pPipelineCache
    );
    
  • 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;
    
  • 从缓存中取出数据
    VkResult vkGetPipelineCacheData (
      VkDevice        device,
      VkPipelineCache pipelineCache,
      //内存区域的大小
      size_t*         pDataSize,
      //缓存数据的内存区域
      void*           pData
    );
    
  • 可以调用 vkGetPipelineCacheData() 两次来存储所有的缓存数据
  • 将管线缓存数据保存到文件中
    • Page 149
  • 缓存数据文件头的定义
    • Page 149
  • 合并两个缓存对象
    • 在多个线程中创建管线时,特别有用
    VkResult vkMergePipelineCaches (
      VkDevice               device,
      //目标缓存的句柄
      VkPipelineCache        dstCache,
      //待融合缓存的个数
      uint32_t               srcCacheCount,
      const VkPipelineCache* pSrcCaches
    ); 
    
  • 销毁管线缓存对象
    void vkDestroyPipelineCache (
      VkDevice                     device,
      VkPipelineCache              pipelineCache,
      const VkAllocationCallbacks* pAllocator
    ); 
    

6.3.5 绑定管线

  • 把管线绑定到一个命令缓冲区
    void vkCmdBindPipeline (
      VkCommandBuffer     commandBuffer,
      VkPipelineBindPoint pipelineBindPoint,
      VkPipeline          pipeline
    ); 
    
  • 每一个命令缓冲区上有两个管线绑定点
    • 图形绑定点
      • 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
    ); 
    
  • 间接分发
    • 在工作组中分发的个数来自缓冲区对象
    • 允许分发大小在命令缓冲区构建之后计算
      • 使用一个缓冲区来间接分发,然后用主机重写缓冲区里的内容
      void vkCmdDispatchIndirect (
        VkCommandBuffer commandBuffer,
        //工作组存储在 3 个连续的 uint32_t 类型的变量
        VkBuffer        buffer,
        //偏移量
        VkDeviceSize    offset
      );
      
    • 缓冲区中的参数实质上代表了一个结构体
      typedef struct VkDispatchIndirectCommand 
      {
        uint32_t x;
        uint32_t y;
        uint32_t z;
      } VkDispatchIndirectCommand; 
      

6.5 在着色器中访问资源

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

6.5.1 描述符集

  • 描述符集是作为整体绑定到管线的资源的集合
    • 可以同时将多个集合绑定到一个管线
    • 每一个集合都有一个布局
      • 布局描述了集合中资源的排列顺序和类型
      • 两个拥有相同布局的集合被视为兼容的和可相互交换的
  • 管线布局
    • 可被管线访问的集合的集合组成的对象
    • 管线通过参照这个管线布局对象来创建
  • 对于描述符集兼容的两个管线布局,它们必须符合如下两点
    • 使用相同的推送常量范围
    • 按照相同顺序使用相同的描述符集布局 (或等同的布局)
  • 符集布局和管线布局之间的关系
    • Page 153
  • 创建描述符集布局对象
    VkResult vkCreateDescriptorSetLayout (
      VkDevice                               device,
      const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
      const VkAllocationCallbacks*           pAllocator,
      VkDescriptorSetLayout*                 pSetLayout
    );
    
  • 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;
    
  • VkDescriptorSetLayoutBinding
    typedef struct VkDescriptorSetLayoutBinding 
    {
      //每个着色器可访问的资源都具有一个绑定序号
      //
      uint32_t           binding;
      //绑定点的描述符的类型
      VkDescriptorType   descriptorType;
      uint32_t           descriptorCount;
      VkShaderStageFlags stageFlags;
      const VkSampler*   pImmutableSamplers;
    } VkDescriptorSetLayoutBinding;
    
  • 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
    );
    
  • 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;
    
  • 一次可绑定的描述符集的最大数至少为 4
    • vkGetPhysicalDeviceProperties()
      • VkPhysicalDeviceLimits
        • maxBoundDescriptorSets
  • 管线资源限制表格
    • Page 158
  • 如果两个管线布局对于前面几个集合使用相同(或者等同的)的集合布局,而对于后面的集合使用不相同的集合布局,那么认为这两个管线布局是部分兼容的
  • 在两个部分兼容的管线之间切换时,无须重新绑定任何集合,直到管线共享布局的地方
  • 如果你有一系列的资源并想在全局范围内访问
    • 比如包含每帧所需常量的 uniform 块
    • 或者在每个着色器都需要访问的纹理
    • 就把这些资源放在第一个集合里
    • 频繁改变的资源可以放到高序号的集合里
  • 销毁管线布局
    void vkDestroyPipelineLayout (
      VkDevice                     device,
      VkPipelineLayout             pipelineLayout,
      const VkAllocationCallbacks* pAllocator
    );
    
  • 销毁描述符集布局对象
    void vkDestroyDescriptorSetLayout (
      VkDevice                     device,
      VkDescriptorSetLayout        descriptorSetLayout,
      const VkAllocationCallbacks* pAllocator
    );
    

6.5.2 绑定资源到描述符集

  • 资源是通过描述符表示的,绑定到管线上的顺序是
    • 先把描述符绑定到集合上
    • 然后把描述符集绑定到管线
  • 创建描述符缓存池
    VkResult vkCreateDescriptorPool (
      VkDevice                          device,
      const VkDescriptorPoolCreateInfo* pCreateInfo,
      const VkAllocationCallbacks*      pAllocator,
      VkDescriptorPool*                 pDescriptorPool
    );
    
  • 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;
    
  • VkDescriptorPoolSize
    typedef struct VkDescriptorPoolSize 
    {
      //资源的类型
      VkDescriptorType type;
      //池中该种类型资源的个数
      uint32_t         descriptorCount;
    } VkDescriptorPoolSize;
    
  • 创建描述符集对象
    VkResult vkAllocateDescriptorSets (
      VkDevice                           device,
      const VkDescriptorSetAllocateInfo* pAllocateInfo,
      VkDescriptorSet*                   pDescriptorSets
    );
    
  • 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;
    
  • 释放一个或者多个描述符集
    VkResult vkFreeDescriptorSets (
      VkDevice               device,
      VkDescriptorPool       descriptorPool,
      uint32_t               descriptorSetCount,
      const VkDescriptorSet* pDescriptorSets
    );
    
  • 重置缓存池
    VkResult vkResetDescriptorPool (
      VkDevice                   device,
      VkDescriptorPool           descriptorPool,
      //为 0
      VkDescriptorPoolResetFlags flags
    );
    
  • 销毁缓冲池对象
    void vkDestroyDescriptorPool(
      VkDevice                     device,
      VkDescriptorPool             descriptorPool,
      const VkAllocationCallbacks* pAllocator
    ); 
    
  • 直接写入描述符集或者从另一个描述符集复制绑定,来把资源绑定到描述符集
    void vkUpdateDescriptorSets (
      VkDevice                    device,
      uint32_t                    descriptorWriteCount,
      const VkWriteDescriptorSet* pDescriptorWrites,
      //描述符复制的次数
      uint32_t                    descriptorCopyCount,
      const VkCopyDescriptorSet*  pDescriptorCopies
    );
    
  • 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;
    
  • VkDescriptorImageInfo
    typedef struct VkDescriptorImageInfo 
    {
      VkSampler     sampler;
      //视图
      VkImageView   imageView;
      //所期望的布局
      VkImageLayout imageLayout;
    } VkDescriptorImageInfo;
    
  • VkDescriptorBufferInfo
    typedef struct VkDescriptorBufferInfo 
    {
      VkBuffer     buffer;
      VkDeviceSize offset;
      //如果是 uniform, 需要小于或者等于 maxUniformBufferRange 
      VkDeviceSize range;
    } VkDescriptorBufferInfo; 
    
  • 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;
    

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
    );
    

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; 
    
纹素缓冲区
  • 在数据读取时可进行格式转换
  • 纹素缓冲区是只读的
    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;
    
  • 中纹素缓冲区要求的最小上限是 65535 个元素
  • 1D 纹素要求的最小尺寸是 4096 纹素

6.5.5 推送常量

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

6.5.6 采样图像

  • 当着色器从图像中读数据时,它们可以使用两种方式
    • 执行原始加载,从图像的指定位置直接读取格式化的或非格式化的数据
    • 使用采样器对图像采样
      • 采样可以包括如下操作
        • 在图像坐标上做基础变换,
        • 过滤纹素来向着色器返回光滑的图像数据
  • 创建采样器对象
    VkResult vkCreateSampler (
      VkDevice                     device,
      const VkSamplerCreateInfo*   pCreateInfo,
      const VkAllocationCallbacks* pAllocator,
      VkSampler*                   pSampler
    ); 
    
  • 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; 
    
  • 一个设备上可以创建的采样器个数的上限取决于 Vulkan 实现。保证的是至少有 4000 个
    • VkPhysicalDeviceLimits
      • maxSamplerAllocationCount
  • 销毁采样器
    void vkDestroySampler (
      VkDevice                     device,
      VkSampler                    sampler,
      const VkAllocationCallbacks* pAllocator
    );
    

第7章 图形管线

7.1 逻辑图形管线

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

7.2 渲染通道

  • 创建渲染通道对象
    VkResult vkCreateRenderPass (
      VkDevice                      device,
      const VkRenderPassCreateInfo* pCreateInfo,
      const VkAllocationCallbacks*  pAllocator,
      VkRenderPass*                 pRenderPass
    );
    
  • 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;
    
  • 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; 
    
  • 定义子通道
    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; 
    
  • VkAttachmentReference
    typedef struct VkAttachmentReference 
    {
      //附件数组的索引
      uint32_t      attachment;
      //图像布局
      VkImageLayout layout;
    } VkAttachmentReference;
    
  • 单个子通道可以渲染输出的颜色附件的最大个数
    • maxColorAttachments
    • 保证最少支持 4 个
  • VkSubpassDependency
    typedef struct VkSubpassDependency 
    {
      //源子通道
      uint32_t             srcSubpass;
      //目标子通道
      uint32_t             dstSubpass;
      //指定了源子通道的哪些管线阶段产生数据
      VkPipelineStageFlags srcStageMask;
      //指定了目标子通道的哪些管线阶段使用数据
      VkPipelineStageFlags dstStageMask;
      //如何访问数据
      VkAccessFlags        srcAccessMask;
      VkAccessFlags        dstAccessMask;
      VkDependencyFlags    dependencyFlags;
    } VkSubpassDependency;
    
  • 销毁渲染通道
    void vkDestroyRenderPass (
      VkDevice                     device,
      VkRenderPass                 renderPass,
      const VkAllocationCallbacks* pAllocator
    );
    

7.3 帧缓冲区

  • 帧缓冲区影响管线的最后几个阶段
    • 深度和模板测试
    • 混合
    • 逻辑操作
    • 多采样
  • 帧缓冲区对象通过使用渲染通道的引用来创建
    • 可以和任何有类似的附件排布方式的渲染通道一同使用
  • 创建
    VkResult vkCreateFramebuffer (
      VkDevice                       device,
      const VkFramebufferCreateInfo* pCreateInfo,
      //如果要求使用主机内存,就将会用到 pAllocator 所指向的分配器
      const VkAllocationCallbacks*   pAllocator,
      VkFramebuffer*                 pFramebuffer
    );
    
  • 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; 
    
  • 支持的帧缓冲区最大尺寸依赖于设备
    • VkPhysicalDeviceLimits
      • maxFramebufferWidth
      • maxFramebufferHeight
      • maxFramebufferLayers
    • vulkan 标准保证最小支持的宽度和高度是 4096 像素
    • 层数至少为 256
    • 大多数桌面级硬件支持 16384 像素的宽度和高度
    • 2048 层
  • 无附件帧缓冲区
    • 存储图像
    • 遮挡查询
    • 无须在任何地方存储渲染结果
  • 销毁帧缓冲
    void vkDestroyFramebuffer (
      VkDevice                     device,
      VkFramebuffer                framebuffer,
      const VkAllocationCallbacks* pAllocator
    );
    
  • 销毁帧缓冲区对象并不影响附着到它上面的图像
  • 图像可以同时附着到多个帧缓冲区上

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

  • 创建图形管线
    VkResult vkCreateGraphicsPipelines (
      VkDevice                            device,
      //管线缓存
      VkPipelineCache                     pipelineCache,
      uint32_t                            createInfoCount,
      const VkGraphicsPipelineCreateInfo* pCreateInfos,
      const VkAllocationCallbacks*        pAllocator,
      VkPipeline*                         pPipelines
    );
    
  • 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; 
    
  • 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;
    
  • 管线最多由 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;
    
  • VkVertexInputBindingDescription
    typedef struct VkVertexInputBindingDescription 
    {
      //结构体描述的绑定的索引
      uint32_t          binding;
      //数组的步长
      uint32_t          stride;
      //遍历数组的方式
      //实例索引 VK_VERTEX_INPUT_RATE_VERTEX
      //顶点索引 VK_VERTEX_INPUT_RATE_INSTANCE。
      VkVertexInputRate inputRate;
    } VkVertexInputBindingDescription;
    
  • 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;
    
  • 每一个属性在数据结构内的偏移量也有上限
    • 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;
    
  • 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;
    

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;
    
  • 视口变换是 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;
    
  • 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; 
    
  • 当进行多重采样时,颜色和深度-模板附件必须是多重采样图像

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;
    
  • 深度和模板测试可以在片段着色器运行之前或之后进行。默认情况下,深度测试在片段着 色器运行之后发生

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;
    
  • VkPipelineColorBlendAttachmentState
    //第 10 章
    typedef struct VkPipelineColorBlendAttachmentState 
    {
      VkBool32              blendEnable;
      VkBlendFactor         srcColorBlendFactor;
      VkBlendFactor         dstColorBlendFactor;
      VkBlendOp             colorBlendOp;
      VkBlendFactor         srcAlphaBlendFactor;
      VkBlendFactor         dstAlphaBlendFactor;
      VkBlendOp             alphaBlendOp;
      //控制了把输出图像的哪个通道写入附件中
      VkColorComponentFlags colorWriteMask;
    } VkPipelineColorBlendAttachmentState;
    

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;
    
  • 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
  • 最佳实践是,首先绑定管线,然后绑定任何相关状态,以避免潜在的未定义行为发生
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值