Можно ли дождаться завершения передачи из промежуточного буфера без вызова vkQueueWaitIdle? - PullRequest
4 голосов
/ 19 июня 2019

В следующем фрагменте кода показано, как передать данные буфера вершин из промежуточного буфера в буфер локальной памяти:

bool Vulkan::UpdateVertexBuffer(std::vector<VERTEX>& data, VULKAN_BUFFER& vertex_buffer)
{
    std::memcpy(this->staging_buffer.pointer, &data[0], vertex_buffer.size);

    size_t flush_size = static_cast<size_t>(vertex_buffer.size);
    unsigned int multiple = static_cast<unsigned int>(flush_size / this->physical_device.properties.limits.nonCoherentAtomSize);
    flush_size = this->physical_device.properties.limits.nonCoherentAtomSize * ((uint64_t)multiple + 1);

    VkMappedMemoryRange flush_range = {};
    flush_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
    flush_range.pNext = nullptr;
    flush_range.memory = this->staging_buffer.memory;
    flush_range.offset = 0;
    flush_range.size = flush_size;
    vkFlushMappedMemoryRanges(this->device, 1, &flush_range);

    VkResult result = vkWaitForFences(this->device, 1, &this->transfer.fence, VK_FALSE, 1000000000);
    if(result != VK_SUCCESS) {
        #if defined(_DEBUG)
        std::cout << "UpdateVertexBuffer => vkWaitForFences : Timeout" << std::endl;
        #endif
        return false;
    }
    vkResetFences(this->device, 1, &this->transfer.fence);

    VkCommandBufferBeginInfo command_buffer_begin_info = {};
    command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    command_buffer_begin_info.pNext = nullptr; 
    command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    command_buffer_begin_info.pInheritanceInfo = nullptr;

    vkBeginCommandBuffer(this->transfer.command_buffer, &command_buffer_begin_info);

    VkBufferCopy buffer_copy_info = {};
    buffer_copy_info.srcOffset = 0;
    buffer_copy_info.dstOffset = 0;
    buffer_copy_info.size = vertex_buffer.size;

    vkCmdCopyBuffer(this->transfer.command_buffer, this->staging_buffer.handle, vertex_buffer.handle, 1, &buffer_copy_info);

    VkBufferMemoryBarrier buffer_memory_barrier = {};
    buffer_memory_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
    buffer_memory_barrier.pNext = nullptr;
    buffer_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
    buffer_memory_barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
    buffer_memory_barrier.srcQueueFamilyIndex = this->queue_stack[this->transfer_stack_index].index;
    buffer_memory_barrier.dstQueueFamilyIndex = this->queue_stack[this->graphics_stack_index].index;
    buffer_memory_barrier.buffer = vertex_buffer.handle;
    buffer_memory_barrier.offset = 0;
    buffer_memory_barrier.size = VK_WHOLE_SIZE;

    vkCmdPipelineBarrier(this->transfer.command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, &buffer_memory_barrier, 0, nullptr);

    vkEndCommandBuffer(this->transfer.command_buffer);

    VkSubmitInfo submit_info = {};
    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submit_info.pNext = nullptr;
    submit_info.waitSemaphoreCount = 0;
    submit_info.pWaitSemaphores = nullptr;
    submit_info.pWaitDstStageMask = nullptr;
    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &this->transfer.command_buffer;
    submit_info.signalSemaphoreCount = 0;
    submit_info.pSignalSemaphores = nullptr;

    VkResult result = vkQueueSubmit(this->queue_stack[this->transfer_stack_index].handle, 1, &submit_info, this->transfer.fence);
    if(result != VK_SUCCESS) {
        #if defined(_DEBUG)
        std::cout << "UpdateVertexBuffer => vkQueueSubmit : Failed" << std::endl;
        #endif
        return false;
    }

    #if defined(_DEBUG)
    std::cout << "UpdateVertexBuffer : Success" << std::endl;
    #endif
    return true;
}

Отлично работает без предупреждения слоя валидации. Но когда я вызываю i дважды, оба буфера содержат одни и те же данные из второго вызова. Например:

UpdateVertexBuffer(cube_data, cube_buffer);
UpdateVertexBuffer(prism_data, prism_buffer);

Это приведет к тому, что призма будет внутри как cube_buffer, так и prism_buffer. Чтобы это исправить, я могу просто подождать несколько миллисекунд между двумя вызовами:

UpdateVertexBuffer(cube_data, cube_buffer);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
UpdateVertexBuffer(prism_data, prism_buffer);

или предпочтительно, я могу заменить забор вызовом vkQueueWaitIdle(this->queue_stack[this->transfer_stack_index].handle);

По моему мнению, это приведет к снижению производительности, и забор, как предполагается, является оптимальным способом ожидания завершения операции передачи, так почему мой первый буфер заполняется секундами, когда я использую забор. И есть ли способ сделать это правильно, не используя vkQueueWaitIdle.

Спасибо за вашу помощь.

1 Ответ

5 голосов
/ 19 июня 2019

Вы ожидаете ограничения для предыдущей загрузки. . После вы уже записали данные в промежуточный буфер. Слишком поздно; есть ограждение, которое запрещает вам записывать данные в читаемую память.

Но на самом деле ваша проблема в том, что ваш дизайн неправильный. Ваш дизайн таков, что все последовательные обновления используют одну и ту же память. Они не должны. Вместо этого последовательные обновления должны использовать разные области одной и той же памяти, чтобы они не могли перекрываться. Таким образом, вы можете выполнять переносы, и вам вообще не придется ждать на заборах (или, по крайней мере, не до следующего кадра).

По сути, вы должны обрабатывать свой промежуточный буфер как кольцевой буфер. Каждая операция, которая хочет выполнить некоторую поэтапную передачу, должна «выделить» X байтов памяти из буфера промежуточного кольца. Промежуточная буферная система выделяет память последовательно, оборачиваясь, если не хватает места. Но он также запоминает, где находится последняя область памяти, с которой он синхронизирован. Если вы попытаетесь поставить слишком много работы, она должна синхронизироваться.

Кроме того, одна из целей отображения памяти заключается в том, что вы можете записывать непосредственно в эту память, а не записывать в какую-либо другую память ЦП и копировать ее. Поэтому вместо передачи VULKAN_BUFFER (что бы это ни было), процесс, который сгенерировал эти данные, должен был получить указатель на область активного промежуточного буфера и записать в него свои данные.

Да, и еще одна вещь: никогда, ever , создайте буфер команд и немедленно отправляйте его. Просто не делай этого. Есть причина, по которой vkQueueSubmit может принимать несколько буферов команд и несколько пакетов буферов команд. Для любой очереди вы никогда не должны отправлять более одного (или, возможно, дважды) за кадр.

...