如何通过信号量调度传输队列上的vkCmdCopyBuffer拷贝任务?
我希望通过vkCmdCopyBuffer实现:由运行在图形队列上的计算任务发出信号量,触发独立的传输队列执行主机拷贝操作,同时向渲染流程发送信号,让主机拷贝完全独立于图形队列运行。但目前遇到了Vulkan验证层的错误。
错误信息
VULKAN> ERROR(violation): Validation Error: [ UNASSIGNED-CoreValidation-DrawState-QueueForwardProgress ] Object 0: handle = 0x301e6c0000000022, type = VK_OBJECT_TYPE_SEMAPHORE; Object 1: handle = 0x21d51d0ac80, type = VK_OBJECT_TYPE_QUEUE; Object 2: handle = 0x21d51d0ac80, type = VK_OBJECT_TYPE_QUEUE; | MessageID = 0xe825f293 | vkQueueSubmit(): pSubmits[0].pSignalSemaphores[1] is signaling VkQueue 0x21d51d0ac80[] (VkSemaphore 0x301e6c0000000022[]) that was previously signaled by VkQueue 0x21d51d0ac80[] but has not since been waited on by any queue.
相关代码
计算任务提交代码
void Renderer::runCompute() { VkSemaphore waitSemaphores[] = { mComputeReadySemaphore }; VkSemaphore signalSemaphores[] = { mComputeFinishedSemaphoreGraphics, mComputeFinishedSemaphoreTransfer }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT }; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; if (mHasDispatchedCompute) [[likely]] { submitInfo.waitSemaphoreCount = ARR_LEN(waitSemaphores); submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; } else { mHasDispatchedCompute = true; } submitInfo.signalSemaphoreCount = ARR_LEN(signalSemaphores); submitInfo.pSignalSemaphores = signalSemaphores; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mComputeCommandBuffer; if (vkQueueSubmit(mpGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) throw std::runtime_error("Failed to submit command buffer!"); }
传输队列提交代码
void Renderer::runTransfer() { VkSemaphore waitSemaphores[] = { mComputeFinishedSemaphoreTransfer }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT }; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; if (mHasDispatchedTransfer) [[likely]] { submitInfo.waitSemaphoreCount = ARR_LEN(waitSemaphores); submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; } else { mHasDispatchedTransfer = true; } submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mTransferCommandBuffer; if (vkQueueSubmit(mpTransferQueue, 1, &submitInfo, mTransferReadFence) != VK_SUCCESS) throw std::runtime_error("Failed to submit command buffer!"); }
主调用逻辑代码
constexpr uint32_t SINGLE_FRAME_RENDERING = 0; vkWaitForFences(mpLogicDevice, 1, &mTransferReadFence, VK_TRUE, UINT64_MAX); updateFrame(SINGLE_FRAME_RENDERING); vkResetFences(mpLogicDevice, 1, &mTransferReadFence); runCompute(); runTransfer(); auto imageIndex = prepareFrame(); // Submit the command buffer VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = { mImageAvailableSemaphores[SINGLE_FRAME_RENDERING], mComputeFinishedSemaphoreGraphics }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT }; submitInfo.waitSemaphoreCount = ARR_LEN(waitSemaphores); submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mGraphicsCommandBuffers[createCommandBuffersOnce() ? imageIndex : SINGLE_FRAME_RENDERING]; VkSemaphore signalSemaphores[] = { mRenderFinishedSemaphores[SINGLE_FRAME_RENDERING], mComputeReadySemaphore }; submitInfo.signalSemaphoreCount = ARR_LEN(signalSemaphores); submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(mpGraphicsQueue, 1, &submitInfo, mInFlightFences[SINGLE_FRAME_RENDERING]) != VK_SUCCESS) throw std::runtime_error("Failed to submit draw command buffer!"); submitFrame(signalSemaphores, imageIndex);
说明:updateFrame()为主机端缓冲区操作,prepareFrame()用于获取交换链图像,submitFrame()会调用vkQueuePresentKHR()
问题分析与修复方案
这个错误的核心是二进制信号量被重复触发信号,但中间没有任何队列对其执行等待操作。具体原因:
- 第一次执行
runTransfer()时,由于mHasDispatchedTransfer初始为false,跳过了等待mComputeFinishedSemaphoreTransfer的逻辑,导致该信号量被runCompute()触发后,没有任何队列等待它 - 下一轮循环中,
runCompute()再次尝试触发同一个信号量,验证层检测到信号量处于已触发状态且未被等待,抛出错误
修复步骤:
- 移除
runTransfer()中的分支逻辑:传输任务必须每次都等待计算完成的信号量,不能跳过。同时将等待阶段从VK_PIPELINE_STAGE_ALL_COMMANDS_BIT改为更精准的VK_PIPELINE_STAGE_TRANSFER_BIT,提升效率:
void Renderer::runTransfer() { VkSemaphore waitSemaphores[] = { mComputeFinishedSemaphoreTransfer }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_TRANSFER_BIT }; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; // 每次提交都必须等待计算完成的信号量 submitInfo.waitSemaphoreCount = ARR_LEN(waitSemaphores); submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mTransferCommandBuffer; if (vkQueueSubmit(mpTransferQueue, 1, &submitInfo, mTransferReadFence) != VK_SUCCESS) throw std::runtime_error("Failed to submit command buffer!"); }
- 确认信号量的生命周期:Vulkan二进制信号量在被队列等待后会自动重置为未触发状态,只要每次传输都正确等待,下一次计算触发信号量就不会有问题。
内容的提问来源于stack exchange,提问作者phoenixinwater

