Основная логика цикла:
- в основном потоке — ожидание на границах, получение образа цепочки обмена.
Код: Выделить всё
PreRender - Отправьте AcademicFrame и RenderFrame в пул потоков.
- Подождите () для завершения задач пула потоков.
Код: Выделить всё
Join - в основном потоке — представьте изображение.
Код: Выделить всё
PostRender - Повторите.
На первой итерации:
- записывает команды (проход очистки цвета).
Код: Выделить всё
PrepareFrame - Но RenderFrame, который отправляет буфер команд, работает с неправильным индексом кадра, отправляя буфер команд, который никогда не записывался.
- Уровни проверки сообщают, что семафор ожидания никогда не сигнализировался.
Вот соответствующая цепочка вызовов:Фрагменты кода (фреймы/Vulkan)Код: Выделить всё
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(); } }
Структура данных фрейма:Ротация индекса:Код: Выделить всё
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