How to VkCmdCopyBuffer without a Single Time Command Buffer

45 Views Asked by At

I was wondering if it is possible to call vkCmdCopyBuffer on the Main Command Buffer (without using a secondary command buffer) to update a VkBuffer. Whenever I call vkCmdCopyBuffer on the main command buffer it stops rendering anything and the screen appears pitch black.

//Updating the 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);


VkBufferCopy copy_region = { 0 };
copy_region.size = size;
vkCmdCopyBuffer(vulkan_context.command_buffer, staging_buffer, buffer->buffer, 1, &copy_region); <-- it breaks here (still continues to run but stops rendering)

vkDestroyBuffer(vulkan_context.device.handle, staging_buffer, NULL);
vkFreeMemory(vulkan_context.device.handle, staging_memory, NULL);


uint32_t vulkan_find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags properties)
    {
        VkPhysicalDeviceMemoryProperties mem_properties;
        vkGetPhysicalDeviceMemoryProperties(vulkan_context.physical_device.handle, &mem_properties);
    
        for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++)
            if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties)
                return i;
    
        return 0;
    }

void vulkan_allocate_buffer(size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer *buffer, VkDeviceMemory *memory)
{
    VkBufferCreateInfo buffer_info = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
    buffer_info.size = size;
    buffer_info.usage = usage;
    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    VkResult result = vkCreateBuffer(vulkan_context.device.handle, &buffer_info, NULL, buffer);
    assert(result == VK_SUCCESS);

    VkMemoryRequirements mem_requirements;
    vkGetBufferMemoryRequirements(vulkan_context.device.handle, *buffer, &mem_requirements);

    VkMemoryAllocateInfo alloc_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
    alloc_info.allocationSize = mem_requirements.size;
    alloc_info.memoryTypeIndex = mg_vulkan_find_memory_type(mem_requirements.memoryTypeBits, properties);
    
    result = vkAllocateMemory(vulkan_context.device.handle, &alloc_info, NULL, memory);
    assert(result == VK_SUCCESS);

    vkBindBufferMemory(vulkan_context.device.handle, *buffer, *memory, 0);
}

// To create the buffer itself
    vulkan_allocate_buffer(size,
    VK_BUFFER_USAGE_TRANSFER_DST_BIT | usage,
    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
    &buffer, &memory);
1

There are 1 best solutions below

0
Jherico On

You should really try to explain what it is you're trying to do. When you say "main command buffer" do you mean the command buffer you're re-using frame after frame to render to the screen? Do you re-record the "main" command buffer every frame? What kind of data are you trying to update? A vertex buffer, index buffer, uniform buffer or something else? The reason I ask is that generally you want rendering command buffers to be re-usable while large copy operations are something you tend to do at startup to bulk load data to device local memory. It's legitimately unusual that people would want to do large copies in the same code where they execute rendering operations, so it's possible you're using the wrong pattern. For instance, if you're updating a uniform buffer (or something else small that needs to change every frame) then using vkCmdCopyBuffer is absolutely not the best way to do that.

That said, generally there's no reason that you can't execute a copy inside the same command buffer that you're using to render, but you need to bear in mind the following

Copies need to happen outside of a renderpass

Presumably most of your main command buffer will consist of commands between a beginRenderPass/endRenderPass pair (or beingRendering/endRendering if you're using dynamic rendering). Copy commands aren't allowed in this area, so they have to come before the begin or after the end bit. Presumably if you want to use the buffer in the rendering, it would need to be before the begin.

Both the source and dest buffers have to have the same lifetime as the command buffer referencing them.

If you free a "staging buffer" and then re-use the command buffer containing a copy with it as the source you're going to have a bad time, because then the copy command will be referencing a non-existent buffer. This could crash your app.

You need to ensure that there are barriers between writing the buffer and using it (reads)

Usually when using a one-off submission you don't need to worry about synchronization, because you just wait on a fence to ensure the copy command buffer has finished executing completely. If the copy happens in the same command buffer where you use the buffer for rendering, then you're doing a read after write and you need a barrier ensuring that the read actually happens on the GPU after the write. It doesn't matter that you've put the commands into the command buffer in a specific order. The GPU can do work in parallel and re-order the work you did unless you explicitly tell it that reads from the buffer aren't allowed to happen until after writes have finished. That means calling vkCmdPipelineBarrrier or vkCmdPipelineBarrier2 after the copy but before the rendering.