Search code examples
vulkan

Vulkan API: Memory barrier for vkDestroyBuffer


When I call the vkDestroyBuffer function I get an error:

vkDestroyBuffer():  can't be called on VkBuffer that is currently in use by VkCommandBuffer

I am destroying a wasted buffer betweet submitting render passes so I understand the nuture of this problem. A call of the vkQueueWaitIdle for my transfer queue works fine:

, but I would like to utilize the memory barrier:

auto& commandPool = commandPoolMap.at(transferQueueFamily);
vk::CommandBufferAllocateInfo commandBufferInfo(commandPool, vk::CommandBufferLevel::ePrimary, 1);
auto commandBuffer = vulkanContext.deviceController->device.allocateCommandBuffers(commandBufferInfo).front();

vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
commandBuffer.begin(beginInfo);
{
    auto srcAccess = vk::AccessFlagBits::eTransferWrite;
    auto dstAccess = vk::AccessFlagBits::eTransferWrite;

    vk::BufferMemoryBarrier barrier(srcAccess, dstAccess, {}, {}, buffer, 0, count);
    auto srcStage = vk::PipelineStageFlagBits::eTransfer;
    auto dstStage = vk::PipelineStageFlagBits::eTransfer;
    commandBuffer.pipelineBarrier(srcStage, dstStage, {}, {}, barrier, {});

    vulkanContext.deviceController->device.destroyBuffer(buffer);           //vkDestroyBuffer
    vulkanContext.deviceController->device.freeMemory(deviceMemory.memory); //vkFreeMemory
}
commandBuffer.end();

vk::SubmitInfo submitInfo({}, {}, commandBuffer, {});
auto& queue = vulkanContext.queueFamilies->queueMap.at(transferQueueFamily);
queue.submit(submitInfo);
queue.waitIdle();

, and get errors like:

vkEndCommandBuffer():  was called in VkCommandBuffer which is invalid because bound VkBuffer was destroyed.

I understand that I do something off as this doesn't want to work, but I want to understand what is the propper way to destroy a vkBuffer between rendering frames


Solution

  • A lot of Vulkan operations are asynchronously executed by the GPU. Destroying a buffer is not one of them.

    At the moment on the CPU that vkDestroyBuffer is called, the buffer in question must not be referenced by any command buffer that has been submitted for execution. Similarly, any command buffer that gets submitted cannot use a buffer that has been destroyed before its submission.

    Therefore, to safely call vkDestroyBuffer, the CPU must synchronize itself with GPU operations. GPU barriers synchronize GPU operations with each other; they cannot synchronize with the CPU. Stalling the CPU until the queue is idle works, but that's terrible for performance.

    A better way to handle this is to poll the fence you gave to the queue submission command for the last batch that references the buffer. When that fence is set, all submitted command buffers have completed execution, and you can destroy the buffer safely.

    But you also cannot still be using it. You have to remove it from any descriptor sets you are using it in. You can't have bound it as a vertex buffer or the like. Etc. The last frame that's using it must be the last frame that uses it.