如何在主命令缓冲区调用VkCmdCopyBuffer(无需单次命令缓冲)及解决黑屏问题
主命令缓冲区调用vkCmdCopyBuffer的可行性与问题修复
完全可以在主命令缓冲区调用vkCmdCopyBuffer更新VkBuffer,你的渲染黑屏问题是代码里的资源生命周期错误和缺少同步屏障导致的,具体问题和修复方案如下:
1. 致命错误:资源生命周期提前结束
你在录制完拷贝命令后立刻销毁了staging buffer和对应的内存,但此时命令缓冲区还没提交给GPU执行。GPU真正执行拷贝操作时,源缓冲区已经被释放,这会导致GPU访问无效资源,触发设备静默错误或丢失,直接导致渲染停止。
修复:
必须等到包含拷贝命令的主命令缓冲区提交给队列并执行完成后,才能销毁staging资源。可以用Fence同步等待GPU执行完毕,再释放资源。
2. 缺少必要的同步屏障
Vulkan是显式同步API,必须手动保证操作的执行顺序:
- Host向staging buffer写入数据后,要确保数据对GPU可见,才能作为拷贝源
- GPU完成拷贝后,要确保目标buffer的数据对后续渲染管线可见
修复:
在vkUnmapMemory后添加Host到Transfer阶段的屏障:
VkMemoryBarrier host_to_transfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER}; host_to_transfer.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; host_to_transfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier( vulkan_context.command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &host_to_transfer, 0, NULL, 0, NULL );
在vkCmdCopyBuffer后添加Transfer到渲染阶段的屏障(根据目标buffer的用途调整,比如顶点缓冲区对应顶点输入阶段):
VkBufferMemoryBarrier transfer_to_render = {VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; transfer_to_render.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; transfer_to_render.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; transfer_to_render.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; transfer_to_render.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; transfer_to_render.buffer = buffer->buffer; transfer_to_render.offset = 0; transfer_to_render.size = size; vkCmdPipelineBarrier( vulkan_context.command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, NULL, 1, &transfer_to_render, 0, NULL );
3. 命令缓冲区状态检查
确保调用vkCmdCopyBuffer时,主命令缓冲区处于录制状态(已经调用vkBeginCommandBuffer,未调用vkEndCommandBuffer)。如果命令缓冲区已经提交到队列,无法再向其中录制命令,需要重新开始录制或创建新的命令缓冲区。
修复后的完整流程示例
// 开始录制主命令缓冲区 VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(vulkan_context.command_buffer, &begin_info); // 创建staging buffer并写入数据 VkBuffer staging_buffer; VkDeviceMemory staging_memory; vulkan_allocate_buffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buffer, &staging_memory); void *_data; vkMapMemory(vulkan_context.device.handle, staging_memory, 0, size, 0, &_data); memcpy(_data, data, size); vkUnmapMemory(vulkan_context.device.handle, staging_memory); // Host到Transfer的同步屏障 VkMemoryBarrier host_to_transfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER}; host_to_transfer.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; host_to_transfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier( vulkan_context.command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &host_to_transfer, 0, NULL, 0, NULL ); // 执行拷贝命令 VkBufferCopy copy_region = {0}; copy_region.size = size; vkCmdCopyBuffer(vulkan_context.command_buffer, staging_buffer, buffer->buffer, 1, ©_region); // Transfer到渲染阶段的同步屏障 VkBufferMemoryBarrier transfer_to_render = {VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; transfer_to_render.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; transfer_to_render.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; transfer_to_render.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; transfer_to_render.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; transfer_to_render.buffer = buffer->buffer; transfer_to_render.offset = 0; transfer_to_render.size = size; vkCmdPipelineBarrier( vulkan_context.command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, NULL, 1, &transfer_to_render, 0, NULL ); // 结束命令缓冲区录制 vkEndCommandBuffer(vulkan_context.command_buffer); // 提交命令缓冲区并等待执行完成 VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &vulkan_context.command_buffer; VkFence fence; VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; vkCreateFence(vulkan_context.device.handle, &fence_info, NULL, &fence); vkQueueSubmit(vulkan_context.graphics_queue, 1, &submit_info, fence); vkWaitForFences(vulkan_context.device.handle, 1, &fence, VK_TRUE, UINT64_MAX); // 销毁staging资源和Fence vkDestroyBuffer(vulkan_context.device.handle, staging_buffer, NULL); vkFreeMemory(vulkan_context.device.handle, staging_memory, NULL); vkDestroyFence(vulkan_context.device.handle, fence, NULL);
内容的提问来源于stack exchange,提问作者Kabajat2008
相关产品推荐
相关产品推荐

