图像的布局实际上就是vulkan内部的图像的存储方式。也就是不同的布局,对应着图像的不同的使用场景,因此多数场景下,我们在使用图像前,都要关注下它的布局,如果布局不对的话,是要做布局转换的。
布局定义了数据在内存中的排列方式。图像的初始布局在创建时指定,在其生命周期内可以改变,有两种方式可以改变,一种是用barrier,一种是在renderpass中隐式的改变。
新创建的图像只能是VK_IMAGE_LAYOUT_UNDEFINED或者VK_IMAGE_LAYOUT_PREINITIALIZED布局,因此在使用前要做布局转换。
在vulkan中定义了如下的布局格式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28typedef enum VkImageLayout {
VK_IMAGE_LAYOUT_UNDEFINED = 0,
VK_IMAGE_LAYOUT_GENERAL = 1,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL = 4,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL = 5,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7,
VK_IMAGE_LAYOUT_PREINITIALIZED = 8,
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL = 1000117000,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL = 1000117001,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL = 1000241000,
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL = 1000241001,
VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL = 1000241002,
VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL = 1000241003,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002,
VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR = 1000111000,
VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV = 1000164003,
VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT = 1000218000,
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_MAX_ENUM = 0x7FFFFFFF
} VkImageLayout;
常用的图像布局有以下的几种
VK_IMAGE_LAYOUT_UNDEFINED这个状态是未定义的,图像在使用前要转换为另外的一个布局
VK_IMAGE_LAYOUT_GENERAL 通用布局,那都可以用,但是通用必然导致效率不高
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 图像在管线中作为attachment使用
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 图像在管线中作为DEPTH/STENCIL attachment使用
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL DEPTH/STENCIL只读布局,这个用在深度测试
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL shader读取用,这个就是用作纹理了。
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL 传输操作的源
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 传输操作的目的
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 用作显示
我们在创建纹理图像的时候,创建出来的图像布局是VK_IMAGE_LAYOUT_UNDEFINED,而随后我们需要动用vkCmdCopyBufferToImage命令将stagingBuffer的内容transfer到Image中,因此我们首先要对Image做布局变换,将其布局变换为VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL。transfer完成后,图像会作为纹理来使用,因此需要将其布局再从VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL变换为VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL。
因此在我们创建纹理图像的时候,做了两次布局变换
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
copyBufferToImage(stagingBuffer, textureImage, static_cast
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
图像的布局变换,用的是vkCmdPipelineBarrier命令,这个命令是用来触发屏蔽操作的,Barrier也是一块非常非常复杂困难的东西,我们后续会讲到。
函数transitionImageLayout在实现上主要就是调用vkCmdPipelineBarrier命令来实现的布局变换1
2
3
4
5
6
7
8
9
10
11VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(
VkCommandBuffer commandBuffer,
VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkDependencyFlags dependencyFlags,
uint32_t memoryBarrierCount,
const VkMemoryBarrier* pMemoryBarriers,
uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier* pBufferMemoryBarriers,
uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier* pImageMemoryBarriers);
这里我们只关心图像,因此最关键的就是pImageMemoryBarriers
srcStageMask 管线的那个阶段最后写入的资源
dstStageMask 管线的那个阶段要从资源读取数据1
2
3
4
5
6
7
8
9
10
11
12
13VkImageMemoryBarrier的结构如下:
typedef struct VkImageMemoryBarrier {
VkStructureType sType; // VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER
const void* pNext;
VkAccessFlags srcAccessMask; // srcStage对图像的操作
VkAccessFlags dstAccessMask; // dstStage对图像的操作
VkImageLayout oldLayout; // 旧的布局
VkImageLayout newLayout; // 新的布局
uint32_t srcQueueFamilyIndex; // 跨队列访问的时候才会用,值为队列的index,否则设置为VK_QUEUE_FAMILY_IGNORED
uint32_t dstQueueFamilyIndex; //
VkImage image; // 要装换布局的图像
VkImageSubresourceRange subresourceRange; // barrier影响到图像的部分
} VkImageMemoryBarrier;
VkImageSubresourceRange 定义了barrier影响到了图像的哪些部分1
2
3
4
5
6
7typedef struct VkImageSubresourceRange {
VkImageAspectFlags aspectMask;
uint32_t baseMipLevel;
uint32_t levelCount;
uint32_t baseArrayLayer;
uint32_t layerCount;
} VkImageSubresourceRange;