赞
踩
本文为Vulkan® Memory Allocator系列系列教程,定时更新,请大家关注。如果需要深入学习Vulkan的同学,可以点击课程链接,学习链接
Vulkan学习群:594715138
腾讯课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》
网易课堂:《Vulkan原理与实战—铸造渲染核武器—基石篇》
Memory Mapping技术,是指把Vulkan当中生成的任何内存(VkDeviceMemory)映射到CPU端的一个void*指针的过程,这样我们就可以从CPU端读取或者写入这块内存。这种可以映射的内存可能只能适用于拥有VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT标识符的内存块。在Vulkan当中,我们是使用vkMapMemory这个函数进行映射操作,使用vkUnmapMemory这个函数进行映射解除操作。你可以直接使用Vulkan的这两个函数对VMA生成的内存进行操作,但是呢,我们并不推荐这么做,因为:多次对同一块VkDeviceMemory进行Mapping的操作,在Vulkan当中是禁止的,一块内存只能够Mapping一次。所以在Vulkan当中,Mapping这个操作是没有操作计数这一说的,在VMA当中,我们就能够做到操作计数以及多次映射。
VMA(Vulkan Memory Allocator)提供了如下函数进行映射以及关闭映射操作: vmaMapMemory(), vmaUnmapMemory()。这组函数比Vulkan更加的便捷且安全。你可以同时对同一块内存(VmaAllocation)进行多次Mapping操作——Mapping这个操作是拥有操作计数的(Mapping一次就+1,UnMapping一次就-1)。对于多个VmaAllocation,他们可能来自同一个VkDeviceMemory,那么你仍然可以对他们进行各自的Mapping操作,这是安全的!原因是,VMA对于每一个VmaAllocation的Mapping操作,其实是把整个内存Chunk都进行了Mapping操作,并不是只Mapping了一个区域,我们可以这么写代码:
代码如下(示例):
// Having these objects initialized: struct ConstantBuffer { ... }; ConstantBuffer constantBufferData; VmaAllocator allocator; VkBuffer constantBuffer; VmaAllocation constantBufferAllocation; // You can map and fill your buffer using following code: void* mappedData; vmaMapMemory(allocator, constantBufferAllocation, &mappedData); memcpy(mappedData, &constantBufferData, sizeof(constantBufferData)); vmaUnmapMemory(allocator, constantBufferAllocation);
当进行Mapping操作的时候,你可能在Vulkan的Validation Layer看到一个警告:
Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.
这个警告被发出,是因为VMA把整个VkDeviceMemory都给映射了,不同的Image或者Buffer可能会在同一块内存上,特别是在集成显卡上(比如Intel),如果你有完全把握,可以忽略这个警告。
在 Vulkan当种,我们可以永久的让一块内存处于Mapping状态。在GPU使用数据前你可以不进行Unmapping操作。VMA定义了一个特殊的特性Flags:使用VMA_ALLOCATION_CREATE_MAPPED_BIT 这个标识符生成的VmaAllocation会永久性的保持Mapping状态,你可以通过VkAllocationInfo这个结构体对象的pMappedData成员对内存进行直接访问,你可以这么写代码:
代码如下(示例):
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = sizeof(ConstantBuffer);
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer buf;
VmaAllocation alloc;
VmaAllocationInfo allocInfo;
vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
// Buffer is already mapped. You can access its memory.
memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData));
当然,在某些情况下,你也得考虑只对内存进行短时间的Mapping操作:
在Vulkan当中,除非GPU上要使用,你是不需要对Mapped内存进行UnMap操作的。但是呢,如果这块内存不具备VK_MEMORY_PROPERTY_HOST_COHERENT_BIT这个能力的话,CPU与GPU的数据就无法及时更新入内存(存在缓存)。你就得在CPU端读取之前,调用invalidate cache操作;在CPU写入数据后,进行flush cache操作(CPU的Cache到主存RAM都会有一些延迟)。Map/UnMap并不会自动的执行相关操作。所以Vulkan提供了vkFlushMappedMemoryRanges(), vkInvalidateMappedMemoryRanges()作为CPU写入/CPU读取相关的刷新函数。VMA提供了更为方便的接口 vmaFlushAllocation(), vmaInvalidateAllocation(), 并且我们可以同时操作多个内存,使用如下接口: vmaFlushAllocations(), vmaInvalidateAllocations()。
当不使用VK_MEMORY_PROPERTY_HOST_COHERENT_BIT的时候,我们flush/invalidate的内存区域大小需要符合VkPhysicalDeviceLimits::nonCoherentAtomSize的基本对齐。VMA会自动保证这个对齐。在任何的HOST_VISIBLE且非HOST_COHERENT的内存当中,所有的内存分配都是自动符合这个规则的。所以他们的offset值总是nonCoherentAtomSize的整数倍且两个不同的allocation是不会在同一个AtomSize内部重叠的。
请注意使用VMA_MEMORY_USAGE_CPU_ONLY 这种方式分配的VMA内存是保证HOST_COHERENT的。
当然,作为Windows的图形驱动,在如下三个GPU供应商上(AMD,Intel,NVIDIA),只要本内存拥有HOST_VISIBLE属性,就直接打开了HOST_COHERENT,所以在这些平台上你不需要烦恼这个问题哦。
在VMA当中,可能在你没有强制要求的情况下,给你分配的内存就拥有了HOST_VISIBLE的属性。比如你在Intel的集成显卡上工作或者在显存上分配失败而给到了你一个RAM的内存作为补偿。
你可以检测这种情况是否发生,如果发生了就可以直接对内存进行Mapping操作,从而直接操作其数据。为了做到这点,你可以调用 vmaGetAllocationMemoryProperties(),然后看下是否拥有VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT这个Flag。你可以这么写代码:
代码如下(示例):
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = sizeof(ConstantBuffer); bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; VkBuffer buf; VmaAllocation alloc; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); VkMemoryPropertyFlags memFlags; vmaGetAllocationMemoryProperties(allocator, alloc, &memFlags); if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) { // Allocation ended up in mappable memory. You can map it and access it directly. void* mappedData; vmaMapMemory(allocator, alloc, &mappedData); memcpy(mappedData, &constantBufferData, sizeof(constantBufferData)); vmaUnmapMemory(allocator, alloc); } else { // Allocation ended up in non-mappable memory. // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer. }
你也可以直接使用VMA_ALLOCATION_CREATE_MAPPED_BIT这个标识符直接创建Allocation(这个不一定是HOST_VISIBLE的要求),如果得到的内存属于HOST_VISIBLE,那么就可以测试下VmaALlocationInfo当中的pMappedData这个成员,看看是否已经被开启了永久Mapping,你可以这么写代码:
代码如下(示例):
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = sizeof(ConstantBuffer); bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); if(allocInfo.pMappedData != nullptr) { // Allocation ended up in mappable memory. // It is persistently mapped. You can access it directly. memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData)); } else { // Allocation ended up in non-mappable memory. // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer. }
以上就是今天的内容,大家对于vulkan的学习,也可以参考我出品的vulkan系列教程,下面给大家贴出链接。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。