Vulkan系列教程—VMA教程(二)—选择内存类型


前言

本文为系列系列教程,定时更新,请大家关注。如果需要深入学习Vulkan的同学,可以点击课程链接:

腾讯课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》

网易课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》

Vulkan学习群:594715138


一、选择内存类型

在Vulkan物理设备这一层当中,支持各种各样的堆内存与类型的组合。对于VMA来说,帮助用户选择正确的或者说最优的内存类型这件事儿是一个重要任务。用户可以填写合适的参数到VmaAllocationCreateInfo这个结构体当中。
1 如果你想找到符合你需求的内存类型Index,你可以使用如下函数:vmaFindMemoryTypeIndex(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo().

2 如果你想你想分配一个跟任何VkImage或者VkBuffer不相关(不绑定)的设备内存,那么可以使用vmaAllocateMemory这个函数,但是并不推荐这么使用。vmaAllocateMemoryPages可以生成多个设备内存,这一点对于稀疏绑定(Sparse Binding很有用处。

3 如果你已经创建了一个VkBuffer或者VkImage,而想为其分配内存并且绑定,那么可以使用如下函数:
vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage()。对于绑定操作,可以使用如下函数: vmaBindBufferMemory(), vmaBindImageMemory() 或者他们的扩展版本: vmaBindBufferMemory2(), vmaBindImageMemory2()

4 如果你想一口气完成:buffer或Image的创建、分配内存以及绑定操作,那么就可以直接使用如下函数: vmaCreateBuffer(), vmaCreateImage()。当然,这也是最简单,最值得推荐的使用方式。

当使用3/4两条时,vma内部会根据所需的buffer/image类型来查询合适的内存类型(vkGetBufferMemoryRequirements),然后使用其中的一种类型来分配。

如果没有符合用户需求的内存,就会返回VK_ERROR_FEATURE_NOT_PRESENT

你可以在创建buffer/image的时候使用一个空白的VmaAllocationCreateInfo(用0填满的),这意味着对于内存没有任何要求,虽然如此可行,但是没啥用。

二、Usage类型

最简单的生成内存的方法,在于填充VmaAllocationCreateInfo::usage字段,使用VmaMemoryUsage中的某个类型。这个字段定义了高封装性的,通常用得到的内存类型。比如,我们可以这么写代码:

VkBufferCreateInfo bufferInfo = {
    
     VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = 65536;
bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
 
VmaAllocationCreateInfo allocInfo = {
    
    };

//这里就体现了具体的用法,直接一个GPU_ONLY就能表示旨在GPU访问的显存
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
 
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);
Usage参数 含义描述
VMA_MEMORY_USAGE_UNKNOWN 没有预设的内存使用,可以使用VmaAllocationCreateInfo当中的其他字段来对内存进行规范(比如preferrableFlags或者requiredFlags)
VMA_MEMORY_USAGE_GPU_ONLY 内存只能被GPU使用,在GPU上访问速度是最快的;也意味着从显存(VRAM)上面进行分配,不需要在host端做mapping操作。用途:GPU对其进行读写的设备,比如作为attachments的图片。只会从CPU端传输一次或者特别低频的数据,而且这个数据会被GPU高频读取,比如纹理贴图,VBO,UniformBuffer(注意前面说了CPU低频)以及大部分的只在GPU使用的资源。在某些情况下(比如AMD的256M的VRAM)可以使用HOST_VISIBLE与此并存,所以也可以进行Map后来直接操作数据。
VMA_MEMORY_USAGE_CPU_ONLY 此类内存可以直接通过Map到CPU端进行操作,这个usage绝对保证是HOST_VISIBLE并且是HOST_COHERENT(即时同步),CPU端是uncached状态,就是说不需要flush到GPU。写入操作有可能是Write-Combined类型(也就是说写入一堆数据一起送入GPU)。GPU端也是可以自由访问这种内存,但是会比较慢。用途:用于StagingBuffer(中间传输跳板)
VMA_MEMORY_USAGE_CPU_TO_GPU 此类内存可以从CPU进行Map操作后访问(一定是HOST_VISIBLE),并且尽可能快速地从GPU访问。CPU段Uncached,写入操作可能是Write-Combined。用途:CPU端频繁进行数据写入,GPU端对数据进行读取。比如说纹理,VBO,UniformBuffers,这些Buffer如果每一帧都更新,可以这么使用。
VMA_MEMORY_USAGE_GPU_TO_CPU 此类内存可以通过Map操作在CPU访问(一定是HOST_VISIBLE)并且是拥有Cached状态。用途:被GPU写入并且被CPU读取的buffer/image,比如录屏,HDR的全屏操作等。任何在CPU端随机访问的资源。
VMA_MEMORY_USAGE_CPU_COPY CPU的内存,尽可能不是GPU可访问的,但是一定是HOST_VISIBLE。用途:属于GPU当中,由于换页等操作,临时挪动到CPU的资源,当需要的时候会被换回到GPU显存
VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED 懒惰分配的内存,也就是及说GPU端Memory在分配的时候使用了VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT标识符,也就是按需分配,如果内存不被访问,即便是调用了接口也不会申请内存分配。注意:如果是没有这种标识符对应类型的内存,就会分配失败哦。用途:临时的图片等,比如MSAA抗锯齿的时候会用到,因为可能关掉抗锯齿。此类图片内存一定有关键字:VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT

三、Required and Preferred标识符

你可以在内存生成的时候设置更多的细节,这里介绍两个flags:
VmaAllocationCreateInfo::requiredFlags:表示这次内存分配,必须要满足的标识符。
VmaAllocationCreateInfo::preferredFlags:表示这次内存分配,尽可能满足的标识符,不满足就算了。

这两个flags都是VkMemoryPropertyFlags的类型。比如你想分配一个永远开启mappped状态(一直不unmap)的内存,并且最好是coherent以及cached。那么就可以这么写:

VmaAllocationCreateInfo allocInfo = {
    
    };
allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
allocInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
 
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);

一块内存被创建的时候,是必须满足所有Required类型的Flags,尽可能满足最多的Preferred类型Flags。当然如果你直接使用了VmaAllocationCreateInfo::usage,在VMA内部其实也会转化为一些列的RequiredFlags与PreferredFlags。

四、Explicit memory types(精确的内存分配类型)

即便VMA给我们提供了上述方便的接口,我们还是想直接给到VMA一个内存类型的Index,那么我们就可以填写VmaAllocationCreateInfo::memoryTypeBits这个字段。

本字段的每个bit都代表一个内存类型Index,那么填写后就是告诉VMA这里面点亮的每一种类型都可以用于本次分配,您看着办。

但是大家注意,这个bit位与Index之间的关系是如何的呢?我们看下方代码:

uint32_t memoryTypeIndex = 2;
 
VmaAllocationCreateInfo allocInfo = {
    
    };
allocInfo.memoryTypeBits = 1u << memoryTypeIndex;
 
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);

在这里我们要的是Index = 2的内存,那么就是0x0010,这个掩码,所以就把1左移两位,然后给到memoryTypeBits。

五、Dedicated allocations(专用内存分配)

我们上述分配的内存都属于在一整块已经分配好的内存上,通过vmaAllocation这个对象来记录其Offset以及Size,从而割出来的一块内存。但我们确实可以直接分配一块独立的内存出来,而不是从原有的内存Chunk上割一块。我们可以使用VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT这个Flag,当然VMA在内部也可以决定使用专用内存,在如下情况当中:

  • 当本次分配的Size太大了。
  • VK_KHR_dedicated_allocation扩展被打开,并且系统建议使用专用内存。
  • 当从内存Chunk上分配失败了,但是却从专用内存上分配成功了。

总结

以上就是今天的内容,大家对于vulkan的学习,也可以参考我出品的vulkan系列教程,下面给大家贴出链接。

腾讯课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》

网易课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_50523841/article/details/122330306