Vulkan系列教程—VMA教程(四)—内存预算(Memory Budget)

前言

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

Vulkan学习群:594715138

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

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


当开发一款图形学为主的游戏或者程序的时候,必须避免分配内存的时候超出物理内存承受能力。当内存被过量分配的命令提交之后,不好的事情就会发生(取决于GPU类型,图形驱动,以及操作系统)。

  • 程序可能不会出任何问题。
  • 程序可能会变慢,因为某些GPU的内存(VRAM)会被分配到CPU端的内存种(RAM),那么GPU想访问这块内存就得通过PCI总线。
  • 一个新的内存分配,可能会花费长达几秒钟,甚至可能冻结整个系统。
  • 新的内存分配可能会返回VK_ERROR_OUT_OF_DEVICE_MEMORY
  • 可能会引发GPU的崩溃(crash),称为TDR(Timeout Detection & Recovery),返回的错误是VK_ERROR_DEVICE_LOST

一、Querying for budget(检查内存预算)

为了获得当前系统内存使用情况以及可用的内存预算,使用 vmaGetHeapBudgets()这个VMA函数,返回一个 VmaBudget结构提,里面包含了一些数值变量(都是以bytes)为单位,描述了Vulkan的堆内存的情况。

先明确几个概念:

  • 堆内存(Heap Memory)属于操作系统管辖范围,然后各种程序都有可能再这一块堆内存上拿到显存。
  • 你自己的Vulkan程序的管辖范围,在这里面 ,有的内存分配是你自己管理,有的你可能会托付给VMA。
  • VMA内存范围,是在Vulkan之上来管理,是指一切通过Vma进行Allocate分配的内存。VMA会在堆内存上先分配各种Chunk,然后再每个Chunk上自己再分配Allocation给到我们使用者
//函数定义,这里传入的pBudgets是一个已经分配好的内存数组
VMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets(
    VmaAllocator allocator,
    VmaBudget* pBudgets)

//返回结果结构
typedef struct VmaBudget
{
    
    
    /** 本堆内存当中,已经被VMA分配出去的内存数量(这里是Chunk分配的总数bytes,有的被allocation用了,有的还没被用
    */
    VkDeviceSize blockBytes;

    /** 对于本堆内存来说,已经被VMA分配到Allocation当中的内存有多少,那么
        `blockBytes - allocationBytes`这个减法结果,就是当前剩余的没被Allocation用掉的内存池,当然,由于内存碎片的存在,也有可能代表了一些没法被使用且浪费的内存。
    */
    VkDeviceSize allocationBytes;

    /** 
	这个数值,必须通过开启 `VK_EXT_memory_budget`的Vulkan特性才能获取
    对于整个程序而言(Vulkan)层面上,这块Vulkan的堆内存被使用了多少
    这个数值往往会大于blockBytes,因为这块堆内存不只是VMA再使用,可能还会被
    SwapChain,Pipeline,DescriptorSet,CommandBuffer或者其他不是从VMA当中分配的VkDeviceMemory使用!
    */
    VkDeviceSize usage;

    /**
	这个数值,必须通过开启 `VK_EXT_memory_budget`的Vulkan特性才能获取
	描述了这块堆内存给到你自己的Vulkan程序的可见大小,有可能比 `VkMemoryHeap::size[heapIndex]`要小,因为
	其他应用程序可能也会占用这块内存。
	`budget - usage` 这个减法值就代表了可以被使用的剩余内存量。超过这个数值再去分配就各种问题了。
    */
    VkDeviceSize budget;
} VmaBudget;

注意

  • 这里传入的pBudgets是一个已经分配好的内存数组,里面元素的数量必须大于等于当前显卡的堆内存的数量。
  • allocationBytes这个值可能会大于blockBytes,因为存在内存换页,比如GPU想分配一个内存,结果不够用了,就把没用到的资源放到RAM,然后给新的腾地方,像这种也会被计算到allocationBytes当中。

请注意vmaGetHeapBudgets返回的数值信息不同于 vmaCalculateStats(). vmaGetHeapBudgets(),并且比他们更快。vmaGetHeapBudgets可以每一帧都调用甚至是每一次内存Allocation的分配之前都可以调用,vmaCalculateStats就用的比较少,只是为了得到静态信息,比如你要Debug。

我们应该使用VK_EXT_memory_budget 这个Device扩展去获得Vulkan的预算信息,VMA可以自动的应用这个扩展。当这个扩展没有被开启的时候,分配器的行为不会发生改变,但是当它估计当前的使用以及可用情况的时候,在精确度方面就出现了一些偏差。为了使用这个扩展:

  • 确保VK_EXT_memory_budget (Device扩展)以及VK_KHR_get_physical_device_properties2 (Instance扩展)已经被开启了。
  • 在创建VmaAllocator的时候,开启 VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT标识符。
  • 在每一帧都要调用vmaSetCurrentFrameIndex这个函数,传入当前帧号,这个函数可以每一帧都更新当前的Budget预算。这种更新,是从Vulkan内部获取一次数据,然后本帧的各种Allocation都会依赖于这个数据进行检查,你不需要每一次Allocation都调用Vulkan内部的东西了。

二、Controlling memory usage(控制内存使用)

如果你想限定所有的Allocation操作都能够在Budget预算范围内搞定(比如不能换页,比如不能VRAM转RAM),不超出边界,还是有一些方法的:

首先,当你要本次的分配,需要分配一块全新的内存Chunk的时候,VMA会努力在Budget预算范围内进行分配。如果超出了这个预算范围,可能会分配一个比原定数额小的内存Chunk。即便你是需要分配一个Dedicated(专用)内存,也会被压制在预算范围内,给出一个小的内存块。

第二,如果你要分配的内存大小加上当前已经使用的大小,超出了内存Budget,默认来讲,VMA会把这件事丢给Vulkan去执行,让Vulkan来决定失败还是成功。

你可以通过启用VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT 这个Flags来避免丢给Vulkan决策,这个是VmaAllocationCreateInfo当中的Flags。

使用这个Flag后,当内存分配超出Budget就不会成功。当Lost Allocations这个VMA特性被启用的时候,如果超出边界就会做LOST换页行为(这个我们后面的章节会详细介绍,说白了就是不用的资源先换到RAM)。如果换页都不行了,那么就会丢出VK_ERROR_OUT_OF_DEVICE_MEMORY。这种机制一般都是用于系统不必须的资源上面,比如Texture贴图;当然这种不必须的资源,不能是创建关键资源的时候必须的资源。

最后,你也可以使用 VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT这个flag去保证本次分配必须在当前VMA已经创建的Chunk范围内。如果要分配新的Chunk池子, 那就会以VK_ERROR_OUT_OF_DEVICE_MEMORY的返回失败告终。这个主要是保证了分配速度,因为永远不需要深入Vulkan内部分配新的Chunk内存块了。

注意一点,如果使用了用户**自主内存池( Custom memory pools )**并且把 VmaPoolCreateInfo::minBlockCount设置为大于0的数值,那么就会在不检查Budget的情况下去分配内存Chunk块了。

总结

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

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

网易课堂《Vulkan原理与实战—铸造渲染核武器—基石篇》
在这里插入图片描述

猜你喜欢

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