You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过信号量调度传输队列上的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()再次尝试触发同一个信号量,验证层检测到信号量处于已触发状态且未被等待,抛出错误

修复步骤:

  1. 移除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!");
}
  1. 确认信号量的生命周期:Vulkan二进制信号量在被队列等待后会自动重置为未触发状态,只要每次传输都正确等待,下一次计算触发信号量就不会有问题。

内容的提问来源于stack exchange,提问作者phoenixinwater

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:50:20