内存¶
更新记录
2024/1/2 增加该文档。
2024/2/17 更新该文档。
2024/2/17 增加
句柄对象的内存分配器
章节。2024/2/17 增加
PFN_vkAllocationFunction
章节。2024/2/17 增加
PFN_vkReallocationFunction
章节。2024/2/21 更新
PFN_vkAllocationFunction
章节。2024/2/21 更新
PFN_vkReallocationFunction
章节。2024/2/21 增加
PFN_vkInternalAllocationNotification
章节。2024/2/21 增加
PFN_vkInternalFreeNotification
章节。2024/2/21 增加
VkSystemAllocationScope
章节。2024/2/21 增加
VkInternalAllocationType
章节。2024/2/21 增加
示例
章节。2024/2/27 增加
设备内存
章节。2024/2/27 增加
vkGetPhysicalDeviceMemoryProperties
章节。2024/3/3 更新
vkGetPhysicalDeviceMemoryProperties
章节。2024/3/3 增加
VkPhysicalDeviceMemoryProperties
章节。2024/3/3 增加
VkMemoryHeap
章节。2024/3/3 增加
VkMemoryType
章节。2024/3/3 更新
设备内存
章节。2024/3/9 更新
VkMemoryType
章节。2024/3/9 增加
VkMemoryPropertyFlagBits
章节。2024/3/10 增加
VkMemoryPropertyFlagBits
章节。2024/3/10 增加
内存分配
章节。2024/3/10 增加
vkAllocateMemory
章节。2024/3/14 更新
vkAllocateMemory
章节。2024/3/14 增加
VkMemoryAllocateInfo
章节。2024/3/14 增加
VkMemoryAllocateInfo
章节下增加示例
章节。2024/3/14 增加
内存回收
章节。2024/3/14 增加
vkFreeMemory
章节。2024/3/14 增加
vkFreeMemory
章节下增加示例
章节。2024/3/15 增加
内存映射
章节。2024/3/16 增加
vkMapMemory
章节。2024/3/16 增加
vkMapMemory
章节下增加示例
章节。2024/3/16 更新
设备内存类型示意图
内容,之前图中有错误。2024/3/16 更新
内存分配
章节中的示例
章节。2024/3/16 增加
内存解映射
章节。2024/3/16 增加
内存解映射
章节下增加示例
章节。2024/3/16 增加
内存同步
章节。2024/3/17 更新
PFN_vkAllocationFunction
章节,更新示例代码,增加通用自定义
代码模块。2024/3/17 更新
PFN_vkReallocationFunction
章节,更新示例代码,增加通用自定义
代码模块。2024/3/17 更新
PFN_vkFreeFunction
章节,更新示例代码,增加通用自定义
代码模块。2024/3/19 更新
PFN_vkAllocationFunction
章节,增加通用自定义
代码模块内存分配说明。2024/3/19 更新
PFN_vkReallocationFunction
章节,增加通用自定义
代码模块内存分配说明。2024/3/19 更新
PFN_vkFreeFunction
章节,增加通用自定义
代码模块内存分配说明。2024/3/23 更新
vkMapMemory
章节,增加示意图。2024/3/23 更新
内存同步
章节。2024/3/24 更新
vkMapMemory
章节。2024/3/24 更新
内存同步
章节。2024/3/24 增加
虚拟内存同步到设备内存
章节。2024/3/24 增加
vkFlushMappedMemoryRanges
章节。2024/3/24 增加
设备内存同步到虚拟内存
章节。2024/3/24
设备内存同步到虚拟内存
章节,增加示例
章节。2024/3/26 更新
内存映射
章节。2024/3/26 增加
vkUnmapMemory
章节。2024/4/13 增加
惰性内存
章节。2024/4/13 增加
vkGetDeviceMemoryCommitment
章节。
Vulkan
中有两种分配内存的途径:
在
vkCreate{对象名称}(...)
或vkDestroy{对象名称}(...)
函数中指定const VkAllocationCallbacks* pAllocator
内存分配器。比如:vkCreateInstance(...)
和vkDestroyInstance(...)
vkCreateDevice(...)
和vkDestroyDevice(...)
该方式是在创建和销毁句柄对象时指定,用于在
内存条
上分配和回收内存。其内部通过malloc(...)
和free(...)
之类的函数进行内存分配和销毁。用于句柄对象
本身的分配和销毁。备注
一般
pAllocator
可以直接指定为nullptr
,用于告诉Vulkan
使用内置的内存分配器。如果 不为
nullptr
,则用于指定自定义内存分配器。
备注
自定义内存分配器常用于内存统计。
通过
vkAllocateMemory(...)
函数分配内存。该方式主要用于在
Host
端和Device
端进行内存分配。主要用于存储GPU
的计算结果。
现在就安顺进行讲解:
句柄对象的内存分配器¶
在创建句柄(对象)时需要指定 const VkAllocationCallbacks* pAllocator
的内存分配器。其中 VkAllocationCallbacks
定义如下:
// 由 VK_VERSION_1_0 提供
typedef struct VkAllocationCallbacks {
void* pUserData;
PFN_vkAllocationFunction pfnAllocation;
PFN_vkReallocationFunction pfnReallocation;
PFN_vkFreeFunction pfnFree;
PFN_vkInternalAllocationNotification pfnInternalAllocation;
PFN_vkInternalFreeNotification pfnInternalFree;
} VkAllocationCallbacks;
pUserData 为用户自定义数据指针。当该分配器中的回调被调用时将会传入
pUserData
作为回调的第一个参数。pfnAllocation 内存分配回调。用于分配内存。
pfnReallocation 内存重分配回调。用于重分配内存。
pfnFree 内存释放回调。用于释放内存。
pfnInternalAllocation 内部内存分配通知回调。该回调由驱动在分配内部内存时调用。仅用于将内部内存分配信息反馈给用户。该回调内部 不应该 分配新内存。
pfnInternalFree 内部内存释放通知回调。该回调由驱动在释放内部内存时调用。仅用于将内部内存释放信息反馈给用户。该回调内部 不应该 释放内存。
其中 PFN_vkAllocationFunction
定义如下:
PFN_vkAllocationFunction¶
// 由 VK_VERSION_1_0 提供
typedef void* (VKAPI_PTR *PFN_vkAllocationFunction)(
void* pUserData,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
pUserData 为用户自定义数据指针。对应
VkAllocationCallbacks::pUserData
。size 要分配的内存大小。单位为
字节
。alignment 要分配内存的
内存对齐
大小。单位为字节
。必须 为2
的幂次方。allocationScope 该内存声明周期所属的分配范围。
该函数回调将返回大小为 size
比特,内存对齐为 alignment
分配的新内存。
如果分配失败,该函数 必须 返回 NULL
。如果分配成功,需要返回空间 最少 为 size
字节,并且指针地址为 alignment
的倍数。
内存对齐
重要
此处简单讲解内存对齐,并不完善,只是说明了基本思想,网上有很多详细资料可供参阅。
处理芯片在读取内存时并不是一比特一比特的读,而是 \(n\) 字节 \(n\) 字节的读取(其中 \(n\) 为 2
的幂次方)。如下结构体:
struct Demo
{
char a; // 占 1 字节
int b; // 占 4 字节
short c; // 占 2 字节
};
比如当 \(n = 4\) 时,也就是一次读取 4
个字节。判定如下:
由于
a
只占1
个字节,而处理器一次性读4
个字节,则a
成员大小将会扩展到4
个字节。其中只有第一个字节为a
成员的有效内存,其他3
个扩展字节用于占位。由于
b
的大小为4
个字节,正好为4
的倍数。则不需要扩展字节就可以直接读。由于
c
的大小小于4
则其处理方式与a
的一样,扩展到4
字节,其中前两个字节为c
成员的有效内存,其他2
个字节用于占位。
示意图如下:
这样处理器在 4
个字节 4
个字节读的时候就能够读取到正确的数据了。
如上就是按照 4
字节进行的内存对齐。
PFN_vkAllocationFunction
是一个函数指针,需要指向一个返回值为 void*
形参为 (void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
的函数。比如:
#include <cstdlib>
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return std::aligned_alloc(alignment, size);
}
PFN_vkAllocationFunction pfn_allocation = &Allocation;
警告
C++
标准中没有定义如何获取 std::aligned_alloc(...)
分配的内存大小函数。需要自己存储。
具体如何存储,可参考 通用自定义
代码模块,该模块给出了一种解决方案。
#include <malloc.h>
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return _aligned_malloc(size, alignment);
}
PFN_vkAllocationFunction pfn_allocation = &Allocation;
#include <malloc.h>
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return memalign(alignment, size);
}
PFN_vkAllocationFunction pfn_allocation = &Allocation;
#include <stdlib.h>
void* AlignedMalloc(size_t size, size_t alignment)
{
size_t meta_point_size = sizeof(void *);
size_t aligned_size = sizeof(size_t);
size_t meta_size = aligned_size + meta_point_size + alignment - 1 + size;
void *meta = malloc(meta_size);
uintptr_t start = (uintptr_t)meta + aligned_size + meta_point_size;
void *aligned_meta = (void *)((start + ((alignment) - 1)) & ~(alignment - 1));
*(void **)((uintptr_t)aligned_meta - meta_point_size) = meta;
*(size_t *)((uintptr_t)aligned_meta - (meta_point_size + aligned_size)) = size;
return aligned_meta;
}
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return AlignedMalloc(size, alignment);
}
PFN_vkAllocationFunction pfn_allocation = &Allocation;
算法说明
该算法分配的对齐内存结构示意图如下:
其中示意图最上面一行标注为各部分所占字节长度:
alignment - 1 用于内存对齐所需的基本占位符长度。该部分数据没用上,仅仅用于占位符。最大为
alignment - 1
,会随着(void *)((start + ((alignment) - 1)) & ~(alignment - 1))
对齐算法中start
的不同而不同。alignment_size 用于存储需要分配的对齐内存长度。也就是
size
的字面值。meta_point_size 用于存储
malloc(...)
分配的原指针。也就是meta
的字面值(指针)。size 对齐内存长度。真正会被使用的对齐内存。
最下面一行标注为核心指针位置:
meta
malloc(...)
分配的原指针。字面值(指针)被存储在meta_point_size
占有的内存中。aligned_meta 被需要的对齐内存指针。作为结果返回。
其中 aligned_meta
满足 Vulkan
要求的对齐内存地址。并作为目标内存返回给 Vulkan
。
备注
这里 aligned_meta
前只存储了 size
和 meta
基本数据,您可以根据需求自定义扩展这些数据存储,一般会抽象出一个 内存头
用于存储该内存分配信息。
其中 PFN_vkReallocationFunction
定义如下:
PFN_vkReallocationFunction¶
// 由 VK_VERSION_1_0 提供
typedef void* (VKAPI_PTR *PFN_vkReallocationFunction)(
void* pUserData,
void* pOriginal,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
pUserData 为用户自定义数据指针。对应
VkAllocationCallbacks::pUserData
。pOriginal 在该内存的基础上进行重分配。
size 要重分配的内存大小。单位为
字节
。alignment 要分配内存的
内存对齐
大小。单位为字节
。必须 为2
的幂次方。allocationScope 该内存声明周期所属的分配范围。
如果分配成功,需要返回空间 最少 为 size
字节,并且 pOriginal
原始内存内的 \([0, min(原始内存大小, 新分配的内存大小)-1]\) 范围的数据需要原封不动的转移至新分配的内存中。
如果新分配的内存大小大于之前的分配,则多出来的内存数据初始值是未定义的。
如果满足如上要求进行了重新单独分配,则之前的内存需要进行回收。
如果 pOriginal
为 空
,则该回调的行为需要与 PFN_vkAllocationFunction
回调一致。
如果 size
为 0
,则该回调的行为需要与 PFN_vkFreeFunction
回调一致。
如果 pOriginal
非空,该分配 必须 确保 alignment
与 pOriginal
分配的 alignment
保持一致。
如果重分配失败,并且 pOriginal
非空,则 不能 回收 pOriginal
之前的内存。
PFN_vkReallocationFunction
是一个函数指针,需要指向一个返回值为 void*
形参为 (void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
的函数。比如:
#include <cstdlib>
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
void* new_memory = std::aligned_alloc(alignment, size);
if(new_memory)
{
memcpy(new_memory, pOriginal, size);// 此处 size 不一定对应 pOriginal 的内存大小,存在一定的问题。需要自己存储管理内存大小。
free(pOriginal);
return new_memory;
}
return nullptr;
}
PFN_vkReallocationFunction pfn_reallocation = &Reallocate;
警告
memcpy(new_memory, pOriginal, size)
中由于标准中没有定义如何获取 memalign(...)
分配的内存大小函数。需要自己存储。所以 size
不一定对应 pOriginal
的内存大小,存在一定的问题。
具体如何存储,可参考 通用自定义
代码模块,该模块给出了一种解决方案。
#include <malloc.h>
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return _aligned_realloc(pOriginal, size, alignment);
}
PFN_vkReallocationFunction pfn_reallocation = &Reallocate;
#include <malloc.h>
#include <algorithm>
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
void* new_memory = memalign(alignment, size);
if(new_memory)
{
memcpy(new_memory, pOriginal, std::min(malloc_usable_size(pOriginal), size));
free(pOriginal);
return new_memory;
}
return nullptr;
}
PFN_vkReallocationFunction pfn_reallocation = &Reallocate;
#include <stdlib.h>
#include <algorithm>
void* AlignedRealloc(void* memory, size_t size, size_t alignment)
{
auto get_aligned_memory_size = [](void *memory) -> size_t
{
return *(size_t *)((uintptr_t)memory - sizeof(void *) - sizeof(size_t));
};
void *new_meta = AlignedMalloc(size, alignment);
memcpy(new_meta, memory, std::min(size, get_aligned_memory_size(memory)));
AlignedFree(memory); // 源码见 PFN_vkFreeFunction 章节中 通用自定义 代码模块
return new_meta;
}
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
return AlignedRealloc(pOriginal, size, alignment);
}
PFN_vkReallocationFunction pfn_reallocation = &Reallocate;
算法说明
该算法分配的对齐内存结构示意图如下:
其中获取 memory
分配大小,直接获取 aligned_size
字段中的数据即可。
其中 PFN_vkFreeFunction
定义如下:
PFN_vkFreeFunction¶
// 由 VK_VERSION_1_0 提供
typedef void (VKAPI_PTR *PFN_vkFreeFunction)(
void* pUserData,
void* pMemory);
pUserData 为用户自定义数据指针。对应
VkAllocationCallbacks::pUserData
。pMemory 要回收的内存指针。
PFN_vkFreeFunction
是一个函数指针,需要指向一个返回值为 void
形参为 (void *pUserData, void *pMemory)
的函数。比如:
#include <cstdlib>
void VKAPI_PTR Free(void *pUserData, void *pMemory)
{
std::free(pMemory);
}
PFN_vkFreeFunction pfn_free = &Free;
#include <malloc.h>
void VKAPI_PTR Free(void *pUserData, void *pMemory)
{
_aligned_free(pMemory);
}
PFN_vkFreeFunction pfn_free = &Free;
#include <malloc.h>
void VKAPI_PTR Free(void *pUserData, void *pMemory)
{
free(pMemory);
}
PFN_vkFreeFunction pfn_free = &Free;
#include <stdlib.h>
void AlignedFree(void* memory)
{
auto get_aligned_meta = [](void* memory) -> void*
{
return (((void **)pMemory)[-1]);
};
free(get_aligned_meta(memory));
}
void VKAPI_PTR Free(void *pUserData, void *pMemory)
{
AlignedFree(pMemory);
}
PFN_vkFreeFunction pfn_free = &Free;
算法说明
该算法分配的对齐内存结构示意图如下:
其中获取 memory
之前通过 malloc(...)
分配的原指针,直接获取 meta_point_size
字段中的数据即可。
其中 PFN_vkInternalAllocationNotification
定义如下:
PFN_vkInternalAllocationNotification¶
// 由 VK_VERSION_1_0 提供
typedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)(
void* pUserData,
size_t size,
VkInternalAllocationType allocationType,
VkSystemAllocationScope allocationScope);
pUserData 为用户自定义数据指针。对应
VkAllocationCallbacks::pUserData
。size 分配的内存大小。单位为
字节
。allocationType 分配的类型。
allocationScope 该内存声明周期所属的分配范围。
该函数回调仅仅用于纯信息返回。
其中 PFN_vkInternalFreeNotification
定义如下:
PFN_vkInternalFreeNotification¶
// 由 VK_VERSION_1_0 提供
typedef void (VKAPI_PTR *PFN_vkInternalFreeNotification)(
void* pUserData,
size_t size,
VkInternalAllocationType allocationType,
VkSystemAllocationScope allocationScope);
pUserData 为用户自定义数据指针。对应
VkAllocationCallbacks::pUserData
。size 回收的内存大小。单位为
字节
。allocationType 分配的类型。
allocationScope 该内存声明周期所属的分配范围。
该函数回调仅仅用于纯信息返回。
每一次分配都对应的 allocationScope
分配范围用于定义此次分配与之相关的对象。有效的枚举值被定义在了 VkSystemAllocationScope
中。其定义如下:
VkSystemAllocationScope¶
// 由 VK_VERSION_1_0 提供
typedef enum VkSystemAllocationScope {
VK_SYSTEM_ALLOCATION_SCOPE_COMMAND = 0,
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT = 1,
VK_SYSTEM_ALLOCATION_SCOPE_CACHE = 2,
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE = 3,
VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE = 4,
} VkSystemAllocationScope;
VK_SYSTEM_ALLOCATION_SCOPE_COMMAND 表示此次分配作用于
Vulkan
指令。VK_SYSTEM_ALLOCATION_SCOPE_OBJECT 表示此次分配作用于
Vulkan
对象创建或使用。VK_SYSTEM_ALLOCATION_SCOPE_CACHE 表示此次分配作用于
VkPipelineCache
或者 ``VkValidationCacheEXT `` 对象。VK_SYSTEM_ALLOCATION_SCOPE_DEVICE 表示此次分配作用于
Vulkan
的设备。VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE 表示此次分配作用于
Vulkan
的实例。
其中作为 pfnInternalAllocation
和 pfnInternalFree
回调函数形参的 allocationType
有效的枚举值被定义在了 VkInternalAllocationType
中。其定义如下:
VkInternalAllocationType¶
// 由 VK_VERSION_1_0 提供
typedef enum VkInternalAllocationType {
VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE = 0,
} VkInternalAllocationType;
VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE 表示此次分配作用于
Host
端程序。
示例¶
这里给出 Windows
平台和 通用自定义
代码完整示例, 其他平台以此类推。
#include <malloc.h>
size_t memory_in_use = 0; // 统计内存使用大小
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
memory_in_use += size;
return _aligned_malloc(size, alignment);
}
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
memory_in_use -= _aligned_msize(pOriginal, alignment, 0);
memory_in_use += size;
return _aligned_realloc(pOriginal, size, alignment);
}
void *VKAPI_PTR Free(void *pUserData, void *pMemory)
{
memory_in_use -= _aligned_msize(pMemory, alignment, 0);
return _aligned_free(pMemory);
}
void VKAPI_PTR InternalAllocationNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope)
{
}
void VKAPI_PTR InternalFreeNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope)
{
}
VkAllocationCallbacks GetVkAllocationCallbacks(void* pUserData)
{
VkAllocationCallbacks vk_allocation_callbacks = {};
vk_allocation_callbacks.pUserData = pUserData;
vk_allocation_callbacks.pfnAllocation = &Allocation;
vk_allocation_callbacks.pfnReallocation = &Reallocate;
vk_allocation_callbacks.pfnFree = &Free;
vk_allocation_callbacks.pfnInternalAllocation = &InternalAllocationNotification;
vk_allocation_callbacks.pfnInternalFree = &InternalFreeNotification;
return vk_allocation_callbacks;
}
VkInstanceCreateInfo instance_create_info = 之前填写的创建信息;
VkAllocationCallbacks allocation_callbacks = GetVkAllocationCallbacks(nullptr);
VkInstance instance = VK_NULL_HANDLE;
VkResult result = vkCreateInstance(&instance_create_info, &allocation_callbacks, &instance);
if (result != VK_SUCCESS)
{
throw std::runtime_error("VkInstance 创建失败");
}
// 缤纷绚丽的 Vulkan 程序 ...
vkDestroyInstance(instance, &allocation_callbacks);
void* AlignedMalloc(size_t size, size_t alignment)
{
size_t meta_point_size = sizeof(void *);
size_t aligned_size = sizeof(size_t);
size_t meta_size = aligned_size + meta_point_size + alignment - 1 + size;
void *meta = malloc(meta_size);
uintptr_t start = (uintptr_t)meta + aligned_size + meta_point_size;
void *aligned_meta = (void *)((start + ((alignment) - 1)) & ~(alignment - 1));
*(void **)((uintptr_t)aligned_meta - meta_point_size) = meta;
*(size_t *)((uintptr_t)aligned_meta - (meta_point_size + aligned_size)) = size;
return aligned_meta;
}
void AlignedFree(void* memory)
{
auto get_aligned_meta = [](void* memory) -> void*
{
return (((void **)pMemory)[-1]);
};
free(get_aligned_meta(memory));
}
void* AlignedRealloc(void* memory, size_t size, size_t alignment)
{
auto get_aligned_memory_size = [](void *memory) -> size_t
{
return *(size_t *)((uintptr_t)memory - sizeof(void *) - sizeof(size_t));
};
void *new_meta = AlignedMalloc(size, alignment);
memcpy(new_meta, memory, std::min(size, get_aligned_memory_size(memory)));
AlignedFree(memory);
return new_meta;
}
size_t GetAlignedMemorySize(void* memory)
{
return *(size_t *)((uintptr_t)memory - sizeof(void *) - sizeof(size_t));
}
size_t memory_in_use = 0; // 统计内存使用大小
void *VKAPI_PTR Allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
memory_in_use += size;
return AlignedMalloc(size, alignment);
}
void *VKAPI_PTR Reallocate(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
memory_in_use -= GetAlignedMemorySize(pOriginal);
memory_in_use += size;
return AlignedRealloc(pOriginal, size, alignment);
}
void VKAPI_PTR Free(void *pUserData, void *pMemory)
{
memory_in_use -= GetAlignedMemorySize(pMemory);
AlignedFree(pMemory);
}
VkAllocationCallbacks GetVkAllocationCallbacks(void* pUserData)
{
VkAllocationCallbacks vk_allocation_callbacks = {};
vk_allocation_callbacks.pUserData = pUserData;
vk_allocation_callbacks.pfnAllocation = &Allocation;
vk_allocation_callbacks.pfnReallocation = &Reallocate;
vk_allocation_callbacks.pfnFree = &Free;
vk_allocation_callbacks.pfnInternalAllocation = &InternalAllocationNotification;
vk_allocation_callbacks.pfnInternalFree = &InternalFreeNotification;
return vk_allocation_callbacks;
}
VkInstanceCreateInfo instance_create_info = 之前填写的创建信息;
VkAllocationCallbacks allocation_callbacks = GetVkAllocationCallbacks(nullptr);
VkInstance instance = VK_NULL_HANDLE;
VkResult result = vkCreateInstance(&instance_create_info, &allocation_callbacks, &instance);
if (result != VK_SUCCESS)
{
throw std::runtime_error("VkInstance 创建失败");
}
// 缤纷绚丽的 Vulkan 程序 ...
vkDestroyInstance(instance, &allocation_callbacks);
设备内存¶
Vulkan
标准规定了两种设备内存:
Host 端内存 一般表示主板内存条上的内存。
Device 端内存 一般表示
GPU
设备内部使用的内存。
这些设备内存根据不同特性又分为两种类型:
Host 端内存,但可被 Device 端访问 这类内存的前提是在主板的内存条上,并且这部分内存可被
GPU
访问。Device 端独占内存
GPU
设备自身携带的专有内存。数据在该内存中将会有更高的性能。
其示意图如下:
重要
不管内存是内存条上的还是物理设备上的,只要能被 Vulkan
识别并使用的内存都叫做 设备内存
。
由于 Vulkan
支持多种类型的内存,所以需要先通过 vkGetPhysicalDeviceMemoryProperties(...)
获取支持的内存信息。其定义如下:
vkGetPhysicalDeviceMemoryProperties¶
// 由 VK_VERSION_1_0 提供
void vkGetPhysicalDeviceMemoryProperties(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMemoryProperties* pMemoryProperties);
physicalDevice 要获取设备内存所对应的物理设备。
pMemoryProperties 返回设备内存信息。
其中 pMemoryProperties
将会写入 physicalDevice
所对应设备的所有可访问内存信息,有关 VkPhysicalDeviceMemoryProperties
定义如下:
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;
memoryTypeCount 支持的内存类型数量。
memoryTypes 有效元素个数为
memoryTypeCount
的内存类型信息数组。memoryHeapCount 支持的内存堆数量。
memoryHeaps 有效元素个数为
memoryHeapCount
的内存堆信息数组。
VK_MAX_MEMORY_TYPES 和 VK_MAX_MEMORY_HEAPS
#define VK_MAX_MEMORY_TYPES 32U
#define VK_MAX_MEMORY_HEAPS 16U
内存堆
所谓 堆
其实就是一大块连续的容器,当分配内存时,操作系统会尝试从一大块容器中分配连续并且大小合适的小容器返回给用户,之后用户就可以使用这部分容器读写数据了。
在 Vulkan
中我们知道内存堆可分为两种:
Host 端
Device 端
其中 memoryHeaps
中就是用于获取具体内存堆是哪一种。其中 VkMemoryHeap
定义如下:
VkMemoryHeap¶
// 由 VK_VERSION_1_0 提供
typedef struct VkMemoryHeap {
VkDeviceSize size;
VkMemoryHeapFlags flags;
} VkMemoryHeap;
size 该堆大小。单位为字节。
flags 该堆类型标志位。
其中 flags
就是用于指示该堆的类型。其有效值定义于 VkMemoryHeapFlagBits
中,如下:
VkMemoryHeapFlagBits¶
// Provided by VK_VERSION_1_0
typedef enum VkMemoryHeapFlagBits {
VK_MEMORY_HEAP_DEVICE_LOCAL_BIT = 0x00000001,
} VkMemoryHeapFlagBits;
VK_MEMORY_HEAP_DEVICE_LOCAL_BIT 该堆为设备端独占内存。
备注
有时 VkMemoryHeap::flags
为 0
,该值并没有定义于 VkMemoryHeapFlagBits
中。此时一般认为该内存堆为 Host
端内存。
如下,为一种可能的设备内存堆获取结果:
其中每个堆自身可以包含一到多个类型的内存,堆上的内存类型信息被定义在 memoryTypes
中,其 VkMemoryType
定义如下:
VkMemoryType¶
// 由 VK_VERSION_1_0 提供
typedef struct VkMemoryType {
VkMemoryPropertyFlags propertyFlags;
uint32_t heapIndex;
} VkMemoryType;
propertyFlags 内存类型标志位。
heapIndex 对应的
memoryHeaps
堆索引。
其中 propertyFlags
有效值被定义在了 VkMemoryPropertyFlagBits
枚举中,其定义如下:
VkMemoryPropertyFlagBits¶
// 由 VK_VERSION_1_0 提供
typedef enum VkMemoryPropertyFlagBits {
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
} VkMemoryPropertyFlagBits;
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT 表示在此内存类型上分配的内存可被物理设备高效访问。只有对应的堆为
VK_MEMORY_HEAP_DEVICE_LOCAL_BIT
才会有该内存类型。VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT 表示在此内存类型上分配的内存可被
Host
端通过vkMapMemory(...)
函数进行映射,进而进行访问。VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 表示在此内存类型上分配的内存将会自动进行同步,不需要手动调用
vkFlushMappedMemoryRanges(...)
和vkInvalidateMappedMemoryRanges(...)
来进行内存同步。VK_MEMORY_PROPERTY_HOST_CACHED_BIT 表示在此内存类型上分配的内存为
缓存
(高速缓存)内存,Host
端访问非缓存
内存要比访问缓存
内存慢。但是非缓存
内存总是同步内存
(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
)。VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT 表示在此内存类型上分配的内存只有物理设备可访问。内存类型不能同时为
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
和VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
。此外其底层内存将会用于惰性内存
。
备注
有时 VkMemoryType::propertyFlags
为 0
,该值并没有定义于 VkMemoryPropertyFlagBits
中。此时一般认为该内存堆为 Host
端内存(纯内存条上的内存)。
内存同步
所谓内存同步,就是将内存公开给 目标端
,使得目标端能够看见完整的最新内容并访问。
如果在 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
类型内存上进行内存分配,则这部分内存将会自动进行内存同步,否则需要手动进行内存同步。
具体如何进行内存同步将会在之后的章节进行讲解。
惰性内存¶
当使用 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
类型分配内存时,表示底层分配为 惰性内存
。所谓惰性内存表示:在该内存分配时,其底层内存占用大小可以为 0
也可以为申请的内存大小。当该内存被需要时,其内存大小会随着需求单调增加。
备注
某些设备 没有 提供支持 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
类型的内存。所以该类型内存平时用的不多。
且惰性内存有如下限制:
只有
Device
端能够访问该内存分配的
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
惰性内存类型中不能包含VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
(VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
与VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
为互斥关系,不能同时存在)。只能用于
VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT
的图片进行绑定(这意味着缓存
资源不存在使用惰性内存的情况)。(具体可参阅 资源 文档)。
查询一个惰性内存挡墙占有的内存大小,可通过 vkGetDeviceMemoryCommitment(...)
函数获取到,其定义如下:
vkGetDeviceMemoryCommitment¶
// 由 VK_VERSION_1_0 提供
void vkGetDeviceMemoryCommitment(
VkDevice device,
VkDeviceMemory memory,
VkDeviceSize* pCommittedMemoryInBytes);
device 对应的逻辑设备。
memory 对应的设备内存。其 必须 以
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
内存类型进行分配的惰性内存。pCommittedMemoryInBytes 指定的
memory
当前的大小将会写入其中。单位为字节
。
备注
驱动会随时更新占用的大小,所以该函数返回的惰性内存大小值将会在不久失效。
如下,为一种可能的 设备内存类型
获取结果:
从如上示例可看出,不同的 VkMemoryType::propertyFlags
之间可以有重叠的 VkMemoryPropertyFlagBits
,但是两两 VkMemoryType
不会有完全相同的 propertyFlags
。 Vulkan
中是根据不同的 VkMemoryType::propertyFlags
对内存进行分类的。
备注
有些设备的 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
类型内存也会带有 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
、 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
属性。这表示该设备专用内存可以被 Host
端直接访问。这种情况多见于移动端,某些 PC
端也可能出现该情况。
重要
VkPhysicalDeviceMemoryProperties::memoryTypes[i]
中的 i
非常重要,内存的分配主要是通过指定该索引进行分配。
内存分配¶
通过之前 vkGetPhysicalDeviceMemoryProperties(...)
函数我们可以获取到设备的内存信息,现在我们就可以通过这些信息进行内存分配了。为此 Vulkan
为我们提供了 vkAllocateMemory(...)
函数进行内存分配。该函数定义如下:
vkAllocateMemory¶
// 由 VK_VERSION_1_0 提供
VkResult vkAllocateMemory(
VkDevice device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMemory);
device 分配内存的目标设备。
pAllocateInfo 内存分配信息。
pAllocator 句柄内存分配器。
pMemory 分配的内存句柄。
其中 pAllocateInfo
用于指定内存的分配信息, pAllocator
用于指定创建 pMemory
内存句柄时的分配器。
其中主要的内存分配信息被定义在了 pAllocateInfo
,对应的 VkMemoryAllocateInfo
定义如下:
VkMemoryAllocateInfo¶
// 由 VK_VERSION_1_0 提供
typedef struct VkMemoryAllocateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
sType 是该结构体的类型枚举值, 必须 是
VkStructureType::VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
。pNext 要么是
NULL
要么指向其他结构体来扩展该结构体。allocationSize 要分配的内存大小。单位为
字节
。memoryTypeIndex 分配内存的目标内存类型索引。
其中 memoryTypeIndex
尤为重要,用于指定在 memoryTypes[memoryTypeIndex]
对应的内存类型上进行内存分配,对应分配的堆为 memoryHeaps[memoryTypes[memoryTypeIndex].heapIndex]
。
由于每个 memoryTypes
都有着不同的属性,所以一般会根据功能需求在某个内存类型上进行分配。
示例¶
比如在设备专用内存中分配内存(根据 设备内存类型示意图 中的情况):
VkDevice device = 之前创建的逻辑设备;
struct Color
{
float r;
float g;
float b;
Color(float r, float g, float b)
{
this->r = r;
this->g = g;
this->b = b;
}
};
std::vector<Color> colors;
colors.push_back(Color(0, 0, 0));
colors.push_back(Color(0, 0, 1));
colors.push_back(Color(0, 1, 0));
colors.push_back(Color(0, 1, 1));
colors.push_back(Color(1, 0, 0));
colors.push_back(Color(1, 0, 1));
colors.push_back(Color(1, 1, 0));
colors.push_back(Color(1, 1, 1));
VkMemoryAllocateInfo memory_allocate_info = {};
memory_allocate_info.sType = VkStructureType::VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = sizeof(Color) * colors.size();
memory_allocate_info.memoryTypeIndex = 1; // 对应 VkPhysicalDeviceMemoryProperties::memoryTypes[1]
VkDeviceMemory device_memory = VK_NULL_HANDLE;
VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &device_memory);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 内存创建失败");
}
内存回收¶
当内存成功分配之后,一般会对该内存进行一些列写入和读取操作,当该内存不再被需要时,就可以将该内存通过调用 vkFreeMemory(...)
进行回收了。其定义如下:
vkFreeMemory¶
// 由 VK_VERSION_1_0 提供
void vkFreeMemory(
VkDevice device,
VkDeviceMemory memory,
const VkAllocationCallbacks* pAllocator);
device 要回收
memory
在分配时所对应的逻辑设备。memory 要回收的目标内存。
pAllocator 要回收
memory
在分配时所对应的句柄分配器。
内存回收相对简单,只要 device
和 pAllocator
与分配时一致即可。
示例¶
VkDevice device = 之前创建的逻辑设备;
VkDeviceMemory device_memory = 之前分配的设备内存;
vkFreeMemory(device, device_memory, nullptr);
内存映射¶
如果内存分配时指定的内存类型支持 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
的话,说明该内存 可映射 。
所谓 可映射 意思是:可以将该设备内存所对应的内存地址返回给
CPU
。
原则上所有的设备内存对于 CPU
来说并不像 new/malloc
分配出来的内存那样能够直接进行读写。为了 CPU
能够读写设备内存,硬件供应商都会提供一部分带有 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
属性的内存用于 CPU
访问。
而在 Vulkan
中分配的内存最终只会对应一个 VkDeviceMemory
句柄,为了能够获得 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
内存类型分配的内存句柄底层的内存地址,可以通过 vkMapMemory(...)
函数将分配的设备内存底层的 虚拟 (说明见下文)地址返回给 CPU
(也就是 Host
端)。
该函数定义如下:
vkMapMemory¶
// 由 VK_VERSION_1_0 提供
VkResult vkMapMemory(
VkDevice device,
VkDeviceMemory memory,
VkDeviceSize offset,
VkDeviceSize size,
VkMemoryMapFlags flags,
void** ppData);
device 内存对应的逻辑设备。
memory 要映射的目标内存。
offset 内存映射从内存首地址开始的偏移量。从
0
开始。单位为字节
。size 要映射的内存大小。单位为
字节
。如果指定为VK_WHOLE_SIZE
,则表明映射范围为从offset
开始到memory
结尾。flags 内存映射的额外标志位参数。
ppData 内存映射结果。为
void*
的指针。该指针减去offset
的对齐大小最小 必须 为VkPhysicalDeviceLimits::minMemoryMapAlignment
。
其中 memory
必须 在 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
类型的内存上分配。当该函数成功返回后, memory
就被认为在 Host 端
进行了 内存映射
,并处于 映射态 。
VkMemoryMapFlags
在 Vulkan 1.0
标准中, VkMemoryMapFlags
没有定义有效值,所以相应的 flags
参数赋值为 0
即可。
备注
在已经进行 内存映射
的内存上再次调用 vkMapMemory(...)
是开发错误。开发者应避免该错误。
虚拟地址
vkMapMemory(...)
函数返回的 ppData
内存映射结果确切来说 不是 真正意义上的内存地址,而是一个 虚拟 内存地址,对该地址的操作就 好似 对底层真正的内存进行操作,其本质上是对虚拟内存的操作。
由于返回的是虚拟内存地址,不同平台对于虚拟内存大小有不同的限制,所以当 vkMapMemory()
映射的虚拟地址范围超过平台限制后该函数将会返回 VkResult::VK_ERROR_MEMORY_MAP_FAILED
表示本次映射失败。为此,可通过将内存进行分小块进行映射或对已经映射的内存进行 解映射 (说明见下文)来释放一部分虚拟内存。
虚拟内存
映射出来的 虚拟内存
与 VkDeviceMemory
底层设备内存是两个独立不同的内存。映射的内存有点类似于将 VkDeviceMemory
底层设备内存拷贝到虚拟内存中,并将这部分虚拟内存的首地址返回,作为映射结果。这对理解下文的 内存同步 非常重要。
ppData
对于 vkMapMemory(...)
返回的 ppData
指针进行操作时,其本质上是 对映射的虚拟内存进行操作 ,严格意义上不会影响底层映射的 VkDeviceMemory
内部数据(详情见下文的 内存同步 )。
示例¶
在有 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
内存类型的内存上分配内存,并进行内存映射(根据 设备内存类型示意图 中的情况):
VkDevice device = 之前创建的逻辑设备;
struct Position
{
float x;
float y;
float z;
Position(float x, float y, float z)
{
this->x = x;
this->y = y;
this->z = z;
}
};
std::vector<Position> positions;
positions.push_back(Position(0, 0, 0));
positions.push_back(Position(0, 0, 1));
positions.push_back(Position(0, 1, 0));
positions.push_back(Position(0, 1, 1));
positions.push_back(Position(1, 0, 0));
positions.push_back(Position(1, 0, 1));
positions.push_back(Position(1, 1, 0));
positions.push_back(Position(1, 1, 1));
VkMemoryAllocateInfo memory_allocate_info = {};
memory_allocate_info.sType = VkStructureType::VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = sizeof(Position) * positions.size();
memory_allocate_info.memoryTypeIndex = 2; // 对应 VkPhysicalDeviceMemoryProperties::memoryTypes[2]
VkDeviceMemory device_memory = VK_NULL_HANDLE;
VkResult result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &device_memory);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 内存创建失败");
}
void* device_memory_ptr = nullptr;
result = vkMapMemory(device, device_memory, 0, VK_WHOLE_SIZE, 0, &device_memory_ptr);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 内存映射失败");
}
memcpy(device_memory_ptr, positions.data(), memory_allocate_info.allocationSize); // 将数据写入 device_memory 内存中
内存解映射¶
当内存映射并使用结束后,可进行解除映射,进而释放系统的虚拟内存。可通过 vkUnmapMemory(...)
函数将映射过的内存进行 解映射 。该函数定义如下:
vkUnmapMemory¶
// 由 VK_VERSION_1_0 提供
void vkUnmapMemory(
VkDevice device,
VkDeviceMemory memory);
device 内存对应的逻辑设备。
memory 要解映射的目标内存。该内存 必须 处于 映射态 。
该函数之后 映射态 的状态将解除,回归到 原始状态 。
示例¶
对前一个示例中分配的设备内存进行解映射:
VkDevice device = 之前创建的逻辑设备;
VkDeviceMemory device_memory = 之前分配的设备内存; // 分配于 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT 并处于 映射态
vkUnmapMemory(device, device_memory);
内存同步¶
所谓内存同步是指:虚拟内存中的数据与对应的 VkDeviceMemory
设备内存底层数据保持一致。
当分配的设备内存所对应的内存类型 包含 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
时,内存同步将 会自动 进行。其同步规则如下:
当向映射的虚拟内存中写入时,写入虚拟内存中的数据也会同步到对应的
VkDeviceMemory
底层设备内存中。如果
GPU
向VkDeviceMemory
底层设备内存中写入数据时,这部分修改的设备内存也会同步到映射的虚拟内存中。
如果分配的设备内存所对应的内存类型 不包含 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
的话,内存同步将 不会自动 进行。需要手动进行内存同步。
换句话说就是,映射的虚拟内存和对应的 VkDeviceMemory
设备内存是两个独立的内存,如果分配的设备内存 包含 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
则无论对虚拟内存做修改,还是对设备内存做修改,双方数据将会自动保持一致。否则需要手动进行内存同步。
如此就有两个同步方:
映射的虚拟内存
VkDeviceMemory
设备内存
虚拟内存同步到设备内存¶
当对映射的虚拟内存中的数据修改时,如果设备内存类型 不包含 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
的话,则需要通过调用 vkFlushMappedMemoryRanges(...)
函数手动将虚拟内存中的数据同步(拷贝)到设备内存中。也就是将虚拟内存中的内容 冲刷
到设备内存中。其定义如下:
vkFlushMappedMemoryRanges¶
// 由 VK_VERSION_1_0 提供
VkResult vkFlushMappedMemoryRanges(
VkDevice device,
uint32_t memoryRangeCount,
const VkMappedMemoryRange* pMemoryRanges);
device 内存对应的逻辑设备。
memoryRangeCount 指定
pMemoryRanges
数组长度。pMemoryRanges 指向
VkMappedMemoryRange
数组。用于配置虚拟内存到设备内存的同步。
设备内存同步到虚拟内存¶
当对设备内存数据修改时,如果设备内存类型 不包含 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
的话,则需要通过调用 vkInvalidateMappedMemoryRanges(...)
函数手动将设备内存中的数据同步(拷贝)到虚拟内存中。也就是 放弃
当前虚拟内存中的内容。其定义如下:
设备内存数据修改
对于设备内存数据的修改一般都是通过执行 GPU
的指令将数据写入到设备内存中,详细说明将会在之后的章节进行讲解。
vkInvalidateMappedMemoryRanges¶
// 由 VK_VERSION_1_0 提供
VkResult vkInvalidateMappedMemoryRanges(
VkDevice device,
uint32_t memoryRangeCount,
const VkMappedMemoryRange* pMemoryRanges);
device 内存对应的逻辑设备。
memoryRangeCount 指定
pMemoryRanges
数组长度。pMemoryRanges 指向
VkMappedMemoryRange
数组。用于配置设备内存到虚拟内存的同步。
其中 VkMappedMemoryRange
定义如下:
VkMappedMemoryRange¶
// 由 VK_VERSION_1_0 提供
typedef struct VkMappedMemoryRange {
VkStructureType sType;
const void* pNext;
VkDeviceMemory memory;
VkDeviceSize offset;
VkDeviceSize size;
} VkMappedMemoryRange;
sType 是该结构体的类型枚举值, 必须 是
VkStructureType::VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE
。pNext 要么是
NULL
要么指向其他结构体来扩展该结构体。memory 要同步的目标设备内存。
offset 要同步的目标设备内存的偏移。单位为
字节
。有效值为[0, memory 的大小]
。size 要同步的目标设备内存的大小。单位为
字节
。如果为VK_WHOLE_SIZE
则表示同步范围为[offset, memory 结尾]
。
其中 VkMappedMemoryRange::memory
在手动同步时 必须 处在 映射态 。也就是 VkMappedMemoryRange::memory
必须已经通过 vkMapMemory(...)
将设备内存进行映射,并且 没有 解映射 。当内存同步结束之后,就可以进行 解映射 了。
示例¶
虚拟内存同步到设备内存¶
VkDevice device = 之前创建的逻辑设备;
VkDeviceMemory device_memory = 之前分配的 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT 设备内存; // 设备内存类型没有 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
void* device_memory_ptr = nullptr;
VkResult result = vkMapMemory(device, device_memory, 0, VK_WHOLE_SIZE, 0, &device_memory_ptr);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 内存映射失败");
}
// 对 device_memory_ptr 进行操作,比如:
memcpy(device_memory_ptr, 数据源, 数据大小); // 将数据写入虚拟内存中
VkMappedMemoryRange mapped_memory_range = {};
mapped_memory_range.sType = VkStructureType::VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mapped_memory_range.pNext = nullptr;
mapped_memory_range.memory = device_memory;
mapped_memory_range.offset = 0;
mapped_memory_range.size = VK_WHOLE_SIZE;
// 内存同步
result = vkFlushMappedMemoryRanges(device, 1, &mapped_memory_range);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 同步失败");
}
// 解映射
vkUnmapMemory(device, device_memory);
设备内存同步到虚拟内存¶
VkDevice device = 之前创建的逻辑设备;
VkDeviceMemory device_memory = 之前分配的 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT 设备内存; // 设备内存类型没有 VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
// 此时该设备内存底层内存中已有相应的数据
void* device_memory_ptr = nullptr;
VkResult result = vkMapMemory(device, device_memory, 0, VK_WHOLE_SIZE, 0, &device_memory_ptr);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 内存映射失败");
}
VkMappedMemoryRange mapped_memory_range = {};
mapped_memory_range.sType = VkStructureType::VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mapped_memory_range.pNext = nullptr;
mapped_memory_range.memory = device_memory;
mapped_memory_range.offset = 0;
mapped_memory_range.size = VK_WHOLE_SIZE;
// 内存同步
result = vkInvalidateMappedMemoryRanges(device, 1, &mapped_memory_range);
if(result != VkResult::VK_SUCCESS)
{
throw std::runtime_error("VkDeviceMemory 同步失败");
}
// 对 device_memory_ptr 进行操作,比如:
目标类型* meta_date = (目标类型*)device_memory_ptr; // 转成目标类型,进行操作
meta_date-> ...;
// 解映射
vkUnmapMemory(device, device_memory);