资源

更新记录
  • 2024/1/2 增加该文档

  • 2024/3/27 更新该文档

  • 2024/3/27 增加 缓存资源 章节。

  • 2024/3/27 增加 创建缓存 章节。

  • 2024/3/28 增加 vkCreateBuffer 章节。

  • 2024/3/28 增加 VkBufferCreateInfo 章节。

  • 2024/3/28 增加 VkBufferUsageFlagBits 章节。

  • 2024/3/29 更新 VkBufferUsageFlagBits 章节。

  • 2024/3/29 创建缓存 章节下增加 示例 章节。

  • 2024/3/29 增加 VkSharingMode 章节。

  • 2024/3/29 增加 销毁缓存 章节。

  • 2024/3/29 销毁缓存 章节下增加 示例 章节。

  • 2024/3/29 增加 图片资源 章节。

  • 2024/3/31 更新 图片资源 章节。

  • 2024/3/31 增加 创建图片 章节。

  • 2024/3/31 增加 vkCreateImage 章节。

  • 2024/3/31 增加 VkImageCreateInfo 章节。

  • 2024/3/31 增加 VkImageType 章节。

  • 2024/3/31 增加 VkExtent3D 章节。

  • 2024/4/2 增加 VkFormat 章节。

  • 2024/4/2 增加 格式布局 章节。

  • 2024/4/2 增加 数据类型 章节。

  • 2024/4/6 更新 格式布局 章节。

  • 2024/4/6 增加 VkSampleCountFlagBits 章节。

  • 2024/4/6 增加 图片资源逻辑模型 章节。

  • 2024/4/6 增加 VkImageTiling 章节。

  • 2024/4/9 更新 图片资源逻辑模型 章节。

  • 2024/4/10 增加 VkImageUsageFlagBits 章节。

  • 2024/4/13 增加 多级渐远 章节。

  • 2024/4/14 更新 多级渐远 章节。

  • 2024/4/14 更新 VkExtent3D 章节。

  • 2024/4/14 更新 VkImageCreateInfo 章节。

  • 2024/4/14 更新 VkImageUsageFlagBits 章节。

  • 2024/4/14 增加 格式属性 章节。

  • 2024/4/14 更新 VkImageTiling 章节。

  • 2024/4/14 增加 vkGetPhysicalDeviceFormatProperties 章节。

  • 2024/4/15 更新 vkGetPhysicalDeviceFormatProperties 章节。

  • 2024/4/15 增加 VkFormatFeatureFlagBits 章节。

  • 2024/4/16 更新 VkFormatFeatureFlagBits 章节。

  • 2024/4/16 增加 arrayLayers VkImageCreateFlags 章节。

  • 2024/4/16 更新 图片资源逻辑模型 章节。

  • 2024/4/17 更新 arrayLayers VkImageCreateFlags 章节。

  • 2024/4/18 更新 arrayLayers VkImageCreateFlags 章节。

  • 2024/4/18 增加 VkImageCreateFlagBits 章节。

  • 2024/4/18 增加 立方体 章节。

  • 2024/4/20 更新 立方体 章节。

  • 2024/4/23 增加 示例 章节。

  • 2024/4/23 增加 二维纹理 章节。

  • 2024/4/23 增加 图片布局 章节。

  • 2024/4/23 增加 VkImageLayout 章节。

  • 2024/4/25 更新 VkImageLayout 章节。

  • 2024/4/29 更新 二维纹理 章节。

  • 2024/4/29 增加 销毁图片 章节。

  • 2024/4/29 增加 vkDestroyImage 章节。

  • 2024/4/29 vkDestroyImage 章节下增加 示例 章节。

  • 2024/5/7 更新 二维纹理 示例章节。

  • 2024/5/7 增加 三维纹理 示例章节。

  • 2024/5/7 增加 立方体纹理 示例章节。

  • 2024/5/7 增加 二维多级渐远纹理 示例章节。

  • 2024/5/7 增加 多采样二维颜色附件纹理 示例章节。

  • 2024/5/7 增加 深度-模板附件纹理 示例章节。

  • 2024/9/3 修正 VkImageTiling 打印错误。

Vulkan 中只有 2 种资源 :

  • Buffer 缓存资源。一段连续内存的数据集合。

  • Image 图片资源。有特定数据格式的数据块集合。

备注

Vulkan 标准中的资源其实并不只有这 2 种,比如其中的一种资源为 加速结构 ,该资源在说明 Vulkan 硬件实时光追 时会涉及。由于 BufferImage 是最为核心的两个资源所以目前仅涉及这两个资源。

Vulkan 中创建的所有资源都是 资源,换句话说就是,创建的资源仅仅是一个资源句柄,并没有对应存储资源数据的内存。资源需要绑定到合适的设备内存中才具有 完整的一生 (图桓宇给出了一个赞许的大拇指 (๑•̀ㅂ•́)و✧ )。

缓存资源

Vulkan 中使用 VkBuffer 句柄代表缓存资源。其定义如下:

// 由 VK_VERSION_1_0 提供
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer)

创建缓存

缓存资源通过 vkCreateBuffer(...) 函数创建,其定义如下:

vkCreateBuffer

// 由 VK_VERSION_1_0 提供
VkResult vkCreateBuffer(
    VkDevice                                    device,
    const VkBufferCreateInfo*                   pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkBuffer*                                   pBuffer);
  • device 要创建缓存的目标逻辑设备。

  • pCreateInfo 缓存的创建信息。

  • pAllocator 缓存句柄的内存分配器。如果为 nullptr 则使用内置的分配器,否则需要自定义句柄内存分配器。

  • pBuffer 创建的缓存结果。

其中 pCreateInfo 为缓存创建配置信息,对应的 VkBufferCreateInfo 类型定义如下:

VkBufferCreateInfo

// 由 VK_VERSION_1_0 提供
typedef struct VkBufferCreateInfo {
    VkStructureType        sType;
    const void*            pNext;
    VkBufferCreateFlags    flags;
    VkDeviceSize           size;
    VkBufferUsageFlags     usage;
    VkSharingMode          sharingMode;
    uint32_t               queueFamilyIndexCount;
    const uint32_t*        pQueueFamilyIndices;
} VkBufferCreateInfo;
  • sType 该结构体的类型枚举值, 必须VkStructureType::VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO

  • pNext 要么是 NULL 要么指向其他结构体来扩展该结构体。

  • flags 缓存创建的额外标志位参数。

  • size 要创建的缓存大小。单位为字节。

  • usage 用于指定该缓存的用途。

  • sharingMode 当该缓存会被多个设备队列访问时,该参数用于配置该缓存的共享模式。

  • queueFamilyIndexCount 指定 pQueueFamilyIndices 数组中元素数量。

  • pQueueFamilyIndices 用于指定将会访问该缓存的设备队列(族)。如果共享模式 不是 VkSharingMode::VK_SHARING_MODE_CONCURRENT (并行访问)将会忽略该数组。

VkBufferCreateFlags

VkBufferCreateFlags 的有效值被定义在了 VkBufferCreateFlagBits 枚举中。 Vulkan 1.0 标准中在 VkBufferCreateFlagBits 枚举中定义了 稀疏资源 的标志位。由于目前还不会涉及到 稀疏资源 所以暂时先忽略。

其中 VkBufferCreateInfo::usage 用于配置该缓存的用途。在开发时,一个缓存 一定 是由于某些特定功能需求而存在的,底层设备可以在不同的需求(用途)的前提下使用更加高效的内部算法和结构,以此能够得到更加高效的执行效率。比如一个缓存中存储的结构如下:

struct Position
{
   float x;
   float y;
   float z;
};

struct UV
{
   float u;
   float v;
};

struct Vertex
{
   Position position;
   UV uv;
}

std::vector<Vertex> vertices;
vertices.push_back(...);
vertices.push_back(...);

VkBuffer buffer = 创建存储 Vertex 结构的数组缓存(vertices);
vk设置该缓存的内部结构(Vertex);

由于 GPU 上的设备队列都是并行执行的(设备上有很多并行单元),当设备知道该缓存中存储的各个元素结构都相同时,可以并行的一块块的读取各个元素,而不需要像 CPU 那样从头按字节读取。这极大的提高了执行效率。

由于设备队列的并行性,其对于缓存的读写也是并行的,所以需要协调好各个队列对该缓存的读写,否则就会导致缓存数据混乱。如果某资源是某设备队列独享的,这将会省去不必要的跨设备队列间的同步,提高效率。为此,其中的 VkBufferCreateInfo::sharingModeVkBufferCreateInfo::queueFamilyIndexCountVkBufferCreateInfo::pQueueFamilyIndices 就是用于配置各个设备队列对该资源的访问权限,进一步明确设备对该资源的访问方式以提高效率。

其中 VkBufferCreateInfo::usage 的有效值被定义在了 VkBufferUsageFlagBits 枚举中,其定义如下:

VkBufferUsageFlagBits
// 由 VK_VERSION_1_0 提供
typedef enum VkBufferUsageFlagBits {
    VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,
    VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,
    VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,
    VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,
    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,
    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,
    VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,
    VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100
} VkBufferUsageFlagBits;
  • VK_BUFFER_USAGE_TRANSFER_SRC_BIT 该缓存用于数据传输的数据源。

  • VK_BUFFER_USAGE_TRANSFER_DST_BIT 该缓存用于数据传输的目的数据。

  • VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT 该缓存用于存储纹素数据。用于设备读取。

  • VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT 该缓存用于存储纹素数据。用于设备读取和存储。

  • VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT 该缓存用于存储任意格式数据。用于设备读取。

  • VK_BUFFER_USAGE_STORAGE_BUFFER_BIT 该缓存用于存储任意格式数据。用于设备读取和存储。

  • VK_BUFFER_USAGE_INDEX_BUFFER_BIT 该缓存用于存储整型索引数据。

  • VK_BUFFER_USAGE_VERTEX_BUFFER_BIT 该缓存用于存储具有相同结构的顶点数据。

  • VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT 该缓存用于间接数据。用于存储指令参数,设备可一次性读取这些参数。

备注

如上示例 中就是 VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT 用途的典型用例。

备注

VkBufferUsageFlagBits 中各个缓存用途将会在之后的章节逐渐涉及。

纹素

纹素可以简单理解为带有格式的(像素)数据块。比如,可以对像素数据进行如下规定:

一个像素颜色可以由 绿 三种颜色值组成:

_images/rgb.png

规则一

  • 16 位浮点数,有效值范围为 [0.0, 1.0]

  • 绿16 位浮点数,有效值范围为 [0.0, 1.0]

  • 16 位浮点数,有效值范围为 [0.0, 1.0]

规则二

  • 8 位无符号整数,有效值范围为 [0, 255]

  • 绿8 位无符号整数,有效值范围为 [0, 255]

  • 8 位无符号整数,有效值范围为 [0, 255]

由此可以看出,一个像素其内部的数据会根据格式的不同而不同。

其中 VkBufferCreateInfo::sharingMode 有效值定义在 VkSharingMode 枚举中,其定义如下:

VkSharingMode
// 由 VK_VERSION_1_0 提供
typedef enum VkSharingMode {
    VK_SHARING_MODE_EXCLUSIVE = 0,
    VK_SHARING_MODE_CONCURRENT = 1,
} VkSharingMode;
  • VK_SHARING_MODE_EXCLUSIVE 表示该资源为设备队列独享资源。该资源一次只能被一种设备队列族中的队列访问。

  • VK_SHARING_MODE_CONCURRENT 表示该资源为设备队列共享资源。该资源一次能被多种设备队列族中的队列访问。

备注

详细的说明将会在之后的章节展开。

示例

创建一个存储顶点数据的缓存

VkDevice device = 之前创建的逻辑设备;

struct Position
{
   float x;
   float y;
   float z;
};

struct Normal
{
   float x;
   float y;
   float z;
};

struct Color
{
   float r;
   float g;
   float b;
   float a;
};

struct UV
{
   float u;
   float v;
};

struct Vertex
{
   Position position;
   Normal normal;
   Color color;
   UV uv;
}

std::vector<Vertex> vertices;
vertices.push_back(/*position*/{-1, -1, 0}, /*normal*/{0, 0, 1}, /*color*/{1, 0, 0, 1}, /*uv*/{0, 0});
vertices.push_back(/*position*/{ 1, -1, 0}, /*normal*/{0, 0, 1}, /*color*/{0, 1, 0, 1}, /*uv*/{1, 0});
vertices.push_back(/*position*/{-1,  1, 0}, /*normal*/{0, 0, 1}, /*color*/{1, 1, 0, 1}, /*uv*/{0, 1});
vertices.push_back(/*position*/{ 1, -1, 0}, /*normal*/{0, 0, 1}, /*color*/{0, 1, 0, 1}, /*uv*/{1, 0});
vertices.push_back(/*position*/{ 1,  1, 0}, /*normal*/{0, 0, 1}, /*color*/{0, 0, 1, 1}, /*uv*/{1, 1});
vertices.push_back(/*position*/{-1,  1, 0}, /*normal*/{0, 0, 1}, /*color*/{1, 1, 0, 1}, /*uv*/{0, 1});

VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.pNext = nullptr;
buffer_create_info.flags = 0;
buffer_create_info.size = sizeof(Vertex) * vertices.size();
buffer_create_info.usage = VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // 该资源将用于顶点缓存
buffer_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE; // 使用队列独享模式
buffer_create_info.queueFamilyIndexCount = 0;
buffer_create_info.pQueueFamilyIndices = nullptr; // 当使用队列独享模式时,该字段将会被忽略

VkBuffer buffer = VK_NULL_HANDLE;

VkResult result = vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkBuffer 缓存资源创建失败");
}

备注

此时 vertices 中的数据并没有写入 buffer 中,其仅仅用于告诉 Vulkan 我需要多大的( sizeof(Vertex) * vertices.size() )缓存资源,并且 buffer 此时没有与之相关联的底层设备内存,这将会在之后的章节涉及。

销毁缓存

当缓存资源不再需要时就可以通过 vkDestroyBuffer(...) 函数将其销毁,该函数定义如下:

// 由 VK_VERSION_1_0 提供
void vkDestroyBuffer(
    VkDevice                                    device,
    VkBuffer                                    buffer,
    const VkAllocationCallbacks*                pAllocator);
  • device 要销毁的缓存对应所在的逻辑设备。

  • buffer 要销毁的缓存。

  • pAllocator 该缓存的句柄内存分配器。

示例

VkDevice device = 之前创建的逻辑设备;
VkBuffer buffer = 之前创建的缓存;

vkDestroyBuffer(device, buffer, nullptr); // 此处假定缓存创建时,指定的内部分配器

图片资源

Vulkan 中一个图片资源代表相同格式数据块的多维集合,比如 一维/二维/三维 图片等。其通过 VkImage 句柄代表其图片资源,其定义如下:

// 由 VK_VERSION_1_0 提供
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage)

创建图片

图片资源通过 vkCreateImage(...) 函数创建,其定义如下:

vkCreateImage

// 由 VK_VERSION_1_0 提供
VkResult vkCreateImage(
    VkDevice                                    device,
    const VkImageCreateInfo*                    pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkImage*                                    pImage);
  • device 要创建图片对应所在的逻辑设备。

  • pCreateInfo 图片资源的创建配置信息。

  • pCreateInfo 句柄内存分配器。

  • pImage 创建的目标图片句柄。

其中 VkImageCreateInfo 定义如下:

VkImageCreateInfo

// 由 VK_VERSION_1_0 提供
typedef struct VkImageCreateInfo {
    VkStructureType          sType;
    const void*              pNext;
    VkImageCreateFlags       flags;
    VkImageType              imageType;
    VkFormat                 format;
    VkExtent3D               extent;
    uint32_t                 mipLevels;
    uint32_t                 arrayLayers;
    VkSampleCountFlagBits    samples;
    VkImageTiling            tiling;
    VkImageUsageFlags        usage;
    VkSharingMode            sharingMode;
    uint32_t                 queueFamilyIndexCount;
    const uint32_t*          pQueueFamilyIndices;
    VkImageLayout            initialLayout;
} VkImageCreateInfo;
  • sType 该结构体的类型枚举值, 必须VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO

  • pNext 要么是 NULL 要么指向其他结构体来扩展该结构体。

  • flags 创建该图片资源额外的标志位参数。

  • imageType 图片资源的类型。

  • format 该图片资源的纹素格式。

  • extent 该图片资源(各维度上的)大小。

  • mipLevels 多级渐远纹理级别。 必须 大于 0

  • arrayLayers 层级数量。 必须 大于 0

  • samples 采样点数量。

  • tiling 瓦片排布。

  • usage 该图片资源的用途。

  • sharingMode 当该图片会被多个设备队列访问时,该参数用于配置该图片共享模式。

  • queueFamilyIndexCount 指定 pQueueFamilyIndices 数组中元素数量。

  • pQueueFamilyIndices 用于指定将会访问该缓存的设备队列(族)。如果共享模式 不是 VkSharingMode::VK_SHARING_MODE_CONCURRENT (并行访问)将会忽略该数组。

  • initialLayout 该图片的初始布局。

其中 VkImageType 定义如下:

VkImageType
// Provided by VK_VERSION_1_0
typedef enum VkImageType {
    VK_IMAGE_TYPE_1D = 0,
    VK_IMAGE_TYPE_2D = 1,
    VK_IMAGE_TYPE_3D = 2,
} VkImageType;
  • VK_IMAGE_TYPE_1D 一维图片。

  • VK_IMAGE_TYPE_2D 二维图片。

  • VK_IMAGE_TYPE_3D 三维图片。

其中 一维 纹理其本质上就是有相同数据块类型的一维数组:

// 假如纹素结构如下
typedef struct TexelFormat
{
   uint8_t r;
   uint8_t g;
   uint8_t b;
   uint8_t a;
}R8G8B8A8;

// VK_IMAGE_TYPE_1D 图片资源可理解为
TexelFormat images[VkImageCreateInfo.extent.width][1][1]; // 一维图片
// 等价于
TexelFormat images[VkImageCreateInfo.extent.width]; // 一维图片

其中 二维 纹理其本质上就是有相同数据块类型的二维数组:

// 假如纹素结构如下
typedef struct TexelFormat
{
   uint8_t r;
   uint8_t g;
   uint8_t b;
   uint8_t a;
}R8G8B8A8;

// VK_IMAGE_TYPE_2D 图片资源可理解为
TexelFormat images[VkImageCreateInfo.extent.width][VkImageCreateInfo.extent.height][1]; // 二维图片
// 等价于
TexelFormat images[VkImageCreateInfo.extent.width][VkImageCreateInfo.extent.height]; // 二维图片

其中 三维 纹理其本质上就是有相同数据块类型的二维数组:

// 假如纹素结构如下
typedef struct TexelFormat
{
   uint8_t r;
   uint8_t g;
   uint8_t b;
   uint8_t a;
}R8G8B8A8;

// VK_IMAGE_TYPE_3D 图片资源可理解为
TexelFormat images[VkImageCreateInfo.extent.width][VkImageCreateInfo.extent.height][VkImageCreateInfo.extent.depth]; // 三维图片

由此可见图片的各维度的大小是由 VkImageCreateInfo::extent 定义的,其 VkExtent3D 类型定义如下:

VkExtent3D
// 由 VK_VERSION_1_0 提供
typedef struct VkExtent3D {
    uint32_t    width;
    uint32_t    height;
    uint32_t    depth;
} VkExtent3D;
  • width 宽。 必须 大于 0

  • height 高。 必须 大于 0

  • depth 深度。 必须 大于 0

VkImageCreateInfo::imageTypeVkImageType::VK_IMAGE_TYPE_1D 时,其大小规则如下:

  • 维度大小使用 VkExtent3D::width 表示

  • VkExtent3D::height 固定为 1

  • VkExtent3D::depth 固定为 1

VkImageCreateInfo::imageTypeVkImageType::VK_IMAGE_TYPE_2D 时,其大小规则如下:

  • 维度大小使用 VkExtent3D::widthVkExtent3D::height 表示

  • VkExtent3D::depth 固定为 1

VkImageCreateInfo::imageTypeVkImageType::VK_IMAGE_TYPE_3D 时,其大小规则如下:

  • 维度大小使用 VkExtent3D::widthVkExtent3D::heightVkExtent3D::depth 表示

备注

无论是几维图片,在 Vulkan 看来全部都是 三维 图片。只不过一维和二维会在固定维度上会坍缩到 1 。( 智子 表示:来看看我坍缩了几个维度?╭(●`∀´●)╯)

其中 VkImageCreateInfo::format 对应的 VkFormat 枚举类型中有非常多的枚举值,我们这里拿几个经典的进行讲解:

VkFormat
// 由 VK_VERSION_1_0 提供
typedef enum VkFormat {
    VK_FORMAT_UNDEFINED = 0,
    ...
    VK_FORMAT_R8_UNORM = 9,
    VK_FORMAT_R8_SNORM = 10,
    VK_FORMAT_R8_USCALED = 11,
    VK_FORMAT_R8_SSCALED = 12,
    VK_FORMAT_R8_UINT = 13,
    VK_FORMAT_R8_SINT = 14,
    VK_FORMAT_R8_SRGB = 15,
    VK_FORMAT_R8G8_UNORM = 16,
    ...
    VK_FORMAT_R8G8B8_UNORM = 23,
    ...
    VK_FORMAT_R8G8B8A8_UNORM = 37,
    ...
    VK_FORMAT_B8G8R8A8_SRGB = 50,
    ...
    VK_FORMAT_R16_SFLOAT = 76,
    ...
    VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122,
    ...
    VK_FORMAT_D16_UNORM = 124,
    ...
    VK_FORMAT_D32_SFLOAT = 126,
    VK_FORMAT_S8_UINT = 127,
    ...
    VK_FORMAT_D16_UNORM_S8_UINT = 128,
    VK_FORMAT_D24_UNORM_S8_UINT = 129,
    VK_FORMAT_D32_SFLOAT_S8_UINT = 130,
    VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131,
    ...
    VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147,
    ...
    VK_FORMAT_EAC_R11_UNORM_BLOCK = 153,
    ...
    VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157,
    ...
} VkFormat;

其实 VK_FORMAT_UNDEFINED 表示未定义格式,这个没什么好说的,我们现在来说明其他的。可以发现每个枚举值声明基本规则如下:

\[\{VK\_FORMAT\}+\{\_\{格式布局\}\_+\_\{数据类型\}\_\} \times m\]

其中 VK_FORMAT 为枚举声明前缀,我们主要是关心 格式布局数据类型

其中 格式布局 如下:

格式布局

格式布局主要是用于明确该格式下 纹素内部结构

  • R8 拥有 红色通道 数据,占 8 个比特。

  • R11 拥有 红色通道 数据,占 11 个比特。

  • R16 拥有 红色通道 数据,占 16 个比特。

  • R8G8 拥有 红色通道绿色通道 数据,每个通道占 8 个比特。

  • R8G8B8 拥有 红色通道绿色通道蓝色通道 数据,每个通道占 8 个比特。

  • R8G8B8A8 拥有 红色通道绿色通道蓝色通道 数据和 透明度通道 数据,每个通道占 8 个比特。

  • B8G8R8A8 拥有 蓝色通道绿色通道红色通道 数据和 透明度通道 数据,每个通道占 8 个比特。

  • D16 拥有 深度 数据,占 16 个比特。

  • D24 拥有 深度 数据,占 24 个比特。

  • D32 拥有 深度 数据,占 32 个比特。

  • S8 拥有 模板 数据,占 8 个比特。

  • BC ETC EAC ASTC 表示数据为压缩形式。

压缩

使用压缩可以在相似的视觉效果下可以占用更小的存储空间。此时我们先略过压缩格式,主要关注非压缩格式。

深度

深度数据一般都是一个浮点数,其值一般用于表示图形表面到某一平面的距离信息。

_images/depth.png

如上图,深度数据存储着如图红线所示的距离。

模板

模板数据一般都是一个整数,与深度类似,其值一般用于表示图形表面是否(覆盖)映射到对应像素。

其中 数据类型 如下:

数据类型

数据类型主要是用于明确 纹素 内部结构数据类型

  • UNORM 无符号归一化数据。类型为 float 。数据有效范围为 \([0, 1]\)

  • SNORM 有符号归一化数据。类型为 float 。数据有效范围为 \([-1, 1]\)

  • USCALED 无符号整数。数据将会转成 float 。数据有效范围为 \([0, {2^n}-1]\) 。( n格式布局 中各数据所占比特位数)。

  • SSCALED 有符号整数。数据将会转成 float 。数据有效范围为 \([{-2^{n-1}}, {2^{n-1}}-1]\) 。( n格式布局 中各数据所占比特位数)。

  • UINT 有符号整数。数据将会转成 无符号整形 。数据有效范围为 \([0, {2^n}-1]\) 。( n格式布局 中各数据所占比特位数)。

  • SINT 有符号整数。数据将会转成 无符号整形 。数据有效范围为 \([{-2^{n-1}}, {2^{n-1}}-1]\) 。( n格式布局 中各数据所占比特位数)。

  • UFLOAT 无符号浮点数。用于数据包和一些压缩格式中。

  • SFLOAT 有符号浮点数。

  • SRGB 标准颜色空间 R G B 通道为无符号归一化数据(同 UNORM )。但其数据使用 sRGB 的非线性编码标准解析,如果 A 通道存在则同样为无符号归一化数据。

sRGB

sRGB 标准一般用于屏幕显示。现在市面上几乎所有的设备都能够支持显示 sRGB 格式的图像数据。

其中 VkImageCreateInfo::samplesVkSampleCountFlagBits 枚举类型定义如下:

VkSampleCountFlagBits
// 由 VK_VERSION_1_0 提供
typedef enum VkSampleCountFlagBits {
    VK_SAMPLE_COUNT_1_BIT = 0x00000001,
    VK_SAMPLE_COUNT_2_BIT = 0x00000002,
    VK_SAMPLE_COUNT_4_BIT = 0x00000004,
    VK_SAMPLE_COUNT_8_BIT = 0x00000008,
    VK_SAMPLE_COUNT_16_BIT = 0x00000010,
    VK_SAMPLE_COUNT_32_BIT = 0x00000020,
    VK_SAMPLE_COUNT_64_BIT = 0x00000040,
} VkSampleCountFlagBits;
  • VK_SAMPLE_COUNT_1_BIT1 个采样点。即,纹素自身(将分出 1 个子纹素)。

  • VK_SAMPLE_COUNT_2_BIT2 个采样点。即,纹素自身将分出 2 个子纹素。

  • VK_SAMPLE_COUNT_4_BIT4 个采样点。即,纹素自身将分出 4 个子纹素。

  • VK_SAMPLE_COUNT_8_BIT8 个采样点。即,纹素自身将分出 8 个子纹素。

  • VK_SAMPLE_COUNT_16_BIT16 个采样点。即,纹素自身将分出 16 个子纹素。

  • VK_SAMPLE_COUNT_32_BIT32 个采样点。即,纹素自身将分出 32 个子纹素。

  • VK_SAMPLE_COUNT_64_BIT64 个采样点。即,纹素自身将分出 64 个子纹素。

由于像素都是一块块的,并不能像一条线那样丝滑连续,当将连续的数据存入像像素这样的离散数据时,需要对连续数据进行采样,进而确定离散的像素值。像这样将连续数据转成离散数据必定会导致部分原始信息的丢失。在图像上就会产生锯齿。

_images/samples.png

1 个纹素仅进行 1 次采样

如上图中每个格子为一个纹素(像素),其中心的点为采样点。当黄色部分完全覆盖了对应的 采样点 后,对应的纹素才会存储相应的数据,而部分覆盖纹素,但没有覆盖 采样点 的地方将不会存储(采样)任何值。进而导致锯齿。

为了减少锯齿,我们可以将一个纹素分割成多个子纹素来增加采样点,这样之前采样不到的纹素也会随着采样点的增多得到覆盖,进而得到采样,以此来达到抗锯齿的目的。

但越多的采样次数意味着更多的计算量,过多的计算量可能会延长运行时间。

子纹素

纹素会将各子纹素的采样结果根据权重进行汇总,并将汇总结果作为该(顶级)纹素的结果。

_images/multi_samples.png

1 个纹素进行 16 次采样( VkSampleCountFlagBits::VK_SAMPLE_COUNT_16_BIT

如下为 VK_SAMPLE_COUNT_1_BITVK_SAMPLE_COUNT_8_BIT 的成像对比:

_images/sample.jpg

单次采样与 8 次采样对比示意图

图片资源逻辑模型

现在我们来讲解一下如何理解 VkImageCreateInfo 中的各参数,并将他们从逻辑上关联起来,并建立一个易于理解的模型。

其中 VkImageCreateInfo 中与之有关的核心参数如下:

typedef struct VkImageCreateInfo {
   ...
    VkImageType              imageType;
    VkFormat                 format;
    VkExtent3D               extent;
   ...
    uint32_t                 arrayLayers;
    VkSampleCountFlagBits    samples;
   ...
} VkImageCreateInfo;

首先明确一下这几个变量的含义。

图片大小是由如下 2 个参数指定的:

  • imageType 用于指定该图片的维度。一维、二维还是三维图片。

  • extent 用于指定该图片每一个维度的大小。

而图片的每个纹素是由如下 2 个参数指定的:

  • format 用于指定该图片每一个纹素的具体格式。

  • samples 用于指定该图片每一个纹素会被分割成多少个子纹素。

如上这几个参数已经能够定义一个图片资源了。但 VkImageCreateInfo 中还有一个 arrayLayers 参数,说明如下:

  • arrayLayers 用于指定如上配置的图片个数。

也就是说通过 imageTypeformatextentsamples 确定一个图片,使用 arrayLayers 来指定这样的图片有几个。对应 C++ 逻辑代码如下:

struct Image
{
   VkImageType              imageType;
   VkFormat                 format;
   VkExtent3D               extent;
   VkSampleCountFlagBits    samples;
};

struct ImageCreateInfo
{
   Image images[arrayLayers];
};
_images/image_create_info_struct.png

图片资源逻辑结构示意图

arrayLayers

arrayLayers 不可以 随意指定数量,有一些限制。具体见 arrayLayers 与 VkImageCreateFlags 章节。

arrayLayers 与 VkImageCreateFlags

在介绍 VkImageCreateFlags 之前,先来说明一下与 VkImageCreateInfo::flags 无关的 arrayLayers 限制:

  • 如果 VkImageCreateInfo::imageTypeVkImageType::VK_IMAGE_TYPE_2D 并且 VkImageCreateInfo::tilingVkImageTiling::VK_IMAGE_TILING_LINEAR 的话, VkImageCreateInfo::arrayLayers 必须1

  • 如果 VkImageCreateInfo::imageTypeVkImageType::VK_IMAGE_TYPE_3D 的话, VkImageCreateInfo::arrayLayers 必须1

接下来的话让我们看看 VkImageCreateInfo::flags 的有效值,对应的有效值被声明在 VkImageCreateFlagBits 枚举类型中,其定义如下:

VkImageCreateFlagBits
// 由 VK_VERSION_1_0 提供
typedef enum VkImageCreateFlagBits {
    VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001,
    VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,
    VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004,
    VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008,
    VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010,
} VkImageCreateFlagBits;
  • VK_IMAGE_CREATE_SPARSE_BINDING_BIT 表示该图片将会使用 稀疏 内存进行绑定。

  • VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT 表示该图片将会部分使用 稀疏 内存进行绑定。如果指定了该标志位,则 VK_IMAGE_CREATE_SPARSE_BINDING_BIT必须 使用开启。

  • VK_IMAGE_CREATE_SPARSE_ALIASED_BIT 表示该图片将会部分使用 稀疏 内存进行绑定。并且这一部分内存可能同时被另一个图片使用(或部分使用),如果指定了该标志位,则 VK_IMAGE_CREATE_SPARSE_BINDING_BIT必须 使用开启。

  • VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT 表示该图片可用于创建 图片视图 的格式可与该图片的格式不同。对于 多平面 格式, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT 表示 图片视图 可以用于表示图片中的某平面。

  • VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 表示该图片可用于创建 立方体 图片。

图片视图

在通过 vkCreateImage(...) 创建完图片之后,需要创建相应的 图片视图VkImageView )才能被 Vulkan 使用。具体将会在之后的章节讲解。

多平面格式

好像是一种压缩格式,具体没研究过。一般像如下格式是用于多平面格式:

VkFormat::VK_FORMAT_G8B8G8R8_422_UNORM
VkFormat::VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM
VkFormat::VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16

具体咋回事待研究。

其中我们主要关注 VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 这个标志位。该标志位经常用于 立方体 图片:

立方体

所谓 立方体 图片(有时也叫 立方体纹理 英文为 Cubemap )其实就是 6二维 图片拼成的一个盒子。示意图如下:

_images/3d_image_cubemap.png

立方体示意图

而立方体中的图片数据经常用于存储场景的环境信息,比如天空信息。所以也常被称为 天空盒 。示意图如下:

_images/sky_cube_0.png

天空盒示意图

VkImageCreateInf::flags 中指定了 VkImageCreateFlagBits::VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 标志位的话,则说明要创建一个立方体图片,则有如下限制要求:

  • VkImageCreateInf::imageType 必须VkImageType::VK_IMAGE_TYPE_2D

  • VkImageCreateInf::arrayLayers 必须 大于等于 6

  • VkImageCreateInf::extent 中的 widthheight 必须 相等

多级渐远

VkImageCreateInfo 中有一个 mipLevels 参数。该参数用于设置该图片的 多级渐远纹理级别

当使用透视投影(近大远小)相机加看向场景进行渲染时:

  • 离相机近的物体会比较大,占用更多的像素。此时由于离相机近,使用分辨率较高的纹理将会获得更佳清晰的渲染结果。

  • 离相机远的物体会比较小,占用更少的像素。此时由于离相机较远,使用分辨率较高的纹理在如此小范围的像素范围内采样将会导致效果锐化。为了减少这种锐化,最简单的方式就是使用一个相对较低分辨率的图片进行采样。

随着距离采样不同分辨率图片的技术叫做 多级渐远 ,支持这种技术的图片叫做 多级渐远纹理(图片)

如下为 不使用使用 多级渐远纹理的结果示意图:

_images/mip_mapping_off.jpg

无多级渐远效果示意

_images/mip_mapping_anisotropic.jpg

多级渐远效果示意

为了生成一系列低分辨率的图片,需要通过 VkImageCreateInfo::mipLevels 指定要为低分辨率图片分配的级别,每一个级别都对应一张新图片,下一级别图片的分辨率是上一级别图片分辨率的一半。

备注

VkImageCreateInfo::mipLevels1 时表示图片自身即为 一级渐远纹理

如下为一张二维图片的 多级渐远级别4 的多级渐远纹理结构示意图:

  • W 为一级渐远纹理(图片其本身)宽度。

  • H 为一级渐远纹理(图片其本身)高度。

_images/image_level.png

二维图片多级渐远纹理结构示意图

多级渐远纹理内部数据

如上示意图中各级的渐远纹理中每个像素都是有确切图像值的,这些只是帮助您从逻辑上理解多级渐远,但是在实际通过 vkCreateImage(...) 创建带有多级渐远纹理中,图片数据全都是初始值(可能为 0 )。每一级别的多级渐远图片中每个像素具体为何值,需要通过执行 GPU指令 手动运算赋值。这将会在之后的章节进行讲解。

其中 VkImageCreateInfo::tilingVkImageTiling 类型定义如下:

VkImageTiling
// 由 VK_VERSION_1_0 提供
typedef enum VkImageTiling {
    VK_IMAGE_TILING_OPTIMAL = 0,
    VK_IMAGE_TILING_LINEAR = 1,
} VkImageTiling;
  • VK_IMAGE_TILING_OPTIMAL 优化排布。

  • VK_IMAGE_TILING_LINEAR 线性排布。

缓存资源 章节我们已经知道缓存资源在 Host端Device端 其为了更高的效率,内部的结构是不同的,图片资源也是如此。

当使用 VkImageTiling::VK_IMAGE_TILING_OPTIMAL 时,用于指示该图片资源将会使用 Device端 内部偏爱的结构(驱动内部结构)进行创建。这一般在 GPU 上高速并行读写计算时使用。

当使用 VkImageTiling::VK_IMAGE_TILING_LINEAR 时,用于指示该图片资源将会使用 Host端 偏爱的线性结构进行创建。这一般在 CPU 读写图片资源数据时使用。

其中 VkImageCreateInfo::usage 标志位的有效值定义在 VkImageUsageFlagBits 枚举中,其定义如下:

VkImageUsageFlagBits
// 由 VK_VERSION_1_0 提供
typedef enum VkImageUsageFlagBits {
    VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001,
    VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002,
    VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004,
    VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008,
    VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010,
    VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020,
    VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040,
    VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080,
} VkImageUsageFlagBits;
  • VK_IMAGE_USAGE_TRANSFER_SRC_BIT 该图片用于数据传输的数据源。

  • VK_IMAGE_USAGE_TRANSFER_DST_BIT 该图片用于数据传输的目的数据。

  • VK_IMAGE_USAGE_SAMPLED_BIT 该图片用于(纹素)采样(读取)。

  • VK_IMAGE_USAGE_STORAGE_BIT 该图片用于(纹素)数据存储(也可以读)。

  • VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 该图片用于颜色附件。

  • VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT 该图片用于深度-模板附件。

  • VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT 该图片用于临时附件。该附件支持与 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT 属性的(惰性)内存进行交互。

  • VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT 该图片用于输入附件。既可以用于采样(读取),也可以用于存储。与 VK_IMAGE_USAGE_STORAGE_BIT 不同的是可以用于附件。

采样

图片采样就是获取图片中某一坐标位置像素的值。

附件

所有的 附件 都是用于存储 GPU 的输出数据。在 Vulkan 中有 4 种附件:

  • VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 颜色附件。用于存储 GPU 在渲染图形后的输出数据。主要以颜色的形式( rgba 等)进行存储。

  • VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT 深度-模板附件。用于存储 GPU 在渲染图形后输出的深度-模板数据。主要以深度-模板的形式(浮点数-整数)进行存储。

  • VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT 临时附件。主要用于与 惰性内存 进行交互。当图片资源确定只在 GPU 端进行读写时,可以使用该类型。

  • VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT 输入附件。既可以用于采样(读取),也可以用于存储。与 VK_IMAGE_USAGE_STORAGE_BIT 不同的是可以用于附件。与其他附件类型不同的是,该附件类型原生支持 操作。

更多 附件 说明将会在之后的 管线帧缓冲(存) 中进行展开。

图片读写

VkImageUsageFlagBits 中有些枚举值对应的图片用途或都支持读,或都支持写,但不同类型的图片用途在读写途径上不尽相同。这将会在之后的章节展开。

图片布局

VkImageCreateInfo 的结构体中,最后一个成员为 initialLayout ,其用于设置目标图片的 布局Vulkan 之所以在此声明一个布局,其最终目的还是为了提高设备对于该图片的操作效率。 在某些特定场合下,如果图片布局为高效布局,则会提高设备的执行效率 。其中支持的 VkImageLayout 布局枚举定义如下:

VkImageLayout
// 由 VK_VERSION_1_0 提供
typedef 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_KHR_swapchain 提供
    VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002,
} VkImageLayout;
  • VK_IMAGE_LAYOUT_UNDEFINED 未定义布局。

  • VK_IMAGE_LAYOUT_GENERAL 通用布局。

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 图片附件最优布局。

  • VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 深度-模板附件最优布局。

  • VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL 深度-模板只读最优布局。

  • VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 着色器只读最优布局。

  • VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL 数据传输源最优布局。

  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 数据传输目标最优布局。

  • VK_IMAGE_LAYOUT_PREINITIALIZED 数据预初始化布局。

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 显示源布局。

在通过 VkImageCreateInfo 创建图片资源时,对应的 VkImageCreateInfo::initialLayout 必须 为如下两种布局中的一个:

  • VK_IMAGE_LAYOUT_UNDEFINED

  • VK_IMAGE_LAYOUT_PREINITIALIZED

一般创建图片资源时,初始布局都是 VK_IMAGE_LAYOUT_UNDEFINED 。但如果图片资源对应的资源内存中,其有初始数据,并且按照一定布局存储在内存中,但此时还未被驱动初始化(识别),则其初始布局为 VK_IMAGE_LAYOUT_PREINITIALIZED

VK_IMAGE_LAYOUT_PREINITIALIZED 布局一般用于在 Host 端提前写入数据的 线性VkImageTiling::VK_IMAGE_TILING_LINEAR )图片。

VK_IMAGE_LAYOUT_PREINITIALIZED

该图片布局平时用的并不多,当图片数据为 Host 端写入数据后,其 VkImageCreateInfo::initialLayout 可以为 VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED 。之后在使用时将布局转成成目标布局即可。

布局转换

图片布局在图片创建完成之后,可以从当前布局,转换成另一个布局。但 VkImageLayout::VK_IMAGE_LAYOUT_PREINITIALIZED 布局 不能 用于转换的目标布局。

有关如何转换布局将会在之后的章节进行讲解。

像其他类型的布局适用如下情形:

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 该布局适用于 VkImageCreateInfo::usage 中包含 VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 用于颜色附件的图片资源。

  • VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 该布局适用于 VkImageCreateInfo::usage 中包含 VkImageUsageFlagBits::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT 用于深度-模板附件的图片资源。

  • VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL 该布局适用于 只读 深度-模板(附件)的图片资源。

  • VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 该布局适用于 VkImageCreateInfo::usage 中包含 VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT 用于图片采样。

  • VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL 该布局适用于 VkImageCreateInfo::usage 中包含 VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_SRC_BIT 用于数据传输源。

  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 该布局适用于 VkImageCreateInfo::usage 中包含 VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT 用于数据传输目标。

其中 VkImageLayout::VK_IMAGE_LAYOUT_GENERAL 通用布局需要强调一下,如果您对于维护各种布局感到麻烦,可以直接转换成该通用布局。通用布局支持所有设备访问类型。像 google 的 filament 渲染引擎 中基本上都是 转换成通用布局 后进行使用的。其关键代码如下:

// 位于 filament/filament/backend/src/vulkan/VulkanStagePool.cpp
...
VulkanImageUtility::transitionLayout(cmdbuffer, {
         .image = image->image,
         .oldLayout = VulkanLayout::UNDEFINED,
         .newLayout = VulkanLayout::READ_WRITE, // (= VK_IMAGE_LAYOUT_GENERAL)
         .subresources = { aspectFlags, 0, 1, 0, 1 },
     });
 return image;
}

VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

该布局属于交换链的图片扩展布局。将会在之后专门的章节进行讲解。目前只需要知道该布局的图片用于显示器窗口显示即可。

现在基本上将 VkImageCreateInfo 中相关的核心概念过了一遍,但目前还有一个问题需要解决:

问题

VkImageCreateInfo::format 具体应该如何选取正确的格式进行设置?

格式属性

VkFormat 中有各种各样的格式,每种格式都代表着不同的数据布局和数据类型。相应 VkImageCreateInfo::format 的选择也会跟着 VkImageCreateInfo::usage 中指定的图片用途的不同而不同。

为此我们需要知道哪些格式在何种情况下会被使用。这就需要我们知道各种格式的属性。如果我们能够获取某一格式的属性,我们就能知道该格式支持何种使用方式。

Vulkan 中为我们提供了 vkGetPhysicalDeviceFormatProperties(...) 函数,用于获取某一格式的属性数据。其定义如下:

vkGetPhysicalDeviceFormatProperties
// 由 VK_VERSION_1_0 提供
void vkGetPhysicalDeviceFormatProperties(
    VkPhysicalDevice                            physicalDevice,
    VkFormat                                    format,
    VkFormatProperties*                         pFormatProperties);
  • physicalDevice 要查询格式是否在该逻辑设备上支持。

  • format 要查询的格式。

  • pFormatProperties 格式的支持信息。

该函数用于查询 format 格式在 physicalDevice 上的支持情况,支持的信息数据将会写入 pFormatProperties 所指向的内存中。

其中 pFormatPropertiesVkFormatProperties 类型定义如下:

VkFormatProperties
// 由 VK_VERSION_1_0 提供
typedef struct VkFormatProperties {
    VkFormatFeatureFlags    linearTilingFeatures;
    VkFormatFeatureFlags    optimalTilingFeatures;
    VkFormatFeatureFlags    bufferFeatures;
} VkFormatProperties;
  • linearTilingFeatures 中存储着 VkFormatFeatureFlagBits 枚举中定义的特性标志位。用于表示当图片使用 VkImageTiling::VK_IMAGE_TILING_LINEAR 线性排布时,该格式支持的特性。

  • optimalTilingFeatures 中存储着 VkFormatFeatureFlagBits 枚举中定义的特性标志位。用于表示当图片使用 VkImageTiling::VK_IMAGE_TILING_OPTIMAL 优化排布时,该格式支持的特性。

  • bufferFeatures 中存储着 VkFormatFeatureFlagBits 枚举中定义的特性标志位。用于表示当缓存资源中存储对应格式的纹素数据时,该格式支持的特性。

缓存资源中存储对应格式的纹素数据

缓存中可以存储任何形式的数据,缓存当然也可以用于存储一系列纹素数据。

其中 VkFormatFeatureFlags 类型的有效标志位定义在 VkFormatFeatureFlagBits 中,其定义如下:

VkFormatFeatureFlagBits
// 由 VK_VERSION_1_0 提供
typedef enum VkFormatFeatureFlagBits {
    VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT = 0x00000001,
    VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT = 0x00000002,
    VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT = 0x00000004,
    VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000008,
    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT = 0x00000010,
    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT = 0x00000020,
    VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT = 0x00000040,
    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0x00000080,
    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT = 0x00000100,
    VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000200,
    VK_FORMAT_FEATURE_BLIT_SRC_BIT = 0x00000400,
    VK_FORMAT_FEATURE_BLIT_DST_BIT = 0x00000800,
    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT = 0x00001000,
} VkFormatFeatureFlagBits;

如下为 VkFormatProperties::linearTilingFeaturesVkFormatProperties::optimalTilingFeatures 会拥有的标志位:

  • VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT 该格式图片支持采样( VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT )。

  • VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT 该格式图片支持存储( VkImageUsageFlagBits::VK_IMAGE_USAGE_STORAGE_BIT )。

  • VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT 该格式图片支持原子存储。

  • VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT 该格式图片支持颜色附件( VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT )和输入附件( VkImageUsageFlagBits::VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT )。

  • VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT 该格式图片支持颜色附件( VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT )并且支持颜色混合。

  • VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT 该格式图片支持深度-模板附件( VkImageUsageFlagBits::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT )。

  • VK_FORMAT_FEATURE_BLIT_SRC_BIT 该格式图片支持作为 构建Blit )源头数据。

  • VK_FORMAT_FEATURE_BLIT_DST_BIT 该格式图片支持作为 构建Blit )目标数据。

  • VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT 如果同时支持 VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT 特性的话,该格式图片支持 线性 采样。如果同时支持 VK_FORMAT_FEATURE_BLIT_SRC_BIT 特性的话,该格式图片支持 构建Blit )。当支持 VK_FORMAT_FEATURE_SAMPLED_IMAGE_BITVK_FORMAT_FEATURE_BLIT_SRC_BIT 时,则该 VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT 特性也 必须 支持。

如下为 VkFormatProperties::bufferFeatures 会拥有的标志位:

  • VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT 该格式缓存支持存储相应格式的纹素数据用于采样。

  • VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT 该格式缓存支持存储相应格式的纹素数据用于存储。

  • VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT 该格式缓存支持存储相应格式的纹素数据用于原子存储。

  • VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT 该格式缓存支持存储相应格式的顶点缓存数据。

线性采样

为图片采样的一种方式,将会在专门的章节进行讲解。

VkImageTiling::VK_IMAGE_TILING_LINEAR

该线性采样与 VkImageTiling::VK_IMAGE_TILING_LINEAR 不是同一事物,不要搞混。

构建 (Blit)

用于图片与图片之间数据的拷贝和构建,将会在专门的章节进行讲解。

颜色混合

用于图片与图片之间颜色的混合,经常用于实现透明效果。将会在专门的章节进行讲解。

原子操作

原子操作只支持 单通道 格式数据(比如 VK_FORMAT_R8_UNORM 之类的)。

未知

图片的原子操作笔者没有研究过,平时开发也没有碰到过,笔者也不知道具体是什么。需等笔者研究完或某位爱心大佬给出知识点说明。这里只给出笔者已知概念:

  • C++ 中的原子操作为:某一系列操作指令是不可分割的, CPU 在处理这一部分指令时不会执行任何其他操作(挂起等)。这在多线程无锁读写同一数据时会涉及到。

  • 图片的原子操作好像是通过着色器进行的。

着色器

GPU 上执行的代码。将会在专门的章节进行讲解。

顶点缓存

一个缓存(数组),内部的每一个 都是指定的相同格式。用于存储顶点数据(位置、法线等)。将会在专门的章节进行讲解。

销毁图片

销毁一个图片只需要通过调用 vkDestroyImage(...) 函数即可,其定义如下:

vkDestroyImage

// 由 VK_VERSION_1_0 提供
void vkDestroyImage(
    VkDevice                                    device,
    VkImage                                     image,
    const VkAllocationCallbacks*                pAllocator);
  • device 目标逻辑设备。

  • image 要销毁的目标图片。

  • pAllocator 目标图片句柄的分配器。

示例
VkDevice device = 图片资源对应的逻辑设备;
VkImage image = 之前成功创建的图片资源;

vkDestroyImage(device, image, nullptr); // 此处假定图片创建时,指定的内部分配器

示例

二维纹理

在渲染时经常需要对纹理进行采样获取颜色信息。这需要我们准备用于采样的二维纹理:

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_2D;
image_create_info.format = VkFormat::VK_FORMAT_R8G8B8A8_UNORMS; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT | VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}

其中 image_create_info.usage 中设置 VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT 是因为:作为采样纹理,其纹素数据一般都是外部拷贝进来的。该纹理作为数据的转移目的地而存在。

三维纹理

二维纹理 类似,这里创建三维的。

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_3D;
image_create_info.format = VkFormat::VK_FORMAT_R8G8B8A8_UNORMS; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 512;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT | VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}
  • image_create_info.imageType 设置为 VkImageType::VK_IMAGE_TYPE_3D

  • image_create_info.extent.depth 设置为大于 1512

立方体纹理

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = VkImageCreateFlagBits::VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_2D;
image_create_info.format = VkFormat::VK_FORMAT_R8G8B8A8_UNORMS; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 6;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT | VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}
  • image_create_info.flags 设置为 VkImageCreateFlagBits::VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT

  • image_create_info.extent.widthimage_create_info.extent.height 设置为相同的值 512

  • image_create_info.extent.depth 设置为 1

  • image_create_info.arrayLayers 设置为 6

二维多级渐远纹理

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_2D;
image_create_info.format = VkFormat::VK_FORMAT_R8G8B8A8_UNORMS; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 4;
image_create_info.arrayLayers = 1;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT | VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}
  • image_create_info.mipLevels 设置为 4

多采样二维颜色附件纹理

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_2D;
image_create_info.format = VkFormat::VK_FORMAT_R8G8B8A8_UNORMS; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}
  • image_create_info.samples 设置为 VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT

  • image_create_info.usage 设置为 VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT

颜色附件

有关 颜色附件 具体是什么,如何使用将会在之后的章节展开。可以简单理解为:画家的画纸。

深度-模板附件纹理

VkDevice device = 之前创建的逻辑设备;

VkImageCreateInfo image_create_info = {};
image_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VkImageType::VK_IMAGE_TYPE_2D;
image_create_info.format = VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT; // 假如设备支持该格式
image_create_info.extent.width = 512;
image_create_info.extent.height = 512;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_create_info.sharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;

VkImage image = VK_NULL_HANDLE;
VkResult result = vkCreateImage(device, &image_create_info, nullptr, &image);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("VkImage 图片资源创建失败");
}
  • image_create_info.format 设置为 VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT

  • image_create_info.usage 设置为 VkImageUsageFlagBits::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT

深度-模板附件

有关 深度-模板附件 具体是什么,如何使用将会在之后的章节展开。可以简单理解为:用于计算远近物体间的遮挡关系。