如何在 Vulkan 中创建一个缓冲区来存储多个不同大小的纹理

How do you create one buffer to store multiple textures of different sizes in Vulkan

在我的 Vulkan 应用程序中,我希望有一个内存缓冲区可以存储多个不同大小的纹理。然后,我想要一个 VkImageView 对应于缓冲区中的每个纹理。我不确定如何创建这样的缓冲区,这是我想出的:

// Create images
VkImage images[TEXTURE_COUNT];
for (int i = 0; i < TEXTURE_COUNT, i++) {
    VkImageCreateInfo imageCreateInfo{};

    // Specific to texture i
    imageCreateInfo.extent.width  = ...
    iamgeCreateInfo.extent.height = ...
    
    // Other imageCreateInfo properties are constant across all textures
    ...

    VkCreateImage(device, &imageCreateInfo, nullptr, &images[i]);
}

// Find total size of memory buffer & image offsets
int totalSize = 0;
int offsets[TEXTURE_COUNT];
for (int i = 0; i < TEXTURE_COUNT; i++) {
    VkMemoryRequirements memoryRequirements;
    vkGetImageMemoryRequirements(device, images[i], &memoryRequirements);
    offsets[i] = totalSize;
    totalSize += memoryRequirements.size;
}

// Get memory type index of memory buffer
VkMemoryRequirements firstImageMemoryRequirements;
vkGetImageMemoryRequirements(device, images[0], &firstImageMemoryRequirements);
int memoryTypeIndex = ... // Get memory type index using firstImageMemoryRequirements.memoryTypeBits

// Allocate memory
VkMemoryAllocateInfo memoryAllocateInfo{};
memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memoryAllocateInfo.allocationSize = totalSize;
memoryAllocateInfo.memoryTypeIndex = memoryTypeIndex;
VkMemory memory;
vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &memory)

// Bind images to memory at corresponding offset
VkBindImageMemoryInfo bindImageMemoryInfos[TEXTURE_COUNT];
for (int i = 0; i < TEXTURE_COUNT, i++) {
    VkBindImageMemoryInfo bindImageMemoryInfo{};
    bindImageMemoryInfo.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO;
    bindImageMemoryInfo.image = images[i];
    bindImageMemoryInfo.memory = memory;
    bindImageMemoryInfo.memoryOffset = offsets[i];
    bindImageMemoryInfos[i] = bindImageMemoryInfo;
}
vkBindImageMemory2(device, TEXTURE_COUNT, &bindImageMemoryInfos);

// Create image views
VkImageView imageViews[TEXTURE_COUNT];
for (int i = 0; i < TEXTURE_COUNT; i++) {
    VkImageViewCreateInfo imageViewCreateInfo{};
    imageViewCreateInfo.image = images[i];
    ...

    VkCreateImageView(device, &imageViewCreateInfo, nullptr, &imageViews[i]);
}

// Now I have a bunch of image views tied to each texture,
// where each texture is stored in one memory buffer at a certain offset.

这看起来合理吗,还是这种方法不对?对我来说似乎有点奇怪的一件事是当我获取内存缓冲区的内存类型索引时。为此,您需要一张我有 TEXTURE_COUNT 的图片,因此我只选择第一张图片。每个纹理唯一的图像属性是宽度和高度,所以我希望这不会影响每个图像的内存类型位。我假设每个图像都有相同的内存类型位,所以我可以使用第一张图像的内存类型位来获取内存类型索引。对这个假设的想法也很好。

对于您使用的每个 VkImage,它 必须 存储在适合该特定 VkImage 对象的一块内存中。这意味着 offset/size 必须匹配该图像对象的对齐方式和大小,并且它绑定到的内存类型 必须 是图像可以使用的内存类型之一与.

一起使用

对于您使用的每个 VkImage 对象,必须独立查询。或者通常;两个相同的 VkImage 对象(即:从相同的 VkImageCreateInfo 结构创建)将具有相同的要求,因此如果您重复创建相同的 VkImage,则无需查询它们的要求再次。还有一些其他情况允许具有不同创建参数的图像具有相同的要求,所以如果你想利用它,你需要查看详细信息。

如果您在一个可以控制图像的大小、格式、用途和其他创建参数的环境中操作,那么您可以提前了解这几种图像的要求,并在这些限制范围内工作。否则,您将不得不在分配它们的内存之前查询图像的需求信息,然后在您确切知道您需要什么后为它们分配内存。

或者,您可以根据需要在大型平板中分配内存。也就是说,如果你需要为特定的 VkImage 分配新的内存(要么是因为最后一个 slab 已满,要么是图像需要新的内存类型),那么你分配一大块它,然后你可以稍后再放图像到相同的存储。这要求您跟踪将什么放入了哪些内存块。

代码的一个问题是您没有考虑图像 (VkMemoryRequirements::alignment) 的内存 对齐 要求。您也没有考虑并非所有图像都可以共享相同分配的可能性;你假设他们都可以使用相同的内存类型。

因此您需要相应地更改代码。

话虽这么说,Vulkan 对 VkImage 内存要求的实现施加的限制包括一个声明,该声明有效地说明图像所需的内存类型对于所有颜色格式都是相同的,假设许多其他创建参数相同。所以你不应该担心不同的内存类型只是改变图像的大小或具有不同格式的图像。

可能会让您使用不同内存类型的主要是使用参数(以及颜色与深度格式)。用作渲染目标的图像可以有自己的内存类型。