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之类的值或用下面的BIT
VK_USAGE_TRANSFER_SRC_BIT 
VK_USAGE_TRANSFER_DST_BIT 
VK_USAGE_UNIFORM_TEXEL_BUFFER_BIT 
VK_USAGE_STORAGE_TEXEL_BUFFER_BIT 
VK_USAGE_UNIFORM_BUFFER_BIT 
VK_USAGE_STORAGE_BUFFER_BIT 
VK_USAGE_INDEX_BUFFER_BIT 
VK_USAGE_VERTEX_BUFFER_BIT VK_USAGE_INDIRECT_BUFFER_BIT 
vbci.sharingMode = << one of: >> 
VK_SHARING_MODE_EXCLUSIVE VK_SHARING_MODE_CONCURRENT 
vbci.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 enum
vrci.physicalDevice = PhysicalDevice;      // from usual vulkan setup
vrci.device = LogicalDevice;              // from usual vulkan setup
vrci.instance = = Instance;               // from usual vulkan setup
vrci.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,点击【阅读原文】可查看详情。)

敬请期待~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虹图AI开放平台

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值