管理Vulkan内存,Vulkan将内存大致划分为两种类型:宿主机内存和设备内存。在此基础上,每种内存类型还可以单独按照属性进一步划分。Vulkan提供了一种透明的机制来显示内部内存的细节以及相关属性。这样的做法在OpenGL中是完全不可能的,后者不允许应用程序显示地控制内存区域和布局。
对比这两种内存类型的话,宿主机内存比设备内存更慢。但是宿主机内存的容量通常更大。另一方面来说,设备内存是直接对物理设备可见的,因此它更有效率也更为快速。下面我们将了解宿主机和设备内存,以及访问它们的方法。
宿主机内存
Vulkan使用宿主机内存来存储API的内部数据结构。Vulkan提供了内存分配器机制,允许应用程序控制宿主机端的内存分配。如果应用程序不使用分配器机制,那么Vulkan将使用一个默认的分配器来管理内存和数据结构。
宿主机内存的管理是通过VkAllocationCallbacks
结构体来完成的,它可以被传递给Vulkan的API函数来实现自定义的宿主机内存管理。例如,指令池的创建(vkCreate-CommandPool)和销毁(vkDestroyCommandPool)API的最后一个参数都是宿主机内存的分配器(即VkAllocationCallbacks指针)。
以下给出了这个结构体的语法定义:
它的成员参数定义如表所示:
本文我们只介绍主要的一些内存分配函数,如表所示。更深入的信息可以参考https://www.khronos.org/registry/vulkan/specs/l.O/apispec.html
。
设备内存
设备内存,即GPU内存,它对于物理设备是直接可见的。物理设备可以直接读取其中的内存区块。设备内存与物理设备之间的关系非常紧密,因此它的性能比宿主机内存更高。图像对象、缓存对象,以及一致变量的缓存对象都是在设备内存端分配的。
单一的物理设备可能有多种类型的内存;根据它们的堆类型以及属性的不同还可能进一步细分。函数vkGetPhysicalDeviceMemoryProperties()
负责查询物理设备上可用的内存堆和内存属性。应用程序非常有必要据此提前了解内存的特性。这样可以依照应用程序本身的逻辑和资源的类型,更好地执行资源的分配。
以下给出了函数vkGetPhysicalDeviceMemoryProperties()
的语法定义:
函数vkGetPhysicalDeviceMemoryProperties的参数域如表所示:
结构体VkPhysicalDeviceMemoryProperties的语法定义如下所示:
结构体vkGetPhysicalDeviceMemoryProperties
提供了以下信息:
- 内存类型:包括当前物理设备上可以访问的可用内存类型的数量(memory-TypeCount)以及可用内存的类型(VkMemoryType)。内存类型是描述一组内存属性的关键信息,例如,我们因此可以判断宿主机内存支持缓存还是不支持缓存。某种内存类型的分配器与该类型的内存堆是相互关联的,后者同样保存在这个结构体的变量当中。
- 内存堆:这里包括了物理设备上可用的内存堆的数量(memoryHeapCount)。它可以用来设置访问堆上的内存空间。每个内存堆都是一个VkMemoryHeap类型的对象,即特定大小的一类内存资源。每个堆可能有不止一种内存类型。
我们可以根据内存类型和内存堆来精确查询物理内存资源的正确大小。这样我们就可以通过设置一系列的属性来管理内存的使用了。
以下给出了结构体VkMemoryHeap
的语法定义:
第一个参数size设置了堆中内存的总大小,内存大小的单位为字节。第二个参数flag是一个按位的掩码变量,类型为VkMemoryHeapFlagBits,它表示堆的属性。以下给出对应的语法定义:
标识量VK_MEMORY_HEAP_DEVICE_LOCAL_BIT表示这个堆属于设备的本地内存。该内存类型可能有多种不同的内存属性(VkMemoryPropertyFlagBits),因此与宿主机的本地内存相比也会有完全不同的性能表现。
VkMemoryType
中包含了内存类型的相关属性标识量(propertyFlags,按位存储)以及堆的索引号(heapIndex)。以下给出它的语法定义:
VkMemoryPropertyFlagBits
可能的枚举标识量如表所示:
分配设备内存
Vulkan中的设备内存分配可以通过API函数vkAllocateMemory()
来完成。如果设备内存的分配成功的话,这个函数的返回值是一个VkDeviceMemory对象。这个对象可以在应用程序中用来访问或者操作设备内存的数据。以下给出了它的语法定义:
这个函数的参数说明如表所示:
这里的VkMemoryAllocateInfo
结构体定义如下:
这个结构体的成员参数的说明如表所示:
这种实现方式同样非常适合子内存空间的分配。举例来说,如果每幅图像需要分配512字节对齐的内存空间,而每个缓存对象是64字节对齐,那么vkAllocate-Memory可以确保完全满足这个需求。它可以返回512字节对齐的设备内存,并且用来分配任何目标类型的对象。
当内存分配完成后,它还处于没有初始化的状态。应用程序需要创建一个VkDevice-Memory对象并且进行进一步的子内存分配,以便获得更好的内存性能。
物理设备内存端的可分配数量取决于具体的设备实现,并且可以通过VkPhysical-DeviceLimits的成员函数maxMemoryAllocationCount查询。如果超出了maxMemoryAllocation-Count的限制,那么vkAllocateMemory的返回值可能是VK_ERROR_TOO_MANY_OBJECTS。
释放设备内存
已经分配的设备内存可以通过函数vkFreeMemory()
进行释放。以下给出了这个函数的语法定义:
函数的参数说明如表所示:
从宿主机访问设备内存
使用vkAllocateMemory函数分配的设备内存只能在设备端进行访问,它对于宿主机来说是不可见的。宿主机只能访问那些支持映射的设备内存类型,即,内存属性包含了VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT标识量的内存对象。
我们通过API函数vkMapMemory()来实现宿主机对设备内存的映射访问。这个函数会返回一个虚拟地址的指针,指向映射后的设备内存区域。以下给出了这个函数的语法定义:
这个函数的参数说明如表所示:
函数vkMapMemory可以立即返回映射内存的指针。它不会主动检查内存区域是不是已经被映射过了。
因此,应用程序需要自己负责管理映射内存对象。因为访问已经映射的内存可能会导致无法预测的行为,甚至导致驱动程序死机。
一旦内存映射完毕之后,我们可以像使用一般的宿主机内存一样使用它,并且更新其中保存的数据。当内存更新完成之后,我们需要结束映射工作,这样设备内存可以将最新的修改内容反射回设备端。内存的结束映射操作通过函数vkUnmapMemory()
来完成。它支持两个参数。第一个参数是类型VkDevice,表示当前内存所在的逻辑设备对象。第二个参数是等待结束映射的设备内存的句柄(VkDeviceMemory)。详细的语法定义如下所示:
void vkUnmapMemory(VkDevice device, VkDeviceMemory memory);
延迟分配内存
通过标识量定义的VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
内存,分配的时候不一定遵循输入的需求大小,而是采用一种更为科学的方式,即内存是根据应用程序当前的需要动态增长的。这种内存一开始可能是0字节的大小,然后随着不断使用慢慢增加。
延迟分配的内存只能用于图像对象理性(VkImage)。这种图像对象必须带有内存类型标识量VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT.。
在某个给定的时刻,延迟分配的内存当前大小可以被赋予某个特定的内存对象(VkDeviceMemory),并且通过函数vkGetDeviceMemoryCommitment()进行查询。这个API函数的语法定义如下:
这个函数支持3个输入参数,如表所示:
酷客网相关文章:
评论前必须登录!
注册