“Staging Buffer
” ,可翻译成暂存缓冲区或临时缓冲区,在使用诸如Vulkan、DirectX等现代图形API时,经常用于充当主机和GPU之间的桥梁,以进行高效的数据传输。要明确知道,staging buffer是显存上开辟的
以Index Buffer的创建为例:
indexBuffer
来指向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, ©Region);
// 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);
}
}