VK_KHR_sampler_ycbcr_conversion
在 Vulkan 1.1 中晋升为核心 |
以下所有示例都使用 4:2:0
多平面 Y′CBCR 格式进行说明。
多平面格式
要表示 Y′CBCR 图像,其中 Y'(亮度)数据存储在平面 0 中,CB 蓝色色度差值(“U”)数据存储在平面 1 中,CR 红色色度差值(“V”)数据存储在平面 2 中,应用程序将使用 VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM
格式。
Vulkan 规范分别描述了每个多平面格式的表示及其与每个颜色分量的映射。由于映射和颜色转换与格式分离,Vulkan 在格式中使用“RGB”颜色通道表示法,然后转换描述从这些通道到颜色转换输入的映射。
例如,这允许 VK_FORMAT_B8G8R8_UNORM
图像表示 Y′CBCR 纹素。
-
G
==Y
-
B
==Cb
-
R
==Cr
在映射 RGBA
和 Y′CBCR 格式之间的交换组件时,这可能需要一些额外的关注。
不相交
通常,当应用程序创建 VkImage
时,它只会将其绑定到单个 VkDeviceMemory
对象。如果实现支持给定格式的 VK_FORMAT_FEATURE_DISJOINT_BIT
,则应用程序可以将多个不相交的 VkDeviceMemory
绑定到单个 VkImage
,其中每个 VkDeviceMemory
表示单个平面。
对 Y′CBCR 图像的图像处理操作通常会单独处理通道。例如,对亮度通道应用锐化操作或有选择地对亮度进行去噪。分离平面允许单独处理它们或为不同的最终图像重用未更改的平面数据。
使用不相交图像遵循与将内存正常绑定到图像相同的模式,只是使用了一些新函数。以下是一些伪代码,用于表示新的工作流程
VkImagePlaneMemoryRequirementsInfo imagePlaneMemoryRequirementsInfo = {};
imagePlaneMemoryRequirementsInfo.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT;
VkImageMemoryRequirementsInfo2 imageMemoryRequirementsInfo2 = {};
imageMemoryRequirementsInfo2.pNext = &imagePlaneMemoryRequirementsInfo;
imageMemoryRequirementsInfo2.image = myImage;
// Get memory requirement for each plane
VkMemoryRequirements2 memoryRequirements2 = {};
vkGetImageMemoryRequirements2(device, &imageMemoryRequirementsInfo2, &memoryRequirements2);
// Allocate plane 0 memory
VkMemoryAllocateInfo memoryAllocateInfo = {};
memoryAllocateInfo.allocationSize = memoryRequirements2.memoryRequirements.size;
vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &disjointMemoryPlane0);
// Allocate the same for each plane
// Bind plane 0 memory
VkBindImagePlaneMemoryInfo bindImagePlaneMemoryInfo = {};
bindImagePlaneMemoryInfo0.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT;
VkBindImageMemoryInfo bindImageMemoryInfo = {};
bindImageMemoryInfo.pNext = &bindImagePlaneMemoryInfo0;
bindImageMemoryInfo.image = myImage;
bindImageMemoryInfo.memory = disjointMemoryPlane0;
// Bind the same for each plane
vkBindImageMemory2(device, bindImageMemoryInfoSize, bindImageMemoryInfoArray);
将内存复制到每个平面
即使应用程序未使用不相交的内存,它在将数据复制到每个平面时仍然需要使用 VK_IMAGE_ASPECT_PLANE_0_BIT
。
例如,如果应用程序计划执行 vkCmdCopyBufferToImage
以将单个 VkBuffer
复制到单个非不相交的 VkImage
数据,则 YUV420p
布局的逻辑将部分如下所示
VkBufferImageCopy bufferCopyRegions[3];
bufferCopyRegions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
bufferCopyRegions[0].imageOffset = {0, 0, 0};
bufferCopyRegions[0].imageExtent.width = myImage.width;
bufferCopyRegions[0].imageExtent.height = myImage.height;
bufferCopyRegions[0].imageExtent.depth = 1;
/// ...
// the Cb component is half the height and width
bufferCopyRegions[1].imageOffset = {0, 0, 0};
bufferCopyRegions[1].imageExtent.width = myImage.width / 2;
bufferCopyRegions[1].imageExtent.height = myImage.height / 2;
bufferCopyRegions[1].imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
/// ...
// the Cr component is half the height and width
bufferCopyRegions[2].imageOffset = {0, 0, 0};
bufferCopyRegions[2].imageExtent.width = myImage.width / 2;
bufferCopyRegions[2].imageExtent.height = myImage.height / 2;
bufferCopyRegions[2].imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_2_BIT;
vkCmdCopyBufferToImage(...)
这里值得注意的是,imageOffset
为零,因为它的基础是平面,而不是整个 VkImage
。因此,在使用 imageOffset
时,请确保从平面的基础开始,而不是始终从平面 0 开始。
VkSamplerYcbcrConversion
VkSamplerYcbcrConversion
描述了 Y′CBCR 转换的所有“此处未解释的范围”方面,这些方面在 Khronos 数据格式规范中进行了描述。此处设置的值取决于获取的输入 Y′CBCR 数据以及如何执行到 RGB 颜色空间的转换。
以下是一些伪代码,有助于从 API 的角度了解如何使用它
// Create conversion object that describes how to have the implementation do the {YCbCr} conversion
VkSamplerYcbcrConversion samplerYcbcrConversion;
VkSamplerYcbcrConversionCreateInfo samplerYcbcrConversionCreateInfo = {};
// ...
vkCreateSamplerYcbcrConversion(device, &samplerYcbcrConversionCreateInfo, nullptr, &samplerYcbcrConversion);
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {};
samplerYcbcrConversionInfo.conversion = samplerYcbcrConversion;
// Create an ImageView with conversion
VkImageViewCreateInfo imageViewInfo = {};
imageViewInfo.pNext = &samplerYcbcrConversionInfo;
// ...
vkCreateImageView(device, &imageViewInfo, nullptr, &myImageView);
// Create a sampler with conversion
VkSamplerCreateInfo samplerInfo = {};
samplerInfo.pNext = &samplerYcbcrConversionInfo;
// ...
vkCreateSampler(device, &samplerInfo, nullptr, &mySampler);
combinedImageSamplerDescriptorCount
需要监控的一个重要值是 combinedImageSamplerDescriptorCount
,它描述了实现为每个多平面格式使用的描述符数量。这意味着对于 VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM
,实现可以为使用的每个组合图像采样器使用 1、2 或 3 个描述符。
绑定中的所有描述符都使用相同的最大 combinedImageSamplerDescriptorCount
描述符,以允许实现为绑定中描述符的动态索引使用统一步幅。
例如,考虑一个描述符集布局绑定,其中包含两个描述符和用于多平面格式的不可变采样器,这些多平面格式的 VkSamplerYcbcrConversionImageFormatProperties::combinedImageSamplerDescriptorCount
值分别为 2
和 3
。绑定中有两个描述符,最大 combinedImageSamplerDescriptorCount
为 3
,因此具有此布局的描述符集会从描述符池中消耗 6
个描述符。要创建一个允许分配 4
个具有此布局的描述符集的描述符池,descriptorCount
必须至少为 24
。
目前,没有办法确定最大的 combinedImageSamplerDescriptorCount
可以是多少。实际上,这个值通常是 3
,但在使用一些带有 Alpha 分量的外部格式时,可以为 4
。
以下是一些伪代码,说明如何查询 combinedImageSamplerDescriptorCount
:
VkSamplerYcbcrConversionImageFormatProperties samplerYcbcrConversionImageFormatProperties = { /* ... */ };
VkImageFormatProperties imageFormatProperties = { /* ... */ };
VkImageFormatProperties2 imageFormatProperties2 = { /* ... */ };
// ...
imageFormatProperties2.pNext = &samplerYcbcrConversionImageFormatProperties;
imageFormatProperties2.imageFormatProperties = imageFormatProperties;
VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { /* ... */ };
// ...
imageFormatInfo.format = formatToQuery;
vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &imageFormatInfo, &imageFormatProperties2));
printf("combinedImageSamplerDescriptorCount = %u\n", samplerYcbcrConversionImageFormatProperties.combinedImageSamplerDescriptorCount);