闭关之 Vulkan 应用开发指南笔记(二):队列、命令、移动数据和展示

28 篇文章 12 订阅

第3章 队列和命令

3.1 设备队列

  • 查询物理设备队列族的属性
    • vkGetPhysicalDeviceQueueFamilyProperties
      • VkQueueFamilyProperties
  • VkDeviceQueueCreateInfo
    typedef struct VkDeviceQueueCreateInfo 
    {
      //VK_STRUCTURE_TYPE_QUEUE_CREATE_INFO。
      VkStructureType          sType;
      //nullptr
      const void*              pNext;
      //控制队列构建的标记信息, 暂时无标志,设置为 0
      VkDeviceQueueCreateFlags flags;
      //从哪个族中分配队列
      uint32_t                 queueFamilyIndex;
      //需分配的队列的个数
      uint32_t                 queueCount;
      const float*             pQueuePriorities;
    } VkDeviceQueueCreateInfo;
    
  • 设备中获取队列
    void vkGetDeviceQueue (
      VkDevice device,
      uint32_t queueFamilyIndex,
      uint32_t queueIndex,
      VkQueue* pQueue
    );
    

3.2 创建命令缓冲区

  • 队列的主要目的就是代表应用程序处理任务。任务就是记录到命令缓冲区(command buffer) 中的一系列命令
  • 创建命令池
    VkResult vkCreateCommandPool 
    (
      VkDevice                       device,
      const VkCommandPoolCreateInfo* pCreateInfo,
      //应用程序管理的主机内存分配
      const VkAllocationCallbacks*   pAllocator,
      VkCommandPool*                 pCommandPool
    ); 
    
  • VkCommandPoolCreateInfo
    typedef struct VkCommandPoolCreateInfo 
    {
      //VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO
      VkStructureType          sType;
      //nullptr
      const void*              pNext;
      //池以及从池分配的命令缓冲区的行为
      VkCommandPoolCreateFlags flags;
      //指定队列族
      uint32_t                 queueFamilyIndex;
    } VkCommandPoolCreateInfo; 
    
  • VkCommandPoolCreateFlagBits
    • VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
      • 命令缓冲区使用周期短,使用完很快退回给缓存池
      • 不设置这个标志位就意味着告诉 Vulkan,你将长时间持有这个命令缓冲区
      • 使用更先进的分配策略,以避免在频繁分配和归还命令缓冲区时产生内存碎片
    • VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
      • 允许单个命令缓冲区可通过重置(或重启)而重用
      • 没有这个标志位, 只有池本身能够重置, 它隐式地回收所有由它分配的命令缓冲区
      • 跟踪每个命令缓冲区的重置状态,而不是简单地只是在池的级别上跟踪
  • 每一个标志位都会增加一些开销
  • 获取命令缓冲区
    VkResult vkAllocateCommandBuffers (
      VkDevice                           device,
      const VkCommandBufferAllocateInfo* pAllocateInfo,
      VkCommandBuffer*                   pCommandBuffers
    );
    
  • VkCommandBufferAllocateInfo
    typedef struct VkCommandBufferAllocateInfo 
    {
      //VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_ INFO
      //or
      //VK_COMMAND_BUFFER_LEVEL_SECONDARY
      VkStructureType      sType;
      const void*          pNext;
      VkCommandPool        commandPool;
      VkCommandBufferLevel level;
      //要从缓存池中分配的命令缓冲区的数量
      uint32_t             commandBufferCount;
    } VkCommandBufferAllocateInfo;
    
  • 释放命令缓冲区
    void vkFreeCommandBuffers (
      VkDevice               device,
      VkCommandPool          commandPool,
      uint32_t               commandBufferCount,
      const VkCommandBuffer* pCommandBuffers
    );
    
  • 释放一个命令池所用的所有资源和它创建的所有命令缓冲区
    void vkDestroyCommandPool (
      VkDevice                     device,
      VkCommandPool                commandPool,
      const VkAllocationCallbacks* pAllocator
    );
    

3.3 记录命令

  • 多线程记录命令规则
    • 一个线程可以通过调用命令缓冲区函数依次将命令记录到多个命令缓冲区中
    • 两个或多个线程可以同时构建一个命令缓冲区,只要应用程序可以保证它们不同时执行命令缓冲区的构建函数
  • 最好对于一个线程有一个或者多个命令缓冲区,而不是多个线程共享一个
  • 为每一个线程创建一个命令池,就不会有任何冲突
  • 启动命令缓冲区
    VkResult vkBeginCommandBuffer (
      VkCommandBuffer                 commandBuffer,
      const VkCommandBufferBeginInfo* pBeginInfo
    );
    
  • VkCommandBufferBeginInfo
    typedef struct VkCommandBufferBeginInfo 
    {
      //VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
      VkStructureType                       sType;
      //nullptr
      const void*                           pNext;
      //命令缓冲区将会如何使用
      VkCommandBufferUsageFlags             flags;
      //开启副命令缓冲区所需要的 第 13 章介绍
      const VkCommandBufferInheritanceInfo* pInheritanceInfo;
    } VkCommandBufferBeginInfo; 
    
  • VkCommandBufferUsageFlagBits
    • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
      • 命令缓冲区只会记录和执行一次,然后销毁或回收
    • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
      • 命令缓冲区会在渲染通道(render pass)里使用,只在副命令缓冲区里有效
      • 在创建主命令缓冲区时这个标志位会被忽略
    • VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT
      • 命令缓冲区有可能多次执行或者暂停
  • 在两个缓冲区对象之间复制数据
    void vkCmdCopyBuffer (
      VkCommandBuffer     commandBuffer,
      //指定源
      VkBuffer            srcBuffer,
      //指目标
      VkBuffer            dstBuffer,
      //指定区域的数量
      uint32_t            regionCount,
      //区域数组的地址
      const VkBufferCopy* pRegions
     );
    
  • VkBufferCopy
    typedef struct VkBufferCopy 
    {
      //源偏移值
      VkDeviceSize srcOffset;
      //目标偏移值
      VkDeviceSize dstOffset;
      //要复制的区域的大小
      VkDeviceSize size;
    } VkBufferCopy; 
    
  • 关于 Vulkan 操作的基本事实是,命令在调用时并不是立即执行的,仅仅把它们添加到命令缓 冲区的尾部。如果你正在从一个 CPU 可访问的内存区域复制数据(或者向其中复制),那么你需要确保以下几件事
    • 保证在设备执行命令前,数据在源区域
    • 保证源区域的数据是有效的,直到命令在设备上执行以后
    • 保证不读取目标数据,直到命令在设备上执行之后
  • 记录命令已完成
    VkResult vkEndCommandBuffer (VkCommandBuffer commandBuffer);
    

3.4 回收利用命令缓冲区

  • 命令缓冲区重量级的操作
    • 调用 vkAllocateCommand Buffers() 获取一个或者多个命令缓冲区的句柄,将命令记录到命令缓冲区中
    • 然后调用 vkFreeCommandBuffers() 把缓冲区归还到各自的池内
  • 重用命令缓冲区,重置
    VkResult vkResetCommandBuffer (
      VkCommandBuffer           commandBuffer,
      //指定重置命令缓冲区时的附加操作
      //现只有 VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT
      VkCommandBufferResetFlags flags
    );
    
  • 一次性把从一个池分配的所有命令缓冲区进行重置
    VkResult vkResetCommandPool (
      VkDevice                device,
      VkCommandPool           commandPool,
      //VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT
      VkCommandPoolResetFlags flags
    ); 
    

3.5 命令的提交

  • 命令缓冲区提交给设备的一个队列
    VkResult vkQueueSubmit (
      //目标设备队列
      VkQueue             queue,
      //提交的次数
      uint32_t            submitCount,
      //描述了每一次提交的信息
      const VkSubmitInfo* pSubmits,
      //栅栏(fence)对象的句柄
      //可用来等待本次提交执行的命令完成
      VkFence             fence
    );
    
  • VkSubmitInfo
    typedef struct VkSubmitInfo 
    {
      //VK_STRUCTURE_TYPE_SUBMIT_INFO
      VkStructureType             sType;
      //nullptr
      const void*                 pNext;
      //第十一章介绍等待信号量数量
      uint32_t                    waitSemaphoreCount;
      //第十一章介绍等待信号量
      const VkSemaphore*          pWaitSemaphores;
      //第十一章介绍
      const VkPipelineStageFlags* pWaitDstStageMask;
      //要执行的命令缓冲区的个数
      uint32_t                    commandBufferCount;
      //要执行的命令缓冲区
      const VkCommandBuffer*      pCommandBuffers;
      //第十一章介绍信号量个数
      uint32_t                    signalSemaphoreCount;
      //第十一章介绍信号量
      const VkSemaphore*          pSignalSemaphores;
    } VkSubmitInfo;
    
  • 对队列的访问必须要在外部保持同步
  • 等待提交给队列的所有任务完成
    VkResult vkQueueWaitIdle (VkQueue queue);
    
  • 要等待一个设备上所有队列的所有命令完成
    VkResult vkDeviceWaitIdle (VkDevice device);
    
  • 调用 vkQueueWaitIdle() 或者 vkDeviceWaitIdle()是不推荐的
    • 因为它们会强制完成队列或设备上的任何工作,这是非常重量级的工作

第4章 移动数据

  • 移动数据命令
    • 填充、复制、清除缓冲区与图像的相关命令

4.1 管理资源状态

  • 将资源从一个状态转变为另一个状态的基本行为就是屏障

4.1.1 管线屏障

  • 屏障是一种同步机制,用来管理内存访问,以及在 Vulkan 管线各个阶段里的资源状态变化

  • 对资源访问进行同步和改变状态的主要命令

    void vkCmdPipelineBarrier (
      VkCommandBuffer              commandBuffer,
      //哪个阶段的管线最后写入资源
      VkPipelineStageFlags         srcStageMask,
      //哪个阶段接下来要从资源读数据
      VkPipelineStageFlags         dstStageMask,
      //由屏障表示的依赖关系如何影响屏障引用的资源
      //只有 VK_DEPENDENCY_BY_REGION_BIT
      //表示屏障只影响被源阶段(如果能确定)改变的区域,此区域被目标阶段所使用
      VkDependencyFlags            dependencyFlags,
      uint32_t                     memoryBarrierCount,
      const VkMemoryBarrier*       pMemoryBarriers,
      uint32_t                     bufferMemoryBarrierCount,
      const VkBufferMemoryBarrier* pBufferMemoryBarriers,
      uint32_t                     imageMemoryBarrierCount,
      const VkImageMemoryBarrier*  pImageMemoryBarriers
    );
    
  • VkPipelineStageFlags

    • VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
      • 当设备开始处理命令时,马上认为访问到了管线的顶端
    • VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT
      • 当管线执行一个间接命令时,它为命令从内存中取出一些参数。这是取出这些参数的阶段
    • VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
      • 这是顶点属性从它们所在的缓冲区被取回的阶段
      • 此后,就可以覆盖顶点缓冲区的内容了,即使相关的顶点着色器没有执行完
    • VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
      • 当一个绘制命令产生的所有顶点着色器工作完成时,这个阶段通过
    • VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT
      • 当一个绘制命令产生的所有细分控制着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT
      • 当一个绘制命令产生的所有细分评估着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT
      • 当一个绘制命令产生的所有几何着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
      • 当一个绘制命令产生的所有片段着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
      • 在片段着色器开始运行之前可能发生的所有逐片段测试都已经完成了
    • VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
      • 在片段着色器开始运行之后可能发生的所有逐片段测试都已经完成了
    • VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
      • 管线产生的片段都已经写入颜色附件中
    • VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
      • 调度产生的计算着色器调用已经完成
    • VK_PIPELINE_STAGE_TRANSFER_BIT
      • 诸如 vkCmdCopyImage()vkCmdCopyBuffer() 等调用产生的任何延迟转移已经完成
    • VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
      • 图形管线任何一部分的操作都已经完成
    • VK_PIPELINE_STAGE_HOST_BIT
      • 该管线阶段对应来自主机的访问
    • VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT
      • 当用作目标时,这个特殊的标志表示任何管线阶段都可能访问内存
      • 当用作源时,实际上和 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT 相同
    • VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
      • 当你不知道接下来要发生什么时,使用它,它将同步所有的东西
  • 单次调用 vkCmdPipelineBarrier() 可用来触发多个屏障操作

  • 有 3 种类型的屏障操作

    • 全局内存屏障
      • 例如在主机和设备之间对映射内存的同步访问
    • 缓冲区屏障
      • 影响设备对缓冲区资源的访问
    • 图像屏障
      • 影响设备对图像资源的访问

4.1.2 全局内存屏障

  • vkCmdPipelineBarrier() 的形参 VkMemoryBarrier
    • 代表一个内存屏障
    typedef struct VkMemoryBarrier 
    {
      //VK_STRUCTURE_TYPE_MEMORY_BARRIER
      VkStructureType sType;
      //nullptr
      const void*     pNext;
      //源的访问掩码
      VkAccessFlags   srcAccessMask;
      //目标的访问掩码
      VkAccessFlags   dstAccessMask;
    } VkMemoryBarrier;
    
  • VkAccessFlagBits
    • 用于表示如何写入,指明该内存接下来如何读取
    • VK_ACCESS_INDIRECT_COMMAND_READ_BIT
      • 引用的内存将会是位于间接绘制命令或者调度命令里的命令源
        • vkCmdDrawIndirect()
        • vkCmdDispatchIndirect()
    • VK_ACCESS_INDEX_READ_BIT
      • 引用的内存将会是索引绘制命令
        • vkCmdDrawIndexed()
        • vkCmdDrawIndexedIndirect()
    • VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT
      • 引用的内存将是顶点数据源
    • VK_ACCESS_UNIFORM_READ_BIT
      • 引用的内存是被着色器访问的 uniform 块的数据源
    • VK_ACCESS_INPUT_ATTACHMENT_READ_BIT
      • 引用的内存用来存储用作一个输入附件的图像
    • VK_ACCESS_SHADER_READ_BIT
      • 引用的内存用于存储一个图像对象,在着色器内使用图像加载或者纹理读取的操作来读取该对象
    • VK_ACCESS_SHADER_WRITE_BIT
      • 引用的内存用于存储一个图像对象,在着色器内使用图像存储操作写入该对象
    • VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
      • 引用的内存用于存储一个用作颜色附件的图像对象
    • VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT:
      • 引用的内存用来存储图像,该图像用作可写入的颜色附件
    • VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
      • 用来存储图像,该图像用作深度或模板附件
    • VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
      • 用来存储图像,该图像用作深度或模板附件
    • VK_ACCESS_TRANSFER_READ_BIT
      • 用作转移操作
        • vkCmdCopyImage()
        • vkCmdCopyBuffer()
        • vkCmdCopyBufferToImage()
    • VK_ACCESS_TRANSFER_WRITE_BIT
      • 引用的内存用作转移操作的目标
    • VK_ACCESS_HOST_READ_BIT
      • 引用的内存被映射,并将被主机读取
    • VK_ACCESS_HOST_WRITE_BIT
      • 引用的内存被映射,并将被主机写入
    • VK_ACCESS_MEMORY_READ_BIT
      • 所有没有在之前明确提到的其他内存读取都应该指定这个标志位
    • VK_ACCESS_MEMORY_WRITE_BIT
      • 所有没有在之前明确提到的其他内存写入都应该指定这个标志位
  • 内存屏障提供两个重要的功能
    • 帮助避免危险的事
      • 写后读
      • 读后写
      • 写后写
    • 可确保数据一致性
      • 保证在管线不同部分中数据的视图的一致性

4.1.3 缓冲区内存屏障

  • 缓冲区内存屏障为备份缓冲区对象提供细粒度的内存控制
  • 执行的缓冲区内存屏障
    • vkCmdPipeline Barrier()
      • pBufferMemoryBarriers
  • VkBufferMemoryBarrier
    typedef struct VkBufferMemoryBarrier
    {
      //VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER 
      VkStructureType sType;
      //nullptr
      const void*     pNext;
      VkAccessFlags   srcAccessMask;
      VkAccessFlags   dstAccessMask;
      //缓冲区的所有权从一个队列转移到另一个队列且这些队列属于不同的族
      //没有转移下面两个参数为 VK_QUEUE_FAMILY_IGNORED
      uint32_t        srcQueueFamilyIndex;
      uint32_t        dstQueueFamilyIndex;
      //缓冲区
      VkBuffer        buffer;
      //控制对整个缓冲区的访问为 0
      VkDeviceSize    offset;
      //控制对整个缓冲区的访问为 VK_WHOLE_SIZE
      VkDeviceSize    size;
    } VkBufferMemoryBarrier;
    

4.1.4 图像内存屏障

  • VkImageMemoryBarrier
    typedef struct VkImageMemoryBarrier
    {
      //VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER 
      VkStructureType         sType;
      //nullptr
      const void*             pNext;
      VkAccessFlags           srcAccessMask;
      VkAccessFlags           dstAccessMask;
      //屏障前面的图像所使用的布局
      VkImageLayout           oldLayout;
      //屏障后面的图像所使用的布局
      VkImageLayout           newLayout;
      uint32_t                srcQueueFamilyIndex;
      uint32_t                dstQueueFamilyIndex;
      VkImage                 image;
      //影响到图像的哪些部分
      VkImageSubresourceRange subresourceRange;
    } VkImageMemoryBarrier;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange 
    {
      VkImageAspectFlags aspectMask;
      //指定最小数字的(最高分辨率)mipmap 层级
      //无则为0
      uint32_t           baseMipLevel;
      //指定层级数量
      //无则为1
      uint32_t           levelCount;
      //设置第一个层的索引
      //无则为0
      uint32_t           baseArrayLayer;
      //包含的层数
      //无则为1
      uint32_t           layerCount;
    } VkImageSubresourceRange; 
    

4.2 清除和填充缓冲区

  • 向缓冲区填入特定值
    void vkCmdFillBuffer (
      VkCommandBuffer commandBuffer,
      //需要填充数据的缓冲区
      VkBuffer        dstBuffer,
      //指定填充操作开始的位置
      //必须是 4 的倍数
      //全部填充 0
      VkDeviceSize    dstOffset,
      //指定填充区域的大小
      //必须是 4 的倍数
      //全部填充 VK_WHOLE_SIZE
      VkDeviceSize    size,
      uint32_t        data
    );
    
    • 它适合小的、立即模式的缓冲区更新
  • 用浮点数清除缓冲区,可以把浮点数的值转换为 uint32_t 类型的值
    • 再传入 vkCmdFillBuffer()
    • 示例 Page 106
  • 更新数组或者小的数据结构里的数据
    • 直接从主机内存复制数据到缓冲区对象中
    void vkCmdUpdateBuffer (
      VkCommandBuffer commandBuffer,
      VkBuffer        dstBuffer,
      VkDeviceSize    dstOffset,
      VkDeviceSize    dataSize,
      //指向包含数据(最终会复制进缓冲区对象)的主机内存
      const uint32_t* pData
    );
    
  • vkCmdUpdateBuffer() 调用完成后,就可以释放主机内存数据结构体,或覆盖该结构体的内容
  • 向 uniform 缓冲区写入单个值,使用 vkCmdFillBuffer() 比使用缓冲区映射并调用 vkCmdCopyBuffer() 高效得多

4.3 清空和填充图像

  • 清除图像并把它变成固定值
    void vkCmdClearColorImage (
      VkCommandBuffer                commandBuffer,
      //需要清除数据的图像
      VkImage                        image,
      //在执行清除操作时,图像期望的布局
      //VK_IMAGE_LAYOUT_GENERAL
      //VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 
      VkImageLayout                  imageLayout,
      //用于清除图像的值 union
      const VkClearColorValue*       pColor,
      //指定区域的数量
      uint32_t                       rangeCount,
      const VkImageSubresourceRange* pRanges
    );
    
  • VkClearColorValue
    typedef union VkClearColorValue 
    {
      float    float32[4];
      int32_t  int32[4];
      uint32_t uint32[4];
    } VkClearColorValue;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange 
    {
      //必须设置为 VK_IMAGE_ASPECT_COLOR_BIT
      VkImageAspectFlags aspectMask;
      uint32_t           baseMipLevel;
      uint32_t           levelCount;
      uint32_t           baseArrayLayer;
      uint32_t           layerCount;
    } VkImageSubresourceRange;
    
  • 如果需要清除同一个图像里的多个区域,并填充不同的颜色值,就需要多次调用该函数
  • 清除深度-模板图像
    void vkCmdClearDepthStencilImage (
      VkCommandBuffer                 commandBuffer,
      VkImage                         image,
      VkImageLayout                   imageLayout,
      const VkClearDepthStencilValue* pDepthStencil,
      uint32_t                        rangeCount,
      const VkImageSubresourceRange * pRanges
    );
    
  • VkClearDepthStencilValue
    typedef struct VkClearDepthStencilValue 
    {
      float depth;
      uint32_t stencil;
    } VkClearDepthStencilValue;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange 
    {
      //含VK_IMAGE_ASPECT_DEPTH_BIT和/或VK_IMAGE_ASPECT_STENCIL_BIT
      VkImageAspectFlags aspectMask;
      uint32_t           baseMipLevel;
      uint32_t           levelCount;
      uint32_t           baseArrayLayer;
      uint32_t           layerCount;
    } VkImageSubresourceRange;
    
  • 给单个区域指定 VK_IMAGE_ASPECT_DEPTH_BIT 和 VK_IMAGE_ASPECT_STENCIL_BIT 属性,通常会比为两个区域中的每一个指定一个标志位要高效得多

4.4 复制图像数据

  • Vulkan 支持 3 种图像数据复制方式
    • 从缓冲区向图像复制
    • 在图像之间复制
    • 从图像向缓冲区复制
  • 从缓冲区向图像的一个或者多个区域复制数据
    void vkCmdCopyBufferToImage (
      VkCommandBuffer          commandBuffer,
      VkBuffer                 srcBuffer,
      VkImage                  dstImage,
      //VK_IMAGE_LAYOUT_GENERAL
      //VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
      VkImageLayout            dstImageLayout,
      uint32_t                 regionCount,
      //需要更新的区域
      const VkBufferImageCopy* pRegions
    );
    
  • VkBufferImageCopy
    typedef struct VkBufferImageCopy 
    {
      //缓冲区中数据的偏移量
      VkDeviceSize             bufferOffset;
      //源图像中纹素的个数
      //为 0, 图像是在缓冲区中紧密排布与imageExtent.width 相等
      uint32_t                 bufferRowLength;
      //指定图像数据的行数
      //为 0,为源图像的行数等于imageExtent.height
      uint32_t                 bufferImageHeight;
      VkImageSubresourceLayers imageSubresource;
      VkOffset3D               imageOffset;
      VkExtent3D               imageExtent;
    } VkBufferImageCopy;
    
  • VkImageSubresourceLayers
    typedef struct VkImageSubresourceLayers 
    {
      //包含一个或者多个图像层面
      VkImageAspectFlags aspectMask;
      uint32_t           mipLevel;
      uint32_t           baseArrayLayer;
      uint32_t           layerCount;
    } VkImageSubresourceLayers;
    
  • 从一个图像对象复制数据到缓冲区中
    void vkCmdCopyImageToBuffer (
      VkCommandBuffer          commandBuffer,
      VkImage                  srcImage,
      VkImageLayout            srcImageLayout,
      VkBuffer                 dstBuffer,
      uint32_t                 regionCount,
      const VkBufferImageCopy* pRegions
    ); 
    
  • 在两幅图像之间复制数据
    • 可以一次复制多个区域
    void vkCmdCopyImage (
      VkCommandBuffer    commandBuffer,
      VkImage            srcImage,
      VkImageLayout      srcImageLayout,
      VkImage            dstImage,
      VkImageLayout      dstImageLayout,
      uint32_t           regionCount,
      const VkImageCopy* pRegions
    );
    
  • VkImageCopy
    typedef struct VkImageCopy 
    {
      VkImageSubresourceLayers srcSubresource;
      VkOffset3D               srcOffset;
      VkImageSubresourceLayers dstSubresource;
      VkOffset3D               dstOffset;
      VkExtent3D               extent;
    } VkImageCopy;
    

4.5 复制压缩图像数据

  • 调用 vkCmdCopyImage() 命令,也可以在两幅压缩图像间或者压缩图像与非压缩图像间复制数据
    • 要求源图像和目标图像格式必须拥有相同的压缩块大小
    • 当从非压缩图像向压缩图像复制数据时,每一个源纹素被当作单个原生数据
      • 包含一个位数,与压缩图像中一个块的大小相同
    • 当从压缩格式向非压缩格式复制数据时,正好相反
      • Vulkan 不会解压缩图像数据

4.6 拉伸图像

  • 支持格式转换和改变复制区域的尺寸
    void vkCmdBlitImage (
      VkCommandBuffer    commandBuffer,
      //必须支持 VK_FORMAT_FEATURE_BLIT_SRC_BIT
      VkImage            srcImage,
      VkImageLayout      srcImageLayout,
      //必须支持 VK_FORMAT_FEATURE_ BLIT_DST_BIT
      VkImage            dstImage,
      VkImageLayout      dstImageLayout,
      uint32_t           regionCount,
      const VkImageBlit* pRegions,
      //VK_FILTER_NEAREST 或 VK_FILTER_LINEAR
      VkFilter           filter
    ); 
    
  • VkImageBli
    typedef struct VkImageBlit {
      //定义源图像的子资源
      VkImageSubresourceLayers srcSubresource;
      //定义待复制区域的一角
      VkOffset3D               srcOffsets[2];
      //定义目标图像子资源
      VkImageSubresourceLayers dstSubresource;
      //定义目标区域的另外一角
      VkOffset3D               dstOffsets[2];
    } VkImageBlit;
    

第5章 展示

5.1 展示扩展

  • 实际上,一个 Vulkan 实现或许根本不支持展示
    • 展示是通过一套扩展处理的,这套扩展统称为 WS(I Window System Integration) 扩展或 WSI 系统
  • Vulkan 中的扩展功能在使用前必须显式地开启

5.2 展示表面

  • 在包含 <vulkan.h> 之前定义展示扩展的宏
    • VK_USE_PLATFORM_WIN32_KHR
    • VK_USE_PLATFORM_XLIB_KHR
    • VK_USE_PLATFORM_LIB_XCB_KHR

5.2.1 在微软的 Windows 上展示

  • 判断一个队列是否支持展示操作
    VkBool32 vkGetPhysicalDeviceWin32PresentationSupportKHR(
      VkPhysicalDevice physicalDevice,
      uint32_t         queueFamilyIndex
    );
    
  • 创建表面对象
    VkResult vkCreateWin32SurfaceKHR(
      VkInstance                         instance,
      const VkWin32SurfaceCreateInfoKHR* pCreateInfo,
      const VkAllocationCallbacks*       pAllocator,
      VkSurfaceKHR*                      pSurface
    );
    
  • VkWin32SurfaceCreateInfoKHR
    typedef struct VkWin32SurfaceCreateInfoKHR 
    {
      //VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR 
      VkStructure Type             sType;
      //nullptr
      const void*                  pNext;
      //为 0
      VkWin32SurfaceCreateFlagsKHR flags;
      //用来创建本地窗口的应用程序或者模块的 HINSTANCE
      //可以调用 Win32 函数 GetModuleHandle 获取
      HINSTANCE                    hinstance;
      //本地窗口的句柄
      HWND                         hwnd;
    } VkWin32SurfaceCreateInfoKHR;
    

5.2.2 在基于 Xlib 的平台上展示

5.2.3 在 Xcb 上展示

5.3 交换链

  • 交换链对象是用来请求窗口系统创建一个或者多个可用来代表 Vulkan 表面的图像
  • 每一个交换链对象管理一个图像集
  • 创建交换链对象
    VkResult vkCreateSwapchainKHR(
      VkDevice                        device,
      const VkSwapchainCreateInfoKHR* pCreateInfo,
      const VkAllocationCallbacks*    pAllocator,
      VkSwapchainKHR*                 pSwapchain
    );
    
  • VkSwapchainCreateInfoKHR
    typedef struct VkSwapchainCreateInfoKHR 
    {
      //VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
      VkStructureType               sType;
      //nullptr
      const void*                   pNext;
      //设置为 0
      VkSwapchainCreateFlagsKHR     flags;
      VkSurfaceKHR                  surface;
      //交换链中的图像个数
      //双缓冲或者三缓冲
      //为 1 表示要求直接渲染到前端缓冲区并显示
      //可调用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR()来获取交换链支持的最小和最大图像数目
      uint32_t                      minImageCount;
      //的图像的格式
      VkFormat                      imageFormat;
      //颜色空间
      VkColorSpaceKHR               imageColorSpace;
      //中图像以像素为单位的维度
      VkExtent2D                    imageExtent;
      //每张图像的层数
      uint32_t                      imageArrayLayers;
      //图像将如何使用
      VkImageUsageFlags             imageUsage;
      //图像在队列之间是如何共享的
      //如果图像在每个时刻仅用在一个队列上
      //为 VK_SHARING_MODE_EXCLUSIVE
      //用在多个队列上VK_SHARING_MODE_CONCURRENT
      VkSharingMode                 imageSharingMode;
      uint32_t                      queueFamilyIndexCount;
      const uint32_t*               pQueueFamilyIndices;
      //图像在展示给用户之前如何做变换
      //允许旋转或者翻转
      VkSurfaceTransformFlagBitsKHR preTransform;
      //如何处理 alpha 分量
      //忽略 alpha,为VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
      VkCompositeAlphaFlagBitsKHR   compositeAlpha;
      //控制了与窗口系统的同步,以及图像展示到表面上的速率
      VkPresentModeKHR              presentMode;
      VkBool32                      clipped;
      //窗口改变尺寸并且交换链需要重新分配更大的图像时需要
      VkSwapchainKHR                oldSwapchain;
    } VkSwapchainCreateInfoKHR;
    
  • VkCompositeAlphaFlagBitsKHR
    • VK_PRESENT_MODE_IMMEDIATE_KHR
      • 当展示已经安排就绪时,图像要尽快展示给用户,不用等待诸如垂直清空等外部事件
      • 能提供可能的最高帧率
      • 但是会引起屏幕分裂或者其他瑕疵
    • VK_PRESENT_MODE_MAILBOX_KHR
      • 当展示新的图像时,就把它标记为待处理图像,在下一次,系统将把它展示给用户
    • VK_PRESENT_MODE_FIFO_KHR
      • 将要展示的图像存储在一个内部队列给,顺序地展示用户
    • VK_PRESENT_MODE_FIFO_RELAXED_KHR
      • 如果队列是空的并且垂直刷新发生了,队列中下一幅图像就立即展示
  • VkSwapchainCreateInfoKHR 中的参数必须和表面的能力相匹配
    • 查询
      VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
        VkPhysicalDevice          physicalDevice,
        VkSurfaceKHR              surface,
        VkSurfaceCapabilitiesKHR* pSurfaceCapabilities
      );
      
  • VkSurfaceCapabilitiesKHR
    typedef struct VkSurfaceCapabilitiesKHR {
      uint32_t                      minImageCount;
      uint32_t                      maxImageCount;
      VkExtent2D                    currentExtent;
      VkExtent2D                    minImageExtent;
      VkExtent2D                    maxImageExtent;
      uint32_t                      maxImageArrayLayers;
      VkSurfaceTransformFlagsKHR    supportedTransforms; 
      VkSurfaceTransformFlagBitsKHR currentTransform;
      VkCompositeAlphaFlagsKHR      supportedCompositeAlpha;
      VkImageUsageFlags             supportedUsageFlags;
    } VkSurfaceCapabilitiesKHR;
    
  • 获取交换链图像
    VkResult vkGetSwapchainImagesKHR(
      VkDevice       device,
      VkSwapchainKHR swapchain,
      //获取的图像数量
      //pSwapchainImages 为 nullptr时会被重写为交换链中的图像数量
      uint32_t*      pSwapchainImageCount,
      //指向 VkImage 类型数组的指针
      VkImage*       pSwapchainImages
    );
    
  • 获知交换链中实际上有多少幅图像
    • vkGetSwapchainImagesKHR()
  • 获知哪些格式可用于和表面相关联的交换链
    VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR(
      VkPhysicalDevice    physicalDevice,
      VkSurfaceKHR        surface,
      uint32_t*           pSurfaceFormatCount,
      VkSurfaceFormatKHR* pSurfaceFormats
    );
    
  • VkSurfaceFormatKHR
    typedef struct VkSurfaceFormatKHR {
      VkFormat        format;
      //VK_COLORSPACE_SRGB_NONLINEAR_KHR。
      VkColorSpaceKHR colorSpace;
    } VkSurfaceFormatKHR;
    
  • 调用 vkGetSwapchainImagesKHR() 获取的图像并不是马上就能用的
    • 在写入之前,需要调用 vkAcquireNextImageKHR()获取下一幅可用的图像
      VkResult vkAcquireNextImageKHR(
        VkDevice       device,
        VkSwapchainKHR swapchain,
        //等待时间
        //如果超时, 将会返回 VK_NOT_READY
        //为 0,可以实现非阻塞行为
        uint64_t       timeout,
        //传入信号量句柄
        VkSemaphore    semaphore,
        //传入栅栏
        VkFence        fence,
        //下一个应用程序渲染的图像的索引
        uint32_t*      pImageIndex
      );
      

5.4 全屏表面

  • 直接渲染到显示器
    • VK_KHR_displayVK_KHR_display_swapchain 扩展提供
  • 获知物理设备连接了几个显示器
    VkResult vkGetPhysicalDeviceDisplayPropertiesKHR(
      VkPhysicalDevice        physicalDevice,
      uint32_t*               pPropertyCount,
      VkDisplayPropertiesKHR* pProperties
    );
    
  • VkDisplayPropertiesKHR
    typedef struct VkDisplayPropertiesKHR 
    {
      VkDisplayKHR               display;
      //可读的字符串,描述了显示器
      const char*                displayName;
      //显示器的尺寸,以毫米为单位
      VkExtent2D                 physicalDimensions;
      //显示器的分辨率,以像素为单位
      VkExtent2D                 physicalResolution;
      //显示时支持翻转或者旋转
      VkSurfaceTransformFlagsKHR supportedTransforms;
      //如果显示器支持多个平面,那么当这些平面相互之间的顺序可重排时
      //设置为 VK_TRUE
      //如果这些平面只能以固定的顺序显示,设置为 VK_FALSE
      VkBool32                   planeReorderPossible;
      //接受部分更新或者低频率的更新
      VkBool32                   persistentContent;
    } VkDisplayPropertiesKHR;
    
  • 查询设备支持的平面个数和类型
    VkResult vkGetPhysicalDeviceDisplayPlanePropertiesKHR(
      VkPhysicalDevice             physicalDevice,
      uint32_t*                    pPropertyCount,
      VkDisplayPlanePropertiesKHR* pProperties
    );
    
  • VkDisplayPlanePropertiesKHR
    typedef struct VkDisplayPlanePropertiesKHR 
    {
      //显示器
      VkDisplayKHR currentDisplay;
      //平面之间的叠加顺序
      uint32_t     currentStackIndex;
    } VkDisplayPlanePropertiesKHR; 
    
  • 来获知哪些显示器设备对一个显示平面是可见的
    • 一些设备的显示器平面也许会跨越多个物理显示器
    VkResult vkGetDisplayPlaneSupportedDisplaysKHR(
      VkPhysicalDevice physicalDevice,
      //显示器平面
      uint32_t         planeIndex,
      //显示器数量
      uint32_t*        pDisplayCount,
      VkDisplayKHR*    pDisplays
    );
    
  • 获知显示平面功能
    VkResult vkGetDisplayPlaneCapabilitiesKHR(
      VkPhysicalDevice               physicalDevice,
      //显示模式
      VkDisplayModeKHR               mode,
      uint32_t                       planeIndex,
      VkDisplayPlaneCapabilitiesKHR* pCapabilities
    );
    
  • VkDisplayPlaneCapabilitiesKHR
    typedef struct VkDisplayPlaneCapabilitiesKHR 
    {
      //显示平面支持的组合模式
      VkDisplayPlaneAlphaFlagsKHR supportedAlpha;
      //可展示区域的最小偏移量
      VkOffset2D                  minSrcPosition;
      //可展示区域的最大偏移量
      VkOffset2D                  maxSrcPosition;
      //最小尺寸
      VkExtent2D                  minSrcExtent;
      //最大尺寸
      VkExtent2D                  maxSrcExtent;
      //在其上放置对应的物理显示器上的平面最小偏移量
      VkOffset2D                  minDstPosition;
      //在其上放置对应的物理显示器上的平面最大偏移量
      VkOffset2D                  maxDstPosition;
      //该显示器上以像素为单位的物理尺寸
      VkExtent2D                  minDstExtent;
      //该显示器上以像素为单位的物理尺寸
      VkExtent2D                  maxDstExtent;
    } VkDisplayPlaneCapabilitiesKHR;
    
  • VkDisplayPlaneAlphaFlagsKHR
    • VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR
      • 这个平面不支持混合,该平面上展示的所有表面都是完全不透明的
    • VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR
      • 这个平面支持一个全局的 alpha 值
      • 通过用于创建表面的结构体 VkDisplaySurfaceCreateInfoKHR 的成员 globalAlpha 来传递
    • VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR
      • 平面支持逐像素半透明, 从展示到表面的图像的 alpha 通道里取值
  • 获取每一个显示器预定义的显示模式
    VkResult vkGetDisplayModePropertiesKHR(
      VkPhysicalDevice            physicalDevice,
      VkDisplayKHR                display,
      uint32_t*                   pPropertyCount,
      VkDisplayModePropertiesKHR* pProperties
    );
    
  • VkDisplayModePropertiesKHR
    typedef struct VkDisplayModePropertiesKHR {
      //该显示模式
      VkDisplayModeKHR           displayMode;
      VkDisplayModeParametersKHR parameters;
    } VkDisplayModePropertiesKHR; 
    
  • VkDisplayModeParametersKHR
    typedef struct VkDisplayModeParametersKHR 
    {
      //显示范围(以像素为单位)
      VkExtent2D visibleRegion;
      //刷新频率
      uint32_t   refreshRate;
    } VkDisplayModeParametersKHR; 
    
  • 如果没有合适的预定义的显示模式,创建新的模式
    VkResult vkCreateDisplayModeKHR(
      VkPhysicalDevice                  physicalDevice,
      VkDisplayKHR                      display,
      const VkDisplayModeCreateInfoKHR* pCreateInfo,
      const VkAllocationCallbacks*      pAllocator,
      VkDisplayModeKHR*                 pMode
    );
    
  • VkDisplayModeCreateInfoKHR
    typedef struct VkDisplayModeCreateInfoKHR 
    {
      //VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR
      VkStructureType             sType;
      //nullptr
      const void*                 pNext;
      //为 0
      VkDisplayModeCreateFlagsKHR flags;
      VkDisplayModeParametersKHR  parameters;
    } VkDisplayModeCreateInfoKHR;
    
  • 创建一个 VkSurfaceKHR 对象
    VkResult vkCreateDisplayPlaneSurfaceKHR(
      VkInstance                           instance,
      const VkDisplaySurfaceCreateInfoKHR* pCreateInfo,
      const VkAllocationCallbacks*         pAllocator,
      VkSurfaceKHR*                        pSurface
    );
    
  • VkDisplaySurfaceCreateInfoKHR
    typedef struct VkDisplaySurfaceCreateInfoKHR 
    {
      //VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR 
      VkStructureType                sType;
      //nullptr
      const void*                    pNext;
      //为 0
      VkDisplaySurfaceCreateFlagsKHR flags;
      //显示模式
      //可以是预定义的模式
      //或通过调用 vkCreateDisplayModeKHR() 产生的用户自定义的显示模式
      VkDisplayModeKHR               displayMode;
      //该平面
      uint32_t                       planeIndex;
      //相对次序
      uint32_t                       planeStackIndex;
      //图像翻转或者旋转
      VkSurfaceTransformFlagBitsKHR  transform;
      float                          globalAlpha;
      //VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR
      //VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR
      //VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR
      VkDisplayPlaneAlphaFlagBitsKHR alphaMode;
      //可展示表面的尺寸
      //对于全屏渲染,这个应该和 displayMode 选择的显示模式的范围一样
      VkExtent2D                     imageExtent;
    } VkDisplaySurfaceCreateInfoKHR;
    
  • 在展示时可应用于一个表面的变换依赖于设备和表面能力
    • 调用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR() 可以获取该能力

5.5 执行展示

  • 一个队列是否支持展示
    VkResult vkGetPhysicalDeviceSurfaceSupportKHR(
      VkPhysicalDevice physicalDevice,
      uint32_t         queueFamilyIndex,
      VkSurfaceKHR     surface,
      VkBool32*        pSupported
    );
    
  • 在图像能够展示之前须有正确的布局
    • 使用图像内存屏障,图像可从一个布局转变到另一个布局
    • 用图像内存屏障把图像从 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 转变到 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 布局
      • Page 133
  • 图像处于 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 布局即可展示
    VkResult vkQueuePresentKHR(
      VkQueue                 queue,
      const VkPresentInfoKHR* pPresentInfo
    );
    
  • VkPresentInfoKHR
    typedef struct VkPresentInfoKHR 
    {
      //VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
      VkStructureType       sType;
      //nullptr
      const void*           pNext;
      //等待一个或者多个信号量
      //来使渲染图像和展示操作保持同步
      uint32_t              waitSemaphoreCount;
      const VkSemaphore*    pWaitSemaphores;
      uint32_t              swapchainCount;
      const VkSwapchainKHR* pSwapchains;
      const uint32_t*       pImageIndices;
      VkResult*             pResults;
    } VkPresentInfoKHR;
    
  • 单次调用 vkQueuePresentKHR() 可以同时向多个交换链中展示多幅图像

5.6 清除

  • 不管在应用程序中用什么方法来展示,都需要正确地清理
    void vkDestroySwapchainKHR(
      VkDevice                     device,
      VkSwapchainKHR               swapchain,
      const VkAllocationCallbacks* pAllocator
    );
    
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值