资源与内存

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

  • 2024/5/11 更新该文档

  • 2024/5/13 更新该文档

  • 2024/5/13 增加 获取支持的设备内存 章节。

  • 2024/5/13 增加 vkGetBufferMemoryRequirements 章节。

  • 2024/5/13 增加 vkGetImageMemoryRequirements 章节。

  • 2024/5/13 增加 VkMemoryRequirements 章节。

  • 2024/5/17 更新 VkMemoryRequirements 章节。

  • 2024/5/18 更新 VkMemoryRequirements 章节。

  • 2024/5/21 更新 VkMemoryRequirements 章节。

  • 2024/5/21 增加 资源与设备内存绑定 章节。

资源 章节中我们知道一个资源仅仅是一个 虚拟资源句柄 ,其本质上并没有相应的内存实体用于存储数据。所以在创建完资源后,需要分配内存并与资源进行绑定,用于之后的数据读写。

根据不同资源的不同配置,相应支持资源的内存策略不尽相同,比如:

  • 当创建图片指定 VkImageCreateInfo::tilingVkImageTiling::VK_IMAGE_TILING_OPTIMAL 的话,一般期望是在 Device 端的本地内存 ( VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ) 上进行内存分配并绑定。

  • 当创建图片指定 VkImageCreateInfo::tilingVkImageTiling::VK_IMAGE_TILING_LINEAR 的话,一般期望是在 Host 端的内存 ( VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ) 上进行内存分配并绑定。

为了能够获取支持资源的内存信息, Vulkan 为我们提供了如下查询接口:

  • vkGetBufferMemoryRequirements(…) 获取支持该缓存资源的内存信息。

  • vkGetImageMemoryRequirements(…) 获取支持该图片资源的内存信息。

获取支持的设备内存

其中 vkGetBufferMemoryRequirements(...)vkGetImageMemoryRequirements(...) 定义如下:

vkGetBufferMemoryRequirements

// 由 VK_VERSION_1_0 提供
void vkGetBufferMemoryRequirements(
    VkDevice                                    device,
    VkBuffer                                    buffer,
    VkMemoryRequirements*                       pMemoryRequirements);
  • device 对应的逻辑设备。

  • buffer 目标缓存。

  • pMemoryRequirements 支持该缓存资源的内存信息。

vkGetImageMemoryRequirements

// 由 VK_VERSION_1_0 提供
void vkGetImageMemoryRequirements(
    VkDevice                                    device,
    VkImage                                     image,
    VkMemoryRequirements*                       pMemoryRequirements);
  • device 对应的逻辑设备。

  • image 目标图片。

  • pMemoryRequirements 支持该图片资源的内存信息。

无论是获取缓存支持的内存信息,还是图片的,其都会将资源支持的设备内存信息写入类型为 pMemoryRequirements 成员中,其类型为 VkMemoryRequirements ,定义如下:

VkMemoryRequirements

// 由 VK_VERSION_1_0 提供
typedef struct VkMemoryRequirements {
    VkDeviceSize    size;
    VkDeviceSize    alignment;
    uint32_t        memoryTypeBits;
} VkMemoryRequirements;
  • size 资源需要分配的设备内存大小。单位为 字节

  • alignment 为该资源绑定的设备内存起始地址 必须 进行内存对齐位数。单位为 字节

  • memoryTypeBits 支持的设备内存索引位域。

其中 memoryTypeBits 成员变量是最重要的设备内存信息。该参数为一个 uint32_t 类型变量,也就是一个 32 位的整形。

设备内存 章节的 VkPhysicalDeviceMemoryProperties 中给出了其定义,如下:

// 由 VK_VERSION_1_0 提供
typedef struct VkPhysicalDeviceMemoryProperties {
    uint32_t        memoryTypeCount;
    VkMemoryType    memoryTypes[VK_MAX_MEMORY_TYPES];
    uint32_t        memoryHeapCount;
    VkMemoryHeap    memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;

再此之前反复强调过 VkPhysicalDeviceMemoryProperties::memoryTypes 数组索引值非常重要,是因为 VkMemoryRequirements::memoryTypeBitsVkPhysicalDeviceMemoryProperties::memoryTypes 有对应关系。其对应关系如下:

对应关系

VkMemoryRequirements::memoryTypeBits 中的 32 个位,如果对应第 i 位为 1 说明 VkPhysicalDeviceMemoryProperties::memoryTypes[i] 对应的设备内存支持用于相应的资源。

VK_MAX_MEMORY_TYPES

由于 VK_MAX_MEMORY_TYPES32 ,其定义如下:

#define VK_MAX_MEMORY_TYPES 32U

所以一个 32 位的 VkMemoryRequirements::memoryTypeBits 完全可以覆盖到所有的 VkPhysicalDeviceMemoryProperties::memoryTypes 对应索引。

由于 VkMemoryRequirements::memoryTypeBits 中是按比特位存储的索引,所以我们需要遍历 32 位的每一位,来确定对应位是否为 1 。示例代码如下:

VkMemoryRequirements memory_requirements = 之前通过 vkGetBufferMemoryRequirements(...)  vkGetImageMemoryRequirements(...) 获得的 VkMemoryRequirements 信息;

uint32_t memory_type_bits = memory_requirements.memoryTypeBits;

std::vector<uint32_t> support_memory_type_indices; // 存储所有支持的设备内存类型索引

for(uint32_t index = 0; index < VK_MAX_MEMORY_TYPES; index++)
{
   if((memory_type_bits & 1) == 1)
   {
      support_memory_type_indices.push_back(index);
   }

   memory_type_bits >>= 1; // 向右移1位,依次遍历所有比特位
}

如上,便得到了所有支持对应资源的设备内存类型索引。之后就可以根据这些索引来筛选出满足需求的设备内存。比如筛选出带有 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT 的设备本地内存:

VkPhysicalDeviceMemoryProperties physical_device_memory_properties = 之前通过 vkGetPhysicalDeviceMemoryProperties(...) 获取到的设备内存信息;

std::vector<uint32_t> support_memory_type_indices = 之前筛选出的所有支持对应资源的设备内存索引;

std::vector<uint32_t> available_device_local_memory_type_indices; // 存储支持 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT 的设备内存类型索引

for(uint32_t index = 0; index < support_memory_type_indices.size(); index++)
{
   uint32_t memory_type_index = support_memory_type_indices[index];
   VkMemoryType memory_type = physical_device_memory_properties.memoryTypes[memory_type_index];

   // 如果对应的设备内存支持 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,则为我们期望的设备内存,存储其索引
   if((memory_type.propertyFlags & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
   {
      available_device_local_memory_type_indices.push_back(memory_type_index);
   }
}

if(!available_device_local_memory_type_indices.empty())
{
   // 找到了既支持资源也支持 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT 的设备内存类型索引
   for(uint32_t index = 0; index < available_device_local_memory_type_indices.size(); index++)
   {
      uint32_t memory_type_index = available_device_local_memory_type_indices[index];
      ... // 使用 memory_type_index 分配内存
   }
}
else
{
   throw std::runtime_error("没找到支持的内存类型");
}

以此类推,我们就可以根据不同的需求,筛选出不同情况下最理想的设备内存索引,并在之后用于 内存分配

资源与设备内存绑定

备注

未完待续