目录
第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
- 允许单个命令缓冲区可通过重置(或重启)而重用
- 没有这个标志位, 只有池本身能够重置, 它隐式地回收所有由它分配的命令缓冲区
- 跟踪每个命令缓冲区的重置状态,而不是简单地只是在池的级别上跟踪
- VK_COMMAND_POOL_CREATE_TRANSIENT_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
- 命令缓冲区有可能多次执行或者暂停
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_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
- 当你不知道接下来要发生什么时,使用它,它将同步所有的东西
- VK_PIPELINE_STAGE_TOP_OF_PIPE_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
- 如果队列是空的并且垂直刷新发生了,队列中下一幅图像就立即展示
- VK_PRESENT_MODE_IMMEDIATE_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_display
和VK_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 通道里取值
- VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR
- 获取每一个显示器预定义的显示模式
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 );