Отправка первого кадра не удалась из-за незаписанного командного буфера и несигнализированного семафора в многопоточном C++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Отправка первого кадра не удалась из-за незаписанного командного буфера и несигнализированного семафора в многопоточном

Сообщение Anonymous »

Я реализую цикл рендеринга Vulkan с несколькими динамическими кадрами, используя шаблон производитель-потребитель и пул потоков.
Основная логика цикла:
  • Код: Выделить всё

    PreRender
    в основном потоке — ожидание на границах, получение образа цепочки обмена.
  • Отправьте AcademicFrame и RenderFrame в пул потоков.
  • Подождите () для завершения задач пула потоков.
  • Код: Выделить всё

    PostRender
    в основном потоке — представьте изображение.
  • Повторите.
Однако в самом первом кадре проверка Vulkan сообщает об ошибках, что буфер команд не записан, а семафор, используемый для ожидания, не может быть сигнализирован.

❓ С чем я борюсь
На первой итерации:
  • Код: Выделить всё

    PrepareFrame
    записывает команды (проход очистки цвета).
  • Но RenderFrame, который отправляет буфер команд, работает с неправильным индексом кадра, отправляя буфер команд, который никогда не записывался.
  • Уровни проверки сообщают, что семафор ожидания никогда не сигнализировался.
    Вот соответствующая цепочка вызовов:

    Код: Выделить всё

    void View::OnUpdate(double deltaTime)
    {
    if (m_RenderSurface)
    {
    m_RenderSurface->PreRender();
    
    m_ThreadsUpdate.Submit([&]()
    {
    m_RenderQueue->SetDeltaTime(static_cast(deltaTime));
    m_RenderSurface->PrepareFrame(m_RenderQueue);
    });
    
    m_ThreadsUpdate.Submit([&]()
    {
    m_RenderSurface->RenderFrame();
    });
    
    m_ThreadsUpdate.Join();
    
    m_RenderSurface->PostRender();
    }
    }
    
    Фрагменты кода (фреймы/Vulkan)
    Структура данных фрейма:

    Код: Выделить всё

    struct FrameData
    {
    VkFence fence = VK_NULL_HANDLE;
    VkSemaphore acquireSemaphore = VK_NULL_HANDLE;
    VkSemaphore renderSemaphore = VK_NULL_HANDLE;
    VkCommandBuffer cmdBuffer = VK_NULL_HANDLE;
    uint32_t imageIndex = 0;
    bool inFlight = false;
    };
    std::array m_Frames;
    
    Ротация индекса:

    Код: Выделить всё

    void IGAPI::EndRender()
    {
    m_IndexFrameRender = (m_IndexFrameRender + 1) % BACK_BUFFER_COUNT;
    m_IndexFramePrepare = (m_IndexFrameRender + 1) % BACK_BUFFER_COUNT;
    }
    
код:

Код: Выделить всё

#pragma region Rendering
void SurfView_VK::PreRender()
{
static zU32 stepCounter = 0;
stepCounter++;

VkDevice device = m_VulkanAPI->GetDevice();
zU32 indexPrepare = m_VulkanAPI->GetIndexFramePrepare();
auto& frame = m_Frames[indexPrepare];

VkResult vr = vkWaitForFences(device, 1, &frame.fence, VK_TRUE, UINT64_MAX);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkWaitForFences().");

uint32_t imageIndex;
vr = vkAcquireNextImageKHR(device, m_Swapchain, UINT64_MAX, frame.acquireSemaphore, VK_NULL_HANDLE, &imageIndex);
if (vr == VK_ERROR_OUT_OF_DATE_KHR) {
if (!RecreateSwapchain())
throw_runtime_error("Failed to RecreateSwapchain().");

return;
}
else if (vr != VK_SUCCESS && vr != VK_SUBOPTIMAL_KHR)
{
throw_runtime_error("Failed to vkAcquireNextImageKHR().");
}

DebugOutputLite(L">>>>> #0 PreRender(). stepCounter: {}, indexPrepare: {}, imageIndex: {}.", stepCounter, indexPrepare, imageIndex);

frame.imageIndex = imageIndex;
}

void SurfView_VK::PrepareFrame(const std::shared_ptr renderQueue)
{
static zU32 stepCounter = 0;
stepCounter++;

VkDevice device = m_VulkanAPI->GetDevice();
zU32 indexPrepare = m_VulkanAPI->GetIndexFramePrepare();
auto& frame = m_Frames[indexPrepare];

DebugOutputLite(L">>>>> PrepareFrame().  stepCounter: {}, indexPrepare: {}, frame.imageIndex: {}.", stepCounter, indexPrepare, frame.imageIndex);

VkResult vr = vkResetCommandBuffer(frame.cmdBuffer, 0);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkResetCommandBuffer().");

VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vr = vkBeginCommandBuffer(frame.cmdBuffer, &beginInfo);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkBeginCommandBuffer().");

// TODO: команды рендера
// Начало render pass
VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = m_RenderPass;
renderPassInfo.framebuffer = m_Framebuffers[frame.imageIndex];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = m_SwapchainExtent;

VkClearValue clearValues[2];
clearValues[0].color = { {0.3f, 0.3f, 0.6f, 1.0f} };
clearValues[1].depthStencil = { 1.0f, 0 };

renderPassInfo.clearValueCount = 2;
renderPassInfo.pClearValues = clearValues;

vkCmdBeginRenderPass(frame.cmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
// Здесь ваши команды рендеринга (draw calls)
vkCmdEndRenderPass(frame.cmdBuffer);

vr = vkEndCommandBuffer(frame.cmdBuffer);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkEndCommandBuffer().");

frame.inFlight = true;
}

void SurfView_VK::RenderFrame()
{
static zU32 stepCounter = 0;
stepCounter++;

VkDevice device = m_VulkanAPI->GetDevice();
zU32 indexRender = m_VulkanAPI->GetIndexFrameRender();
//zU32 indexRender = m_VulkanAPI->GetIndexFramePrepare();
auto& frame = m_Frames[indexRender];

DebugOutputLite(L">>>>> RenderFrame(). stepCounter: {}, indexRender: {}.", stepCounter, indexRender);

VkResult vr = vkResetFences(device, 1, &frame.fence);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkResetFences().");

VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;// VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &frame.acquireSemaphore;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &frame.cmdBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &frame.renderSemaphore;
vr = vkQueueSubmit(m_VulkanAPI->GetGraphicsQueue(), 1, &submitInfo, frame.fence);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkQueueSubmit().");
}

void SurfView_VK::PostRender()
{
static zU32 stepCounter = 0;
stepCounter++;

VkDevice device = m_VulkanAPI->GetDevice();
zU32 indexRender = m_VulkanAPI->GetIndexFrameRender();
//zU32 indexRender = m_VulkanAPI->GetIndexFramePrepare();
auto& frame = m_Frames[indexRender];

DebugOutputLite(L">>>>> PostRender(). stepCounter: {}, indexRender: {}.", stepCounter, indexRender);

VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &frame.renderSemaphore;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &m_Swapchain;
presentInfo.pImageIndices = &frame.imageIndex;
std::lock_guard lock(m_SwapchainMutex);
VkResult vr = vkQueuePresentKHR(m_VulkanAPI->GetPresentQueue(), &presentInfo);
if (vr != VK_SUCCESS)
throw_runtime_error("Failed to vkQueuePresentKHR().");

frame.inFlight = false;
}
#pragma endregion
Вывод проверки при первой итерации

Код: Выделить всё

>>>> #0 PreRender().  stepCounter: 1, indexPrepare: 0, imageIndex: 0.
>>>>> PrepareFrame(). stepCounter: 1, indexPrepare: 0, frame.imageIndex: 0.
>>>>> RenderFrame(). stepCounter: 1, indexRender: 1.
>>>>> -= DebugOutput =-
Message: [Vulkan Validation] ERROR | VALIDATION: vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0x1c2556b58f0 is unrecorded and contains no commands.
The Vulkan spec states: Each element of the pCommandBuffers member of each element of pSubmits must be in the pending or executable state (https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html#VUID-vkQueueSubmit-pCommandBuffers-00070)
Source: [void __cdecl zzz::IGAPI::LogGPUDebugMessage(const class std::basic_string &)] line: 114, file: C:\Workspaces\zzz\Zzz\engineFoundation\engineCore\Source\Interfaces\IGAPI\IGAPI.cppm
>>>>> -= DebugOutput =-
Message: [Vulkan Validation] ERROR | VALIDATION: vkQueueSubmit(): pSubmits[0].pWaitSemaphores[0] queue (VkQueue 0x1c25308d560) is waiting on semaphore (VkSemaphore 0x170000000017) that has no way to be signaled.
The Vulkan spec states: All elements of the pWaitSemaphores member of all elements of pSubmits created with a VkSemaphoreType of VK_SEMAPHORE_TYPE_BINARY must reference a semaphore signal operation that has been submitted for execution and any semaphore signal operations on which it depends must have also been submitted for execution (https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html#VUID-vkQueueSubmit-pWaitSemaphores-03238)
Source: [void __cdecl zzz::IGAPI::LogGPUDebugMessage(const class std::basic_string &)] line: 114, file: C:\Workspaces\zzz\Zzz\engineFoundation\engineCore\Source\Interfaces\IGAPI\IGAPI.cppm
>>>>> PostRender(). stepCounter: 1, indexRender: 1.
>>>>> -= DebugOutput =-
Message: [Vulkan Validation] ERROR | VALIDATION: vkQueuePresentKHR(): pPresentInfo->pImageIndices[0] was acquired with a semaphore VkSemaphore 0x140000000014 that has not since been waited on
Source: [void __cdecl zzz::IGAPI::LogGPUDebugMessage(const class std::basic_string &)] line: 114, file: C:\Workspaces\zzz\Zzz\engineFoundation\engineCore\Source\Interfaces\IGAPI\IGAPI.cppm
Ожидаемое поведение
Я ожидаю, что:
  • Первый кадр записывает команды в AcceptFrame и отправляет их в RenderFrame.
  • Настоящий семафор правильно сигнализируется и ожидает в vkQueuePresentKHR.
Репозиторий кода
Полный код и ветка с этой реализацией Vulkan доступны здесь:
https://github.com/k119-55524/Zzz

Ветвь: origin/clear-VK-api

Вопрос
  • Как правильно записать и отправить первый кадр в этом конвейере Vulkan с кадрами в полете, чтобы избежать ошибок «незаписанного командного буфера» и «несигнализированного семафора»?
  • Есть ли ошибка рекомендуемый шаблон для инициализации и отправки самого первого кадра при использовании нескольких динамических кадров и пула потоков?
  • В качестве альтернативы предоставьте ссылки на учебные пособия или руководства, в которых объясняется, как реализовать правильный конвейер рендеринга производитель-потребитель в Vulkan с несколькими динамическими кадрами и синхронизацией.


Подробнее здесь: https://stackoverflow.com/questions/799 ... aled-semap
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»