vulkan中从CPU到GPU传输数据,暂存缓冲区(staging buffer)的必要性

发布时间:2024年01月17日

Staging Buffer” ,可翻译成暂存缓冲区临时缓冲区,在使用诸如Vulkan、DirectX等现代图形API时,经常用于充当主机和GPU之间的桥梁,以进行高效的数据传输。要明确知道,staging buffer是显存上开辟的

以Index Buffer的创建为例:

  • 主机端创建了索引数组(CPU端内存开辟),并填充数据
  • 调用API在显存上创建一块区域,用indexBuffer来指向
  • 目的:把CPU端的数据拷贝到indexBuffer

考虑问题: 既然都是从CPU到GPU传输数据,为什么要先从CPU端拷贝到staging buffer(显存块)中,再从staging buffer拷贝到index buffer(显存块)呢,这不很多余么?

知道答案的就不用往下看了

看注释

VulkanIndexBuffer::VulkanIndexBuffer(void* data, uint64_t size)
	: m_Size(size)
{
	// 这里通过Buffer类申请了堆区空间,并把形参的数据拷贝进去
	m_LocalData = Buffer::Copy(data, size);		

	// 封装的一个类,用于申请显存块
	VulkanAllocator allocator("IndexBuffer");	

	if (usingStaging)
	{
		// Staging Buffer
		// 注意usage参数:传输源(我们要从staging buffer拷贝数据到index buffer)
		VkBufferCreateInfo bufferCreateInfo{};
		bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
		bufferCreateInfo.size = instance->m_Size;
		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;	/
		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
		
		// 这里给分配器传的flags是CPU_TO_GPU,即CPU可见
		VkBuffer stagingBuffer;
		VmaAllocation stagingBufferAllocation = allocator.AllocateBuffer(bufferCreateInfo, VMA_MEMORY_USAGE_CPU_TO_GPU, stagingBuffer);
	
		// MapMemory和UnMapMemory:将GPU显存块(CPU可见的)的地址映射到程序端的虚拟地址空间,然后通过指针就能写入数据到这块内存了
		uint8_t* destData = allocator.MapMemory<uint8_t>(stagingBufferAllocation);
		memcpy(destData, this->m_LocalData.Data, this->m_LocalData.Size);
		allocator.UnmapMemory(stagingBufferAllocation);
	
		// Index Buffer
		// 注意usage参数:传输的目标,且指明是用作Index buffer(vulkan会对此显存块进行相应优化)
		VkBufferCreateInfo indexBufferCreateInfo = {};	
		indexBufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
		indexBufferCreateInfo.size = instance->m_Size;
		indexBufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;	
		
		// 注意flags参数:仅GPU(因此它不能进行映射,也就是CPU不可见,不能传输数据,因此需要staging buffer)
		this->m_MemoryAllocation = allocator.AllocateBuffer(indexBufferCreateInfo, VMA_MEMORY_USAGE_GPU_ONLY, instance->m_IndexBuffer);	
	
		// 创建/获取 支持传输指令的command buffer来记录传输指令(vkCmdCopyBuffer)
		// true为调用vkBeginCommandBuffer函数,即创建该commandbuffer的同时开始指令记录
		VkCommandBuffer copyCmd = device->GetCommandBuffer(true);
		
		// 记录指令
		VkBufferCopy copyRegion = {};
		copyRegion.size = this->m_LocalData.Size;
		vkCmdCopyBuffer(copyCmd, stagingBuffer,this->m_IndexBuffer,	1, &copyRegion);
		
		// Flush意味着提交给指令队列并销毁该comman buffer 因为只是一个复制指令而已,用完就销毁该commandbuffer
		device->FlushCommandBuffer(copyCmd);
		
		// staging buffer已无用,须销毁
		allocator.DestroyBuffer(stagingBuffer, stagingBufferAllocation);
	}
	else
	{
		VkBufferCreateInfo indexbufferCreateInfo = {};
		indexbufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
		indexbufferCreateInfo.size = instance->m_Size;
		indexbufferCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
		
		// 如果不用stagingbuffer,那indexBuffer就需要CPU可见,可读可写,会影响效率,禁用很多优化
		auto bufferAlloc = allocator.AllocateBuffer(indexbufferCreateInfo, VMA_MEMORY_USAGE_CPU_TO_GPU, instance->m_VulkanBuffer);
	
		void* dstBuffer = allocator.MapMemory<void>(bufferAlloc);
		memcpy(dstBuffer, instance->m_LocalData.Data, instance->m_Size);
		allocator.UnmapMemory(bufferAlloc);
	}
}
文章来源:https://blog.csdn.net/Motarookie/article/details/135639174
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。