Hello Vulkan(四)| 图形渲染新技术Vulkan Data Buffers及内存分配管理器VMA


回顾
上一期技术分享中,我们讲述了如何使用Vulkan Shader、Vulkan GLSL和OpenGL GLSL的区别,以及Vulkan对于Shader的SPIR-V编译方法等内容。
本期分享内容各种「缓冲区Buffers」、「内存分配管理VMA」,我们会介绍如何在Vulkan中各类Buffers,主要是Data Buffers以及AMD推出的Vulkan的内存分配管理的使用等。其实在最初接触Vulkan时,Buffer这个词让我很混乱,加上Vulkan里还有其他许多Buffer,比如后面要讲述的纹理的Texture Buffers、以及Command Buffers,但我们这里讲的也就是Vulkan称之为“Buffers”的东西 – Data Buffers。
Data Buffers
其实所谓一个data buffer其实就是一片连续的GPU显存字节,并没有其他更多的意义,这些内存用来存放你想存放的任何东西,也被成为BLOB.(Binary Large Object)。上面已经提到,其实最初接触Buffers时,有点奇怪,因为Vulkan里还有其他buffers,但是Vulkan称这些缓冲区就叫“Buffers”,所以我自己在写代码时通常会自己将变量名写为“VkDataBuffer”之类的,类似于“typedef VkBuffer VkDataBuffer;”, 这样当然,长期来看未必是个好主意,但在给出的Sample Code里,我都是这么定义的。
我们来看下这个图,这个图是创建和装载Data Buffers的过程。

首先,得明确这个Buffer用来做什么,并且确定字节大小,然后装载进一个叫vkBufferCreateInfo的结构体,然后调用vkCreateBuffer()的方法去创建这个Buffer,再然后我们需要将这个Buffer传递给一个vkGetBufferMemoryRequirements( )的方法,这个方法是用来指定显存需求的,并且指定了内存类型和大小,最后,调用vkAllocateMemory( )方法,进行显存分配,到这一步,才算是真正分配了显存。所以其实在vkCreateBuffer()之后,并未真正分配实际显存,而是在vkAllocateMemory()之后才实际创建了buffer显存。
分配完显存后,就可以进行装载,利用bufferMemoryHandle做一个memory map内存映射,会得到GPU显存地址,这样我们就可以从CPU端读取或者写入这块GPU内存。
我们来看下具体的代码:
首先是vkCreateBuffer的部分:
VkBuffer Buffer; // or "VkDataBuffer Buffer"VkBufferCreateInfo vbci;vbci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;vbci.pNext = nullptr;vbci.flags = 0;vbci.size = << buffer size in bytes >>vbci.usage = <<or’ed bits of: >> //设置buffer用途 1 2 4 8 16 32之类的值或用下面的BITVK_USAGE_TRANSFER_SRC_BITVK_USAGE_TRANSFER_DST_BITVK_USAGE_UNIFORM_TEXEL_BUFFER_BITVK_USAGE_STORAGE_TEXEL_BUFFER_BITVK_USAGE_UNIFORM_BUFFER_BITVK_USAGE_STORAGE_BUFFER_BITVK_USAGE_INDEX_BUFFER_BITVK_USAGE_VERTEX_BUFFER_BIT VK_USAGE_INDIRECT_BUFFER_BITvbci.sharingMode = << one of: >>VK_SHARING_MODE_EXCLUSIVE VK_SHARING_MODE_CONCURRENTvbci.queueFamilyIndexCount = 0;vbci.pQueueFamilyIndices = (const iont32_t) nullptr;result = vkCreateBuffer ( LogicalDevice, IN &vbci, PALLOCATOR, OUT &Buffer );
然后是为这个Buffer指定显存、绑定和写入内容的部分:
VkMemoryRequirements vmr;result = vkGetBufferMemoryRequirements( LogicalDevice, Buffer, OUT &vmr );VkMemoryAllocateInfo vmai;vmai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;vmai.pNext = nullptr;vmai.flags = 0;vmai.allocationSize = vmr.size;vmai.memoryTypeIndex = FindMemoryThatIsHostVisible( );...VkDeviceMemory vdm;result = vkAllocateMemory( LogicalDevice, IN &vmai, PALLOCATOR, OUT &vdm );result = vkBindBufferMemory( LogicalDevice, Buffer, IN vdm, 0 ); // 0 is the offset...result = vkMapMemory( LogicalDevice, IN vdm, 0, VK_WHOLE_SIZE, 0, &ptr );<< do the memory copy >>result = vkUnmapMemory( LogicalDevice, IN vdm );
最后是Memory-Mapped内存映射到CPU的部分,为了从CPU端向缓冲写入数据,我们需要将缓冲映射到CPU内存中,并用指针进行读写:
void *mappedDataAddr;vkMapMemory( LogicalDevice, myBuffer.vdm, 0, VK_WHOLE_SIZE, 0, OUT (void *)&mappedDataAddr );memcpy( mappedDataAddr, &VertexData, sizeof(VertexData) );vkUnmapMemory( LogicalDevice, myBuffer.vdm );
这个只是个例子,也可以进行一些循环填充结构体的操作。
Vulkan Memory Allocator(VMA)
VMA是AMD提供的Vulkan内存分配管理器,我把Github的地址放出来,如果有需要的可以直接下载使用。
VMA属于单头文件的“stb-style”风格库。你不需要单独去构建它,只需要把头文件加入到工程里就好了,然后引入头文件:
在一个cpp文件里开启宏定义并引入头文件:
#define VMA_IMPLEMENTATION#define VMA_VULKAN_VERSION 1001000#include "vk_mem_alloc.h"
在其他cpp文件里只需要引入头文件即可:
#define VMA_VULKAN_VERSION 1001000#include "vk_mem_alloc.h"
然后初始化VMA Allocator:
VmaAllocator Allocator; // global...VmaAllocatorCreateInfo vrci;vrci.vulkanApiVersion = VK_API_VERSION_1_1;vrci.flags = 0; // VmaAllocatorCreateFlagBits enumvrci.physicalDevice = PhysicalDevice; // from usual vulkan setupvrci.device = LogicalDevice; // from usual vulkan setupvrci.instance = = Instance; // from usual vulkan setupvrci.pVulkanFunctions = nullptr;vmaCreateAllocator( IN &vrci, OUT &Allocator );
下一步我们就不需要自己调用vkCreateBuffer()创建buffer了,直接调用VMA的vmaCreateBuffer()进行创建和绑定:
#include “vk_mem_alloc.h”...VkBuffer Buffer; // global...VmaAllocationCreateInfo vaci;vaci.usage = VMA_MEMORY_USAGE_AUTO;vaci.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;vaci.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;vaci.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;vmaCreateBuffer( IN Allocator, IN &vbci, IN &vaci, OUT &Buffer. OUT &Allocation, nullptr );
当然,最后不要忘记回收内存:
vmaDestroyBuffer( Allocator, Buffer. Allocation );vmaDestroyAllocator( Allocator );
那么VMA如何做内存映射呢?VMA提供了vmaMapMemory()、vmaUnmapMemory()方法进行映射以及关闭映射。可以同时对同一块内存(VmaAllocation)进行多次Mapping操作,Mapping是拥有操作计数的(Mapping一次就+1,UnMapping一次就-1)。

代码如下:
void *mappedDataAddr; vmaMapMemory( Allocator, Allocation, OUT &mappedDataAddr ); memcpy( mappedDataAddr, &VertexData, sizeof(VertexData) ); vmaUnmapMemory( Allocator, Allocation );
到这儿所有的Vulkan Data Buffers和VMA内存分配管理器的内容就结束了。欢迎大家关注虹图AI开放平台公众号,后台留言交流,也欢迎大家移步至我们刚上线的开发者社区中交流和分享。
关于Vulkan以及纹理相关的实践内容,我们会在本系列后续内容中继续与大家分享。(虹图人像人体中美颜SDK部分是基于Vulkan进行开发封装的,性能极致,对开发者更加友好,十行之内完成一个简单的demo,点击【阅读原文】可查看详情。)
敬请期待~
到顶部