Vulkan编程指南翻译 第二章 第一节 CPU内存管理

2017-02-17

第二章 内存与资源 第一节 CPU内存管理

 

你将在本章中学到:

Vulkan如何管理主机和设备内存

在应用程序中如何有效地管理内存

Vulkan如何使用imagesbuffers消费和生产数据

memory是是几乎所有计算机系统做任何操作的基础,也包括Vulkan。在Vulkan里,memory基本上有两种类型:主机memory和设备memoryVulkan操作的所有资源必须被设备内存支持,应用程序需要负责管理内存。此外,内存也被用作主机上存储数据。Vulkan提供了让应用程序管理内存的机会。在本章中,您将学到Vulkan用来管理内存的各种机制。

         主机内存管理

每当Vulkan创建新对象时,它可能需要内存来存储与它们相关的数据。为此,它使用主机内存,可以被CPU访问,是通过malloc或者new调用返回的通常意义的内存。然而,除了正常的分配器,Vulkan有一些特殊的内存分配需求。其中最常见的,是它希望分配的内存被对齐。这是因为一些高性能CPU指令在对齐的内存上工作的最好。若存储CPU端的数据被对齐,Vulkan可以无条件的使用这些高性能指令,提供实质性能优势。

         由于上述要求,Vulkan实现将使用高级分配器。但是,为了某些,甚至全部操作,它还为您的应用程序提供了替换默认分配器的机会。这是通过指定多数的设备创建函数的pAllocator参数来实现的。例如,让我们重新看一遍vkCreateInstance() 函数,它可能是你的应用程序第一个调用的函数。原型如下:

VkResult vkCreateInstance (

const VkInstanceCreateInfo*         pCreateInfo,

const VkAllocationCallbacks*         pAllocator,

VkInstance*                                         pInstance);

pAllocator参数是一个指向VkAllocationCallbacks类型数据的指针。直到目前,我们一直设置pAllocatornullptr,这告诉Vulkan去使用它内部提哦那个的默认内存分配器,而不是应用程序提供的内存分配器。VkAllocationCallbacks书籍结构封装了我们提供的自定义内存分配器。这个数据结构定义如下:

typedef struct VkAllocationCallbacks {

void* pUserData;

PFN_vkAllocationFunction pfnAllocation;

PFN_vkReallocationFunction pfnReallocation;

PFN_vkFreeFunction pfnFree;

PFN_vkInternalAllocationNotification pfnInternalAllocation;

PFN_vkInternalFreeNotification pfnInternalFree;

} VkAllocationCallbacks;

你可以通过VkAllocationCallbacks的定义知道,它基本上是一些函数指针的集合和一个void* pUserData。这个指针可以供应用程序使用。它可以指向任何位置。Vulkan不会dereference它。事实上,它甚至不需要是一个指针。你可以放任何东西在哪儿,只要它放入一个指针占用的内存区。VulkanpUserData做的唯一的事情,就是将它传递到包含VkAllocationCallback指针的回调函数。

         pfnAllocationpfnReallocationpfnFree用于通常的、对象级的内存管理。它们被定义为指向与以下声明匹配的函数的指针:

         void* VKAPI_CALL Allocation(

void* pUserData,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

void* VKAPI_CALL Reallocation(

void* pUserData,

void* pOriginal

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

void VKAPI_CALL Free(

void* pUserData,

void* pMemory);

         注意,这三个函数用一个pUserData作为第一个参数,这是和VkAllocationCallbacks数据结构的pUserData是同一东西。如果你的应用程序使用数据结构来管理内存,这是放置他们的地址的好地方。用一个C ++类实现你的内存分配器(假设你在使用C++),并且把这个类的this指针放到pUserData,是一个合理的方式。

         Allocation函数负责新的内存分配。size参数指定了分配多少byteAlignment参数指定了安几个byte进行要求的内存对齐,这是一个经常被忽视的参数。非常容易和原生的内存分配器malloc挂钩联系起来。如果这么做,你将会发现程序会正常运行一段时间,但是在某个函数中神奇的崩溃。如果你提供自己的allocator,,你需要重视alignment参数。

         最后一个参数allocationScope,,告诉应用程序,内存分配的范围,生命周期是什么样的。它是VkSystemAllocationScope值中的某一个,

VK_SYSTEM_ALLOCATION_SCOPE_COMMAND  意味着分配的内存将只生存于调用Allocationdemand中。因为只在一个command内生存,Vulkan有可能使用它做作为临时的内存分配。

VK_SYSTEM_ALLOCATION_SCOPE_OBJECT  内存分配和一个特定的Vulkan对象关联。在对象被销毁之前,分配的内存一直存在。这种类型的内存分配只发生在command创建(所有以vkCreate开头的函数)期间。

VK_SYSTEM_ALLOCATION_SCOPE_CACHE  意味着分配的内存和内部缓存或者VkPipelineCache对象关联。

VK_SYSTEM_ALLOCATION_SCOPE_DEVICE  意味着分配的内存在整个devcie中都有效。当Vulkan需要和GPU关联的内存,而不是和一个对象关联。比如,该内存分配器在某些blocks中分配内存,有很多对象或许都在这个block内,那么,分配的内存就不能和任何特定对象直接关联。

VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE  意味着分配的内存在一个instance内有效。这个与VK_SYSTEM_ALLOCATION_SCOPE_DEVICE.相似。这种类型的内存分配通常是layer或者Vulkan启动时做的,比如vkCreateInstance()vkEnumeratePhysicalDevices()

pfnInternalAllocationpfnInternalFree函数指针指向Vulkan使用自带分配器时的 备用的函数。他们和pfnAllocationpfnInternalFree的函数签名相同,除了pfnInternalAllocation不返回值,且pfnInternalFree不应该真的释放内存。这些函数仅仅用来通知应用程序管理好Vulkan在使用的内存。这些函数原型如下:

         void VKAPI_CALL InternalAllocationNotification(

void* pUserData,

size_t                                           size,

VkInternalAllocationType        allocationType,

VkSystemAllocationScope       allocationScope);

 

         void VKAPI_CALL InternalFreeNotification(

void*                                             pUserData,

size_t                                           size,

VkInternalAllocationType        allocationType,

VkSystemAllocationScope       allocationScope);

 

         对于pfnInternalAllocationpfnInternalFree提供的信息,你并不能做什么,除了做日志和跟踪应用程序的内存使用量。这些函数指针是可选的,但如果你指定了一个,另外一个也需要指定。如果你不想用,把他们都设置为nullptr即可。

         Listing2.1 展示了一个如何写C++ class作为和Vulkan内存分配回调函数匹配的allocator的例子。因为这些回调函数被Vulkan通过C函数指针调用,所以这些回调函数被声明为该class静态成员函数,然而,真的实现函数被声明为非静态成员函数。

         class allocator

{

public:

// Operator that allows an instance of this class to be used as a

// VkAllocationCallbacks structure

inline operator VkAllocationCallbacks() const

{

VkAllocationCallbacks result;

result.pUserData = (void*)this;

result.pfnAllocation = &Allocation;

result.pfnReallocation = &Reallocation;

result.pfnFree = &Free;

result.pfnInternalAllocation = nullptr;

result.pfnInternalFree = nullptr;

return result;

};

private:

// Declare the allocator callbacks as static member functions.

static void* VKAPI_CALL Allocation(

void* pUserData,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

static void* VKAPI_CALL Reallocation(

void* pUserData,

void* pOriginal,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

static void VKAPI_CALL Free(

void* pUserData,

void* pMemory);

// Now declare the nonstatic member functions that will actually

perform

// the allocations.

void* Allocation(

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

void* Reallocation(

void* pOriginal,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope);

void Free(

void* pMemory);

};

Listing2.2 中展示了该类的实现。它把Vulkan的内存分配函数映射到符合POSIX标准的aligned_malloc函数。注意,这个allocator几乎不会比Vulkan内部大多的默认分配器好,这只是作为一个用自己的代码写回调函数的例子。

void* allocator::Allocation(

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope)

{

return aligned_malloc(size, alignment);

}

 

void* VKAPI_CALL allocator::Allocation(

void* pUserData,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope)

{

return static_cast<allocator*>(pUserData)->Allocation(size,

alignment,

allocationScope);

}

 

void* allocator::Reallocation(

void* pOriginal,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope)

{

return aligned_realloc(pOriginal, size, alignment);

}

void* VKAPI_CALL allocator::Reallocation(

void* pUserData,

void* pOriginal,

size_t size,

size_t alignment,

VkSystemAllocationScope allocationScope)

{

return static_cast<allocator*>(pUserData)->Reallocation(pOriginal,

size,

alignment,

allocationScope);

}

 

void allocator::Free(

void* pMemory)

{

aligned_free(pMemory);

}

 

void VKAPI_CALL allocator::Free(

void* pUserData,

void* pMemory)

{

return static_cast<allocator*>(pUserData)->Free(pMemory);

}

Listing2.2中我们可以看到,静态成员函数内部,可以简单的把pUserData参数静态类型转换回该类的一个实例对象,并调用非静态成员函数。因为非静态和静态函数在同一个编译单元内,非静态函数很有可能被内联了,以致这种实现是很高效的。

如果有任何意见,欢迎留言讨论。


[ 主页 ]
COMMENTS
POST A COMMENT

(optional)



(optional)